Integrating up through commit 90f050496

This commit is contained in:
alexpete
2021-04-07 14:03:29 -07:00
parent 8f2ed080a9
commit c2cbd430fe
2694 changed files with 285622 additions and 176874 deletions
@@ -93,8 +93,8 @@ namespace AzFramework
/// Execute a function in a new thread and pump the system event loop at the specified frequency until the thread returns.
virtual void PumpSystemEventLoopWhileDoingWorkInNewThread(const AZStd::chrono::milliseconds& /*eventPumpFrequency*/,
const AZStd::function<void()>& /*workForNewThread*/,
const char* /*newThreadName*/) {}
const AZStd::function<void()>& /*workForNewThread*/,
const char* /*newThreadName*/) {}
/// Run the main loop until ExitMainLoop is called.
virtual void RunMainLoop() {}
@@ -114,6 +114,21 @@ namespace AzFramework
/// Calculate the branch token from the current application's engine root
virtual void CalculateBranchTokenForEngineRoot(AZStd::string& token) const = 0;
/// Returns true if Prefab System is enabled, false if Legacy Slice System is enabled
virtual bool IsPrefabSystemEnabled() const { return true; }
/// Returns true if the additional work in progress Prefab features are enabled, false otherwise
virtual bool ArePrefabWipFeaturesEnabled() const { return false; }
/// Sets whether or not the Prefab System should be enabled. The application will need to be restarted when this changes
virtual void SetPrefabSystemEnabled([[maybe_unused]] bool enable) {}
/// Returns true if Prefab System is enabled for use with levels, false if legacy level system is enabled (level.pak)
virtual bool IsPrefabSystemForLevelsEnabled() const { return false; }
/// Returns true if code should assert when the Legacy Slice System is used
virtual bool ShouldAssertForLegacySlicesUsage() const { return false; }
/*!
* Returns a Type Uuid of the component for the given componentId and entityId.
* if no component matches the entity and component Id pair, a Null Uuid is returned
@@ -89,6 +89,10 @@ namespace AzFramework
{
namespace ApplicationInternal
{
static constexpr const char s_prefabSystemKey[] = "/Amazon/Preferences/EnablePrefabSystem";
static constexpr const char s_prefabWipSystemKey[] = "/Amazon/Preferences/EnablePrefabSystemWipFeatures";
static constexpr const char s_legacySlicesAssertKey[] = "/Amazon/Preferences/ShouldAssertForLegacySlicesUsage";
// A Helper function that can load an app descriptor from file.
AZ::Outcome<AZStd::unique_ptr<AZ::ComponentApplication::Descriptor>, AZStd::string> LoadDescriptorFromFilePath(const char* appDescriptorFilePath, AZ::SerializeContext& serializeContext)
{
@@ -411,9 +415,10 @@ namespace AzFramework
// UserSettingsFileLocatorBus
AZStd::string Application::ResolveFilePath([[maybe_unused]] AZ::u32 providerId)
{
AZStd::string result;
AzFramework::StringFunc::Path::Join(GetEngineRoot(), "UserSettings.xml", result, /*bCaseInsenitive*/false);
return result;
AZ::IO::Path userSettingsPath;
m_settingsRegistry->Get(userSettingsPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath);
userSettingsPath /= "UserSettings.xml";
return userSettingsPath.Native();
}
AZ::Component* Application::EnsureComponentAdded(AZ::Entity* systemEntity, const AZ::Uuid& typeId)
@@ -779,4 +784,47 @@ namespace AzFramework
}
}
bool Application::IsPrefabSystemEnabled() const
{
bool value = true;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, ApplicationInternal::s_prefabSystemKey);
}
return value;
}
bool Application::ArePrefabWipFeaturesEnabled() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, ApplicationInternal::s_prefabWipSystemKey);
}
return value;
}
void Application::SetPrefabSystemEnabled(bool enable)
{
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Set(ApplicationInternal::s_prefabSystemKey, enable);
}
}
bool Application::IsPrefabSystemForLevelsEnabled() const
{
return IsPrefabSystemEnabled();
}
bool Application::ShouldAssertForLegacySlicesUsage() const
{
bool value = false;
if (auto* registry = AZ::SettingsRegistry::Get())
{
registry->Get(value, ApplicationInternal::s_legacySlicesAssertKey);
}
return value;
}
} // namespace AzFramework
@@ -104,6 +104,11 @@ namespace AzFramework
const char* GetAppRoot() const override;
void ResolveEnginePath(AZStd::string& engineRelativePath) const override;
void CalculateBranchTokenForEngineRoot(AZStd::string& token) const override;
bool IsPrefabSystemEnabled() const override;
bool ArePrefabWipFeaturesEnabled() const override;
void SetPrefabSystemEnabled(bool enable) override;
bool IsPrefabSystemForLevelsEnabled() const override;
bool ShouldAssertForLegacySlicesUsage() const override;
#pragma push_macro("GetCommandLine")
#undef GetCommandLine
@@ -31,6 +31,7 @@
#include <AzCore/std/sort.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetBundleManifest.h>
#include <AzFramework/Asset/AssetRegistry.h>
#include <AzFramework/IO/FileOperations.h>
@@ -1506,35 +1507,48 @@ namespace AZ::IO
auto bundleManifest = GetBundleManifest(desc.pZip);
AZStd::shared_ptr<AzFramework::AssetRegistry> bundleCatalog;
AZStd::vector<AZStd::string> levelDirs;
if (bundleManifest)
{
bundleCatalog = GetBundleCatalog(desc.pZip, bundleManifest->GetCatalogName());
}
if (addLevels)
{
// Note that manifest version two and above will contain level directory information inside them
// otherwise we will fallback to scanning the archive for levels.
if (bundleManifest && bundleManifest->GetBundleVersion() >= 2)
{
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
levelDirs = bundleManifest->GetLevelDirectories();
}
else
if (usePrefabSystemForLevels)
{
m_arrZips.insert(revItZip.base(), desc);
}
else
{
// [LYN-2376] Remove once legacy slice support is removed
AZStd::vector<AZStd::string> levelDirs;
if (addLevels)
{
levelDirs = ScanForLevels(desc.pZip);
// Note that manifest version two and above will contain level directory information inside them
// otherwise we will fallback to scanning the archive for levels.
if (bundleManifest && bundleManifest->GetBundleVersion() >= 2)
{
levelDirs = bundleManifest->GetLevelDirectories();
}
else
{
levelDirs = ScanForLevels(desc.pZip);
}
}
if (!levelDirs.empty())
{
desc.m_containsLevelPak = true;
}
m_arrZips.insert(revItZip.base(), desc);
m_levelOpenEvent.Signal(levelDirs);
}
if (!levelDirs.empty())
{
desc.m_containsLevelPak = true;
}
m_arrZips.insert(revItZip.base(), desc);
m_levelOpenEvent.Signal(levelDirs);
AZ::IO::ArchiveNotificationBus::Broadcast([](AZ::IO::ArchiveNotifications* archiveNotifications, const char* bundleName,
AZStd::shared_ptr<AzFramework::AssetBundleManifest> bundleManifest, const char* nextBundle, AZStd::shared_ptr<AzFramework::AssetRegistry> bundleCatalog)
{
@@ -1555,10 +1569,13 @@ namespace AZ::IO
return false;
}
bool usePrefabSystemForLevels = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
AZStd::unique_lock lock(m_csZips);
for (auto it = m_arrZips.begin(); it != m_arrZips.end();)
{
bool needRescan = false;
if (azstricmp(szZipPath->c_str(), it->GetFullPath()) == 0)
{
// this is the pack with the given name - remove it, and if possible it will be deleted
@@ -1571,16 +1588,25 @@ namespace AZ::IO
archiveNotifications->BundleClosed(bundleName);
}, it->GetFullPath());
if (it->m_containsLevelPak)
if (usePrefabSystemForLevels)
{
needRescan = true;
it = m_arrZips.erase(it);
}
it = m_arrZips.erase(it);
if (needRescan)
else
{
m_levelCloseEvent.Signal(szZipPath->Native());
// [LYN-2376] Remove once legacy slice support is removed
bool needRescan = false;
if (it->m_containsLevelPak)
{
needRescan = true;
}
it = m_arrZips.erase(it);
if (needRescan)
{
m_levelCloseEvent.Signal(szZipPath->Native());
}
}
}
else
@@ -120,6 +120,8 @@ namespace AZ::IO
{
AZ::IO::Path m_pathBindRoot; // the zip binding root
AZStd::string strFileName; // the zip file name (with path) - very useful for debugging so please don't remove
// [LYN-2376] Remove once legacy slice support is removed
bool m_containsLevelPak = false; // indicates whether this archive has level.pak inside it or not
const char* GetFullPath() const { return pZip->GetFilePath(); }
@@ -199,6 +201,7 @@ namespace AZ::IO
bool IsInstalledToHDD(AZStd::string_view acFilePath = 0) const override;
// [LYN-2376] Remove 'addLevels' parameter once legacy slice support is removed
bool OpenPack(AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
bool OpenPack(AZStd::string_view szBindRoot, AZStd::string_view pName, uint32_t nFlags = 0, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> pData = nullptr, AZ::IO::FixedMaxPathString* pFullPath = nullptr, bool addLevels = true) override;
// after this call, the file will be unlocked and closed, and its contents won't be used to search for files
@@ -300,7 +303,8 @@ namespace AZ::IO
EStreamSourceMediaType GetFileMediaType(AZStd::string_view szName) const override;
auto GetLevelPackOpenEvent()->LevelPackOpenEvent* override;
// [LYN-2376] Remove once legacy slice support is removed
auto GetLevelPackOpenEvent() -> LevelPackOpenEvent* override;
auto GetLevelPackCloseEvent()->LevelPackCloseEvent* override;
@@ -336,6 +340,8 @@ namespace AZ::IO
//! Return the Manifest from a bundle, if it exists
AZStd::shared_ptr<AzFramework::AssetBundleManifest> GetBundleManifest(ZipDir::CachePtr pZip);
AZStd::shared_ptr<AzFramework::AssetRegistry> GetBundleCatalog(ZipDir::CachePtr pZip, const AZStd::string& catalogName);
// [LYN-2376] Remove once legacy slice support is removed
AZStd::vector<AZStd::string> ScanForLevels(ZipDir::CachePtr pZip);
mutable AZStd::shared_mutex m_csOpenFiles;
@@ -362,6 +368,8 @@ namespace AZ::IO
RecordedFilesSet m_recordedFilesSet;
AZStd::intrusive_ptr<IResourceList> m_pEngineStartupResourceList;
// [LYN-2376] Remove once legacy slice support is removed
AZStd::intrusive_ptr<IResourceList> m_pLevelResourceList;
AZStd::intrusive_ptr<IResourceList> m_pNextLevelResourceList;
@@ -378,6 +386,8 @@ namespace AZ::IO
AZStd::fixed_string<128> m_sLocalizationRoot;
AZStd::set<uint32_t, AZStd::less<>, AZ::OSStdAllocator> m_filesCachedOnHDD;
// [LYN-2376] Remove once legacy slice support is removed
LevelPackOpenEvent m_levelOpenEvent;
LevelPackCloseEvent m_levelCloseEvent;
};
@@ -17,17 +17,28 @@
#include <AzFramework/Archive/ArchiveVars.h>
#include <AzFramework/Archive/ZipDirFind.h>
namespace AZ::IO
{
bool AZStdStringLessCaseInsensitive::operator()(AZStd::string_view left, AZStd::string_view right) const
{
// If one or both strings are 0-length, return true if the left side is smaller, false if they're equal or left is larger.
size_t compareLength = (AZStd::min)(left.size(), right.size());
if (compareLength == 0)
{
return left.size() < right.size();
}
// They're both non-zero, so compare the strings up until the length of the shorter string.
int compareResult = azstrnicmp(left.data(), right.data(), compareLength);
// If both strings are equal for the number of characters compared, return true if the left side is shorter, false if
// they're equal or left is longer.
if (compareResult == 0)
{
return left.size() < right.size();
}
// Return true if the left side should come first alphabetically, false if the right side should.
return compareResult < 0;
}
@@ -124,7 +135,8 @@ namespace AZ::IO
fileDesc.tAccess = fileDesc.tWrite;
fileDesc.tCreate = fileDesc.tWrite;
}
m_mapFiles.emplace(AZStd::move(fullFilePath), fileDesc);
[[maybe_unused]] auto result = m_mapFiles.emplace(AZStd::move(fullFilePath), fileDesc);
AZ_Assert(result.second, "Failed to insert FindData entry for %s", fullFilePath.c_str());
return true;
});
@@ -16,6 +16,7 @@
namespace Camera
{
//! Stores camera configuration values that describe the camera's view frustum.
struct Configuration
{
float m_fovRadians = 0.f;
@@ -25,114 +26,83 @@ namespace Camera
float m_frustumHeight = 0.f;
};
/**
* Use this bus to send messages to a camera component on an entity
* If you create your own camera you should implement this bus
* Call like this:
* Camera::CameraRequestBus::Event(cameraEntityId, &Camera::CameraRequestBus::Events::SetFov, newFov);
*/
//! Use this bus to send messages to a camera component on an entity
//! If you create your own camera you should implement this bus
//! Call like this:
//! Camera::CameraRequestBus::Event(cameraEntityId, &Camera::CameraRequestBus::Events::SetFov, newFov);
class CameraComponentRequests
: public AZ::ComponentBus
{
public:
virtual ~CameraComponentRequests() = default;
/**
* Gets the camera's field of view in degrees
* @return The camera's field of view in degrees
*/
//! Gets the camera's field of view in degrees
//! @return The camera's field of view in degrees
virtual float GetFov()
{
AZ_WarningOnce("CameraBus", false, "GetFov is deprecated. Please use GetFovDegrees or GetFovRadians.");
return GetFovDegrees();
}
/**
* Gets the camera's field of view in degrees
* @return The camera's field of view in degrees
*/
//! Gets the camera's field of view in degrees
//! @return The camera's field of view in degrees
virtual float GetFovDegrees() = 0;
/**
* Gets the camera's field of view in radians
* @return The camera's field of view in radians
*/
//! Gets the camera's field of view in radians
//! @return The camera's field of view in radians
virtual float GetFovRadians() = 0;
/**
* Gets the camera's distance from the near clip plane in meters
* @return The camera's distance from the near clip plane in meters
*/
//! Gets the camera's distance from the near clip plane in meters
//! @return The camera's distance from the near clip plane in meters
virtual float GetNearClipDistance() = 0;
/**
* Gets the camera's distance from the far clip plane in meters
* @return The camera's distance from the far clip plane in meters
*/
//! Gets the camera's distance from the far clip plane in meters
//! @return The camera's distance from the far clip plane in meters
virtual float GetFarClipDistance() = 0;
/**
* Gets the camera frustum's width
* @return The camera frustum's width
*/
//! Gets the camera frustum's width
//! @return The camera frustum's width
virtual float GetFrustumWidth() = 0;
/**
* Gets the camera frustum's height
* @return The camera frustum's height
*/
//! Gets the camera frustum's height
//! @return The camera frustum's height
virtual float GetFrustumHeight() = 0;
/**
* Sets the camera's field of view in degrees between 0 < fov < 180 degrees
* @param fov The camera frustum's new field of view in degrees
*/
//! Sets the camera's field of view in degrees between 0 < fov < 180 degrees
//! @param fov The camera frustum's new field of view in degrees
virtual void SetFov(float fov)
{
AZ_WarningOnce("CameraBus", false, "SetFov is deprecated. Please use SetFovDegrees or SetFovRadians.");
SetFovDegrees(fov);
}
/**
* Sets the camera's field of view in degrees between 0 < fov < 180 degrees
* @param fov The camera frustum's new field of view in degrees
*/
//! Sets the camera's field of view in degrees between 0 < fov < 180 degrees
//! @param fov The camera frustum's new field of view in degrees
virtual void SetFovDegrees(float fovInDegrees) = 0;
/**
* Sets the camera's field of view in radians between 0 < fov < pi radians
* @param fov The camera frustum's new field of view in radians
*/
//! Sets the camera's field of view in radians between 0 < fov < pi radians
//! @param fov The camera frustum's new field of view in radians
virtual void SetFovRadians(float fovInRadians) = 0;
/**
* Sets the near clip plane to a given distance from the camera in meters. Should be small, but greater than 0
* @param nearClipDistance The camera frustum's new near clip plane distance from camera
*/
//! Sets the near clip plane to a given distance from the camera in meters. Should be small, but greater than 0
//! @param nearClipDistance The camera frustum's new near clip plane distance from camera
virtual void SetNearClipDistance(float nearClipDistance) = 0;
/**
* Sets the far clip plane to a given distance from the camera in meters.
* @param farClipDistance The camera frustum's new far clip plane distance from camera
*/
//! Sets the far clip plane to a given distance from the camera in meters.
//! @param farClipDistance The camera frustum's new far clip plane distance from camera
virtual void SetFarClipDistance(float farClipDistance) = 0;
/**
* Sets the camera frustum's width
* @param width The camera frustum's new width
*/
//! Sets the camera frustum's width
//! @param width The camera frustum's new width
virtual void SetFrustumWidth(float width) = 0;
/**
* Sets the camera frustum's height
* @param height The camera frustum's new height
*/
//! Sets the camera frustum's height
//! @param height The camera frustum's new height
virtual void SetFrustumHeight(float height) = 0;
/**
* Makes the camera the active view
*/
//! Makes the camera the active view
virtual void MakeActiveView() = 0;
//! Get the camera frustum's aggregate configuration
virtual Configuration GetCameraConfiguration()
{
return Configuration
@@ -147,14 +117,11 @@ namespace Camera
};
using CameraRequestBus = AZ::EBus<CameraComponentRequests>;
/**
* Use this broadcast bus to gather a list of all active cameras
* If you create your own camera you should handle this bus
* Call like this:
*
* AZ::EBusAggregateResults<AZ::EntityId> results;
* Camera::CameraBus::BroadcastResult(results, &Camera::CameraRequests::GetCameras);
*/
//! Use this broadcast bus to gather a list of all active cameras
//! If you create your own camera you should handle this bus
//! Call like this:
//! AZ::EBusAggregateResults<AZ::EntityId> results;
//! Camera::CameraBus::BroadcastResult(results, &Camera::CameraRequests::GetCameras);
class CameraRequests
: public AZ::EBusTraits
{
@@ -166,18 +133,14 @@ namespace Camera
};
using CameraBus = AZ::EBus<CameraRequests>;
/**
* Use this system broadcast for things like getting the active camera
*/
//! Use this system broadcast for things like getting the active camera
class CameraSystemRequests
: public AZ::EBusTraits
{
public:
virtual ~CameraSystemRequests() = default;
/**
* returns the camera being used by the active view
*/
//! returns the camera being used by the active view
virtual AZ::EntityId GetActiveCamera() = 0;
};
using CameraSystemRequestBus = AZ::EBus<CameraSystemRequests>;
@@ -198,13 +161,11 @@ namespace Camera
};
using ActiveCameraRequestBus = AZ::EBus<ActiveCameraRequests>;
/**
* Handle this bus if you want to know when cameras are added or removed during edit or run time
* You will get an OnCameraAdded event for each camera that is already active
* If you create your own camera you should call this bus on activation/deactivation
* Connect to the bus like this
* Camera::CameraNotificationBus::Handler::Connect()
*/
//! Handle this bus if you want to know when cameras are added or removed during edit or run time
//! You will get an OnCameraAdded event for each camera that is already active
//! If you create your own camera you should call this bus on activation/deactivation
//! Connect to the bus like this
//! Camera::CameraNotificationBus::Handler::Connect()
class CameraNotifications
: public AZ::EBusTraits
{
@@ -223,28 +184,33 @@ namespace Camera
{
handler->OnCameraAdded(cameraId);
}
AZ::EntityId activeView;
CameraSystemRequestBus::BroadcastResult(activeView, &CameraSystemRequestBus::Events::GetActiveCamera);
if (activeView.IsValid())
{
handler->OnActiveViewChanged(activeView);
}
}
};
/**
* If the camera is active when a handler connects to the bus,
* then OnCameraAdded() is immediately dispatched.
*/
//! If the camera is active when a handler connects to the bus,
//! then OnCameraAdded() is immediately dispatched.
template<class Bus>
using ConnectionPolicy = CameraNotificationConnectionPolicy<Bus>;
virtual ~CameraNotifications() = default;
/**
* Called whenever a camera entity is added
* @param cameraId The id of the camera added
*/
virtual void OnCameraAdded(const AZ::EntityId& cameraId) = 0;
//! Called whenever a camera entity is added
//! @param cameraId The id of the camera added
virtual void OnCameraAdded(const AZ::EntityId& /*cameraId*/) {}
/**
* Called whenever a camera entity is removed
* @param cameraId The id of the camera removed
*/
virtual void OnCameraRemoved(const AZ::EntityId& cameraId) = 0;
//! Called whenever a camera entity is removed
//! @param cameraId The id of the camera removed
virtual void OnCameraRemoved(const AZ::EntityId& /*cameraId*/) {}
//! Called whenever the active camera entity changes
//! @param cameraId The id of the newly activated camera
virtual void OnActiveViewChanged(const AZ::EntityId&) {}
};
using CameraNotificationBus = AZ::EBus<CameraNotifications>;
@@ -44,10 +44,7 @@ namespace AzFramework
incompatible.push_back(AZ_CRC_CE("LookAtService"));
incompatible.push_back(AZ_CRC_CE("SequenceService"));
incompatible.push_back(AZ_CRC_CE("ClothMeshService"));
incompatible.push_back(AZ_CRC_CE("PhysXColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXTriggerService"));
incompatible.push_back(AZ_CRC_CE("PhysXJointService"));
incompatible.push_back(AZ_CRC_CE("PhysXShapeColliderService"));
incompatible.push_back(AZ_CRC_CE("PhysXCharacterControllerService"));
incompatible.push_back(AZ_CRC_CE("PhysXRagdollService"));
incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
@@ -1,47 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Engine/Engine.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Utils/Utils.h>
namespace AzFramework
{
namespace Engine
{
AZ::IO::FixedMaxPath FindEngineRoot(AZStd::string_view searchPath)
{
// File to locate
const char engineRootMarker[] = "engine.json";
AZ::IO::FixedMaxPath currentSearchPath{searchPath};
if (currentSearchPath.empty())
{
char executablePath[AZ_MAX_PATH_LEN];
AZ::Utils::GetExecutablePathReturnType result = AZ::Utils::GetExecutablePath(executablePath, AZ_MAX_PATH_LEN);
currentSearchPath = executablePath;
}
do
{
currentSearchPath = currentSearchPath.ParentPath();
if (AZ::IO::SystemFile::Exists((currentSearchPath / engineRootMarker).c_str()))
{
return currentSearchPath;
}
} while (currentSearchPath.ParentPath() != currentSearchPath);
return {};
}
}
} // AzFramework
@@ -1,25 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/string/string_view.h>
namespace AzFramework
{
namespace Engine
{
// Helper to attempt to locate the engine root by searching up the directory tree. If no search path is
// provided the current executable path is used
AZ::IO::FixedMaxPath FindEngineRoot(AZStd::string_view searchPath = {});
} // Engine
} // AzFramework
@@ -100,9 +100,9 @@ namespace AzFramework
//! @param idRemapTable if remapIds is true, the provided table is filled with a map of original ids to new ids
//! @param filterDesc any ObjectStream::LoadFlags
//! @return whether or not the root slice was successfully loaded from the provided stream
virtual bool LoadFromStream(AZ::IO::GenericStream& stream, bool remapIds,
bool LoadFromStream(AZ::IO::GenericStream& stream, bool remapIds,
EntityIdToEntityIdMap* idRemapTable = nullptr,
const AZ::ObjectStream::FilterDescriptor& filterDesc = AZ::ObjectStream::FilterDescriptor());
const AZ::ObjectStream::FilterDescriptor& filterDesc = AZ::ObjectStream::FilterDescriptor()) override;
//! Executes the post-add actions for the provided list of entities, like connecting to required ebuses.
//! @param entities The entities to perform the post-add actions for.
@@ -1,127 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/CollisionBus.h>
#include <AzFramework/Physics/Configuration/CollisionConfiguration.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/std/sort.h>
#include <AzCore/Interface/Interface.h>
namespace Physics
{
/// This structure is used only for reflecting type vector<RayCastHit> to
/// serialize and behavior context. It's not used in the API anywhere
struct RaycastHitArray
{
AZ_TYPE_INFO(RaycastHitArray, "{BAFCC4E7-A06B-4909-B2AE-C89D9E84FE4E}");
AZStd::vector<Physics::RayCastHit> m_hitArray;
};
AZStd::vector<AZStd::pair<AzPhysics::CollisionGroup, AZStd::string>> PopulateCollisionGroups()
{
AZStd::vector<AZStd::pair<AzPhysics::CollisionGroup, AZStd::string>> elems;
const AzPhysics::CollisionConfiguration& configuration = AZ::Interface<Physics::CollisionRequests>::Get()->GetCollisionConfiguration();
for (const AzPhysics::CollisionGroups::Preset& preset : configuration.m_collisionGroups.GetPresets())
{
elems.push_back({ AzPhysics::CollisionGroup(preset.m_name), preset.m_name });
}
return elems;
}
void RayCastHit::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RayCastRequest>()
->Field("Distance", &RayCastRequest::m_distance)
->Field("Start", &RayCastRequest::m_start)
->Field("Direction", &RayCastRequest::m_direction)
->Field("Collision", &RayCastRequest::m_collisionGroup)
->Field("QueryType", &RayCastRequest::m_queryType)
->Field("MaxResults", &RayCastRequest::m_maxResults)
;
serializeContext->Class<RayCastHit>()
->Field("Distance", &RayCastHit::m_distance)
->Field("Position", &RayCastHit::m_position)
->Field("Normal", &RayCastHit::m_normal)
;
serializeContext->Class<RaycastHitArray>()
->Field("HitArray", &RaycastHitArray::m_hitArray)
;
if (auto editContext = azrtti_cast<AZ::EditContext*>(serializeContext->GetEditContext()))
{
editContext->Enum<QueryType>("Query Type", "Object types to include in the query")
->Value("Static", QueryType::Static)
->Value("Dynamic", QueryType::Dynamic)
->Value("Static and Dynamic", QueryType::StaticAndDynamic)
;
editContext->Class<RayCastRequest>("RayCast Request", "Parameters for raycast")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_start, "Start", "Start position of the raycast")
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_distance, "Distance", "Length of the raycast")
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_direction, "Direction", "Direction of the raycast")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &RayCastRequest::m_collisionGroup, "Collision Group", "The layers to include in the query")
->Attribute(AZ::Edit::Attributes::EnumValues, &PopulateCollisionGroups)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &RayCastRequest::m_queryType, "Query Type", "Object types to include in the query")
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_maxResults, "Max results", "The Maximum results for this request to return, this is limited by the value set in WorldConfiguration")
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<RayCastRequest>("RayCastRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Distance", BehaviorValueProperty(&RayCastRequest::m_distance))
->Property("Start", BehaviorValueProperty(&RayCastRequest::m_start))
->Property("Direction", BehaviorValueProperty(&RayCastRequest::m_direction))
->Property("Collision", BehaviorValueProperty(&RayCastRequest::m_collisionGroup))
// Until enum class support for behavior context is done, expose this as an int
->Property("QueryType", [](const RayCastRequest& self) { return static_cast<int>(self.m_queryType); },
[](RayCastRequest& self, int newQueryType) { self.m_queryType = QueryType(newQueryType); })
->Property("MaxResults", BehaviorValueProperty(&RayCastRequest::m_maxResults))
;
behaviorContext->Class<RayCastHit>("RayCastHit")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Distance", BehaviorValueProperty(&RayCastHit::m_distance))
->Property("Position", BehaviorValueProperty(&RayCastHit::m_position))
->Property("Normal", BehaviorValueProperty(&RayCastHit::m_normal))
->Property("EntityId", [](RayCastHit& result) { return result.m_body != nullptr ? result.m_body->GetEntityId() : AZ::EntityId(); }, nullptr)
;
behaviorContext->Class<RaycastHitArray>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Property("HitArray", BehaviorValueProperty(&RaycastHitArray::m_hitArray))
;
}
}
} // namespace Physics
@@ -1,175 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <functional>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Material.h>
namespace Physics
{
class WorldBody;
class Shape;
class ShapeConfiguration;
/// Enum to specify the hit type returned by the filter callback.
enum QueryHitType
{
None, ///< The hit should not be reported.
Touch, ///< The hit should be reported but it should not block the query
Block ///< The hit should be reported and it should block the query
};
/// Callback used for directed scene queries: RayCasts and ShapeCasts
using FilterCallback = AZStd::function<QueryHitType(const Physics::WorldBody* body, const Physics::Shape* shape)>;
/// Enum to specify which shapes are included in the query.
enum class QueryType : int
{
Static, ///< Only test against static shapes
Dynamic, ///< Only test against dynamic shapes
StaticAndDynamic ///< Test against both static and dynamic shapes
};
//! Scene query and geometry query behavior flags.
//!
//! HitFlags are used for 3 different purposes:
//!
//! 1) To request hit fields to be filled in by scene queries (such as hit position, normal, face index or UVs).
//! 2) Once query is completed, to indicate which fields are valid (note that a query may produce more valid fields than requested).
//! 3) To specify additional options for the narrow phase and mid-phase intersection routines.
enum class HitFlags : AZ::u16
{
Position = (1 << 0), //!< "position" member of the hit is valid
Normal = (1 << 1), //!< "normal" member of the hit is valid
UV = (1 << 3), //!< "u" and "v" barycentric coordinates of the hit are valid. Not applicable to ShapeCast queries.
//! Performance hint flag for ShapeCasts when it is known upfront there's no initial overlap.
//! NOTE: using this flag may cause undefined results if shapes are initially overlapping.
AssumeNoInitialOverlap = (1 << 4),
MeshMultiple = (1 << 5), //!< Report all hits for meshes rather than just the first. Not applicable to ShapeCast queries.
//! Report any first hit for meshes. If neither MeshMultiple nor MeshAny is specified,
//! a single closest hit will be reported for meshes.
MeshAny = (1 << 6),
//! Report hits with back faces of mesh triangles. Also report hits for raycast
//! originating on mesh surface and facing away from the surface normal. Not applicable to ShapeCast queries.
MeshBothSides = (1 << 7),
PreciseSweep = (1 << 8), //!< Use more accurate but slower narrow phase sweep tests.
MTD = (1 << 9), //!< Report the minimum translation depth, normal and contact point.
FaceIndex = (1 << 10), //!< "face index" member of the hit is valid. Required to get the per-face material data.
Default = Position | Normal | FaceIndex
};
/// Casts a ray from a starting pose along a direction returning objects that intersected with the ray.
struct RayCastRequest
{
AZ_CLASS_ALLOCATOR(RayCastRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RayCastRequest, "{53EAD088-A391-48F1-8370-2A1DBA31512F}");
float m_distance = 500.0f; ///< The distance along m_dir direction.
AZ::Vector3 m_start = AZ::Vector3::CreateZero(); ///< World space point where ray starts from.
AZ::Vector3 m_direction = AZ::Vector3::CreateZero(); ///< World space direction (normalized).
AzPhysics::CollisionGroup m_collisionGroup = AzPhysics::CollisionGroup::All; ///< The layers to include in the query
FilterCallback m_filterCallback = nullptr; ///< Hit filtering function
QueryType m_queryType = QueryType::StaticAndDynamic; ///< Object types to include in the query
HitFlags m_hitFlags = HitFlags::Default; ///< Query behavior flags
AZ::u64 m_maxResults = 32; ///< The Maximum results for this request to return, this is limited by the value set in WorldConfiguration
};
/// Sweeps a shape from a starting pose along a direction returning objects that intersected with the shape.
struct ShapeCastRequest
{
AZ_CLASS_ALLOCATOR(ShapeCastRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ShapeCastRequest, "{52F6C536-92F6-4C05-983D-0A74800AE56D}");
float m_distance = 500.0f; /// The distance to cast along m_dir direction.
AZ::Transform m_start = AZ::Transform::CreateIdentity(); ///< World space start position. Assumes only rotation + translation (no scaling).
AZ::Vector3 m_direction = AZ::Vector3::CreateZero(); ///< World space direction (Should be normalized)
ShapeConfiguration* m_shapeConfiguration = nullptr; ///< Shape information.
AzPhysics::CollisionGroup m_collisionGroup = AzPhysics::CollisionGroup::All; ///< Collision filter for the query.
FilterCallback m_filterCallback = nullptr; ///< Hit filtering function
QueryType m_queryType = QueryType::StaticAndDynamic; ///< Object types to include in the query
HitFlags m_hitFlags = HitFlags::Default; ///< Query behavior flags
AZ::u64 m_maxResults = 32; ///< The Maximum results for this request to return, this is limited by the value set in WorldConfiguration
};
/// Callback used for undirected scene queries: Overlaps
using OverlapFilterCallback = AZStd::function<bool(const Physics::WorldBody* body, const Physics::Shape* shape)>;
/// Searches a region enclosed by a specified shape for any overlapping objects in the scene.
struct OverlapRequest
{
AZ_CLASS_ALLOCATOR(OverlapRequest, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(OverlapRequest, "{3DC986C2-316B-4C54-A0A6-8ABBB8ABCC4A}");
AZ::Transform m_pose = AZ::Transform::CreateIdentity(); ///< Initial shape pose
ShapeConfiguration* m_shapeConfiguration = nullptr; ///< Shape information.
AzPhysics::CollisionGroup m_collisionGroup = AzPhysics::CollisionGroup::All; ///< Collision filter for the query.
OverlapFilterCallback m_filterCallback = nullptr; ///< Hit filtering function
QueryType m_queryType = QueryType::StaticAndDynamic; ///< Object types to include in the query
AZ::u64 m_maxResults = 32; ///< The Maximum results for this request to return, this is limited by the value set in WorldConfiguration
};
/// Structure used to store the result from either a raycast or a shape cast.
struct RayCastHit
{
AZ_CLASS_ALLOCATOR(RayCastHit, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(RayCastHit, "{A46CBEA6-6B92-4809-9363-9DDF0F74F296}");
static void Reflect(AZ::ReflectContext* context);
inline operator bool() const { return m_body != nullptr; }
float m_distance = 0.0f; ///< The distance along the cast at which the hit occurred as given by Dot(m_normal, startPoint) - Dot(m_normal, m_point).
AZ::Vector3 m_position = AZ::Vector3::CreateZero(); ///< The position of the hit in world space
AZ::Vector3 m_normal = AZ::Vector3::CreateZero(); ///< The normal of the surface hit
WorldBody* m_body = nullptr; ///< World body that was hit.
Shape* m_shape = nullptr; ///< The shape on the body that was hit
Material* m_material = nullptr; ///< The material on the shape (or face) that was hit
};
/// Overlap hit.
struct OverlapHit
{
AZ_CLASS_ALLOCATOR(OverlapHit, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(OverlapHit, "{7A7201B9-67B5-438B-B4EB-F3EEBB78C617}");
inline operator bool() const { return m_body != nullptr; }
WorldBody* m_body = nullptr; ///< World body that was hit.
Shape* m_shape = nullptr; ///< The shape on the body that was hit
Material* m_material = nullptr; ///< The material on the shape (or face) that was hit
};
/// Bitwise operators for HitFlags
inline HitFlags operator|(HitFlags lhs, HitFlags rhs)
{
return static_cast<HitFlags>(static_cast<AZ::u16>(lhs) | static_cast<AZ::u16>(rhs));
}
inline HitFlags operator&(HitFlags lhs, HitFlags rhs)
{
return static_cast<HitFlags>(static_cast<AZ::u16>(lhs) & static_cast<AZ::u16>(rhs));
}
} // namespace Physics
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(Physics::QueryType, "{0E0E56A8-73A8-40B4-B438-B19FC852E3C0}");
}
@@ -88,7 +88,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<CharacterConfiguration>()
serializeContext->Class<CharacterConfiguration, AzPhysics::SimulatedBodyConfiguration>()
->Version(2)
->Field("CollisionLayer", &CharacterConfiguration::m_collisionLayer)
->Field("CollisionGroupId", &CharacterConfiguration::m_collisionGroupId)
@@ -15,11 +15,11 @@
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
namespace Physics
{
@@ -59,11 +59,11 @@ namespace Physics
/// Information required to create the basic physics representation of a character.
class CharacterConfiguration
: public WorldBodyConfiguration
: public AzPhysics::SimulatedBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(CharacterConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(CharacterConfiguration, "{58D5A6CA-113B-4AC3-8D53-239DB0C4E240}");
AZ_RTTI(CharacterConfiguration, "{58D5A6CA-113B-4AC3-8D53-239DB0C4E240}", AzPhysics::SimulatedBodyConfiguration);
virtual ~CharacterConfiguration() = default;
@@ -84,11 +84,11 @@ namespace Physics
/// all-purpose character controller implementation. This class just abstracts some common functionality amongst
/// typical characters, and is take-it-or-leave it style; useful as a starting point or reference.
class Character
: public WorldBody
: public AzPhysics::SimulatedBody
{
public:
AZ_CLASS_ALLOCATOR(Character, AZ::SystemAllocator, 0);
AZ_RTTI(Character, "{962E37A1-3401-4672-B896-0A6157CFAC97}", WorldBody);
AZ_RTTI(Character, "{962E37A1-3401-4672-B896-0A6157CFAC97}", AzPhysics::SimulatedBody);
~Character() override = default;
@@ -36,7 +36,33 @@ namespace AzFramework
public:
virtual ~CharacterPhysicsDataNotifications() = default;
virtual void OnRagdollConfigurationReady() = 0;
virtual void OnRagdollConfigurationReady(const Physics::RagdollConfiguration& ragdollConfiguration) = 0;
//! When connecting to this bus, if the ragdoll configuration is ready
//! it will immediately send an OnRagdollConfigurationReady event.
template<class Bus>
struct ConnectionPolicy
: public AZ::EBusConnectionPolicy<Bus>
{
static void Connect(
typename Bus::BusPtr& busPtr,
typename Bus::Context& context,
typename Bus::HandlerNode& handler,
typename Bus::Context::ConnectLockGuard& connectLock,
const typename Bus::BusIdType& id = 0)
{
AZ::EBusConnectionPolicy<Bus>::Connect(busPtr, context, handler, connectLock, id);
bool ragdollConfigValid = false;
Physics::RagdollConfiguration ragdollConfiguration;
CharacterPhysicsDataRequestBus::EventResult(ragdollConfigValid, id,
&CharacterPhysicsDataRequests::GetRagdollConfiguration, ragdollConfiguration);
if (ragdollConfigValid)
{
handler->OnRagdollConfigurationReady(ragdollConfiguration);
}
}
};
};
using CharacterPhysicsDataNotificationBus = AZ::EBus<CharacterPhysicsDataNotifications>;
@@ -264,63 +264,5 @@ namespace Physics
return success;
}
bool RigidBodyVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() <= 1)
{
const int elementIndex = classElement.FindElement(AZ_CRC("Centre of mass offset", 0x1e569a45));
if (elementIndex >= 0)
{
AZ::Vector3 existingCenterOfMassOffset;
AZ::SerializeContext::DataElementNode& centerOfMassElement = classElement.GetSubElement(elementIndex);
const bool found = centerOfMassElement.GetData<AZ::Vector3>(existingCenterOfMassOffset);
if (found && !existingCenterOfMassOffset.IsZero())
{
// An existing center of mass (COM) offset value was specified for this rigid body.
// Version 2 includes a new m_computeCenterOfMass boolean flag to specify the automatic calculation of COM.
// In this case set m_computeCenterOfMass to false so that the existing center of mass offset value is utilized correctly.
const int idx = classElement.AddElement<bool>(context, "Compute COM");
if (idx != -1)
{
if (!classElement.GetSubElement(idx).SetData<bool>(context, false))
{
return false;
}
}
}
}
}
if (classElement.GetVersion() <= 2)
{
const int elementIndex = classElement.FindElement(AZ_CRC("Mass", 0x6c035b66));
if (elementIndex >= 0)
{
float existingMass = 0;
AZ::SerializeContext::DataElementNode& massElement = classElement.GetSubElement(elementIndex);
const bool found = massElement.GetData<float>(existingMass);
if (found && existingMass > 0)
{
// Keeping the existing mass and disabling auto-compute of the mass for this rigid body.
// Version 3 includes a new m_computeMass boolean flag to specify the automatic calculation of mass.
const int idx = classElement.AddElement<bool>(context, "Compute Mass");
if (idx != -1)
{
if (!classElement.GetSubElement(idx).SetData<bool>(context, false))
{
return false;
}
}
}
}
}
return true;
}
} // namespace ClassConverters
} // namespace Physics
@@ -22,6 +22,6 @@ namespace Physics
bool MaterialLibraryAssetConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
bool ColliderConfigurationConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
bool MaterialSelectionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
bool RigidBodyVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
} // namespace ClassConverters
} // namespace Physics
} // namespace Physics
@@ -0,0 +1,132 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(TriggerEvent, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(Contact, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(CollisionEvent, AZ::SystemAllocator, 0);
/*static*/ void TriggerEvent::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<TriggerEvent>()
->Version(2)
->Field("Type", &TriggerEvent::m_type)
->Field("TriggerBodyHandle", &TriggerEvent::m_triggerBodyHandle)
->Field("OtherBodyHandle", &TriggerEvent::m_otherBodyHandle)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<TriggerEvent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Method("GetTriggerEntityId", &TriggerEvent::GetTriggerEntityId)
->Method("GetOtherEntityId", &TriggerEvent::GetOtherEntityId)
;
}
}
AZ::EntityId TriggerEvent::GetTriggerEntityId() const
{
if (m_triggerBody)
{
return m_triggerBody->GetEntityId();
}
return AZ::EntityId();
}
AZ::EntityId TriggerEvent::GetOtherEntityId() const
{
if (m_otherBody)
{
return m_otherBody->GetEntityId();
}
return AZ::EntityId();
}
/*static*/ void Contact::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<Contact>()
->Version(1)
->Field("Position", &Contact::m_position)
->Field("Normal", &Contact::m_normal)
->Field("Impulse", &Contact::m_impulse)
->Field("Separation", &Contact::m_separation)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<Contact>("Contact")
->Property("Position", BehaviorValueProperty(&Contact::m_position))
->Property("Normal", BehaviorValueProperty(&Contact::m_normal))
->Property("Impulse", BehaviorValueProperty(&Contact::m_impulse))
->Property("Separation", BehaviorValueProperty(&Contact::m_separation))
;
}
}
/*static*/ void CollisionEvent::Reflect(AZ::ReflectContext* context)
{
Contact::Reflect(context);
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CollisionEvent>()
->Version(3)
->Field("Type", &CollisionEvent::m_type)
->Field("Contacts", &CollisionEvent::m_contacts)
->Field("BodyHandle1", &CollisionEvent::m_bodyHandle1)
->Field("BodyHandle2", &CollisionEvent::m_bodyHandle2)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<CollisionEvent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Property("Contacts", BehaviorValueProperty(&CollisionEvent::m_contacts))
->Method("GetBody1EntityId", &CollisionEvent::GetBody1EntityId)
->Method("GetBody2EntityId", &CollisionEvent::GetBody2EntityId)
;
}
}
AZ::EntityId CollisionEvent::GetBody1EntityId() const
{
if (m_body1)
{
return m_body1->GetEntityId();
}
return AZ::EntityId();
}
AZ::EntityId CollisionEvent::GetBody2EntityId() const
{
if (m_body2)
{
return m_body2->GetEntityId();
}
return AZ::EntityId();
}
}
@@ -0,0 +1,107 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
class ReflectContext;
}
namespace Physics
{
class Shape;
}
namespace AzPhysics
{
struct SimulatedBody;
//! Trigger event raised when an object enters/exits a trigger shape.
struct TriggerEvent
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_TYPE_INFO(TriggerEvent, "{7A0851A3-2CBD-4A03-85D5-1C40221E7F61}");
static void Reflect(AZ::ReflectContext* context);
enum class Type : AZ::u8
{
Enter,
Exit
};
Type m_type; //! The type of trigger event.
SimulatedBodyHandle m_triggerBodyHandle = AzPhysics::InvalidSimulatedBodyHandle; //!< Handle to the trigger body
SimulatedBody* m_triggerBody = nullptr; //!< The trigger body
Physics::Shape* m_triggerShape = nullptr; //!< The trigger shape
SimulatedBodyHandle m_otherBodyHandle = AzPhysics::InvalidSimulatedBodyHandle; //!< Handle to the body that entered the trigger
SimulatedBody* m_otherBody = nullptr; //!< The other body that entered the trigger
Physics::Shape* m_otherShape = nullptr; //!< The other shape that entered the trigger
private:
// helpers for reflecting to behaviour context
AZ::EntityId GetTriggerEntityId() const;
AZ::EntityId GetOtherEntityId() const;
};
using TriggerEventList = AZStd::vector<TriggerEvent>;
//! Stores information about the contacts between two overlapping shapes.
struct Contact
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_TYPE_INFO(Contact, "{D7439508-ED10-4395-9D48-1FC3D7815361}");
static void Reflect(AZ::ReflectContext* context);
AZ::Vector3 m_position; //!< The position of the contact
AZ::Vector3 m_normal; //!< The normal of the contact
AZ::Vector3 m_impulse; //!< The impulse force applied to separate the bodies
AZ::u32 m_internalFaceIndex01 = 0; //!< Internal face index of the first shape
AZ::u32 m_internalFaceIndex02 = 0; //!< Internal face index of the second shape
float m_separation = 0.0f; //!< The separation
};
//! A collision event raised when two objects, neither of which can be triggers, overlap.
struct CollisionEvent
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_TYPE_INFO(CollisionEvent, "{7602AA36-792C-4BDC-BDF8-AA16792151A3}");
static void Reflect(AZ::ReflectContext* context);
enum class Type : AZ::u8
{
Begin,
Persist,
End
};
Type m_type; //! The Type of collision event.
SimulatedBodyHandle m_bodyHandle1 = AzPhysics::InvalidSimulatedBodyHandle; //!< Handle to the first body
SimulatedBody* m_body1 = nullptr; //! The first body
Physics::Shape* m_shape1 = nullptr; //!< The shape on the first body
SimulatedBodyHandle m_bodyHandle2 = AzPhysics::InvalidSimulatedBodyHandle; //!< Handle to the second body
SimulatedBody* m_body2 = nullptr; //! The second body
Physics::Shape* m_shape2 = nullptr; //!< The shape on the second body
AZStd::vector<Contact> m_contacts; //!< The contacts between the two shapes
private:
// helpers for reflecting to behaviour context
AZ::EntityId GetBody1EntityId() const;
AZ::EntityId GetBody2EntityId() const;
};
using CollisionEventList = AZStd::vector<CollisionEvent>;
}
@@ -60,8 +60,6 @@ namespace Physics
/// Creates a new collision group preset with corresponding groupName.
virtual void CreateCollisionGroup(const AZStd::string& groupName, const AzPhysics::CollisionGroup& group) = 0;
virtual AzPhysics::CollisionConfiguration GetCollisionConfiguration() = 0;
};
/// Collision requests bus traits. Singleton pattern.
@@ -1,47 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzFramework/Physics/WorldEventhandler.h>
namespace Physics
{
/// CollisionNotifications
/// Bus interface for receiving collision events from a Physics::World
///
/// The bus is addressed by EntityId. Body1 inside collisionEvent will correspond
/// to the eEntity id subscribed to. Body2 will always be the other body colliding with the entity.
class CollisionNotifications
: public AZ::ComponentBus
{
public:
// Ebus Traits. ID'd on body1 entity Id
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const bool EnableEventQueue = true;
using BusIdType = AZ::EntityId;
virtual ~CollisionNotifications() {}
/// Dispatched when two shapes start colliding.
virtual void OnCollisionBegin(const CollisionEvent& /*collisionEvent*/) {}
/// Dispatched when two shapes continue colliding.
virtual void OnCollisionPersist(const CollisionEvent& /*collisionEvent*/) {}
/// Dispatched when two shapes stop colliding.
virtual void OnCollisionEnd(const CollisionEvent& /*collisionEvent*/) {}
};
/// Bus to service the PhysX Trigger Area Component event group.
using CollisionNotificationBus = AZ::EBus<CollisionNotifications>;
} // namespace PhysX
@@ -13,6 +13,14 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/EBus/OrderedEvent.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
class Vector3;
}
namespace AzPhysics
{
@@ -42,6 +50,14 @@ namespace AzPhysics
//! Event triggers at the end of the SystemInterface::Simulate call.
using OnPostsimulateEvent = AZ::Event<>;
//! Event trigger when a Scene is added to the simulation.
//! When triggered will send the handle to the new Scene.
using OnSceneAddedEvent = AZ::Event<AzPhysics::SceneHandle>;
//! Event trigger when a Scene is removed from the simulation.
//! When triggered will send the handle to the old Scene (after this call, the Handle will be invalid).
using OnSceneRemovedEvent = AZ::Event<AzPhysics::SceneHandle>;
//! Event that triggers when the default material library changes.
//! When triggered the event will send the Asset Id of the new material library.
using OnDefaultMaterialLibraryChangedEvent = AZ::Event<const AZ::Data::AssetId&>;
@@ -50,4 +66,80 @@ namespace AzPhysics
//! When triggered the event will send the new default scene configuration.
using OnDefaultSceneConfigurationChangedEvent = AZ::Event<const SceneConfiguration*>;
}
namespace SceneEvents
{
//! Event that triggers when a new config is set on a scene.
//! When triggered the event will send a handle to the Scene that triggered the event and the new configuration.
using OnSceneConfigurationChanged = AZ::Event<AzPhysics::SceneHandle, const AzPhysics::SceneConfiguration&>;
//! Event that triggers when a Simulated body has been added to a scene.
//! When triggered the event will send a handle to the Scene that triggered the event and a handle to the new simulated body.
using OnSimulationBodyAdded = AZ::Event<AzPhysics::SceneHandle, AzPhysics::SimulatedBodyHandle>;
//! Event that triggers when a Simulated body has been removed from a scene.
//! When triggered the event will send a handle to the Scene that triggered the event and a handle to the removed simulated body (after this call, the Handle will be invalid).
using OnSimulationBodyRemoved = AZ::Event<AzPhysics::SceneHandle, AzPhysics::SimulatedBodyHandle>;
//! Event that triggers when a Simulated body has its simulation enabled.
//! When triggered the event will send a handle to the Scene that triggered the event and a handle to the affected simulated body.
using OnSimulationBodySimulationEnabled = AZ::Event<AzPhysics::SceneHandle, AzPhysics::SimulatedBodyHandle>;
//! Event that triggers when a Simulated body has its simulation disabled.
//! When triggered the event will send a handle to the Scene that triggered the event and a handle to the affected simulated body.
using OnSimulationBodySimulationDisabled = AZ::Event<AzPhysics::SceneHandle, AzPhysics::SimulatedBodyHandle>;
//! Enum for use with the OnSceneSimulationStartEvent and OnSceneSimulationFinishEvent AZ::OrderedEvent calls.
//! Higher values are called before lower values.
enum class PhysicsStartFinishSimulationPriority : int32_t
{
Default = 0, //!< All other systems (Game code).
Audio = 1000, //!< Audio systems (occlusion).
Scripting = 2000, //!< Scripting systems (script canvas).
Components = 3000, //!< C++ components (force region).
Animation = 4000, //!< Animation system (ragdolls).
Physics = 5000 //!< The physics system itself
};
//! Event triggers at the beginning of the Scene::StartSimulation call.
//! This will not trigger if the scene if not Enabled (Scene::IsEnabled() returns true).
//! When triggered the event will send a handle to the Scene that triggers the event and the delta time in seconds used to step this update.
//! @note This may fire multiple times per frame.
using OnSceneSimulationStartEvent = AZ::OrderedEvent<AzPhysics::SceneHandle, float>;
using OnSceneSimulationStartHandler = AZ::OrderedEventHandler<AzPhysics::SceneHandle, float>;
//! Event triggers at the End of the Scene::FinishSimulation call.
//! This will not trigger if the scene if not Enabled (Scene::IsEnabled() returns true).
//! When triggered the event will send a handle to the Scene that triggers the event and the delta time in seconds used to step this update.
//! @note This may fire multiple times per frame.
using OnSceneSimulationFinishEvent = AZ::OrderedEvent<AzPhysics::SceneHandle, float>;
using OnSceneSimulationFinishHandler = AZ::OrderedEventHandler<AzPhysics::SceneHandle, float>;
//! Event triggers during the Scene::FinishSimulation call before the OnSceneSimulationFinishEvent for a scene
//! and only if the SceneConfiguration::m_enableActiveActors is true.
//! This will not trigger if the scene is not Enabled (Scene::IsEnabled() must return true to trigger).
//! When triggered, the event will send a handle of the Scene that triggered the event and a list of SimulatedBodyHandles that were updated in this tick.
//! @note There may be a performance penalty for enabling the Active Actor Notification.
using OnSceneActiveSimulatedBodiesEvent = AZ::Event<AzPhysics::SceneHandle, const AzPhysics::SimulatedBodyHandleList&>;
//! Event triggers with an ordered list of all the collision Begin/Persist/End events that happened during a single sub simulation step.
//! When triggered the event will send a handle to the Scene that triggers the event and the list of collision events that occurred.
//! @note The event will trigger at the end of the Scene::FinishSimulation call and only if collision events were generated and will be
//! triggered before the OnSceneSimulationFinishEvent, SimulatedBodyEvents::OnCollisionBegin, SimulatedBodyEvents::OnCollisionPersist, and SimulatedBodyEvents::OnCollisionEnd.
//! This may fire multiple times per frame.
//! The CollisionEventList is only valid for the duration of the callback.
using OnSceneCollisionsEvent = AZ::Event<AzPhysics::SceneHandle, const AzPhysics::CollisionEventList&>;
//! Event triggers with an ordered list of all the trigger Enter/Exit events that happened during single sub simulation step.
//! When triggered the event will send a handle to the Scene that triggers the event and the list of trigger events that occurred.
//! @note The event will trigger at the end of the Scene::FinishSimulation call and only if trigger events were generated and will be
//! triggered before the OnSceneSimulationFinishEvent, SimulatedBodyEvents::OnTriggerEnter and SimulatedBodyEvents::OnTriggerExit.
//! This may fire multiple times per frame.
//! The TriggerEventList is only valid for the duration of the callback.
using OnSceneTriggersEvent = AZ::Event<AzPhysics::SceneHandle, const AzPhysics::TriggerEventList&>;
//! Event trigger when the gravity has been changed on the scene.
//! When triggered the event will send a handle to the Scene that triggers the event and the new gravity vector.
using OnSceneGravityChangedEvent = AZ::Event<AzPhysics::SceneHandle, const AZ::Vector3&>;
}
}
@@ -0,0 +1,305 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Physics/CollisionBus.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/CollisionConfiguration.h>
#include <AzFramework/Physics/PhysicsSystem.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(SceneQueryRequest, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(RayCastRequest, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(ShapeCastRequest, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(OverlapRequest, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(SceneQueryHit, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(SceneQueryHits, AZ::SystemAllocator, 0);
namespace Internal
{
AZStd::vector<AZStd::pair<CollisionGroup, AZStd::string>> PopulateCollisionGroups()
{
AZStd::vector<AZStd::pair<CollisionGroup, AZStd::string>> elems;
const CollisionConfiguration& configuration = AZ::Interface<AzPhysics::SystemInterface>::Get()->GetConfiguration()->m_collisionConfig;
for (const CollisionGroups::Preset& preset : configuration.m_collisionGroups.GetPresets())
{
elems.push_back({ CollisionGroup(preset.m_name), preset.m_name });
}
return elems;
}
}
namespace SceneQuery
{
/*static*/ void ReflectSceneQueryObjects(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
if (auto* editContext = azrtti_cast<AZ::EditContext*>(serializeContext->GetEditContext()))
{
editContext->Enum<QueryType>("Query Type Flags", "Object types to include in the query")
->Value("Static", QueryType::Static)
->Value("Dynamic", QueryType::Dynamic)
->Value("Static and Dynamic", QueryType::StaticAndDynamic)
;
}
}
SceneQueryRequest::Reflect(context);
RayCastRequest::Reflect(context);
ShapeCastRequest::Reflect(context);
OverlapRequest::Reflect(context);
SceneQueryHits::Reflect(context);
}
}
/*static*/ void SceneQueryRequest::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SceneQueryRequest>()
->Field("MaxResults", &SceneQueryRequest::m_maxResults)
->Field("CollisionGroup", &SceneQueryRequest::m_collisionGroup)
->Field("QueryType", &SceneQueryRequest::m_queryType)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<SceneQueryRequest>("Scene Query Request", "Parameters for scene queries")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SceneQueryRequest::m_collisionGroup, "Collision Group", "The layers to include in the query")
->Attribute(AZ::Edit::Attributes::EnumValues, &Internal::PopulateCollisionGroups)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SceneQueryRequest::m_queryType, "Query Type", "Object types to include in the query")
->DataElement(AZ::Edit::UIHandlers::Default, &SceneQueryRequest::m_maxResults, "Max results", "The Maximum results for this request to return, this is limited by the value set in Physics Configuration")
;
}
}
}
/*static*/ void RayCastRequest::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RayCastRequest, SceneQueryRequest>()
->Field("Distance", &RayCastRequest::m_distance)
->Field("Start", &RayCastRequest::m_start)
->Field("Direction", &RayCastRequest::m_direction)
->Field("HitFlags", &RayCastRequest::m_hitFlags)
->Field("ReportMultipleHits", &RayCastRequest::m_reportMultipleHits)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<RayCastRequest>("RayCast Request", "Parameters for raycast")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_start, "Start", "Start position of the raycast")
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_distance, "Distance", "Length of the raycast")
->DataElement(AZ::Edit::UIHandlers::Default, &RayCastRequest::m_direction, "Direction", "Direction of the raycast")
;
}
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<RayCastRequest>("RayCastRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Distance", BehaviorValueProperty(&RayCastRequest::m_distance))
->Property("Start", BehaviorValueProperty(&RayCastRequest::m_start))
->Property("Direction", BehaviorValueProperty(&RayCastRequest::m_direction))
->Property("Collision", BehaviorValueProperty(&RayCastRequest::m_collisionGroup))
// Until enum class support for behavior context is done, expose this as an int
->Property("QueryType", [](const RayCastRequest& self) { return static_cast<int>(self.m_queryType); },
[](RayCastRequest& self, int newQueryType) { self.m_queryType = SceneQuery::QueryType(newQueryType); })
;
}
}
/*static*/ void ShapeCastRequest::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ShapeCastRequest, SceneQueryRequest>()
->Field("Distance", &ShapeCastRequest::m_distance)
->Field("Start", &ShapeCastRequest::m_start)
->Field("Direction", &ShapeCastRequest::m_direction)
->Field("ShapeConfiguration", &ShapeCastRequest::m_shapeConfiguration)
->Field("HitFlags", &ShapeCastRequest::m_hitFlags)
->Field("ReportMultipleHits", &ShapeCastRequest::m_reportMultipleHits)
;
}
}
namespace ShapeCastRequestHelpers
{
ShapeCastRequest CreateSphereCastRequest(float radius,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType /*= SceneQuery::QueryType::StaticAndDynamic*/,
CollisionGroup collisionGroup /*= CollisionGroup::All*/,
SceneQuery::FilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = AZStd::make_shared<Physics::SphereShapeConfiguration>(radius);
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return request;
}
ShapeCastRequest CreateBoxCastRequest(const AZ::Vector3& boxDimensions,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType /*= SceneQuery::QueryType::StaticAndDynamic*/,
CollisionGroup collisionGroup /*= CollisionGroup::All*/,
SceneQuery::FilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = AZStd::make_shared<Physics::BoxShapeConfiguration>(boxDimensions);
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return request;
}
ShapeCastRequest CreateCapsuleCastRequest(float capsuleRadius, float capsuleHeight,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType /*= SceneQuery::QueryType::StaticAndDynamic*/,
CollisionGroup collisionGroup /*= CollisionGroup::All*/,
SceneQuery::FilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = AZStd::make_shared<Physics::CapsuleShapeConfiguration>(capsuleHeight, capsuleRadius);
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return request;
}
} // namespace ShapeCastRequestHelpers
/*static*/ void OverlapRequest::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<OverlapRequest, SceneQueryRequest>()
->Field("Pose", &OverlapRequest::m_pose)
->Field("ShapeConfiguration", &OverlapRequest::m_shapeConfiguration)
;
}
}
namespace OverlapRequestHelpers
{
OverlapRequest CreateSphereOverlapRequest(float radius, const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = AZStd::make_shared<Physics::SphereShapeConfiguration>(radius);
overlapRequest.m_filterCallback = filterCallback;
return overlapRequest;
}
OverlapRequest CreateBoxOverlapRequest(const AZ::Vector3& dimensions, const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = AZStd::make_shared<Physics::BoxShapeConfiguration>(dimensions);
overlapRequest.m_filterCallback = filterCallback;
return overlapRequest;
}
OverlapRequest CreateCapsuleOverlapRequest(float height, float radius, const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback /*= nullptr*/)
{
AzPhysics::OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = AZStd::make_shared<Physics::CapsuleShapeConfiguration>(height, radius);
overlapRequest.m_filterCallback = filterCallback;
return overlapRequest;
}
} // namespace OverlapRequestHelpers
/*static*/ void SceneQueryHit::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SceneQueryHit>()
->Field("Distance", &SceneQueryHit::m_distance)
->Field("Position", &SceneQueryHit::m_position)
->Field("Normal", &SceneQueryHit::m_normal)
->Field("BodyHandle", &SceneQueryHit::m_bodyHandle)
->Field("EntityId", &SceneQueryHit::m_entityId)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SceneQueryHit>("SceneQueryHit")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Distance", BehaviorValueProperty(&SceneQueryHit::m_distance))
->Property("Position", BehaviorValueProperty(&SceneQueryHit::m_position))
->Property("Normal", BehaviorValueProperty(&SceneQueryHit::m_normal))
->Property("BodyHandle", BehaviorValueProperty(&SceneQueryHit::m_bodyHandle))
->Property("EntityId", BehaviorValueProperty(&SceneQueryHit::m_entityId))
;
}
}
/*static*/ void SceneQueryHits::Reflect(AZ::ReflectContext* context)
{
SceneQueryHit::Reflect(context);
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SceneQueryHits>()
->Field("HitArray", &SceneQueryHits::m_hits)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<SceneQueryHits>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Property("HitArray", BehaviorValueProperty(&SceneQueryHits::m_hits))
;
}
}
} // namespace Physics
@@ -0,0 +1,312 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
class ReflectContext;
}
namespace Physics
{
class Material;
class Shape;
class ShapeConfiguration;
}
namespace AzPhysics
{
struct SimulatedBody;
struct SceneQueryHit;
struct SceneQueryHits;
using SceneQueryHitsList = AZStd::vector<SceneQueryHits>;
namespace SceneQuery
{
//! Enum to specify the hit type returned by the filter callback.
enum class QueryHitType : AZ::u8
{
None, //!< The hit should not be reported.
Touch, //!< The hit should be reported but it should not block the query
Block //!< The hit should be reported and it should block the query
};
//! Enum to specify which shapes are included in the query.
enum class QueryType : AZ::u8
{
Static, //!< Only test against static shapes
Dynamic, //!< Only test against dynamic shapes
StaticAndDynamic //!< Test against both static and dynamic shapes
};
//! Scene query and geometry query behavior flags.
//!
//! HitFlags are used for 3 different purposes:
//!
//! 1) To request hit fields to be filled in by scene queries (such as hit position, normal, face index or UVs).
//! 2) Once query is completed, to indicate which fields are valid (note that a query may produce more valid fields than requested).
//! 3) To specify additional options for the narrow phase and mid-phase intersection routines.
enum class HitFlags : AZ::u16
{
Position = (1 << 0), //!< "position" member of the hit is valid
Normal = (1 << 1), //!< "normal" member of the hit is valid
UV = (1 << 3), //!< "u" and "v" barycentric coordinates of the hit are valid. Not applicable to ShapeCast queries.
//! Performance hint flag for ShapeCasts when it is known upfront there's no initial overlap.
//! NOTE: using this flag may cause undefined results if shapes are initially overlapping.
AssumeNoInitialOverlap = (1 << 4),
MeshMultiple = (1 << 5), //!< Report all hits for meshes rather than just the first. Not applicable to ShapeCast queries.
//! Report any first hit for meshes. If neither MeshMultiple nor MeshAny is specified,
//! a single closest hit will be reported for meshes.
MeshAny = (1 << 6),
//! Report hits with back faces of mesh triangles. Also report hits for raycast
//! originating on mesh surface and facing away from the surface normal. Not applicable to ShapeCast queries.
MeshBothSides = (1 << 7),
PreciseSweep = (1 << 8), //!< Use more accurate but slower narrow phase sweep tests.
MTD = (1 << 9), //!< Report the minimum translation depth, normal and contact point.
FaceIndex = (1 << 10), //!< "face index" member of the hit is valid. Required to get the per-face material data.
Default = Position | Normal | FaceIndex
};
//! Bitwise operators for HitFlags
inline HitFlags operator|(HitFlags lhs, HitFlags rhs)
{
return static_cast<HitFlags>(static_cast<AZ::u16>(lhs) | static_cast<AZ::u16>(rhs));
}
inline HitFlags operator&(HitFlags lhs, HitFlags rhs)
{
return static_cast<HitFlags>(static_cast<AZ::u16>(lhs) & static_cast<AZ::u16>(rhs));
}
//! Flag used to mark which members are valid in a SceneQueryHit object.
//! Example: if SceneQueryHit::m_resultFlags & ResultFlags::Distance is true,
//! then the SceneQueryHit::m_distance member would have a valid value.
enum ResultFlags : AZ::u8
{
Invalid = 0,
Distance = (1 << 0),
BodyHandle = (1 << 1),
EntityId = (1 << 2),
Shape = (1 << 3),
Material = (1 << 4),
Position = (1 << 5),
Normal = (1 << 6)
};
//! Bitwise operators for ResultFlags
inline ResultFlags operator|(ResultFlags lhs, ResultFlags rhs)
{
return static_cast<ResultFlags>(static_cast<AZ::u8>(lhs) | static_cast<AZ::u8>(rhs));
}
inline ResultFlags operator|=(ResultFlags& lhs, ResultFlags rhs)
{
return (lhs = (lhs | rhs));
}
inline ResultFlags operator&(ResultFlags lhs, ResultFlags rhs)
{
return static_cast<ResultFlags>(static_cast<AZ::u8>(lhs) & static_cast<AZ::u8>(rhs));
}
//! Callback used for directed scene queries: RayCasts and ShapeCasts
using FilterCallback = AZStd::function<QueryHitType(const SimulatedBody* body, const Physics::Shape* shape)>;
//! Callback used for undirected scene queries: Overlaps
using OverlapFilterCallback = AZStd::function<bool(const SimulatedBody* body, const Physics::Shape* shape)>;
//! Callback for unbounded world queries. These are queries which don't require
//! building the entire result vector, and so saves memory for very large numbers of hits.
//! Called with '{ hit }' repeatedly until there are no more hits, then called with '{}', then never called again.
//! Returns 'true' to continue processing more hits, or 'false' otherwise. If the function ever returns
//! 'false', it is unspecified if the finalizing call '{}' occurs.
using UnboundedOverlapHitCallback = AZStd::function<bool(AZStd::optional<SceneQueryHit>&&)>;
using AsyncRequestId = int;
using AsyncCallback = AZStd::function<void(AsyncRequestId requestId, SceneQueryHits hits)>;
using AsyncBatchCallback = AZStd::function<void(AsyncRequestId requestId, SceneQueryHitsList hits)>;
//! Helper used to reflect all required objects in PhysicsSceneQuery.h
void ReflectSceneQueryObjects(AZ::ReflectContext* context);
} // namespace SceneQuery
//! Base Scene Query request.
//! Not valid to be used with Scene::QueryScene functions
struct SceneQueryRequest
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(SceneQueryRequest, "{76ECAB7D-42BA-461F-82E6-DCED8E1BDCB9}");
static void Reflect(AZ::ReflectContext* context);
virtual ~SceneQueryRequest() = default;
AZ::u64 m_maxResults = 32; //!< The Maximum results for this request to return, this is limited by the value set in the SceneConfiguration
CollisionGroup m_collisionGroup = CollisionGroup::All; //!< Collision filter for the query.
SceneQuery::QueryType m_queryType = SceneQuery::QueryType::StaticAndDynamic; //!< Object types to include in the query
};
using SceneQueryRequests = AZStd::vector<AZStd::shared_ptr<SceneQueryRequest>>;
//! Casts a ray from a starting pose along a direction returning objects that intersected with the ray.
struct RayCastRequest :
public SceneQueryRequest
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(RayCastRequest, "{53EAD088-A391-48F1-8370-2A1DBA31512F}", SceneQueryRequest);
static void Reflect(AZ::ReflectContext* context);
float m_distance = 500.0f; //!< The distance to cast along the direction.
AZ::Vector3 m_start = AZ::Vector3::CreateZero(); //!< World space point where ray starts from.
AZ::Vector3 m_direction = AZ::Vector3::CreateZero(); //!< World space direction (Should be normalized)
SceneQuery::HitFlags m_hitFlags = SceneQuery::HitFlags::Default; //!< Query behavior flags
SceneQuery::FilterCallback m_filterCallback = nullptr; //!< Hit filtering function
bool m_reportMultipleHits = false; //!< flag to have the cast stop after the first hit or return all hits along the query.
};
//! Sweeps a shape from a starting pose along a direction returning objects that intersected with the shape.
struct ShapeCastRequest :
public SceneQueryRequest
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(ShapeCastRequest, "{52F6C536-92F6-4C05-983D-0A74800AE56D}", SceneQueryRequest);
static void Reflect(AZ::ReflectContext* context);
float m_distance = 500.0f; //! The distance to cast along the direction.
AZ::Transform m_start = AZ::Transform::CreateIdentity(); //!< World space start position. Assumes only rotation + translation (no scaling).
AZ::Vector3 m_direction = AZ::Vector3::CreateZero(); //!< World space direction (Should be normalized)
AZStd::shared_ptr<Physics::ShapeConfiguration> m_shapeConfiguration; //!< Shape information.
SceneQuery::HitFlags m_hitFlags = SceneQuery::HitFlags::Default; //!< Query behavior flags
SceneQuery::FilterCallback m_filterCallback = nullptr; //!< Hit filtering function
bool m_reportMultipleHits = false; //!< flag to have the cast stop after the first hit or return all hits along the query.
};
namespace ShapeCastRequestHelpers
{
//! Helper to create a ShapeCastRequest with a SphereShapeConfiguration as its shape configuration.
ShapeCastRequest CreateSphereCastRequest(float radius,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType = SceneQuery::QueryType::StaticAndDynamic,
CollisionGroup collisionGroup = CollisionGroup::All,
SceneQuery::FilterCallback filterCallback = nullptr);
//! Helper to create a ShapeCastRequest with a BoxShapeConfiguration as its shape configuration.
ShapeCastRequest CreateBoxCastRequest(const AZ::Vector3& boxDimensions,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType = SceneQuery::QueryType::StaticAndDynamic,
CollisionGroup collisionGroup = CollisionGroup::All,
SceneQuery::FilterCallback filterCallback = nullptr);
//! Helper to create a ShapeCastRequest with a CapsuleShapeConfiguration as its shape configuration.
ShapeCastRequest CreateCapsuleCastRequest(float capsuleRadius, float capsuleHeight,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType = SceneQuery::QueryType::StaticAndDynamic,
CollisionGroup collisionGroup = CollisionGroup::All,
SceneQuery::FilterCallback filterCallback = nullptr);
} // namespace ShapeCastRequestHelpers
//! Searches a region enclosed by a specified shape for any overlapping objects in the scene.
struct OverlapRequest :
public SceneQueryRequest
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(OverlapRequest, "{3DC986C2-316B-4C54-A0A6-8ABBB8ABCC4A}", SceneQueryRequest);
static void Reflect(AZ::ReflectContext* context);
AZ::Transform m_pose = AZ::Transform::CreateIdentity(); //!< Initial shape pose
AZStd::shared_ptr<Physics::ShapeConfiguration> m_shapeConfiguration; //!< Shape information.
SceneQuery::OverlapFilterCallback m_filterCallback = nullptr; //!< Hit filtering function
SceneQuery::UnboundedOverlapHitCallback m_unboundedOverlapHitCallback = nullptr; //!< When not nullptr the request will perform an unbounded overlap query.
};
namespace OverlapRequestHelpers
{
//! Helper to create a OverlapRequest with a SphereShapeConfiguration as its shape configuration.
OverlapRequest CreateSphereOverlapRequest(float radius,const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback = nullptr);
//! Helper to create a OverlapRequest with a BoxShapeConfiguration as its shape configuration.
OverlapRequest CreateBoxOverlapRequest(const AZ::Vector3& dimensions, const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback = nullptr);
//! Helper to create a OverlapRequest with a CapsuleShapeConfiguration as its shape configuration.
OverlapRequest CreateCapsuleOverlapRequest(float height, float radius, const AZ::Transform& pose,
SceneQuery::OverlapFilterCallback filterCallback = nullptr);
} // namespace OverlapRequestHelpers
//! Structure that contains information of an individual hit related to a SceneQuery.
struct SceneQueryHit
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_TYPE_INFO(SceneQueryHit, "{7A7201B9-67B5-438B-B4EB-F3EEBB78C617}");
static void Reflect(AZ::ReflectContext* context);
virtual ~SceneQueryHit() = default;
explicit operator bool() const { return IsValid(); }
bool IsValid() const { return m_resultFlags != SceneQuery::ResultFlags::Invalid; }
//! Flags used to determine what members are valid.
//! If the flag is true, the member will have a valid value.
SceneQuery::ResultFlags m_resultFlags = SceneQuery::ResultFlags::Invalid;
//! The distance along the cast at which the hit occurred as given by Dot(m_normal, startPoint) - Dot(m_normal, m_position).
//! Valid if SceneQuery::ResultFlags::Distance is set.
float m_distance = 0.0f;
//! Handler to the simulated body that was hit.
//! Valid if SceneQuery::ResultFlags::BodyHandle is set.
AzPhysics::SimulatedBodyHandle m_bodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
//! The Entity Id of the body that was hit.
//! Valid if SceneQuery::ResultFlags::EntityId is set.
AZ::EntityId m_entityId;
//! The shape on the body that was hit.
//! Valid if SceneQuery::ResultFlags::Shape is set.
Physics::Shape* m_shape = nullptr;
//! The material on the shape (or face) that was hit.
//! Valid if SceneQuery::ResultFlags::Material is set.
Physics::Material* m_material = nullptr;
//! The position of the hit in world space.
//! Valid if SceneQuery::ResultFlags::Position is set.
AZ::Vector3 m_position = AZ::Vector3::CreateZero();
//! The normal of the surface hit.
//! Valid if SceneQuery::ResultFlags::Normal is set.
AZ::Vector3 m_normal = AZ::Vector3::CreateZero();
};
//! Structure that contains all hits related to a SceneQuery.
struct SceneQueryHits
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_TYPE_INFO(SceneQueryHits, "{BAFCC4E7-A06B-4909-B2AE-C89D9E84FE4E}");
static void Reflect(AZ::ReflectContext* context);
explicit operator bool() const { return !m_hits.empty(); }
AZStd::vector<SceneQueryHit> m_hits;
};
}
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(AzPhysics::SceneQuery::QueryType, "{0E0E56A8-73A8-40B4-B438-B19FC852E3C0}");
AZ_TYPE_INFO_SPECIALIZE(AzPhysics::SceneQuery::ResultFlags, "{E081DB48-CFC8-4480-BB69-AA5BFC8C5FEE}");
}
@@ -0,0 +1,207 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyAutomation.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBody, AZ::SystemAllocator, 0);
namespace Internal
{
template<class Event, class Function>
Event* GetEvent(AZ::EntityId entityid, Function getEventFunc)
{
auto* physicsSystem = AZ::Interface<SystemInterface>::Get();
auto* sceneInterface = AZ::Interface<SceneInterface>::Get();
if (physicsSystem != nullptr && sceneInterface != nullptr)
{
auto [sceneHandle, bodyHandle] = physicsSystem->FindAttachedBodyHandleFromEntityId(entityid);
if (SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle))
{
auto func = AZStd::bind(getEventFunc, body);
return func();
}
}
return nullptr;
}
}
/*static*/ void SimulatedBody::Reflect(AZ::ReflectContext* context)
{
Automation::SimulatedBodyCollisionAutomationHandler::Reflect(context);
Automation::SimulatedBodyTriggerAutomationHandler::Reflect(context);
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AzPhysics::SimulatedBody>()
->Version(1)
->Field("SceneOwner", &SimulatedBody::m_sceneOwner)
->Field("BodyHandle", &SimulatedBody::m_bodyHandle)
;
}
// reflect the collision and trigger AZ::Events
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
const AZStd::vector<AZStd::string> collisionEventParams = {
"Simulated Body Handle",
"Collision Event"
};
const AZ::BehaviorAzEventDescription onCollisionBeginEventDescription =
{
"On Collision Begin event",
collisionEventParams // Parameters
};
const AZ::BehaviorAzEventDescription onCollisionPersistDescription =
{
"On Collision Persist event",
collisionEventParams // Parameters
};
const AZ::BehaviorAzEventDescription onCollisionEndEventDescription =
{
"On Collision End event",
collisionEventParams // Parameters
};
const AZStd::vector<AZStd::string> triggerEventParams = {
"Simulated Body Handle",
"Trigger Event"
};
const AZ::BehaviorAzEventDescription onTriggerEnterDescription =
{
"On Trigger Enter event",
triggerEventParams // Parameters
};
const AZ::BehaviorAzEventDescription onTriggerExitDescription =
{
"On Trigger Exit event",
triggerEventParams // Parameters
};
const auto getOnCollisionBegin = [](AZ::EntityId id) -> SimulatedBodyEvents::OnCollisionBegin*
{
return Internal::GetEvent<SimulatedBodyEvents::OnCollisionBegin>(id, &SimulatedBody::GetOnCollisionBeginEvent);
};
const auto getOnCollisionPersist = [](AZ::EntityId id) -> SimulatedBodyEvents::OnCollisionPersist*
{
return Internal::GetEvent<SimulatedBodyEvents::OnCollisionPersist>(id, &SimulatedBody::GetOnCollisionPersistEvent);
};
const auto getOnCollisionEnd = [](AZ::EntityId id) -> SimulatedBodyEvents::OnCollisionEnd*
{
return Internal::GetEvent<SimulatedBodyEvents::OnCollisionEnd>(id, &SimulatedBody::GetOnCollisionEndEvent);
};
const auto getOnTriggerEnter = [](AZ::EntityId id) -> SimulatedBodyEvents::OnTriggerEnter*
{
return Internal::GetEvent<SimulatedBodyEvents::OnTriggerEnter>(id, &SimulatedBody::GetOnTriggerEnterEvent);
};
const auto getOnTriggerExit = [](AZ::EntityId id) -> SimulatedBodyEvents::OnTriggerExit*
{
return Internal::GetEvent<SimulatedBodyEvents::OnTriggerExit>(id, &SimulatedBody::GetOnTriggerExitEvent);
};
behaviorContext->Class<SimulatedBody>("SimulatedBody")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "Physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Method("GetOnCollisionBeginEvent", getOnCollisionBegin)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(onCollisionBeginEventDescription))
->Method("GetOnCollisionPersistEvent", getOnCollisionPersist)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(onCollisionPersistDescription))
->Method("GetOnCollisionEndEvent", getOnCollisionEnd)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(onCollisionEndEventDescription))
->Method("GetOnTriggerEnterEvent", getOnTriggerEnter)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(onTriggerEnterDescription))
->Method("GetOnTriggerExitEvent", getOnTriggerExit)
->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move(onTriggerExitDescription))
;
}
}
void SimulatedBody::ProcessCollisionEvent(const CollisionEvent& collision) const
{
switch (collision.m_type)
{
case CollisionEvent::Type::Begin:
m_collisionBeginEvent.Signal(m_bodyHandle, collision);
break;
case CollisionEvent::Type::Persist:
m_collisionPersistEvent.Signal(m_bodyHandle, collision);
break;
case CollisionEvent::Type::End:
m_collisionEndEvent.Signal(m_bodyHandle, collision);
break;
default:
AZ_Warning("Physics", false, "[SimulatedBody::ProcessCollisionEvent] Unexpected collison type.");
break;
}
}
void SimulatedBody::ProcessTriggerEvent(const TriggerEvent& trigger) const
{
switch (trigger.m_type)
{
case AzPhysics::TriggerEvent::Type::Enter:
m_triggerEnterEvent.Signal(m_bodyHandle, trigger);
break;
case AzPhysics::TriggerEvent::Type::Exit:
m_triggerExitEvent.Signal(m_bodyHandle, trigger);
break;
default:
AZ_Warning("Physics", false, "[SimulatedBody::ProcessTriggerEvent] Unexpected trigger type.");
break;
}
}
Scene* SimulatedBody::GetScene()
{
if (auto* physicsSystem = AZ::Interface<SystemInterface>::Get())
{
return physicsSystem->GetScene(m_sceneOwner);
}
return nullptr;
}
SimulatedBodyEvents::OnCollisionBegin* SimulatedBody::GetOnCollisionBeginEvent()
{
return &m_collisionBeginEvent;
}
SimulatedBodyEvents::OnCollisionPersist* SimulatedBody::GetOnCollisionPersistEvent()
{
return &m_collisionPersistEvent;
}
SimulatedBodyEvents::OnCollisionEnd* SimulatedBody::GetOnCollisionEndEvent()
{
return &m_collisionEndEvent;
}
SimulatedBodyEvents::OnTriggerEnter* SimulatedBody::GetOnTriggerEnterEvent()
{
return &m_triggerEnterEvent;
}
SimulatedBodyEvents::OnTriggerExit* SimulatedBody::GetOnTriggerExitEvent()
{
return &m_triggerExitEvent;
}
}
@@ -0,0 +1,170 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyEvents.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
class ReflectContext;
}
namespace AzPhysics
{
namespace Automation
{
class SimulatedBodyCollisionAutomationHandler;
class SimulatedBodyTriggerAutomationHandler;
}
class Scene;
struct CollisionEvent;
struct TriggerEvent;
//! Base class for all Simulated bodies in Physics.
struct SimulatedBody
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(AzPhysics::SimulatedBody, "{BCC37A4F-1C05-4660-9E41-0CCF2D5E7175}");
static void Reflect(AZ::ReflectContext* context);
virtual ~SimulatedBody() = default;
//! The current Scene the simulated body is contained.
SceneHandle m_sceneOwner = AzPhysics::InvalidSceneHandle;
//! The handle to this simulated body
SimulatedBodyHandle m_bodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
//! Flag to determine if the body is part of the simulation.
//! When true the body will be affected by any forces, collisions, and found with scene queries.
bool m_simulating = true;
//! Helper functions for setting user data.
//! @param userData Can be a pointer to any type as internally will be cast to a void*. Object lifetime not managed by the SimulatedBody.
template<typename T>
void SetUserData(T* userData);
//! Helper functions for getting the set user data.
//! @return Will return a void* to the user data set.
void* GetUserData()
{
return m_customUserData;
}
//! Perform a ray cast on this Simulated Body.
//! @param request The request to make.
//! @return Returns the closest hit, if any, against this simulated body.
virtual AzPhysics::SceneQueryHit RayCast(const RayCastRequest& request) = 0;
//! Helper to direct the CollisionEvent to the correct handler.
//! Will invoke the OnCollisionBegin or OnCollisionPersist or OnCollisionEnd event.
//! @param collision The collision data to be routed.
void ProcessCollisionEvent(const CollisionEvent& collision) const;
//! Helper to direct the TriggerEvent to the correct handler.
//! Will invoke the OnTriggerEnter or OnTriggerExitevent event.
//! @param trigger The trigger data to be routed.
void ProcessTriggerEvent(const TriggerEvent& trigger) const;
//! Helpers to register a handler for Collision events on this Simulated body.
//! OnCollisionBegin is when two bodies start to collide.
//! OnCollisionPersist is when two bodies continue to collide.
//! OnCollisionEnd is when two bodies stop colliding.
void RegisterOnCollisionBeginHandler(SimulatedBodyEvents::OnCollisionBegin::Handler& handler);
//! see RegisterOnCollisionBeginHandler
void RegisterOnCollisionPersistHandler(SimulatedBodyEvents::OnCollisionPersist::Handler& handler);
//! see RegisterOnCollisionBeginHandler
void RegisterOnCollisionEndHandler(SimulatedBodyEvents::OnCollisionEnd::Handler& handler);
//! Helpers to register a handler for Trigger Events on this Simulated body.
//! OnTriggerEnter is when a body enters a trigger.
//! OnTriggerExit is when a body leaves a trigger.
void RegisterOnTriggerEnterHandler(SimulatedBodyEvents::OnTriggerEnter::Handler& handler);
//! see RegisterOnTriggerEnterHandler
void RegisterOnTriggerExitHandler(SimulatedBodyEvents::OnTriggerExit::Handler& handler);
virtual AZ::Crc32 GetNativeType() const = 0;
virtual void* GetNativePointer() const = 0;
//! Helper to get the scene this body is attached too.
//! @return Returns a pointer to the scene.
virtual Scene* GetScene();
// Temporary until LYN-438 work is complete - from old WorldBody Class
virtual AZ::EntityId GetEntityId() const = 0;
virtual AZ::Transform GetTransform() const = 0;
virtual void SetTransform(const AZ::Transform& transform) = 0;
virtual AZ::Vector3 GetPosition() const = 0;
virtual AZ::Quaternion GetOrientation() const = 0;
virtual AZ::Aabb GetAabb() const = 0;
private:
friend class Automation::SimulatedBodyCollisionAutomationHandler;
friend class Automation::SimulatedBodyTriggerAutomationHandler;
SimulatedBodyEvents::OnCollisionBegin m_collisionBeginEvent;
SimulatedBodyEvents::OnCollisionPersist m_collisionPersistEvent;
SimulatedBodyEvents::OnCollisionEnd m_collisionEndEvent;
SimulatedBodyEvents::OnTriggerEnter m_triggerEnterEvent;
SimulatedBodyEvents::OnTriggerExit m_triggerExitEvent;
void* m_customUserData = nullptr;
// helpers for reflecting to behavior context
SimulatedBodyEvents::OnCollisionBegin* GetOnCollisionBeginEvent();
SimulatedBodyEvents::OnCollisionPersist* GetOnCollisionPersistEvent();
SimulatedBodyEvents::OnCollisionEnd* GetOnCollisionEndEvent();
SimulatedBodyEvents::OnTriggerEnter* GetOnTriggerEnterEvent();
SimulatedBodyEvents::OnTriggerExit* GetOnTriggerExitEvent();
};
//! Alias for a list of non owning weak pointers to SimulatedBody objects.
using SimulatedBodyList = AZStd::vector<SimulatedBody*>;
template<typename T>
void SimulatedBody::SetUserData(T* userData)
{
m_customUserData = static_cast<void*>(userData);
}
inline void SimulatedBody::RegisterOnCollisionBeginHandler(SimulatedBodyEvents::OnCollisionBegin::Handler& handler)
{
handler.Connect(m_collisionBeginEvent);
}
inline void SimulatedBody::RegisterOnCollisionPersistHandler(SimulatedBodyEvents::OnCollisionPersist::Handler& handler)
{
handler.Connect(m_collisionPersistEvent);
}
inline void SimulatedBody::RegisterOnCollisionEndHandler(SimulatedBodyEvents::OnCollisionEnd::Handler& handler)
{
handler.Connect(m_collisionEndEvent);
}
inline void SimulatedBody::RegisterOnTriggerEnterHandler(SimulatedBodyEvents::OnTriggerEnter::Handler& handler)
{
handler.Connect(m_triggerEnterEvent);
}
inline void SimulatedBody::RegisterOnTriggerExitHandler(SimulatedBodyEvents::OnTriggerExit::Handler& handler)
{
handler.Connect(m_triggerExitEvent);
}
}
@@ -0,0 +1,260 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyAutomation.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
namespace AzPhysics::Automation
{
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyCollisionAutomationHandler, AZ::SystemAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyTriggerAutomationHandler, AZ::SystemAllocator, 0);
/*static*/ void SimulatedBodyCollisionAutomationHandler::Reflect(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<AzPhysics::Automation::AutomationCollisionNotificationsBus>("CollisionNotificationBus")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "Physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Handler<SimulatedBodyCollisionAutomationHandler>()
;
}
}
SimulatedBodyCollisionAutomationHandler::SimulatedBodyCollisionAutomationHandler()
{
m_collisionBeginHandler = SimulatedBodyEvents::OnCollisionBegin::Handler(
[this]([[maybe_unused]] SimulatedBodyHandle bodyHandle,
const CollisionEvent& event)
{
OnCollisionBeginEvent(event);
});
m_collisionPersistHandler = SimulatedBodyEvents::OnCollisionPersist::Handler(
[this]([[maybe_unused]] SimulatedBodyHandle bodyHandle,
const CollisionEvent& event)
{
OnCollisionPersistEvent(event);
});
m_collisionEndHandler = SimulatedBodyEvents::OnCollisionEnd::Handler(
[this]([[maybe_unused]] SimulatedBodyHandle bodyHandle,
const CollisionEvent& event)
{
OnCollisionEndEvent(event);
});
SetEvent(&SimulatedBodyCollisionAutomationHandler::OnCollisionBegin, "OnCollisionBegin");
SetEvent(&SimulatedBodyCollisionAutomationHandler::OnCollisionPersist, "OnCollisionPersist");
SetEvent(&SimulatedBodyCollisionAutomationHandler::OnCollisionEnd, "OnCollisionEnd");
}
void SimulatedBodyCollisionAutomationHandler::Disconnect()
{
m_collisionBeginHandler.Disconnect();
m_collisionPersistHandler.Disconnect();
m_collisionEndHandler.Disconnect();
}
bool SimulatedBodyCollisionAutomationHandler::Connect(AZ::BehaviorValueParameter* id /*= nullptr*/)
{
if (id && id->ConvertTo<typename AZ::EntityId>())
{
m_connectedEntityId = *id->GetAsUnsafe<typename AZ::EntityId>();
auto* physicsSystem = AZ::Interface<SystemInterface>::Get();
auto* sceneInterface = AZ::Interface<SceneInterface>::Get();
if (physicsSystem != nullptr && sceneInterface != nullptr)
{
auto [sceneHandle, bodyHandle] = physicsSystem->FindAttachedBodyHandleFromEntityId(m_connectedEntityId);
if (SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle))
{
bool connected = false;
if (auto* collisionBeginEvent = body->GetOnCollisionBeginEvent())
{
m_collisionBeginHandler.Connect(*collisionBeginEvent);
connected = true;
}
if (auto* collisionPersistEvent = body->GetOnCollisionPersistEvent())
{
m_collisionPersistHandler.Connect(*collisionPersistEvent);
connected = true;
}
if (auto* collisionEndEvent = body->GetOnCollisionEndEvent())
{
m_collisionEndHandler.Connect(*collisionEndEvent);
connected = true;
}
return connected;
}
}
}
return false;
}
bool SimulatedBodyCollisionAutomationHandler::IsConnected()
{
return m_collisionBeginHandler.IsConnected() || m_collisionPersistHandler.IsConnected() || m_collisionEndHandler.IsConnected();
}
bool SimulatedBodyCollisionAutomationHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
{
if (id && id->ConvertTo<typename AZ::EntityId>())
{
return m_connectedEntityId == *id->GetAsUnsafe<typename AZ::EntityId>() && IsConnected();
}
return false;
}
int SimulatedBodyCollisionAutomationHandler::GetFunctionIndex(const char* functionName) const
{
if (azstricmp(functionName, "OnCollisionBegin") == 0)
{
return FN_OnCollisionBegin;
}
if (azstricmp(functionName, "OnCollisionPersist") == 0)
{
return FN_OnCollisionPersist;
}
if (azstricmp(functionName, "OnCollisionEnd") == 0)
{
return FN_OnCollisionEnd;
}
return -1;
}
void SimulatedBodyCollisionAutomationHandler::OnCollisionBeginEvent(const CollisionEvent& event)
{
Call(FN_OnCollisionBegin, event.m_body2->GetEntityId(), event.m_contacts); //send m_body2 entity id as that is the other body involved in the collision.
}
void SimulatedBodyCollisionAutomationHandler::OnCollisionPersistEvent(const CollisionEvent& event)
{
Call(FN_OnCollisionPersist, event.m_body2->GetEntityId(), event.m_contacts); //send m_body2 entity id as that is the other body involved in the collision.
}
void SimulatedBodyCollisionAutomationHandler::OnCollisionEndEvent(const CollisionEvent& event)
{
Call(FN_OnCollisionEnd, event.m_body2->GetEntityId()); //send m_body2 entity id as that is the other body involved in the collision.
}
/*static*/ void SimulatedBodyTriggerAutomationHandler::Reflect(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<AzPhysics::Automation::AutomationTriggerNotificationsBus>("TriggerNotificationBus")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "Physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Handler<SimulatedBodyTriggerAutomationHandler>()
;
}
}
SimulatedBodyTriggerAutomationHandler::SimulatedBodyTriggerAutomationHandler()
{
m_triggerEnterHandler = SimulatedBodyEvents::OnTriggerEnter::Handler(
[this]([[maybe_unused]] SimulatedBodyHandle bodyHandle,
const TriggerEvent& event)
{
OnTriggerEnterEvent(event);
});
m_triggerExitHandler = SimulatedBodyEvents::OnTriggerExit::Handler(
[this]([[maybe_unused]] SimulatedBodyHandle bodyHandle,
const TriggerEvent& event)
{
OnTriggerExitEvent(event);
});
SetEvent(&SimulatedBodyTriggerAutomationHandler::OnTriggerEnter, "OnTriggerEnter");
SetEvent(&SimulatedBodyTriggerAutomationHandler::OnTriggerExit, "OnTriggerExit");
}
void SimulatedBodyTriggerAutomationHandler::Disconnect()
{
m_triggerEnterHandler.Disconnect();
m_triggerExitHandler.Disconnect();
}
bool SimulatedBodyTriggerAutomationHandler::Connect(AZ::BehaviorValueParameter* id /*= nullptr*/)
{
if (id && id->ConvertTo<typename AZ::EntityId>())
{
m_connectedEntityId = *id->GetAsUnsafe<typename AZ::EntityId>();
auto* physicsSystem = AZ::Interface<SystemInterface>::Get();
auto* sceneInterface = AZ::Interface<SceneInterface>::Get();
if (physicsSystem != nullptr && sceneInterface != nullptr)
{
auto [sceneHandle, bodyHandle] = physicsSystem->FindAttachedBodyHandleFromEntityId(m_connectedEntityId);
if (SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle))
{
bool connected = false;
if (auto* triggerEnterEvent = body->GetOnTriggerEnterEvent())
{
m_triggerEnterHandler.Connect(*triggerEnterEvent);
connected = true;
}
if (auto* triggerExitEvent = body->GetOnTriggerExitEvent())
{
m_triggerExitHandler.Connect(*triggerExitEvent);
connected = true;
}
return connected;
}
}
}
return false;
}
bool SimulatedBodyTriggerAutomationHandler::IsConnected()
{
return m_triggerEnterHandler.IsConnected() || m_triggerExitHandler.IsConnected();
}
bool SimulatedBodyTriggerAutomationHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
{
if (id && id->ConvertTo<typename AZ::EntityId>())
{
return m_connectedEntityId == *id->GetAsUnsafe<typename AZ::EntityId>() && IsConnected();
}
return false;
}
int SimulatedBodyTriggerAutomationHandler::GetFunctionIndex(const char* functionName) const
{
if (azstricmp(functionName, "OnTriggerEnter") == 0)
{
return FN_OnTriggerEnter;
}
if (azstricmp(functionName, "OnTriggerExit") == 0)
{
return FN_OnTriggerExit;
}
return -1;
}
void SimulatedBodyTriggerAutomationHandler::OnTriggerEnterEvent(const TriggerEvent& event)
{
Call(FN_OnTriggerEnter, event.m_otherBody->GetEntityId());
}
void SimulatedBodyTriggerAutomationHandler::OnTriggerExitEvent(const TriggerEvent& event)
{
Call(FN_OnTriggerExit, event.m_otherBody->GetEntityId());
}
}
@@ -0,0 +1,147 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyEvents.h>
namespace AZ
{
class ReflectContext;
struct BehaviorValueParameter;
}
namespace AzPhysics::Automation
{
//! Buses to expose Collision and Trigger event to Automation
//! @{
class AutomationCollisionNotifications
: public AZ::ComponentBus
{
public:
virtual ~AutomationCollisionNotifications() = default;
virtual void OnCollisionBegin(AZ::EntityId entityId, const AZStd::vector<AzPhysics::Contact>& contacts) = 0;
virtual void OnCollisionPersist(AZ::EntityId entityId, const AZStd::vector<AzPhysics::Contact>& contacts) = 0;
virtual void OnCollisionEnd(AZ::EntityId entityId) = 0;
};
using AutomationCollisionNotificationsBus = AZ::EBus<AutomationCollisionNotifications>;
class AutomationTriggerNotifications
: public AZ::ComponentBus
{
public:
virtual ~AutomationTriggerNotifications() = default;
virtual void OnTriggerEnter(AZ::EntityId entityId) = 0;
virtual void OnTriggerExit(AZ::EntityId entityId) = 0;
};
using AutomationTriggerNotificationsBus = AZ::EBus<AutomationTriggerNotifications>;
//! @}
//! Collision Event Handler for Automation
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER macro as the signature
//! needs to be changed for script canvas.
class SimulatedBodyCollisionAutomationHandler
: public AutomationCollisionNotificationsBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(AzPhysics::Automation::SimulatedBodyCollisionAutomationHandler, "{B0493CB7-9D20-44E1-B744-1419E54CAF67}", AZ::BehaviorEBusHandler);
static void Reflect(AZ::ReflectContext* context);
SimulatedBodyCollisionAutomationHandler();
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
decltype(&SimulatedBodyCollisionAutomationHandler::OnCollisionBegin),
decltype(&SimulatedBodyCollisionAutomationHandler::OnCollisionPersist),
decltype(&SimulatedBodyCollisionAutomationHandler::OnCollisionEnd)
>;
private:
// AutomationCollisionNotificationsBus::Handler Interface
void OnCollisionBegin([[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const AZStd::vector<AzPhysics::Contact>& contacts) override {}
void OnCollisionPersist([[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const AZStd::vector<AzPhysics::Contact>& contacts) override {}
void OnCollisionEnd([[maybe_unused]] AZ::EntityId entityId) override {}
enum
{
FN_OnCollisionBegin,
FN_OnCollisionPersist,
FN_OnCollisionEnd,
FN_MAX
};
// AZ::BehaviorEBusHandler interface
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
int GetFunctionIndex(const char* functionName) const override;
void OnCollisionBeginEvent(const CollisionEvent& event);
void OnCollisionPersistEvent(const CollisionEvent& event);
void OnCollisionEndEvent(const CollisionEvent& event);
AZ::EntityId m_connectedEntityId;
SimulatedBodyEvents::OnCollisionBegin::Handler m_collisionBeginHandler;
SimulatedBodyEvents::OnCollisionPersist::Handler m_collisionPersistHandler;
SimulatedBodyEvents::OnCollisionEnd::Handler m_collisionEndHandler;
};
//! Trigger Event Handler for Automation
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER macro as the signature
//! needs to be changed for script canvas.
class SimulatedBodyTriggerAutomationHandler
: public AutomationTriggerNotificationsBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(AzPhysics::Automation::SimulatedBodyTriggerAutomationHandler, "{0BFA757E-F270-40D7-8543-B21260A987D0}", AZ::BehaviorEBusHandler);
static void Reflect(AZ::ReflectContext* context);
SimulatedBodyTriggerAutomationHandler();
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
decltype(&SimulatedBodyTriggerAutomationHandler::OnTriggerEnter),
decltype(&SimulatedBodyTriggerAutomationHandler::OnTriggerExit)
>;
private:
// AutomationTriggerNotificationsBus::Handler Interface
void OnTriggerEnter([[maybe_unused]] AZ::EntityId entityId) override {}
void OnTriggerExit([[maybe_unused]] AZ::EntityId entityId) override {}
enum
{
FN_OnTriggerEnter,
FN_OnTriggerExit,
FN_MAX
};
// AZ::BehaviorEBusHandler interface
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
int GetFunctionIndex(const char* functionName) const override;
void OnTriggerEnterEvent(const TriggerEvent& event);
void OnTriggerExitEvent(const TriggerEvent& event);
AZ::EntityId m_connectedEntityId;
SimulatedBodyEvents::OnTriggerEnter::Handler m_triggerEnterHandler;
SimulatedBodyEvents::OnTriggerExit::Handler m_triggerExitHandler;
};
}
@@ -0,0 +1,72 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Common/PhysicsSimulatedBodyEvents.h>
#include <AzCore/std/functional.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
namespace AzPhysics
{
namespace SimulatedBodyEvents
{
namespace Internal
{
//helper to register a handler
template<typename Handler, class Function>
void RegisterHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
Handler& handler, Function registerFunc)
{
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
if (AzPhysics::SimulatedBody* body = sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle))
{
auto func = AZStd::bind(registerFunc, body, AZStd::placeholders::_1);
func(handler);
}
}
}
}
void RegisterOnCollisionBeginHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionBegin::Handler& handler)
{
Internal::RegisterHandler(sceneHandle, bodyHandle, handler, &SimulatedBody::RegisterOnCollisionBeginHandler);
}
void RegisterOnCollisionPersistHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionPersist::Handler& handler)
{
Internal::RegisterHandler(sceneHandle, bodyHandle, handler, &SimulatedBody::RegisterOnCollisionPersistHandler);
}
void RegisterOnCollisionEndHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionEnd::Handler& handler)
{
Internal::RegisterHandler(sceneHandle, bodyHandle, handler, &SimulatedBody::RegisterOnCollisionEndHandler);
}
void RegisterOnTriggerEnterHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnTriggerEnter::Handler& handler)
{
Internal::RegisterHandler(sceneHandle, bodyHandle, handler, &SimulatedBody::RegisterOnTriggerEnterHandler);
}
void RegisterOnTriggerExitHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnTriggerExit::Handler& handler)
{
Internal::RegisterHandler(sceneHandle, bodyHandle, handler, &SimulatedBody::RegisterOnTriggerExitHandler);
}
} // namespace SimulatedBodyEvents
}
@@ -0,0 +1,77 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/Event.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AzPhysics
{
struct CollisionEvent;
struct TriggerEvent;
namespace SimulatedBodyEvents
{
//! Collision Events for Simulated bodies.
//! OnCollisionBegin is when two bodies start to collide. Will always be triggered before OnCollisionPersist and OnCollisionEnd.
//! OnCollisionPersist is when two bodies continue to collide. Can only be triggered after OnCollisionBegin and before OnCollisionEnd.
//! OnCollisionEnd is when two bodies stop colliding. Will always be triggered after OnCollisionBegin and OnCollisionPersist.
//! The SimulatedBodyHandle passed in the event will match CollisionEvent::m_bodyHandle1, CollisionEvent::m_bodyHandle2 will be the other body involved.
//! @note The CollisionEvent is only valid for the duration of the callback.
//! This may fire multiple times per frame.
using OnCollisionBegin = AZ::Event<SimulatedBodyHandle, const CollisionEvent&>;
//! see OnCollisionBegin
using OnCollisionPersist = AZ::Event<SimulatedBodyHandle, const CollisionEvent&>;
//! see OnCollisionBegin
using OnCollisionEnd = AZ::Event<SimulatedBodyHandle, const CollisionEvent&>;
//! Trigger Events for Simulated bodies.
//! OnTriggerEnter is when a body enters a trigger.
//! OnTriggerExit is when a body leaves a trigger. Will only be triggered after OnTriggerEnter.
//! These events will be triggered on both the trigger body and the body that entered/exited the trigger.
//! The SimulatedBodyHandle passed will be the body that the handler is registered to, which can be the Trigger or Other body.
//! @note The TriggerEvent is only valid for the duration of the callback.
//! This may fire multiple times per frame.
using OnTriggerEnter = AZ::Event<SimulatedBodyHandle, const TriggerEvent&>;
//! see OnTriggerEnter
using OnTriggerExit = AZ::Event<SimulatedBodyHandle, const TriggerEvent&>;
//! Helper to register a Collision Event handler.
//! @param sceneHandle A handle to the scene that owns the simulated body.
//! @param bodyHandle A handle to the simulated body.
//! @param handler The handle to register.
void RegisterOnCollisionBeginHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionBegin::Handler& handler);
//! see RegisterOnCollisionBeginHandler
void RegisterOnCollisionPersistHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionPersist::Handler& handler);
//! see RegisterOnCollisionBeginHandler
void RegisterOnCollisionEndHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnCollisionEnd::Handler& handler);
//! Helper to register a Trigger Event handler.
//! @param sceneHandle A handle to the scene that owns the simulated body.
//! @param bodyHandle A handle to the simulated body.
//! @param handler The handle to register.
void RegisterOnTriggerEnterHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnTriggerEnter::Handler& handler);
//! see RegisterOnTriggerEnterHandler
void RegisterOnTriggerExitHandler(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle,
OnTriggerExit::Handler& handler);
} // namespace SimulatedBodyEvents
}
@@ -11,33 +11,121 @@
*/
#pragma once
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/tuple.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
namespace Physics
{
class ColliderConfiguration;
class Shape;
class ShapeConfiguration;
}
namespace AzPhysics
{
using SceneIndex = AZ::s8;
//! Default Scene Names and Crc32
static constexpr const char* DefaultPhysicsSceneName = "DefaultScene";
static constexpr const AZ::Crc32 DefaultPhysicsSceneId = AZ_CRC_CE(DefaultPhysicsSceneName);
static constexpr const char* EditorPhysicsSceneName = "EditorScene";
static constexpr const AZ::Crc32 EditorPhysicsSceneId = AZ_CRC_CE(EditorPhysicsSceneName);
//! A handle to a Scene within the physics simulation.
//! A SceneHandle is a tuple of a Crc of the scenes name and the index in the Scene list.
using SceneHandle = AZStd::tuple<AZ::Crc32, SceneIndex>;
//! Default gravity.
static const AZ::Vector3 DefaultGravity = AZ::Vector3(0.0f, 0.0f, -9.81f);
//! Helper for retrieving the values from the SceneHandle tuple.
//! Example usage
//! @code{ .cpp }
//! SceneHandle someHandle;
//! AZ::Crc32 handleCrc = AZStd::get<SceneHandleValues::Crc>(someHandle);
//! SceneIndex index = AZStd::get<SceneHandleValues::Index>(someHandle);
//! const AZ::Crc32 handleCrc = AZStd::get<HandleTypeIndex::Crc>(someHandle);
//! const SceneIndex index = AZStd::get<HandleTypeIndex::Index>(someHandle);
//! @endcode
enum SceneHandleValues
enum HandleTypeIndex
{
Crc = 0,
Index
};
using SceneIndex = AZ::s8;
using SimulatedBodyIndex = AZ::s32;
static_assert(std::is_signed<SceneIndex>::value
&& std::is_signed<SimulatedBodyIndex>::value, "SceneIndex and SimulatedBodyIndex must be signed integers.");
//! A handle to a Scene within the physics simulation.
//! A SceneHandle is a tuple of a Crc of the scenes name and the index in the Scene list.
using SceneHandle = AZStd::tuple<AZ::Crc32, SceneIndex>;
static constexpr SceneHandle InvalidSceneHandle = { AZ::Crc32(), -1 };
//! Ease of use type for referencing a List of SceneHandle objects.
using SceneHandleList = AZStd::vector<SceneHandle>;
//! A handle to a Simulated body within a physics scene.
//! A SimulatedBodyHandle is a tuple of a Crc of the scene's name and the index in the SimulatedBody list.
using SimulatedBodyHandle = AZStd::tuple<AZ::Crc32, SimulatedBodyIndex>;
static constexpr SimulatedBodyHandle InvalidSimulatedBodyHandle = { AZ::Crc32(), -1 };
using SimulatedBodyHandleList = AZStd::vector<SimulatedBodyHandle>;
//! Helper used for pairing the ShapeConfiguration and ColliderConfiguration together which is used when creating a Simulated Body.
using ShapeColliderPair = AZStd::pair<Physics::ColliderConfiguration*, Physics::ShapeConfiguration*>;
//! Flags used to specifying which properties of a body to compute.
enum class MassComputeFlags : AZ::u8
{
NONE = 0,
//! Flags indicating whether a certain mass property should be auto-computed or not.
COMPUTE_MASS = 1,
COMPUTE_INERTIA = 1 << 1,
COMPUTE_COM = 1 << 2,
//! If set, non-simulated shapes will also be included in the mass properties calculation.
INCLUDE_ALL_SHAPES = 1 << 3,
DEFAULT = COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS
};
//! Bitwise operators for MassComputeFlags
inline MassComputeFlags operator|(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) | aznumeric_cast<AZ::u8>(rhs));
}
inline MassComputeFlags operator&(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) & aznumeric_cast<AZ::u8>(rhs));
}
//! Variant to allow support for the system to either create the Shape(s) or use the provide Shape(s) that have been created externally.
//! Can be one of the following.
//! @code{ .cpp }
//! // A ShapeColliderPair, which contains a ColliderConfiguration and ShapeConfiguration.
//! AzPhysics::StaticRigidBodyConfiguration staticRigidBodyConfig;
//! staticRigidBodyConfig.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(&colliderConfig, &shapeConfig);
//!
//! // A pointer to a Physics::Shape. The Simulated Body will take ownership of the pointer.
//! AZStd::shared_ptr<Physics::Shape> shapePtr /*Created through other means*/;
//! AzPhysics::StaticRigidBodyConfiguration staticRigidBodyConfig;
//! staticRigidBodyConfig.m_colliderAndShapeData = shapePtr;
//!
//! // A list of ShapeColliderPairs.
//! AZStd::vector<AzPhysics::ShapeColliderPair> shapeColliderPairList;
//! shapeColliderPairList.emplace_back(&colliderConfig, &shapeConfig); //add as many configs as required.
//! AzPhysics::StaticRigidBodyConfiguration staticRigidBodyConfig;
//! staticRigidBodyConfig.m_colliderAndShapeData = shapeColliderPairList;
//!
//! // A list of Physics::Shape pointers. The Simulated Body will take ownership of these pointers.
//! AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapePtrList;
//! shapePtrList.emplace_back(/*Shape created through other means*/);
//! AzPhysics::StaticRigidBodyConfiguration staticRigidBodyConfig;
//! staticRigidBodyConfig.m_colliderAndShapeData = shapePtrList;
//! @endcode
using ShapeVariantData = AZStd::variant<
AZStd::monostate,
ShapeColliderPair,
AZStd::shared_ptr<Physics::Shape>,
AZStd::vector<ShapeColliderPair>,
AZStd::vector<AZStd::shared_ptr<Physics::Shape>>>;
}
@@ -0,0 +1,242 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/Shape.h>
namespace AzPhysics
{
namespace Internal
{
// Visibility functions.
AZ::Crc32 GetPropertyVisibility(AZ::u16 flags, RigidBodyConfiguration::PropertyVisibility property)
{
return (flags & property) != 0 ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
void SetPropertyVisibility(AZ::u16 flags, RigidBodyConfiguration::PropertyVisibility property, bool isVisible)
{
if (isVisible)
{
flags |= property;
}
else
{
flags &= ~property;
}
}
bool RigidBodyVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() <= 1)
{
const int elementIndex = classElement.FindElement(AZ_CRC("Centre of mass offset", 0x1e569a45));
if (elementIndex >= 0)
{
AZ::Vector3 existingCenterOfMassOffset;
AZ::SerializeContext::DataElementNode& centerOfMassElement = classElement.GetSubElement(elementIndex);
const bool found = centerOfMassElement.GetData<AZ::Vector3>(existingCenterOfMassOffset);
if (found && !existingCenterOfMassOffset.IsZero())
{
// An existing center of mass (COM) offset value was specified for this rigid body.
// Version 2 includes a new m_computeCenterOfMass boolean flag to specify the automatic calculation of COM.
// In this case set m_computeCenterOfMass to false so that the existing center of mass offset value is utilized correctly.
const int idx = classElement.AddElement<bool>(context, "Compute COM");
if (idx != -1)
{
if (!classElement.GetSubElement(idx).SetData<bool>(context, false))
{
return false;
}
}
}
}
}
if (classElement.GetVersion() <= 2)
{
const int elementIndex = classElement.FindElement(AZ_CRC("Mass", 0x6c035b66));
if (elementIndex >= 0)
{
float existingMass = 0;
AZ::SerializeContext::DataElementNode& massElement = classElement.GetSubElement(elementIndex);
const bool found = massElement.GetData<float>(existingMass);
if (found && existingMass > 0)
{
// Keeping the existing mass and disabling auto-compute of the mass for this rigid body.
// Version 3 includes a new m_computeMass boolean flag to specify the automatic calculation of mass.
const int idx = classElement.AddElement<bool>(context, "Compute Mass");
if (idx != -1)
{
if (!classElement.GetSubElement(idx).SetData<bool>(context, false))
{
return false;
}
}
}
}
}
if (classElement.GetVersion() <= 3)
{
classElement.RemoveElementByName(AZ_CRC_CE("Property Visibility Flags"));
}
return true;
}
}
AZ_CLASS_ALLOCATOR_IMPL(RigidBodyConfiguration, AZ::SystemAllocator, 0);
void RigidBodyConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RigidBodyConfiguration, AzPhysics::SimulatedBodyConfiguration>()
->Version(4, &Internal::RigidBodyVersionConverter)
->Field("Initial linear velocity", &RigidBodyConfiguration::m_initialLinearVelocity)
->Field("Initial angular velocity", &RigidBodyConfiguration::m_initialAngularVelocity)
->Field("Linear damping", &RigidBodyConfiguration::m_linearDamping)
->Field("Angular damping", &RigidBodyConfiguration::m_angularDamping)
->Field("Sleep threshold", &RigidBodyConfiguration::m_sleepMinEnergy)
->Field("Start Asleep", &RigidBodyConfiguration::m_startAsleep)
->Field("Interpolate Motion", &RigidBodyConfiguration::m_interpolateMotion)
->Field("Gravity Enabled", &RigidBodyConfiguration::m_gravityEnabled)
->Field("Simulated", &RigidBodyConfiguration::m_simulated)
->Field("Kinematic", &RigidBodyConfiguration::m_kinematic)
->Field("CCD Enabled", &RigidBodyConfiguration::m_ccdEnabled)
->Field("Compute Mass", &RigidBodyConfiguration::m_computeMass)
->Field("Mass", &RigidBodyConfiguration::m_mass)
->Field("Compute COM", &RigidBodyConfiguration::m_computeCenterOfMass)
->Field("Centre of mass offset", &RigidBodyConfiguration::m_centerOfMassOffset)
->Field("Compute inertia", &RigidBodyConfiguration::m_computeInertiaTensor)
->Field("Inertia tensor", &RigidBodyConfiguration::m_inertiaTensor)
->Field("Maximum Angular Velocity", &RigidBodyConfiguration::m_maxAngularVelocity)
->Field("Include All Shapes In Mass", &RigidBodyConfiguration::m_includeAllShapesInMassCalculation)
->Field("CCD Min Advance", &RigidBodyConfiguration::m_ccdMinAdvanceCoefficient)
->Field("CCD Friction", &RigidBodyConfiguration::m_ccdFrictionEnabled)
;
}
}
MassComputeFlags RigidBodyConfiguration::GetMassComputeFlags() const
{
MassComputeFlags flags = MassComputeFlags::NONE;
if (m_computeCenterOfMass)
{
flags = flags | MassComputeFlags::COMPUTE_COM;
}
if (m_computeInertiaTensor)
{
flags = flags | MassComputeFlags::COMPUTE_INERTIA;
}
if (m_computeMass)
{
flags = flags | MassComputeFlags::COMPUTE_MASS;
}
if (m_includeAllShapesInMassCalculation)
{
flags = flags | MassComputeFlags::INCLUDE_ALL_SHAPES;
}
return flags;
}
void RigidBodyConfiguration::SetMassComputeFlags(MassComputeFlags flags)
{
m_computeCenterOfMass = MassComputeFlags::COMPUTE_COM == (flags & MassComputeFlags::COMPUTE_COM);
m_computeInertiaTensor = MassComputeFlags::COMPUTE_INERTIA == (flags & MassComputeFlags::COMPUTE_INERTIA);
m_computeMass = MassComputeFlags::COMPUTE_MASS == (flags & MassComputeFlags::COMPUTE_MASS);
m_includeAllShapesInMassCalculation =
MassComputeFlags::INCLUDE_ALL_SHAPES == (flags & MassComputeFlags::INCLUDE_ALL_SHAPES);
}
bool RigidBodyConfiguration::IsCCDEnabled() const
{
return m_ccdEnabled;
}
AZ::Crc32 RigidBodyConfiguration::GetInitialVelocitiesVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::InitialVelocities);
}
AZ::Crc32 RigidBodyConfiguration::GetInertiaSettingsVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::InertiaProperties);
}
AZ::Crc32 RigidBodyConfiguration::GetInertiaVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & RigidBodyConfiguration::PropertyVisibility::InertiaProperties) != 0) && !m_computeInertiaTensor;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetCoMVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & RigidBodyConfiguration::PropertyVisibility::InertiaProperties) != 0) && !m_computeCenterOfMass;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetMassVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & RigidBodyConfiguration::PropertyVisibility::InertiaProperties) != 0) && !m_computeMass;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetDampingVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::Damping);
}
AZ::Crc32 RigidBodyConfiguration::GetSleepOptionsVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::SleepOptions);
}
AZ::Crc32 RigidBodyConfiguration::GetInterpolationVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::Interpolation);
}
AZ::Crc32 RigidBodyConfiguration::GetGravityVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::Gravity);
}
AZ::Crc32 RigidBodyConfiguration::GetKinematicVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::Kinematic);
}
AZ::Crc32 RigidBodyConfiguration::GetCCDVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::ContinuousCollisionDetection);
}
AZ::Crc32 RigidBodyConfiguration::GetMaxVelocitiesVisibility() const
{
return Internal::GetPropertyVisibility(m_propertyVisibilityFlags, RigidBodyConfiguration::PropertyVisibility::MaxVelocities);
}
}
@@ -0,0 +1,106 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
namespace AZ
{
class ReflectContext;
}
namespace AzPhysics
{
//! Configuration used to Add Rigid bodies to a Scene.
struct RigidBodyConfiguration
: public AzPhysics::SimulatedBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(RigidBodyConfiguration, "{ACFA8900-8530-4744-AF00-AA533C868A8E}", AzPhysics::SimulatedBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
RigidBodyConfiguration() = default;
MassComputeFlags GetMassComputeFlags() const;
void SetMassComputeFlags(MassComputeFlags flags);
bool IsCCDEnabled() const;
// Basic initial settings.
AZ::Vector3 m_initialLinearVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_initialAngularVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_centerOfMassOffset = AZ::Vector3::CreateZero();
// Simulation parameters.
float m_mass = 1.0f;
AZ::Matrix3x3 m_inertiaTensor = AZ::Matrix3x3::CreateIdentity();
float m_linearDamping = 0.05f;
float m_angularDamping = 0.15f;
float m_sleepMinEnergy = 0.005f;
float m_maxAngularVelocity = 100.0f;
bool m_startAsleep = false;
bool m_interpolateMotion = false;
bool m_gravityEnabled = true;
bool m_simulated = true;
bool m_kinematic = false;
bool m_ccdEnabled = false; //!< Whether continuous collision detection is enabled.
float m_ccdMinAdvanceCoefficient = 0.15f; //!< Coefficient affecting how granularly time is subdivided in CCD.
bool m_ccdFrictionEnabled = false; //!< Whether friction is applied when resolving CCD collisions.
bool m_computeCenterOfMass = true;
bool m_computeInertiaTensor = true;
bool m_computeMass = true;
//! If set, non-simulated shapes will also be included in the mass properties calculation.
bool m_includeAllShapesInMassCalculation = false;
//! Variant to support multiple having the system creating the Shape(s) or just providing the Shape(s) that have been created externally.
//! See ShapeVariantData for more information.
ShapeVariantData m_colliderAndShapeData;
//Visibility helpers for use in the Editor when reflected. RagdollNodeConfiguration also uses these.
enum PropertyVisibility : AZ::u16
{
InitialVelocities = 1 << 0, //!< Whether the initial linear and angular velocities are visible.
InertiaProperties = 1 << 1, //!< Whether the whole category of inertia properties (mass, compute inertia, inertia tensor etc) is visible.
Damping = 1 << 2, //!< Whether linear and angular damping are visible.
SleepOptions = 1 << 3, //!< Whether the sleep threshold and start asleep options are visible.
Interpolation = 1 << 4, //!< Whether the interpolation option is visible.
Gravity = 1 << 5, //!< Whether the effected by gravity option is visible.
Kinematic = 1 << 6, //!< Whether the option to make the body kinematic is visible.
ContinuousCollisionDetection = 1 << 7, //!< Whether the option to enable continuous collision detection is visible.
MaxVelocities = 1 << 8 //!< Whether upper limits on velocities are visible.
};
AZ::Crc32 GetInitialVelocitiesVisibility() const;
AZ::Crc32 GetInertiaSettingsVisibility() const;
AZ::Crc32 GetInertiaVisibility() const;
AZ::Crc32 GetMassVisibility() const;
AZ::Crc32 GetCoMVisibility() const;
AZ::Crc32 GetDampingVisibility() const;
AZ::Crc32 GetSleepOptionsVisibility() const;
AZ::Crc32 GetInterpolationVisibility() const;
AZ::Crc32 GetGravityVisibility() const;
AZ::Crc32 GetKinematicVisibility() const;
AZ::Crc32 GetCCDVisibility() const;
AZ::Crc32 GetMaxVelocitiesVisibility() const;
AZ::u16 m_propertyVisibilityFlags = (std::numeric_limits<AZ::u16>::max)();
};
}
@@ -13,6 +13,7 @@
#include <AzFramework/Physics/Configuration/SceneConfiguration.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzPhysics
@@ -21,16 +22,47 @@ namespace AzPhysics
/*static*/ void SceneConfiguration::Reflect(AZ::ReflectContext* context)
{
Physics::WorldConfiguration::Reflect(context);
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SceneConfiguration>()
->Version(1)
->Field("LegacyConfig", &SceneConfiguration::m_legacyConfiguration)
->Field("LegacyId", &SceneConfiguration::m_legacyId)
->Version(2)
->Field("Name", &SceneConfiguration::m_sceneName)
->Field("WorldBounds", &SceneConfiguration::m_worldBounds)
->Field("Gravity", &SceneConfiguration::m_gravity)
->Field("EnableCcd", &SceneConfiguration::m_enableCcd)
->Field("MaxCcdPasses", &SceneConfiguration::m_maxCcdPasses)
->Field("EnableCcdResweep", &SceneConfiguration::m_enableCcdResweep)
->Field("EnableActiveActors", &SceneConfiguration::m_enableActiveActors)
->Field("EnablePcm", &SceneConfiguration::m_enablePcm)
->Field("BounceThresholdVelocity", &SceneConfiguration::m_bounceThresholdVelocity)
;
if (auto* editContext = serializeContext->GetEditContext())
{
editContext->Class<SceneConfiguration>("Scene Configuration", "Default scene configuration")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_worldBounds, "World Bounds", "World bounds")
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_gravity, "Gravity", "Gravity")
->ClassElement(AZ::Edit::ClassElements::Group, "Continuous Collision Detection")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_enableCcd, "Enable CCD", "Enabled continuous collision detection in the world")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_maxCcdPasses,
"Max CCD Passes", "Maximum number of continuous collision detection passes")
->Attribute(AZ::Edit::Attributes::Visibility, &SceneConfiguration::GetCcdVisibility)
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_enableCcdResweep,
"Enable CCD Resweep", "Enable a more accurate but more expensive continuous collision detection method")
->Attribute(AZ::Edit::Attributes::Visibility, &SceneConfiguration::GetCcdVisibility)
->ClassElement(AZ::Edit::ClassElements::Group, "") // end previous group by starting new unnamed expanded group
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_enablePcm, "Persistent Contact Manifold", "Enabled the persistent contact manifold narrow-phase algorithm")
->DataElement(AZ::Edit::UIHandlers::Default, &SceneConfiguration::m_bounceThresholdVelocity,
"Bounce Threshold Velocity", "Relative velocity below which colliding objects will not bounce")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
;
}
}
}
@@ -41,14 +73,28 @@ namespace AzPhysics
bool SceneConfiguration::operator==(const SceneConfiguration& other) const
{
return m_legacyId == other.m_legacyId
&& m_sceneName == other.m_sceneName
&& m_legacyConfiguration == other.m_legacyConfiguration
;
return m_sceneName == other.m_sceneName
&& m_enableCcd == other.m_enableCcd
&& m_enableCcdResweep == other.m_enableCcdResweep
&& m_enableActiveActors == other.m_enableActiveActors
&& m_enablePcm == other.m_enablePcm
&& m_kinematicFiltering == other.m_kinematicFiltering
&& m_kinematicStaticFiltering == other.m_kinematicStaticFiltering
&& m_customUserData == other.m_customUserData
&& m_maxCcdPasses == other.m_maxCcdPasses
&& AZ::IsClose(m_bounceThresholdVelocity, other.m_bounceThresholdVelocity)
&& m_gravity.IsClose(other.m_gravity)
&& m_worldBounds == other.m_worldBounds
;
}
bool SceneConfiguration::operator!=(const SceneConfiguration& other) const
{
return !(*this == other);
}
AZ::Crc32 SceneConfiguration::GetCcdVisibility() const
{
return m_enableCcd ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
}
@@ -11,11 +11,13 @@
*/
#pragma once
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Physics/World.h> //this will be removed with LYN-438.
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
@@ -33,14 +35,28 @@ namespace AzPhysics
static SceneConfiguration CreateDefault();
// Legacy members Will be removed and replaced with LYN-438 work.
Physics::WorldConfiguration m_legacyConfiguration;
AZ::Crc32 m_legacyId; //use SceneConfiguration::m_SceneName instead
AZStd::string m_sceneName; //!< Name given to the scene.
AZStd::string m_sceneName = "DefaultScene"; //!< Name given to the scene.
AZ::Aabb m_worldBounds = AZ::Aabb::CreateFromMinMax(-AZ::Vector3(1000.f, 1000.f, 1000.f), AZ::Vector3(1000.f, 1000.f, 1000.f));
AZ::Vector3 m_gravity = AzPhysics::DefaultGravity;
void* m_customUserData = nullptr;
bool m_enableCcd = false; //!< Enables continuous collision detection in the world.
AZ::u32 m_maxCcdPasses = 1; //!< Maximum number of continuous collision detection passes.
bool m_enableCcdResweep = true; //!< Use a more accurate but more expensive continuous collision detection method.
//! Enables reporting of changed Simulated bodies on the OnSceneActiveSimulatedBodiesEvent event.
//! @note There may be a performance penalty for enabling the Active Actor Notification.
bool m_enableActiveActors = false;
bool m_enablePcm = true; //!< Enables the persistent contact manifold algorithm to be used as the narrow phase algorithm.
bool m_kinematicFiltering = true; //!< Enables filtering between kinematic/kinematic objects.
bool m_kinematicStaticFiltering = true; //!< Enables filtering between kinematic/static objects.
float m_bounceThresholdVelocity = 2.0f; //!< Relative velocity below which colliding objects will not bounce.
bool operator==(const SceneConfiguration& other) const;
bool operator!=(const SceneConfiguration& other) const;
private:
AZ::Crc32 GetCcdVisibility() const;
};
//! Alias for a list of SceneConfiguration objects, used for the creation of multiple Scenes at once.
@@ -0,0 +1,52 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzPhysics
{
namespace Internal
{
bool DeprecateWorldBodyConfiguration(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
// WorldBodyConfiguration on serialized the name so capture that.
AZStd::string name = "";
classElement.GetChildData(AZ::Crc32("name"), name);
//convert to the new class
classElement.Convert<SimulatedBodyConfiguration>(context);
//add the captured name
classElement.AddElementWithData(context, "name", name);
return true;
}
}
AZ_CLASS_ALLOCATOR_IMPL(SimulatedBodyConfiguration, AZ::SystemAllocator, 0);
void SimulatedBodyConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->ClassDeprecate("WorldBodyConfiguration", "{6EEB377C-DC60-4E10-AF12-9626C0763B2D}", &Internal::DeprecateWorldBodyConfiguration);
serializeContext->Class<SimulatedBodyConfiguration>()
->Version(1)
->Field("name", &SimulatedBodyConfiguration::m_debugName)
->Field("position", &SimulatedBodyConfiguration::m_position)
->Field("orientation", &SimulatedBodyConfiguration::m_orientation)
->Field("scale", &SimulatedBodyConfiguration::m_scale)
->Field("entityId", &SimulatedBodyConfiguration::m_entityId)
;
}
}
}
@@ -0,0 +1,54 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
namespace AZ
{
class ReflectContext;
}
namespace AzPhysics
{
//! Base Class of all Physics Bodies that will be simulated.
struct SimulatedBodyConfiguration
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(SimulatedBodyConfiguration, "{52844E3D-79C8-4F34-AF63-5C45ADE77F85}");
static void Reflect(AZ::ReflectContext* context);
SimulatedBodyConfiguration() = default;
virtual ~SimulatedBodyConfiguration() = default;
// Basic initial settings.
AZ::Vector3 m_position = AZ::Vector3::CreateZero();
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity();
AZ::Vector3 m_scale = AZ::Vector3::CreateOne();
// Entity/object association.
AZ::EntityId m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId);
void* m_customUserData = nullptr;
// For debugging/tracking purposes only.
AZStd::string m_debugName;
};
//! Alias for a list of non owning weak pointers to SceneConfiguration objects.
//! Used for the creation of multiple SimulatedBodies at once with Scene::AddSimulatedBodies.
using SimulatedBodyConfigurationList = AZStd::vector<SimulatedBodyConfiguration*>;
}
@@ -0,0 +1,34 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(StaticRigidBodyConfiguration, AZ::SystemAllocator, 0);
void StaticRigidBodyConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<StaticRigidBodyConfiguration, SimulatedBodyConfiguration>()
->Version(1)
->Field("ColliderAndShapeData", &StaticRigidBodyConfiguration::m_colliderAndShapeData);
;
}
}
}
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
namespace AZ
{
class ReflectContext;
}
namespace Physics
{
class ShapeConfiguration;
class ColliderConfiguration;
class Shape;
}
namespace AzPhysics
{
struct StaticRigidBodyConfiguration : public SimulatedBodyConfiguration
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(StaticRigidBodyConfiguration, "{E68A14C0-21DC-4FC7-9AD0-04BB9D972004}", SimulatedBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
StaticRigidBodyConfiguration() = default;
virtual ~StaticRigidBodyConfiguration() = default;
//! Variant to support multiple having the system creating the Shape(s) or just providing the Shape(s) that have been created externally.
//! See ShapeVariantData for more information.
ShapeVariantData m_colliderAndShapeData;
};
}
@@ -13,17 +13,24 @@
#include <AzFramework/Physics/Configuration/SystemConfiguration.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzPhysics
{
namespace
{
const float TimestepMin = 0.001f; //1000fps
const float TimestepMax = 0.05f; //20fps
}
AZ_CLASS_ALLOCATOR_IMPL(SystemConfiguration, AZ::SystemAllocator, 0);
/*static*/ void SystemConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SystemConfiguration>()
serializeContext->Class<AzPhysics::SystemConfiguration>()
->Version(2)
->Field("AutoManageSimulationUpdate", &SystemConfiguration::m_autoManageSimulationUpdate)
->Field("MaxTimestep", &SystemConfiguration::m_maxTimestep)
@@ -33,6 +40,34 @@ namespace AzPhysics
->Field("OverlapBufferSize", &SystemConfiguration::m_overlapBufferSize)
->Field("CollisionConfig", &SystemConfiguration::m_collisionConfig)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AzPhysics::SystemConfiguration>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemConfiguration::m_maxTimestep, "Max Time Step (sec)", "Max time step in seconds")
->Attribute(AZ::Edit::Attributes::Min, TimestepMin)
->Attribute(AZ::Edit::Attributes::Max, TimestepMax)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &SystemConfiguration::OnMaxTimeStepChanged)//need to clamp m_fixedTimeStep if this value changes
->Attribute(AZ::Edit::Attributes::Decimals, 8)
->Attribute(AZ::Edit::Attributes::DisplayDecimals, 8)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemConfiguration::m_fixedTimestep, "Fixed Time Step (sec)", "Fixed time step in seconds. Limited by 'Max Time Step'")
->Attribute(AZ::Edit::Attributes::Min, TimestepMin)
->Attribute(AZ::Edit::Attributes::Max, &SystemConfiguration::GetFixedTimeStepMax)
->Attribute(AZ::Edit::Attributes::Decimals, 8)
->Attribute(AZ::Edit::Attributes::DisplayDecimals, 8)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemConfiguration::m_raycastBufferSize,
"Raycast Buffer Size", "Maximum number of hits from a raycast")
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemConfiguration::m_shapecastBufferSize,
"Shapecast Buffer Size", "Maximum number of hits from a shapecast")
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemConfiguration::m_overlapBufferSize,
"Overlap Query Buffer Size", "Maximum number of hits from a overlap query")
->Attribute(AZ::Edit::Attributes::Min, 1u)
;
}
}
}
@@ -52,4 +87,15 @@ namespace AzPhysics
{
return !(*this == other);
}
AZ::u32 SystemConfiguration::OnMaxTimeStepChanged()
{
m_fixedTimestep = AZStd::GetMin(m_fixedTimestep, GetFixedTimeStepMax()); //since m_maxTimeStep has changed, m_fixedTimeStep might be larger then the max.
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
float SystemConfiguration::GetFixedTimeStepMax() const
{
return m_maxTimestep;
}
}
@@ -26,7 +26,7 @@ namespace AzPhysics
struct SystemConfiguration
{
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(SystemConfiguration, "{24697CAF-AC00-443D-9C27-28D58734A84C}");
AZ_RTTI(AzPhysics::SystemConfiguration, "{24697CAF-AC00-443D-9C27-28D58734A84C}");
static void Reflect(AZ::ReflectContext* context);
SystemConfiguration() = default;
@@ -51,5 +51,10 @@ namespace AzPhysics
bool operator==(const SystemConfiguration& other) const;
bool operator!=(const SystemConfiguration& other) const;
private:
// helpers for edit context
AZ::u32 OnMaxTimeStepChanged();
float GetFixedTimeStepMax() const;
};
}
@@ -13,6 +13,7 @@
#include <AzFramework/Physics/Joint.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
namespace Physics
{
@@ -14,7 +14,11 @@
#include <AzCore/Math/Transform.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/WorldBody.h>
namespace AzPhysics
{
struct SimulatedBody;
}
namespace Physics
{
@@ -43,10 +47,10 @@ namespace Physics
AZ_CLASS_ALLOCATOR(Joint, AZ::SystemAllocator, 0);
AZ_RTTI(Joint, "{405F517C-E986-4ACB-9606-D5D080DDE987}");
virtual Physics::WorldBody* GetParentBody() const = 0;
virtual Physics::WorldBody* GetChildBody() const = 0;
virtual void SetParentBody(Physics::WorldBody* parentBody) = 0;
virtual void SetChildBody(Physics::WorldBody* childBody) = 0;
virtual AzPhysics::SimulatedBody* GetParentBody() const = 0;
virtual AzPhysics::SimulatedBody* GetChildBody() const = 0;
virtual void SetParentBody(AzPhysics::SimulatedBody* parentBody) = 0;
virtual void SetChildBody(AzPhysics::SimulatedBody* childBody) = 0;
virtual const AZStd::string& GetName() const = 0;
virtual void SetName(const AZStd::string& name) = 0;
virtual const AZ::Crc32 GetNativeType() const = 0;
@@ -71,7 +71,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MaterialConfiguration>()
serializeContext->Class<Physics::MaterialConfiguration>()
->Version(3, &VersionConverter)
->Field("SurfaceType", &MaterialConfiguration::m_surfaceType)
->Field("DynamicFriction", &MaterialConfiguration::m_dynamicFriction)
@@ -88,7 +88,7 @@ namespace Physics
{
AZStd::unordered_set<AZStd::string> forbiddenSurfaceTypeNames;
forbiddenSurfaceTypeNames.insert("Default");
editContext->Class<MaterialConfiguration>("", "")
editContext->Class<Physics::MaterialConfiguration>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "Physics Material")
->DataElement(MaterialConfiguration::s_configLineEdit, &MaterialConfiguration::m_surfaceType, "Surface type", "Game surface type") // Uses ConfigStringLineEditCtrl in PhysX gem.
->Attribute(AZ::Edit::Attributes::MaxLength, 64)
@@ -168,7 +168,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MaterialLibraryAsset, AZ::Data::AssetData>()
serializeContext->Class<Physics::MaterialLibraryAsset, AZ::Data::AssetData>()
->Version(2, &ClassConverters::MaterialLibraryAssetConverter)
->Attribute(AZ::Edit::Attributes::EnableForAssetEditor, true)
->EventHandler<MaterialLibraryAssetEventHandler>()
@@ -178,7 +178,7 @@ namespace Physics
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<MaterialLibraryAsset>("", "")
editContext->Class<Physics::MaterialLibraryAsset>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialLibraryAsset::m_materialLibrary, "Physics Materials", "List of physics materials")
@@ -196,7 +196,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MaterialLibraryAssetReflectionWrapper>()
serializeContext->Class<Physics::MaterialLibraryAssetReflectionWrapper>()
->Version(1)
->Field("Asset", &MaterialLibraryAssetReflectionWrapper::m_asset)
;
@@ -204,7 +204,7 @@ namespace Physics
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<MaterialLibraryAssetReflectionWrapper>("", "")
editContext->Class<Physics::MaterialLibraryAssetReflectionWrapper>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
@@ -223,7 +223,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<DefaultMaterialLibraryAssetReflectionWrapper>()
serializeContext->Class<Physics::DefaultMaterialLibraryAssetReflectionWrapper>()
->Version(1)
->Field("Asset", &DefaultMaterialLibraryAssetReflectionWrapper::m_asset)
;
@@ -231,7 +231,7 @@ namespace Physics
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<DefaultMaterialLibraryAssetReflectionWrapper>("", "")
editContext->Class<Physics::DefaultMaterialLibraryAssetReflectionWrapper>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
@@ -250,7 +250,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<MaterialFromAssetConfiguration>()
serializeContext->Class<Physics::MaterialFromAssetConfiguration>()
->Version(1)
->Field("Configuration", &MaterialFromAssetConfiguration::m_configuration)
->Field("UID", &MaterialFromAssetConfiguration::m_id)
@@ -259,7 +259,7 @@ namespace Physics
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<MaterialFromAssetConfiguration>("", "")
editContext->Class<Physics::MaterialFromAssetConfiguration>("", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialFromAssetConfiguration::m_configuration, "Physics Material", "Physics Material properties")
@@ -369,7 +369,7 @@ namespace Physics
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<MaterialSelection>()
serializeContext->Class<Physics::MaterialSelection>()
->Version(2, &ClassConverters::MaterialSelectionConverter)
->EventHandler<MaterialSelectionEventHandler>()
->Field("Material", &MaterialSelection::m_materialLibrary)
@@ -378,7 +378,7 @@ namespace Physics
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<MaterialSelection>("Physics Material", "Select physics material library and which materials to use for the object")
editContext->Class<Physics::MaterialSelection>("Physics Material", "Select physics material library and which materials to use for the object")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &MaterialSelection::m_materialLibrary, "Library", "Physics material library to use for this object")
@@ -49,7 +49,7 @@ namespace Physics
{
public:
AZ_CLASS_ALLOCATOR(Material, AZ::SystemAllocator, 0);
AZ_RTTI(Material, "{44636CEA-46DD-4D4A-B1EF-5ED6DEA7F714}");
AZ_RTTI(Physics::Material, "{44636CEA-46DD-4D4A-B1EF-5ED6DEA7F714}");
/// Enumeration that determines how two materials properties are combined when
/// processing collisions.
@@ -102,7 +102,7 @@ namespace Physics
class MaterialConfiguration
{
public:
AZ_TYPE_INFO(MaterialConfiguration, "{8807CAA1-AD08-4238-8FDB-2154ADD084A1}");
AZ_TYPE_INFO(Physics::MaterialConfiguration, "{8807CAA1-AD08-4238-8FDB-2154ADD084A1}");
static void Reflect(AZ::ReflectContext* context);
@@ -140,7 +140,7 @@ namespace Physics
{
public:
AZ_CLASS_ALLOCATOR(MaterialId, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(MaterialId, "{744CCE6C-9F69-4E2F-B950-DAB8514F870B}");
AZ_TYPE_INFO(Physics::MaterialId, "{744CCE6C-9F69-4E2F-B950-DAB8514F870B}");
static void Reflect(AZ::ReflectContext* context);
static MaterialId Create();
@@ -160,7 +160,7 @@ namespace Physics
class MaterialFromAssetConfiguration
{
public:
AZ_TYPE_INFO(MaterialFromAssetConfiguration, "{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}");
AZ_TYPE_INFO(Physics::MaterialFromAssetConfiguration, "{FBD76628-DE57-435E-BE00-6FFAE64DDF1D}");
static void Reflect(AZ::ReflectContext* context);
@@ -182,7 +182,7 @@ namespace Physics
{
public:
AZ_CLASS_ALLOCATOR(MaterialLibraryAsset, AZ::SystemAllocator, 0);
AZ_RTTI(MaterialLibraryAsset, "{9E366D8C-33BB-4825-9A1F-FA3ADBE11D0F}", AZ::Data::AssetData);
AZ_RTTI(Physics::MaterialLibraryAsset, "{9E366D8C-33BB-4825-9A1F-FA3ADBE11D0F}", AZ::Data::AssetData);
MaterialLibraryAsset() = default;
virtual ~MaterialLibraryAsset() = default;
@@ -231,7 +231,7 @@ namespace Physics
{
public:
AZ_CLASS_ALLOCATOR(MaterialLibraryAssetReflectionWrapper, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(MaterialLibraryAssetReflectionWrapper, "{3D2EF5DF-EFD0-47EB-B88F-3E6FE1FEE5B0}");
AZ_TYPE_INFO(Physics::MaterialLibraryAssetReflectionWrapper, "{3D2EF5DF-EFD0-47EB-B88F-3E6FE1FEE5B0}");
static void Reflect(AZ::ReflectContext* context);
AZ::Data::Asset<Physics::MaterialLibraryAsset> m_asset =
@@ -243,7 +243,7 @@ namespace Physics
{
public:
AZ_CLASS_ALLOCATOR(MaterialLibraryAssetReflectionWrapper, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(DefaultMaterialLibraryAssetReflectionWrapper, "{02AB8CBC-D35B-4E0F-89BA-A96D94DAD4F9}");
AZ_TYPE_INFO(Physics::DefaultMaterialLibraryAssetReflectionWrapper, "{02AB8CBC-D35B-4E0F-89BA-A96D94DAD4F9}");
static void Reflect(AZ::ReflectContext* context);
AZ::Data::Asset<Physics::MaterialLibraryAsset> m_asset =
@@ -263,7 +263,7 @@ namespace Physics
friend class MaterialSelectionEventHandler;
public:
AZ_CLASS_ALLOCATOR(MaterialSelection, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(MaterialSelection, "{F571AFF4-C4BB-4590-A204-D11D9EEABBC4}");
AZ_TYPE_INFO(Physics::MaterialSelection, "{F571AFF4-C4BB-4590-A204-D11D9EEABBC4}");
using SlotsArray = AZStd::vector<AZStd::string>;
@@ -0,0 +1,75 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Configuration/SceneConfiguration.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(Scene, AZ::SystemAllocator, 0);
/*static*/ void Scene::Reflect(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
const auto getOnGravityChange = [](const AZStd::string& sceneName) -> SceneEvents::OnSceneGravityChangedEvent*
{
if (auto* physicsSystem = AZ::Interface<SystemInterface>::Get())
{
SceneHandle sceneHandle = physicsSystem->GetSceneHandle(sceneName);
if (sceneHandle != AzPhysics::InvalidSceneHandle)
{
if (Scene* scene = physicsSystem->GetScene(sceneHandle))
{
return scene->GetOnGravityChangedEvent();
}
}
}
return nullptr;
};
const AZ::BehaviorAzEventDescription gravityChangedEventDescription =
{
"On Gravity Changed event",
{
"Scene Handle",
"Gravity Vector"
} // Parameters
};
behaviorContext->Class<Scene>("PhysicsScene")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "Physics")
->Method("GetOnGravityChangeEvent", getOnGravityChange)
->Attribute(AZ::Script::Attributes::AzEventDescription, gravityChangedEventDescription)
;
}
}
Scene::Scene(const SceneConfiguration& config)
: m_id(config.m_sceneName)
{
}
const AZ::Crc32& Scene::GetId() const
{
return m_id;
}
SceneEvents::OnSceneGravityChangedEvent* Scene::GetOnGravityChangedEvent()
{
return &m_sceneGravityChangedEvent;
}
} // namespace AzPhysics
@@ -12,21 +12,232 @@
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/World.h> // Temporary until LYN-438 work is complete
#include <AzFramework/Physics/Common/PhysicsEvents.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
namespace AzPhysics
{
struct SceneConfiguration;
//! Interface to access a Physics Scene with a SceneHandle.
class SceneInterface
{
public:
AZ_RTTI(SceneInterface, "{912CE8D1-7E3E-496F-B7BE-D17F8B30C228}");
SceneInterface() = default;
virtual ~SceneInterface() = default;
AZ_DISABLE_COPY_MOVE(SceneInterface);
//! Returns a Scene Handle connected to the given scene name.
//! @param sceneName The name of the scene to look up.
//! @returns Will return a SceneHandle to a Scene connected with the given name, otherwise will return InvalidSceneHandle.
virtual SceneHandle GetSceneHandle(const AZStd::string& sceneName) = 0;
//! Start the simulation process.
//! As an example, this is a good place to trigger and queue any long running work in separate threads.
//! @param sceneHandle The SceneHandle of the scene to use.
//! @param deltatime The time in seconds to step the simulation for.
virtual void StartSimulation(SceneHandle sceneHandle, float deltatime) = 0;
//! Complete the simulation process.
//! As an example, this is a good place to wait for any work to complete that was triggered in StartSimulation, or swap buffers if double buffering.
//! @param sceneHandle The SceneHandle of the scene to use.
virtual void FinishSimulation(SceneHandle sceneHandle) = 0;
//! Enable or Disable this Scene's Simulation tick.
//! Default is Enabled.
//! @param sceneHandle The SceneHandle of the scene to use.
//! @param enable When true the Scene will execute its simulation tick when StartSimulation is called. When false, StartSimulation will not execute.
virtual void SetEnabled(SceneHandle sceneHandle, bool enable) = 0;
//! Check if this Scene is currently Enabled.
//! @param sceneHandle The SceneHandle of the scene to use.
//! @return When true the Scene is enabled and will execute its simulation tick when StartSimulation is called. When false,
//! StartSimulation will not execute or the SceneHandle is invalid.
virtual bool IsEnabled(SceneHandle sceneHandle) const = 0;
//! Add a simulated body to the Scene.
//! @param sceneHandle A handle to the scene to add the requested simulated body.
//! @param simulatedBodyConfig The config of the simulated body.
//! @return Returns a handle to the created Simulated body. Will return AzPhyiscs::InvalidSimulatedBodyHandle if it fails.
virtual SimulatedBodyHandle AddSimulatedBody(SceneHandle sceneHandle, const SimulatedBodyConfiguration* simulatedBodyConfig) = 0;
//! Add a set of simulated bodied to the Scene.
//! @param sceneHandle A handle to the scene to Add the simulated bodies to.
//! @param simulatedBodyConfigs The list of simulated body configs.
//! @return Returns a list of handles to the created Simulated bodies. Will be in the same order as supplied in simulatedBodyConfigs.
//! If the scene handle is invalid, this will return an empty list. If one fails, that index will be set to AzPhyiscs::InvalidSimulatedBodyHandle.
virtual SimulatedBodyHandleList AddSimulatedBodies(SceneHandle sceneHandle, const SimulatedBodyConfigurationList& simulatedBodyConfigs) = 0;
//! Get the Raw pointer to the requested simulated body.
//! @param sceneHandle A handle to the scene to get the simulated bodies from.
//! @param bodyHandle A handle to the simulated body to retrieve the raw pointer.
//! @return A raw pointer to the Simulated body. If the either handle is invalid this will return null.
virtual SimulatedBody* GetSimulatedBodyFromHandle(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
//! Get the Raw pointer to the set of requested simulated bodies.
//! @param sceneHandle A handle to the scene to get the simulated bodies from.
//! @param bodyHandles A list of simulated body handles to retrieve the raw pointers.
//! @return A list of raw pointers to the Simulated bodies requested. If the scene handle is invalid this will return an empty list.
//! If a simulated body handle is invalid, that index in the list will be null.
virtual SimulatedBodyList GetSimulatedBodiesFromHandle(SceneHandle sceneHandle, const SimulatedBodyHandleList& bodyHandles) = 0;
//! Remove a simulated body from the Scene.z
//! @param sceneHandle A handle to the scene to remove the requested simulated body.
//! @param bodyHandle A handle to the simulated body being removed.
virtual void RemoveSimulatedBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
//! Remove a list of simulated bodies from the Scene.
//! @param sceneHandle A handle to the scene to remove the simulated bodies from.
//! @param bodyHandles A list of simulated body handles to be removed.
virtual void RemoveSimulatedBodies(SceneHandle sceneHandle, const SimulatedBodyHandleList& bodyHandles) = 0;
//! Enable / Disable simulation of the requested body. By default all bodies added are enabled.
//! Disabling simulation the body will no longer be affected by any forces, collisions, or found with scene queries.
//! @param sceneHandle A handle to the scene to enable / disable the requested simulated body.
//! @param bodyHandle The handle of the simulated body to enable / disable.
virtual void EnableSimulationOfBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
virtual void DisableSimulationOfBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
//! Make a blocking query into the scene.
//! @param sceneHandle A handle to the scene to make the scene query with.
//! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @return Returns a structure that contains a list of Hits. Depending on flags set in the request, this may only contain 1 result.
virtual SceneQueryHits QueryScene(SceneHandle sceneHandle, const SceneQueryRequest* request) = 0;
//! Make many blocking queries into the scene.
//! @param sceneHandle A handle to the scene to make the scene query with.
//! @param requests A list of requests to make. Each entry should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @return Returns a list of SceneQueryHits. Will be in the same order as supplied in SceneQueryRequests.
virtual SceneQueryHitsList QuerySceneBatch(SceneHandle sceneHandle, const SceneQueryRequests& requests) = 0;
//! Make a non-blocking query into the scene.
//! @param sceneHandle A handle to the scene to make the scene query with.
//! @param requestId A user defined value to identify the request when the callback is called.
//! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @param callback The callback to trigger when the request is complete.
//! @return Returns If the request was queued successfully. If returns false, the callback will never be called.
[[nodiscard]] virtual bool QuerySceneAsync(SceneHandle sceneHandle, SceneQuery::AsyncRequestId requestId,
const SceneQueryRequest* request, SceneQuery::AsyncCallback callback) = 0;
//! Make a non-blocking query into the scene.
//! @param sceneHandle A handle to the scene to make the scene query with.
//! @param requestId A user defined valid to identify the request when the callback is called.
//! @param requests A list of requests to make. Each entry should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @param callback The callback to trigger when all the request are complete.
//! @return Returns If the request was queued successfully. If returns false, the callback will never be called.
[[nodiscard]] virtual bool QuerySceneAsyncBatch(SceneHandle sceneHandle, SceneQuery::AsyncRequestId requestId,
const SceneQueryRequests& requests, SceneQuery::AsyncBatchCallback callback) = 0;
//! Registers a pair of simulated bodies for which collisions should be suppressed.
//! Making multiple requests with the same pair result are dropped. To remove the suppression call UnsuppressCollisionEvents with the pair.
//! The order of the bodies do not matter, {body0, body1} collision pair is equal to {body1, body0}.
//! @param sceneHandle A handle to the scene to register the collision pair with.
//! @param bodyHandleA A handle to a simulated body.
//! @param bodyHandleB A handle to a simulated body.
virtual void SuppressCollisionEvents(SceneHandle sceneHandle,
const SimulatedBodyHandle& bodyHandleA,
const SimulatedBodyHandle& bodyHandleB) = 0;
//! Unregisters a pair of simulated bodies for which collisions should be suppressed.
//! Making multiple requests with the same pair result are dropped. To add a suppression call SuppressCollisionEvents with the pair.
//! The order of the bodies do not matter, {body0, body1} collision pair is equal to {body1, body0}.
//! @param sceneHandle A handle to the scene to unregister the collision pair with.
//! @param bodyHandleA A handle to a simulated body.
//! @param bodyHandleB A handle to a simulated body.
virtual void UnsuppressCollisionEvents(SceneHandle sceneHandle,
const SimulatedBodyHandle& bodyHandleA,
const SimulatedBodyHandle& bodyHandleB) = 0;
//! Set the Gravity of the given Scene.
//! @param sceneHandle A handle to the scene to set the gravity vector of.
//! @Param The new gravity vector to be used in the Scene
virtual void SetGravity(SceneHandle sceneHandle, const AZ::Vector3& gravity) = 0;
//! Get the Gravity of the given Scene.
//! @param sceneHandle A handle to the scene to get the gravity vector of.
//! @return A Vector3 of the gravity used in the Scene, will return a Zero Vector if sceneHandle is invalid or not found.
virtual AZ::Vector3 GetGravity(SceneHandle sceneHandle) const = 0;
//! Register a handler to receive an event when the SceneConfiguration changes.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneConfigurationChangedEventHandler(SceneHandle sceneHandle, SceneEvents::OnSceneConfigurationChanged::Handler& handler) = 0;
//! Register a handler to receive an event when a Simulated body is added to the Scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSimulationBodyAddedHandler(SceneHandle sceneHandle, SceneEvents::OnSimulationBodyAdded::Handler& handler) = 0;
//! Register a handler to receive an event when a Simulated body is removed from the Scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSimulationBodyRemovedHandler(SceneHandle sceneHandle, SceneEvents::OnSimulationBodyRemoved::Handler& handler) = 0;
//! Register a handler to receive an event when a Simulated body has its simulation enabled in the Scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! This will only trigger if the simulated body was disabled, when first added to a scene SceneEvents::OnAnySimulationBodyCreated will trigger instead.
//! @param handler The handler to receive the event.
virtual void RegisterSimulationBodySimulationEnabledHandler(SceneHandle sceneHandle, SceneEvents::OnSimulationBodySimulationEnabled::Handler& handler) = 0;
//! Register a handler to receive an event when a Simulated body has its simulation disabled in the Scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSimulationBodySimulationDisabledHandler(SceneHandle sceneHandle, SceneEvents::OnSimulationBodySimulationDisabled::Handler& handler) = 0;
//! Register a handler to receive an event when Scene::StartSimulation is called.
//! @note This may fire multiple times per frame.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneSimulationStartHandler(SceneHandle sceneHandle, SceneEvents::OnSceneSimulationStartHandler& handler) = 0;
//! Register a handler to receive an event when Scene::FinishSimulation is called.
//! @note This may fire multiple times per frame.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneSimulationFinishHandler(SceneHandle sceneHandle, SceneEvents::OnSceneSimulationFinishHandler& handler) = 0;
//! Register a handler to receive an event with a list of SimulatedBodyHandles that updated this scene tick.
//! @note This will fire after the OnSceneSimulationStartEvent and before the OnSceneSimulationFinishEvent when SceneConfiguration::m_enableActiveActors is true.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneActiveSimulatedBodiesHandler(SceneHandle sceneHandle, SceneEvents::OnSceneActiveSimulatedBodiesEvent::Handler& handler) = 0;
//! Register a handler to receive all collision events in the scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneCollisionEventHandler(SceneHandle sceneHandle, SceneEvents::OnSceneCollisionsEvent::Handler& handler) = 0;
//! Register a handler to receive all trigger events in the scene.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneTriggersEventHandler(SceneHandle sceneHandle, SceneEvents::OnSceneTriggersEvent::Handler& handler) = 0;
//! Register a handler to receive a notification when the Scene's gravity has changed.
//! @param sceneHandle A handle to the scene to register the event with.
//! @param handler The handler to receive the event.
virtual void RegisterSceneGravityChangedEvent(SceneHandle sceneHandle, SceneEvents::OnSceneGravityChangedEvent::Handler& handler) = 0;
};
//! Interface of a Physics Scene
class Scene
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(Scene, "{52BD8163-BDC4-4B09-ABB2-11DD1F601FFD}");
static void Reflect(AZ::ReflectContext* context);
Scene() = default;
explicit Scene(const SceneConfiguration& config);
virtual ~Scene() = default;
//! Get the Id of the Scene.
//! @return The Crc32 of the scene.
const AZ::Crc32& GetId() const;
//! Start the simulation process.
//! As an example, this is a good place to trigger and queue any long running work in separate threads.
//! @param deltatime The time in seconds to run the simulation for.
@@ -39,7 +250,7 @@ namespace AzPhysics
//! Enable or Disable this Scene's Simulation tick.
//! Default is Enabled.
//! @param enable When true the Scene will execute its simulation tick when StartSimulation is called. When false, StartSimulation will not execute.
virtual void Enable(bool enable) = 0;
virtual void SetEnabled(bool enable) = 0;
//! Check if this Scene is currently Enabled.
//! @return When true the Scene is enabled and will execute its simulation tick when StartSimulation is called. When false, StartSimulation will not execute.
@@ -53,8 +264,217 @@ namespace AzPhysics
//! @param config The new configuration to apply.
virtual void UpdateConfiguration(const SceneConfiguration& config) = 0;
// Temporary until LYN-438 work is complete
virtual AZStd::shared_ptr<Physics::World> GetLegacyWorld() const = 0;
//! Add a simulated body to the Scene.
//! @param simulatedBodyConfig The config of the simulated body.
//! @return Returns a handle to the created Simulated body. Will return AzPhyiscs::InvalidSimulatedBodyHandle if it fails.
virtual SimulatedBodyHandle AddSimulatedBody(const SimulatedBodyConfiguration* simulatedBodyConfig) = 0;
//! Add a set of simulated bodied to the Scene.
//! @param simulatedBodyConfigs The list of simulated body configs.
//! @return Returns a list of handles to the created Simulated bodies. Will be in the same order as supplied in simulatedBodyConfigs.
//! If one fails, that index will be set to AzPhyiscs::InvalidSimulatedBodyHandle.
virtual SimulatedBodyHandleList AddSimulatedBodies(const SimulatedBodyConfigurationList& simulatedBodyConfigs) = 0;
//! Get the Raw pointer to the requested simulated body.
//! @param bodyHandle A handle to the simulated body to retrieve the raw pointer for.
//! @return A raw pointer to the Simulated body. If the handle is invalid this will return null.
virtual SimulatedBody* GetSimulatedBodyFromHandle(SimulatedBodyHandle bodyHandle) = 0;
//! Get the Raw pointer to the set of requested simulated bodies.
//! @param bodyHandles A list of simulated body handles to retrieve the raw pointers for.
//! @return A list of raw pointers to the Simulated bodies requested. If a simulated body handle is invalid, that index in the list will be null.
virtual SimulatedBodyList GetSimulatedBodiesFromHandle(const SimulatedBodyHandleList& bodyHandles) = 0;
//! Remove a simulated body from the Scene.
//! @param bodyHandle A handle to the simulated body being removed.
virtual void RemoveSimulatedBody(SimulatedBodyHandle bodyHandle) = 0;
//! Remove a list of simulated bodies from the Scene.
//! @param bodyHandles A list of simulated body handles to be removed.
virtual void RemoveSimulatedBodies(const SimulatedBodyHandleList& bodyHandles) = 0;
//! Enable / Disable simulation of the requested body. By default all bodies added are enabled.
//! Disabling simulation the body will no longer be affected by any forces, collisions, or found with scene queries.
//! @param bodyHandle The handle of the simulated body to enable / disable.
virtual void EnableSimulationOfBody(SimulatedBodyHandle bodyHandle) = 0;
virtual void DisableSimulationOfBody(SimulatedBodyHandle bodyHandle) = 0;
//! Make a blocking query into the scene.
//! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @return Returns a structure that contains a list of Hits. Depending on flags set in the request, this may only contain 1 result.
virtual SceneQueryHits QueryScene(const SceneQueryRequest* request) = 0;
//! Make many blocking queries into the scene.
//! @param requests A list of requests to make. Each entry should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @return Returns a list of SceneQueryHits. Will be in the same order as supplied in SceneQueryRequests.
virtual SceneQueryHitsList QuerySceneBatch(const SceneQueryRequests& requests) = 0;
//! Make a non-blocking query into the scene.
//! @param requestId A user defined valid to identify the request when the callback is called.
//! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @param callback The callback to trigger when the request is complete.
//! @return Returns if the request was queued successfully. If returns false, the callback will never be called.
[[nodiscard]] virtual bool QuerySceneAsync(SceneQuery::AsyncRequestId requestId,
const SceneQueryRequest* request, SceneQuery::AsyncCallback callback) = 0;
//! Make a non-blocking query into the scene.
//! @param requestId A user defined valid to identify the request when the callback is called.
//! @param requests A list of requests to make. Each entry should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
//! @param callback The callback to trigger when all the request are complete.
//! @return Returns If the request was queued successfully. If returns false, the callback will never be called.
[[nodiscard]] virtual bool QuerySceneAsyncBatch(SceneQuery::AsyncRequestId requestId,
const SceneQueryRequests& requests, SceneQuery::AsyncBatchCallback callback) = 0;
//! Registers a pair of simulated bodies for which collisions should be suppressed.
//! Making multiple requests with the same pair result are dropped. To remove the suppression call UnsuppressCollisionEvents with the pair.
//! The order of the bodies do not matter, {body0, body1} collision pair is equal to {body1, body0}.
//! @param bodyHandleA A handle to a simulated body.
//! @param bodyHandleB A handle to a simulated body.
virtual void SuppressCollisionEvents(
const SimulatedBodyHandle& bodyHandleA,
const SimulatedBodyHandle& bodyHandleB) = 0;
//! Unregisters a pair of simulated bodies for which collisions should be suppressed.
//! Making multiple requests with the same pair result are dropped. To add a suppression call SuppressCollisionEvents with the pair.
//! The order of the bodies do not matter, {body0, body1} collision pair is equal to {body1, body0}.
//! @param bodyHandleA A handle to a simulated body.
//! @param bodyHandleB A handle to a simulated body.
virtual void UnsuppressCollisionEvents(
const SimulatedBodyHandle& bodyHandleA,
const SimulatedBodyHandle& bodyHandleB) = 0;
//! Set the Gravity of the Scene.
//! @Param The new gravity vector to be used in the Scene
virtual void SetGravity(const AZ::Vector3& gravity) = 0;
//! Get the Gravity of the Scene.
//! @return A Vector3 of the gravity used in the Scene.
virtual AZ::Vector3 GetGravity() const = 0;
//! Get the native pointer for a scene. Should be used with caution as it allows direct access to the lower level physics simulation.
//! @return A pointer to the underlying implementation of a scene if there is one.
virtual void* GetNativePointer() const = 0;
//! Register a handler to receive an event when the SceneConfiguration changes.
//! @param handler The handler to receive the event.
void RegisterSceneConfigurationChangedEventHandler(SceneEvents::OnSceneConfigurationChanged::Handler& handler);
//! Register a handler to receive an event when a Simulated body is added to the Scene.
//! @param handler The handler to receive the event.
void RegisterSimulationBodyAddedHandler(SceneEvents::OnSimulationBodyAdded::Handler& handler);
//! Register a handler to receive an event when a Simulated body is removed from the Scene.
//! @param handler The handler to receive the event.
void RegisterSimulationBodyRemovedHandler(SceneEvents::OnSimulationBodyRemoved::Handler& handler);
//! Register a handler to receive an event when a Simulated body has its simulation enabled in the Scene.
//! This will only trigger if the simulated body was disabled, when first added to a scene SceneEvents::OnAnySimulationBodyCreated will trigger instead.
//! @param handler The handler to receive the event.
void RegisterSimulationBodySimulationEnabledHandler(SceneEvents::OnSimulationBodySimulationEnabled::Handler& handler);
//! Register a handler to receive an event when a Simulated body has its simulation disabled in the Scene.
//! @param handler The handler to receive the event.
void RegisterSimulationBodySimulationDisabledHandler(SceneEvents::OnSimulationBodySimulationDisabled::Handler& handler);
//! Register a handler to receive an event when Scene::StartSimulation is called.
//! @note This may fire multiple times per frame.
//! @param handler The handler to receive the event.
void RegisterSceneSimulationStartHandler(SceneEvents::OnSceneSimulationStartHandler& handler);
//! Register a handler to receive an event when Scene::FinishSimulation is called.
//! @note This may fire multiple times per frame.
//! @param handler The handler to receive the event.
void RegisterSceneSimulationFinishHandler(SceneEvents::OnSceneSimulationFinishHandler& handler);
//! Register a handler to receive an event with a list of SimulatedBodyHandles that updated this scene tick.
//! @note This will fire after the OnSceneSimulationStartEvent and before the OnSceneSimulationFinishEvent when SceneConfiguration::m_enableActiveActors is true.
//! @param handler The handler to receive the event.
void RegisterSceneActiveSimulatedBodiesHandler(SceneEvents::OnSceneActiveSimulatedBodiesEvent::Handler& handler);
//! Register a handler to receive all collision events in the scene.
//! @param handler The handler to receive the event.
void RegisterSceneCollisionEventHandler(SceneEvents::OnSceneCollisionsEvent::Handler& handler);
//! Register a handler to receive all trigger events in the scene.
//! @param handler The handler to receive the event.
void RegisterSceneTriggersEventHandler(SceneEvents::OnSceneTriggersEvent::Handler& handler);
//! Register a handler to receive a notification when the Scene's gravity has changed.
//! @param handler The handler to receive the event.
void RegisterSceneGravityChangedEvent(SceneEvents::OnSceneGravityChangedEvent::Handler& handler);
protected:
SceneEvents::OnSceneConfigurationChanged m_configChangeEvent;
SceneEvents::OnSimulationBodyAdded m_simulatedBodyAddedEvent;
SceneEvents::OnSimulationBodyRemoved m_simulatedBodyRemovedEvent;
SceneEvents::OnSimulationBodySimulationEnabled m_simulatedBodySimulationEnabledEvent;
SceneEvents::OnSimulationBodySimulationDisabled m_simulatedBodySimulationDisabledEvent;
SceneEvents::OnSceneSimulationStartEvent m_sceneSimuationStartEvent;
SceneEvents::OnSceneSimulationFinishEvent m_sceneSimuationFinishEvent;
SceneEvents::OnSceneActiveSimulatedBodiesEvent m_sceneActiveSimulatedBodies;
SceneEvents::OnSceneCollisionsEvent m_sceneCollisionEvent;
SceneEvents::OnSceneTriggersEvent m_sceneTriggerEvent;
SceneEvents::OnSceneGravityChangedEvent m_sceneGravityChangedEvent;
private:
// helper for behaviour context
SceneEvents::OnSceneGravityChangedEvent* GetOnGravityChangedEvent();
AZ::Crc32 m_id;
};
using SceneList = AZStd::vector<Scene*>;
using SceneList = AZStd::vector<AZStd::unique_ptr<Scene>>;
inline void Scene::RegisterSceneConfigurationChangedEventHandler(SceneEvents::OnSceneConfigurationChanged::Handler& handler)
{
handler.Connect(m_configChangeEvent);
}
inline void Scene::RegisterSimulationBodyAddedHandler(SceneEvents::OnSimulationBodyAdded::Handler& handler)
{
handler.Connect(m_simulatedBodyAddedEvent);
}
inline void Scene::RegisterSimulationBodyRemovedHandler(SceneEvents::OnSimulationBodyRemoved::Handler& handler)
{
handler.Connect(m_simulatedBodyRemovedEvent);
}
inline void Scene::RegisterSimulationBodySimulationEnabledHandler(SceneEvents::OnSimulationBodySimulationEnabled::Handler& handler)
{
handler.Connect(m_simulatedBodySimulationEnabledEvent);
}
inline void Scene::RegisterSimulationBodySimulationDisabledHandler(SceneEvents::OnSimulationBodySimulationDisabled::Handler& handler)
{
handler.Connect(m_simulatedBodySimulationDisabledEvent);
}
inline void Scene::RegisterSceneSimulationStartHandler(SceneEvents::OnSceneSimulationStartHandler& handler)
{
handler.Connect(m_sceneSimuationStartEvent);
}
inline void Scene::RegisterSceneSimulationFinishHandler(SceneEvents::OnSceneSimulationFinishHandler& handler)
{
handler.Connect(m_sceneSimuationFinishEvent);
}
inline void Scene::RegisterSceneActiveSimulatedBodiesHandler(SceneEvents::OnSceneActiveSimulatedBodiesEvent::Handler& handler)
{
handler.Connect(m_sceneActiveSimulatedBodies);
}
inline void Scene::RegisterSceneCollisionEventHandler(SceneEvents::OnSceneCollisionsEvent::Handler& handler)
{
handler.Connect(m_sceneCollisionEvent);
}
inline void Scene::RegisterSceneTriggersEventHandler(SceneEvents::OnSceneTriggersEvent::Handler& handler)
{
handler.Connect(m_sceneTriggerEvent);
}
inline void Scene::RegisterSceneGravityChangedEvent(SceneEvents::OnSceneGravityChangedEvent::Handler& handler)
{
handler.Connect(m_sceneGravityChangedEvent);
}
} // namespace AzPhysics
@@ -0,0 +1,60 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/PhysicsSystem.h>
namespace AzPhysics
{
void SystemInterface::Reflect(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
const auto getOnPresimulateEvent = []()->SystemEvents::OnPresimulateEvent*
{
if (auto* physicsSystem = AZ::Interface<SystemInterface>::Get())
{
return &physicsSystem->m_preSimulateEvent;
}
return nullptr;
};
const AZ::BehaviorAzEventDescription presimulateEventDescription =
{
"Presimulate event",
{"Tick time"} // Parameters
};
const auto getOnPostsimulateEvent = []() -> SystemEvents::OnPostsimulateEvent*
{
if (auto* physicsSystem = AZ::Interface<SystemInterface>::Get())
{
return &physicsSystem->m_postSimulateEvent;
}
return nullptr;
};
const AZ::BehaviorAzEventDescription postsimulateEventDescription =
{
"Postsimulate event",
{} // Parameters
};
behaviorContext->Class<SystemInterface>("System Interface")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Method("GetOnPresimulateEvent", getOnPresimulateEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, presimulateEventDescription)
->Method("GetOnPostsimulateEvent", getOnPostsimulateEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, postsimulateEventDescription)
;
}
}
} // namespace AzPhysics
@@ -35,6 +35,8 @@ namespace AzPhysics
virtual ~SystemInterface() = default;
AZ_DISABLE_COPY_MOVE(SystemInterface);
static void Reflect(AZ::ReflectContext* context);
//! Initialize the Physics system with the given configuration.
//! @param config Contains the configuration options
virtual void Initialize(const SystemConfiguration* config) = 0;
@@ -83,6 +85,11 @@ namespace AzPhysics
//! @return Returns a list of SceneHandle objects for each created Scene. Order will be the same as the SceneConfigurationList provided.
virtual SceneHandleList AddScenes(const SceneConfigurationList& configs) = 0;
//! Returns a Scene Handle connected to the given scene name.
//! @param sceneName The name of the scene to look up.
//! @returns Will return a SceneHandle to a Scene connected with the given name, otherwise will return InvalidSceneHandle.
virtual SceneHandle GetSceneHandle(const AZStd::string& sceneName) = 0;
//! Get the Scene of the requested SceneHandle.
//! @param handle The SceneHandle of the requested scene.
//! @return Returns a SceneInterface pointer if found, otherwise nullptr.
@@ -108,6 +115,12 @@ namespace AzPhysics
//! Removes All Scenes.
virtual void RemoveAllScenes() = 0;
//! Helper to find the SceneHandle and SimulatedBodyHandle of a body related to the requested EntityId.
//! @note This will search all scenes and maybe slow if there are many Scenes.
//! @param entityId The entity to search for.
//! @return Will return a AZStd::pair of SceneHandle and SimulatedBodyHandle of the requested entityid, otherwise will return AzPhysics::InvalidSceneHandle, AzPhysics::SimulatedBodyHandle.
virtual AZStd::pair<SceneHandle, SimulatedBodyHandle> FindAttachedBodyHandleFromEntityId(AZ::EntityId entityId) = 0;
//! Get the current SystemConfiguration used to initialize the Physics system.
virtual const SystemConfiguration* GetConfiguration() const = 0;
@@ -147,6 +160,12 @@ namespace AzPhysics
//! Register to receive notifications when the Physics System simulation ends.
//! @param handler The handler to receive the event.
void RegisterPostSimulateEvent(SystemEvents::OnPostsimulateEvent::Handler& handler) { handler.Connect(m_postSimulateEvent); }
//! Register to receive notifications when the a new Scene is added to the simulation.
//! @param handler The handler to receive the event.
void RegisterSceneAddedEvent(SystemEvents::OnSceneAddedEvent::Handler& handler) { handler.Connect(m_sceneAddedEvent); }
//! Register to receive notifications when the a Scene is removed from the simulation.
//! @param handler The handler to receive the event.
void RegisterSceneRemovedEvent(SystemEvents::OnSceneAddedEvent::Handler& handler) { handler.Connect(m_sceneRemovedEvent); }
//! Register to receive notifications when the SystemConfiguration changes.
//! @param handler The handler to receive the event.
void RegisterSystemConfigurationChangedEvent(SystemEvents::OnConfigurationChangedEvent::Handler& handler) { handler.Connect(m_configChangeEvent); }
@@ -163,6 +182,8 @@ namespace AzPhysics
SystemEvents::OnShutdownEvent m_shutdownEvent;
SystemEvents::OnPresimulateEvent m_preSimulateEvent;
SystemEvents::OnPostsimulateEvent m_postSimulateEvent;
SystemEvents::OnSceneAddedEvent m_sceneAddedEvent;
SystemEvents::OnSceneRemovedEvent m_sceneRemovedEvent;
SystemEvents::OnConfigurationChangedEvent m_configChangeEvent;
SystemEvents::OnDefaultMaterialLibraryChangedEvent m_onDefaultMaterialLibraryChangedEvent;
SystemEvents::OnDefaultSceneConfigurationChangedEvent m_onDefaultSceneConfigurationChangedEvent;
@@ -21,13 +21,13 @@ namespace Physics
RagdollNodeConfiguration::RagdollNodeConfiguration()
{
m_propertyVisibilityFlags =
PropertyVisibility::InertiaProperties |
PropertyVisibility::Damping |
PropertyVisibility::SleepOptions |
PropertyVisibility::Interpolation |
PropertyVisibility::Gravity |
PropertyVisibility::ContinuousCollisionDetection |
PropertyVisibility::MaxVelocities;
RigidBodyConfiguration::PropertyVisibility::InertiaProperties |
RigidBodyConfiguration::PropertyVisibility::Damping |
RigidBodyConfiguration::PropertyVisibility::SleepOptions |
RigidBodyConfiguration::PropertyVisibility::Interpolation |
RigidBodyConfiguration::PropertyVisibility::Gravity |
RigidBodyConfiguration::PropertyVisibility::ContinuousCollisionDetection |
RigidBodyConfiguration::PropertyVisibility::MaxVelocities;
}
void RagdollNodeConfiguration::Reflect(AZ::ReflectContext* context)
@@ -57,7 +57,7 @@ namespace Physics
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<RagdollConfiguration, WorldBodyConfiguration>()
serializeContext->Class<RagdollConfiguration, AzPhysics::SimulatedBodyConfiguration>()
->Version(2, &ClassConverters::RagdollConfigConverter)
->Field("nodes", &RagdollConfiguration::m_nodes)
->Field("colliders", &RagdollConfiguration::m_colliders)
@@ -103,4 +103,4 @@ namespace Physics
m_nodes.erase(m_nodes.begin() + configIndex.GetValue());
}
}
} // namespace Physics
} // namespace Physics
@@ -15,19 +15,21 @@
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Physics/Character.h>
#include <AzFramework/Physics/Shape.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
#include <AzFramework/Physics/RagdollPhysicsBus.h>
#include <AzFramework/Physics/Joint.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
namespace Physics
{
class RagdollNodeConfiguration
: public RigidBodyConfiguration
: public AzPhysics::RigidBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(RagdollNodeConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(RagdollNodeConfiguration, "{A1796586-85AB-496E-93C9-C5841F03B1AD}", RigidBodyConfiguration);
AZ_RTTI(RagdollNodeConfiguration, "{A1796586-85AB-496E-93C9-C5841F03B1AD}", AzPhysics::RigidBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
RagdollNodeConfiguration();
@@ -37,11 +39,11 @@ namespace Physics
};
class RagdollConfiguration
: public WorldBodyConfiguration
: public AzPhysics::SimulatedBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(RagdollConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(RagdollConfiguration, "{7C96D332-61D8-4C58-A2BF-707716D38D14}", WorldBodyConfiguration);
AZ_RTTI(RagdollConfiguration, "{7C96D332-61D8-4C58-A2BF-707716D38D14}", AzPhysics::SimulatedBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
RagdollConfiguration() = default;
@@ -58,25 +60,26 @@ namespace Physics
/// Represents a single rigid part of a ragdoll.
class RagdollNode
: public WorldBody
: public AzPhysics::SimulatedBody
{
public:
AZ_CLASS_ALLOCATOR(RagdollNode, AZ::SystemAllocator, 0);
AZ_RTTI(RagdollNode, "{226D02B7-6138-4F6B-9870-DE5A1C3C5077}", WorldBody);
AZ_RTTI(RagdollNode, "{226D02B7-6138-4F6B-9870-DE5A1C3C5077}", AzPhysics::SimulatedBody);
virtual RigidBody& GetRigidBody() = 0;
virtual AzPhysics::RigidBody& GetRigidBody() = 0;
virtual ~RagdollNode() = default;
virtual const AZStd::shared_ptr<Physics::Joint>& GetJoint() const = 0;
virtual bool IsSimulating() const = 0;
};
/// A hierarchical collection of rigid bodies connected by joints typically used to physically simulate a character.
class Ragdoll
: public WorldBody
: public AzPhysics::SimulatedBody
{
public:
AZ_CLASS_ALLOCATOR(Ragdoll, AZ::SystemAllocator, 0);
AZ_RTTI(Ragdoll, "{01F09602-80EC-4693-A0E7-C2719239044B}", WorldBody);
AZ_RTTI(Ragdoll, "{01F09602-80EC-4693-A0E7-C2719239044B}", AzPhysics::SimulatedBody);
virtual ~Ragdoll() = default;
/// Inserts the ragdoll into the physics simulation.
@@ -131,8 +134,5 @@ namespace Physics
/// Returns the number of ragdoll nodes in the ragdoll.
virtual size_t GetNumNodes() const = 0;
/// Returns the id of the world the ragdoll exists in.
virtual AZ::Crc32 GetWorldId() const = 0;
};
}
@@ -1,188 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/RigidBody.h>
#include <AzFramework/Physics/ClassConverters.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace Physics
{
float DefaultRigidBodyConfiguration::m_mass = 1.f;
bool DefaultRigidBodyConfiguration::m_computeInertiaTensor = false;
float DefaultRigidBodyConfiguration::m_linearDamping = 0.05f;
float DefaultRigidBodyConfiguration::m_angularDamping = 0.15f;
float DefaultRigidBodyConfiguration::m_sleepMinEnergy = 0.5f;
float DefaultRigidBodyConfiguration::m_maxAngularVelocity = 100.0f;
void RigidBodyConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RigidBodyConfiguration, WorldBodyConfiguration>()
->Version(3, &ClassConverters::RigidBodyVersionConverter)
->Field("Initial linear velocity", &Physics::RigidBodyConfiguration::m_initialLinearVelocity)
->Field("Initial angular velocity", &Physics::RigidBodyConfiguration::m_initialAngularVelocity)
->Field("Linear damping", &Physics::RigidBodyConfiguration::m_linearDamping)
->Field("Angular damping", &Physics::RigidBodyConfiguration::m_angularDamping)
->Field("Sleep threshold", &Physics::RigidBodyConfiguration::m_sleepMinEnergy)
->Field("Start Asleep", &Physics::RigidBodyConfiguration::m_startAsleep)
->Field("Interpolate Motion", &Physics::RigidBodyConfiguration::m_interpolateMotion)
->Field("Gravity Enabled", &Physics::RigidBodyConfiguration::m_gravityEnabled)
->Field("Simulated", &Physics::RigidBodyConfiguration::m_simulated)
->Field("Kinematic", &Physics::RigidBodyConfiguration::m_kinematic)
->Field("CCD Enabled", &Physics::RigidBodyConfiguration::m_ccdEnabled)
->Field("Compute Mass", &Physics::RigidBodyConfiguration::m_computeMass)
->Field("Mass", &Physics::RigidBodyConfiguration::m_mass)
->Field("Compute COM", &Physics::RigidBodyConfiguration::m_computeCenterOfMass)
->Field("Centre of mass offset", &RigidBodyConfiguration::m_centerOfMassOffset)
->Field("Compute inertia", &RigidBodyConfiguration::m_computeInertiaTensor)
->Field("Inertia tensor", &RigidBodyConfiguration::m_inertiaTensor)
->Field("Property Visibility Flags", &RigidBodyConfiguration::m_propertyVisibilityFlags)
->Field("Maximum Angular Velocity", &RigidBodyConfiguration::m_maxAngularVelocity)
->Field("Include All Shapes In Mass", &RigidBodyConfiguration::m_includeAllShapesInMassCalculation)
->Field("CCD Min Advance", &RigidBodyConfiguration::m_ccdMinAdvanceCoefficient)
->Field("CCD Friction", &RigidBodyConfiguration::m_ccdFrictionEnabled)
;
}
}
AZ::Crc32 RigidBodyConfiguration::GetPropertyVisibility(PropertyVisibility property) const
{
return (m_propertyVisibilityFlags & property) != 0 ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
void RigidBodyConfiguration::SetPropertyVisibility(PropertyVisibility property, bool isVisible)
{
if (isVisible)
{
m_propertyVisibilityFlags |= property;
}
else
{
m_propertyVisibilityFlags &= ~property;
}
}
AZ::Crc32 RigidBodyConfiguration::GetInitialVelocitiesVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::InitialVelocities);
}
AZ::Crc32 RigidBodyConfiguration::GetInertiaSettingsVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::InertiaProperties);
}
AZ::Crc32 RigidBodyConfiguration::GetInertiaVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & PropertyVisibility::InertiaProperties) != 0) && !m_computeInertiaTensor;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetCoMVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & PropertyVisibility::InertiaProperties) != 0) && !m_computeCenterOfMass;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetMassVisibility() const
{
bool visible = ((m_propertyVisibilityFlags & PropertyVisibility::InertiaProperties) != 0) && !m_computeMass;
return visible ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
AZ::Crc32 RigidBodyConfiguration::GetDampingVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::Damping);
}
AZ::Crc32 RigidBodyConfiguration::GetSleepOptionsVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::SleepOptions);
}
AZ::Crc32 RigidBodyConfiguration::GetInterpolationVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::Interpolation);
}
AZ::Crc32 RigidBodyConfiguration::GetGravityVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::Gravity);
}
AZ::Crc32 RigidBodyConfiguration::GetKinematicVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::Kinematic);
}
AZ::Crc32 RigidBodyConfiguration::GetCCDVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::ContinuousCollisionDetection);
}
AZ::Crc32 RigidBodyConfiguration::GetMaxVelocitiesVisibility() const
{
return GetPropertyVisibility(PropertyVisibility::MaxVelocities);
}
Physics::MassComputeFlags RigidBodyConfiguration::GetMassComputeFlags() const
{
using Physics::MassComputeFlags;
MassComputeFlags flags = MassComputeFlags::NONE;
if (m_computeCenterOfMass)
{
flags = flags | MassComputeFlags::COMPUTE_COM;
}
if (m_computeInertiaTensor)
{
flags = flags | MassComputeFlags::COMPUTE_INERTIA;
}
if (m_computeMass)
{
flags = flags | MassComputeFlags::COMPUTE_MASS;
}
if (m_includeAllShapesInMassCalculation)
{
flags = flags | MassComputeFlags::INCLUDE_ALL_SHAPES;
}
return flags;
}
void RigidBodyConfiguration::SetMassComputeFlags(MassComputeFlags flags)
{
using Physics::MassComputeFlags;
m_computeCenterOfMass = MassComputeFlags::COMPUTE_COM == (flags & MassComputeFlags::COMPUTE_COM);
m_computeInertiaTensor = MassComputeFlags::COMPUTE_INERTIA == (flags & MassComputeFlags::COMPUTE_INERTIA);
m_computeMass = MassComputeFlags::COMPUTE_MASS == (flags & MassComputeFlags::COMPUTE_MASS);
m_includeAllShapesInMassCalculation =
MassComputeFlags::INCLUDE_ALL_SHAPES == (flags & MassComputeFlags::INCLUDE_ALL_SHAPES);
}
bool RigidBodyConfiguration::IsCCDEnabled() const
{
return m_ccdEnabled;
}
RigidBody::RigidBody(const RigidBodyConfiguration& settings)
: WorldBody(settings)
{
}
} // namespace Physics
@@ -1,239 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
namespace
{
class ReflectContext;
}
namespace Physics
{
class ShapeConfiguration;
class World;
class Shape;
/// Default values used for initializing RigidBodySettings.
/// These can be modified by Physics Implementation gems.
// LUMBERYARD_DEPRECATED(LY-114472) - DefaultRigidBodyConfiguration values are not shared across modules.
// Use RigidBodyConfiguration default values.
struct DefaultRigidBodyConfiguration
{
static float m_mass;
static bool m_computeInertiaTensor;
static float m_linearDamping;
static float m_angularDamping;
static float m_sleepMinEnergy;
static float m_maxAngularVelocity;
};
enum class MassComputeFlags : AZ::u8
{
NONE = 0,
//! Flags indicating whether a certain mass property should be auto-computed or not.
COMPUTE_MASS = 1,
COMPUTE_INERTIA = 1 << 1,
COMPUTE_COM = 1 << 2,
//! If set, non-simulated shapes will also be included in the mass properties calculation.
INCLUDE_ALL_SHAPES = 1 << 3,
DEFAULT = COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS
};
class RigidBodyConfiguration
: public WorldBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(RigidBodyConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBodyConfiguration, "{ACFA8900-8530-4744-AF00-AA533C868A8E}", WorldBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
enum PropertyVisibility : AZ::u16
{
InitialVelocities = 1 << 0, ///< Whether the initial linear and angular velocities are visible.
InertiaProperties = 1 << 1, ///< Whether the whole category of inertia properties (mass, compute inertia,
///< inertia tensor etc) is visible.
Damping = 1 << 2, ///< Whether linear and angular damping are visible.
SleepOptions = 1 << 3, ///< Whether the sleep threshold and start asleep options are visible.
Interpolation = 1 << 4, ///< Whether the interpolation option is visible.
Gravity = 1 << 5, ///< Whether the effected by gravity option is visible.
Kinematic = 1 << 6, ///< Whether the option to make the body kinematic is visible.
ContinuousCollisionDetection = 1 << 7, ///< Whether the option to enable continuous collision detection is visible.
MaxVelocities = 1 << 8 ///< Whether upper limits on velocities are visible.
};
RigidBodyConfiguration() = default;
RigidBodyConfiguration(const RigidBodyConfiguration& settings) = default;
// Visibility functions.
AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const;
void SetPropertyVisibility(PropertyVisibility property, bool isVisible);
AZ::Crc32 GetInitialVelocitiesVisibility() const;
/// Returns whether the whole category of inertia settings (mass, inertia, center of mass offset etc) is visible.
AZ::Crc32 GetInertiaSettingsVisibility() const;
/// Returns whether the individual inertia tensor field is visible or is hidden because the compute inertia option is selected.
AZ::Crc32 GetInertiaVisibility() const;
/// Returns whether the mass field is visible or is hidden because compute mass option is selected.
AZ::Crc32 GetMassVisibility() const;
/// Returns whether the individual centre of mass offset field is visible or is hidden because compute CoM option is selected.
AZ::Crc32 GetCoMVisibility() const;
AZ::Crc32 GetDampingVisibility() const;
AZ::Crc32 GetSleepOptionsVisibility() const;
AZ::Crc32 GetInterpolationVisibility() const;
AZ::Crc32 GetGravityVisibility() const;
AZ::Crc32 GetKinematicVisibility() const;
AZ::Crc32 GetCCDVisibility() const;
AZ::Crc32 GetMaxVelocitiesVisibility() const;
MassComputeFlags GetMassComputeFlags() const;
void SetMassComputeFlags(MassComputeFlags flags);
bool IsCCDEnabled() const;
// Basic initial settings.
AZ::Vector3 m_initialLinearVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_initialAngularVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_centerOfMassOffset = AZ::Vector3::CreateZero();
// Simulation parameters.
float m_mass = DefaultRigidBodyConfiguration::m_mass;
AZ::Matrix3x3 m_inertiaTensor = AZ::Matrix3x3::CreateIdentity();
float m_linearDamping = DefaultRigidBodyConfiguration::m_linearDamping;
float m_angularDamping = DefaultRigidBodyConfiguration::m_angularDamping;
float m_sleepMinEnergy = DefaultRigidBodyConfiguration::m_sleepMinEnergy;
float m_maxAngularVelocity = DefaultRigidBodyConfiguration::m_maxAngularVelocity;
// Visibility settings.
AZ::u16 m_propertyVisibilityFlags = (std::numeric_limits<AZ::u16>::max)();
bool m_startAsleep = false;
bool m_interpolateMotion = false;
bool m_gravityEnabled = true;
bool m_simulated = true;
bool m_kinematic = false;
bool m_ccdEnabled = false; ///< Whether continuous collision detection is enabled.
float m_ccdMinAdvanceCoefficient = 0.15f; ///< Coefficient affecting how granularly time is subdivided in CCD.
bool m_ccdFrictionEnabled = false; ///< Whether friction is applied when resolving CCD collisions.
bool m_computeCenterOfMass = true;
bool m_computeInertiaTensor = true;
bool m_computeMass = true;
//! If set, non-simulated shapes will also be included in the mass properties calculation.
bool m_includeAllShapesInMassCalculation = false;
};
/// Dynamic rigid body.
class RigidBody
: public WorldBody
{
public:
AZ_CLASS_ALLOCATOR(RigidBody, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBody, "{156E459F-7BB7-4B4E-ADA0-2130D96B7E80}", WorldBody);
public:
RigidBody() = default;
explicit RigidBody(const RigidBodyConfiguration& settings);
virtual void AddShape(AZStd::shared_ptr<Shape> shape) = 0;
virtual void RemoveShape(AZStd::shared_ptr<Shape> shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
virtual AZ::Vector3 GetCenterOfMassWorld() const = 0;
virtual AZ::Vector3 GetCenterOfMassLocal() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaWorld() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaLocal() const = 0;
virtual float GetMass() const = 0;
virtual float GetInverseMass() const = 0;
virtual void SetMass(float mass) = 0;
virtual void SetCenterOfMassOffset(const AZ::Vector3& comOffset) = 0;
/// Retrieves the velocity at center of mass; only linear velocity, no rotational velocity contribution.
virtual AZ::Vector3 GetLinearVelocity() const = 0;
virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0;
virtual AZ::Vector3 GetAngularVelocity() const = 0;
virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0;
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0;
virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0;
virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0;
virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0;
virtual float GetLinearDamping() const = 0;
virtual void SetLinearDamping(float damping) = 0;
virtual float GetAngularDamping() const = 0;
virtual void SetAngularDamping(float damping) = 0;
virtual bool IsAwake() const = 0;
virtual void ForceAsleep() = 0;
virtual void ForceAwake() = 0;
virtual float GetSleepThreshold() const = 0;
virtual void SetSleepThreshold(float threshold) = 0;
virtual bool IsKinematic() const = 0;
virtual void SetKinematic(bool kinematic) = 0;
virtual void SetKinematicTarget(const AZ::Transform& targetPosition) = 0;
virtual bool IsGravityEnabled() const = 0;
virtual void SetGravityEnabled(bool enabled) = 0;
virtual void SetSimulationEnabled(bool enabled) = 0;
virtual void SetCCDEnabled(bool enabled) = 0;
//! Recalculates mass, inertia and center of mass based on the flags passed.
//! @param flags MassComputeFlags specifying which properties should be recomputed.
//! @param centerOfMassOffsetOverride Optional override of the center of mass. Note: This parameter will be ignored if COMPUTE_COM is passed in flags.
//! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags.
//! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags.
virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT,
const AZ::Vector3* centerOfMassOffsetOverride = nullptr,
const AZ::Matrix3x3* inertiaTensorOverride = nullptr,
const float* massOverride = nullptr) = 0;
};
/// Bitwise operators for MassComputeFlags
inline MassComputeFlags operator|(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) | aznumeric_cast<AZ::u8>(rhs));
}
inline MassComputeFlags operator&(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) & aznumeric_cast<AZ::u8>(rhs));
}
/// Static rigid body.
class RigidBodyStatic
: public WorldBody
{
public:
AZ_CLASS_ALLOCATOR(RigidBodyStatic, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBodyStatic, "{13A677BB-7085-4EDB-BCC8-306548238692}", WorldBody);
virtual void AddShape(const AZStd::shared_ptr<Shape>& shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
};
} // namespace Physics
@@ -13,12 +13,17 @@
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
namespace AzPhysics
{
struct RigidBody;
}
namespace Physics
{
class RigidBody;
class RigidBodyRequests
: public AZ::ComponentBus
@@ -69,9 +74,9 @@ namespace Physics
virtual void SetSimulationEnabled(bool enabled) = 0;
virtual AZ::Aabb GetAabb() const = 0;
virtual Physics::RigidBody* GetRigidBody() = 0;
virtual AzPhysics::RigidBody* GetRigidBody() = 0;
virtual Physics::RayCastHit RayCast(const Physics::RayCastRequest& request) = 0;
virtual AzPhysics::SceneQueryHit RayCast(const AzPhysics::RayCastRequest& request) = 0;
};
using RigidBodyRequestBus = AZ::EBus<RigidBodyRequests>;
@@ -1,162 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ScriptCanvasPhysicsUtils.h"
#include <AzFramework/Physics/WorldBody.h>
namespace Physics
{
namespace ReflectionUtils
{
CollisionNotificationBusBehaviorHandler::CollisionNotificationBusBehaviorHandler()
{
m_events.resize(FN_MAX);
SetEvent(&CollisionNotificationBusBehaviorHandler::OnCollisionBeginDummy, "OnCollisionBegin");
SetEvent(&CollisionNotificationBusBehaviorHandler::OnCollisionPersistDummy, "OnCollisionPersist");
SetEvent(&CollisionNotificationBusBehaviorHandler::OnCollisionEndDummy, "OnCollisionEnd");
}
void CollisionNotificationBusBehaviorHandler::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<Physics::Contact>()
->Version(1)
->Field("Position", &Physics::Contact::m_position)
->Field("Normal", &Physics::Contact::m_normal)
->Field("Impulse", &Physics::Contact::m_impulse)
->Field("Separation", &Physics::Contact::m_separation)
;
serializeContext->Class<Physics::CollisionEvent>()
->Field("Contacts", &Physics::CollisionEvent::m_contacts)
;
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<Physics::Contact>("Contact")
->Property("Position", BehaviorValueProperty(&Physics::Contact::m_position))
->Property("Normal", BehaviorValueProperty(&Physics::Contact::m_normal))
->Property("Impulse", BehaviorValueProperty(&Physics::Contact::m_impulse))
->Property("Separation", BehaviorValueProperty(&Physics::Contact::m_separation))
;
behaviorContext->Class<Physics::CollisionEvent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Property("Contacts", BehaviorValueProperty(&Physics::CollisionEvent::m_contacts))
;
behaviorContext->EBus<Physics::CollisionNotificationBus>("CollisionNotificationBus")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Handler<CollisionNotificationBusBehaviorHandler>()
;
}
}
void CollisionNotificationBusBehaviorHandler::Disconnect()
{
BusDisconnect();
}
bool CollisionNotificationBusBehaviorHandler::Connect(AZ::BehaviorValueParameter* id)
{
return AZ::Internal::EBusConnector<CollisionNotificationBusBehaviorHandler>::Connect(this, id);
}
bool CollisionNotificationBusBehaviorHandler::IsConnected()
{
return AZ::Internal::EBusConnector<CollisionNotificationBusBehaviorHandler>::IsConnected(this);
}
bool CollisionNotificationBusBehaviorHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
{
return AZ::Internal::EBusConnector<CollisionNotificationBusBehaviorHandler>::IsConnectedId(this, id);
}
int CollisionNotificationBusBehaviorHandler::GetFunctionIndex(const char* functionName) const
{
if (strcmp(functionName, "OnCollisionBegin") == 0) return FN_OnCollisionBegin;
if (strcmp(functionName, "OnCollisionPersist") == 0) return FN_OnCollisionPersist;
if (strcmp(functionName, "OnCollisionEnd") == 0) return FN_OnCollisionEnd;
return -1;
}
void CollisionNotificationBusBehaviorHandler::OnCollisionBeginDummy(AZ::EntityId /*entityId*/, const AZStd::vector<Contact>& /*contacts*/)
{
// This is never invoked, and only used for type deduction when calling SetEvent
}
void CollisionNotificationBusBehaviorHandler::OnCollisionPersistDummy(AZ::EntityId /*entityId*/, const AZStd::vector<Contact>& /*contacts*/)
{
// This is never invoked, and only used for type deduction when calling SetEvent
}
void CollisionNotificationBusBehaviorHandler::OnCollisionEndDummy(AZ::EntityId /*entityId*/)
{
// This is never invoked, and only used for type deduction when calling SetEvent
}
void CollisionNotificationBusBehaviorHandler::OnCollisionBegin(const CollisionEvent& collisionEvent)
{
Call(FN_OnCollisionBegin, collisionEvent.m_body2->GetEntityId(), collisionEvent.m_contacts);
}
void CollisionNotificationBusBehaviorHandler::OnCollisionPersist(const CollisionEvent& collisionEvent)
{
Call(FN_OnCollisionPersist, collisionEvent.m_body2->GetEntityId(), collisionEvent.m_contacts);
}
void CollisionNotificationBusBehaviorHandler::OnCollisionEnd(const CollisionEvent& collisionEvent)
{
Call(FN_OnCollisionEnd, collisionEvent.m_body2->GetEntityId());
}
void WorldNotificationBusBehaviorHandler::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<WorldNotificationBus>("WorldNotificationBus")
->Handler<WorldNotificationBusBehaviorHandler>()
;
}
}
void WorldNotificationBusBehaviorHandler::OnPrePhysicsTick(float deltaTime)
{
Call(FN_OnPrePhysicsTick, deltaTime);
}
void WorldNotificationBusBehaviorHandler::OnPrePhysicsSubtick(float fixedDeltaTime)
{
Call(FN_OnPrePhysicsSubtick, fixedDeltaTime);
}
void WorldNotificationBusBehaviorHandler::OnPostPhysicsSubtick(float fixedDeltaTime)
{
Call(FN_OnPostPhysicsSubtick, fixedDeltaTime);
}
void WorldNotificationBusBehaviorHandler::OnPostPhysicsTick(float deltaTime)
{
Call(FN_OnPostPhysicsTick, deltaTime);
}
int WorldNotificationBusBehaviorHandler::GetPhysicsTickOrder()
{
int order = WorldNotifications::Scripting;
CallResult(order, FN_GetPhysicsTickOrder);
return order;
}
} // namespace ReflectionUtils
} // namespace Physics
@@ -1,96 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Physics/TriggerBus.h>
#include <AzFramework/Physics/CollisionNotificationBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/Physics/World.h>
namespace Physics
{
namespace ReflectionUtils
{
/// Behavior handler which forwards CollisionNotificationBus events to script canvas.
/// Note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER macro as the signature
/// needs to be changed for script canvas
class CollisionNotificationBusBehaviorHandler
: public CollisionNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_CLASS_ALLOCATOR(CollisionNotificationBusBehaviorHandler, AZ::SystemAllocator, 0);
AZ_RTTI(CollisionNotificationBusBehaviorHandler, "{A28ACB8F-3429-4F92-88DB-481ACF90EF21}", AZ::BehaviorEBusHandler);
static void Reflect(AZ::ReflectContext* context);
CollisionNotificationBusBehaviorHandler();
// Script Canvas Signature
void OnCollisionBeginDummy(AZ::EntityId entityId, const AZStd::vector<Contact>& contacts);
void OnCollisionPersistDummy(AZ::EntityId entityId, const AZStd::vector<Contact>& contacts);
void OnCollisionEndDummy(AZ::EntityId entityId);
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence
<
decltype(&CollisionNotificationBusBehaviorHandler::OnCollisionBeginDummy),
decltype(&CollisionNotificationBusBehaviorHandler::OnCollisionPersistDummy),
decltype(&CollisionNotificationBusBehaviorHandler::OnCollisionEndDummy)
>;
private:
enum
{
FN_OnCollisionBegin,
FN_OnCollisionPersist,
FN_OnCollisionEnd,
FN_MAX
};
// AZ::BehaviorEBusHandler
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
int GetFunctionIndex(const char* functionName) const override;
// CollisionNotificationBus
void OnCollisionBegin(const CollisionEvent& triggerEvent) override;
void OnCollisionPersist(const CollisionEvent& triggerEvent) override;
void OnCollisionEnd(const CollisionEvent& triggerEvent) override;
};
class WorldNotificationBusBehaviorHandler
: public WorldNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
static void Reflect(AZ::ReflectContext* context);
AZ_EBUS_BEHAVIOR_BINDER(WorldNotificationBusBehaviorHandler, "{D8B108B8-9126-4C66-B857-377BA5DB3062}", AZ::SystemAllocator
, OnPrePhysicsTick
, OnPrePhysicsSubtick
, OnPostPhysicsSubtick
, OnPostPhysicsTick
, GetPhysicsTickOrder
);
// WorldNotificationBus ...
void OnPrePhysicsTick(float deltaTime) override;
void OnPrePhysicsSubtick(float fixedDeltaTime) override;
void OnPostPhysicsSubtick(float fixedDeltaTime) override;
void OnPostPhysicsTick(float deltaTime) override;
int GetPhysicsTickOrder() override;
};
} // namespace ReflectionUtils
} // namespace Physics
@@ -17,6 +17,7 @@
#include <AzFramework/Physics/Material.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
namespace AZ
{
@@ -83,7 +84,6 @@ namespace Physics
using ShapeConfigurationList = AZStd::vector<ShapeConfigurationPair>;
struct RayCastRequest;
struct RayCastHit;
class Shape
{
@@ -121,11 +121,11 @@ namespace Physics
//! Raycast against this shape.
//! @param request Ray parameters in world space.
//! @param worldTransform World transform of this shape.
virtual Physics::RayCastHit RayCast(const Physics::RayCastRequest& worldSpaceRequest, const AZ::Transform& worldTransform) = 0;
virtual AzPhysics::SceneQueryHit RayCast(const AzPhysics::RayCastRequest& worldSpaceRequest, const AZ::Transform& worldTransform) = 0;
//! Raycast against this shape using local coordinates.
//! @param request Ray parameters in local space.
virtual Physics::RayCastHit RayCastLocal(const Physics::RayCastRequest& localSpaceRequest) = 0;
virtual AzPhysics::SceneQueryHit RayCastLocal(const AzPhysics::RayCastRequest& localSpaceRequest) = 0;
//! Retrieve this shape AABB.
//! @param worldTransform World transform of this shape.
@@ -0,0 +1,20 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
#include <AzCore/Memory/SystemAllocator.h>
namespace AzPhysics
{
AZ_CLASS_ALLOCATOR_IMPL(RigidBody, AZ::SystemAllocator, 0);
}
@@ -0,0 +1,101 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
#include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
namespace Physics
{
class ShapeConfiguration;
class Shape;
}
namespace AzPhysics
{
//! Dynamic rigid body.
struct RigidBody
: public SimulatedBody
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(AzPhysics::RigidBody, "{156E459F-7BB7-4B4E-ADA0-2130D96B7E80}", AzPhysics::SimulatedBody);
RigidBody() = default;
virtual void AddShape(AZStd::shared_ptr<Physics::Shape> shape) = 0;
virtual void RemoveShape(AZStd::shared_ptr<Physics::Shape> shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Physics::Shape> GetShape([[maybe_unused]]AZ::u32 index) { return nullptr; }
virtual AZ::Vector3 GetCenterOfMassWorld() const = 0;
virtual AZ::Vector3 GetCenterOfMassLocal() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaWorld() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaLocal() const = 0;
virtual float GetMass() const = 0;
virtual float GetInverseMass() const = 0;
virtual void SetMass(float mass) = 0;
virtual void SetCenterOfMassOffset(const AZ::Vector3& comOffset) = 0;
//! Retrieves the velocity at center of mass; only linear velocity, no rotational velocity contribution.
virtual AZ::Vector3 GetLinearVelocity() const = 0;
virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0;
virtual AZ::Vector3 GetAngularVelocity() const = 0;
virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0;
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0;
virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0;
virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0;
virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0;
virtual float GetLinearDamping() const = 0;
virtual void SetLinearDamping(float damping) = 0;
virtual float GetAngularDamping() const = 0;
virtual void SetAngularDamping(float damping) = 0;
virtual bool IsAwake() const = 0;
virtual void ForceAsleep() = 0;
virtual void ForceAwake() = 0;
virtual float GetSleepThreshold() const = 0;
virtual void SetSleepThreshold(float threshold) = 0;
virtual bool IsKinematic() const = 0;
virtual void SetKinematic(bool kinematic) = 0;
virtual void SetKinematicTarget(const AZ::Transform& targetPosition) = 0;
virtual bool IsGravityEnabled() const = 0;
virtual void SetGravityEnabled(bool enabled) = 0;
virtual void SetSimulationEnabled(bool enabled) = 0;
virtual void SetCCDEnabled(bool enabled) = 0;
//! Recalculates mass, inertia and center of mass based on the flags passed.
//! @param flags MassComputeFlags specifying which properties should be recomputed.
//! @param centerOfMassOffsetOverride Optional override of the center of mass. Note: This parameter will be ignored if COMPUTE_COM is passed in flags.
//! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags.
//! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags.
virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT,
const AZ::Vector3* centerOfMassOffsetOverride = nullptr,
const AZ::Matrix3x3* inertiaTensorOverride = nullptr,
const float* massOverride = nullptr) = 0;
};
} // namespace AzPhysics
@@ -10,26 +10,22 @@
*
*/
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/SimulatedBodies/StaticRigidBody.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace Physics
namespace AzPhysics
{
void WorldBodyConfiguration::Reflect(AZ::ReflectContext* context)
AZ_CLASS_ALLOCATOR_IMPL(StaticRigidBody, AZ::SystemAllocator, 0);
/*static*/ void StaticRigidBody::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<WorldBodyConfiguration>()
serializeContext->Class<StaticRigidBody, SimulatedBody>()
->Version(1)
->Field("name", &WorldBodyConfiguration::m_debugName)
;
}
}
void WorldBody::SetUserData(void* userData)
{
m_customUserData = userData;
}
} // namespace Physics
}
@@ -0,0 +1,47 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
namespace AZ
{
class ReflectContext;
}
namespace Physics
{
class ShapeConfiguration;
class ColliderConfiguration;
class Shape;
}
namespace AzPhysics
{
//! Static rigid body.
struct StaticRigidBody
: public SimulatedBody
{
public:
AZ_CLASS_ALLOCATOR_DECL;
AZ_RTTI(StaticRigidBody, "{13A677BB-7085-4EDB-BCC8-306548238692}", SimulatedBody);
static void Reflect(AZ::ReflectContext* context);
//Legacy API - may change with LYN-438
virtual void AddShape(const AZStd::shared_ptr<Physics::Shape>& shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Physics::Shape> GetShape([[maybe_unused]]AZ::u32 index) { return nullptr; }
};
}
@@ -16,33 +16,31 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Asset/GenericAssetHandler.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace AZ
{
class Vector3;
}
namespace AzPhysics
{
struct SimulatedBody;
struct RigidBodyConfiguration;
struct RigidBody;
}
namespace Physics
{
class World;
class WorldBody;
class RigidBody;
class RigidBodyStatic;
class Shape;
class Material;
class MaterialSelection;
class MaterialConfiguration;
class MaterialLibraryAsset;
class WorldBodyConfiguration;
class RigidBodyConfiguration;
class ColliderConfiguration;
class ShapeConfiguration;
class JointLimitConfiguration;
class Joint;
struct RayCastRequest;
struct RayCastResult;
struct ShapeCastRequest;
struct ShapeCastResult;
class CharacterConfiguration;
class Character;
@@ -71,9 +69,9 @@ namespace Physics
/// Settings structure provided to DebugDrawPhysics to drive debug drawing behavior.
struct DebugDrawSettings
{
using DebugDrawLineCallback = AZStd::function<void(const DebugDrawVertex& from, const DebugDrawVertex& to, const AZStd::shared_ptr<WorldBody>& body, float thickness, void* udata)>;
using DebugDrawTriangleCallback = AZStd::function<void(const DebugDrawVertex& a, const DebugDrawVertex& b, const DebugDrawVertex& c, const AZStd::shared_ptr<WorldBody>& body, void* udata)>;
using DebugDrawTriangleBatchCallback = AZStd::function<void(const DebugDrawVertex* verts, AZ::u32 numVerts, const AZ::u32* indices, AZ::u32 numIndices, const AZStd::shared_ptr<WorldBody>& body, void* udata)>;
using DebugDrawLineCallback = AZStd::function<void(const DebugDrawVertex& from, const DebugDrawVertex& to, const AZStd::shared_ptr<AzPhysics::SimulatedBody>& body, float thickness, void* udata)>;
using DebugDrawTriangleCallback = AZStd::function<void(const DebugDrawVertex& a, const DebugDrawVertex& b, const DebugDrawVertex& c, const AZStd::shared_ptr<AzPhysics::SimulatedBody>& body, void* udata)>;
using DebugDrawTriangleBatchCallback = AZStd::function<void(const DebugDrawVertex* verts, AZ::u32 numVerts, const AZ::u32* indices, AZ::u32 numIndices, const AZStd::shared_ptr<AzPhysics::SimulatedBody>& body, void* udata)>;
DebugDrawLineCallback m_drawLineCB; ///< Required user callback for line drawing.
DebugDrawTriangleBatchCallback m_drawTriBatchCB; ///< User callback for triangle batch drawing. Required if \ref m_isWireframe is false.
@@ -84,8 +82,8 @@ namespace Physics
bool m_drawBodyTransforms = false; ///< If enabled, draws transform axes for each body.
void* m_udata = nullptr; ///< Platform specific and/or gem specific optional user data pointer.
void DrawLine(const DebugDrawVertex& from, const DebugDrawVertex& to, const AZStd::shared_ptr<WorldBody>& body, float thickness = 1.0f) { m_drawLineCB(from, to, body, thickness, m_udata); }
void DrawTriangleBatch(const DebugDrawVertex* verts, AZ::u32 numVerts, const AZ::u32* indices, AZ::u32 numIndices, const AZStd::shared_ptr<WorldBody>& body) { m_drawTriBatchCB(verts, numVerts, indices, numIndices, body, m_udata); }
void DrawLine(const DebugDrawVertex& from, const DebugDrawVertex& to, const AZStd::shared_ptr<AzPhysics::SimulatedBody>& body, float thickness = 1.0f) { m_drawLineCB(from, to, body, thickness, m_udata); }
void DrawTriangleBatch(const DebugDrawVertex* verts, AZ::u32 numVerts, const AZ::u32* indices, AZ::u32 numIndices, const AZStd::shared_ptr<AzPhysics::SimulatedBody>& body) { m_drawTriBatchCB(verts, numVerts, indices, numIndices, body, m_udata); }
};
/// An interface to get the default physics world for systems that do not support multiple worlds.
@@ -95,8 +93,8 @@ namespace Physics
public:
using MutexType = AZStd::mutex;
/// Returns the Default world managed by a relevant system.
virtual AZStd::shared_ptr<World> GetDefaultWorld() = 0;
//! Returns a handle to the Default Scene managed by a relevant system.
virtual AzPhysics::SceneHandle GetDefaultSceneHandle() const = 0;
};
typedef AZ::EBus<DefaultWorldRequests> DefaultWorldBus;
@@ -108,8 +106,8 @@ namespace Physics
public:
using MutexType = AZStd::mutex;
/// Returns the Editor world managed editor system component.
virtual AZStd::shared_ptr<World> GetEditorWorld() = 0;
//! Returns a handle to the Editor Scene managed by editor system component.
virtual AzPhysics::SceneHandle GetEditorSceneHandle() const = 0;
};
using EditorWorldBus = AZ::EBus<EditorWorldRequests>;
@@ -142,8 +140,6 @@ namespace Physics
//////////////////////////////////////////////////////////////////////////
//// General Physics
virtual AZStd::unique_ptr<RigidBodyStatic> CreateStaticRigidBody(const WorldBodyConfiguration& configuration) = 0;
virtual AZStd::unique_ptr<RigidBody> CreateRigidBody(const RigidBodyConfiguration& configuration) = 0;
virtual AZStd::shared_ptr<Shape> CreateShape(const ColliderConfiguration& colliderConfiguration, const ShapeConfiguration& configuration) = 0;
/// Adds an appropriate collider component to the entity based on the provided shape configuration.
@@ -177,7 +173,7 @@ namespace Physics
virtual AZStd::vector<AZ::TypeId> GetSupportedJointTypes() = 0;
virtual AZStd::shared_ptr<JointLimitConfiguration> CreateJointLimitConfiguration(AZ::TypeId jointType) = 0;
virtual AZStd::shared_ptr<Joint> CreateJoint(const AZStd::shared_ptr<JointLimitConfiguration>& configuration,
Physics::WorldBody* parentBody, Physics::WorldBody* childBody) = 0;
AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody) = 0;
/// Generates joint limit visualization data in appropriate format to pass to DebugDisplayRequests draw functions.
/// @param configuration The joint configuration to generate visualization data for.
/// @param parentRotation The rotation of the joint's parent body (in the same frame as childRotation).
@@ -272,11 +268,7 @@ namespace Physics
/// Creates the physics representation used to handle basic character interactions (also known as a character
/// controller).
virtual AZStd::unique_ptr<Character> CreateCharacter(const CharacterConfiguration& characterConfig,
const ShapeConfiguration& shapeConfig, World& world) = 0;
/// Performs any updates related to character controllers which are per-world and not per-character, such as
/// computing character-character interactions.
virtual void UpdateCharacters(World& world, float deltaTime) = 0;
const ShapeConfiguration& shapeConfig, AzPhysics::SceneHandle& sceneHandle) = 0;
};
typedef AZ::EBus<CharacterSystemRequests> CharacterSystemRequestBus;
@@ -301,16 +293,4 @@ namespace Physics
typedef AZ::EBus<SystemDebugRequests> SystemDebugRequestBus;
class SystemNotifications
: public AZ::EBusTraits
{
public:
virtual ~SystemNotifications() {}
virtual void OnWorldCreated(World* /*world*/) {};
virtual void OnPreWorldDestroy(World* /*world*/) {};
};
using SystemNotificationBus = AZ::EBus<SystemNotifications>;
} // namespace Physics
@@ -1,41 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
namespace Physics
{
struct TriggerEvent;
/// Services provided by the PhysX Trigger Area Component.
class TriggerNotifications
: public AZ::ComponentBus
{
public:
// Ebus Traits. ID'd on trigger entity Id
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
static const bool EnableEventQueue = true;
using BusIdType = AZ::EntityId;
virtual ~TriggerNotifications() {}
/// Dispatched when an entity enters a trigger. The bus message is ID'd on the triggers entity Id.
virtual void OnTriggerEnter(const TriggerEvent& /*triggerEvent*/) {};
/// Dispatched when an entity exits a trigger. The bus message is ID'd on the triggers entity Id.
virtual void OnTriggerExit(const TriggerEvent& /*triggerEvent*/) {};
};
/// Bus to service the PhysX Trigger Area Component event group.
using TriggerNotificationBus = AZ::EBus<TriggerNotifications>;
} // namespace PhysX
@@ -12,138 +12,33 @@
#include "Utils.h"
#include "RigidBody.h"
#include "World.h"
#include "Material.h"
#include "Shape.h"
#include <AzFramework/Physics/AnimationConfiguration.h>
#include <AzFramework/Physics/CharacterBus.h>
#include <AzFramework/Physics/Character.h>
#include <AzFramework/Physics/Ragdoll.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Physics/CollisionNotificationBus.h>
#include <AzFramework/Physics/TriggerBus.h>
#include <AzFramework/Physics/ScriptCanvasPhysicsUtils.h>
#include <AzFramework/Physics/CollisionBus.h>
#include <AzFramework/Physics/WorldBodyBus.h>
#include <AzFramework/Physics/WindBus.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/Collision/CollisionEvents.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/CollisionConfiguration.h>
#include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
#include <AzFramework/Physics/Configuration/SceneConfiguration.h>
#include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
namespace Physics
{
namespace ReflectionUtils
{
/// Behavior handler which forwards TriggerNotificationBus events to script canvas.
/// Note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER macro as the signature
/// needs to be changed for script canvas
class TriggerNotificationBusBehaviorHandler
: public TriggerNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_CLASS_ALLOCATOR(TriggerNotificationBusBehaviorHandler, AZ::SystemAllocator, 0);
AZ_RTTI(TriggerNotificationBusBehaviorHandler, "{0519A121-16F9-4A97-8D54-092BCD963B95}", AZ::BehaviorEBusHandler);
TriggerNotificationBusBehaviorHandler() {
m_events.resize(FN_MAX);
SetEvent(&TriggerNotificationBusBehaviorHandler::OnTriggerEnterDummy, "OnTriggerEnter");
SetEvent(&TriggerNotificationBusBehaviorHandler::OnTriggerExitDummy, "OnTriggerExit");
}
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<Physics::TriggerNotificationBus>("TriggerNotificationBus")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Handler<TriggerNotificationBusBehaviorHandler>()
;
}
}
void OnTriggerEnterDummy(AZ::EntityId /*entityId*/)
{
// This is never invoked, and only used for type deduction when calling SetEvent
}
void OnTriggerExitDummy(AZ::EntityId /*entityId*/)
{
// This is never invoked, and only used for type deduction when calling SetEvent
}
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence
<
decltype(&TriggerNotificationBusBehaviorHandler::OnTriggerEnterDummy),
decltype(&TriggerNotificationBusBehaviorHandler::OnTriggerExitDummy)
>;
private:
enum
{
FN_OnTriggerEnter,
FN_OnTriggerExit,
FN_MAX
};
void Disconnect() override
{
BusDisconnect();
}
bool Connect(AZ::BehaviorValueParameter * id = nullptr) override
{
return AZ::Internal::EBusConnector<TriggerNotificationBusBehaviorHandler>::Connect(this, id);
}
bool IsConnected() override
{
return AZ::Internal::EBusConnector<TriggerNotificationBusBehaviorHandler>::IsConnected(this);
}
bool IsConnectedId(AZ::BehaviorValueParameter * id) override
{
return AZ::Internal::EBusConnector<TriggerNotificationBusBehaviorHandler>::IsConnectedId(this, id);
}
int GetFunctionIndex(const char * functionName) const override
{
if (strcmp(functionName, "OnTriggerEnter") == 0) return FN_OnTriggerEnter;
if (strcmp(functionName, "OnTriggerExit") == 0) return FN_OnTriggerExit;
return -1;
}
void OnTriggerEnter(const TriggerEvent& triggerEvent) override
{
Call(FN_OnTriggerEnter, triggerEvent.m_otherBody->GetEntityId());
}
void OnTriggerExit(const TriggerEvent& triggerEvent) override
{
Call(FN_OnTriggerExit, triggerEvent.m_otherBody->GetEntityId());
}
};
void ReflectWorldBus(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<Physics::WorldRequestBus>("WorldRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Event("GetGravity", &Physics::WorldRequestBus::Events::GetGravity)
->Event("SetGravity", &Physics::WorldRequestBus::Events::SetGravity)
;
}
}
void ReflectWorldBodyBus(AZ::ReflectContext* context)
{
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
@@ -179,6 +74,29 @@ namespace Physics
}
}
void ReflectCharacterBus(AZ::ReflectContext* context)
{
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<Physics::CharacterRequestBus>("CharacterControllerRequestBus", "Character Controller")
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::RuntimeOwn)
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Event("GetBasePosition", &Physics::CharacterRequests::GetBasePosition, "Get Base Position")
->Event("SetBasePosition", &Physics::CharacterRequests::SetBasePosition, "Set Base Position")
->Event("GetCenterPosition", &Physics::CharacterRequests::GetCenterPosition, "Get Center Position")
->Event("GetStepHeight", &Physics::CharacterRequests::GetStepHeight, "Get Step Height")
->Event("SetStepHeight", &Physics::CharacterRequests::SetStepHeight, "Set Step Height")
->Event("GetUpDirection", &Physics::CharacterRequests::GetUpDirection, "Get Up Direction")
->Event("GetSlopeLimitDegrees", &Physics::CharacterRequests::GetSlopeLimitDegrees, "Get Slope Limit (Degrees)")
->Event("SetSlopeLimitDegrees", &Physics::CharacterRequests::SetSlopeLimitDegrees, "Set Slope Limit (Degrees)")
->Event("GetMaximumSpeed", &Physics::CharacterRequests::GetMaximumSpeed, "Get Maximum Speed")
->Event("SetMaximumSpeed", &Physics::CharacterRequests::SetMaximumSpeed, "Set Maximum Speed")
->Event("GetVelocity", &Physics::CharacterRequests::GetVelocity, "Get Velocity")
->Event("AddVelocity", &Physics::CharacterRequests::AddVelocity, "Add Velocity")
;
}
}
void ReflectPhysicsApi(AZ::ReflectContext* context)
{
ShapeConfiguration::Reflect(context);
@@ -189,33 +107,35 @@ namespace Physics
PhysicsAssetShapeConfiguration::Reflect(context);
NativeShapeConfiguration::Reflect(context);
CookedMeshShapeConfiguration::Reflect(context);
AzPhysics::SystemInterface::Reflect(context);
AzPhysics::Scene::Reflect(context);
AzPhysics::CollisionLayer::Reflect(context);
AzPhysics::CollisionGroup::Reflect(context);
AzPhysics::CollisionLayers::Reflect(context);
AzPhysics::CollisionGroups::Reflect(context);
AzPhysics::CollisionConfiguration::Reflect(context);
AzPhysics::CollisionEvent::Reflect(context);
AzPhysics::TriggerEvent::Reflect(context);
AzPhysics::SceneConfiguration::Reflect(context);
MaterialConfiguration::Reflect(context);
MaterialLibraryAsset::Reflect(context);
MaterialLibraryAssetReflectionWrapper::Reflect(context);
DefaultMaterialLibraryAssetReflectionWrapper::Reflect(context);
JointLimitConfiguration::Reflect(context);
WorldBodyConfiguration::Reflect(context);
RigidBodyConfiguration::Reflect(context);
AzPhysics::SimulatedBodyConfiguration::Reflect(context);
AzPhysics::RigidBodyConfiguration::Reflect(context);
RagdollNodeConfiguration::Reflect(context);
RagdollConfiguration::Reflect(context);
CharacterColliderNodeConfiguration::Reflect(context);
CharacterColliderConfiguration::Reflect(context);
AnimationConfiguration::Reflect(context);
CharacterConfiguration::Reflect(context);
ReflectWorldBus(context);
AzPhysics::SimulatedBody::Reflect(context);
ReflectWorldBodyBus(context);
CollisionFilteringRequests::Reflect(context);
TriggerNotificationBusBehaviorHandler::Reflect(context);
CollisionNotificationBusBehaviorHandler::Reflect(context);
RayCastHit::Reflect(context);
WorldNotificationBusBehaviorHandler::Reflect(context);
AzPhysics::SceneQuery::ReflectSceneQueryObjects(context);
ReflectWindBus(context);
ReflectCharacterBus(context);
}
}
@@ -249,22 +169,6 @@ namespace Physics
}
}
void DeferDelete(AZStd::unique_ptr<Physics::WorldBody> worldBody)
{
if (!worldBody)
{
return;
}
// If the body is in a world, remove it from the world and defer
// the deletion until after the next update to ensure trigger exit events get raised.
if (Physics::World* world = worldBody->GetWorld())
{
world->RemoveBody(*worldBody);
world->DeferDelete(AZStd::move(worldBody));
}
}
bool FilterTag(AZ::Crc32 tag, AZ::Crc32 filterTag)
{
// If the filter tag is empty, then ignore it
@@ -22,10 +22,13 @@ namespace AZ
class ReflectContext;
}
namespace AzPhysics
{
struct SimulatedBody;
}
namespace Physics
{
class WorldBody;
namespace ReflectionUtils
{
void ReflectPhysicsApi(AZ::ReflectContext* context);
@@ -47,13 +50,7 @@ namespace Physics
, AZStd::string& stringInOut
, AZ::u64 maxStringLength);
/// Defers the deletion of the body until after the next world update.
/// The body is first removed from the world, and then deleted.
/// This ensures trigger exit events are raised correctly on deleted
/// objects.
void DeferDelete(AZStd::unique_ptr<Physics::WorldBody> body);
//! Returns true if the tag matches the filter tag, or the filter tag is empty
bool FilterTag(AZ::Crc32 tag, AZ::Crc32 filter);
}
}
}
@@ -1,335 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Physics/World.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzCore/Serialization/EditContext.h>
namespace
{
const float TimestepMin = 0.001f; //1000fps
const float TimestepMax = 0.05f; //20fps
}
namespace Physics
{
bool WorldConfiguration::VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement)
{
// conversion from version 1:
// - remove AutoSimulate
// - remove TerrainGroup
// - remove TerrainLayer
if (classElement.GetVersion() <= 1)
{
classElement.RemoveElementByName(AZ_CRC("AutoSimulate", 0xcce85fd9));
classElement.RemoveElementByName(AZ_CRC("TerrainGroup", 0xca808c89));
classElement.RemoveElementByName(AZ_CRC("TerrainLayer", 0x439be956));
}
// conversion from version 2:
// - remove TerrainMaterials
if (classElement.GetVersion() <= 2)
{
classElement.RemoveElementByName(AZ_CRC("TerrainMaterials", 0x6da24f86));
}
if (classElement.GetVersion() <= 3)
{
classElement.RemoveElementByName(AZ_CRC("HandleSimulationEvents", 0xba508787));
}
//clamping of time steps
if (classElement.GetVersion() <= 4)
{
if (AZ::SerializeContext::DataElementNode* maxTimeStepElement = classElement.FindSubElement(AZ_CRC("MaxTimeStep", 0x34e83795)))
{
float maxTimeStep = TimestepMax;
const bool foundMaxTimeStep = maxTimeStepElement->GetData<float>(maxTimeStep);
if (foundMaxTimeStep)
{
//clamp maxTimeStep between max and min
maxTimeStep = AZ::GetClamp(maxTimeStep, TimestepMin, TimestepMax);
maxTimeStepElement->SetData<float>(context, maxTimeStep);
}
if (AZ::SerializeContext::DataElementNode* fixedTimeStepElement = classElement.FindSubElement(AZ_CRC("FixedTimeStep", 0xd748ea77)))
{
float fixedTimeStep = TimestepMax;
bool foundFixedTimeStep = fixedTimeStepElement->GetData<float>(fixedTimeStep);
if (foundFixedTimeStep)
{
//clamp fixedTimeStep between maxTimeStep and min
fixedTimeStep = AZ::GetClamp(fixedTimeStep, TimestepMin, maxTimeStep);
fixedTimeStepElement->SetData<float>(context, fixedTimeStep);
}
}
}
}
return true;
}
AZ::u32 WorldConfiguration::OnMaxTimeStepChanged()
{
m_fixedTimeStep = AZStd::GetMin(m_fixedTimeStep, GetFixedTimeStepMax()); //since m_maxTimeStep has changed, m_fixedTimeStep might be larger then the max.
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
float WorldConfiguration::GetFixedTimeStepMax() const
{
return m_maxTimeStep;
}
AZ::Crc32 WorldConfiguration::GetCcdVisibility() const
{
return m_enableCcd ? AZ::Edit::PropertyVisibility::Show : AZ::Edit::PropertyVisibility::Hide;
}
void WorldConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<WorldConfiguration>()
->Version(5, &VersionConverter)
->Field("WorldBounds", &WorldConfiguration::m_worldBounds)
->Field("MaxTimeStep", &WorldConfiguration::m_maxTimeStep)
->Field("FixedTimeStep", &WorldConfiguration::m_fixedTimeStep)
->Field("Gravity", &WorldConfiguration::m_gravity)
->Field("RaycastBufferSize", &WorldConfiguration::m_raycastBufferSize)
->Field("SweepBufferSize", &WorldConfiguration::m_sweepBufferSize)
->Field("OverlapBufferSize", &WorldConfiguration::m_overlapBufferSize)
->Field("EnableCcd", &WorldConfiguration::m_enableCcd)
->Field("MaxCcdPasses", &WorldConfiguration::m_maxCcdPasses)
->Field("EnableCcdResweep", &WorldConfiguration::m_enableCcdResweep)
->Field("EnableActiveActors", &WorldConfiguration::m_enableActiveActors)
->Field("EnablePcm", &WorldConfiguration::m_enablePcm)
->Field("BounceThresholdVelocity", &WorldConfiguration::m_bounceThresholdVelocity)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<WorldConfiguration>("World Configuration", "Default world configuration")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_worldBounds, "World Bounds", "World bounds")
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_maxTimeStep, "Max Time Step (sec)", "Max time step in seconds")
->Attribute(AZ::Edit::Attributes::Min, TimestepMin)
->Attribute(AZ::Edit::Attributes::Max, TimestepMax)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &WorldConfiguration::OnMaxTimeStepChanged)//need to clamp m_fixedTimeStep if this value changes
->Attribute(AZ::Edit::Attributes::Decimals, 8)
->Attribute(AZ::Edit::Attributes::DisplayDecimals, 8)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_fixedTimeStep, "Fixed Time Step (sec)", "Fixed time step in seconds. Limited by 'Max Time Step'")
->Attribute(AZ::Edit::Attributes::Min, TimestepMin)
->Attribute(AZ::Edit::Attributes::Max, &WorldConfiguration::GetFixedTimeStepMax)
->Attribute(AZ::Edit::Attributes::Decimals, 8)
->Attribute(AZ::Edit::Attributes::DisplayDecimals, 8)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_gravity, "Gravity", "Gravity")
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_raycastBufferSize, "Raycast Buffer Size", "Maximum number of hits from a raycast")
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_sweepBufferSize, "Shapecast Buffer Size", "Maximum number of hits from a shapecast")
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_overlapBufferSize, "Overlap Query Buffer Size", "Maximum number of hits from a overlap query")
->Attribute(AZ::Edit::Attributes::Min, 1u)
->ClassElement(AZ::Edit::ClassElements::Group, "Continuous Collision Detection")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_enableCcd, "Enable CCD", "Enabled continuous collision detection in the world")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_maxCcdPasses,
"Max CCD Passes", "Maximum number of continuous collision detection passes")
->Attribute(AZ::Edit::Attributes::Visibility, &WorldConfiguration::GetCcdVisibility)
->Attribute(AZ::Edit::Attributes::Min, 1u)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_enableCcdResweep,
"Enable CCD Resweep", "Enable a more accurate but more expensive continuous collision detection method")
->Attribute(AZ::Edit::Attributes::Visibility, &WorldConfiguration::GetCcdVisibility)
->ClassElement(AZ::Edit::ClassElements::Group, "") // end previous group by starting new unnamed expanded group
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_enablePcm, "Persistent Contact Manifold", "Enabled the persistent contact manifold narrow-phase algorithm")
->DataElement(AZ::Edit::UIHandlers::Default, &WorldConfiguration::m_bounceThresholdVelocity,
"Bounce Threshold Velocity", "Relative velocity below which colliding objects will not bounce")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
;
}
}
}
bool WorldConfiguration::operator==(const WorldConfiguration& other) const
{
constexpr const float timeStepTolerance = 0.0001f;
return m_enableCcd == other.m_enableCcd
&& m_enableCcdResweep == other.m_enableCcdResweep
&& m_enableActiveActors == other.m_enableActiveActors
&& m_enablePcm == other.m_enablePcm
&& m_kinematicFiltering == other.m_kinematicFiltering
&& m_kinematicStaticFiltering == other.m_kinematicStaticFiltering
&& m_customUserData == other.m_customUserData
&& m_raycastBufferSize == other.m_raycastBufferSize
&& m_sweepBufferSize == other.m_sweepBufferSize
&& m_overlapBufferSize == other.m_overlapBufferSize
&& m_maxCcdPasses == other.m_maxCcdPasses
&& AZ::IsClose(m_maxTimeStep, other.m_maxTimeStep, timeStepTolerance)
&& AZ::IsClose(m_fixedTimeStep, other.m_fixedTimeStep, timeStepTolerance)
&& AZ::IsClose(m_bounceThresholdVelocity, other.m_bounceThresholdVelocity)
&& m_gravity.IsClose(other.m_gravity)
&& m_worldBounds == other.m_worldBounds
;
}
bool WorldConfiguration::operator!=(const WorldConfiguration& other) const
{
return !(*this == other);
}
AZStd::vector<OverlapHit> World::OverlapSphere(float radius, const AZ::Transform& pose,
OverlapFilterCallback filterCallback)
{
SphereShapeConfiguration shapeConfiguration;
shapeConfiguration.m_radius = radius;
OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = &shapeConfiguration;
overlapRequest.m_filterCallback = filterCallback;
return Overlap(overlapRequest);
}
AZStd::vector<OverlapHit> World::OverlapBox(const AZ::Vector3& dimensions, const AZ::Transform& pose,
OverlapFilterCallback filterCallback)
{
BoxShapeConfiguration shapeConfiguration;
shapeConfiguration.m_dimensions = dimensions;
OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = &shapeConfiguration;
overlapRequest.m_filterCallback = filterCallback;
return Overlap(overlapRequest);
}
AZStd::vector<OverlapHit> World::OverlapCapsule(float height, float radius, const AZ::Transform& pose,
OverlapFilterCallback filterCallback)
{
CapsuleShapeConfiguration shapeConfiguration;
shapeConfiguration.m_height = height;
shapeConfiguration.m_radius = radius;
OverlapRequest overlapRequest;
overlapRequest.m_pose = pose;
overlapRequest.m_shapeConfiguration = &shapeConfiguration;
overlapRequest.m_filterCallback = filterCallback;
return Overlap(overlapRequest);
}
Physics::RayCastHit World::SphereCast(float radius, const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
SphereShapeConfiguration shapeConfiguration;
shapeConfiguration.m_radius = radius;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCast(request);
}
AZStd::vector<Physics::RayCastHit> World::SphereCastMultiple(float radius, const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
SphereShapeConfiguration shapeConfiguration;
shapeConfiguration.m_radius = radius;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCastMultiple(request);
}
Physics::RayCastHit World::BoxCast(const AZ::Vector3& boxDimensions, const AZ::Transform& startPose,
const AZ::Vector3& direction, float distance, QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
BoxShapeConfiguration shapeConfiguration;
shapeConfiguration.m_dimensions = boxDimensions;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCast(request);
}
AZStd::vector<Physics::RayCastHit> World::BoxCastMultiple(const AZ::Vector3& boxDimensions, const AZ::Transform& startPose,
const AZ::Vector3& direction, float distance, QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
BoxShapeConfiguration shapeConfiguration;
shapeConfiguration.m_dimensions = boxDimensions;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCastMultiple(request);
}
Physics::RayCastHit World::CapsuleCast(float capsuleRadius, float capsuleHeight, const AZ::Transform& startPose,
const AZ::Vector3& direction, float distance, QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
CapsuleShapeConfiguration shapeConfiguration;
shapeConfiguration.m_height = capsuleHeight;
shapeConfiguration.m_radius = capsuleRadius;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCast(request);
}
AZStd::vector<Physics::RayCastHit> World::CapsuleCastMultiple(float capsuleRadius, float capsuleHeight, const AZ::Transform& startPose,
const AZ::Vector3& direction, float distance, QueryType queryType, AzPhysics::CollisionGroup collisionGroup, FilterCallback filterCallback)
{
CapsuleShapeConfiguration shapeConfiguration;
shapeConfiguration.m_height = capsuleHeight;
shapeConfiguration.m_radius = capsuleRadius;
ShapeCastRequest request;
request.m_distance = distance;
request.m_start = startPose;
request.m_direction = direction;
request.m_shapeConfiguration = &shapeConfiguration;
request.m_queryType = queryType;
request.m_collisionGroup = collisionGroup;
request.m_filterCallback = filterCallback;
return ShapeCastMultiple(request);
}
} // namespace Physics
@@ -1,296 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <functional>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Component/EntityId.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/Configuration/SystemConfiguration.h>
namespace Physics
{
static AZ::Crc32 DefaultPhysicsWorldId = AZ_CRC("AZPhysicalWorld", 0x18f33e24);
static AZ::Crc32 EditorPhysicsWorldId = AZ_CRC("EditorWorld", 0x8d93f191);
class RigidBody;
class WorldBody;
class WorldEventHandler;
class ITriggerEventCallback;
//! Default world configuration.
class WorldConfiguration
{
public:
AZ_CLASS_ALLOCATOR(WorldConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(WorldConfiguration, "{3C87DF50-AD02-4746-B19F-8B7453A86243}")
static void Reflect(AZ::ReflectContext* context);
virtual ~WorldConfiguration() = default;
AZ::Crc32 GetCcdVisibility() const;
AZ::Aabb m_worldBounds = AZ::Aabb::CreateFromMinMax(-AZ::Vector3(1000.f, 1000.f, 1000.f), AZ::Vector3(1000.f, 1000.f, 1000.f));
float m_maxTimeStep = 1.f / 20.f;
float m_fixedTimeStep = AzPhysics::SystemConfiguration::DefaultFixedTimestep;
AZ::Vector3 m_gravity = AZ::Vector3(0.f, 0.f, -9.81f);
void* m_customUserData = nullptr;
AZ::u64 m_raycastBufferSize = 32; //!< Maximum number of hits that will be returned from a raycast.
AZ::u64 m_sweepBufferSize = 32; //!< Maximum number of hits that can be returned from a shapecast.
AZ::u64 m_overlapBufferSize = 32; //!< Maximum number of overlaps that can be returned from an overlap query.
bool m_enableCcd = false; //!< Enables continuous collision detection in the world.
AZ::u32 m_maxCcdPasses = 1; //!< Maximum number of continuous collision detection passes.
bool m_enableCcdResweep = true; //!< Use a more accurate but more expensive continuous collision detection method.
bool m_enableActiveActors = false; //!< Enables pxScene::getActiveActors method.
bool m_enablePcm = true; //!< Enables the persistent contact manifold algorithm to be used as the narrow phase algorithm.
bool m_kinematicFiltering = true; //!< Enables filtering between kinematic/kinematic objects.
bool m_kinematicStaticFiltering = true; //!< Enables filtering between kinematic/static objects.
float m_bounceThresholdVelocity = 2.0f; //!< Relative velocity below which colliding objects will not bounce.
bool operator==(const WorldConfiguration& other) const;
bool operator!=(const WorldConfiguration& other) const;
private:
static bool VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement);
AZ::u32 OnMaxTimeStepChanged();
float GetFixedTimeStepMax() const;
};
//! Callback for unbounded world queries. These are queries which don't require
//! building the entire result vector, and so saves memory for very large numbers of hits.
//! Called with '{ hit }' repeatedly until there are no more hits, then called with '{}', then never called again.
//! Returns 'true' to continue processing more hits, or 'false' otherwise. If the function ever returns
//! 'false', it is unspecified if the finalizing call '{}' occurs.
template<class HitType>
using HitCallback = AZStd::function<bool(AZStd::optional<HitType>&&)>;
//! Physics world.
class World
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::Crc32;
using MutexType = AZStd::recursive_mutex;
AZ_CLASS_ALLOCATOR(World, AZ::SystemAllocator, 0);
AZ_RTTI(World, "{61832612-9F5C-4A2E-8E11-00655A6DDDD2}");
virtual ~World() = default;
virtual void Update(float deltaTime) = 0;
//! Start the simulation process. This will spawn physics jobs.
virtual void StartSimulation(float deltaTime) = 0;
//! Complete the simulation process. This will wait for the simulation jobs to complete, swap the buffers and process events.
virtual void FinishSimulation() = 0;
//! Perform a raycast in the world returning the closest object that intersected.
virtual RayCastHit RayCast(const RayCastRequest& request) = 0;
//! Perform a raycast in the world returning all objects that intersected.
virtual AZStd::vector<Physics::RayCastHit> RayCastMultiple(const RayCastRequest& request) = 0;
//! Perform a shapecast in the world returning the closest object that intersected.
virtual RayCastHit ShapeCast(const ShapeCastRequest& request) = 0;
//! Perform a shapecast in the world returning all objects that intersected.
virtual AZStd::vector<RayCastHit> ShapeCastMultiple(const ShapeCastRequest& request) = 0;
//! Perform a spherecast in the world returning the closest object that intersected.
Physics::RayCastHit SphereCast(float radius,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform a spherecast in the world returning all objects that intersected.
AZStd::vector<Physics::RayCastHit> SphereCastMultiple(float radius,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform a boxcast in the world returning the closest object that intersected.
Physics::RayCastHit BoxCast(const AZ::Vector3& boxDimensions,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform a boxcast in the world returning all objects that intersected.
AZStd::vector<Physics::RayCastHit> BoxCastMultiple(const AZ::Vector3& boxDimensions,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform a capsule in the world returning all objects that intersected.
Physics::RayCastHit CapsuleCast(float capsuleRadius, float capsuleHeight,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform a capsule in the world returning all objects that intersected.
AZStd::vector<Physics::RayCastHit> CapsuleCastMultiple(float capsuleRadius, float capsuleHeight,
const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
QueryType queryType = QueryType::StaticAndDynamic,
AzPhysics::CollisionGroup collisionGroup = AzPhysics::CollisionGroup::All,
FilterCallback filterCallback = nullptr);
//! Perform an overlap query returning all objects that overlapped.
virtual AZStd::vector<OverlapHit> Overlap(const OverlapRequest& request) = 0;
//! Perform an unbounded overlap query, calling the provided callback for each
virtual void OverlapUnbounded(const OverlapRequest& request, const HitCallback<OverlapHit>& cb) = 0;
//! Perform an overlap sphere query returning all objects that overlapped.
AZStd::vector<OverlapHit> OverlapSphere(float radius, const AZ::Transform& pose, OverlapFilterCallback filterCallback = nullptr);
//! Perform an overlap box query returning all objects that overlapped.
AZStd::vector<OverlapHit> OverlapBox(const AZ::Vector3& dimensions, const AZ::Transform& pose, OverlapFilterCallback filterCallback = nullptr);
//! Perform an overlap capsule query returning all objects that overlapped.
AZStd::vector<OverlapHit> OverlapCapsule(float height, float radius, const AZ::Transform& pose, OverlapFilterCallback filterCallback = nullptr);
//! Registers a pair of world bodies for which collisions should be suppressed.
virtual void RegisterSuppressedCollision(const WorldBody& body0, const WorldBody& body1) = 0;
//! Unregisters a pair of world bodies for which collisions should be suppressed.
virtual void UnregisterSuppressedCollision(const WorldBody& body0, const WorldBody& body1) = 0;
virtual void AddBody(WorldBody& body) = 0;
virtual void RemoveBody(WorldBody& body) = 0;
virtual AZ::Crc32 GetNativeType() const { return AZ::Crc32(); }
virtual void* GetNativePointer() const { return nullptr; }
virtual void SetSimFunc(std::function<void(void*)> func) = 0;
virtual void SetEventHandler(WorldEventHandler* eventHandler) = 0;
virtual AZ::Vector3 GetGravity() const = 0;
virtual void SetGravity(const AZ::Vector3& gravity) = 0;
virtual void SetMaxDeltaTime(float maxDeltaTime) = 0;
virtual void SetFixedDeltaTime(float fixedDeltaTime) = 0;
virtual void DeferDelete(AZStd::unique_ptr<WorldBody> worldBody) = 0;
//! @brief Similar to SetEventHandler, relevant for Touch Bending.
//!
//! SetEventHandler is useful to catch onTrigger events when the bodies
//! involved were created with the standard physics Components attached to
//! entities. On the other hand, this method was added since Touch Bending, and it is useful
//! for the touch bending simulator to catch onTrigger events of Actors that
//! don't have valid AZ:EntityId.
//!
//! @param triggerCallback Pointer to the callback object that will get the On
//! @returns Nothing.
virtual void SetTriggerEventCallback(ITriggerEventCallback* triggerCallback) = 0;
//! Returns this world's ID.
virtual AZ::Crc32 GetWorldId() const = 0;
};
using WorldRequestBus = AZ::EBus<World>;
using WorldRequests = World;
//! Broadcasts notifications for a specific Physics::World.
//! This bus is addressed on the id of the world.
//! Subscribe to the bus using Physics::DefaultPhysicsWorldId for the default world,
//! or Physics::EditorPhysicsWorldId for the editor world.
class WorldNotifications
: public AZ::EBusTraits
{
public:
enum PhysicsTickOrder
{
Physics = 0, //!< The physics system itself. Should always be first.
Animation = 100, //!< Animation system (ragdolls).
Components = 200, //!< C++ components (force region).
Scripting = 300, //!< Scripting systems (script canvas).
Audio = 400, //!< Audio systems (occlusion).
Default = 1000 //!< All other systems (Game code).
};
virtual ~WorldNotifications() = default;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::MultipleAndOrdered;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::Crc32;
using MutexType = AZStd::recursive_mutex;
//! Broadcast before each world simulation tick.
//! Each tick may include multiple fixed timestep subticks. For example, if the fixed timestep was set at 10ms,
//! and a game tick of 25ms occurred, 2 fixed timestep subticks would be performed this tick, and the
//! remaining 5ms would be accumulated for the subsequent tick. So, in this example, the events fired would be:
//! OnPrePhysicsTick (for the whole 25ms tick)
//! OnPrePhysicsSubtick (for the first 10ms subtick)
//! OnPostPhysicsSubtick (for the first 10ms subtick)
//! OnPrePhysicsSubtick (for the second 10ms subtick)
//! OnPostPhysicsSubtick (for the second 10ms subtick)
//! OnPostPhysicsTick (for the whole 25ms tick)
//! @param deltaTime The duration of the tick as a whole (which may contain multiple fixed timestep subticks).
virtual void OnPrePhysicsTick([[maybe_unused]] float deltaTime) {}
//! Broadcast before each fixed timestep subtick.
//! @param fixedDeltaTime The duration for fixed timestep subticks.
virtual void OnPrePhysicsSubtick([[maybe_unused]] float fixedDeltaTime) {}
//! Broadcast after each fixed timestep subtick.
//! @param fixedDeltaTime The duration for fixed timestep subticks.
virtual void OnPostPhysicsSubtick([[maybe_unused]] float fixedDeltaTime) {}
//! Broadcast after each world simulation tick.
//! Each tick may include multiple fixed timestep subticks.
//! @param deltaTime The duration of the tick as a whole (which may contain multiple fixed timestep subticks).
virtual void OnPostPhysicsTick([[maybe_unused]] float deltaTime) {}
//! Event fired when the gravity for a world is changed.
//! @param gravity The world's new value for gravity acceleration.
virtual void OnGravityChanged([[maybe_unused]] const AZ::Vector3& gravity) {}
//! Specified the order in which a handler receives WorldNotification events.
//! Users subscribing to this bus can override this function to change
//! the order events are received relative to other systems.
//! @return a value specifying this handler'S relative order.
virtual int GetPhysicsTickOrder() { return Default; }
//! Determines the order in which handlers receive events.
struct BusHandlerOrderCompare
{
//! Compare function used to control physics update order.
//! @param left an instance of the handler to compare.
//! @param right another instance of the handler to compare.
//! @return True if the priority of left is greater than right, false otherwise.
AZ_FORCE_INLINE bool operator()(WorldNotifications* left, WorldNotifications* right) const
{
return left->GetPhysicsTickOrder() < right->GetPhysicsTickOrder();
}
};
};
using WorldNotificationBus = AZ::EBus<WorldNotifications>;
} // namespace Physics
@@ -1,99 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Component/Entity.h>
#include <AzFramework/Physics/Casts.h>
namespace Physics
{
class WorldBody;
class World;
struct RayCastRequest;
struct RayCastHit;
class WorldBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(WorldBodyConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(WorldBodyConfiguration, "{6EEB377C-DC60-4E10-AF12-9626C0763B2D}");
WorldBodyConfiguration() = default;
WorldBodyConfiguration(const WorldBodyConfiguration& settings) = default;
virtual ~WorldBodyConfiguration() = default;
static void Reflect(AZ::ReflectContext* context);
// Basic initial settings.
AZ::Vector3 m_position = AZ::Vector3::CreateZero();
AZ::Quaternion m_orientation = AZ::Quaternion::CreateIdentity();
AZ::Vector3 m_scale = AZ::Vector3::CreateOne();
// Entity/object association.
AZ::EntityId m_entityId;
void* m_customUserData = nullptr;
// For debugging/tracking purposes only.
AZStd::string m_debugName;
};
class WorldBody
{
public:
AZ_CLASS_ALLOCATOR(WorldBody, AZ::SystemAllocator, 0);
AZ_RTTI(WorldBody, "{4F1D9B44-FC21-4E93-83F0-41B6A78D9B4B}");
friend class World;
public:
WorldBody() = default;
WorldBody(const WorldBodyConfiguration& /*settings*/) {};
virtual ~WorldBody() = default;
virtual AZ::EntityId GetEntityId() const = 0;
void SetUserData(void* userData);
template<typename T>
T* GetUserData() const;
virtual Physics::World* GetWorld() const = 0;
virtual AZ::Transform GetTransform() const = 0;
virtual void SetTransform(const AZ::Transform& transform) = 0;
virtual AZ::Vector3 GetPosition() const = 0;
virtual AZ::Quaternion GetOrientation() const = 0;
virtual AZ::Aabb GetAabb() const = 0;
virtual Physics::RayCastHit RayCast(const RayCastRequest& request) = 0;
virtual AZ::Crc32 GetNativeType() const = 0;
virtual void* GetNativePointer() const = 0;
virtual void AddToWorld(Physics::World&) = 0;
virtual void RemoveFromWorld(Physics::World&) = 0;
private:
void* m_customUserData = nullptr;
};
template<typename T>
T* WorldBody::GetUserData() const
{
return static_cast<T*>(m_customUserData);
}
} // namespace Physics
@@ -13,13 +13,17 @@
#pragma once
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Physics/Casts.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
namespace AzPhysics
{
struct SimulatedBody;
}
namespace Physics
{
class WorldBody;
//! Requests for generic physical world bodies
class WorldBodyRequests
: public AZ::ComponentBus
@@ -36,11 +40,11 @@ namespace Physics
//! Retrieves the AABB(aligned-axis bounding box) for this body
virtual AZ::Aabb GetAabb() const = 0;
//! Retrieves current WorldBody* for this body. Note: Do not hold a reference to Physics::WorldBody* as could be deleted
virtual Physics::WorldBody* GetWorldBody() = 0;
//! Retrieves current WorldBody* for this body. Note: Do not hold a reference to AzPhysics::SimulatedBody* as could be deleted
virtual AzPhysics::SimulatedBody* GetWorldBody() = 0;
//! Perform a single-object raycast against this body
virtual Physics::RayCastHit RayCast(const Physics::RayCastRequest& request) = 0;
virtual AzPhysics::SceneQueryHit RayCast(const AzPhysics::RayCastRequest& request) = 0;
};
using WorldBodyRequestBus = AZ::EBus<WorldBodyRequests>;
@@ -1,81 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Math/Vector3.h>
namespace Physics
{
class WorldBody;
class Shape;
/// Trigger event raised when an object enters/exits a trigger shape.
struct TriggerEvent
{
Physics::WorldBody* m_triggerBody; ///< The trigger body
Physics::Shape* m_triggerShape; ///< The trigger shape
Physics::WorldBody* m_otherBody; ///< The other body that entered the trigger
Physics::Shape* m_otherShape; ///< The other shape that entered the trigger
};
/// Stores information about the contacts between two overlapping shapes.
struct Contact
{
AZ_CLASS_ALLOCATOR(Contact, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(Contact, "{D7439508-ED10-4395-9D48-1FC3D7815361}");
AZ::Vector3 m_position; ///< The position of the contact
AZ::Vector3 m_normal; ///< The normal of the contact
AZ::Vector3 m_impulse; ///< The impulse force applied to separate the bodies
AZ::u32 m_internalFaceIndex01; ///< Intenal face index of the first shape
AZ::u32 m_internalFaceIndex02; ///< Internal face index of the second shape
float m_separation; ///< The separation
};
/// A collision event raised when two objects, neither of which can be triggers, overlap.
struct CollisionEvent
{
AZ_CLASS_ALLOCATOR(CollisionEvent, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(CollisionEvent, "{7602AA36-792C-4BDC-BDF8-AA16792151A3}");
Physics::WorldBody* m_body1; ///< The first body
Physics::Shape* m_shape1; ///< The shape on the first body
Physics::WorldBody* m_body2; ///< The second body
Physics::Shape* m_shape2; ///< The shape on the second body
AZStd::vector<Contact> m_contacts; ///< The contacts between the two shapes
};
/// Implement this interface and call SetEventHandler on Physics::World
/// to receive events from that world.
/// CActionGame is the default handler for the default physics world which
/// translates these events into bus events.
class WorldEventHandler
{
public:
/// Raised when an object starts overlapping with a trigger shape.
virtual void OnTriggerEnter(const TriggerEvent& triggerEvent) = 0;
/// Raised when an object stops overlapping with a trigger shape.
virtual void OnTriggerExit(const TriggerEvent& triggerEvent) = 0;
/// Raised when two shapes come into contact with each other.
virtual void OnCollisionBegin(const CollisionEvent& collisionEvent) = 0;
/// Raised when two shapes continue contact with each other.
virtual void OnCollisionPersist(const CollisionEvent& collisionEvent) = 0;
/// Raised when two shapes stop contacting each other.
virtual void OnCollisionEnd(const CollisionEvent& collisionEvent) = 0;
};
} //namespace Physics
@@ -12,123 +12,133 @@
#include <AzFramework/ProjectManager/ProjectManager.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Platform.h>
#include <AzCore/Settings/CommandLine.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/AzFramework_Traits_Platform.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <AzFramework/Engine/Engine.h>
namespace AzFramework
namespace AzFramework::ProjectManager
{
namespace ProjectManager
AZStd::tuple<AZ::IO::FixedMaxPath, AZ::IO::FixedMaxPath> FindProjectAndEngineRootPaths(const int argc, char* argv[])
{
// Check for a project name, if not found, attempt to launch project manager and shut down
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[])
bool ownsAllocator = false;
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
// If we were able to locate a path to a project, we're done
if (HasProjectPath(argc, argv))
{
return ProjectPathCheckResult::ProjectPathFound;
}
if (LaunchProjectManager())
{
AZ_TracePrintf("ProjectManager", "Project Manager launched successfully, requesting exit.");
return ProjectPathCheckResult::ProjectManagerLaunched;
}
AZ_Error("ProjectManager", false, "Project Manager failed to launch and no project selected!");
return ProjectPathCheckResult::ProjectManagerLaunchFailed;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
ownsAllocator = true;
}
bool HasProjectPath(const int argc, char* argv[])
AZ::IO::FixedMaxPath projectRootPath;
AZ::IO::FixedMaxPath engineRootPath;
{
bool hasProjectPath = false;
bool ownsAllocator = false;
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
ownsAllocator = true;
}
{
AZ::CommandLine commandLine;
commandLine.Parse(argc, argv);
AZ::SettingsRegistryImpl settingsRegistry;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(settingsRegistry, commandLine, false);
auto sysGameFolderKey = AZ::SettingsRegistryInterface::FixedValueString(
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath registryPath;
hasProjectPath = settingsRegistry.Get(registryPath.Native(), sysGameFolderKey);
}
if (ownsAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
return hasProjectPath;
// AZ::CommandLine and SettingsRegistryImpl is in block scope to make sure
// that the allocated memory is cleaned up before destroying the SystemAllocator
// at the end of the function
AZ::CommandLine commandLine;
commandLine.Parse(argc, argv);
AZ::SettingsRegistryImpl settingsRegistry;
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_Bootstrap(settingsRegistry);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {});
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(settingsRegistry, commandLine, false);
engineRootPath = AZ::SettingsRegistryMergeUtils::FindEngineRoot(settingsRegistry);
projectRootPath = AZ::SettingsRegistryMergeUtils::FindProjectRoot(settingsRegistry);
}
if (ownsAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
return AZStd::make_tuple(projectRootPath, engineRootPath);
}
// Check for a project name, if not found, attempt to launch project manager and shut down
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[])
{
auto [projectRootPath, engineRootPath] = FindProjectAndEngineRootPaths(argc, argv);
// If we were able to locate a path to a project, we're done
if (!projectRootPath.empty())
{
return ProjectPathCheckResult::ProjectPathFound;
}
bool LaunchProjectManager()
if (LaunchProjectManager(engineRootPath))
{
bool launchSuccess = false;
AZ_TracePrintf("ProjectManager", "Project Manager launched successfully, requesting exit.");
return ProjectPathCheckResult::ProjectManagerLaunched;
}
AZ_Error("ProjectManager", false, "Project Manager failed to launch and no project selected!");
return ProjectPathCheckResult::ProjectManagerLaunchFailed;
}
bool LaunchProjectManager([[maybe_unused]] const AZ::IO::FixedMaxPath& engineRootPath)
{
bool launchSuccess = false;
#if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER)
bool ownsSystemAllocator = false;
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
ownsSystemAllocator = true;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
{
const char projectsScript[] = "projects.py";
AZ_Warning("ProjectManager", false, "No project provided - launching project selector.");
AZ::IO::FixedMaxPath enginePath = Engine::FindEngineRoot();
if (enginePath.empty())
{
AZ_Error("ProjectManager", false, "Couldn't find engine root");
return false;
}
auto projectManagerPath = enginePath / "scripts" / "project_manager";
if (!AZ::IO::SystemFile::Exists((projectManagerPath / projectsScript).c_str()))
{
AZ_Error("ProjectManager", false, "%s not found at %s!", projectsScript, projectManagerPath.c_str());
return false;
}
char executablePath[AZ_MAX_PATH_LEN];
AZ::Utils::GetExecutablePathReturnType result = AZ::Utils::GetExecutablePath(executablePath, AZ_MAX_PATH_LEN);
auto exeFolder = AZ::IO::PathView(executablePath).ParentPath().Filename().Native();
AZStd::fixed_string<8> debugOption;
if (exeFolder == "debug")
{
// We need to use the debug version of the python interpreter to load up our debug version of our libraries which work with the debug version of QT living in this folder
debugOption = "debug ";
}
AZ::IO::FixedMaxPath pythonPath = enginePath / "python";
pythonPath /= AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL;
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s %s%s --executable_path=%s --parent_pid=%" PRId64, pythonPath.Native().c_str(),
debugOption.c_str(), (projectManagerPath / projectsScript).c_str(), executablePath, AZ::Platform::GetCurrentProcessId());
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = cmdPath;
processLaunchInfo.m_showWindow = false;
launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
if(ownsSystemAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
#endif // #if defined(AZ_FRAMEWORK_USE_PROJECT_MANAGER)
return launchSuccess;
bool ownsSystemAllocator = false;
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
ownsSystemAllocator = true;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
} // ProjectManager
} // AzFramework
{
const char projectsScript[] = "projects.py";
AZ_Warning("ProjectManager", false, "No project provided - launching project selector.");
if (engineRootPath.empty())
{
AZ_Error("ProjectManager", false, "Couldn't find engine root");
return false;
}
auto projectManagerPath = engineRootPath / "scripts" / "project_manager";
if (!AZ::IO::SystemFile::Exists((projectManagerPath / projectsScript).c_str()))
{
AZ_Error("ProjectManager", false, "%s not found at %s!", projectsScript, projectManagerPath.c_str());
return false;
}
AZ::IO::FixedMaxPathString executablePath;
AZ::Utils::GetExecutablePathReturnType result = AZ::Utils::GetExecutablePath(executablePath.data(), executablePath.capacity());
if (result.m_pathStored != AZ::Utils::ExecutablePathResult::Success)
{
AZ_Error("ProjectManager", false, "Could not determine executable path!");
return false;
}
AZ::IO::FixedMaxPath parentPath(executablePath.c_str());
auto exeFolder = parentPath.ParentPath();
AZStd::fixed_string<8> debugOption;
auto lastSep = exeFolder.Native().find_last_of(AZ_CORRECT_FILESYSTEM_SEPARATOR);
if (lastSep != AZStd::string_view::npos)
{
exeFolder = exeFolder.Native().substr(lastSep + 1);
}
if (exeFolder == "debug")
{
// We need to use the debug version of the python interpreter to load up our debug version of our libraries which work with the debug version of QT living in this folder
debugOption = "debug ";
}
AZ::IO::FixedMaxPath pythonPath = engineRootPath / "python";
pythonPath /= AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL;
auto cmdPath = AZ::IO::FixedMaxPathString::format("%s %s%s --executable_path=%s --parent_pid=%" PRId64, pythonPath.Native().c_str(),
debugOption.c_str(), (projectManagerPath / projectsScript).c_str(), executablePath.c_str(), AZ::Platform::GetCurrentProcessId());
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_commandlineParameters = cmdPath;
processLaunchInfo.m_showWindow = false;
launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
if (ownsSystemAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
#endif // #if defined(AZ_FRAMEWORK_USE_PROJECT_MANAGER)
return launchSuccess;
}
} // AzFramework::ProjectManager
@@ -11,33 +11,18 @@
*/
#pragma once
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzCore/std/string/string_view.h>
#include <AzCore/IO/Path/Path_fwd.h>
namespace AzFramework
namespace AzFramework::ProjectManager
{
namespace ProjectManager
enum class ProjectPathCheckResult
{
constexpr AZ::IO::SystemFile::SizeType MaxBootstrapFileSize = 1024 * 10;
// Check if any project name can be found anywhere
bool HasProjectPath(const int argc, char* argv[]);
// Check if any project name can be found on the command line
bool HasCommandLineProjectName(const int argc, char* argv[]);
// Check if a relative project is being used through bootstrap
bool HasBootstrapProjectName(AZStd::string_view projectFolder = {});
// Search content for project name key
bool ContentHasProjectName(AZStd::fixed_string< MaxBootstrapFileSize>& bootstrapString);
enum class ProjectPathCheckResult
{
ProjectManagerLaunchFailed = -1,
ProjectManagerLaunched = 0,
ProjectPathFound = 1
};
// Check for a project name, if not found, attempts to launch project manager and returns false
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[]);
// Attempt to Launch the project manager. Requires locating the engine root, project manager script, and python.
bool LaunchProjectManager();
}
} // AzFramework
ProjectManagerLaunchFailed = -1,
ProjectManagerLaunched = 0,
ProjectPathFound = 1
};
// Check for a project name, if not found, attempts to launch project manager and returns false
ProjectPathCheckResult CheckProjectPathProvided(const int argc, char* argv[]);
// Attempt to Launch the project manager. Requires locating the engine root, project manager script, and python.
bool LaunchProjectManager(const AZ::IO::FixedMaxPath& engineRootPath);
} // AzFramework::ProjectManager
@@ -296,6 +296,95 @@ namespace AzFramework
namespace Internal
{
static AZStd::string PrintLuaValue(lua_State* lua, int stackIdx, int depth = 0)
{
constexpr int MaxDepth = 4;
if (depth > MaxDepth)
{
return "";
}
const int elementType = lua_type(lua, stackIdx);
switch (elementType)
{
case LUA_TSTRING:
return lua_tostring(lua, stackIdx);
case LUA_TBOOLEAN:
return lua_toboolean(lua, stackIdx) ? "true" : "false";
case LUA_TNUMBER:
return AZStd::to_string(lua_tonumber(lua, stackIdx));
case LUA_TTABLE:
{
AZStd::string tableStr = "{";
AZStd::string keyValuePairs;
int keyCount = 0;
// check if this lua table contains a meta-table
if(lua_getmetatable(lua, stackIdx))
{
keyValuePairs += "Meta";
keyValuePairs += PrintLuaValue(lua, lua_gettop(lua), depth+1);
lua_pop(lua, 1);
keyValuePairs += " ";
++keyCount;
}
if (depth < MaxDepth)
{
lua_pushnil(lua);
bool tableKeyExists = lua_next(lua, stackIdx);
while(tableKeyExists)
{
const int valueIndex = lua_gettop(lua);
const int keyIndex = valueIndex-1;
const AZStd::string key = PrintLuaValue(lua, keyIndex, depth+1);
const AZStd::string value = PrintLuaValue(lua, valueIndex, depth+1);
keyValuePairs += key + AZStd::string("=")+value;
++keyCount;
lua_pop(lua, 1); // removes 'value'; keeps 'key' for next iteration
tableKeyExists = lua_next(lua, stackIdx);
if(tableKeyExists)
{
keyValuePairs += " ";
}
}
}
tableStr += keyValuePairs.length() < 1024 ? keyValuePairs : AZStd::string::format("too many keys (%i)!", keyCount);
tableStr += "}";
return tableStr;
}
default:
return lua_typename(lua, elementType);
}
}
#pragma warning( push )
#pragma warning( disable : 4505 ) // StackDump is useful to debug the lua stack. Disable warning about this method being unused.
//=========================================================================
// DebugPrintStack
// Prints the Lua stack starting from the bottom.
//=========================================================================
static void DebugPrintStack(lua_State* lua, const AZStd::string& prefix = "")
{
AZStd::string dump = prefix;
const int stackSize = lua_gettop(lua);
for (int stackIdx = 1; stackIdx <= stackSize; ++stackIdx)
{
dump += PrintLuaValue(lua, stackIdx);
dump += " "; // add separator
}
AZ_Warning("ScriptComponent", false, "Stack Dump: '%s'", dump.c_str());
}
#pragma warning( pop )
//=========================================================================
// Properties__IndexFindSubtable
//=========================================================================
@@ -373,7 +462,7 @@ namespace AzFramework
// and script are not in sync and we added new properties.
lua_getmetatable(lua, -2); // get the metatable which will be the top property table
int entityProperties = lua_gettop(lua);
if (lua_getmetatable(lua, -1) == 0) // get the matateble of the property which will be the original table
if (lua_getmetatable(lua, -1) == 0) // get the metatable of the property which will be the original table
{
// we are looking at top level properties
lua_pushvalue(lua, -2); // copy the key
@@ -510,6 +599,16 @@ namespace AzFramework
m_script = script;
}
AZ::ScriptProperty* ScriptComponent::GetScriptProperty(const char* propertyName)
{
return m_properties.GetProperty(propertyName);
}
const AZ::ScriptProperty* ScriptComponent::GetNetworkedScriptProperty(const char* propertyName) const
{
return m_netBindingTable->FindScriptProperty(propertyName);
}
void ScriptComponent::Init()
{
// Grab the script context
@@ -681,17 +780,23 @@ namespace AzFramework
// Point the __index of the Script table to itself
// because it will be used as a metatable
lua_pushliteral(lua, "__index");
lua_pushvalue(lua, -2);
lua_rawset(lua, -3);
// Stack = ScriptRootTable
lua_pushliteral(lua, "__index"); // Stack = ScriptRootTable __index
lua_pushvalue(lua, -2); // Stack = ScriptRootTable __index CopyOfScriptRootTable
lua_pushlstring(lua, m_properties.m_name.c_str(), m_properties.m_name.length()); // load Property table name
lua_rawget(lua, -2);
// raw set: t[k] = v, where t is the value at the given index, v is the value at the top of the stack, and k is the value just below the top. Both key and value are popped off the stack.
// ie: ScriptRootTable[__index] = CopyOfScriptRootTable
// Since __index value is a table and not a function, the final result is the result of indexing this table with key. However, this indexing is regular, not raw, and therefore can trigger another metamethod.
lua_rawset(lua, -3); // Stack = ScriptRootTable
// load Property table name
lua_pushlstring(lua, m_properties.m_name.c_str(), m_properties.m_name.length()); // Stack = ScriptRootTable "Properties"
lua_rawget(lua, -2); // Stack = ScriptRootTable ThisScriptPropertiesTable
if (lua_istable(lua, -1))
{
// This property table will be used a metatable from all instances
// set the __index so we can read values in case we change the script
// after we export the component (so the properties will not the default value)
// after we export the component
lua_pushliteral(lua, "__index");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__Index, 1);
@@ -730,7 +835,7 @@ namespace AzFramework
{
const char* tableName = lua_tolstring(lua, -2, nullptr);
if (strncmp(tableName, "__", 2) == 0 || // skip metatables
strcmp(tableName, propertyTableName) == 0 || // check if this is NOT the property table
strcmp(tableName, propertyTableName) == 0 || // Skip the Properties table
strcmp(tableName, ScriptComponent::NetRPCFieldName) == 0) // Want to skip the RPC table as well
{
break;
@@ -786,12 +891,11 @@ namespace AzFramework
LSV_BEGIN(lua, -1);
AZ_Error("Script", lua_istable(lua, -1), "%s", "Script did not return a table!");
int baseTableIndex = lua_gettop(lua);
int baseStackIndex = lua_gettop(lua);
// Stack: base
lua_pushlstring(lua, m_properties.m_name.c_str(), m_properties.m_name.length());
lua_rawget(lua, baseTableIndex);
// Stack: ScriptRootTable
lua_pushlstring(lua, m_properties.m_name.c_str(), m_properties.m_name.length()); // Stack: ScriptRootTable "Properties"
lua_rawget(lua, baseStackIndex); // Stack: ScriptRootTable PropertiesTable
AZ_Error("Script", lua_istable(lua, -1) || lua_isnil(lua, -1), "We should have the %s table for properties!", m_properties.m_name.c_str());
int basePropertyTable = -1;
if (lua_istable(lua, -1))
@@ -799,31 +903,30 @@ namespace AzFramework
basePropertyTable = lua_gettop(lua);
}
// Stack: base, properties
lua_createtable(lua, 0, 1); // Create entity table;
int entityTableIndex = lua_gettop(lua);
int entityStackIndex = lua_gettop(lua);
// Stack: base, properties, entity
// Stack: ScriptRootTable PropertiesTable EntityTable
// Create our network binding.
CreateNetworkBindingTable(baseTableIndex, entityTableIndex);
CreateNetworkBindingTable(baseStackIndex, entityStackIndex);
if (basePropertyTable > -1) // if property table exists
{
CreatePropertyGroup(m_properties, basePropertyTable, lua_gettop(lua), basePropertyTable, true);
// Stack: ScriptRootTable PropertiesTable EntityTable{ PropertiesTable{__index __newIndex Meta{CopyOfPropertiesTable}} }
}
// replicate other tables to make sure we have table per instance.
CopyAndHookEntityTables(lua, baseTableIndex, lua_gettop(lua), m_properties.m_name.c_str());
CopyAndHookEntityTables(lua, baseStackIndex, lua_gettop(lua), m_properties.m_name.c_str());
// set my entity id
lua_pushliteral(lua, "entityId");
AZ::ScriptValue<AZ::EntityId>::StackPush(lua, GetEntityId());
lua_rawset(lua, -3);
lua_pushliteral(lua, "entityId"); // Stack: ScriptRootTable PropertiesTable EntityTable{ PropertiesTable{__index __newIndex Meta{CopyOfPropertiesTable}} } "entityId"
AZ::ScriptValue<AZ::EntityId>::StackPush(lua, GetEntityId()); // Stack: ScriptRootTable PropertiesTable EntityTable{ PropertiesTable{__index __newIndex Meta{CopyOfPropertiesTable}} } "entityId" userdata
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable{ PropertiesTable{__index __newIndex Meta{CopyOfPropertiesTable}} entityId }
// set the base metatable
lua_pushvalue(lua, baseTableIndex); // copy the "Base table"
lua_pushvalue(lua, baseStackIndex); // copy the "Base table"
lua_setmetatable(lua, -2); // set the scriptTable as a metatable for the entity table
// Keep the entity table in the registry
@@ -836,7 +939,7 @@ namespace AzFramework
// call OnActivate
lua_pushliteral(lua, "OnActivate");
lua_rawget(lua, baseTableIndex); // ScriptTable[OnActivate]
lua_rawget(lua, baseStackIndex); // ScriptTable[OnActivate]
if (lua_isfunction(lua, -1))
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Script, "OnActivate");
@@ -894,11 +997,11 @@ namespace AzFramework
// CreateNetworkBindingTable
// [6/27/2016]
//=========================================================================
void ScriptComponent::CreateNetworkBindingTable(int baseTableStack, int entityTableStack)
void ScriptComponent::CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex)
{
if (m_netBindingTable)
{
m_netBindingTable->CreateNetworkBindingTable(m_context, baseTableStack, entityTableStack);
m_netBindingTable->CreateNetworkBindingTable(m_context, baseStackIndex, entityStackIndex);
}
}
@@ -916,21 +1019,26 @@ namespace AzFramework
if (isRoot)
{
// this is the root table (properties) it will be used as properties for all sub tables
lua_pushliteral(lua, "__index");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__Index, 1);
lua_rawset(lua, -3);
// Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {}
// This is the root table (properties) it will be used as properties for all sub tables
// ScriptComponents can share the same lua script asset, but each instance's Properties table needs to be unique.
// This way the script can change a property at runtime and not affect the other ScriptComponents which are using the same script.
// For normal properties we will create new variable instances, but NetSynched variables aren't stored in Lua, and instead
// are retrieved using the __index and __newIndex metamethods.
// Ensure that this instance of Properties table has the proper __index and __newIndex metamethods.
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
lua_pushliteral(lua, "__index"); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index
lua_pushlightuserdata(lua, m_netBindingTable); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index m_netBinding
lua_pushcclosure(lua, &Internal::Properties__Index, 1); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
lua_pushliteral(lua, "__newindex");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_rawset(lua, -3);
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
lua_pushvalue(lua, metatableIndex);
lua_setmetatable(lua, -2);
metatableIndex = lua_gettop(lua); // this should the metatable for all subtables
metatableIndex = lua_gettop(lua); // This will be the metatable for all subtables
}
else
{
@@ -947,35 +1055,39 @@ namespace AzFramework
lua_pushlstring(lua, prop->m_name.c_str(), prop->m_name.length());
lua_rawget(lua, propertyGroupTableIndex);
// Stack: ... SomePropertyInThePropertiesTable. This may be any basic lua type (number, string, table etc)
if (lua_istable(lua, -1))
{
bool isPropertyHandled = false;
bool isNetworkedProperty = false;
AZ::ScriptDataContext stackContext;
// If we find a table value. We want to inspect it for information.
if (m_context->ReadStack(stackContext))
{
lua_pushliteral(lua, "netSynched");
lua_rawget(lua, -2);
// check if the current property, which is a table, has a sub-table called "netSynched"
lua_pushliteral(lua, "netSynched"); // Stack: ... SomePropertyInThePropertiesTable netSynched
lua_rawget(lua, -2); // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable/nil
if (stackContext.IsTable(-1))
{
AZ::ScriptDataContext networkTableContext;
if (stackContext.InspectTable(-1, networkTableContext))
if (stackContext.InspectTable(-1, networkTableContext)) // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable NetSynchedSubTable nil nil
{
isPropertyHandled = m_netBindingTable->RegisterDataSet(networkTableContext, prop);
// RegisterDataSet will make sure our __NewIndex function callback will be triggered whenever modifying netSynched Properties.
//isNetworkedProperty = true;
isNetworkedProperty = m_netBindingTable->RegisterDataSet(networkTableContext, prop);
}
}
// Network binding table
lua_pop(lua, 1);
lua_pop(lua, 1); // Stack: ... SomePropertyInThePropertiesTable
}
// Property name table
// Pop this PropertiesTable's property
lua_pop(lua, 1);
// If the property is networked, we don't want to copy it over into the table.
if (isPropertyHandled)
if (isNetworkedProperty)
{
continue;
}
@@ -1016,7 +1128,7 @@ namespace AzFramework
lua_remove(lua, childPropertyGroupIndex);
}
lua_rawset(lua, parentIndex); // set the table into the parent table
lua_rawset(lua, parentIndex); // Stack: ScriptRootTable PropertiesTable EntityTable{ PropertiesTable{Meta{__index __newIndex}} }
}
//=========================================================================
@@ -102,6 +102,9 @@ namespace AzFramework
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", NetBindable);
/// \red ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
ScriptComponent();
~ScriptComponent();
@@ -111,6 +114,10 @@ namespace AzFramework
const AZ::Data::Asset<AZ::ScriptAsset>& GetScript() const { return m_script; }
void SetScript(const AZ::Data::Asset<AZ::ScriptAsset>& script);
// Methods used for unit tests
AZ::ScriptProperty* GetScriptProperty(const char* propertyName);
const AZ::ScriptProperty* GetNetworkedScriptProperty(const char* propertyName) const;
protected:
ScriptComponent(const ScriptComponent&) = delete;
//////////////////////////////////////////////////////////////////////////
@@ -145,18 +152,15 @@ namespace AzFramework
void CreateEntityTable();
void DestroyEntityTable();
void CreateNetworkBindingTable(int baseTableIndex, int entityTableIndex);
void CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex);
void CreatePropertyGroup(const ScriptPropertyGroup& group, int prototypeParentIndex, int parentIndex, int metatableIndex, bool isRoot);
/// \red ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
void CreatePropertyGroup(const ScriptPropertyGroup& group, int propertyGroupTableIndex, int parentIndex, int metatableIndex, bool isRoot);
AZ::ScriptContext* m_context; ///< Context in which the script will be running
AZ::ScriptContextId m_contextId; ///< Id of the script context.
AZ::Data::Asset<AZ::ScriptAsset> m_script; ///< Reference to the script asset used for this component.
int m_table; ///< Cached table index
ScriptPropertyGroup m_properties; ///< List with all properties that were tweaked in the editor and should override values in the m_scourceScriptName class inside m_script.
ScriptPropertyGroup m_properties; ///< List with all properties that were tweaked in the editor and should override values in the m_sourceScriptName class inside m_script.
ScriptNetBindingTable* m_netBindingTable; ///< Table that will hold our networked script values, and manage callbacks
};
} // namespace AZ
@@ -1239,7 +1239,13 @@ namespace AzFramework
}
return canProxyExecute;
}
}
const AZ::ScriptProperty* ScriptNetBindingTable::FindScriptProperty(const AZStd::string& name) const
{
const NetworkedTableValue* networkedTableValue = FindTableValue(name);
return networkedTableValue ? networkedTableValue->GetShimmedScriptProperty() : nullptr;
}
void ScriptNetBindingTable::AssignDataSets()
{
@@ -43,7 +43,7 @@ namespace AzFramework
friend class ScriptComponentReplicaChunk;
friend class ScriptPropertyDataSet;
// Helper struct to keep track of a a ScriptConctext
// Helper struct to keep track of a a ScriptContext
// and the entityTableReference. Mainly used for
// calling in to functions in LUA where we want
// to push in the table reference as the first parameter
@@ -117,6 +117,8 @@ namespace AzFramework
bool AssignValue(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
bool InspectValue(AZ::ScriptContext* scriptContext) const;
// Methods used for unit tests
const AZ::ScriptProperty* GetShimmedScriptProperty() const { return m_shimmedScriptProperty; }
private:
// This value will be used if we have a networked property, but don't have a valid chunk yet.
@@ -202,6 +204,10 @@ namespace AzFramework
void OnPropertyUpdate(AZ::ScriptProperty*const& scriptProperty, const GridMate::TimeContext& tc);
bool OnInvokeRPC(AZStd::string functionName, AZStd::vector< AZ::ScriptProperty*> properties, const GridMate::RpcContext& rpcContext);
// Methods used for unit tests
const AZ::ScriptProperty* FindScriptProperty(const AZStd::string& name) const;
private:
void RegisterMetaTableCache();
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzFramework
{
//! Notifications send when the root spawnable updates. Events will always be called from the main thread.
class RootSpawnableNotifications
: public AZ::EBusTraits
{
public:
virtual ~RootSpawnableNotifications() = default;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const bool EnableEventQueue = true;
using MutexType = AZStd::recursive_mutex;
//! Called when the root spawnable has been assigned a new value. This may be called several times without a call to release
//! in between.
//! @param rootSpawnable The new root spawnable that was assigned.
//! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned.
virtual void OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation) {}
//! Called when the root spawnable has Released. This will only be called if there's no root spawnable assigned to take the
//! place of the original root spawnable.
//! @param generation The generation of the root spawnable that was released.
virtual void OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) {}
};
using RootSpawnableNotificationBus = AZ::EBus<RootSpawnableNotifications>;
//! Interface to manage the root spawnable. All calls to this interface need to be made
//! from the main thread and all events are called from the main thread.
class RootSpawnableDefinition
{
public:
AZ_RTTI(AzFramework::RootSpawnableDefinition, "{6F3698F4-005D-4F26-BE99-5DC2E21FAD38}");
using OnRootSpawnableReadyEvent = AZ::Event < const AZ::Data::Asset<Spawnable>&, bool>;
//! Sets the provided spawnable as the new root spawnable. If a root spawnable has already
//! been assigned this will unload any entities spawned from it and replace it. The provided
//! spawnable will become the new root and all entities in it will be instanced into the
//! game entity context.
//! @return the generation of the root spawnable that has been assigned.
virtual uint64_t AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable) = 0;
//! Releases the root spawnable if one is set, resulting in all entities spawned from it to
//! be deleted and the spawnable asset to be released. This call is automatically done when
//! AssignRootSpawnable is called while a root spawnable is assigned.
virtual void ReleaseRootSpawnable() = 0;
};
using RootSpawnableInterface = AZ::Interface<RootSpawnableDefinition>;
} // namespace AzFramework
@@ -16,8 +16,8 @@
namespace AzFramework
{
Spawnable::Spawnable(const AZ::Data::AssetId& id)
: AZ::Data::AssetData(id)
Spawnable::Spawnable(const AZ::Data::AssetId& id, AssetStatus status)
: AZ::Data::AssetData(id, status)
{
}
@@ -33,9 +33,10 @@ namespace AzFramework
using EntityList = AZStd::vector<AZStd::unique_ptr<AZ::Entity>>;
inline static constexpr const char* FileExtension = "spawnable";
inline static constexpr const char* DotFileExtension = ".spawnable";
Spawnable() = default;
explicit Spawnable(const AZ::Data::AssetId& id);
explicit Spawnable(const AZ::Data::AssetId& id, AssetStatus status = AssetStatus::NotLoaded);
Spawnable(const Spawnable& rhs) = delete;
Spawnable(Spawnable&& other);
~Spawnable() override = default;
@@ -12,8 +12,8 @@
#include <AzCore/Serialization/Utils.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
namespace AzFramework
{
@@ -0,0 +1,115 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Spawnable/SpawnableEntitiesContainer.h>
namespace AzFramework
{
SpawnableEntitiesContainer::SpawnableEntitiesContainer(AZ::Data::Asset<Spawnable> spawnable)
{
Connect(AZStd::move(spawnable));
}
SpawnableEntitiesContainer::~SpawnableEntitiesContainer()
{
Clear();
}
bool SpawnableEntitiesContainer::IsSet() const
{
return m_threadData != nullptr;
}
uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const
{
return m_currentGeneration;
}
void SpawnableEntitiesContainer::SpawnAllEntities()
{
AZ_Assert(m_threadData, "Calling SpawnAllEntities on a Spawnable container that's not set.");
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
}
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<size_t> entityIndices)
{
AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set.");
SpawnableEntitiesInterface::Get()->SpawnEntities(m_threadData->m_spawnedEntitiesTicket, AZStd::move(entityIndices));
}
void SpawnableEntitiesContainer::DespawnAllEntities()
{
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
}
void SpawnableEntitiesContainer::Reset(AZ::Data::Asset<Spawnable> spawnable)
{
Clear();
Connect(AZStd::move(spawnable));
}
void SpawnableEntitiesContainer::Clear()
{
if (m_threadData != nullptr)
{
m_monitor.Disconnect();
m_monitor.m_threadData.reset();
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket,
[threadData = m_threadData](EntitySpawnTicket&) mutable
{
threadData.reset();
});
m_threadData.reset();
// The generation is incremented here instead of Connect in order to make sure that any callback that checks the
// provided generation with the current generation is aware that the container has moved on to the next iteration
// of the container even though it's empty and unassigned at this point.
m_currentGeneration++;
}
}
void SpawnableEntitiesContainer::Alert(AlertCallback callback)
{
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket,
[generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket&)
{
callback(generation);
});
}
void SpawnableEntitiesContainer::Connect(AZ::Data::Asset<Spawnable> spawnable)
{
AZ::Data::AssetId spawnableId = spawnable.GetId();
AZ_Assert(m_threadData == nullptr, "Connecting a spawnable entities container that's already connected.");
AZ_Assert(m_monitor.m_threadData == nullptr, "Connecting a spawnable entities monitor that's already connected.");
m_threadData = AZStd::make_shared<ThreadSafeData>();
m_threadData->m_spawnedEntitiesTicket = EntitySpawnTicket(AZStd::move(spawnable));
m_threadData->m_generation = m_currentGeneration;
m_monitor.m_threadData = m_threadData;
m_monitor.Connect(spawnableId);
}
void SpawnableEntitiesContainer::Monitor::OnSpawnableReloaded(AZ::Data::Asset<Spawnable>&& replacementAsset)
{
AZ_Assert(m_threadData, "SpawnableEntitiesContainer is monitoring a spawnable, but doesn't have the associated data.");
AZ_TracePrintf("Spawnables", "Reloading spawnable '%s'.\n", replacementAsset.GetHint().c_str());
SpawnableEntitiesInterface::Get()->ReloadSpawnable(m_threadData->m_spawnedEntitiesTicket, AZStd::move(replacementAsset));
}
} // namespace AzFramework
@@ -0,0 +1,105 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
#include <AzFramework/Spawnable/SpawnableMonitor.h>
namespace AZ
{
class Entity;
}
namespace AzFramework
{
//! A utility class to simplify the life-cycle management of entities created from a Spawnable.
//! This class will keep track of the created entities and take appropriate action when the spawnable changes.
//! Calls to this container should be done from the same thread, but multiple threads can create their own container for
//! the same Spawnable. This container is for simple use cases. Complex situations can directly call the
//! SpawnablesEntitesInterface and use the SpawnableMonitor if needed.
class SpawnableEntitiesContainer
{
public:
using AlertCallback = AZStd::function<void(uint32_t generation)>;
//! Constructs a new spawnables entity container that has not been connected.
SpawnableEntitiesContainer() = default;
//! Constructs a new spawnables entity container that connects to the provided spawnable.
//! @param spawnable The Spawnable that will be monitored and used as a template to create entities from.
explicit SpawnableEntitiesContainer(AZ::Data::Asset<Spawnable> spawnable);
~SpawnableEntitiesContainer();
//! Returns true if the container has a spawnable set and can process requests, otherwise returns falls.
[[nodiscard]] bool IsSet() const;
//! Returns a number that identifies the current generation of the container with. The completion callback can still receive
//! calls from older generations as processing completes on those. The returned value can be used to help calls tell
//! older versions apart from newer ones.
[[nodiscard]] uint64_t GetCurrentGeneration() const;
//! Puts in a request to spawn entities using all entities in the provided spawnable as a template.
void SpawnAllEntities();
//! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template.
//! @param entityIndices A list of indices to the entities in the spawnable.
void SpawnEntities(AZStd::vector<size_t> entityIndices);
//! Puts in a request to despawn all previous spawned entities.
void DespawnAllEntities();
//! Resets the spawnable and completion callback. This call will clear first if a spawnable has already been set. See
//! Clear for more details.
//! @param spawnable The Spawnable that will be monitored and used as a template to create entities from.
void Reset(AZ::Data::Asset<Spawnable> spawnable);
//! Puts in a request to disconnect from the connected spawnable. This will immediately clear the internal
//! state to allow for a Reset, but the release of the spawnable itself will be delayed. As a result any pending and
//! in-flight requests will complete first. If a callback has been set it will be called one more time after this
//! function returns, possibly outliving the lifetime of the container.
void Clear();
//! Adds an alert that will trigger the provided callback once all previous calls to change the container have completed.
//! This includes calls to (de)spawn entities, reset the container or clear. The callback can be called from threads
//! other than the calling thread including the main thread. Note that because the alert is queued it can still be called
//! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check
//! if the current generation matches the generation provided with the callback.
void Alert(AlertCallback callback);
private:
void Connect(AZ::Data::Asset<Spawnable> spawnable);
struct ThreadSafeData
{
AZ_CLASS_ALLOCATOR(SpawnableEntitiesContainer::ThreadSafeData, AZ::SystemAllocator, 0);
EntitySpawnTicket m_spawnedEntitiesTicket;
uint32_t m_generation{ 0 };
};
class Monitor final : public SpawnableMonitor
{
public:
AZStd::shared_ptr<ThreadSafeData> m_threadData;
protected:
void OnSpawnableReloaded(AZ::Data::Asset<Spawnable>&& replacementAsset) override;
};
Monitor m_monitor;
AZStd::atomic_uint32_t m_currentGeneration{ 1 };
AZStd::shared_ptr<ThreadSafeData> m_threadData;
};
} // namespace AzFramework
@@ -0,0 +1,139 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace AzFramework
{
SpawnableEntityContainerView::SpawnableEntityContainerView(AZ::Entity** begin, size_t length)
: m_begin(begin)
, m_end(begin + length)
{}
SpawnableEntityContainerView::SpawnableEntityContainerView(AZ::Entity** begin, AZ::Entity** end)
: m_begin(begin)
, m_end(end)
{
AZ_Assert(m_begin <= m_end, "SpawnableEntityContainerView created with a begin that's past the end.");
}
AZ::Entity** SpawnableEntityContainerView::begin()
{
return m_begin;
}
AZ::Entity** SpawnableEntityContainerView::end()
{
return m_end;
}
const AZ::Entity* const* SpawnableEntityContainerView::cbegin()
{
return m_begin;
}
const AZ::Entity* const* SpawnableEntityContainerView::cend()
{
return m_end;
}
size_t SpawnableEntityContainerView::size()
{
return AZStd::distance(m_begin, m_end);
}
SpawnableConstEntityContainerView::SpawnableConstEntityContainerView(AZ::Entity** begin, size_t length)
: m_begin(begin)
, m_end(begin + length)
{}
SpawnableConstEntityContainerView::SpawnableConstEntityContainerView(AZ::Entity** begin, AZ::Entity** end)
: m_begin(begin)
, m_end(end)
{
AZ_Assert(m_begin <= m_end, "SpawnableConstEntityContainerView created with a begin that's past the end.");
}
const AZ::Entity* const* SpawnableConstEntityContainerView::begin()
{
return m_begin;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::end()
{
return m_end;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::cbegin()
{
return m_begin;
}
const AZ::Entity* const* SpawnableConstEntityContainerView::cend()
{
return m_end;
}
size_t SpawnableConstEntityContainerView::size()
{
return AZStd::distance(m_begin, m_end);
}
EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs)
: m_payload(rhs.m_payload)
{
rhs.m_payload = nullptr;
}
EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset<Spawnable> spawnable)
{
auto manager = SpawnableEntitiesInterface::Get();
AZ_Assert(manager, "Attempting to create an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
m_payload = manager->CreateTicket(AZStd::move(spawnable));
}
EntitySpawnTicket::~EntitySpawnTicket()
{
if (m_payload)
{
auto manager = SpawnableEntitiesInterface::Get();
AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
manager->DestroyTicket(m_payload);
m_payload = nullptr;
}
}
EntitySpawnTicket& EntitySpawnTicket::operator=(EntitySpawnTicket&& rhs)
{
if (this != &rhs)
{
if (m_payload)
{
auto manager = SpawnableEntitiesInterface::Get();
AZ_Assert(manager, "Attempting to destroy an entity spawn ticket while the SpawnableEntitiesInterface has no implementation.");
manager->DestroyTicket(m_payload);
}
m_payload = rhs.m_payload;
rhs.m_payload = nullptr;
}
return *this;
}
bool EntitySpawnTicket::IsValid() const
{
return m_payload != nullptr;
}
} // namespace AzFramework
@@ -0,0 +1,181 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/functional.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AZ
{
class Entity;
}
namespace AzFramework
{
class SpawnableEntityContainerView
{
public:
SpawnableEntityContainerView(AZ::Entity** begin, size_t length);
SpawnableEntityContainerView(AZ::Entity** begin, AZ::Entity** end);
AZ::Entity** begin();
AZ::Entity** end();
const AZ::Entity* const* cbegin();
const AZ::Entity* const* cend();
size_t size();
private:
AZ::Entity** m_begin;
AZ::Entity** m_end;
};
class SpawnableConstEntityContainerView
{
public:
SpawnableConstEntityContainerView(AZ::Entity** begin, size_t length);
SpawnableConstEntityContainerView(AZ::Entity** begin, AZ::Entity** end);
const AZ::Entity* const* begin();
const AZ::Entity* const* end();
const AZ::Entity* const* cbegin();
const AZ::Entity* const* cend();
size_t size();
private:
AZ::Entity** m_begin;
AZ::Entity** m_end;
};
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that be used as a template. A ticket can
//! be reused for multiple calls on the same spawnable and is safe to use by multiple threads at the same time. Entities created
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
//! by a call so spawn entities. The life cycle of the spawned entities is tied to the ticket and all entities spawned using a
//! ticket will be despawned when it's deleted.
class EntitySpawnTicket
{
public:
friend class SpawnableEntitiesDefinition;
EntitySpawnTicket() = default;
EntitySpawnTicket(const EntitySpawnTicket&) = delete;
EntitySpawnTicket(EntitySpawnTicket&& rhs);
explicit EntitySpawnTicket(AZ::Data::Asset<Spawnable> spawnable);
~EntitySpawnTicket();
EntitySpawnTicket& operator=(const EntitySpawnTicket&) = delete;
EntitySpawnTicket& operator=(EntitySpawnTicket&& rhs);
bool IsValid() const;
private:
void* m_payload{ nullptr };
};
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket&)>;
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
using BarrierCallback = AZStd::function<void(EntitySpawnTicket&)>;
//! Interface definition to (de)spawn entities from a spawnable into the game world.
//! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be
//! issued from threads other than the one that issued the call, including the main thread.
//! Calls on the same ticket are guaranteed to be executed in the order they are issued. Note that when issuing requests from
//! multiple threads on the same ticket the order in which the requests are assigned to the ticket is not guaranteed.
class SpawnableEntitiesDefinition
{
public:
AZ_RTTI(AzFramework::SpawnableEntitiesDefinition, "{A9ED3F1F-4D69-4182-B0CD-EB561EEA7068}");
friend class EntitySpawnTicket;
virtual ~SpawnableEntitiesDefinition() = default;
//! Spawn instances of all entities in the spawnable.
//! @param spawnable The Spawnable asset that will be used to create entity instances from.
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
//! a different thread than the one that made the function call. The returned list of entities contains all the newly
//! created entities.
virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) = 0;
//! Spawn instances of some entities in the spawnable.
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
//! a different thread than the one that made this function call. The returned list of entities contains all the newly
//! created entities.
virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
EntitySpawnCallback completionCallback = {}) = 0;
//! Removes all entities in the provided list from the environment.
//! @param ticket The ticket previously used to spawn entities with.
//! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from
//! a different thread than the one that made this function call.
virtual void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) = 0;
//! Removes all entities in the provided list from the environment and reconstructs the entities from the provided spawnable.
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
//! @param spawnable The spawnable that will replace the existing spawnable. Both need to have the same asset id.
//! @param completionCallback Optional callback that's called when the entities have been reloaded. This can be called from
//! a different thread than the one that made this function call. The returned list of entities contains all the replacement
//! entities.
virtual void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
ReloadSpawnableCallback completionCallback = {}) = 0;
//! List all entities that are spawned using this ticket.
//! @param ticket Only the entities associated with this ticket will be listed.
//! @param listCallback Required callback that will be called to list the entities on.
virtual void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) = 0;
//! Claim all entities that are spawned using this ticket. Ownership of the entities is transferred from the ticket to the
//! caller through the callback. After this call the ticket will have no entities associated with it. The caller of
//! this function will need to manage the entities after this call.
//! @param ticket Only the entities associated with this ticket will be released.
//! @param listCallback Required callback that will be called to transfer the entities through.
virtual void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) = 0;
//! Blocks until all operations made on the provided ticket before the barrier call have completed.
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback) = 0;
protected:
[[nodiscard]] virtual void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
virtual void DestroyTicket(void* ticket) = 0;
template<typename T>
static T& GetTicketPayload(EntitySpawnTicket& ticket)
{
return *reinterpret_cast<T*>(ticket.m_payload);
}
template<typename T>
static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
{
return *reinterpret_cast<const T*>(ticket.m_payload);
}
template<typename T>
static T* GetTicketPayload(EntitySpawnTicket* ticket)
{
return reinterpret_cast<T*>(ticket->m_payload);
}
template<typename T>
static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
{
return reinterpret_cast<const T*>(ticket->m_payload);
}
};
using SpawnableEntitiesInterface = AZ::Interface<SpawnableEntitiesDefinition>;
} // namespace AzFramework
@@ -0,0 +1,457 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
namespace AzFramework
{
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback)
{
SpawnAllEntitiesCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_completionCallback = AZStd::move(completionCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
EntitySpawnCallback completionCallback)
{
SpawnEntitiesCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_entityIndices = AZStd::move(entityIndices);
queueEntry.m_completionCallback = AZStd::move(completionCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback)
{
DespawnAllEntitiesCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_completionCallback = AZStd::move(completionCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
ReloadSpawnableCallback completionCallback)
{
ReloadSpawnableCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_spawnable = AZStd::move(spawnable);
queueEntry.m_completionCallback = AZStd::move(completionCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback)
{
AZ_Assert(listCallback, "ListEntities called on spawnable entities without a valid callback to use.");
ListEntitiesCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_listCallback = AZStd::move(listCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback)
{
AZ_Assert(listCallback, "ClaimEntities called on spawnable entities without a valid callback to use.");
ClaimEntitiesCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_listCallback = AZStd::move(listCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
void SpawnableEntitiesManager::Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback)
{
AZ_Assert(completionCallback, "Barrier on spawnable entities called without a valid callback to use.");
BarrierCommand queueEntry;
queueEntry.m_ticket = &ticket;
queueEntry.m_completionCallback = AZStd::move(completionCallback);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
auto SpawnableEntitiesManager::ProcessQueue() -> CommandQueueStatus
{
AZStd::queue<Requests> pendingRequestQueue;
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
m_pendingRequestQueue.swap(pendingRequestQueue);
}
if (!pendingRequestQueue.empty() || !m_delayedQueue.empty())
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Assert(serializeContext, "Failed to retrieve serialization context.");
// Only process the requests that are currently in this queue, not the ones that could be re-added if they still can't complete.
size_t delayedSize = m_delayedQueue.size();
for (size_t i = 0; i < delayedSize; ++i)
{
Requests& request = m_delayedQueue.front();
bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool
{
return ProcessRequest(args, *serializeContext);
}, request);
if (!result)
{
m_delayedQueue.emplace_back(AZStd::move(request));
}
m_delayedQueue.pop_front();
}
do
{
while (!pendingRequestQueue.empty())
{
Requests& request = pendingRequestQueue.front();
bool result = AZStd::visit([this, serializeContext](auto&& args) -> bool
{
return ProcessRequest(args, *serializeContext);
}, request);
if (!result)
{
m_delayedQueue.emplace_back(AZStd::move(request));
}
pendingRequestQueue.pop();
}
// Spawning entities can result in more entities being queued to spawn. Repeat spawning until the queue is
// empty to avoid a chain of entity spawning getting dragged out over multiple frames.
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
m_pendingRequestQueue.swap(pendingRequestQueue);
}
} while (!pendingRequestQueue.empty());
}
return m_delayedQueue.empty() ? CommandQueueStatus::NoCommandLeft : CommandQueueStatus::HasCommandsLeft;
}
void* SpawnableEntitiesManager::CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable)
{
auto result = aznew Ticket();
result->m_spawnable = AZStd::move(spawnable);
return result;
}
void SpawnableEntitiesManager::DestroyTicket(void* ticket)
{
DestroyTicketCommand queueEntry;
queueEntry.m_ticket = reinterpret_cast<Ticket*>(ticket);
{
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
queueEntry.m_ticketId = reinterpret_cast<Ticket*>(ticket)->m_nextTicketId++;
m_pendingRequestQueue.push(AZStd::move(queueEntry));
}
}
AZ::Entity* SpawnableEntitiesManager::SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext)
{
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
clone->SetId(AZ::Entity::MakeId());
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone);
return clone;
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
{
size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size();
const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities();
size_t entitiesSize = entities.size();
ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize);
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize);
for(size_t i=0; i<entitiesSize; ++i)
{
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext));
ticket.m_spawnedEntityIndices.push_back(i);
}
if (request.m_completionCallback)
{
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
}
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
{
size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size();
const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities();
size_t entitiesSize = entities.size();
ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize);
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize);
for (size_t index : request.m_entityIndices)
{
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], serializeContext));
ticket.m_spawnedEntityIndices.push_back(index);
}
ticket.m_loadAll = false;
if (request.m_completionCallback)
{
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
}
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request,
[[maybe_unused]] AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (request.m_ticketId == ticket.m_currentTicketId)
{
for (AZ::Entity* entity : ticket.m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
}
}
ticket.m_spawnedEntities.clear();
ticket.m_spawnedEntityIndices.clear();
if (request.m_completionCallback)
{
request.m_completionCallback(*request.m_ticket);
}
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
"Spawnable is being reloaded, but the provided spawnable has a different asset id. "
"This will likely result in unexpected entities being created.");
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
{
// Delete the original entities.
for (AZ::Entity* entity : ticket.m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
}
}
// Rebuild the list of entities.
ticket.m_spawnedEntities.clear();
const Spawnable::EntityList& entities = request.m_spawnable->GetEntities();
if (ticket.m_loadAll)
{
// The new spawnable may have a different number of entities and since the intent of the user was
// to load every, simply start over.
ticket.m_spawnedEntityIndices.clear();
size_t entitiesSize = entities.size();
for (size_t i = 0; i < entitiesSize; ++i)
{
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext));
ticket.m_spawnedEntityIndices.push_back(i);
}
}
else
{
size_t entitiesSize = entities.size();
for (size_t index : ticket.m_spawnedEntityIndices)
{
ticket.m_spawnedEntities.push_back(
index < entitiesSize ? SpawnSingleEntity(*entities[index], serializeContext) : nullptr);
}
}
ticket.m_spawnable = AZStd::move(request.m_spawnable);
if (request.m_completionCallback)
{
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
}
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (request.m_ticketId == ticket.m_currentTicketId)
{
request.m_listCallback(*request.m_ticket, SpawnableConstEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (request.m_ticketId == ticket.m_currentTicketId)
{
request.m_listCallback(*request.m_ticket, SpawnableEntityContainerView(
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
ticket.m_spawnedEntities.clear();
ticket.m_spawnedEntityIndices.clear();
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
{
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
if (request.m_ticketId == ticket.m_currentTicketId)
{
if (request.m_completionCallback)
{
request.m_completionCallback(*request.m_ticket);
}
ticket.m_currentTicketId++;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request, [[maybe_unused]] AZ::SerializeContext& serializeContext)
{
if (request.m_ticketId == request.m_ticket->m_currentTicketId)
{
for (AZ::Entity* entity : request.m_ticket->m_spawnedEntities)
{
if (entity != nullptr)
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntityAndDescendants, entity->GetId());
}
}
delete request.m_ticket;
return true;
}
else
{
return false;
}
}
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs)
{
return GetTicketPayload<Ticket>(lhs) == GetTicketPayload<Ticket>(rhs);
}
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs)
{
return lhs == GetTicketPayload<Ticket>(rhs);
}
bool SpawnableEntitiesManager::IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs)
{
return GetTicketPayload<Ticket>(lhs) == rhs;
}
bool SpawnableEntitiesManager::IsEqualTicket(const Ticket* lhs, const Ticket* rhs)
{
return lhs = rhs;
}
} // namespace AzFramework
@@ -0,0 +1,160 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/limits.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/variant.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/parallel/mutex.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace AZ
{
class Entity;
class SerializeContext;
}
namespace AzFramework
{
class SpawnableEntitiesManager
: public SpawnableEntitiesInterface::Registrar
{
public:
AZ_RTTI(AzFramework::SpawnableEntitiesManager, "{6E14333F-128C-464C-94CA-A63B05A5E51C}");
enum class CommandQueueStatus : bool
{
HasCommandsLeft,
NoCommandLeft
};
~SpawnableEntitiesManager() override = default;
//
// The following functions are thread safe
//
void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) override;
void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
EntitySpawnCallback completionCallback = {}) override;
void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override;
void ReloadSpawnable(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable,
ReloadSpawnableCallback completionCallback = {}) override;
void ListEntities(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback) override;
void ClaimEntities(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback) override;
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback) override;
//
// The following function is thread safe but intended to be run from the main thread.
//
CommandQueueStatus ProcessQueue();
protected:
void* CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) override;
void DestroyTicket(void* ticket) override;
private:
struct Ticket
{
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
static constexpr uint32_t Processing = AZStd::numeric_limits<uint32_t>::max();
AZStd::vector<AZ::Entity*> m_spawnedEntities;
AZStd::vector<size_t> m_spawnedEntityIndices;
AZ::Data::Asset<Spawnable> m_spawnable;
uint32_t m_nextTicketId{ 0 }; //!< Next id for this ticket.
uint32_t m_currentTicketId{ 0 }; //!< The id for the command that should be executed.
bool m_loadAll{ true };
};
struct SpawnAllEntitiesCommand
{
EntitySpawnCallback m_completionCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct SpawnEntitiesCommand
{
AZStd::vector<size_t> m_entityIndices;
EntitySpawnCallback m_completionCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct DespawnAllEntitiesCommand
{
EntityDespawnCallback m_completionCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct ReloadSpawnableCommand
{
AZ::Data::Asset<Spawnable> m_spawnable;
ReloadSpawnableCallback m_completionCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct ListEntitiesCommand
{
ListEntitiesCallback m_listCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct ClaimEntitiesCommand
{
ClaimEntitiesCallback m_listCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct BarrierCommand
{
BarrierCallback m_completionCallback;
EntitySpawnTicket* m_ticket;
uint32_t m_ticketId;
};
struct DestroyTicketCommand
{
Ticket* m_ticket;
uint32_t m_ticketId;
};
using Requests = AZStd::variant<SpawnAllEntitiesCommand, SpawnEntitiesCommand, DespawnAllEntitiesCommand, ReloadSpawnableCommand,
ListEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, DestroyTicketCommand>;
AZ::Entity* SpawnSingleEntity(const AZ::Entity& entityTemplate, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(SpawnEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(DespawnAllEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(ReloadSpawnableCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(ListEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(ClaimEntitiesCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(BarrierCommand& request, AZ::SerializeContext& serializeContext);
bool ProcessRequest(DestroyTicketCommand& request, AZ::SerializeContext& serializeContext);
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const EntitySpawnTicket* rhs);
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const EntitySpawnTicket* rhs);
[[nodiscard]] static bool IsEqualTicket(const EntitySpawnTicket* lhs, const Ticket* rhs);
[[nodiscard]] static bool IsEqualTicket(const Ticket* lhs, const Ticket* rhs);
AZStd::deque<Requests> m_delayedQueue; //!< Requests that were processed before, but couldn't be completed.
AZStd::queue<Requests> m_pendingRequestQueue;
AZStd::mutex m_pendingRequestQueueMutex;
};
} // namespace AzFramework
@@ -0,0 +1,158 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzFramework/Spawnable/SpawnableMonitor.h>
namespace AzFramework
{
SpawnableMonitor::SpawnableMonitor(AZ::Data::AssetId spawnableAssetId)
{
AZ::Data::AssetBus::MultiHandler::BusConnect(spawnableAssetId);
m_isConnected = true;
}
SpawnableMonitor::~SpawnableMonitor()
{
Disconnect();
}
bool SpawnableMonitor::Connect(AZ::Data::AssetId spawnableAssetId)
{
if (!m_isConnected)
{
AZ::Data::AssetBus::MultiHandler::BusConnect(spawnableAssetId);
m_isConnected = true;
return true;
}
else
{
return false;
}
}
bool SpawnableMonitor::Disconnect()
{
if (m_isConnected)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
m_isLoaded = false;
m_isConnected = false;
return true;
}
else
{
return false;
}
}
bool SpawnableMonitor::IsConnected() const
{
return m_isConnected;
}
bool SpawnableMonitor::IsLoaded() const
{
return m_isLoaded;
}
void SpawnableMonitor::OnSpawnableLoaded()
{
}
void SpawnableMonitor::OnSpawnableUnloaded()
{
}
void SpawnableMonitor::OnSpawnableReloaded([[maybe_unused]] AZ::Data::Asset<Spawnable>&& replacementAsset)
{
OnSpawnableUnloaded();
OnSpawnableLoaded();
}
void SpawnableMonitor::OnSpawnableIssue([[maybe_unused]] IssueType issueType, [[maybe_unused]] AZStd::string_view message)
{
switch (issueType)
{
case IssueType::Error:
AZ_Error("Spawnables", false, "%.*s", AZ_STRING_ARG(message));
break;
case IssueType::Cancel:
AZ_TracePrintf("Spawnables", "%.*s", AZ_STRING_ARG(message));
break;
case IssueType::ReloadError:
AZ_TracePrintf("Spawnables", "%.*s", AZ_STRING_ARG(message));
break;
}
}
void SpawnableMonitor::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
AZ_Assert(!m_isLoaded, "Trying to load spawnable %s (%s) for a second time.",
asset.GetHint().c_str(), asset.GetId().ToString<AZStd::string>().c_str());
m_isLoaded = true;
OnSpawnableLoaded();
}
void SpawnableMonitor::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
AZ_Assert(m_isLoaded, "Trying to reload a spawnable %s (%s) that wasn't loaded.",
asset.GetHint().c_str(), asset.GetId().ToString<AZStd::string>().c_str());
OnSpawnableReloaded(AZStd::move(asset));
}
void SpawnableMonitor::OnAssetUnloaded([[maybe_unused]] const AZ::Data::AssetId assetId,
[[maybe_unused]] const AZ::Data::AssetType assetType)
{
AZ_Assert(m_isLoaded, "Trying to unload a spawnable %s that was never loaded.", assetId.ToString<AZStd::string>().c_str());
m_isLoaded = false;
OnSpawnableUnloaded();
}
void SpawnableMonitor::OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (m_isLoaded)
{
m_isLoaded = false;
OnSpawnableUnloaded();
}
AZStd::string message = AZStd::string::format("An error occurred during the loading of spawnable '%s' (%s).",
asset.GetHint().c_str(), asset.GetId().ToString<AZStd::string>().c_str());
OnSpawnableIssue(IssueType::Error, message);
}
void SpawnableMonitor::OnAssetCanceled(AZ::Data::AssetId assetId)
{
if (m_isLoaded)
{
m_isLoaded = false;
OnSpawnableUnloaded();
}
AZStd::string message = AZStd::string::format("The loading of spawnable '%s' was canceled.",
assetId.ToString<AZStd::string>().c_str());
OnSpawnableIssue(IssueType::Cancel, message);
}
void SpawnableMonitor::OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (m_isLoaded)
{
m_isLoaded = false;
OnSpawnableUnloaded();
}
AZStd::string message = AZStd::string::format("An error occurred while trying to reload spawnable '%s' (%s).",
asset.GetHint().c_str(), asset.GetId().ToString<AZStd::string>().c_str());
OnSpawnableIssue(IssueType::ReloadError, message);
}
} // namespace AzFramework
@@ -0,0 +1,78 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string_view.h>
#include <AzFramework/Spawnable/Spawnable.h>
namespace AzFramework
{
//! A utility class to make it easier to work with individual spawnables.
//! This class will monitor a spawnable for changes and provides a simplified interface to
//! respond to.
class SpawnableMonitor
: public AZ::Data::AssetBus::MultiHandler
{
public:
enum IssueType
{
Error,
Cancel,
ReloadError
};
SpawnableMonitor() = default;
//! Constructs the monitor to immediately start monitoring a specific spawnable. Depending on the state of the
//! spawnable this can result in immediate calls to OnSpawnable* functions.
explicit SpawnableMonitor(AZ::Data::AssetId spawnableAssetId);
//! Destroys the monitor, including disconnecting from the spawnable if connected.
virtual ~SpawnableMonitor();
//! Connects the monitor to start monitoring a specific spawnable. Depending on the state of the spawnable this
//! can result in immediate calls to OnSpawnable* functions. If the monitor is already connected this will do
//! nothing and return false.
virtual bool Connect(AZ::Data::AssetId spawnableAssetId);
//! Disconnects from the monitor if connected and otherwise does nothing and returns false.
virtual bool Disconnect();
//! Returns true if the monitor is connected and otherwise false.
[[nodiscard]] virtual bool IsConnected() const;
//! Returns true if the monitor tracked the spawnable able being ready for use, otherwise false.
[[nodiscard]] virtual bool IsLoaded() const;
protected:
//! Called when a spawnable has been loaded and is ready for used.
virtual void OnSpawnableLoaded();
//! Called when an spawnable has been unloaded and should no longer be used.
virtual void OnSpawnableUnloaded();
//! Called when a spawnable has been reloaded. Note that if the replacement isn't used to replace
//! the original spawnable then the replacement asset will eventually run out of references and send
//! an OnSpawnableUnloaded event.
virtual void OnSpawnableReloaded(AZ::Data::Asset<Spawnable>&& replacementAsset);
//! Called when an error occurred while the Asset Manager did an operation on the spawnable.
virtual void OnSpawnableIssue(IssueType issueType, AZStd::string_view message);
private:
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) override;
void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetCanceled(AZ::Data::AssetId assetId) override;
void OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
bool m_isConnected{ false };
bool m_isLoaded{ false };
};
} // namespace AzFramework
@@ -46,30 +46,82 @@ namespace AzFramework
services.push_back(AZ_CRC_CE("AssetCatalogService"));
}
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
m_entitiesManager.ProcessQueue();
RootSpawnableNotificationBus::ExecuteQueuedEvents();
}
void SpawnableSystemComponent::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
{
if (!m_rootSpawnableInitialized)
if (!m_catalogAvailable)
{
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "Unable to check for root spawnable because the Settings Registry is not available.");
if (registry->GetObject(m_rootSpawnable, RootSpawnableRegistryKey) && m_rootSpawnable.GetId().IsValid())
{
AZ_TracePrintf("Spawnables", "Root spawnable '%s' used.\n", m_rootSpawnable.GetHint().c_str());
if (!m_rootSpawnable.QueueLoad())
{
AZ_Error("Spawnables", false, "Unable to queue root spawnable for loading.\n");
}
}
else
{
AZ_Warning("Spawnables", false, "No root spawnable assigned or root spawanble couldnt' be loaded.\n"
"The root spawnable can be assigned in the Settings Registry under the key '%s'.\n", RootSpawnableRegistryKey);
}
m_rootSpawnableInitialized = true;
m_catalogAvailable = true;
LoadRootSpawnableFromSettingsRegistry();
}
}
uint64_t SpawnableSystemComponent::AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable)
{
uint64_t generation = 0;
if (m_rootSpawnableId == rootSpawnable.GetId())
{
AZ_TracePrintf("Spawnables", "Root spawnable wasn't updated because it's already assigned to the requested asset.");
return m_rootSpawnableContainer.GetCurrentGeneration();
}
if (rootSpawnable.QueueLoad())
{
m_rootSpawnableId = rootSpawnable.GetId();
// Suspend and resume processing in the container that completion calls aren't received until
// everything has been setup to accept callbacks from the call.
m_rootSpawnableContainer.Reset(rootSpawnable);
m_rootSpawnableContainer.SpawnAllEntities();
generation = m_rootSpawnableContainer.GetCurrentGeneration();
AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(),
generation);
m_rootSpawnableContainer.Alert(
[newSpawnable = AZStd::move(rootSpawnable)](uint32_t generation)
{
RootSpawnableNotificationBus::QueueBroadcast(
&RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, newSpawnable, generation);
});
}
else
{
AZ_Error("Spawnables", false, "Unable to queue root spawnable '%s' for loading.", rootSpawnable.GetHint().c_str());
}
return generation;
}
void SpawnableSystemComponent::ReleaseRootSpawnable()
{
if (m_rootSpawnableContainer.IsSet())
{
m_rootSpawnableContainer.Alert(
[](uint32_t generation)
{
RootSpawnableNotificationBus::QueueBroadcast(&RootSpawnableNotificationBus::Events::OnRootSpawnableReleased, generation);
});
m_rootSpawnableContainer.Clear();
}
m_rootSpawnableId = AZ::Data::AssetId();
}
void SpawnableSystemComponent::OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation)
{
AZ_TracePrintf("Spawnables", "New root spawnable '%s' assigned (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation);
}
void SpawnableSystemComponent::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
{
AZ_TracePrintf("Spawnables", "Generation %i of the root spawnable has been released.\n", generation);
}
void SpawnableSystemComponent::Activate()
{
// Register with AssetDatabase
@@ -83,14 +135,105 @@ namespace AzFramework
&AZ::Data::AssetCatalogRequestBus::Events::AddExtension, Spawnable::FileExtension);
AssetCatalogEventBus::Handler::BusConnect();
RootSpawnableNotificationBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "Unable to change root spawnable callback because Settings Registry is not available.");
m_registryChangeHandler = registry->RegisterNotifier([this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type /*type*/)
{
if (path.starts_with(RootSpawnableRegistryKey))
{
LoadRootSpawnableFromSettingsRegistry();
}
});
}
void SpawnableSystemComponent::Deactivate()
{
m_registryChangeHandler.Disconnect();
AZ::TickBus::Handler::BusDisconnect();
RootSpawnableNotificationBus::Handler::BusDisconnect();
AssetCatalogEventBus::Handler::BusDisconnect();
AZ_Assert(AZ::Data::AssetManager::IsReady(),
"Spawnables can't be unregistered because the Asset Manager has been destroyed already or never started.");
if (m_catalogAvailable)
{
ReleaseRootSpawnable();
// The SpawnalbleSystemComponent needs to guarantee there's no more processing left to do by the
// entity manager before it can safely destroy it on shutdown, but also to make sure that are no
// more calls to the callback registered to the root spawnable as that accesses this component.
m_rootSpawnableContainer.Clear();
SpawnableEntitiesManager::CommandQueueStatus queueStatus;
do
{
queueStatus = m_entitiesManager.ProcessQueue();
} while (queueStatus == SpawnableEntitiesManager::CommandQueueStatus::HasCommandsLeft);
}
AZ::Data::AssetManager::Instance().UnregisterHandler(&m_assetHandler);
}
void SpawnableSystemComponent::LoadRootSpawnableFromSettingsRegistry()
{
AZ_Assert(m_catalogAvailable, "Attempting to load root spawnable while the catalog is not available yet.");
auto registry = AZ::SettingsRegistry::Get();
AZ_Assert(registry, "Unable to check for root spawnable because the Settings Registry is not available.");
AZ::SettingsRegistryInterface::Type rootSpawnableKeyType = registry->GetType(RootSpawnableRegistryKey);
if (rootSpawnableKeyType == AZ::SettingsRegistryInterface::Type::Object)
{
AZ::Data::Asset<Spawnable> rootSpawnable;
if (registry->GetObject(rootSpawnable, RootSpawnableRegistryKey) && rootSpawnable.GetId().IsValid())
{
AssignRootSpawnable(AZStd::move(rootSpawnable));
}
else
{
AZ_Warning("Spawnables", false, "Root spawnable couldn't be queued for loading");
ReleaseRootSpawnable();
}
}
else if (rootSpawnableKeyType == AZ::SettingsRegistryInterface::Type::String)
{
AZStd::string rootSpawnableName;
if (registry->Get(rootSpawnableName, RootSpawnableRegistryKey))
{
AZ::Data::AssetId rootSpawnableId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
rootSpawnableId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, rootSpawnableName.c_str(),
azrtti_typeid<Spawnable>(), false);
if (rootSpawnableId.IsValid())
{
AZ::Data::Asset<Spawnable> rootSpawnable = AZ::Data::Asset<Spawnable>(rootSpawnableId, azrtti_typeid<Spawnable>());
if (rootSpawnable.GetId().IsValid())
{
AssignRootSpawnable(AZStd::move(rootSpawnable));
}
else
{
AZ_Warning(
"Spawnables", false, "Root spawnable at '%s' couldn't be queued for loading.", rootSpawnableName.c_str());
ReleaseRootSpawnable();
}
}
else
{
AZ_Warning(
"Spawnables", false, "Root spawnable with name '%s' wasn't found in the asset catalog.", rootSpawnableName.c_str());
ReleaseRootSpawnable();
}
}
}
else if (rootSpawnableKeyType == AZ::SettingsRegistryInterface::Type::NoType)
{
AZ_Warning(
"Spawnables", false,
"No root spawnable assigned. The root spawnable can be assigned in the Settings Registry under the key '%s'.\n",
RootSpawnableRegistryKey);
ReleaseRootSpawnable();
}
}
} // namespace AzFramework
@@ -13,15 +13,24 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <AzFramework/Spawnable/Spawnable.h>
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
#include <AzFramework/Spawnable/SpawnableEntitiesContainer.h>
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
namespace AzFramework
{
class SpawnableSystemComponent
: public AZ::Component
, public AZ::TickBus::Handler
, public AssetCatalogEventBus::Handler
, public RootSpawnableInterface::Registrar
, public RootSpawnableNotificationBus::Handler
{
public:
AZ_COMPONENT(SpawnableSystemComponent, "{12D0DA52-BB86-4AC3-8862-9493E0D0E207}");
@@ -35,23 +44,53 @@ namespace AzFramework
SpawnableSystemComponent& operator=(const SpawnableSystemComponent&) = delete;
SpawnableSystemComponent& operator=(SpawnableSystemComponent&&) = delete;
//
// Component
//
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& services);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& services);
//
// TickBus
//
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//
// AssetCatalogEventBus
//
void OnCatalogLoaded(const char* catalogFile) override;
//
// RootSpawnableInterface
//
uint64_t AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable) override;
void ReleaseRootSpawnable() override;
//
// RootSpawnbleNotificationBus
//
void OnRootSpawnableAssigned(AZ::Data::Asset<Spawnable> rootSpawnable, uint32_t generation) override;
void OnRootSpawnableReleased(uint32_t generation) override;
protected:
void Activate() override;
void Deactivate() override;
void LoadRootSpawnableFromSettingsRegistry();
SpawnableAssetHandler m_assetHandler;
AZ::Data::Asset<Spawnable> m_rootSpawnable;
bool m_rootSpawnableInitialized{ false };
SpawnableEntitiesManager m_entitiesManager;
SpawnableEntitiesContainer m_rootSpawnableContainer;
AZ::SettingsRegistryInterface::NotifyEventHandler m_registryChangeHandler;
AZ::Data::AssetId m_rootSpawnableId;
bool m_catalogAvailable{ false };
};
} // namespace AzFramework
@@ -0,0 +1,121 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "TestDebugDisplayRequests.h"
namespace UnitTest
{
TestDebugDisplayRequests::TestDebugDisplayRequests()
{
m_transforms.push(AZ::Transform::CreateIdentity());
}
const AZStd::vector<AZ::Vector3>& TestDebugDisplayRequests::GetPoints() const
{
return m_points;
}
void TestDebugDisplayRequests::ClearPoints()
{
m_points.clear();
}
AZ::Aabb TestDebugDisplayRequests::GetAabb() const
{
return m_points.size() > 0 ? AZ::Aabb::CreatePoints(m_points.data(), m_points.size()) : AZ::Aabb::CreateNull();
}
void TestDebugDisplayRequests::DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max)
{
const AZ::Transform& tm = m_transforms.back();
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), min.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), max.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), max.GetY(), min.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), max.GetY(), max.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(max.GetX(), min.GetY(), min.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(max.GetX(), min.GetY(), max.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(max.GetX(), max.GetY(), min.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(max.GetX(), max.GetY(), max.GetZ())));
}
void TestDebugDisplayRequests::DrawSolidBox(const AZ::Vector3& min, const AZ::Vector3& max)
{
DrawWireBox(min, max);
}
void TestDebugDisplayRequests::DrawWireQuad(float width, float height)
{
const AZ::Transform& tm = m_transforms.back();
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, -0.5f * height)));
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, 0.5f * height)));
m_points.push_back(tm.TransformPoint(AZ::Vector3(0.5f * width, 0.0f, -0.5f * height)));
m_points.push_back(tm.TransformPoint(AZ::Vector3(0.5f * width, 0.0f, 0.5f * height)));
}
void TestDebugDisplayRequests::DrawQuad(float width, float height)
{
DrawWireQuad(width, height);
}
void TestDebugDisplayRequests::DrawPoints(const AZStd::vector<AZ::Vector3>& points)
{
const AZ::Transform& tm = m_transforms.back();
for (const auto& point : points)
{
m_points.push_back(tm.TransformPoint(point));
}
}
void TestDebugDisplayRequests::DrawTriangles(const AZStd::vector<AZ::Vector3>& vertices, [[maybe_unused]] const AZ::Color& color)
{
DrawPoints(vertices);
}
void TestDebugDisplayRequests::DrawTrianglesIndexed(const AZStd::vector<AZ::Vector3>& vertices,
[[maybe_unused]] const AZStd::vector<AZ::u32>& indices, [[maybe_unused]] const AZ::Color& color)
{
DrawPoints(vertices);
}
void TestDebugDisplayRequests::DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2)
{
DrawPoints({ p1, p2 });
}
void TestDebugDisplayRequests::DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2,
[[maybe_unused]] const AZ::Vector4& col1, [[maybe_unused]] const AZ::Vector4& col2)
{
DrawPoints({ p1, p2 });
}
void TestDebugDisplayRequests::DrawLines(const AZStd::vector<AZ::Vector3>& lines, [[maybe_unused]] const AZ::Color& color)
{
DrawPoints(lines);
}
void TestDebugDisplayRequests::PushMatrix(const AZ::Transform& tm)
{
m_transforms.push(m_transforms.back() * tm);
}
void TestDebugDisplayRequests::PopMatrix()
{
if (m_transforms.size() == 1)
{
AZ_Error("TestDebugDisplayRequest", false, "Invalid call to PopMatrix when no matrices were pushed.");
}
else
{
m_transforms.pop();
}
}
} // namespace UnitTest
@@ -0,0 +1,46 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace UnitTest
{
//! Minimal implementation of DebugDisplayRequests to support testing shapes.
//! Stores a list of points based on received draw calls to delineate the exterior of the object requested to be drawn.
class TestDebugDisplayRequests : public AzFramework::DebugDisplayRequests
{
public:
TestDebugDisplayRequests();
const AZStd::vector<AZ::Vector3>& GetPoints() const;
void ClearPoints();
//! Returns the AABB of the points generated from received draw calls.
AZ::Aabb GetAabb() const;
// DebugDisplayRequests ...
void DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max) override;
void DrawSolidBox(const AZ::Vector3& min, const AZ::Vector3& max) override;
void DrawWireQuad(float width, float height) override;
void DrawQuad(float width, float height) override;
void DrawTriangles(const AZStd::vector<AZ::Vector3>& vertices, const AZ::Color& color) override;
void DrawTrianglesIndexed(const AZStd::vector<AZ::Vector3>& vertices, const AZStd::vector<AZ::u32>& indices, const AZ::Color& color) override;
void DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2) override;
void DrawLine(const AZ::Vector3& p1, const AZ::Vector3& p2, const AZ::Vector4& col1, const AZ::Vector4& col2) override;
void DrawLines(const AZStd::vector<AZ::Vector3>& lines, const AZ::Color& color) override;
void PushMatrix(const AZ::Transform& tm) override;
void PopMatrix() override;
private:
void DrawPoints(const AZStd::vector<AZ::Vector3>& points);
AZStd::vector<AZ::Vector3> m_points;
AZStd::stack<AZ::Transform> m_transforms;
};
} // namespace UnitTest
@@ -82,8 +82,6 @@ set(FILES
CommandLine/CommandLine.h
CommandLine/CommandRegistrationBus.h
Debug/DebugCameraBus.h
Engine/Engine.cpp
Engine/Engine.h
Viewport/ViewportBus.h
Viewport/ViewportBus.cpp
Viewport/ViewportColors.h
@@ -212,19 +210,41 @@ set(FILES
StreamingInstall/StreamingInstall.cpp
StreamingInstall/StreamingInstallRequests.h
StreamingInstall/StreamingInstallNotifications.h
Physics/Configuration/CollisionConfiguration.h
Physics/Configuration/CollisionConfiguration.cpp
Physics/Common/PhysicsSceneQueries.h
Physics/Common/PhysicsSceneQueries.cpp
Physics/Common/PhysicsEvents.h
Physics/Common/PhysicsSimulatedBody.h
Physics/Common/PhysicsSimulatedBody.cpp
Physics/Common/PhysicsSimulatedBodyAutomation.h
Physics/Common/PhysicsSimulatedBodyAutomation.cpp
Physics/Common/PhysicsSimulatedBodyEvents.h
Physics/Common/PhysicsSimulatedBodyEvents.cpp
Physics/Common/PhysicsTypes.h
Physics/Collision/CollisionEvents.h
Physics/Collision/CollisionEvents.cpp
Physics/Collision/CollisionLayers.h
Physics/Collision/CollisionLayers.cpp
Physics/Collision/CollisionGroups.h
Physics/Collision/CollisionGroups.cpp
Physics/Configuration/CollisionConfiguration.h
Physics/Configuration/CollisionConfiguration.cpp
Physics/Configuration/RigidBodyConfiguration.h
Physics/Configuration/RigidBodyConfiguration.cpp
Physics/Configuration/StaticRigidBodyConfiguration.h
Physics/Configuration/StaticRigidBodyConfiguration.cpp
Physics/Configuration/SceneConfiguration.h
Physics/Configuration/SceneConfiguration.cpp
Physics/Configuration/SimulatedBodyConfiguration.h
Physics/Configuration/SimulatedBodyConfiguration.cpp
Physics/Configuration/SystemConfiguration.h
Physics/Configuration/SystemConfiguration.cpp
Physics/Configuration/SceneConfiguration.cpp
Physics/Configuration/SceneConfiguration.h
Physics/SimulatedBodies/RigidBody.h
Physics/SimulatedBodies/RigidBody.cpp
Physics/SimulatedBodies/StaticRigidBody.h
Physics/SimulatedBodies/StaticRigidBody.cpp
Physics/PhysicsSystem.h
Physics/PhysicsSystem.cpp
Physics/PhysicsScene.cpp
Physics/PhysicsScene.h
Physics/AnimationConfiguration.cpp
Physics/AnimationConfiguration.h
@@ -236,20 +256,12 @@ set(FILES
Physics/Material.h
Physics/NameConstants.cpp
Physics/NameConstants.h
Physics/RigidBody.cpp
Physics/RigidBody.h
Physics/RigidBodyBus.h
Physics/Shape.cpp
Physics/Shape.h
Physics/ShapeConfiguration.h
Physics/ShapeConfiguration.cpp
Physics/SystemBus.h
Physics/World.cpp
Physics/World.h
Physics/Casts.h
Physics/Casts.cpp
Physics/WorldBody.cpp
Physics/WorldBody.h
Physics/WorldBodyBus.h
Physics/ColliderComponentBus.h
Physics/RagdollPhysicsBus.h
@@ -261,19 +273,18 @@ set(FILES
Physics/Utils.cpp
Physics/Joint.h
Physics/Joint.cpp
Physics/TriggerBus.h
Physics/CollisionNotificationBus.h
Physics/ClassConverters.cpp
Physics/ClassConverters.h
Physics/MaterialBus.h
Physics/WorldEventhandler.h
Physics/ScriptCanvasPhysicsUtils.h
Physics/ScriptCanvasPhysicsUtils.cpp
Process/ProcessCommunicator.cpp
Process/ProcessCommunicator.h
Process/ProcessWatcher.cpp
Process/ProcessWatcher.h
Process/ProcessCommon_fwd.h
Process/ProcessCommunicator.h
Process/ProcessWatcher.cpp
Process/ProcessWatcher.h
Process/ProcessCommon_fwd.h
ProjectManager/ProjectManager.h
ProjectManager/ProjectManager.cpp
Render/GameIntersectorComponent.h
@@ -283,12 +294,21 @@ set(FILES
Render/Intersector.cpp
Render/Intersector.h
Render/IntersectorInterface.h
Spawnable/RootSpawnableInterface.h
Spawnable/Spawnable.cpp
Spawnable/Spawnable.h
Spawnable/SpawnableAssetHandler.h
Spawnable/SpawnableAssetHandler.cpp
Spawnable/SpawnableEntitiesContainer.h
Spawnable/SpawnableEntitiesContainer.cpp
Spawnable/SpawnableEntitiesInterface.h
Spawnable/SpawnableEntitiesInterface.cpp
Spawnable/SpawnableEntitiesManager.h
Spawnable/SpawnableEntitiesManager.cpp
Spawnable/SpawnableMetaData.cpp
Spawnable/SpawnableMetaData.h
Spawnable/SpawnableMonitor.h
Spawnable/SpawnableMonitor.cpp
Spawnable/SpawnableSystemComponent.h
Spawnable/SpawnableSystemComponent.cpp
Terrain/TerrainDataRequestBus.h
@@ -388,6 +408,8 @@ set(FILES
FileTag/FileTagComponent.h
FileTag/FileTagComponent.cpp
UnitTest/FrameworkTestTypes.h
UnitTest/TestDebugDisplayRequests.h
UnitTest/TestDebugDisplayRequests.cpp
Slice/SliceEntityBus.h
Slice/SliceInstantiationBus.h
Slice/SliceInstantiationTicket.h
@@ -10,7 +10,6 @@
*
*/
#if AZ_TRAIT_OS_PLATFORM_APPLE
#include <errno.h>
#include <sys/ioctl.h>
#include <AzFramework/Process/ProcessCommunicator.h>
@@ -242,4 +241,3 @@ namespace AzFramework
}
} // namespace AzFramework
#endif // AZ_TRAIT_OS_PLATFORM_APPLE
@@ -11,7 +11,6 @@
*/
#if AZ_TRAIT_OS_PLATFORM_APPLE
#include <AzFramework/Process/ProcessWatcher.h>
#include <AzFramework/Process/ProcessCommunicator.h>
@@ -433,4 +432,3 @@ namespace AzFramework
}
} //namespace AzFramework
#endif // AZ_TRAIT_OS_PLATFORM_APPLE