You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp

789 lines
31 KiB
C++

/*
* 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/Interface/Interface.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Math/PackedVector3.h>
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
#include <Atom/RHI/RHIUtils.h>
#include <NvCloth/IClothSystem.h>
#include <NvCloth/IFabricCooker.h>
#include <NvCloth/IClothConfigurator.h>
#include <NvCloth/ITangentSpaceHelper.h>
#include <Components/ClothComponentMesh/ActorClothColliders.h>
#include <Components/ClothComponentMesh/ActorClothSkinning.h>
#include <Components/ClothComponentMesh/ClothConstraints.h>
#include <Components/ClothComponentMesh/ClothDebugDisplay.h>
#include <Components/ClothComponentMesh/ClothComponentMesh.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/WindBus.h>
#include <AzFramework/Physics/Common/PhysicsTypes.h>
namespace NvCloth
{
AZ_CVAR(float, cloth_DistanceToTeleport, 0.5f, nullptr, AZ::ConsoleFunctorFlags::Null,
"The amount of meters the entity has to move in a frame to consider it a teleport for cloth.");
AZ_CVAR(float, cloth_SecondsToDelaySimulationOnActorSpawned, 0.25f, nullptr, AZ::ConsoleFunctorFlags::Null,
"The amount of time in seconds the cloth simulation will be delayed to avoid sudden impulses when actors are spawned.");
// Helper class to map an RPI buffer from a buffer asset view.
template<typename T>
class MappedBuffer
{
public:
MappedBuffer(
const AZ::RPI::BufferAssetView* bufferAssetView,
const size_t expectedElementCount,
const AZ::RHI::Format expectedElementFormat);
~MappedBuffer();
T* GetBuffer()
{
return m_buffer;
}
private:
AZ::Data::Instance<AZ::RPI::Buffer> m_rpiBuffer;
T* m_buffer = nullptr;
};
template<typename T>
MappedBuffer<T>::MappedBuffer(
const AZ::RPI::BufferAssetView* bufferAssetView,
[[maybe_unused]] const size_t expectedElementCount,
[[maybe_unused]] const AZ::RHI::Format expectedElementFormat)
{
if (!bufferAssetView)
{
return;
}
const AZ::RHI::BufferViewDescriptor& bufferViewDescriptor = bufferAssetView->GetBufferViewDescriptor();
AZ_Assert(bufferViewDescriptor.m_elementCount == expectedElementCount,
"Unexpected buffer size: expected is %d but descriptor's is %d", expectedElementCount, bufferViewDescriptor.m_elementCount);
AZ_Assert(bufferViewDescriptor.m_elementSize == sizeof(T),
"Unexpected buffer element size: expected is %d but descriptor's is %d", sizeof(T), bufferViewDescriptor.m_elementSize);
AZ_Assert(bufferViewDescriptor.m_elementFormat == expectedElementFormat,
"Unexpected buffer format: expected is %d but descriptor's is %d", expectedElementFormat, bufferViewDescriptor.m_elementFormat);
const AZ::Data::Asset<AZ::RPI::BufferAsset>& bufferAsset = bufferAssetView->GetBufferAsset();
m_rpiBuffer = AZ::RPI::Buffer::FindOrCreate(bufferAsset);
if (m_rpiBuffer == nullptr)
{
AZ_Error("ClothComponentMesh", false,
"Failed to find or create RPI buffer from buffer asset '%s'", bufferAsset.GetHint().c_str());
return;
}
const uint64_t byteCount = aznumeric_cast<uint64_t>(bufferViewDescriptor.m_elementCount) * aznumeric_cast<uint64_t>(bufferViewDescriptor.m_elementSize);
const uint64_t byteOffset = aznumeric_cast<uint64_t>(bufferViewDescriptor.m_elementOffset) * aznumeric_cast<uint64_t>(bufferViewDescriptor.m_elementSize);
m_buffer = static_cast<T*>(m_rpiBuffer->Map(byteCount, byteOffset));
}
template<typename T>
MappedBuffer<T>::~MappedBuffer()
{
if (m_buffer)
{
m_rpiBuffer->Unmap();
}
}
ClothComponentMesh::ClothComponentMesh(AZ::EntityId entityId, const ClothConfiguration& config)
: m_preSimulationEventHandler(
[this](ClothId clothId, float deltaTime)
{
this->OnPreSimulation(clothId, deltaTime);
})
, m_postSimulationEventHandler(
[this](ClothId clothId, float deltaTime, const AZStd::vector<SimParticleFormat>& updatedParticles)
{
this->OnPostSimulation(clothId, deltaTime, updatedParticles);
})
{
Setup(entityId, config);
}
ClothComponentMesh::~ClothComponentMesh()
{
TearDown();
}
void ClothComponentMesh::UpdateConfiguration(AZ::EntityId entityId, const ClothConfiguration& config)
{
if (m_entityId != entityId ||
m_config.m_meshNode != config.m_meshNode ||
m_config.m_removeStaticTriangles != config.m_removeStaticTriangles)
{
Setup(entityId, config);
}
else if (m_cloth)
{
m_config = config;
ApplyConfigurationToCloth();
// Update the cloth constraints parameters
m_clothConstraints->SetMotionConstraintMaxDistance(m_config.m_motionConstraintsMaxDistance);
m_clothConstraints->SetBackstopMaxRadius(m_config.m_backstopRadius);
m_clothConstraints->SetBackstopMaxOffsets(m_config.m_backstopBackOffset, m_config.m_backstopFrontOffset);
UpdateSimulationConstraints();
// Subscribe to WindNotificationsBus only if custom wind velocity flag is not set
if (m_config.IsUsingWindBus())
{
Physics::WindNotificationsBus::Handler::BusConnect();
}
else
{
Physics::WindNotificationsBus::Handler::BusDisconnect();
}
}
}
void ClothComponentMesh::Setup(AZ::EntityId entityId, const ClothConfiguration& config)
{
TearDown();
m_entityId = entityId;
m_config = config;
if (!CreateCloth())
{
TearDown();
return;
}
// Initialize render data
m_renderDataBufferIndex = 0;
{
auto& renderData = GetRenderData();
renderData.m_particles = m_meshClothInfo.m_particles;
renderData.m_tangents = m_meshClothInfo.m_tangents;
renderData.m_bitangents = m_meshClothInfo.m_bitangents;
renderData.m_normals = m_meshClothInfo.m_normals;
}
UpdateRenderData(m_cloth->GetParticles());
// Copy the first initialized element to the rest of the buffer
for (AZ::u32 i = 1; i < RenderDataBufferSize; ++i)
{
m_renderDataBuffer[i] = m_renderDataBuffer[0];
}
// It will return a valid instance if it's an actor with cloth colliders in it.
m_actorClothColliders = ActorClothColliders::Create(m_entityId);
// It will return a valid instance if it's an actor with skinning data.
m_actorClothSkinning = ActorClothSkinning::Create(
m_entityId,
m_meshNodeInfo,
m_meshClothInfo.m_particles,
m_cloth->GetParticles().size(),
m_meshRemappedVertices);
m_timeClothSkinningUpdates = 0.0f;
m_clothConstraints = ClothConstraints::Create(
m_meshClothInfo.m_motionConstraints,
m_config.m_motionConstraintsMaxDistance,
m_meshClothInfo.m_backstopData,
m_config.m_backstopRadius,
m_config.m_backstopBackOffset,
m_config.m_backstopFrontOffset,
m_cloth->GetParticles(),
m_cloth->GetInitialIndices(),
m_meshRemappedVertices);
AZ_Assert(m_clothConstraints, "Failed to create cloth constraints");
UpdateSimulationConstraints();
#ifndef RELEASE
m_clothDebugDisplay = AZStd::make_unique<ClothDebugDisplay>(this);
#endif
AZ::TransformNotificationBus::Handler::BusConnect(m_entityId);
AZ::TickBus::Handler::BusConnect();
m_cloth->ConnectPreSimulationEventHandler(m_preSimulationEventHandler);
m_cloth->ConnectPostSimulationEventHandler(m_postSimulationEventHandler);
if (m_config.IsUsingWindBus())
{
Physics::WindNotificationsBus::Handler::BusConnect();
}
}
void ClothComponentMesh::TearDown()
{
if (m_cloth)
{
Physics::WindNotificationsBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
m_preSimulationEventHandler.Disconnect();
m_postSimulationEventHandler.Disconnect();
AZ::Interface<IClothSystem>::Get()->RemoveCloth(m_cloth);
AZ::Interface<IClothSystem>::Get()->DestroyCloth(m_cloth);
}
m_entityId.SetInvalid();
m_renderDataBuffer = {};
m_meshRemappedVertices.clear();
m_meshNodeInfo = {};
m_meshClothInfo = {};
m_actorClothColliders.reset();
m_actorClothSkinning.reset();
m_clothConstraints.reset();
m_motionConstraints.clear();
m_separationConstraints.clear();
m_clothDebugDisplay.reset();
}
void ClothComponentMesh::OnPreSimulation(
[[maybe_unused]] ClothId clothId,
float deltaTime)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
UpdateSimulationCollisions();
if (m_actorClothSkinning)
{
UpdateSimulationSkinning(deltaTime);
UpdateSimulationConstraints();
}
}
void ClothComponentMesh::OnPostSimulation(
[[maybe_unused]] ClothId clothId,
[[maybe_unused]] float deltaTime,
const AZStd::vector<SimParticleFormat>& updatedParticles)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
// Next buffer index of the render data
m_renderDataBufferIndex = (m_renderDataBufferIndex + 1) % RenderDataBufferSize;
UpdateRenderData(updatedParticles);
}
void ClothComponentMesh::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
{
// At the moment there is no way to distinguish "move" from "teleport".
// As a workaround we will consider a teleport if the position has changed considerably.
bool teleport = (m_worldPosition.GetDistance(world.GetTranslation()) >= cloth_DistanceToTeleport);
if (teleport)
{
TeleportCloth(world);
}
else
{
MoveCloth(world);
}
}
void ClothComponentMesh::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
CopyRenderDataToModel();
}
int ClothComponentMesh::GetTickOrder()
{
return AZ::TICK_PRE_RENDER;
}
void ClothComponentMesh::OnGlobalWindChanged()
{
m_cloth->GetClothConfigurator()->SetWindVelocity(GetWindBusVelocity());
}
void ClothComponentMesh::OnWindChanged([[maybe_unused]] const AZ::Aabb& aabb)
{
OnGlobalWindChanged();
}
ClothComponentMesh::RenderData& ClothComponentMesh::GetRenderData()
{
return const_cast<RenderData&>(
static_cast<const ClothComponentMesh&>(*this).GetRenderData());
}
const ClothComponentMesh::RenderData& ClothComponentMesh::GetRenderData() const
{
return m_renderDataBuffer[m_renderDataBufferIndex];
}
void ClothComponentMesh::UpdateSimulationCollisions()
{
if (m_actorClothColliders)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_actorClothColliders->Update();
const auto& spheres = m_actorClothColliders->GetSpheres();
m_cloth->GetClothConfigurator()->SetSphereColliders(spheres);
const auto& capsuleIndices = m_actorClothColliders->GetCapsuleIndices();
m_cloth->GetClothConfigurator()->SetCapsuleColliders(capsuleIndices);
}
}
void ClothComponentMesh::UpdateSimulationSkinning(float deltaTime)
{
if (m_actorClothSkinning)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_actorClothSkinning->UpdateSkinning();
// Since component activation order is not trivial, the actor's pose might not be updated
// immediately. Because of this cloth will receive a sudden impulse when changing from
// T pose to animated pose. To avoid this undesired effect we will override cloth simulation during
// a short amount of time.
m_timeClothSkinningUpdates += deltaTime;
// While the actor is not visible the skinned joints are not updated. Then when
// it becomes visible the jump to the new skinned positions causes a sudden
// impulse to cloth simulation. To avoid this undesired effect we will override cloth simulation during
// a short amount of time.
m_actorClothSkinning->UpdateActorVisibility();
if (!m_actorClothSkinning->WasActorVisible() &&
m_actorClothSkinning->IsActorVisible())
{
m_timeClothSkinningUpdates = 0.0f;
}
if (m_timeClothSkinningUpdates <= cloth_SecondsToDelaySimulationOnActorSpawned)
{
// Update skinning for all particles and apply it to cloth
AZStd::vector<SimParticleFormat> particles = m_cloth->GetParticles();
m_actorClothSkinning->ApplySkinning(m_cloth->GetInitialParticles(), particles);
m_cloth->SetParticles(AZStd::move(particles));
m_cloth->DiscardParticleDelta();
}
}
}
void ClothComponentMesh::UpdateSimulationConstraints()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_motionConstraints = m_clothConstraints->GetMotionConstraints();
m_separationConstraints = m_clothConstraints->GetSeparationConstraints();
if (m_actorClothSkinning)
{
m_actorClothSkinning->ApplySkinning(m_clothConstraints->GetMotionConstraints(), m_motionConstraints);
m_actorClothSkinning->ApplySkinning(m_clothConstraints->GetSeparationConstraints(), m_separationConstraints);
}
m_cloth->GetClothConfigurator()->SetMotionConstraints(m_motionConstraints);
if (!m_separationConstraints.empty())
{
m_cloth->GetClothConfigurator()->SetSeparationConstraints(m_separationConstraints);
}
}
void ClothComponentMesh::UpdateRenderData(const AZStd::vector<SimParticleFormat>& particles)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
if (!m_cloth)
{
return;
}
auto& renderData = GetRenderData();
if (m_actorClothSkinning)
{
// Apply skinning to the non-simulated part of the mesh.
m_actorClothSkinning->ApplySkinningOnNonSimulatedVertices(m_meshClothInfo, renderData);
}
// Calculate normals of the cloth particles (simplified mesh).
AZStd::vector<AZ::Vector3> normals;
[[maybe_unused]] bool normalsCalculated =
AZ::Interface<ITangentSpaceHelper>::Get()->CalculateNormals(particles, m_cloth->GetInitialIndices(), normals);
AZ_Assert(normalsCalculated, "Cloth component mesh failed to calculate normals.");
// Copy particles and normals to render data.
// Since cloth's vertices were welded together,
// the full mesh will result in smooth normals.
for (size_t index = 0; index < m_meshRemappedVertices.size(); ++index)
{
const int remappedIndex = m_meshRemappedVertices[index];
if (remappedIndex >= 0)
{
renderData.m_particles[index] = particles[remappedIndex];
// For static particles only use the updated normal when indicated in the configuration.
const bool useSimulatedClothParticleNormal =
m_meshClothInfo.m_particles[index].GetW() != 0.0f ||
m_config.m_updateNormalsOfStaticParticles;
if (useSimulatedClothParticleNormal)
{
renderData.m_normals[index] = normals[remappedIndex];
}
}
}
// Calculate tangents and bitangents for the full mesh.
[[maybe_unused]] bool tangentsAndBitangentsCalculated =
AZ::Interface<ITangentSpaceHelper>::Get()->CalculateTangentsAndBitagents(
renderData.m_particles, m_meshClothInfo.m_indices,
m_meshClothInfo.m_uvs, renderData.m_normals,
renderData.m_tangents, renderData.m_bitangents);
AZ_Assert(tangentsAndBitangentsCalculated, "Cloth component mesh failed to calculate tangents and bitangents.");
}
void ClothComponentMesh::CopyRenderDataToModel()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
// Previous buffer index of the render data
const AZ::u32 previousBufferIndex = (m_renderDataBufferIndex + RenderDataBufferSize - 1) % RenderDataBufferSize;
// Workaround to sync debug drawing with cloth rendering as
// the Entity Debug Display Bus renders on the next frame.
const bool isDebugDrawEnabled = m_clothDebugDisplay && m_clothDebugDisplay->IsDebugDrawEnabled();
const RenderData& renderData = (isDebugDrawEnabled)
? m_renderDataBuffer[previousBufferIndex]
: m_renderDataBuffer[m_renderDataBufferIndex];
const auto& renderParticles = renderData.m_particles;
const auto& renderNormals = renderData.m_normals;
const auto& renderTangents = renderData.m_tangents;
const auto& renderBitangents = renderData.m_bitangents;
// Since Atom has a 1:1 relation with between ModelAsset buffers and Model buffers,
// internally it created a new asset for the model instance. So it's important to
// get the asset from the model when we want to write to them, instead of getting the
// ModelAsset directly from the bus (which returns the original asset shared by all entities).
AZ::Data::Instance<AZ::RPI::Model> model;
AZ::Render::MeshComponentRequestBus::EventResult(model, m_entityId, &AZ::Render::MeshComponentRequestBus::Events::GetModel);
if (!model)
{
return;
}
AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset = model->GetModelAsset();
if (!modelAsset.IsReady())
{
return;
}
if (modelAsset->GetLodCount() < m_meshNodeInfo.m_lodLevel)
{
AZ_Error("ClothComponentMesh", false,
"Unable to access lod %d from model asset '%s' as it only has %d lod levels.",
m_meshNodeInfo.m_lodLevel,
modelAsset.GetHint().c_str(),
modelAsset->GetLodCount());
return;
}
const AZ::Data::Asset<AZ::RPI::ModelLodAsset>& modelLodAsset = modelAsset->GetLodAssets()[m_meshNodeInfo.m_lodLevel];
if (!modelLodAsset.GetId().IsValid())
{
AZ_Error("ClothComponentMesh", false,
"Model asset '%s' returns an invalid lod asset '%s' (lod level %d).",
modelAsset.GetHint().c_str(),
modelLodAsset.GetHint().c_str(),
m_meshNodeInfo.m_lodLevel);
return;
}
const AZ::Name positionSemantic("POSITION");
const AZ::Name normalSemantic("NORMAL");
const AZ::Name tangentSemantic("TANGENT");
const AZ::Name bitangentSemantic("BITANGENT");
// For each submesh...
for (const auto& subMeshInfo : m_meshNodeInfo.m_subMeshes)
{
if (modelLodAsset->GetMeshes().size() < subMeshInfo.m_primitiveIndex)
{
AZ_Error("ClothComponentMesh", false,
"Unable to access submesh %d from lod asset '%s' as it only has %d submeshes.",
subMeshInfo.m_primitiveIndex,
modelAsset.GetHint().c_str(),
modelLodAsset->GetMeshes().size());
continue;
}
const AZ::RPI::ModelLodAsset::Mesh& subMesh = modelLodAsset->GetMeshes()[subMeshInfo.m_primitiveIndex];
const int numVertices = subMeshInfo.m_numVertices;
const int firstVertex = subMeshInfo.m_verticesFirstIndex;
if (subMesh.GetVertexCount() != numVertices)
{
AZ_Error("ClothComponentMesh", false,
"Render mesh to be modified doesn't have the same number of vertices (%d) as the cloth's submesh (%d).",
subMesh.GetVertexCount(),
numVertices);
continue;
}
AZ_Assert(firstVertex >= 0, "Invalid first vertex index %d", firstVertex);
AZ_Assert((firstVertex + numVertices) <= static_cast<int>(renderParticles.size()),
"Submesh number of vertices (%d) reaches outside the particles (%zu)", (firstVertex + numVertices), renderParticles.size());
MappedBuffer<AZ::PackedVector3f> destVertices(subMesh.GetSemanticBufferAssetView(positionSemantic), numVertices, AZ::RHI::Format::R32G32B32_FLOAT);
MappedBuffer<AZ::PackedVector3f> destNormals(subMesh.GetSemanticBufferAssetView(normalSemantic), numVertices, AZ::RHI::Format::R32G32B32_FLOAT);
MappedBuffer<AZ::Vector4> destTangents(subMesh.GetSemanticBufferAssetView(tangentSemantic), numVertices, AZ::RHI::Format::R32G32B32A32_FLOAT);
MappedBuffer<AZ::PackedVector3f> destBitangents(subMesh.GetSemanticBufferAssetView(bitangentSemantic), numVertices, AZ::RHI::Format::R32G32B32_FLOAT);
auto* destVerticesBuffer = destVertices.GetBuffer();
auto* destNormalsBuffer = destNormals.GetBuffer();
auto* destTangentsBuffer = destTangents.GetBuffer();
auto* destBitangentsBuffer = destBitangents.GetBuffer();
if (!destVerticesBuffer)
{
AZ_Error("ClothComponentMesh", AZ::RHI::IsNullRenderer(),
"Invalid vertex position buffer obtained from the render mesh to be modified.");
continue;
}
for (size_t index = 0; index < numVertices; ++index)
{
const int renderVertexIndex = firstVertex + index;
const SimParticleFormat& renderParticle = renderParticles[renderVertexIndex];
destVerticesBuffer[index].Set(
renderParticle.GetX(),
renderParticle.GetY(),
renderParticle.GetZ());
if (destNormalsBuffer)
{
const AZ::Vector3& renderNormal = renderNormals[renderVertexIndex];
destNormalsBuffer[index].Set(
renderNormal.GetX(),
renderNormal.GetY(),
renderNormal.GetZ());
}
if (destTangentsBuffer)
{
const AZ::Vector3& renderTangent = renderTangents[renderVertexIndex];
destTangentsBuffer[index].Set(
renderTangent,
1.0f);
}
if (destBitangentsBuffer)
{
const AZ::Vector3& renderBitangent = renderBitangents[renderVertexIndex];
destBitangentsBuffer[index].Set(
renderBitangent.GetX(),
renderBitangent.GetY(),
renderBitangent.GetZ());
}
}
}
}
bool ClothComponentMesh::CreateCloth()
{
AZStd::unique_ptr<AssetHelper> assetHelper = AssetHelper::CreateAssetHelper(m_entityId);
if (!assetHelper)
{
return false;
}
// Obtain cloth mesh info
bool clothInfoObtained = assetHelper->ObtainClothMeshNodeInfo(m_config.m_meshNode,
m_meshNodeInfo, m_meshClothInfo);
if (!clothInfoObtained)
{
return false;
}
// Generate a simplified mesh for simulation
AZStd::vector<SimParticleFormat> meshSimplifiedParticles;
AZStd::vector<SimIndexType> meshSimplifiedIndices;
AZ::Interface<IFabricCooker>::Get()->SimplifyMesh(
m_meshClothInfo.m_particles, m_meshClothInfo.m_indices,
meshSimplifiedParticles, meshSimplifiedIndices,
m_meshRemappedVertices,
m_config.m_removeStaticTriangles);
if (meshSimplifiedParticles.empty() ||
meshSimplifiedIndices.empty())
{
return false;
}
// Cook Fabric
AZStd::optional<FabricCookedData> cookedData =
AZ::Interface<IFabricCooker>::Get()->CookFabric(meshSimplifiedParticles, meshSimplifiedIndices);
if (!cookedData)
{
return false;
}
// Create cloth instance
m_cloth = AZ::Interface<IClothSystem>::Get()->CreateCloth(meshSimplifiedParticles, *cookedData);
if (!m_cloth)
{
return false;
}
// Set initial Position and Rotation
AZ::Transform transform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(transform, m_entityId, &AZ::TransformInterface::GetWorldTM);
TeleportCloth(transform);
ApplyConfigurationToCloth();
// Add cloth to default solver to be simulated
AZ::Interface<IClothSystem>::Get()->AddCloth(m_cloth);
return true;
}
void ClothComponentMesh::ApplyConfigurationToCloth()
{
IClothConfigurator* clothConfig = m_cloth->GetClothConfigurator();
// Mass
clothConfig->SetMass(m_config.m_mass);
// Gravity and scale
if (m_config.IsUsingWorldBusGravity())
{
AZ::Vector3 gravity = AzPhysics::DefaultGravity;
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{
AzPhysics::SceneHandle defaultScene = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName);
if (defaultScene != AzPhysics::InvalidSceneHandle)
{
gravity = sceneInterface->GetGravity(defaultScene);
}
}
clothConfig->SetGravity(gravity * m_config.m_gravityScale);
}
else
{
clothConfig->SetGravity(m_config.m_customGravity * m_config.m_gravityScale);
}
// Stiffness Frequency
clothConfig->SetStiffnessFrequency(m_config.m_stiffnessFrequency);
// Motion constraints parameters
clothConfig->SetMotionConstraintsScale(m_config.m_motionConstraintsScale);
clothConfig->SetMotionConstraintsBias(m_config.m_motionConstraintsBias);
clothConfig->SetMotionConstraintsStiffness(m_config.m_motionConstraintsStiffness);
// Damping parameters
clothConfig->SetDamping(m_config.m_damping);
clothConfig->SetDampingLinearDrag(m_config.m_linearDrag);
clothConfig->SetDampingAngularDrag(m_config.m_angularDrag);
// Inertia parameters
clothConfig->SetLinearInertia(m_config.m_linearInteria);
clothConfig->SetAngularInertia(m_config.m_angularInteria);
clothConfig->SetCentrifugalInertia(m_config.m_centrifugalInertia);
// Wind parameters
if (m_config.IsUsingWindBus())
{
clothConfig->SetWindVelocity(GetWindBusVelocity());
}
else
{
clothConfig->SetWindVelocity(m_config.m_windVelocity);
}
clothConfig->SetWindDragCoefficient(m_config.m_airDragCoefficient);
clothConfig->SetWindLiftCoefficient(m_config.m_airLiftCoefficient);
clothConfig->SetWindFluidDensity(m_config.m_fluidDensity);
// Collision parameters
clothConfig->SetCollisionFriction(m_config.m_collisionFriction);
clothConfig->SetCollisionMassScale(m_config.m_collisionMassScale);
clothConfig->EnableContinuousCollision(m_config.m_continuousCollisionDetection);
clothConfig->SetCollisionAffectsStaticParticles(m_config.m_collisionAffectsStaticParticles);
// Self Collision parameters
clothConfig->SetSelfCollisionDistance(m_config.m_selfCollisionDistance);
clothConfig->SetSelfCollisionStiffness(m_config.m_selfCollisionStiffness);
// Tether Constraints parameters
clothConfig->SetTetherConstraintStiffness(m_config.m_tetherConstraintStiffness);
clothConfig->SetTetherConstraintScale(m_config.m_tetherConstraintScale);
// Quality parameters
clothConfig->SetSolverFrequency(m_config.m_solverFrequency);
clothConfig->SetAcceleationFilterWidth(m_config.m_accelerationFilterIterations);
// Fabric Phases
clothConfig->SetVerticalPhaseConfig(
m_config.m_verticalStiffness,
m_config.m_verticalStiffnessMultiplier,
m_config.m_verticalCompressionLimit,
m_config.m_verticalStretchLimit);
clothConfig->SetHorizontalPhaseConfig(
m_config.m_horizontalStiffness,
m_config.m_horizontalStiffnessMultiplier,
m_config.m_horizontalCompressionLimit,
m_config.m_horizontalStretchLimit);
clothConfig->SetBendingPhaseConfig(
m_config.m_bendingStiffness,
m_config.m_bendingStiffnessMultiplier,
m_config.m_bendingCompressionLimit,
m_config.m_bendingStretchLimit);
clothConfig->SetShearingPhaseConfig(
m_config.m_shearingStiffness,
m_config.m_shearingStiffnessMultiplier,
m_config.m_shearingCompressionLimit,
m_config.m_shearingStretchLimit);
}
void ClothComponentMesh::MoveCloth(const AZ::Transform& worldTransform)
{
m_worldPosition = worldTransform.GetTranslation();
m_cloth->GetClothConfigurator()->SetTransform(worldTransform);
if (m_config.IsUsingWindBus())
{
// Wind velocity is affected by world position
m_cloth->GetClothConfigurator()->SetWindVelocity(GetWindBusVelocity());
}
}
void ClothComponentMesh::TeleportCloth(const AZ::Transform& worldTransform)
{
MoveCloth(worldTransform);
// By clearing inertia the cloth won't be affected by the sudden translation caused when teleporting the entity.
m_cloth->GetClothConfigurator()->ClearInertia();
}
AZ::Vector3 ClothComponentMesh::GetWindBusVelocity()
{
const Physics::WindRequests* windRequests = AZ::Interface<Physics::WindRequests>::Get();
if (windRequests)
{
const AZ::Vector3 globalWind = windRequests->GetGlobalWind();
const AZ::Vector3 localWind = windRequests->GetWind(m_worldPosition);
return globalWind + localWind;
}
return AZ::Vector3::CreateZero();
}
} // namespace NvCloth