Files
o3de/Gems/NvCloth/Code/Source/Components/ClothComponentMesh/ClothComponentMesh.cpp
T
Aaron Ruiz Mora 0b551a0c21 Fixed cloth simulation generating incorrect normals
Fixed cloth simulation generating incorrect normals.
Also added option in Cloth Component to update normals of static particles (disabled by default).
User will want to keep the option disabled when continuity of the normals is important, like in characters, but for environmental cloth the mesh could look better when the normals of static particles are updated too.

Enable 'Update normals of static particles' option on blind slices of  Nvcloth gem

Fix unit tests
2021-05-17 09:53:14 +01:00

786 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 <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.");
// 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_numberOfClothSkinningUpdates = 0;
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,
[[maybe_unused]] float deltaTime)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
UpdateSimulationCollisions();
if (m_actorClothSkinning)
{
UpdateSimulationSkinning();
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()
{
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 frames.
const AZ::u32 numberOfTicksToDoFullSkinning = 10;
m_numberOfClothSkinningUpdates++;
// 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 frames.
m_actorClothSkinning->UpdateActorVisibility();
if (!m_actorClothSkinning->WasActorVisible() &&
m_actorClothSkinning->IsActorVisible())
{
m_numberOfClothSkinningUpdates = 0;
}
if (m_numberOfClothSkinningUpdates <= numberOfTicksToDoFullSkinning)
{
// 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", false,
"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