Merge remote-tracking branch 'upstream/development' into Atom/santorac/MaterialPropertyRenameInMaterialComponent

This commit is contained in:
santorac
2021-10-22 09:27:24 -07:00
149 changed files with 4429 additions and 996 deletions
@@ -404,18 +404,18 @@ namespace AZ
bool AzslCompiler::ParseSrgPopulateRootConstantData(const rapidjson::Document& input, RootConstantData& rootConstantData) const
{
if (input.HasMember("InlineConstantBuffer"))
if (input.HasMember("RootConstantBuffer"))
{
const rapidjson::Value& rootConstantBufferValue = input["InlineConstantBuffer"];
AZ_Assert(rootConstantBufferValue.IsObject(), "InlineConstantBuffer is not an object");
const rapidjson::Value& rootConstantBufferValue = input["RootConstantBuffer"];
AZ_Assert(rootConstantBufferValue.IsObject(), "RootConstantBuffer is not an object");
for (rapidjson::Value::ConstMemberIterator itr = rootConstantBufferValue.MemberBegin(); itr != rootConstantBufferValue.MemberEnd(); ++itr)
{
AZStd::string_view rootConstantBufferMemberName = itr->name.GetString();
const rapidjson::Value& rootConstantBufferMemberValue = itr->value;
if (rootConstantBufferMemberName == "bufferForInlineConstants")
if (rootConstantBufferMemberName == "bufferForRootConstants")
{
AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForInlineConstants is not an object");
AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForRootConstants is not an object");
for (rapidjson::Value::ConstMemberIterator itr2 = rootConstantBufferMemberValue.MemberBegin(); itr2 != rootConstantBufferMemberValue.MemberEnd(); ++itr2)
{
@@ -442,14 +442,14 @@ namespace AZ
}
}
}
else if (rootConstantBufferMemberName == "inputsForInlineConstants")
else if (rootConstantBufferMemberName == "inputsForRootConstants")
{
AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForInlineConstants is not an array");
AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForRootConstants is not an array");
for (rapidjson::Value::ConstValueIterator itr2 = rootConstantBufferMemberValue.Begin(); itr2 != rootConstantBufferMemberValue.End(); ++itr2)
{
const rapidjson::Value& rootConstantBufferValue2 = *itr2;
AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForInlineConstants is not an object");
AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForRootConstants is not an object");
SrgConstantData rootConstantInputs;
@@ -81,7 +81,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 104; // ATOM-15871
shaderAssetBuilderDescriptor.m_version = 105; // [AZSL] Changing inlineConstant to rootConstant keyword work.
// .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@@ -96,7 +96,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 25; // ATOM-15871
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -183,7 +183,7 @@ namespace AZ
// access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData(
outcomes[AzslSubProducts::srg].GetValue(),
rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section)
{
AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed;
@@ -561,7 +561,7 @@ namespace AZ
// Access the root constants reflection
if (!azslCompiler.ParseSrgPopulateRootConstantData(
jsonOutcome.GetValue(),
rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section)
rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section)
{
AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to obtain root constant data reflection");
return false;
@@ -48,6 +48,14 @@
}
]
},
"Supervariants":
[
{
"Name": "",
"PlusArguments": "--no-alignment-validation"
}
],
"DrawList" : "forward"
}
@@ -49,5 +49,13 @@
]
},
"Supervariants":
[
{
"Name": "",
"PlusArguments": "--no-alignment-validation"
}
],
"DrawList" : "forward"
}
@@ -469,12 +469,12 @@
"Path": "Passes/OpaqueParent.pass"
},
{
"Name": "ThumbnailPipeline",
"Path": "Passes/ThumbnailPipeline.pass"
"Name": "ToolsPipeline",
"Path": "Passes/ToolsPipeline.pass"
},
{
"Name": "ThumbnailPipelineRenderToTexture",
"Path": "Passes/ThumbnailPipelineRenderToTexture.pass"
"Name": "ToolsPipelineRenderToTexture",
"Path": "Passes/ToolsPipelineRenderToTexture.pass"
},
{
"Name": "TransparentParentTemplate",
@@ -4,7 +4,7 @@
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "ThumbnailPipeline",
"Name": "ToolsPipeline",
"PassClass": "ParentPass",
"Slots": [
{
@@ -4,7 +4,7 @@
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "ThumbnailPipelineRenderToTexture",
"Name": "ToolsPipelineRenderToTexture",
"PassClass": "RenderToTexturePass",
"PassData": {
"$type": "RenderToTexturePassData",
@@ -15,7 +15,7 @@
"PassRequests": [
{
"Name": "Pipeline",
"TemplateName": "ThumbnailPipeline",
"TemplateName": "ToolsPipeline",
"Connections": [
{
"LocalSlot": "SwapChainOutput",
@@ -37,7 +37,7 @@
[
{
"Name": "",
"PlusArguments": "",
"PlusArguments": "--no-alignment-validation",
"MinusArguments": "--strip-unused-srgs"
}
]
@@ -161,8 +161,8 @@ set(FILES
Passes/LutGeneration.pass
Passes/MainPipeline.pass
Passes/MainPipelineRenderToTexture.pass
Passes/ThumbnailPipeline.pass
Passes/ThumbnailPipelineRenderToTexture.pass
Passes/ToolsPipeline.pass
Passes/ToolsPipelineRenderToTexture.pass
Passes/MeshMotionVector.pass
Passes/ModulateTexture.pass
Passes/MorphTarget.pass
@@ -61,7 +61,7 @@ namespace AtomToolsFramework
AZ::RPI::RenderPipelineDescriptor pipelineDesc;
pipelineDesc.m_mainViewTagName = "MainCamera";
pipelineDesc.m_name = pipelineName;
pipelineDesc.m_rootPassTemplate = "MainPipelineRenderToTexture";
pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture";
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost
@@ -71,6 +71,12 @@
}
}
],
"FallbackConnections": [
{
"Input": "DepthLinearInput",
"Output": "DepthLinear"
}
],
"PassRequests": [
{
"Name": "HairGlobalShapeConstraintsComputePass",
@@ -12,7 +12,8 @@
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{ // used for copy from MSAA to regular RT
{
// used for copy from MSAA to regular RT
"Name": "RenderTargetInputOnly",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
@@ -29,7 +30,7 @@
// If DepthLinear is not availbale - connect to another viewport (non MSAA) image.
{
"Name": "DepthLinearInput",
"SlotType": "InputOutput"
"SlotType": "Input"
},
{
"Name": "DepthLinear",
@@ -71,6 +72,12 @@
}
}
],
"FallbackConnections": [
{
"Input": "DepthLinearInput",
"Output": "DepthLinear"
}
],
"PassRequests": [
{
"Name": "HairGlobalShapeConstraintsComputePass",
@@ -257,7 +264,8 @@
"Attachment": "HairColorRenderTarget"
}
},
{ // The final render target - this is MSAA mode RT - would it be cheaper to
{
// The final render target - this is MSAA mode RT - would it be cheaper to
// use non-MSAA and then copy?
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {
@@ -340,7 +348,8 @@
"TemplateName": "HairShortCutResolveColorPassTemplate",
"Enabled": true,
"Connections": [
{ // The final render target - this is MSAA mode RT - would it be cheaper to
{
// The final render target - this is MSAA mode RT - would it be cheaper to
// use non-MSAA and then copy?
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {
@@ -144,25 +144,11 @@ namespace AZ
void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable)
{
return;
// [To Do] - This part should be enabled (remove the return) to reduce overhead
// when Hair is disabled / doesn't exist in the scene.
// Currently it might break features such as fog that depend on the output and for some
// reason doesn't quite work for ShortCut.
// The current overhead is minimal (< 0.1 msec) and this Gem is disabled by default.
/*
if (!m_initialized)
{
return;
}
RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName);
if (desiredPass)
{
desiredPass->SetEnabled(enable);
}
*/
}
bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance<HairRenderObject> renderObject)
@@ -184,15 +170,13 @@ namespace AZ
void HairFeatureProcessor::UpdateHairSkinning()
{
// Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..)
for (auto objIter = m_hairRenderObjects.begin(); objIter != m_hairRenderObjects.end(); ++objIter)
// Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..)
for (auto& hairRenderObject : m_hairRenderObjects)
{
if (!objIter->get()->IsEnabled())
if (hairRenderObject->IsEnabled())
{
return;
hairRenderObject->Update();
}
objIter->get()->Update();
}
}
+1
View File
@@ -209,6 +209,7 @@ namespace Blast
AZStd::shared_ptr<Physics::Shape>(
const Physics::ColliderConfiguration&, const Physics::ShapeConfiguration&));
MOCK_METHOD1(ReleaseNativeMeshObject, void(void*));
MOCK_METHOD1(ReleaseNativeHeightfieldObject, void(void*));
MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration&));
MOCK_METHOD0(GetDefaultMaterial, AZStd::shared_ptr<Physics::Material>());
MOCK_METHOD1(
@@ -597,7 +597,7 @@ namespace EMotionFX
// reset several settings to rewind the motion instance
motionInstance->ResetTimes();
motionInstance->SetIsFrozen(false);
SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32);
SetSyncIndex(animGraphInstance, InvalidIndex);
uniqueData->SetCurrentPlayTime(motionInstance->GetCurrentTime());
uniqueData->SetDuration(motionInstance->GetDuration());
uniqueData->SetPreSyncTime(uniqueData->GetCurrentPlayTime());
@@ -213,12 +213,12 @@ namespace EMotionFX
*/
virtual void SkipOutput([[maybe_unused]] AnimGraphInstance* animGraphInstance) {}
MCORE_INLINE float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); }
float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); }
virtual void SetCurrentPlayTime(AnimGraphInstance* animGraphInstance, float timeInSeconds) { FindOrCreateUniqueNodeData(animGraphInstance)->SetCurrentPlayTime(timeInSeconds); }
virtual float GetCurrentPlayTime(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetCurrentPlayTime(); }
MCORE_INLINE size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); }
MCORE_INLINE void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); }
size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); }
void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); }
virtual void SetPlaySpeed(AnimGraphInstance* animGraphInstance, float speedFactor) { FindOrCreateUniqueNodeData(animGraphInstance)->SetPlaySpeed(speedFactor); }
virtual float GetPlaySpeed(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetPlaySpeed(); }
@@ -51,52 +51,52 @@ namespace EMotionFX
void Init(AnimGraphInstance* animGraphInstance, AnimGraphNode* node);
void Init(AnimGraphNodeData* nodeData);
MCORE_INLINE AnimGraphNode* GetNode() const { return reinterpret_cast<AnimGraphNode*>(m_object); }
MCORE_INLINE void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast<AnimGraphObject*>(node); }
AnimGraphNode* GetNode() const { return reinterpret_cast<AnimGraphNode*>(m_object); }
void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast<AnimGraphObject*>(node); }
MCORE_INLINE void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; }
MCORE_INLINE size_t GetSyncIndex() const { return m_syncIndex; }
void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; }
size_t GetSyncIndex() const { return m_syncIndex; }
MCORE_INLINE void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; }
MCORE_INLINE float GetCurrentPlayTime() const { return m_currentTime; }
void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; }
float GetCurrentPlayTime() const { return m_currentTime; }
MCORE_INLINE void SetPlaySpeed(float speed) { m_playSpeed = speed; }
MCORE_INLINE float GetPlaySpeed() const { return m_playSpeed; }
void SetPlaySpeed(float speed) { m_playSpeed = speed; }
float GetPlaySpeed() const { return m_playSpeed; }
MCORE_INLINE void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; }
MCORE_INLINE float GetDuration() const { return m_duration; }
void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; }
float GetDuration() const { return m_duration; }
MCORE_INLINE void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; }
MCORE_INLINE float GetPreSyncTime() const { return m_preSyncTime; }
void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; }
float GetPreSyncTime() const { return m_preSyncTime; }
MCORE_INLINE void SetGlobalWeight(float weight) { m_globalWeight = weight; }
MCORE_INLINE float GetGlobalWeight() const { return m_globalWeight; }
void SetGlobalWeight(float weight) { m_globalWeight = weight; }
float GetGlobalWeight() const { return m_globalWeight; }
MCORE_INLINE void SetLocalWeight(float weight) { m_localWeight = weight; }
MCORE_INLINE float GetLocalWeight() const { return m_localWeight; }
void SetLocalWeight(float weight) { m_localWeight = weight; }
float GetLocalWeight() const { return m_localWeight; }
MCORE_INLINE uint8 GetInheritFlags() const { return m_inheritFlags; }
uint8 GetInheritFlags() const { return m_inheritFlags; }
MCORE_INLINE bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; }
MCORE_INLINE void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; }
MCORE_INLINE void ClearInheritFlags() { m_inheritFlags = 0; }
bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; }
void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; }
void ClearInheritFlags() { m_inheritFlags = 0; }
MCORE_INLINE uint8 GetPoseRefCount() const { return m_poseRefCount; }
MCORE_INLINE void IncreasePoseRefCount() { m_poseRefCount++; }
MCORE_INLINE void DecreasePoseRefCount() { m_poseRefCount--; }
MCORE_INLINE void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; }
uint8 GetPoseRefCount() const { return m_poseRefCount; }
void IncreasePoseRefCount() { m_poseRefCount++; }
void DecreasePoseRefCount() { m_poseRefCount--; }
void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; }
MCORE_INLINE uint8 GetRefDataRefCount() const { return m_refDataRefCount; }
MCORE_INLINE void IncreaseRefDataRefCount() { m_refDataRefCount++; }
MCORE_INLINE void DecreaseRefDataRefCount() { m_refDataRefCount--; }
MCORE_INLINE void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; }
uint8 GetRefDataRefCount() const { return m_refDataRefCount; }
void IncreaseRefDataRefCount() { m_refDataRefCount++; }
void DecreaseRefDataRefCount() { m_refDataRefCount--; }
void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; }
MCORE_INLINE void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; }
MCORE_INLINE AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; }
void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; }
AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; }
MCORE_INLINE const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; }
MCORE_INLINE AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; }
MCORE_INLINE void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; }
const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; }
AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; }
void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; }
bool GetIsMirrorMotion() const { return m_isMirrorMotion; }
void SetIsMirrorMotion(bool newValue) { m_isMirrorMotion = newValue; }
@@ -326,7 +326,7 @@ namespace EMotionFX
uniqueData->m_totalSeconds = 0.0f;
uniqueData->m_blendProgress = 0.0f;
m_targetNode->SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32);
m_targetNode->SetSyncIndex(animGraphInstance, InvalidIndex);
// Trigger action
for (AnimGraphTriggerAction* action : m_actionSetup.GetActions())
@@ -46,6 +46,7 @@ namespace Physics
}
MOCK_METHOD2(CreateShape, AZStd::shared_ptr<Physics::Shape>(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration));
MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject));
MOCK_METHOD1(ReleaseNativeHeightfieldObject, void(void* nativeMeshObject));
MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration& materialConfiguration));
MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount));
MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3* vertices, AZ::u32 vertexCount, AZStd::vector<AZ::u8>& result));
+11 -1
View File
@@ -107,7 +107,16 @@ endif()
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME GradientSignal.Mocks HEADERONLY
NAMESPACE Gem
FILES_CMAKE
gradientsignal_mocks_files.cmake
INCLUDE_DIRECTORIES
INTERFACE
Mocks
)
ly_add_target(
NAME GradientSignal.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
@@ -122,6 +131,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest
Gem::GradientSignal.Static
Gem::LmbrCentral
Gem::GradientSignal.Mocks
)
ly_add_googletest(
NAME Gem::GradientSignal.Tests
@@ -0,0 +1,34 @@
/*
* 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 <gmock/gmock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
namespace UnitTest
{
class MockGradientRequests
: private GradientSignal::GradientRequestBus::Handler
{
public:
MockGradientRequests(AZ::EntityId entityId)
{
GradientSignal::GradientRequestBus::Handler::BusConnect(entityId);
}
~MockGradientRequests()
{
GradientSignal::GradientRequestBus::Handler::BusDisconnect();
}
MOCK_CONST_METHOD1(GetValue, float(const GradientSignal::GradientSampleParams&));
MOCK_CONST_METHOD1(IsEntityInHierarchy, bool(const AZ::EntityId&));
};
} // namespace UnitTest
@@ -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
#
#
set(FILES
Mocks/GradientSignal/Ebuses/MockGradientRequestBus.h
)
@@ -85,6 +85,10 @@ namespace ImGui
//! Calculate the min and maximum values for the present samples.
void CalcMinMaxValues(float& outMin, float& outMax);
//! Set/get color used by either the lines in case ViewType is Lines or bars in case or Histogram.
void SetBarLineColor(const ImColor& color) { m_barLineColor = color; }
ImColor GetBarLineColor() const { return m_barLineColor; }
private:
// Set the Max Size and clear the container
void SetMaxSize(int size);
@@ -99,6 +103,7 @@ namespace ImGui
bool m_dispalyOverlays;
ScaleMode m_scaleMode; //! Determines if the vertical range of the histogram will be manually specified, auto-expanded or automatically scaled based on the samples.
float m_autoScaleSpeed = 0.05f; //! Indicates how fast the min max values and the visible vertical range are adapting to new samples.
ImColor m_barLineColor = ImColor(66, 166, 178); //! Color used by either the lines in case ViewType is Lines or bars in case or Histogram.
bool m_collapsed;
bool m_drawMostRecentValueText;
};
@@ -147,6 +147,8 @@ namespace ImGui
float imGuiHistoWidgetHeight = m_collapsed ? histogramHeight : (histogramHeight - 15);
if (GetSize() > 0)
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, m_barLineColor.Value);
switch (m_viewType)
{
default:
@@ -160,6 +162,8 @@ namespace ImGui
ImGui::PlotLines(AZStd::string::format("##%s_lines", m_histogramName.c_str()).c_str(), ImGui::LYImGuiUtils::s_histogramContainerGetter, this, GetSize(), 0, m_histogramName.c_str(), m_minScale, m_maxScale, ImVec2(histogramWidth - 10, imGuiHistoWidgetHeight));
break;
}
ImGui::PopStyleColor();
}
@@ -15,12 +15,6 @@
namespace LmbrCentral
{
/// Type ID for the AxisAlignedBoxShapeComponent
static const AZ::Uuid AxisAlignedBoxShapeComponentTypeId = "{641D817E-1BC6-406A-BBB2-218541808E45}";
/// Type ID for the EditorAxisAlignedBoxShapeComponent
static const AZ::Uuid EditorAxisAlignedBoxShapeComponentTypeId = "{8C027DF6-E157-4159-9BF8-F1B925466F1F}";
/// Provide a Component interface for AxisAlignedBoxShape functionality.
class AxisAlignedBoxShapeComponent
: public AZ::Component
@@ -24,6 +24,12 @@ namespace LmbrCentral
/// Type ID for the BoxShapeConfig
static const AZ::Uuid BoxShapeConfigTypeId = "{F034FBA2-AC2F-4E66-8152-14DFB90D6283}";
/// Type ID for the AxisAlignedBoxShapeComponent
static const AZ::Uuid AxisAlignedBoxShapeComponentTypeId = "{641D817E-1BC6-406A-BBB2-218541808E45}";
/// Type ID for the EditorAxisAlignedBoxShapeComponent
static const AZ::Uuid EditorAxisAlignedBoxShapeComponentTypeId = "{8C027DF6-E157-4159-9BF8-F1B925466F1F}";
/// Configuration data for BoxShapeComponent
class BoxShapeConfig
: public ShapeComponentConfig
@@ -57,6 +57,14 @@ namespace Multiplayer
const AzNetworking::PacketEncodingBuffer& correction
) override;
//! Forcibly enables ProcessInput to execute on the entity.
//! Note that this function is quite dangerous and should normally never be used
void ForceEnableAutonomousUpdate();
//! Forcibly disables ProcessInput from executing on the entity.
//! Note that this function is quite dangerous and should normally never be used
void ForceDisableAutonomousUpdate();
//! Return true if we're currently migrating from one host to another.
//! @return boolean true if we're currently migrating from one host to another
bool IsMigrating() const;
@@ -32,7 +32,7 @@ namespace Multiplayer
using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using EntityDirtiedEvent = AZ::Event<>;
using EntitySyncRewindEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&, AzNetworking::ConnectionId>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&>;
using EntityPreRenderEvent = AZ::Event<float>;
using EntityCorrectionEvent = AZ::Event<>;
@@ -113,7 +113,7 @@ namespace Multiplayer
void MarkDirty();
void NotifyLocalChanges();
void NotifySyncRewindState();
void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId);
void NotifyServerMigration(const HostId& remoteHostId);
void NotifyPreRender(float deltaTime);
void NotifyCorrection();
@@ -58,11 +58,10 @@ namespace Multiplayer
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
protected:
//! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
//! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
AZ::ChildChangedEvent::Handler m_childChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
@@ -80,5 +79,8 @@ namespace Multiplayer
bool m_isHierarchyEnabled = true;
void NotifyChildrenHierarchyDisbanded();
AzNetworking::ConnectionId m_previousOwningConnectionId = AzNetworking::InvalidConnectionId;
void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override;
};
}
@@ -30,7 +30,6 @@ namespace Multiplayer
{
friend class NetworkHierarchyChildComponent;
friend class NetworkHierarchyRootComponentController;
friend class ServerToClientReplicationWindow;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
@@ -61,10 +60,9 @@ namespace Multiplayer
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
protected:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
@@ -81,16 +79,19 @@ namespace Multiplayer
//! 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.
//! Builds the hierarchy using breadth-first iterative method.
void InternalBuildHierarchyList(AZ::Entity* underEntity);
void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity);
void SetRootForEntity(AZ::Entity* previousKnownRoot, AZ::Entity* newRoot, const AZ::Entity* childEntity);
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true;
AzNetworking::ConnectionId m_previousOwningConnectionId = AzNetworking::InvalidConnectionId;
void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override;
friend class HierarchyBenchmarkBase;
};
@@ -45,7 +45,7 @@ namespace Multiplayer
using ClientMigrationStartEvent = AZ::Event<ClientInputId>;
using ClientMigrationEndEvent = AZ::Event<>;
using ClientDisconnectedEvent = AZ::Event<>;
using NotifyClientMigrationEvent = AZ::Event<const HostId&, uint64_t, ClientInputId>;
using NotifyClientMigrationEvent = AZ::Event<AzNetworking::ConnectionId, const HostId&, uint64_t, ClientInputId, NetEntityId>;
using NotifyEntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&>;
using ConnectionAcquiredEvent = AZ::Event<MultiplayerAgentDatum>;
using ServerAcceptanceReceivedEvent = AZ::Event<>;
@@ -136,10 +136,12 @@ namespace Multiplayer
virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0;
//! Signals a NotifyClientMigrationEvent with the provided parameters.
//! @param hostId the host id of the host the client is migrating to
//! @param userIdentifier the user identifier the client will provide the new host to validate identity
//! @param lastClientInputId the last processed clientInputId by the current host
virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0;
//! @param connectionId the connection id of the client that is migrating
//! @param hostId the host id of the host the client is migrating to
//! @param userIdentifier the user identifier the client will provide the new host to validate identity
//! @param lastClientInputId the last processed clientInputId by the current host
//! @param controlledEntityId the entityId of the clients autonomous entity
virtual void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) = 0;
//! Signals a NotifyEntityMigrationEvent with the provided parameters.
//! @param entityHandle the network entity handle of the entity being migrated
@@ -181,6 +183,18 @@ namespace Multiplayer
//! @return pointer to the filtered entity manager, or nullptr if not set
virtual IFilterEntityManager* GetFilterEntityManager() = 0;
//! Registers a temp userId to allow a host to look up a players controlled entity in the event of a rejoin or migration event.
//! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts
//! @param controlledEntityId the controlled entityId of the players autonomous entity
virtual void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) = 0;
//! Completes a client migration event by informing the appropriate client to migrate between hosts.
//! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts
//! @param connectionId the connection id of the player being migrated
//! @param publicHostId the public address of the new host the client should connect to
//! @param migratedClientInputId the last clientInputId processed prior to migration
virtual void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) = 0;
//! Enables or disables automatic instantiation of netbound entities.
//! This setting is controlled by the networking layer and should not be touched
//! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server
@@ -29,7 +29,7 @@ namespace Multiplayer
using HostId = AzNetworking::IpAddress;
static const HostId InvalidHostId = HostId();
AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint32_t);
AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint64_t);
static constexpr NetEntityId InvalidNetEntityId = static_cast<NetEntityId>(-1);
AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t);
@@ -68,6 +68,7 @@ namespace Multiplayer
Server, // A simulated proxy on a server
Authority // An authoritative proxy on a server (full authority)
};
const char* GetEnumString(NetEntityRole value);
enum class ComponentSerializationType : uint8_t
{
@@ -113,6 +114,24 @@ namespace Multiplayer
bool Serialize(AzNetworking::ISerializer& serializer);
};
inline const char* GetEnumString(NetEntityRole value)
{
switch (value)
{
case NetEntityRole::InvalidRole:
return "InvalidRole";
case NetEntityRole::Client:
return "Client";
case NetEntityRole::Autonomous:
return "Autonomous";
case NetEntityRole::Server:
return "Server";
case NetEntityRole::Authority:
return "Authority";
}
return "Unknown";
}
inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset)
: m_prefabName(name)
, m_entityOffset(entityOffset)
@@ -57,7 +57,6 @@ namespace Multiplayer
EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode);
~EntityReplicationManager() = default;
void SetRemoteHostId(const HostId& hostId);
const HostId& GetRemoteHostId() const;
void ActivatePendingEntities();
@@ -43,8 +43,7 @@ namespace Multiplayer
//! Constructor for an entity delete message.
//! @param entityId the networkId of the entity being deleted
//! @param isMigrated whether or not the entity is being migrated or deleted
//! @param takeOwnership true if the remote replicator should take ownership of the entity
explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated, bool takeOwnership);
explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated);
NetworkEntityUpdateMessage& operator =(NetworkEntityUpdateMessage&& rhs);
NetworkEntityUpdateMessage& operator =(const NetworkEntityUpdateMessage& rhs);
@@ -71,10 +70,6 @@ namespace Multiplayer
//! @return whether or not the entity was migrated
bool GetWasMigrated() const;
//! Gets the current value of TakeOwnership.
//! @return the current value of TakeOwnership
bool GetTakeOwnership() const;
//! Gets the current value of HasValidPrefabId.
//! @return the current value of HasValidPrefabId
bool GetHasValidPrefabId() const;
@@ -110,7 +105,6 @@ namespace Multiplayer
NetEntityId m_entityId = InvalidNetEntityId;
bool m_isDelete = false;
bool m_wasMigrated = false;
bool m_takeOwnership = false;
bool m_hasValidPrefabId = false;
PrefabEntityId m_prefabEntityId;
@@ -13,9 +13,9 @@
<Include File="Multiplayer/MultiplayerTypes.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInput.h"/>
<Include File="Source/NetworkInput/NetworkInputArray.h"/>
<Include File="Source/NetworkInput/NetworkInputHistory.h"/>
<Include File="Source/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputArray.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputHistory.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Server" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="false" />
@@ -9,6 +9,7 @@
<Packet Name="Connect" HandshakePacket="true" Desc="Client connection packet, on success the server will reply with an Accept">
<Member Type="uint16_t" Name="networkProtocolVersion" Init="0" />
<Member Type="uint64_t" Name="temporaryUserId" Init="0" />
<Member Type="Multiplayer::LongNetworkString" Name="ticket" />
</Packet>
@@ -8,7 +8,7 @@
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Include File="Source/NetworkInput/NetworkInputChild.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputChild.h"/>
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
@@ -354,6 +354,16 @@ namespace Multiplayer
}
}
void LocalPredictionPlayerInputComponentController::ForceEnableAutonomousUpdate()
{
m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
}
void LocalPredictionPlayerInputComponentController::ForceDisableAutonomousUpdate()
{
m_autonomousUpdateEvent.RemoveFromQueue();
}
bool LocalPredictionPlayerInputComponentController::IsMigrating() const
{
return m_lastMigratedInputId != ClientInputId{ 0 };
@@ -394,9 +394,9 @@ namespace Multiplayer
m_syncRewindEvent.Signal();
}
void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId)
void NetBindComponent::NotifyServerMigration(const HostId& remoteHostId)
{
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
m_entityServerMigrationEvent.Signal(m_netEntityHandle, remoteHostId);
}
void NetBindComponent::NotifyPreRender(float deltaTime)
@@ -129,34 +129,48 @@ namespace Multiplayer
handler.Connect(m_networkHierarchyLeaveEvent);
}
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot)
{
if (m_rootEntity != hierarchyRoot)
if (newHierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (m_rootEntity != newHierarchyRoot)
{
m_rootEntity = newHierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId);
}
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
else if ((previousHierarchyRoot && m_rootEntity == previousHierarchyRoot) || !previousHierarchyRoot)
{
m_rootEntity = nullptr;
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();
}
controller->SetHierarchyRoot(InvalidNetEntityId);
}
if (m_rootEntity == nullptr)
{
NotifyChildrenHierarchyDisbanded();
}
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_networkHierarchyLeaveEvent.Signal();
NotifyChildrenHierarchyDisbanded();
}
}
void NetworkHierarchyChildComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId)
{
NetworkHierarchyChildComponentBase::SetOwningConnectionId(connectionId);
if (IsHierarchicalChild() == false)
{
m_previousOwningConnectionId = connectionId;
}
}
@@ -180,14 +194,18 @@ namespace Multiplayer
if (m_rootEntity != newRoot)
{
m_rootEntity = newRoot;
m_previousOwningConnectionId = GetNetBindComponent()->GetOwningConnectionId();
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
else
{
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_isHierarchyEnabled = false;
m_rootEntity = nullptr;
m_networkHierarchyLeaveEvent.Signal();
}
}
@@ -203,11 +221,11 @@ namespace Multiplayer
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr);
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr, nullptr);
}
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr);
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr, nullptr);
}
}
}
@@ -107,7 +107,7 @@ namespace Multiplayer
{
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{
SetRootForEntity(nullptr, childEntity);
SetRootForEntity(GetEntity(), nullptr, childEntity);
}
}
}
@@ -209,7 +209,7 @@ namespace Multiplayer
auto [rootComponent, childComponent] = GetHierarchyComponents(parentEntity);
if (rootComponent == nullptr && childComponent == nullptr)
{
RebuildHierarchy();
SetRootForEntity(nullptr, nullptr, GetEntity());
}
else
{
@@ -219,7 +219,7 @@ namespace Multiplayer
else
{
// Detached from parent
RebuildHierarchy();
SetRootForEntity(nullptr, nullptr, GetEntity());
}
}
@@ -247,14 +247,14 @@ namespace Multiplayer
{
// This is a newly added entity to the network hierarchy.
hierarchyChanged = true;
SetRootForEntity(GetEntity(), currentEntity);
SetRootForEntity(nullptr, GetEntity(), currentEntity);
}
}
// These entities were removed since last rebuild.
for (const AZ::Entity* previousEntity : previousEntities)
{
SetRootForEntity(nullptr, previousEntity);
SetRootForEntity(GetEntity(), nullptr, previousEntity);
}
if (!previousEntities.empty())
@@ -307,45 +307,66 @@ namespace Multiplayer
}
}
void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity)
void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* previousKnownRoot, AZ::Entity* newRoot, const AZ::Entity* childEntity)
{
auto [hierarchyRootComponent, hierarchyChildComponent] = GetHierarchyComponents(childEntity);
if (hierarchyChildComponent)
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root);
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot);
}
else if (hierarchyRootComponent)
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root);
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot);
}
}
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
if (newHierarchyRoot)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (hierarchyRoot)
if (m_rootEntity != newHierarchyRoot)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId());
controller->SetHierarchyRoot(netRootId);
}
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
m_rootEntity = newHierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyRootComponentController* controller = static_cast<NetworkHierarchyRootComponentController*>(GetController());
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId);
}
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
if (m_rootEntity == nullptr)
else if ((previousHierarchyRoot && m_rootEntity == previousHierarchyRoot) || !previousHierarchyRoot)
{
m_rootEntity = nullptr;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyRootComponentController* controller = static_cast<NetworkHierarchyRootComponentController*>(GetController());
controller->SetHierarchyRoot(InvalidNetEntityId);
}
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_networkHierarchyLeaveEvent.Signal();
// We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy.
RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId)
{
NetworkHierarchyRootComponentBase::SetOwningConnectionId(connectionId);
if (IsHierarchicalChild() == false)
{
m_previousOwningConnectionId = connectionId;
}
}
NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent)
: NetworkHierarchyRootComponentControllerBase(parent)
{
@@ -370,7 +391,7 @@ namespace Multiplayer
void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime)
{
NetworkHierarchyRootComponent& component = GetParent();
if(!component.IsHierarchicalRoot())
if (!component.IsHierarchicalRoot())
{
return;
}
@@ -386,7 +407,7 @@ namespace Multiplayer
for (AZ::Entity* child : entities)
{
if(child == component.GetEntity())
if (child == component.GetEntity())
{
continue; // Avoid infinite recursion
}
@@ -23,22 +23,16 @@ namespace Multiplayer
ServerToClientConnectionData::ServerToClientConnectionData
(
AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener,
NetworkEntityHandle controlledEntity
AzNetworking::IConnectionListener& connectionListener
)
: m_connection(connection)
, m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); })
, m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); })
, m_controlledEntity(controlledEntity)
, m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
{
OnControlledEntityMigration(entityHandle, remoteHostId);
})
, m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient)
{
NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent();
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount);
m_entityReplicationManager.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs);
}
@@ -54,6 +48,20 @@ namespace Multiplayer
m_controlledEntityRemovedHandler.Disconnect();
}
void ServerToClientConnectionData::SetControlledEntity(NetworkEntityHandle primaryPlayerEntity)
{
m_controlledEntityRemovedHandler.Disconnect();
m_controlledEntityMigrationHandler.Disconnect();
m_controlledEntity = primaryPlayerEntity;
NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent();
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
}
ConnectionDataType ServerToClientConnectionData::GetConnectionDataType() const
{
return ConnectionDataType::ServerToClient;
@@ -94,8 +102,7 @@ namespace Multiplayer
void ServerToClientConnectionData::OnControlledEntityMigration
(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle,
[[maybe_unused]] const HostId& remoteHostId,
[[maybe_unused]] AzNetworking::ConnectionId connectionId
const HostId& remoteHostId
)
{
ClientInputId migratedClientInputId = ClientInputId{ 0 };
@@ -109,14 +116,12 @@ namespace Multiplayer
}
// Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration
const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64();
const uint64_t temporaryUserIdentifier = AzNetworking::CryptoRand64();
// Tell the new host that a client is about to (re)join
GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId);
// Tell the client who to join
MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId);
GetConnection()->SendReliablePacket(clientMigration);
GetMultiplayer()->SendNotifyClientMigrationEvent(GetConnection()->GetConnectionId(), remoteHostId, temporaryUserIdentifier, migratedClientInputId, m_controlledEntity.GetNetEntityId());
// We need to send a MultiplayerPackets::ClientMigration packet to complete this process
// This happens inside MultiplayerSystemComponent, once we're certain the remote host has appropriately prepared
m_controlledEntity = NetworkEntityHandle();
m_canSendUpdates = false;
@@ -20,11 +20,12 @@ namespace Multiplayer
ServerToClientConnectionData
(
AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener,
NetworkEntityHandle controlledEntity
AzNetworking::IConnectionListener& connectionListener
);
~ServerToClientConnectionData() override;
void SetControlledEntity(NetworkEntityHandle primaryPlayerEntity);
//! IConnectionData interface
//! @{
ConnectionDataType GetConnectionDataType() const override;
@@ -44,7 +45,7 @@ namespace Multiplayer
private:
void OnControlledEntityRemove();
void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId);
void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId);
void OnGameplayStarted();
EntityReplicationManager m_entityReplicationManager;
@@ -18,7 +18,6 @@ namespace Multiplayer
m_canSendUpdates = canSendUpdates;
}
inline NetworkEntityHandle ServerToClientConnectionData::GetPrimaryPlayerEntity()
{
return m_controlledEntity;
@@ -74,7 +74,7 @@ namespace Multiplayer
{
ImGui::Text("%s", entity->GetId().ToString().c_str());
ImGui::NextColumn();
ImGui::Text("%u", GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId()));
ImGui::Text("%llu", static_cast<AZ::u64>(GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId())));
ImGui::NextColumn();
ImGui::Text("%s", entity->GetName().c_str());
ImGui::NextColumn();
@@ -28,24 +28,29 @@ namespace Multiplayer
->Version(1);
}
}
void MultiplayerDebugSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent"));
}
void MultiplayerDebugSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
;
}
void MultiplayerDebugSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile)
{
incompatbile.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent"));
}
void MultiplayerDebugSystemComponent::Activate()
{
#ifdef IMGUI_ENABLED
ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
#endif
}
void MultiplayerDebugSystemComponent::Deactivate()
{
#ifdef IMGUI_ENABLED
@@ -75,6 +80,7 @@ namespace Multiplayer
ImGui::EndMenu();
}
}
void AccumulatePerSecondValues(const MultiplayerStats& stats, const MultiplayerStats::Metric& metric, float& outCallsPerSecond, float& outBytesPerSecond)
{
uint64_t summedCalls = 0;
@@ -107,6 +113,7 @@ namespace Multiplayer
ImGui::Text("%11.2f", bytesPerSecond);
return open;
}
bool DrawSummaryRow(const char* name, const MultiplayerStats& stats)
{
const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics();
@@ -123,6 +130,7 @@ namespace Multiplayer
AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond);
return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond);
}
bool DrawComponentRow(const char* name, const MultiplayerStats& stats, NetComponentId netComponentId)
{
const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId);
@@ -139,6 +147,7 @@ namespace Multiplayer
AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond);
return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond);
}
void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId)
{
MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry();
@@ -503,4 +512,3 @@ void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth)
AZ::Interface<Multiplayer::IMultiplayerDebug>::Get()->HideEntityBandwidthDebugOverlay();
}
}
@@ -8,6 +8,7 @@
#include <Multiplayer/MultiplayerConstants.h>
#include <Multiplayer/Components/MultiplayerComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <MultiplayerSystemComponent.h>
#include <ConnectionData/ClientToServerConnectionData.h>
#include <ConnectionData/ServerToClientConnectionData.h>
@@ -76,6 +77,7 @@ namespace Multiplayer
"The address of the remote server or host to connect to");
AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic");
AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic");
AZ_CVAR(uint16_t, sv_portRange, 999, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The range of ports the host will incrementally attempt to bind to when initializing");
AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load");
AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking");
AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server");
@@ -168,6 +170,7 @@ namespace Multiplayer
AZ::ConsoleFunctorFlags flags,
AZ::ConsoleInvokedFrom invokedFrom
) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); })
, m_autonomousEntityReplicatorCreatedHandler([this]([[maybe_unused]] NetEntityId netEntityId) { OnAutonomousEntityReplicatorCreated(); })
{
AZ::Interface<IMultiplayer>::Register(this);
}
@@ -205,8 +208,23 @@ namespace Multiplayer
bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated)
{
InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer);
return m_networkInterface->Listen(port);
if (port != sv_port)
{
sv_port = port;
}
const uint16_t maxPort = sv_port + sv_portRange;
while (sv_port <= maxPort)
{
if (m_networkInterface->Listen(sv_port))
{
InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer);
return true;
}
AZLOG_WARN("Failed to start listening on port %u, port is in use?", static_cast<uint32_t>(sv_port));
sv_port = sv_port + 1;
}
return false;
}
bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port)
@@ -328,6 +346,11 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
if (bg_multiplayerDebugDraw)
{
m_networkEntityManager.DebugDraw();
}
const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
const AZ::TimeMs serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs);
const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f;
@@ -412,11 +435,6 @@ namespace Multiplayer
{
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
}
if (bg_multiplayerDebugDraw)
{
m_networkEntityManager.DebugDraw();
}
}
int MultiplayerSystemComponent::GetTickOrder()
@@ -487,17 +505,39 @@ namespace Multiplayer
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
return true;
}
}
}
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str());
// Hosts will spawn a new default player prefab for the user that just connected
if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
{
// We use a temporary userId over the clients address so we can maintain client lookups even in the event of wifi handoff
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(packet.GetTemporaryUserId());
EnableAutonomousControl(controlledEntity, connection->GetConnectionId());
ServerToClientConnectionData* connectionData = reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData());
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
connectionData->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
connectionData->SetControlledEntity(controlledEntity);
// If this is a migrate or rejoin, immediately ready the connection for updates
if (packet.GetTemporaryUserId() != 0)
{
connectionData->SetCanSendUpdates(true);
}
}
if (connection->SendReliablePacket(MultiplayerPackets::Accept(sv_map)))
{
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
// Sync our console
ConsoleReplicator consoleReplicator(connection);
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
if (packet.GetTemporaryUserId() == 0)
{
// Sync our console
ConsoleReplicator consoleReplicator(connection);
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
}
return true;
}
return false;
@@ -511,10 +551,26 @@ namespace Multiplayer
)
{
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
if (m_temporaryUserIdentifier == 0)
{
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
}
else
{
// Bypass map loading and immediately ready the connection for updates
IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
if (connectionData)
{
connectionData->SetCanSendUpdates(true);
// @nt: TODO - delete once dropped RPC problem fixed
// Connection has migrated, we are now waiting for the autonomous entity replicator to be created
connectionData->GetReplicationManager().AddAutonomousEntityReplicatorCreatedHandler(m_autonomousEntityReplicatorCreatedHandler);
}
}
m_serverAcceptanceReceivedEvent.Signal();
return true;
@@ -637,13 +693,17 @@ namespace Multiplayer
// Store the temporary user identifier so we can transmit it with our next Connect packet
// The new server will use this to re-attach our set of autonomous entities
m_temporaryUserIdentifier = packet.GetTemporaryUserIdentifier();
// Disconnect our existing server connection
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
AZLOG_INFO("Migrating to new server shard");
m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId());
m_networkInterface->Connect(packet.GetRemoteServerAddress());
if (m_networkInterface->Connect(packet.GetRemoteServerAddress()) == AzNetworking::InvalidConnectionId)
{
AZLOG_ERROR("Failed to connect to new host during client migration event");
}
return true;
}
@@ -673,7 +733,7 @@ namespace Multiplayer
providerTicket = m_pendingConnectionTickets.front();
m_pendingConnectionTickets.pop();
}
connection->SendReliablePacket(MultiplayerPackets::Connect(0, providerTicket.c_str()));
connection->SendReliablePacket(MultiplayerPackets::Connect(0, m_temporaryUserIdentifier, providerTicket.c_str()));
}
else
{
@@ -681,29 +741,10 @@ namespace Multiplayer
m_connectionAcquiredEvent.Signal(datum);
}
// Hosts will spawn a new default player prefab for the user that just connected
if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
{
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
for (auto& netEntity : entityList)
{
if (netEntity.Exists())
{
netEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId());
}
netEntity.Activate();
}
NetworkEntityHandle controlledEntity;
if (entityList.size() > 0)
{
controlledEntity = entityList[0];
}
connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity));
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
connection->SetUserData(new ServerToClientConnectionData(connection, *this));
}
else
{
@@ -725,9 +766,9 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnDisconnect(AzNetworking::IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint)
{
const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remote host disconnected";
const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remotely disconnected";
AZStd::string reasonString = ToString(reason);
AZLOG_INFO("%s due to %s from remote address: %s", endpointString, reasonString.c_str(), connection->GetRemoteAddress().GetString().c_str());
AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str());
// The client is disconnecting
if (GetAgentType() == MultiplayerAgentType::Client)
@@ -809,16 +850,8 @@ namespace Multiplayer
// Spawn the default player for this host since the host is also a player (not a dedicated server)
if (m_agentType == MultiplayerAgentType::ClientServer)
{
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
for (NetworkEntityHandle controlledEntity : entityList)
{
if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent())
{
controlledEntityNetBindComponent->SetAllowAutonomy(true);
}
controlledEntity.Activate();
}
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(0);
EnableAutonomousControl(controlledEntity, AzNetworking::InvalidConnectionId);
}
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
@@ -869,9 +902,9 @@ namespace Multiplayer
handler.Connect(m_shutdownEvent);
}
void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId)
void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId)
{
m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId);
m_notifyClientMigrationEvent.Signal(connectionId, hostId, userIdentifier, lastClientInputId, controlledEntityId);
}
void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
@@ -925,6 +958,22 @@ namespace Multiplayer
return m_filterEntityManager;
}
void MultiplayerSystemComponent::RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId)
{
m_playerRejoinData[temporaryUserIdentifier] = controlledEntityId;
}
void MultiplayerSystemComponent::CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId)
{
IConnection* connection = m_networkInterface->GetConnectionSet().GetConnection(connectionId);
if (connection != nullptr) // Make sure the player has not disconnected since the start of migration
{
// Tell the client who to join
MultiplayerPackets::ClientMigration clientMigration(publicHostId, temporaryUserIdentifier, migratedClientInputId);
connection->SendReliablePacket(clientMigration);
}
}
void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value)
{
m_spawnNetboundEntities = value;
@@ -1055,6 +1104,13 @@ namespace Multiplayer
m_cvarCommands.PushBackItem(AZStd::move(replicateString));
}
void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated()
{
m_autonomousEntityReplicatorCreatedHandler.Disconnect();
//m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 });
m_clientMigrationEndEvent.Signal();
}
void MultiplayerSystemComponent::ExecuteConsoleCommandList(IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands)
{
AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
@@ -1066,19 +1122,69 @@ namespace Multiplayer
}
}
INetworkEntityManager::EntityList MultiplayerSystemComponent::SpawnDefaultPlayerPrefab()
NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier)
{
const auto node = m_playerRejoinData.find(temporaryUserIdentifier);
if (node != m_playerRejoinData.end())
{
return m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second);
}
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str()));
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate);
return entityList;
for (NetworkEntityHandle subEntity : entityList)
{
subEntity.Activate();
}
NetworkEntityHandle controlledEntity;
if (!entityList.empty())
{
controlledEntity = entityList[0];
}
return controlledEntity;
}
void MultiplayerSystemComponent::EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId)
{
if (!entityHandle.Exists())
{
AZLOG_WARN("Attempting to enable autonomous control for an invalid entity");
return;
}
entityHandle.GetNetBindComponent()->SetOwningConnectionId(connectionId);
if (connectionId == InvalidConnectionId)
{
entityHandle.GetNetBindComponent()->SetAllowAutonomy(true);
}
auto* hierarchyComponent = entityHandle.FindComponent<NetworkHierarchyRootComponent>();
if (hierarchyComponent != nullptr)
{
for (AZ::Entity* subEntity : hierarchyComponent->GetHierarchicalEntities())
{
NetworkEntityHandle subEntityHandle = NetworkEntityHandle(subEntity);
NetBindComponent* subEntityNetBindComponent = subEntityHandle.GetNetBindComponent();
if (subEntityNetBindComponent != nullptr)
{
subEntityNetBindComponent->SetOwningConnectionId(connectionId);
if (connectionId == InvalidConnectionId)
{
subEntityNetBindComponent->SetAllowAutonomy(true);
}
}
}
}
}
void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
if (!AZ::Interface<IMultiplayer>::Get()->StartHosting(sv_port, sv_isDedicated))
{
AZLOG_ERROR("Failed to start listening on port %u, port is in use?", static_cast<uint32_t>(sv_port));
AZLOG_ERROR("Failed to start listening on any allocated port");
}
}
AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to");
@@ -123,7 +123,7 @@ namespace Multiplayer
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override;
void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override;
void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) override;
void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override;
void SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override;
@@ -132,6 +132,8 @@ namespace Multiplayer
INetworkEntityManager* GetNetworkEntityManager() override;
void SetFilterEntityManager(IFilterEntityManager* entityFilter) override;
IFilterEntityManager* GetFilterEntityManager() override;
void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) override;
void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) override;
void SetShouldSpawnNetworkEntities(bool value) override;
bool GetShouldSpawnNetworkEntities() const override;
//! @}
@@ -145,9 +147,11 @@ namespace Multiplayer
void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds);
void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom);
void OnAutonomousEntityReplicatorCreated();
void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands);
INetworkEntityManager::EntityList SpawnDefaultPlayerPrefab();
NetworkEntityHandle SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier);
void EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId);
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
AzNetworking::INetworkInterface* m_networkInterface = nullptr;
@@ -170,12 +174,16 @@ namespace Multiplayer
ClientMigrationEndEvent m_clientMigrationEndEvent;
NotifyClientMigrationEvent m_notifyClientMigrationEvent;
NotifyEntityMigrationEvent m_notifyEntityMigrationEvent;
AZ::Event<NetEntityId>::Handler m_autonomousEntityReplicatorCreatedHandler;
AZStd::queue<AZStd::string> m_pendingConnectionTickets;
AZStd::unordered_map<uint64_t, NetEntityId> m_playerRejoinData;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0);
uint64_t m_temporaryUserIdentifier = 0; // Used in the event of a migration or rejoin
double m_serverSendAccumulator = 0.0;
float m_renderBlendFactor = 0.0f;
float m_tickFactor = 0.0f;
@@ -47,6 +47,9 @@ namespace Multiplayer
, m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); })
, m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); })
{
// Set up our remote host identifier, by default we use the IP address of the remote host
m_remoteHostId = connection.GetRemoteAddress();
// Our max payload size is whatever is passed in, minus room for a udp packetheader
m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead;
@@ -62,12 +65,10 @@ namespace Multiplayer
networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler);
}
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler);
}
void EntityReplicationManager::SetRemoteHostId(const HostId& hostId)
{
m_remoteHostId = hostId;
if (m_updateMode == Mode::LocalServerToRemoteServer)
{
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler);
}
}
const HostId& EntityReplicationManager::GetRemoteHostId() const
@@ -258,8 +259,8 @@ namespace Multiplayer
{
AZLOG_WARN
(
"Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d",
aznumeric_cast<uint32_t>(replicator->GetEntityHandle().GetNetEntityId()),
"Serializing extremely large entity (%llu) - MaxPayload: %d NeededSize %d",
aznumeric_cast<AZ::u64>(replicator->GetEntityHandle().GetNetEntityId()),
m_maxPayloadSize,
nextMessageSize
);
@@ -364,15 +365,29 @@ namespace Multiplayer
const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole());
// Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client
bool changedLocalRole(false);
if (AZ::Entity* localEnt = entityReplicator->GetEntityHandle().GetEntity())
NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent();
if (netBindComponent != nullptr)
{
NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent();
AZ_Assert(netBindComponent != nullptr, "No NetBindComponent");
changedLocalRole = (netBindComponent->GetNetEntityRole() != entityReplicator->GetBoundLocalNetworkRole());
}
if (changedRemoteRole || changedLocalRole)
{
const AZ::u64 intEntityId = static_cast<AZ::u64>(netBindComponent->GetNetEntityId());
const char* entityName = entityReplicator->GetEntityHandle().GetEntity()->GetName().c_str();
if (changedLocalRole)
{
const char* oldRoleString = GetEnumString(entityReplicator->GetRemoteNetworkRole());
const char* newRoleString = GetEnumString(remoteNetworkRole);
AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed local role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString);
}
if (changedRemoteRole)
{
const char* oldRoleString = GetEnumString(entityReplicator->GetBoundLocalNetworkRole());
const char* newRoleString = GetEnumString(netBindComponent->GetNetEntityRole());
AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed remote role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString);
}
// If we changed roles, we need to reset everything
if (!entityReplicator->IsMarkedForRemoval())
{
@@ -387,8 +402,8 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Reinited replicator for %u from remote host %s role %d",
entityHandle.GetNetEntityId(),
"Reinited replicator for netEntityId %llu from remote host %s role %d",
static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole)
);
@@ -404,8 +419,8 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Added replicator for %u from remote host %s role %d",
entityHandle.GetNetEntityId(),
"Added replicator for netEntityId %llu from remote host %s role %d",
static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole)
);
@@ -413,7 +428,7 @@ namespace Multiplayer
}
else
{
AZLOG_ERROR("Failed to add entity replicator, entity does not exist, entity id %u", entityHandle.GetNetEntityId());
AZLOG_ERROR("Failed to add entity replicator, entity does not exist, netEntityId %llu", static_cast<AZ::u64>(entityHandle.GetNetEntityId()));
AZ_Assert(false, "Failed to add entity replicator, entity does not exist");
}
return entityReplicator;
@@ -502,24 +517,20 @@ namespace Multiplayer
{
if (entityReplicator->IsMarkedForRemoval())
{
AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
}
else if (entityReplicator->OwnsReplicatorLifetime())
{
// This can occur if we migrate entities quickly - if this is a replicator from C to A, A migrates to B, B then migrates to C, and A's delete replicator has not arrived at C
AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
}
else
{
shouldDeleteEntity = true;
entityReplicator->MarkForRemoval();
AZLOG(NET_RepDeletes, "Deleting replicater for entity id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Deleting replicater for entity id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
}
}
else
{
shouldDeleteEntity = updateMessage.GetTakeOwnership();
}
// Handle entity cleanup
if (shouldDeleteEntity)
@@ -529,17 +540,17 @@ namespace Multiplayer
{
if (updateMessage.GetWasMigrated())
{
AZLOG(NET_RepDeletes, "Leaving id %u using timeout remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Leaving id %llu using timeout remote host %s", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
}
else
{
AZLOG(NET_RepDeletes, "Deleting entity id %u remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Deleting entity id %llu remote host %s", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
GetNetworkEntityManager()->MarkForRemoval(entity);
}
}
else
{
AZLOG(NET_RepDeletes, "Trying to delete entity id %u remote host %s, but it has been removed", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Trying to delete entity id %llu remote host %s, but it has been removed", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
}
}
@@ -583,9 +594,9 @@ namespace Multiplayer
NetBindComponent* netBindComponent = replicatorEntity.GetNetBindComponent();
AZ_Assert(netBindComponent != nullptr, "No NetBindComponent");
if (createEntity)
if (netBindComponent->GetOwningConnectionId() != invokingConnection->GetConnectionId())
{
// Always set our invoking connectionId for any newly created entities, since this connection now 'owns' them from a rewind perspective
// Always ensure our owning connectionId is correct for correct rewind behaviour
netBindComponent->SetOwningConnectionId(invokingConnection->GetConnectionId());
}
@@ -595,10 +606,11 @@ namespace Multiplayer
AZ_Assert(localNetworkRole != NetEntityRole::Authority, "UpdateMessage trying to set local role to Authority, this should only happen via migration");
AZLOG_INFO
(
"EntityReplicationManager: Changing network role on entity %u, old role %u new role %u",
aznumeric_cast<uint32_t>(netEntityId),
aznumeric_cast<uint32_t>(netBindComponent->GetNetEntityRole()),
aznumeric_cast<uint32_t>(localNetworkRole)
"EntityReplicationManager: Changing network role on entity %s(%llu), old role %s new role %s",
replicatorEntity.GetEntity()->GetName().c_str(),
aznumeric_cast<AZ::u64>(netEntityId),
GetEnumString(netBindComponent->GetNetEntityRole()),
GetEnumString(localNetworkRole)
);
if (NetworkRoleHasController(localNetworkRole))
@@ -708,9 +720,9 @@ namespace Multiplayer
AZLOG_WARN
(
"Dropping Packet and LocalServerToRemoteClient connection, unexpected packet "
"LocalShard=%s EntityId=%u RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s",
"LocalShard=%s EntityId=%llu RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s",
GetNetworkEntityManager()->GetHostId().GetString().c_str(),
aznumeric_cast<uint32_t>(entityReplicator->GetEntityHandle().GetNetEntityId()),
aznumeric_cast<AZ::u64>(entityReplicator->GetEntityHandle().GetNetEntityId()),
aznumeric_cast<uint32_t>(entityReplicator->GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityReplicator->GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(entityReplicator->GetNetBindComponent()->GetNetEntityRole()),
@@ -760,13 +772,13 @@ namespace Multiplayer
result = UpdateValidationResult::DropMessage;
if (updateMessage.GetIsDelete())
{
AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %llu, sequence %d latest sequence %d from remote host %s",
(AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
}
else
{
AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %llu, sequence %d latest sequence %d from remote host %s",
(AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
}
}
}
@@ -853,10 +865,10 @@ namespace Multiplayer
{
AZLOG_INFO
(
"EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %u has already been deleted",
"EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %llu has already been deleted",
GetMultiplayerComponentRegistry()->GetComponentName(message.GetComponentId()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()),
message.GetEntityId()
static_cast<AZ::u64>(message.GetEntityId())
);
return false;
}
@@ -1113,7 +1125,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
netBindComponent->NotifyServerMigration(GetRemoteHostId());
}
bool didSucceed = true;
@@ -1145,7 +1157,7 @@ namespace Multiplayer
AZ_Assert(didSucceed, "Failed to migrate entity from server");
m_sendMigrateEntityEvent.Signal(m_connection, message);
AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Migration packet sent %llu to remote host %s", static_cast<AZ::u64>(netEntityId), GetRemoteHostId().GetString().c_str());
// Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status
GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId());
@@ -1201,7 +1213,7 @@ namespace Multiplayer
// Change the role on the replicator
AddEntityReplicator(entityHandle, NetEntityRole::Server);
AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote host %s", entityHandle.GetNetEntityId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "Handle Migration %llu new authority from remote host %s", static_cast<AZ::u64>(entityHandle.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
return true;
}
@@ -103,8 +103,8 @@ namespace Multiplayer
AZ_Assert
(
m_boundLocalNetworkRole != m_remoteNetworkRole,
"Invalid configuration detected, bound local role must differ from remote network role Role: %d",
aznumeric_cast<int32_t>(m_boundLocalNetworkRole)
"Invalid configuration detected, bound local role must differ from remote network role: %s",
GetEnumString(m_boundLocalNetworkRole)
);
if (RemoteManagerOwnsEntityLifetime())
@@ -176,7 +176,6 @@ namespace Multiplayer
switch (GetBoundLocalNetworkRole())
{
case NetEntityRole::Authority:
{
if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
@@ -189,10 +188,8 @@ namespace Multiplayer
{
m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
}
}
break;
break;
case NetEntityRole::Server:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent());
@@ -204,23 +201,21 @@ namespace Multiplayer
// Listen for these to forward the rpc along to the other Client replicators
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
}
// NOTE: e_Autonomous is not connected to e_ServerProxy, it is always connected to an e_Authority
AZ_Assert(GetRemoteNetworkRole() != NetEntityRole::Autonomous, "Unexpected autonomous remote role")
}
break;
else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous)
{
// NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority
AZ_Assert(false, "Unexpected autonomous remote role")
}
break;
case NetEntityRole::Client:
{
// Nothing allowed, no Client to Server communication
}
break;
break;
case NetEntityRole::Autonomous:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent());
}
}
break;
break;
default:
AZ_Assert(false, "Unexpected network role");
}
@@ -252,22 +247,9 @@ namespace Multiplayer
if (entity->GetState() != AZ::Entity::State::Init)
{
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%u)", GetEntityHandle().GetNetEntityId());
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%llu)", static_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()));
}
// First we need to make sure the transform component has been updated with the correct value prior to activation
// This is because vanilla az components may only depend on the transform component, not the multiplayer transform component
//if (auto* locationComponent = FindCommonComponent<LocationComponent::Common>(GetEntityHandle()))
//{
// AZ::Transform newTransform = locationComponent->GetTransform();
// auto* transformComponent = entity->FindComponent<AzFramework::TransformComponent>();
// if (transformComponent)
// {
// // We can't use EBus here since the TransFormBus does not get connected until the activate call below
// transformComponent->SetWorldTM(newTransform);
// }
//}
// Ugly, but this is the only time we need to call a non-const function on this entity
entity->Activate();
m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this);
@@ -281,8 +263,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = m_netBindComponent;
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
if (isAuthority || isClient || isAutonomous)
@@ -296,10 +277,10 @@ namespace Multiplayer
bool EntityReplicator::OwnsReplicatorLifetime() const
{
bool ret(false);
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority // Authority always owns lifetime
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server // Server also owns lifetime if the remote endpoint is a client of some form
&& (GetRemoteNetworkRole() == NetEntityRole::Client
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
{
ret = true;
}
@@ -309,10 +290,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;
}
@@ -429,10 +409,8 @@ namespace Multiplayer
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.
*/
// 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);
@@ -452,9 +430,9 @@ namespace Multiplayer
AZLOG
(
NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - waiting on the parent %u",
"Hierchical entity %s asking for activation - waiting on the parent %llu",
entity->GetName().c_str(),
aznumeric_cast<uint32_t>(parentId)
aznumeric_cast<AZ::u64>(parentId)
);
return false;
}
@@ -472,19 +450,19 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Sending delete replicator id %u migrated %d to remote host %s",
aznumeric_cast<uint32_t>(GetEntityHandle().GetNetEntityId()),
"Sending delete replicator id %llu migrated %d to remote host %s",
aznumeric_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()),
WasMigrated() ? 1 : 0,
m_replicationManager.GetRemoteHostId().GetString().c_str()
);
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated(), m_propertyPublisher->IsRemoteReplicatorEstablished());
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated());
}
NetBindComponent* netBindComponent = GetNetBindComponent();
//const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId());
//if (sendSliceName)
if (sendSliceName)
{
updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId());
}
@@ -553,42 +531,33 @@ namespace Multiplayer
switch (entityRpcMessage.GetRpcDeliveryType())
{
case RpcDeliveryType::AuthorityToClient:
{
if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous))
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
{
// We are a local client, and we are connected to server, aka AuthorityToClient
result = RpcValidationResult::HandleRpc;
}
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
{
// We are on a server, and we received this message from another server, therefore we should forward this to any connected clients
result = RpcValidationResult::ForwardToClient;
}
}
break;
break;
case RpcDeliveryType::AuthorityToAutonomous:
{
if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
{
// We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous
result = RpcValidationResult::HandleRpc;
}
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
{
// We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player
// This can occur if we've recently migrated
result = RpcValidationResult::ForwardToAutonomous;
}
}
break;
break;
case RpcDeliveryType::AutonomousToAuthority:
{
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
{
if (IsMarkedForRemoval())
{
@@ -610,12 +579,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc;
}
}
}
break;
break;
case RpcDeliveryType::ServerToAuthority:
{
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
{
// if we're marked for removal, then we should forward to whomever now owns this entity
if (IsMarkedForRemoval())
@@ -638,9 +604,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc;
}
}
break;
}
break;
}
if (result == RpcValidationResult::DropRpcAndDisconnect)
{
bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server);
@@ -654,30 +620,29 @@ namespace Multiplayer
{
AZLOG_ERROR
(
"Dropping RPC and Connection EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC and Connection EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
}
}
if (result == RpcValidationResult::DropRpc)
{
AZLOG
(
NET_Rpc,
"Dropping RPC EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
@@ -696,13 +661,12 @@ namespace Multiplayer
{
AZLOG_WARN
(
"Dropping RPC since entity deleted EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC since entity deleted EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
@@ -740,23 +704,23 @@ namespace Multiplayer
case RpcValidationResult::DropRpcAndDisconnect:
return false;
case RpcValidationResult::ForwardToClient:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
}
return true;
}
case RpcValidationResult::ForwardToAutonomous:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
}
return true;
}
case RpcValidationResult::ForwardToAuthority:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
}
return true;
}
default:
break;
}
@@ -33,8 +33,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Removing timeout for networkEntityId %u from %s, new owner is %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str(),
newOwner.GetString().c_str()
);
@@ -48,8 +48,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u from %s to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Assigning networkEntityId %llu from %s to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
iter->second.back().GetString().c_str(),
newOwner.GetString().c_str()
);
@@ -59,8 +59,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Assigning networkEntityId %llu to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
newOwner.GetString().c_str()
);
}
@@ -87,7 +87,7 @@ namespace Multiplayer
}
}
AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %u from %s", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str());
AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %llu from %s", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str());
if (auto localEnt = entityHandle.GetEntity())
{
if (authorityStack.empty())
@@ -114,14 +114,14 @@ namespace Multiplayer
}
else
{
AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %u", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()));
AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %llu", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()));
}
}
}
}
else
{
AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %u", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()));
AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %llu", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()));
AZ_Assert(false, "AuthTracker: Remove authority called on entity that was never added");
}
}
@@ -205,8 +205,8 @@ namespace Multiplayer
{
AZLOG_ERROR
(
"Timed out entity id %u during migration previous owner %s, removing it",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"Timed out entity id %llu during migration previous owner %s, removing it",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str()
);
m_networkEntityManager.MarkForRemoval(entityHandle);
@@ -18,21 +18,13 @@ namespace Multiplayer
{
ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* networkEntityTracker)
: m_entity(entity)
, m_networkEntityTracker(networkEntityTracker)
, m_networkEntityTracker((networkEntityTracker != nullptr) ? networkEntityTracker : GetNetworkEntityTracker())
{
if (m_networkEntityTracker == nullptr)
{
m_networkEntityTracker = GetNetworkEntityTracker();
}
if (m_networkEntityTracker)
{
m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity);
}
AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid");
m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity);
if (entity)
{
AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid");
m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(entity);
if (m_netBindComponent != nullptr)
{
@@ -48,6 +48,20 @@ namespace Multiplayer
void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
{
m_hostId = hostId;
// Configure our vended NetEntityIds so that no two hosts generate the same NetEntityId
{
// Needs more thought
const uint64_t addrPortion = hostId.GetAddress(AzNetworking::ByteOrder::Host);
const uint64_t portPortion = hostId.GetPort(AzNetworking::ByteOrder::Host);
const uint64_t hostIdentifier = (portPortion << 32) | addrPortion;
const AZ::HashValue32 hostHash = AZ::TypeHash32(hostIdentifier);
NetEntityId hostEntityIdOffset = static_cast<NetEntityId>(hostHash) << 32;
m_nextEntityId &= NetEntityId{ 0x0000000000000000FFFFFFFFFFFFFFFF };
m_nextEntityId |= hostEntityIdOffset;
}
m_entityDomain = AZStd::move(entityDomain);
m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true);
m_entityDomain->ActivateTracking(m_ownedEntities);
@@ -227,11 +241,19 @@ namespace Multiplayer
{
AZ::Entity* entity = it->second;
NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity);
AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
entityBounds.Expand(AZ::Vector3(0.01f));
if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)
{
const AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax());
debugDisplay->SetColor(AZ::Colors::Black);
debugDisplay->SetAlpha(0.5f);
}
else
{
debugDisplay->SetColor(AZ::Colors::DeepSkyBlue);
debugDisplay->SetAlpha(0.25f);
}
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax());
}
if (m_entityDomain != nullptr)
@@ -18,7 +18,6 @@ namespace Multiplayer
, m_entityId(rhs.m_entityId)
, m_isDelete(rhs.m_isDelete)
, m_wasMigrated(rhs.m_wasMigrated)
, m_takeOwnership(rhs.m_takeOwnership)
, m_hasValidPrefabId(rhs.m_hasValidPrefabId)
, m_prefabEntityId(rhs.m_prefabEntityId)
, m_data(AZStd::move(rhs.m_data))
@@ -31,7 +30,6 @@ namespace Multiplayer
, m_entityId(rhs.m_entityId)
, m_isDelete(rhs.m_isDelete)
, m_wasMigrated(rhs.m_wasMigrated)
, m_takeOwnership(rhs.m_takeOwnership)
, m_hasValidPrefabId(rhs.m_hasValidPrefabId)
, m_prefabEntityId(rhs.m_prefabEntityId)
{
@@ -58,11 +56,10 @@ namespace Multiplayer
;
}
NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated, bool takeOwnership)
NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated)
: m_entityId(entityId)
, m_isDelete(true)
, m_wasMigrated(wasMigrated)
, m_takeOwnership(takeOwnership)
{
// this is a delete entity message c-tor
}
@@ -73,7 +70,6 @@ namespace Multiplayer
m_entityId = rhs.m_entityId;
m_isDelete = rhs.m_isDelete;
m_wasMigrated = rhs.m_wasMigrated;
m_takeOwnership = rhs.m_takeOwnership;
m_hasValidPrefabId = rhs.m_hasValidPrefabId;
m_prefabEntityId = rhs.m_prefabEntityId;
m_data = AZStd::move(rhs.m_data);
@@ -86,7 +82,6 @@ namespace Multiplayer
m_entityId = rhs.m_entityId;
m_isDelete = rhs.m_isDelete;
m_wasMigrated = rhs.m_wasMigrated;
m_takeOwnership = rhs.m_takeOwnership;
m_hasValidPrefabId = rhs.m_hasValidPrefabId;
m_prefabEntityId = rhs.m_prefabEntityId;
if (rhs.m_data != nullptr)
@@ -104,7 +99,6 @@ namespace Multiplayer
&& (m_entityId == rhs.m_entityId)
&& (m_isDelete == rhs.m_isDelete)
&& (m_wasMigrated == rhs.m_wasMigrated)
&& (m_takeOwnership == rhs.m_takeOwnership)
&& (m_hasValidPrefabId == rhs.m_hasValidPrefabId)
&& (m_prefabEntityId == rhs.m_prefabEntityId));
}
@@ -160,11 +154,6 @@ namespace Multiplayer
return m_wasMigrated;
}
bool NetworkEntityUpdateMessage::GetTakeOwnership() const
{
return m_takeOwnership;
}
bool NetworkEntityUpdateMessage::GetHasValidPrefabId() const
{
return m_hasValidPrefabId;
@@ -210,17 +199,15 @@ namespace Multiplayer
serializer.Serialize(m_entityId, "EntityId");
// Use the upper 4 bits for boolean flags, and the lower 4 bits for the network role
uint8_t networkTypeAndFlags = (m_isDelete ? 0x80 : 0x00)
| (m_wasMigrated ? 0x40 : 0x00)
| (m_takeOwnership ? 0x20 : 0x00)
uint8_t networkTypeAndFlags = (m_isDelete ? 0x40 : 0x00)
| (m_wasMigrated ? 0x20 : 0x00)
| (m_hasValidPrefabId ? 0x10 : 0x00)
| static_cast<uint8_t>(m_networkRole);
if (serializer.Serialize(networkTypeAndFlags, "TypeAndFlags"))
{
m_isDelete = (networkTypeAndFlags & 0x80) == 0x80;
m_wasMigrated = (networkTypeAndFlags & 0x40) == 0x40;
m_takeOwnership = (networkTypeAndFlags & 0x20) == 0x20;
m_isDelete = (networkTypeAndFlags & 0x40) == 0x40;
m_wasMigrated = (networkTypeAndFlags & 0x20) == 0x20;
m_hasValidPrefabId = (networkTypeAndFlags & 0x10) == 0x10;
m_networkRole = static_cast<NetEntityRole>(networkTypeAndFlags & 0x0F);
}
@@ -6,7 +6,7 @@
*
*/
#include <Source/NetworkInput/NetworkInputArray.h>
#include <Multiplayer/NetworkInput/NetworkInputArray.h>
#include <Multiplayer/NetworkEntity/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/DeltaSerializer.h>
@@ -6,7 +6,7 @@
*
*/
#include <Source/NetworkInput/NetworkInputChild.h>
#include <Multiplayer/NetworkInput/NetworkInputChild.h>
#include <Multiplayer/IMultiplayer.h>
#include <AzNetworking/Serialization/ISerializer.h>
@@ -6,7 +6,7 @@
*
*/
#include <Source/NetworkInput/NetworkInputHistory.h>
#include <Multiplayer/NetworkInput/NetworkInputHistory.h>
namespace Multiplayer
{
@@ -6,7 +6,7 @@
*
*/
#include <Source/NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/IMultiplayer.h>
#include <AzNetworking/Serialization/ISerializer.h>
@@ -92,17 +92,10 @@ namespace Multiplayer
return m_isPoorConnection ? sv_MinEntitiesToReplicate : sv_MaxEntitiesToReplicate;
}
bool ServerToClientReplicationWindow::IsInWindow(const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const
bool ServerToClientReplicationWindow::IsInWindow([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const
{
// TODO: Clean up this interface, this function is used for server->server migrations, and probably shouldn't be exposed in it's current setup
AZ_Assert(false, "IsInWindow should not be called on the ServerToClientReplicationWindow");
outNetworkRole = NetEntityRole::InvalidRole;
auto iter = m_replicationSet.find(entityHandle);
if (iter != m_replicationSet.end())
{
outNetworkRole = iter->second.m_netEntityRole;
return true;
}
return false;
}
@@ -146,7 +139,7 @@ namespace Multiplayer
NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker();
IFilterEntityManager* filterEntityManager = GetMultiplayer()->GetFilterEntityManager();
// Add all the neighbors
// Add all the neighbours
for (AzFramework::VisibilityEntry* visEntry : gatheredEntries)
{
AZ::Entity* entity = static_cast<AZ::Entity*>(visEntry->m_userData);
@@ -301,7 +294,6 @@ namespace Multiplayer
void ServerToClientReplicationWindow::AddEntityToReplicationSet(ConstNetworkEntityHandle& entityHandle, float priority, [[maybe_unused]] float distanceSquared)
{
// Assumption: the entity has been checked for filtering prior to this call.
if (!sv_ReplicateServerProxies)
{
NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent();
@@ -312,11 +304,11 @@ namespace Multiplayer
}
}
const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set
const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set
const bool isInReplicationSet = m_replicationSet.find(entityHandle) != m_replicationSet.end();
if (!isInReplicationSet)
{
if (isQueueFull) // if our set is full, then we need to remove the worst priority in our set
if (isQueueFull) // If our set is full, then we need to remove the worst priority in our set
{
ConstNetworkEntityHandle removeEnt = m_candidateQueue.top().m_entityHandle;
m_candidateQueue.pop();
@@ -332,7 +324,7 @@ namespace Multiplayer
INetworkEntityManager* networkEntityManager = AZ::Interface<INetworkEntityManager>::Get();
AZ_Assert(networkEntityManager, "NetworkEntityManager must be created.");
for (const AZ::Entity* controlledEntity : hierarchyComponent.m_hierarchicalEntities)
for (const AZ::Entity* controlledEntity : hierarchyComponent.GetHierarchicalEntities())
{
NetEntityId controlledNetEntitydId = networkEntityManager->GetNetEntityIdById(controlledEntity->GetId());
AZ_Assert(controlledNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager");
@@ -76,8 +76,6 @@ namespace Multiplayer
AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler;
AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler;
//NetBindComponent* m_controlledNetBindComponent = nullptr;
AzNetworking::IConnection* m_connection = nullptr;
// Cached values to detect a poor network connection
@@ -16,7 +16,7 @@
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkInput/NetworkInputArray.h>
#include <Multiplayer/NetworkInput/NetworkInputArray.h>
#include <Source/NetworkEntity/NetworkEntityManager.h>
namespace Multiplayer
@@ -213,9 +213,8 @@ namespace Multiplayer
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(value),
"hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
ISerializer& serializer = inSerializer;
serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
@@ -303,6 +302,36 @@ namespace Multiplayer
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
}
TEST_F(ClientSimpleHierarchyTests, ChildHasOwningConnectionIdOfParent)
{
// disconnect and assign new connection ids
SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
m_root->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 1 });
m_child->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 2 });
const ConnectionId previousConnectionId = m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId();
// re-attach, child's owning connection id should then be root's connection id
SetParentIdOnNetworkTransform(m_child->m_entity, RootNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, RootNetEntityId);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
// detach, the child should roll back to his previous owning connection id
SetParentIdOnNetworkTransform(m_child->m_entity, InvalidNetEntityId);
SetHierarchyRootFieldOnNetworkHierarchyChildOnClient(m_child->m_entity, InvalidNetEntityId);
EXPECT_EQ(
m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
previousConnectionId
);
}
/*
* Parent -> Child -> ChildOfChild
*/
@@ -397,7 +426,7 @@ namespace Multiplayer
using MultiplayerTest::TestMultiplayerComponentNetworkInput;
auto* rootNetBind = m_root->m_entity->FindComponent<NetBindComponent>();
NetworkInputArray inputArray(rootNetBind->GetEntityHandle());
NetworkInput& input = inputArray[0];
@@ -342,8 +342,11 @@ namespace Multiplayer
void AddClientMigrationEndEventHandler([[maybe_unused]] ClientMigrationEndEvent::Handler& handler) override {}
void AddNotifyClientMigrationHandler([[maybe_unused]] NotifyClientMigrationEvent::Handler& handler) override {}
void AddNotifyEntityMigrationEventHandler([[maybe_unused]] NotifyEntityMigrationEvent::Handler& handler) override {}
void SendNotifyClientMigrationEvent([[maybe_unused]] const HostId& hostId, [[maybe_unused]] uint64_t userIdentifier, [[maybe_unused]] ClientInputId lastClientInputId) override {}
void SendNotifyClientMigrationEvent([[maybe_unused]] AzNetworking::ConnectionId connectionId, [[maybe_unused]] const HostId& hostId,
[[maybe_unused]] uint64_t userIdentifier, [[maybe_unused]] ClientInputId lastClientInputId, [[maybe_unused]] NetEntityId netEntityId) override {}
void SendNotifyEntityMigrationEvent([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] const HostId& remoteHostId) override {}
void RegisterPlayerIdentifierForRejoin(uint64_t, NetEntityId) override {}
void CompleteClientMigration(uint64_t, AzNetworking::ConnectionId, const HostId&, ClientInputId) override {}
void SetShouldSpawnNetworkEntities([[maybe_unused]] bool value) override {}
bool GetShouldSpawnNetworkEntities() const override { return true; }
@@ -535,9 +538,8 @@ namespace Multiplayer
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(netParentId),
"parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
ISerializer& serializer = inSerializer;
serializer.Serialize(netParentId, "parentEntityId"); // Derived from NetworkTransformComponent.AutoComponent.xml
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
@@ -560,9 +562,8 @@ namespace Multiplayer
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(value),
"hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
ISerializer& serializer = inSerializer;
serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
@@ -317,9 +317,8 @@ namespace Multiplayer
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(netParentId),
"parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
ISerializer& serializer = inSerializer;
serializer.Serialize(netParentId, "parentEntityId"); // Derived from NetworkTransformComponent.AutoComponent.xml
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
@@ -365,9 +364,8 @@ namespace Multiplayer
constexpr uint32_t bufferSize = 100;
AZStd::array<uint8_t, bufferSize> buffer = {};
NetworkInputSerializer inSerializer(buffer.begin(), bufferSize);
inSerializer.Serialize(reinterpret_cast<uint32_t&>(value),
"hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */
AZStd::numeric_limits<uint32_t>::min(), AZStd::numeric_limits<uint32_t>::max());
ISerializer& serializer = inSerializer;
serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml
NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize);
+3 -1
View File
@@ -33,7 +33,7 @@ namespace UnitTest
MOCK_METHOD1(AddServerAcceptanceReceivedHandler, void(Multiplayer::ServerAcceptanceReceivedEvent::Handler&));
MOCK_METHOD1(AddSessionInitHandler, void(Multiplayer::SessionInitEvent::Handler&));
MOCK_METHOD1(AddSessionShutdownHandler, void(Multiplayer::SessionShutdownEvent::Handler&));
MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId));
MOCK_METHOD5(SendNotifyClientMigrationEvent, void(AzNetworking::ConnectionId, const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId, Multiplayer::NetEntityId));
MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&));
MOCK_METHOD1(SendReadyForEntityUpdates, void(bool));
MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs());
@@ -42,6 +42,8 @@ namespace UnitTest
MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ());
MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*));
MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ());
MOCK_METHOD2(RegisterPlayerIdentifierForRejoin, void(uint64_t, Multiplayer::NetEntityId));
MOCK_METHOD4(CompleteClientMigration, void(uint64_t, AzNetworking::ConnectionId, const Multiplayer::HostId&, Multiplayer::ClientInputId));
MOCK_METHOD1(SetShouldSpawnNetworkEntities, void(bool));
MOCK_CONST_METHOD0(GetShouldSpawnNetworkEntities, bool());
};
@@ -17,9 +17,9 @@
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/NetworkInput/NetworkInput.h>
#include <Multiplayer/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <NetworkInput/NetworkInputArray.h>
#include <NetworkInput/NetworkInputHistory.h>
#include <NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/NetworkInput/NetworkInputArray.h>
#include <Multiplayer/NetworkInput/NetworkInputHistory.h>
#include <Multiplayer/NetworkInput/NetworkInputMigrationVector.h>
namespace Multiplayer
{
@@ -195,6 +195,49 @@ namespace Multiplayer
m_child->m_entity.reset();
}
TEST_F(ServerSimpleHierarchyTests, ChildPointsToRootAfterReattachment)
{
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
InvalidNetEntityId
);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_root->m_entity->GetId());
EXPECT_EQ(
m_child->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetNetEntityId()
);
}
TEST_F(ServerSimpleHierarchyTests, ChildHasOwningConnectionIdOfParent)
{
// disconnect and assign new connection ids
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
m_root->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 1 });
m_child->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 2 });
const ConnectionId previousConnectionId = m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId();
// re-attach, child's owning connection id should then be root's connection id
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_root->m_entity->GetId());
EXPECT_EQ(
m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
// detach, the child should roll back to his previous owning connection id
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
EXPECT_EQ(
m_child->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
previousConnectionId
);
}
/*
* Parent -> Child -> ChildOfChild
*/
@@ -410,7 +453,7 @@ namespace Multiplayer
{
MockNetworkHierarchyCallbackHandler mock;
EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root->m_entity->GetId())).Times(2);
m_root->m_entity->FindComponent<NetworkHierarchyRootComponent>()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
@@ -822,6 +865,22 @@ namespace Multiplayer
}
}
TEST_F(ServerHierarchyOfHierarchyTests, InnerChildrenPointToInnerRootAfterDetachmentFromTopRoot)
{
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_root->m_entity->GetId());
// detach
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
EXPECT_EQ(
m_child2->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
m_root2->m_entity->FindComponent<NetBindComponent>()->GetNetEntityId()
);
EXPECT_EQ(
m_childOfChild2->m_entity->FindComponent<NetworkHierarchyChildComponent>()->GetHierarchyRoot(),
m_root2->m_entity->FindComponent<NetBindComponent>()->GetNetEntityId()
);
}
TEST_F(ServerHierarchyOfHierarchyTests, Inner_Root_Has_Child_References_After_Detachment_From_Child_Of_Child)
{
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childOfChild->m_entity->GetId());
@@ -1005,6 +1064,59 @@ namespace Multiplayer
m_console->GetCvarValue<uint32_t>("bg_hierarchyEntityMaxLimit", currentMaxLimit);
}
TEST_F(ServerHierarchyOfHierarchyTests, InnerRootAndItsChildrenHaveOwningConnectionIdOfTopRoot)
{
// Assign new connection ids.
m_root->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 1 });
m_root2->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 2 });
// Attach then inner hierarchy's owning connection id should then be top root's connection id.
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childOfChild->m_entity->GetId());
EXPECT_EQ(
m_root2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
EXPECT_EQ(
m_child2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
EXPECT_EQ(
m_childOfChild2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
}
TEST_F(ServerHierarchyOfHierarchyTests, InnerRootAndItsChildrenHaveTheirOriginalOwningConnectionIdAfterDetachingFromTopRoot)
{
// Assign new connection ids.
m_root->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 1 });
m_root2->m_entity->FindComponent<NetBindComponent>()->SetOwningConnectionId(ConnectionId{ 2 });
// Attach then inner hierarchy's owning connection id should then be top root's connection id.
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childOfChild->m_entity->GetId());
// detach, inner hierarchy should roll back to his previous owning connection id
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
EXPECT_EQ(
m_root2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
ConnectionId{ 2 }
);
EXPECT_EQ(
m_child2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
EXPECT_EQ(
m_childOfChild2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId(),
m_root2->m_entity->FindComponent<NetBindComponent>()->GetOwningConnectionId()
);
}
/*
* Parent -> Child -> ChildOfChild (not marked as in a hierarchy)
*/
@@ -1242,15 +1354,15 @@ namespace Multiplayer
);
}
TEST_F(ServerHierarchyWithThreeRoots, ReattachMiddleChildWhileLastChildGetsLeaveEventOnce)
TEST_F(ServerHierarchyWithThreeRoots, InnerRootLeftTopRootThenLastChildGetsJoinedEventOnce)
{
m_root2->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childOfChild->m_entity->GetId());
m_root3->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(m_childOfChild->m_entity->GetId());
MockNetworkHierarchyCallbackHandler mock;
EXPECT_CALL(mock, OnNetworkHierarchyLeave());
m_childOfChild3->m_entity->FindComponent<NetworkHierarchyChildComponent>()->BindNetworkHierarchyLeaveEventHandler(mock.m_leaveHandler);
EXPECT_CALL(mock, OnNetworkHierarchyUpdated(m_root3->m_entity->GetId()));
m_childOfChild3->m_entity->FindComponent<NetworkHierarchyChildComponent>()->BindNetworkHierarchyChangedEventHandler(mock.m_changedHandler);
m_child->m_entity->FindComponent<AzFramework::TransformComponent>()->SetParent(AZ::EntityId());
}
@@ -45,6 +45,10 @@ set(FILES
Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h
Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h
Include/Multiplayer/NetworkInput/NetworkInput.h
Include/Multiplayer/NetworkInput/NetworkInputArray.h
Include/Multiplayer/NetworkInput/NetworkInputChild.h
Include/Multiplayer/NetworkInput/NetworkInputHistory.h
Include/Multiplayer/NetworkInput/NetworkInputMigrationVector.h
Include/Multiplayer/NetworkTime/INetworkTime.h
Include/Multiplayer/NetworkTime/RewindableArray.h
Include/Multiplayer/NetworkTime/RewindableArray.inl
@@ -114,13 +118,9 @@ set(FILES
Source/NetworkEntity/NetworkSpawnableLibrary.h
Source/NetworkInput/NetworkInput.cpp
Source/NetworkInput/NetworkInputArray.cpp
Source/NetworkInput/NetworkInputArray.h
Source/NetworkInput/NetworkInputChild.cpp
Source/NetworkInput/NetworkInputChild.h
Source/NetworkInput/NetworkInputHistory.cpp
Source/NetworkInput/NetworkInputHistory.h
Source/NetworkInput/NetworkInputMigrationVector.cpp
Source/NetworkInput/NetworkInputMigrationVector.h
Source/NetworkTime/NetworkTime.cpp
Source/NetworkTime/NetworkTime.h
Source/Pipeline/NetworkSpawnableHolderComponent.cpp
+11 -1
View File
@@ -676,7 +676,17 @@ namespace PhysX
}
}
AZ::Transform Collider::GetColliderLocalTransform(const Physics::ColliderConfiguration& colliderConfig,
void Collider::DrawHeightfield(
[[maybe_unused]] AzFramework::DebugDisplayRequests& debugDisplay,
[[maybe_unused]] const Physics::ColliderConfiguration& colliderConfig,
[[maybe_unused]] const Physics::HeightfieldShapeConfiguration& heightfieldShapeConfig,
[[maybe_unused]] const AZ::Vector3& colliderScale,
[[maybe_unused]] const bool forceUniformScaling) const
{
}
AZ::Transform Collider::GetColliderLocalTransform(
const Physics::ColliderConfiguration& colliderConfig,
const AZ::Vector3& colliderScale) const
{
// Apply entity world transform scale to collider offset
+9 -1
View File
@@ -104,7 +104,15 @@ namespace PhysX
const AZ::Vector3& meshScale,
AZ::u32 geomIndex) const;
void DrawPolygonPrism(AzFramework::DebugDisplayRequests& debugDisplay,
void DrawHeightfield(
AzFramework::DebugDisplayRequests& debugDisplay,
const Physics::ColliderConfiguration& colliderConfig,
const Physics::HeightfieldShapeConfiguration& heightfieldShapeConfig,
const AZ::Vector3& colliderScale = AZ::Vector3::CreateOne(),
const bool forceUniformScaling = false) const;
void DrawPolygonPrism(
AzFramework::DebugDisplayRequests& debugDisplay,
const Physics::ColliderConfiguration& colliderConfig, const AZStd::vector<AZ::Vector3>& points) const;
AZ::Transform GetColliderLocalTransform(const Physics::ColliderConfiguration& colliderConfig,
@@ -16,19 +16,21 @@ namespace AzPhysics
{
class CollisionGroup;
class CollisionLayer;
}
} // namespace AzPhysics
namespace physx
{
class PxScene;
class PxSceneDesc;
class PxConvexMesh;
class PxHeightField;
class PxTriangleMesh;
class PxShape;
class PxCooking;
class PxControllerManager;
struct PxFilterData;
}
struct PxHeightFieldSample;
} // namespace physx
namespace PhysX
{
@@ -63,6 +65,13 @@ namespace PhysX
/// @return Pointer to the created mesh.
virtual physx::PxTriangleMesh* CreateTriangleMeshFromCooked(const void* cookedMeshData, AZ::u32 bufferSize) = 0;
/// Creates a new heightfield.
/// @param samples Pointer to beginning of heightfield sample data.
/// @param numRows Number of rows in the heightfield.
/// @param numColumns Number of columns in the heightfield.
/// @return Pointer to the created heightfield.
virtual physx::PxHeightField* CreateHeightField(const physx::PxHeightFieldSample* samples, AZ::u32 numRows, AZ::u32 numColumns) = 0;
/// Creates PhysX collision filter data from generic collision filtering settings.
/// @param layer The collision layer the object belongs to.
/// @param group The set of collision layers the object will interact with.
@@ -12,6 +12,7 @@
#include <Source/BaseColliderComponent.h>
#include <Source/MeshColliderComponent.h>
#include <Source/BoxColliderComponent.h>
#include <Source/HeightfieldColliderComponent.h>
#include <Source/SphereColliderComponent.h>
#include <Source/CapsuleColliderComponent.h>
#include <Source/ShapeColliderComponent.h>
@@ -36,6 +37,7 @@ namespace PhysX
BaseColliderComponent::CreateDescriptor(),
MeshColliderComponent::CreateDescriptor(),
BoxColliderComponent::CreateDescriptor(),
HeightfieldColliderComponent::CreateDescriptor(),
SphereColliderComponent::CreateDescriptor(),
CapsuleColliderComponent::CreateDescriptor(),
ShapeColliderComponent::CreateDescriptor(),
@@ -12,6 +12,7 @@
#include <Source/EditorColliderComponent.h>
#include <Source/EditorFixedJointComponent.h>
#include <Source/EditorForceRegionComponent.h>
#include <Source/EditorHeightfieldColliderComponent.h>
#include <Source/EditorHingeJointComponent.h>
#include <Source/EditorJointComponent.h>
#include <Source/EditorRigidBodyComponent.h>
@@ -35,6 +36,7 @@ namespace PhysX
EditorColliderComponent::CreateDescriptor(),
EditorFixedJointComponent::CreateDescriptor(),
EditorForceRegionComponent::CreateDescriptor(),
EditorHeightfieldColliderComponent::CreateDescriptor(),
EditorHingeJointComponent::CreateDescriptor(),
EditorJointComponent::CreateDescriptor(),
EditorRigidBodyComponent::CreateDescriptor(),
@@ -0,0 +1,341 @@
/*
* 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/smart_ptr/make_shared.h>
#include <Editor/ColliderComponentMode.h>
#include <EditorHeightfieldColliderComponent.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzFramework/Physics/Shape.h>
#include <Source/HeightfieldColliderComponent.h>
#include <Source/Utils.h>
#include <System/PhysXSystem.h>
namespace PhysX
{
void EditorHeightfieldColliderComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorHeightfieldColliderComponent, EditorComponentBase>()
->Version(1)
->Field("ColliderConfiguration", &EditorHeightfieldColliderComponent::m_colliderConfig)
->Field("DebugDrawSettings", &EditorHeightfieldColliderComponent::m_colliderDebugDraw)
->Field("ShapeConfig", &EditorHeightfieldColliderComponent::m_shapeConfig)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorHeightfieldColliderComponent>(
"PhysX Heightfield Collider", "Creates geometry in the PhysX simulation based on an attached heightfield component")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
->Attribute(
AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/heightfield-collider/")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorHeightfieldColliderComponent::m_colliderConfig, "Collider configuration",
"Configuration of the collider")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorHeightfieldColliderComponent::OnConfigurationChanged)
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorHeightfieldColliderComponent::m_colliderDebugDraw, "Debug draw settings",
"Debug draw settings")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
}
}
}
void EditorHeightfieldColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PhysicsWorldBodyService"));
provided.push_back(AZ_CRC_CE("PhysXColliderService"));
provided.push_back(AZ_CRC_CE("PhysXHeightfieldColliderService"));
}
void EditorHeightfieldColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService"));
}
void EditorHeightfieldColliderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("PhysXColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXStaticRigidBodyService"));
incompatible.push_back(AZ_CRC_CE("PhysXRigidBodyService"));
}
EditorHeightfieldColliderComponent::EditorHeightfieldColliderComponent()
: m_physXConfigChangedHandler(
[]([[maybe_unused]] const AzPhysics::SystemConfiguration* config)
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
})
, m_onMaterialLibraryChangedEventHandler(
[this](const AZ::Data::AssetId& defaultMaterialLibrary)
{
m_colliderConfig.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary);
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
})
{
}
EditorHeightfieldColliderComponent ::~EditorHeightfieldColliderComponent()
{
ClearHeightfield();
}
// AZ::Component
void EditorHeightfieldColliderComponent::Activate()
{
AzToolsFramework::Components::EditorComponentBase::Activate();
// Heightfields don't support the following:
// - Offset: There shouldn't be a need to offset the data, since the heightfield provider is giving a physics representation
// - IsTrigger: PhysX heightfields don't support acting as triggers
// - MaterialSelection: The heightfield provider provides per-vertex material selection
m_colliderConfig.SetPropertyVisibility(Physics::ColliderConfiguration::Offset, false);
m_colliderConfig.SetPropertyVisibility(Physics::ColliderConfiguration::IsTrigger, false);
m_colliderConfig.SetPropertyVisibility(Physics::ColliderConfiguration::MaterialSelection, false);
m_sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
if (m_sceneInterface)
{
m_attachedSceneHandle = m_sceneInterface->GetSceneHandle(AzPhysics::EditorPhysicsSceneName);
}
const AZ::EntityId entityId = GetEntityId();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusConnect(entityId);
// Debug drawing
m_colliderDebugDraw.Connect(entityId);
m_colliderDebugDraw.SetDisplayCallback(this);
Physics::HeightfieldProviderNotificationBus::Handler::BusConnect(entityId);
PhysX::ColliderShapeRequestBus::Handler::BusConnect(entityId);
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(entityId);
RefreshHeightfield();
}
void EditorHeightfieldColliderComponent::Deactivate()
{
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
PhysX::ColliderShapeRequestBus::Handler::BusDisconnect();
Physics::HeightfieldProviderNotificationBus::Handler::BusDisconnect();
m_colliderDebugDraw.Disconnect();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::Components::EditorComponentBase::Deactivate();
ClearHeightfield();
}
void EditorHeightfieldColliderComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
auto* heightfieldColliderComponent = gameEntity->CreateComponent<HeightfieldColliderComponent>();
heightfieldColliderComponent->SetShapeConfiguration(
{ AZStd::make_shared<Physics::ColliderConfiguration>(m_colliderConfig), m_shapeConfig });
}
void EditorHeightfieldColliderComponent::OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
{
RefreshHeightfield();
}
void EditorHeightfieldColliderComponent::ClearHeightfield()
{
// There are two references to the heightfield data, we need to clear both to make the heightfield clear out and deallocate:
// - The simulated body has a pointer to the shape, which has a GeometryHolder, which has the Heightfield inside it
// - The shape config is also holding onto a pointer to the Heightfield
// We remove the simulated body first, since we don't want the heightfield to exist any more.
if (m_sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
m_sceneInterface->RemoveSimulatedBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
// Now we can safely clear out the cached heightfield pointer.
m_shapeConfig->SetCachedNativeHeightfield(nullptr);
}
void EditorHeightfieldColliderComponent::InitStaticRigidBody()
{
// Get the transform from the HeightfieldProvider. Because rotation and scale can indirectly affect how the heightfield itself
// is computed and the size of the heightfield, it's possible that the HeightfieldProvider will provide a different transform
// back to us than the one that's directly on that entity.
AZ::Transform transform = AZ::Transform::CreateIdentity();
Physics::HeightfieldProviderRequestsBus::EventResult(
transform, GetEntityId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldTransform);
AzPhysics::StaticRigidBodyConfiguration configuration;
configuration.m_orientation = transform.GetRotation();
configuration.m_position = transform.GetTranslation();
configuration.m_entityId = GetEntityId();
configuration.m_debugName = GetEntity()->GetName();
AzPhysics::ShapeColliderPairList colliderShapePairs;
colliderShapePairs.emplace_back(AZStd::make_shared<Physics::ColliderConfiguration>(m_colliderConfig), m_shapeConfig);
configuration.m_colliderAndShapeData = colliderShapePairs;
if (m_sceneInterface)
{
m_staticRigidBodyHandle = m_sceneInterface->AddSimulatedBody(m_attachedSceneHandle, &configuration);
}
}
void EditorHeightfieldColliderComponent::InitHeightfieldShapeConfiguration()
{
Physics::HeightfieldShapeConfiguration& configuration = static_cast<Physics::HeightfieldShapeConfiguration&>(*m_shapeConfig);
Utils::InitHeightfieldShapeConfiguration(GetEntityId(), configuration);
}
void EditorHeightfieldColliderComponent::RefreshHeightfield()
{
ClearHeightfield();
InitHeightfieldShapeConfiguration();
InitStaticRigidBody();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
AZ::u32 EditorHeightfieldColliderComponent::OnConfigurationChanged()
{
RefreshHeightfield();
return AZ::Edit::PropertyRefreshLevels::None;
}
// AzToolsFramework::EntitySelectionEvents
void EditorHeightfieldColliderComponent::OnSelected()
{
if (auto* physXSystem = GetPhysXSystem())
{
if (!m_physXConfigChangedHandler.IsConnected())
{
physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler);
}
if (!m_onMaterialLibraryChangedEventHandler.IsConnected())
{
physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler);
}
}
}
// AzToolsFramework::EntitySelectionEvents
void EditorHeightfieldColliderComponent::OnDeselected()
{
m_onMaterialLibraryChangedEventHandler.Disconnect();
m_physXConfigChangedHandler.Disconnect();
}
// DisplayCallback
void EditorHeightfieldColliderComponent::Display(AzFramework::DebugDisplayRequests& debugDisplay) const
{
const auto& heightfieldConfig = static_cast<const Physics::HeightfieldShapeConfiguration&>(*m_shapeConfig);
m_colliderDebugDraw.DrawHeightfield(debugDisplay, m_colliderConfig, heightfieldConfig);
}
// SimulatedBodyComponentRequestsBus
void EditorHeightfieldColliderComponent::EnablePhysics()
{
if (!IsPhysicsEnabled() && m_sceneInterface)
{
m_sceneInterface->EnableSimulationOfBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
}
// SimulatedBodyComponentRequestsBus
void EditorHeightfieldColliderComponent::DisablePhysics()
{
if (m_sceneInterface)
{
m_sceneInterface->DisableSimulationOfBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
}
// SimulatedBodyComponentRequestsBus
bool EditorHeightfieldColliderComponent::IsPhysicsEnabled() const
{
if (m_sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body->m_simulating;
}
}
return false;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SimulatedBodyHandle EditorHeightfieldColliderComponent::GetSimulatedBodyHandle() const
{
return m_staticRigidBodyHandle;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SimulatedBody* EditorHeightfieldColliderComponent::GetSimulatedBody()
{
if (m_sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body;
}
}
return nullptr;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SceneQueryHit EditorHeightfieldColliderComponent::RayCast(const AzPhysics::RayCastRequest& request)
{
if (m_sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body->RayCast(request);
}
}
return AzPhysics::SceneQueryHit();
}
// ColliderShapeRequestBus
AZ::Aabb EditorHeightfieldColliderComponent::GetColliderShapeAabb()
{
// Get the Collider AABB directly from the heightfield provider.
AZ::Aabb colliderAabb = AZ::Aabb::CreateNull();
Physics::HeightfieldProviderRequestsBus::EventResult(
colliderAabb, GetEntityId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldAabb);
return colliderAabb;
}
// SimulatedBodyComponentRequestsBus
AZ::Aabb EditorHeightfieldColliderComponent::GetAabb() const
{
// On the SimulatedBodyComponentRequestsBus, get the AABB from the simulated body instead of the collider.
if (m_sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body->GetAabb();
}
}
return AZ::Aabb::CreateNull();
}
} // namespace PhysX
@@ -0,0 +1,104 @@
/*
* 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 <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Editor/DebugDraw.h>
#include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/Shape.h>
#include <PhysX/ColliderShapeBus.h>
namespace PhysX
{
//! Editor PhysX Heightfield Collider Component.
class EditorHeightfieldColliderComponent
: public AzToolsFramework::Components::EditorComponentBase
, protected AzToolsFramework::EntitySelectionEvents::Bus::Handler
, protected DebugDraw::DisplayCallback
, protected AzPhysics::SimulatedBodyComponentRequestsBus::Handler
, protected PhysX::ColliderShapeRequestBus::Handler
, protected Physics::HeightfieldProviderNotificationBus::Handler
{
public:
AZ_EDITOR_COMPONENT(
EditorHeightfieldColliderComponent,
"{C388C3DB-8D2E-4D26-96D3-198EDC799B77}",
AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
EditorHeightfieldColliderComponent();
~EditorHeightfieldColliderComponent();
// AZ::Component
void Activate() override;
void Deactivate() override;
// EditorComponentBase
void BuildGameEntity(AZ::Entity* gameEntity) override;
protected:
// AzToolsFramework::EntitySelectionEvents
void OnSelected() override;
void OnDeselected() override;
// DisplayCallback
void Display(AzFramework::DebugDisplayRequests& debugDisplay) const;
// ColliderShapeRequestBus
AZ::Aabb GetColliderShapeAabb() override;
bool IsTrigger() override
{
// PhysX Heightfields don't support triggers.
return false;
}
// AzPhysics::SimulatedBodyComponentRequestsBus::Handler overrides ...
void EnablePhysics() override;
void DisablePhysics() override;
bool IsPhysicsEnabled() const override;
AZ::Aabb GetAabb() const override;
AzPhysics::SimulatedBody* GetSimulatedBody() override;
AzPhysics::SimulatedBodyHandle GetSimulatedBodyHandle() const override;
AzPhysics::SceneQueryHit RayCast(const AzPhysics::RayCastRequest& request) override;
// Physics::HeightfieldProviderNotificationBus
void OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion) override;
private:
AZ::u32 OnConfigurationChanged();
void ClearHeightfield();
void InitHeightfieldShapeConfiguration();
void InitStaticRigidBody();
void RefreshHeightfield();
DebugDraw::Collider m_colliderDebugDraw; //!< Handles drawing the collider
AzPhysics::SceneInterface* m_sceneInterface{ nullptr };
AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physXConfigChangedHandler;
AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler m_onMaterialLibraryChangedEventHandler;
Physics::ColliderConfiguration m_colliderConfig; //!< Stores collision layers, whether the collider is a trigger, etc.
AZStd::shared_ptr<Physics::HeightfieldShapeConfiguration> m_shapeConfig{ new Physics::HeightfieldShapeConfiguration() };
AzPhysics::SimulatedBodyHandle m_staticRigidBodyHandle =
AzPhysics::InvalidSimulatedBodyHandle; //!< Handle to the body in the editor physics scene if there is no rigid body component.
AzPhysics::SceneHandle m_attachedSceneHandle = AzPhysics::InvalidSceneHandle;
};
} // namespace PhysX
@@ -0,0 +1,365 @@
/*
* 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/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzFramework/Physics/Utils.h>
#include <Source/HeightfieldColliderComponent.h>
#include <Source/RigidBodyStatic.h>
#include <Source/SystemComponent.h>
#include <Source/Utils.h>
#include <PhysX/MathConversion.h>
#include <PhysX/PhysXLocks.h>
#include <Scene/PhysXScene.h>
namespace PhysX
{
void HeightfieldColliderComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<HeightfieldColliderComponent, AZ::Component>()
->Version(1)
->Field("ShapeConfig", &HeightfieldColliderComponent::m_shapeConfig)
;
}
}
void HeightfieldColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PhysicsWorldBodyService"));
provided.push_back(AZ_CRC_CE("PhysXColliderService"));
provided.push_back(AZ_CRC_CE("PhysXHeightfieldColliderService"));
provided.push_back(AZ_CRC_CE("PhysXStaticRigidBodyService"));
}
void HeightfieldColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService"));
}
void HeightfieldColliderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("PhysXColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXStaticRigidBodyService"));
incompatible.push_back(AZ_CRC_CE("PhysXRigidBodyService"));
}
HeightfieldColliderComponent::~HeightfieldColliderComponent()
{
ClearHeightfield();
}
void HeightfieldColliderComponent::Activate()
{
const AZ::EntityId entityId = GetEntityId();
Physics::HeightfieldProviderNotificationBus::Handler::BusConnect(entityId);
ColliderComponentRequestBus::Handler::BusConnect(entityId);
ColliderShapeRequestBus::Handler::BusConnect(entityId);
Physics::CollisionFilteringRequestBus::Handler::BusConnect(entityId);
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(entityId);
RefreshHeightfield();
}
void HeightfieldColliderComponent::Deactivate()
{
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
Physics::CollisionFilteringRequestBus::Handler::BusDisconnect();
ColliderShapeRequestBus::Handler::BusDisconnect();
ColliderComponentRequestBus::Handler::BusDisconnect();
Physics::HeightfieldProviderNotificationBus::Handler::BusDisconnect();
ClearHeightfield();
}
void HeightfieldColliderComponent::OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
{
RefreshHeightfield();
}
void HeightfieldColliderComponent::ClearHeightfield()
{
// There are two references to the heightfield data, we need to clear both to make the heightfield clear out and deallocate:
// - The simulated body has a pointer to the shape, which has a GeometryHolder, which has the Heightfield inside it
// - The shape config is also holding onto a pointer to the Heightfield
// We remove the simulated body first, since we don't want the heightfield to exist any more.
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
sceneInterface && m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
sceneInterface->RemoveSimulatedBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
// Now we can safely clear out the cached heightfield pointer.
Physics::HeightfieldShapeConfiguration& configuration = static_cast<Physics::HeightfieldShapeConfiguration&>(*m_shapeConfig.second);
configuration.SetCachedNativeHeightfield(nullptr);
}
void HeightfieldColliderComponent::InitStaticRigidBody()
{
// Get the transform from the HeightfieldProvider. Because rotation and scale can indirectly affect how the heightfield itself
// is computed and the size of the heightfield, it's possible that the HeightfieldProvider will provide a different transform
// back to us than the one that's directly on that entity.
AZ::Transform transform = AZ::Transform::CreateIdentity();
Physics::HeightfieldProviderRequestsBus::EventResult(
transform, GetEntityId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldTransform);
AzPhysics::StaticRigidBodyConfiguration configuration;
configuration.m_orientation = transform.GetRotation();
configuration.m_position = transform.GetTranslation();
configuration.m_entityId = GetEntityId();
configuration.m_debugName = GetEntity()->GetName();
configuration.m_colliderAndShapeData = GetShapeConfigurations();
if (m_attachedSceneHandle == AzPhysics::InvalidSceneHandle)
{
Physics::DefaultWorldBus::BroadcastResult(m_attachedSceneHandle, &Physics::DefaultWorldRequests::GetDefaultSceneHandle);
}
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
m_staticRigidBodyHandle = sceneInterface->AddSimulatedBody(m_attachedSceneHandle, &configuration);
}
}
void HeightfieldColliderComponent::InitHeightfieldShapeConfiguration()
{
Physics::HeightfieldShapeConfiguration& configuration = static_cast<Physics::HeightfieldShapeConfiguration&>(*m_shapeConfig.second);
Utils::InitHeightfieldShapeConfiguration(GetEntityId(), configuration);
}
void HeightfieldColliderComponent::RefreshHeightfield()
{
ClearHeightfield();
InitHeightfieldShapeConfiguration();
InitStaticRigidBody();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
void HeightfieldColliderComponent::SetShapeConfiguration(const AzPhysics::ShapeColliderPair& shapeConfig)
{
if (GetEntity()->GetState() == AZ::Entity::State::Active)
{
AZ_Warning(
"PhysX", false, "Trying to call SetShapeConfiguration for entity \"%s\" while entity is active.",
GetEntity()->GetName().c_str());
return;
}
m_shapeConfig = shapeConfig;
}
// SimulatedBodyComponentRequestsBus
void HeightfieldColliderComponent::EnablePhysics()
{
if (IsPhysicsEnabled())
{
return;
}
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
sceneInterface->EnableSimulationOfBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
}
// SimulatedBodyComponentRequestsBus
void HeightfieldColliderComponent::DisablePhysics()
{
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
sceneInterface->DisableSimulationOfBody(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
}
// SimulatedBodyComponentRequestsBus
bool HeightfieldColliderComponent::IsPhysicsEnabled() const
{
if (m_staticRigidBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
sceneInterface != nullptr && sceneInterface->IsEnabled(m_attachedSceneHandle)) // check if the scene is enabled
{
if (AzPhysics::SimulatedBody* body =
sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body->m_simulating;
}
}
}
return false;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SimulatedBodyHandle HeightfieldColliderComponent::GetSimulatedBodyHandle() const
{
return m_staticRigidBodyHandle;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SimulatedBody* HeightfieldColliderComponent::GetSimulatedBody()
{
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
return sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle);
}
return nullptr;
}
// SimulatedBodyComponentRequestsBus
AzPhysics::SceneQueryHit HeightfieldColliderComponent::RayCast(const AzPhysics::RayCastRequest& request)
{
if (auto* body = azdynamic_cast<PhysX::StaticRigidBody*>(GetSimulatedBody()))
{
return body->RayCast(request);
}
return AzPhysics::SceneQueryHit();
}
// ColliderComponentRequestBus
AzPhysics::ShapeColliderPairList HeightfieldColliderComponent::GetShapeConfigurations()
{
AzPhysics::ShapeColliderPairList shapeConfigurationList({ m_shapeConfig });
return shapeConfigurationList;
}
AZStd::shared_ptr<Physics::Shape> HeightfieldColliderComponent::GetHeightfieldShape()
{
if (auto* body = azdynamic_cast<PhysX::StaticRigidBody*>(GetSimulatedBody()))
{
// Heightfields should only have one shape
AZ_Assert(body->GetShapeCount() == 1, "Heightfield rigid body has the wrong number of shapes: %zu", body->GetShapeCount());
return body->GetShape(0);
}
return {};
}
// ColliderComponentRequestBus
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> HeightfieldColliderComponent::GetShapes()
{
return { GetHeightfieldShape() };
}
// PhysX::ColliderShapeBus
AZ::Aabb HeightfieldColliderComponent::GetColliderShapeAabb()
{
// Get the Collider AABB directly from the heightfield provider.
AZ::Aabb colliderAabb = AZ::Aabb::CreateNull();
Physics::HeightfieldProviderRequestsBus::EventResult(
colliderAabb, GetEntityId(), &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldAabb);
return colliderAabb;
}
// SimulatedBodyComponentRequestsBus
AZ::Aabb HeightfieldColliderComponent::GetAabb() const
{
// On the SimulatedBodyComponentRequestsBus, get the AABB from the simulated body instead of the collider.
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
if (AzPhysics::SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(m_attachedSceneHandle, m_staticRigidBodyHandle))
{
return body->GetAabb();
}
}
return AZ::Aabb::CreateNull();
}
// CollisionFilteringRequestBus
void HeightfieldColliderComponent::SetCollisionLayer(const AZStd::string& layerName, AZ::Crc32 colliderTag)
{
if (auto heightfield = GetHeightfieldShape())
{
if (Physics::Utils::FilterTag(heightfield->GetTag(), colliderTag))
{
bool success = false;
AzPhysics::CollisionLayer layer;
Physics::CollisionRequestBus::BroadcastResult(
success, &Physics::CollisionRequests::TryGetCollisionLayerByName, layerName, layer);
if (success)
{
heightfield->SetCollisionLayer(layer);
}
}
}
}
// CollisionFilteringRequestBus
AZStd::string HeightfieldColliderComponent::GetCollisionLayerName()
{
AZStd::string layerName;
if (auto heightfield = GetHeightfieldShape())
{
Physics::CollisionRequestBus::BroadcastResult(
layerName, &Physics::CollisionRequests::GetCollisionLayerName, heightfield->GetCollisionLayer());
}
return layerName;
}
// CollisionFilteringRequestBus
void HeightfieldColliderComponent::SetCollisionGroup(const AZStd::string& groupName, AZ::Crc32 colliderTag)
{
if (auto heightfield = GetHeightfieldShape())
{
if (Physics::Utils::FilterTag(heightfield->GetTag(), colliderTag))
{
bool success = false;
AzPhysics::CollisionGroup group;
Physics::CollisionRequestBus::BroadcastResult(
success, &Physics::CollisionRequests::TryGetCollisionGroupByName, groupName, group);
if (success)
{
heightfield->SetCollisionGroup(group);
}
}
}
}
// CollisionFilteringRequestBus
AZStd::string HeightfieldColliderComponent::GetCollisionGroupName()
{
AZStd::string groupName;
if (auto heightfield = GetHeightfieldShape())
{
Physics::CollisionRequestBus::BroadcastResult(
groupName, &Physics::CollisionRequests::GetCollisionGroupName, heightfield->GetCollisionGroup());
}
return groupName;
}
// CollisionFilteringRequestBus
void HeightfieldColliderComponent::ToggleCollisionLayer(const AZStd::string& layerName, AZ::Crc32 colliderTag, bool enabled)
{
if (auto heightfield = GetHeightfieldShape())
{
if (Physics::Utils::FilterTag(heightfield->GetTag(), colliderTag))
{
bool success = false;
AzPhysics::CollisionLayer layer;
Physics::CollisionRequestBus::BroadcastResult(
success, &Physics::CollisionRequests::TryGetCollisionLayerByName, layerName, layer);
if (success)
{
auto group = heightfield->GetCollisionGroup();
group.SetLayer(layer, enabled);
heightfield->SetCollisionGroup(group);
}
}
}
}
} // namespace PhysX
@@ -0,0 +1,104 @@
/*
* 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 <AzFramework/Physics/CollisionBus.h>
#include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
#include <PhysX/ColliderComponentBus.h>
#include <PhysX/ColliderShapeBus.h>
namespace AzPhysics
{
struct SimulatedBody;
}
namespace PhysX
{
class StaticRigidBody;
//! Component that provides a Heightfield Collider and associated Static Rigid Body.
//! The heightfield collider is a bit different from the other shape colliders in that it gets the heightfield data from a
//! HeightfieldProvider, which can control position, rotation, size, and even change its data at runtime.
//!
//! Due to these differences, this component directly implements both the collider and static rigid body services instead of
//! using BaseColliderComponent and StaticRigidBodyComponent.
class HeightfieldColliderComponent
: public AZ::Component
, public ColliderComponentRequestBus::Handler
, public AzPhysics::SimulatedBodyComponentRequestsBus::Handler
, protected PhysX::ColliderShapeRequestBus::Handler
, protected Physics::CollisionFilteringRequestBus::Handler
, protected Physics::HeightfieldProviderNotificationBus::Handler
{
public:
using Configuration = Physics::HeightfieldShapeConfiguration;
AZ_COMPONENT(HeightfieldColliderComponent, "{9A42672C-281A-4CE8-BFDD-EAA1E0FCED76}");
static void Reflect(AZ::ReflectContext* context);
HeightfieldColliderComponent() = default;
~HeightfieldColliderComponent() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
void Activate() override;
void Deactivate() override;
void SetShapeConfiguration(const AzPhysics::ShapeColliderPair& shapeConfig);
protected:
// ColliderComponentRequestBus
AzPhysics::ShapeColliderPairList GetShapeConfigurations() override;
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> GetShapes() override;
// ColliderShapeRequestBus
AZ::Aabb GetColliderShapeAabb() override;
bool IsTrigger() override
{
// PhysX Heightfields don't support triggers.
return false;
}
// CollisionFilteringRequestBus
void SetCollisionLayer(const AZStd::string& layerName, AZ::Crc32 filterTag) override;
AZStd::string GetCollisionLayerName() override;
void SetCollisionGroup(const AZStd::string& groupName, AZ::Crc32 filterTag) override;
AZStd::string GetCollisionGroupName() override;
void ToggleCollisionLayer(const AZStd::string& layerName, AZ::Crc32 filterTag, bool enabled) override;
// SimulatedBodyComponentRequestsBus
void EnablePhysics() override;
void DisablePhysics() override;
bool IsPhysicsEnabled() const override;
AZ::Aabb GetAabb() const override;
AzPhysics::SimulatedBodyHandle GetSimulatedBodyHandle() const override;
AzPhysics::SimulatedBody* GetSimulatedBody() override;
AzPhysics::SceneQueryHit RayCast(const AzPhysics::RayCastRequest& request) override;
// HeightfieldProviderNotificationBus
void OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion) override;
private:
AZStd::shared_ptr<Physics::Shape> GetHeightfieldShape();
void ClearHeightfield();
void InitHeightfieldShapeConfiguration();
void InitStaticRigidBody();
void RefreshHeightfield();
AzPhysics::ShapeColliderPair m_shapeConfig;
AzPhysics::SimulatedBodyHandle m_staticRigidBodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
AzPhysics::SceneHandle m_attachedSceneHandle = AzPhysics::InvalidSceneHandle;
};
} // namespace PhysX
@@ -252,6 +252,22 @@ namespace PhysX
return convex;
}
physx::PxHeightField* SystemComponent::CreateHeightField(const physx::PxHeightFieldSample* samples, AZ::u32 numRows, AZ::u32 numColumns)
{
physx::PxHeightFieldDesc desc;
desc.format = physx::PxHeightFieldFormat::eS16_TM;
desc.nbColumns = numColumns;
desc.nbRows = numRows;
desc.samples.data = samples;
desc.samples.stride = sizeof(physx::PxHeightFieldSample);
physx::PxHeightField* heightfield =
m_physXSystem->GetPxCooking()->createHeightField(desc, m_physXSystem->GetPxPhysics()->getPhysicsInsertionCallback());
AZ_Error("PhysX", heightfield, "Error. Unable to create heightfield");
return heightfield;
}
bool SystemComponent::CookConvexMeshToFile(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount)
{
AZStd::vector<AZ::u8> physxData;
@@ -342,6 +358,14 @@ namespace PhysX
return AZStd::make_shared<PhysX::Material>(materialConfiguration);
}
void SystemComponent::ReleaseNativeHeightfieldObject(void* nativeHeightfieldObject)
{
if (nativeHeightfieldObject)
{
static_cast<physx::PxBase*>(nativeHeightfieldObject)->release();
}
}
void SystemComponent::ReleaseNativeMeshObject(void* nativeMeshObject)
{
if (nativeMeshObject)
+2
View File
@@ -76,6 +76,7 @@ namespace PhysX
physx::PxConvexMesh* CreateConvexMesh(const void* vertices, AZ::u32 vertexNum, AZ::u32 vertexStride) override; // should we use AZ::Vector3* or physx::PxVec3 here?
physx::PxConvexMesh* CreateConvexMeshFromCooked(const void* cookedMeshData, AZ::u32 bufferSize) override;
physx::PxTriangleMesh* CreateTriangleMeshFromCooked(const void* cookedMeshData, AZ::u32 bufferSize) override;
physx::PxHeightField* CreateHeightField(const physx::PxHeightFieldSample* samples, AZ::u32 numRows, AZ::u32 numColumns) override;
bool CookConvexMeshToFile(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount) override;
@@ -112,6 +113,7 @@ namespace PhysX
AZStd::shared_ptr<Physics::Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) override;
void ReleaseNativeMeshObject(void* nativeMeshObject) override;
void ReleaseNativeHeightfieldObject(void* nativeHeightfieldObject) override;
// Assets related data
AZStd::vector<AZStd::unique_ptr<AZ::Data::AssetHandler>> m_assetHandlers;
+193
View File
@@ -9,6 +9,7 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/EBus/Results.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/RTTI/BehaviorContext.h>
@@ -25,6 +26,7 @@
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/SimulatedBodies/StaticRigidBody.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
#include <PhysX/ColliderShapeBus.h>
#include <PhysX/SystemComponentBus.h>
@@ -64,6 +66,136 @@ namespace PhysX
}
}
void CreatePxGeometryFromHeightfield(
const Physics::HeightfieldShapeConfiguration& heightfieldConfig, physx::PxGeometryHolder& pxGeometry)
{
physx::PxHeightField* heightfield = nullptr;
const AZ::Vector2 gridSpacing = heightfieldConfig.GetGridResolution();
const int32_t numCols = heightfieldConfig.GetNumColumns();
const int32_t numRows = heightfieldConfig.GetNumRows();
const float rowScale = gridSpacing.GetX();
const float colScale = gridSpacing.GetY();
const float minHeightBounds = heightfieldConfig.GetMinHeightBounds();
const float maxHeightBounds = heightfieldConfig.GetMaxHeightBounds();
const float halfBounds{ (maxHeightBounds - minHeightBounds) / 2.0f };
// We're making the assumption right now that the min/max bounds are centered around 0.
// If we ever want to allow off-center bounds, we'll need to fix up the float-to-int16 height math below
// to account for it.
AZ_Assert(
AZ::IsClose(-halfBounds, minHeightBounds) && AZ::IsClose(halfBounds, maxHeightBounds),
"Min/Max height bounds aren't centered around 0, the height conversions below will be incorrect.");
AZ_Assert(
maxHeightBounds >= minHeightBounds,
"Max height bounds is less than min height bounds, the height conversions below will be incorrect.");
// To convert our floating-point heights to fixed-point representation inside of an int16, we need a scale factor
// for the conversion. The scale factor is used to map the most important bits of our floating-point height to the
// full 16-bit range.
// Note that the scaleFactor choice here affects overall precision. For each bit that the integer part of our max
// height uses, that's one less bit for the fractional part.
const float scaleFactor = (maxHeightBounds <= minHeightBounds) ? 1.0f : AZStd::numeric_limits<int16_t>::max() / halfBounds;
const float heightScale{ 1.0f / scaleFactor };
const uint8_t physxMaximumMaterialIndex = 0x7f;
// Delete the cached heightfield object if it is there, and create a new one and save in the shape configuration
heightfieldConfig.SetCachedNativeHeightfield(nullptr);
const AZStd::vector<Physics::HeightMaterialPoint>& samples = heightfieldConfig.GetSamples();
AZ_Assert(samples.size() == numRows * numCols, "GetHeightsAndMaterials returned wrong sized heightfield");
if (!samples.empty())
{
AZStd::vector<physx::PxHeightFieldSample> physxSamples(samples.size());
for (int32_t row = 0; row < numRows; row++)
{
const bool lastRowIndex = (row == (numRows - 1));
for (int32_t col = 0; col < numCols; col++)
{
const bool lastColumnIndex = (col == (numCols - 1));
auto GetIndex = [numCols](int32_t row, int32_t col)
{
return (row * numCols) + col;
};
const int32_t sampleIndex = GetIndex(row, col);
const Physics::HeightMaterialPoint& currentSample = samples[sampleIndex];
physx::PxHeightFieldSample& currentPhysxSample = physxSamples[sampleIndex];
AZ_Assert(currentSample.m_materialIndex < physxMaximumMaterialIndex, "MaterialIndex must be less than 128");
currentPhysxSample.height = azlossy_cast<physx::PxI16>(
AZ::GetClamp(currentSample.m_height, minHeightBounds, maxHeightBounds) * scaleFactor);
if (lastRowIndex || lastColumnIndex)
{
// In PhysX, the material indices refer to the quad down and to the right of the sample.
// If we're in the last row or last column, there aren't any quads down or to the right,
// so just clear these out.
currentPhysxSample.materialIndex0 = 0;
currentPhysxSample.materialIndex1 = 0;
}
else
{
// Our source data is providing one material index per vertex, but PhysX wants one material index
// per triangle. The heuristic that we'll go with for selecting the material index is to choose
// the material for the vertex that's not on the diagonal of each triangle.
// Ex: A *---* B
// | / | For this, we'll use A for index0 and D for index1.
// C *---* D
//
// Ex: A *---* B
// | \ | For this, we'll use C for index0 and B for index1.
// C *---* D
//
// This is a pretty arbitrary choice, so the heuristic might need to be revisited over time if this
// causes incorrect or unpredictable physics material mappings.
switch (currentSample.m_quadMeshType)
{
case Physics::QuadMeshType::SubdivideUpperLeftToBottomRight:
currentPhysxSample.materialIndex0 = samples[GetIndex(row + 1, col)].m_materialIndex;
currentPhysxSample.materialIndex1 = samples[GetIndex(row, col + 1)].m_materialIndex;
// Set the tesselation flag to say that we need to go from UL to BR
currentPhysxSample.materialIndex0.setBit();
break;
case Physics::QuadMeshType::SubdivideBottomLeftToUpperRight:
currentPhysxSample.materialIndex0 = currentSample.m_materialIndex;
currentPhysxSample.materialIndex1 = samples[GetIndex(row + 1, col + 1)].m_materialIndex;
break;
case Physics::QuadMeshType::Hole:
currentPhysxSample.materialIndex0 = physx::PxHeightFieldMaterial::eHOLE;
currentPhysxSample.materialIndex1 = physx::PxHeightFieldMaterial::eHOLE;
break;
default:
AZ_Warning("PhysX Heightfield", false, "Unhandled case in CreatePxGeometryFromConfig");
currentPhysxSample.materialIndex0 = 0;
currentPhysxSample.materialIndex1 = 0;
break;
}
}
}
}
SystemRequestsBus::BroadcastResult(heightfield, &SystemRequests::CreateHeightField, physxSamples.data(), numRows, numCols);
}
if (heightfield)
{
heightfieldConfig.SetCachedNativeHeightfield(heightfield);
physx::PxHeightFieldGeometry hfGeom(heightfield, physx::PxMeshGeometryFlags(), heightScale, rowScale, colScale);
pxGeometry.storeAny(hfGeom);
}
}
bool CreatePxGeometryFromConfig(const Physics::ShapeConfiguration& shapeConfiguration, physx::PxGeometryHolder& pxGeometry)
{
if (!shapeConfiguration.m_scale.IsGreaterThan(AZ::Vector3::CreateZero()))
@@ -170,6 +302,14 @@ namespace PhysX
"Please iterate over m_colliderShapes in the asset and call this function for each of them.");
return false;
}
case Physics::ShapeType::Heightfield:
{
const Physics::HeightfieldShapeConfiguration& heightfieldConfig =
static_cast<const Physics::HeightfieldShapeConfiguration&>(shapeConfiguration);
CreatePxGeometryFromHeightfield(heightfieldConfig, pxGeometry);
break;
}
default:
AZ_Warning("PhysX Rigid Body", false, "Shape not supported in PhysX. Shape Type: %d", shapeType);
return false;
@@ -219,6 +359,26 @@ namespace PhysX
physx::PxQuat pxQuat(AZ::Constants::HalfPi, physx::PxVec3(0.0f, 1.0f, 0.0f));
shape->setLocalPose(physx::PxTransform(pxQuat));
}
else if (pxGeomHolder.getType() == physx::PxGeometryType::eHEIGHTFIELD)
{
const Physics::HeightfieldShapeConfiguration& heightfieldConfig =
static_cast<const Physics::HeightfieldShapeConfiguration&>(shapeConfiguration);
// PhysX heightfields have the origin at the corner, not the center, so add an offset to the passed-in transform
// to account for this difference.
const AZ::Vector2 gridSpacing = heightfieldConfig.GetGridResolution();
AZ::Vector3 offset(
-(gridSpacing.GetX() * heightfieldConfig.GetNumColumns() / 2.0f),
-(gridSpacing.GetY() * heightfieldConfig.GetNumRows() / 2.0f),
0.0f);
// PhysX heightfields are always defined to have the height in the Y direction, not the Z direction, so we need
// to provide additional rotations to make it Z-up.
physx::PxQuat pxQuat = PxMathConvert(
AZ::Quaternion::CreateFromEulerAnglesRadians(AZ::Vector3(AZ::Constants::HalfPi, AZ::Constants::HalfPi, 0.0f)));
physx::PxTransform pxHeightfieldTransform = physx::PxTransform(PxMathConvert(offset), pxQuat);
shape->setLocalPose(pxHeightfieldTransform);
}
// Handle a possible misconfiguration when a shape is set to be both simulated & trigger. This is illegal in PhysX.
shape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, colliderConfiguration.m_isSimulated && !colliderConfiguration.m_isTrigger);
@@ -1357,6 +1517,39 @@ namespace PhysX
return entityWorldTransformWithoutScale * jointLocalTransformWithoutScale;
}
void InitHeightfieldShapeConfiguration(AZ::EntityId entityId, Physics::HeightfieldShapeConfiguration& configuration)
{
configuration = Physics::HeightfieldShapeConfiguration();
AZ::Vector2 gridSpacing(1.0f);
Physics::HeightfieldProviderRequestsBus::EventResult(
gridSpacing, entityId, &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridSpacing);
configuration.SetGridResolution(gridSpacing);
int32_t numRows = 0;
int32_t numColumns = 0;
Physics::HeightfieldProviderRequestsBus::Event(
entityId, &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldGridSize, numColumns, numRows);
configuration.SetNumRows(numRows);
configuration.SetNumColumns(numColumns);
float minHeightBounds = 0.0f;
float maxHeightBounds = 0.0f;
Physics::HeightfieldProviderRequestsBus::Event(
entityId, &Physics::HeightfieldProviderRequestsBus::Events::GetHeightfieldHeightBounds, minHeightBounds, maxHeightBounds);
configuration.SetMinHeightBounds(minHeightBounds);
configuration.SetMaxHeightBounds(maxHeightBounds);
AZStd::vector<Physics::HeightMaterialPoint> samples;
Physics::HeightfieldProviderRequestsBus::EventResult(
samples, entityId, &Physics::HeightfieldProviderRequestsBus::Events::GetHeightsAndMaterials);
configuration.SetSamples(samples);
}
} // namespace Utils
namespace ReflectionUtils
+2
View File
@@ -188,6 +188,8 @@ namespace PhysX
//! Returns defaultValue if the input is infinite or NaN, otherwise returns the input unchanged.
const AZ::Vector3& Sanitize(const AZ::Vector3& input, const AZ::Vector3& defaultValue = AZ::Vector3::CreateZero());
void InitHeightfieldShapeConfiguration(AZ::EntityId entityId, Physics::HeightfieldShapeConfiguration& configuration);
namespace Geometry
{
using PointList = AZStd::vector<AZ::Vector3>;
+2
View File
@@ -27,6 +27,8 @@ set(FILES
Source/EditorFixedJointComponent.h
Source/EditorHingeJointComponent.cpp
Source/EditorHingeJointComponent.h
Source/EditorHeightfieldColliderComponent.cpp
Source/EditorHeightfieldColliderComponent.h
Source/EditorJointComponent.cpp
Source/EditorJointComponent.h
Source/Pipeline/MeshExporter.cpp
+2
View File
@@ -35,6 +35,8 @@ set(FILES
Source/MeshColliderComponent.h
Source/BoxColliderComponent.h
Source/BoxColliderComponent.cpp
Source/HeightfieldColliderComponent.h
Source/HeightfieldColliderComponent.cpp
Source/SphereColliderComponent.h
Source/SphereColliderComponent.cpp
Source/CapsuleColliderComponent.h
+1
View File
@@ -111,6 +111,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest
AZ::AzFramework
Gem::LmbrCentral.Mocks
Gem::GradientSignal.Mocks
Gem::Terrain.Mocks
Gem::Terrain.Static
)
+35 -7
View File
@@ -10,11 +10,12 @@
#include <gmock/gmock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <TerrainSystem/TerrainSystemBus.h>
namespace UnitTest
{
class MockTerrainSystemService : private Terrain::TerrainSystemServiceRequestBus::Handler
{
public:
@@ -69,11 +70,7 @@ namespace UnitTest
Terrain::TerrainAreaHeightRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(GetHeight, void(
const AZ::Vector3& inPosition,
AZ::Vector3& outPosition,
bool& terrainExists));
MOCK_METHOD3(GetHeight, void(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, bool& terrainExists));
};
class MockTerrainSpawnerRequests : public Terrain::TerrainSpawnerRequestBus::Handler
@@ -92,4 +89,35 @@ namespace UnitTest
MOCK_METHOD2(GetPriority, void(AZ::u32& outLayer, AZ::u32& outPriority));
MOCK_METHOD0(GetUseGroundPlane, bool());
};
}
class MockTerrainDataRequests : public AzFramework::Terrain::TerrainDataRequestBus::Handler
{
public:
MockTerrainDataRequests()
{
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusConnect();
}
~MockTerrainDataRequests()
{
AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect();
}
MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, AZ::Vector2());
MOCK_METHOD1(SetTerrainHeightQueryResolution, void(AZ::Vector2));
MOCK_CONST_METHOD0(GetTerrainAabb, AZ::Aabb());
MOCK_METHOD1(SetTerrainAabb, void(const AZ::Aabb&));
MOCK_CONST_METHOD3(GetHeight, float(AZ::Vector3, Sampler, bool*));
MOCK_CONST_METHOD4(GetHeightFromFloats, float(float, float, Sampler, bool*));
MOCK_CONST_METHOD3(GetMaxSurfaceWeight, AzFramework::SurfaceData::SurfaceTagWeight(AZ::Vector3, Sampler, bool*));
MOCK_CONST_METHOD3(GetMaxSurfaceWeightFromVector2, AzFramework::SurfaceData::SurfaceTagWeight(const AZ::Vector2&, Sampler, bool*));
MOCK_CONST_METHOD4(GetMaxSurfaceWeightFromFloats, AzFramework::SurfaceData::SurfaceTagWeight(float, float, Sampler, bool*));
MOCK_CONST_METHOD4(GetSurfaceWeights, void(const AZ::Vector3&, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet&, Sampler, bool*));
MOCK_CONST_METHOD4(GetSurfaceWeightsFromVector2, void(const AZ::Vector2&, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet&, Sampler, bool*));
MOCK_CONST_METHOD5(GetSurfaceWeightsFromFloats, void(float, float, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet&, Sampler, bool*));
MOCK_CONST_METHOD3(GetMaxSurfaceName, const char*(AZ::Vector3, Sampler, bool*));
MOCK_CONST_METHOD3(GetIsHoleFromFloats, bool(float, float, Sampler));
MOCK_CONST_METHOD3(GetNormal, AZ::Vector3(AZ::Vector3, Sampler, bool*));
MOCK_CONST_METHOD4(GetNormalFromFloats, AZ::Vector3(float, float, Sampler, bool*));
};
} // namespace UnitTest
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <Terrain/Ebuses/TerrainAreaSurfaceRequestBus.h>
namespace UnitTest
{
class MockTerrainAreaSurfaceRequestBus : public Terrain::TerrainAreaSurfaceRequestBus::Handler
{
public:
MockTerrainAreaSurfaceRequestBus(AZ::EntityId entityId)
{
Terrain::TerrainAreaSurfaceRequestBus::Handler::BusConnect(entityId);
}
~MockTerrainAreaSurfaceRequestBus()
{
Terrain::TerrainAreaSurfaceRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD0(Activate, void());
MOCK_METHOD0(Deactivate, void());
MOCK_CONST_METHOD2(GetSurfaceWeights, void(const AZ::Vector3&, AzFramework::SurfaceData::OrderedSurfaceTagWeightSet&));
};
} // namespace UnitTest
@@ -0,0 +1,39 @@
/*
* 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>
namespace UnitTest
{
class MockTerrainLayerSpawnerComponent
: public AZ::Component
{
public:
AZ_COMPONENT(MockTerrainLayerSpawnerComponent, "{9F27C980-9826-4063-86D8-E981C1E842A3}");
static void Reflect([[maybe_unused]] AZ::ReflectContext* context)
{
}
void Activate() override
{
}
void Deactivate() override
{
}
private:
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("TerrainAreaService"));
}
};
} //namespace UnitTest
@@ -65,7 +65,7 @@ namespace Terrain
void TerrainHeightGradientListComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("TerrainAreaService"));
services.push_back(AZ_CRC_CE("BoxShapeService"));
services.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService"));
}
void TerrainHeightGradientListComponent::Reflect(AZ::ReflectContext* context)
@@ -102,7 +102,6 @@ namespace Terrain
void TerrainLayerSpawnerComponent::Activate()
{
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
TerrainSpawnerRequestBus::Handler::BusConnect(GetEntityId());
@@ -114,8 +113,6 @@ namespace Terrain
TerrainSystemServiceRequestBus::Broadcast(&TerrainSystemServiceRequestBus::Events::UnregisterArea, GetEntityId());
TerrainSpawnerRequestBus::Handler::BusDisconnect();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
}
bool TerrainLayerSpawnerComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@@ -138,13 +135,12 @@ namespace Terrain
return false;
}
void TerrainLayerSpawnerComponent::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, [[maybe_unused]] const AZ::Transform& world)
{
RefreshArea();
}
void TerrainLayerSpawnerComponent::OnShapeChanged([[maybe_unused]] ShapeChangeReasons changeReason)
{
// This will notify us of both shape changes and transform changes.
// It's important to use this event for transform changes instead of listening to OnTransformChanged, because we need to guarantee
// the shape has received the transform change message and updated its internal state before passing it along to us.
RefreshArea();
}
@@ -18,7 +18,6 @@
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <TerrainSystem/TerrainSystemBus.h>
#include <AzCore/Component/TransformBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <AzCore/Math/Aabb.h>
@@ -56,7 +55,6 @@ namespace Terrain
class TerrainLayerSpawnerComponent
: public AZ::Component
, private AZ::TransformNotificationBus::Handler
, private LmbrCentral::ShapeComponentNotificationsBus::Handler
, private Terrain::TerrainSpawnerRequestBus::Handler
{
@@ -81,10 +79,6 @@ namespace Terrain
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
protected:
//////////////////////////////////////////////////////////////////////////
// AZ::TransformNotificationBus::Handler
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// ShapeComponentNotificationsBus
void OnShapeChanged(ShapeChangeReasons changeReason) override;
@@ -0,0 +1,321 @@
/*
* 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 <Components/TerrainPhysicsColliderComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
namespace Terrain
{
void TerrainPhysicsColliderConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<TerrainPhysicsColliderConfig, AZ::ComponentConfig>()
->Version(1)
;
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainPhysicsColliderConfig>(
"Terrain Physics Collider Component",
"Provides terrain data to a physics collider with configurable surface mappings.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
}
}
}
void TerrainPhysicsColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService"));
}
void TerrainPhysicsColliderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("PhysicsHeightfieldProviderService"));
}
void TerrainPhysicsColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("AxisAlignedBoxShapeService"));
}
void TerrainPhysicsColliderComponent::Reflect(AZ::ReflectContext* context)
{
TerrainPhysicsColliderConfig::Reflect(context);
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<TerrainPhysicsColliderComponent, AZ::Component>()
->Version(0)
->Field("Configuration", &TerrainPhysicsColliderComponent::m_configuration)
;
}
}
TerrainPhysicsColliderComponent::TerrainPhysicsColliderComponent(const TerrainPhysicsColliderConfig& configuration)
: m_configuration(configuration)
{
}
TerrainPhysicsColliderComponent::TerrainPhysicsColliderComponent()
{
}
void TerrainPhysicsColliderComponent::Activate()
{
const auto entityId = GetEntityId();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(entityId);
Physics::HeightfieldProviderRequestsBus::Handler::BusConnect(entityId);
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect();
NotifyListenersOfHeightfieldDataChange();
}
void TerrainPhysicsColliderComponent::Deactivate()
{
AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect();
Physics::HeightfieldProviderRequestsBus::Handler ::BusDisconnect();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
}
bool TerrainPhysicsColliderComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
{
if (auto config = azrtti_cast<const TerrainPhysicsColliderConfig*>(baseConfig))
{
m_configuration = *config;
return true;
}
return false;
}
bool TerrainPhysicsColliderComponent::WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const
{
if (auto config = azrtti_cast<TerrainPhysicsColliderConfig*>(outBaseConfig))
{
*config = m_configuration;
return true;
}
return false;
}
void TerrainPhysicsColliderComponent::NotifyListenersOfHeightfieldDataChange()
{
AZ::Aabb worldSize = AZ::Aabb::CreateNull();
LmbrCentral::ShapeComponentRequestsBus::EventResult(
worldSize, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
Physics::HeightfieldProviderNotificationBus::Broadcast(
&Physics::HeightfieldProviderNotificationBus::Events::OnHeightfieldDataChanged, worldSize);
}
void TerrainPhysicsColliderComponent::OnShapeChanged([[maybe_unused]] ShapeChangeReasons changeReason)
{
// This will notify us of both shape changes and transform changes.
// It's important to use this event for transform changes instead of listening to OnTransformChanged, because we need to guarantee
// the shape has received the transform change message and updated its internal state before passing it along to us.
NotifyListenersOfHeightfieldDataChange();
}
void TerrainPhysicsColliderComponent::OnTerrainDataCreateEnd()
{
// The terrain system has finished creating itself, so we should now have data for creating a heightfield.
NotifyListenersOfHeightfieldDataChange();
}
void TerrainPhysicsColliderComponent::OnTerrainDataDestroyBegin()
{
// The terrain system is starting to destroy itself, so notify listeners of a change since the heightfield
// will no longer have any valid data.
NotifyListenersOfHeightfieldDataChange();
}
void TerrainPhysicsColliderComponent::OnTerrainDataChanged(
[[maybe_unused]] const AZ::Aabb& dirtyRegion, [[maybe_unused]] TerrainDataChangedMask dataChangedMask)
{
NotifyListenersOfHeightfieldDataChange();
}
AZ::Aabb TerrainPhysicsColliderComponent::GetHeightfieldAabb() const
{
AZ::Aabb worldSize = AZ::Aabb::CreateNull();
LmbrCentral::ShapeComponentRequestsBus::EventResult(
worldSize, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
auto vector2Floor = [](const AZ::Vector2& in)
{
return AZ::Vector2(floor(in.GetX()), floor(in.GetY()));
};
auto vector2Ceil = [](const AZ::Vector2& in)
{
return AZ::Vector2(ceil(in.GetX()), ceil(in.GetY()));
};
const AZ::Vector2 gridResolution = GetHeightfieldGridSpacing();
const AZ::Vector3 boundsMin = worldSize.GetMin();
const AZ::Vector3 boundsMax = worldSize.GetMax();
const AZ::Vector2 gridMinBoundLower = vector2Floor(AZ::Vector2(boundsMin) / gridResolution) * gridResolution;
const AZ::Vector2 gridMaxBoundUpper = vector2Ceil(AZ::Vector2(boundsMax) / gridResolution) * gridResolution;
return AZ::Aabb::CreateFromMinMaxValues(
gridMinBoundLower.GetX(), gridMinBoundLower.GetY(), boundsMin.GetZ(),
gridMaxBoundUpper.GetX(), gridMaxBoundUpper.GetY(), boundsMax.GetZ()
);
}
void TerrainPhysicsColliderComponent::GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const
{
AZ::Aabb heightfieldAabb = GetHeightfieldAabb();
// Because our terrain heights are relative to the center of the bounding box, the min and max allowable heights are also
// relative to the center. They are also clamped to the size of the bounding box.
minHeightBounds = -(heightfieldAabb.GetZExtent() / 2.0f);
maxHeightBounds = heightfieldAabb.GetZExtent() / 2.0f;
}
AZ::Transform TerrainPhysicsColliderComponent::GetHeightfieldTransform() const
{
// We currently don't support rotation of terrain heightfields.
AZ::Vector3 translate;
AZ::TransformBus::EventResult(translate, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);
AZ::Transform transform = AZ::Transform::CreateTranslation(translate);
return transform;
}
void TerrainPhysicsColliderComponent::GenerateHeightsInBounds(AZStd::vector<float>& heights) const
{
const AZ::Vector2 gridResolution = GetHeightfieldGridSpacing();
AZ::Aabb worldSize = GetHeightfieldAabb();
const float worldCenterZ = worldSize.GetCenter().GetZ();
int32_t gridWidth, gridHeight;
GetHeightfieldGridSize(gridWidth, gridHeight);
heights.clear();
heights.reserve(gridWidth * gridHeight);
for (int32_t row = 0; row < gridHeight; row++)
{
const float y = row * gridResolution.GetY() + worldSize.GetMin().GetY();
for (int32_t col = 0; col < gridWidth; col++)
{
const float x = col * gridResolution.GetX() + worldSize.GetMin().GetX();
float height = 0.0f;
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y,
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, nullptr);
heights.emplace_back(height - worldCenterZ);
}
}
}
void TerrainPhysicsColliderComponent::GenerateHeightsAndMaterialsInBounds(
AZStd::vector<Physics::HeightMaterialPoint>& heightMaterials) const
{
const AZ::Vector2 gridResolution = GetHeightfieldGridSpacing();
AZ::Aabb worldSize = GetHeightfieldAabb();
const float worldCenterZ = worldSize.GetCenter().GetZ();
const float worldHeightBoundsMin = worldSize.GetMin().GetZ();
const float worldHeightBoundsMax = worldSize.GetMax().GetZ();
int32_t gridWidth, gridHeight;
GetHeightfieldGridSize(gridWidth, gridHeight);
heightMaterials.clear();
heightMaterials.reserve(gridWidth * gridHeight);
for (int32_t row = 0; row < gridHeight; row++)
{
const float y = row * gridResolution.GetY() + worldSize.GetMin().GetY();
for (int32_t col = 0; col < gridWidth; col++)
{
const float x = col * gridResolution.GetX() + worldSize.GetMin().GetX();
float height = 0.0f;
bool terrainExists = true;
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
height, &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats, x, y,
AzFramework::Terrain::TerrainDataRequests::Sampler::DEFAULT, &terrainExists);
// Any heights that fall outside the range of our bounding box will get turned into holes.
if ((height < worldHeightBoundsMin) || (height > worldHeightBoundsMax))
{
height = worldHeightBoundsMin;
terrainExists = false;
}
Physics::HeightMaterialPoint point;
point.m_height = height - worldCenterZ;
point.m_quadMeshType = terrainExists ? Physics::QuadMeshType::SubdivideUpperLeftToBottomRight : Physics::QuadMeshType::Hole;
heightMaterials.emplace_back(point);
}
}
}
AZ::Vector2 TerrainPhysicsColliderComponent::GetHeightfieldGridSpacing() const
{
AZ::Vector2 gridResolution = AZ::Vector2(1.0f);
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(
gridResolution, &AzFramework::Terrain::TerrainDataRequests::GetTerrainHeightQueryResolution);
return gridResolution;
}
void TerrainPhysicsColliderComponent::GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const
{
const AZ::Vector2 gridResolution = GetHeightfieldGridSpacing();
const AZ::Aabb bounds = GetHeightfieldAabb();
numColumns = aznumeric_cast<int32_t>((bounds.GetMax().GetX() - bounds.GetMin().GetX()) / gridResolution.GetX());
numRows = aznumeric_cast<int32_t>((bounds.GetMax().GetY() - bounds.GetMin().GetY()) / gridResolution.GetY());
}
AZStd::vector<Physics::MaterialId> TerrainPhysicsColliderComponent::GetMaterialList() const
{
return AZStd::vector<Physics::MaterialId>();
}
AZStd::vector<float> TerrainPhysicsColliderComponent::GetHeights() const
{
AZStd::vector<float> heights;
GenerateHeightsInBounds(heights);
return heights;
}
AZStd::vector<Physics::HeightMaterialPoint> TerrainPhysicsColliderComponent::GetHeightsAndMaterials() const
{
AZStd::vector<Physics::HeightMaterialPoint> heightMaterials;
GenerateHeightsAndMaterialsInBounds(heightMaterials);
return heightMaterials;
}
}
@@ -0,0 +1,91 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
#include <AzFramework/Physics/Material.h>
#include <TerrainSystem/TerrainSystemBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
namespace LmbrCentral
{
template<typename, typename>
class EditorWrappedComponentBase;
}
namespace Terrain
{
class TerrainPhysicsColliderConfig
: public AZ::ComponentConfig
{
public:
AZ_CLASS_ALLOCATOR(TerrainPhysicsColliderConfig, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainPhysicsColliderConfig, "{E9EADB8F-C3A5-4B9C-A62D-2DBC86B4CE59}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
};
class TerrainPhysicsColliderComponent
: public AZ::Component
, public Physics::HeightfieldProviderRequestsBus::Handler
, protected LmbrCentral::ShapeComponentNotificationsBus::Handler
, protected AzFramework::Terrain::TerrainDataNotificationBus::Handler
{
public:
template<typename, typename>
friend class LmbrCentral::EditorWrappedComponentBase;
AZ_COMPONENT(TerrainPhysicsColliderComponent, "{33C20287-1D37-44D0-96A0-2C3766E23624}");
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void Reflect(AZ::ReflectContext* context);
TerrainPhysicsColliderComponent(const TerrainPhysicsColliderConfig& configuration);
TerrainPhysicsColliderComponent();
~TerrainPhysicsColliderComponent() = default;
// HeightfieldProviderRequestsBus
AZ::Vector2 GetHeightfieldGridSpacing() const override;
void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const override;
void GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const override;
AZ::Aabb GetHeightfieldAabb() const override;
AZ::Transform GetHeightfieldTransform() const override;
AZStd::vector<Physics::MaterialId> GetMaterialList() const override;
AZStd::vector<float> GetHeights() const override;
AZStd::vector<Physics::HeightMaterialPoint> GetHeightsAndMaterials() const override;
protected:
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
void GenerateHeightsInBounds(AZStd::vector<float>& heights) const;
void GenerateHeightsAndMaterialsInBounds(AZStd::vector<Physics::HeightMaterialPoint>& heightMaterials) const;
void NotifyListenersOfHeightfieldDataChange();
// ShapeComponentNotificationsBus
void OnShapeChanged(ShapeChangeReasons changeReason) override;
void OnTerrainDataCreateEnd() override;
void OnTerrainDataDestroyBegin() override;
void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override;
private:
TerrainPhysicsColliderConfig m_configuration;
};
}
@@ -0,0 +1,24 @@
/*
* 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 <EditorComponents/EditorTerrainPhysicsColliderComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace Terrain
{
void EditorTerrainPhysicsColliderComponent::Reflect(AZ::ReflectContext* context)
{
// Call ReflectSubClass in EditorWrappedComponentBase to handle all the boilerplate reflection.
BaseClassType::ReflectSubClass<EditorTerrainPhysicsColliderComponent, BaseClassType>(
context, 1,
&LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType,
typename BaseClassType::WrappedConfigType, 1>
);
}
}
@@ -0,0 +1,32 @@
/*
* 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 <Components/TerrainPhysicsColliderComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
namespace Terrain
{
class EditorTerrainPhysicsColliderComponent
: public LmbrCentral::EditorWrappedComponentBase<TerrainPhysicsColliderComponent, TerrainPhysicsColliderConfig>
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainPhysicsColliderComponent, TerrainPhysicsColliderConfig>;
AZ_EDITOR_COMPONENT(EditorTerrainPhysicsColliderComponent, "{C43FAB8F-3968-46A6-920E-E84AEDED3DF5}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
static constexpr auto s_categoryName = "Terrain";
static constexpr auto s_componentName = "Terrain Physics Heightfield Collider";
static constexpr auto s_componentDescription = "Provides terrain data to a physics collider in the form of a heightfield and surface->material mapping.";
static constexpr auto s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg";
static constexpr auto s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg";
static constexpr auto s_helpUrl = "";
};
}
@@ -13,6 +13,7 @@
#include <EditorComponents/EditorTerrainSystemComponent.h>
#include <EditorComponents/EditorTerrainWorldComponent.h>
#include <EditorComponents/EditorTerrainWorldDebuggerComponent.h>
#include <EditorComponents/EditorTerrainPhysicsColliderComponent.h>
#include <EditorComponents/EditorTerrainWorldRendererComponent.h>
#include <TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.h>
#include <TerrainRenderer/EditorComponents/EditorTerrainMacroMaterialComponent.h>
@@ -32,6 +33,7 @@ namespace Terrain
Terrain::EditorTerrainSurfaceMaterialsListComponent::CreateDescriptor(),
Terrain::EditorTerrainWorldComponent::CreateDescriptor(),
Terrain::EditorTerrainWorldDebuggerComponent::CreateDescriptor(),
Terrain::EditorTerrainPhysicsColliderComponent::CreateDescriptor(),
Terrain::EditorTerrainWorldRendererComponent::CreateDescriptor(),
});

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