Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,83 @@
/*
* 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 <ISystem.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Components/ClothComponent.h>
namespace NvCloth
{
void ClothComponent::Reflect(AZ::ReflectContext* context)
{
ClothConfiguration::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ClothComponent, AZ::Component>()
->Version(0)
->Field("ClothConfiguration", &ClothComponent::m_config)
;
}
}
ClothComponent::ClothComponent(const ClothConfiguration& config)
: m_config(config)
{
}
void ClothComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ClothMeshService", 0x6ffcbca5));
}
void ClothComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("MeshService", 0x71d8a455));
required.push_back(AZ_CRC("TransformService", 0x8ee22c50));
}
void ClothComponent::Activate()
{
// Cloth components do not run on dedicated servers.
AZ_Assert(gEnv, "Environment not ready");
if (gEnv->IsDedicated())
{
return;
}
LmbrCentral::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId());
}
void ClothComponent::Deactivate()
{
LmbrCentral::MeshComponentNotificationBus::Handler::BusDisconnect();
m_clothComponentMesh.reset();
}
void ClothComponent::OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (!asset.IsReady())
{
return;
}
m_clothComponentMesh = AZStd::make_unique<ClothComponentMesh>(GetEntityId(), m_config);
}
void ClothComponent::OnMeshDestroyed()
{
m_clothComponentMesh.reset();
}
} // namespace NvCloth
@@ -0,0 +1,58 @@
/*
* 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/Component.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <Components/ClothConfiguration.h>
#include <Components/ClothComponentMesh/ClothComponentMesh.h>
namespace NvCloth
{
//! Class for runtime Cloth Component.
class ClothComponent
: public AZ::Component
, public LmbrCentral::MeshComponentNotificationBus::Handler
{
public:
AZ_COMPONENT(ClothComponent, "{AC9B8FA0-A6DA-4377-8219-25BA7E4A22E9}");
static void Reflect(AZ::ReflectContext* context);
ClothComponent() = default;
explicit ClothComponent(const ClothConfiguration& config);
AZ_DISABLE_COPY_MOVE(ClothComponent);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
const ClothComponentMesh* GetClothComponentMesh() const { return m_clothComponentMesh.get(); }
protected:
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
// LmbrCentral::MeshComponentNotificationBus::Handler overrides ...
void OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
void OnMeshDestroyed() override;
private:
ClothConfiguration m_config;
AZStd::unique_ptr<ClothComponentMesh> m_clothComponentMesh;
};
} // namespace NvCloth
@@ -0,0 +1,267 @@
/*
* 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 <Integration/ActorComponentBus.h>
#include <Components/ClothComponentMesh/ActorClothColliders.h>
namespace NvCloth
{
namespace Internal
{
extern const size_t NvClothMaxNumSphereColliders = 32;
extern const size_t NvClothMaxNumCapsuleColliders = 32;
SphereCollider CreateSphereCollider(
const Physics::ColliderConfiguration* colliderConfig,
const Physics::SphereShapeConfiguration* sphereShapeConfig,
int jointIndex, int sphereIndex)
{
SphereCollider sphereCollider;
sphereCollider.m_jointIndex = jointIndex;
sphereCollider.m_offsetTransform =
AZ::Transform::CreateFromQuaternionAndTranslation(
colliderConfig->m_rotation,
colliderConfig->m_position);
sphereCollider.m_radius = sphereShapeConfig->m_radius;
sphereCollider.m_nvSphereIndex = sphereIndex;
return sphereCollider;
}
CapsuleCollider CreateCapsuleCollider(
const Physics::ColliderConfiguration* colliderConfig,
const Physics::CapsuleShapeConfiguration* capsuleShapeConfig,
int jointIndex, int capsuleIndex, int sphereAIndex, int sphereBIndex)
{
CapsuleCollider capsuleCollider;
capsuleCollider.m_jointIndex = jointIndex;
capsuleCollider.m_offsetTransform =
AZ::Transform::CreateFromQuaternionAndTranslation(
colliderConfig->m_rotation,
colliderConfig->m_position);
capsuleCollider.m_radius = capsuleShapeConfig->m_radius;
capsuleCollider.m_height = capsuleShapeConfig->m_height;
capsuleCollider.m_capsuleIndex = capsuleIndex;
capsuleCollider.m_sphereAIndex = sphereAIndex;
capsuleCollider.m_sphereBIndex = sphereBIndex;
return capsuleCollider;
}
}
AZStd::unique_ptr<ActorClothColliders> ActorClothColliders::Create(AZ::EntityId entityId)
{
Physics::AnimationConfiguration* actorPhysicsConfig = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
actorPhysicsConfig, entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetPhysicsConfig);
if (!actorPhysicsConfig)
{
return nullptr;
}
const Physics::CharacterColliderConfiguration& clothConfig = actorPhysicsConfig->m_clothConfig;
// Maximum number of spheres and capsules is imposed by NvCloth library
size_t sphereCount = 0;
size_t capsuleCount = 0;
bool maxSphereCountReachedWarned = false;
bool maxCapsuleCountReachedWarned = false;
AZStd::vector<SphereCollider> sphereColliders;
AZStd::vector<CapsuleCollider> capsuleColliders;
for (const Physics::CharacterColliderNodeConfiguration& clothNodeConfig : clothConfig.m_nodes)
{
size_t jointIndex = EMotionFX::Integration::ActorComponentRequests::s_invalidJointIndex;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
jointIndex, entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetJointIndexByName,
clothNodeConfig.m_name.c_str());
if (jointIndex == EMotionFX::Integration::ActorComponentRequests::s_invalidJointIndex)
{
AZ_Warning("ActorAssetHelper", false, "Joint '%s' not found", clothNodeConfig.m_name.c_str());
continue;
}
for (const Physics::ShapeConfigurationPair& shapeConfigPair : clothNodeConfig.m_shapes)
{
const auto& colliderConfig = shapeConfigPair.first;
switch (shapeConfigPair.second->GetShapeType())
{
case Physics::ShapeType::Sphere:
{
if (sphereCount >= Internal::NvClothMaxNumSphereColliders)
{
AZ_Warning("ActorAssetHelper", maxSphereCountReachedWarned,
"Maximum number of cloth sphere colliders (%zu) reached",
Internal::NvClothMaxNumSphereColliders);
maxSphereCountReachedWarned = true;
continue;
}
SphereCollider sphereCollider = Internal::CreateSphereCollider(
colliderConfig.get(),
static_cast<const Physics::SphereShapeConfiguration*>(shapeConfigPair.second.get()),
static_cast<int>(jointIndex),
sphereCount);
sphereColliders.push_back(sphereCollider);
++sphereCount;
}
break;
case Physics::ShapeType::Capsule:
{
if (capsuleCount >= Internal::NvClothMaxNumCapsuleColliders)
{
AZ_Warning("ActorAssetHelper", maxCapsuleCountReachedWarned,
"Maximum number of cloth capsule colliders (%zu) reached",
Internal::NvClothMaxNumCapsuleColliders);
maxCapsuleCountReachedWarned = true;
continue;
}
// If there is only 1 sphere left to reach the maximum number
// of spheres the capsule won't fit as each capsule is formed of 2 spheres.
if (sphereCount >= Internal::NvClothMaxNumSphereColliders - 1)
{
AZ_Warning("ActorAssetHelper", maxCapsuleCountReachedWarned,
"Maximum number of cloth capsule colliders reached");
maxCapsuleCountReachedWarned = true;
continue;
}
CapsuleCollider capsuleCollider = Internal::CreateCapsuleCollider(
colliderConfig.get(),
static_cast<const Physics::CapsuleShapeConfiguration*>(shapeConfigPair.second.get()),
static_cast<int>(jointIndex),
capsuleCount * 2, // Each capsule holds 2 sphere indices
sphereCount + 0, // First sphere index
sphereCount + 1); // Second sphere index
capsuleColliders.push_back(capsuleCollider);
++capsuleCount;
sphereCount += 2; // Adds 2 spheres per capsule
}
break;
default:
AZ_Warning("ActorAssetHelper", false, "Joint '%s' has an unexpected shape type (%u) for cloth collider.",
clothNodeConfig.m_name.c_str(), static_cast<AZ::u8>(shapeConfigPair.second->GetShapeType()));
break;
}
}
}
if (sphereCount == 0 && capsuleCount == 0)
{
return nullptr;
}
AZStd::unique_ptr<ActorClothColliders> actorClothColliders = AZStd::make_unique<ActorClothColliders>(entityId);
actorClothColliders->m_sphereColliders = AZStd::move(sphereColliders);
actorClothColliders->m_capsuleColliders = AZStd::move(capsuleColliders);
actorClothColliders->m_spheres.resize(sphereCount);
actorClothColliders->m_capsuleIndices.resize(capsuleCount * 2); // 2 sphere indices per capsule
for (const auto& capsuleCollider : actorClothColliders->m_capsuleColliders)
{
actorClothColliders->m_capsuleIndices[capsuleCollider.m_capsuleIndex + 0] = capsuleCollider.m_sphereAIndex;
actorClothColliders->m_capsuleIndices[capsuleCollider.m_capsuleIndex + 1] = capsuleCollider.m_sphereBIndex;
}
// Calculates the current transforms for the colliders
// and fills the data as nvcloth needs them, ready to be
// queried by the cloth component.
actorClothColliders->Update();
return actorClothColliders;
}
ActorClothColliders::ActorClothColliders(AZ::EntityId entityId)
: m_entityId(entityId)
{
}
void ActorClothColliders::Update()
{
for (auto& sphereCollider : m_sphereColliders)
{
AZ::Transform jointModelSpaceTransform = AZ::Transform::Identity();
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
jointModelSpaceTransform, m_entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetJointTransform,
static_cast<size_t>(sphereCollider.m_jointIndex), EMotionFX::Integration::Space::ModelSpace);
sphereCollider.m_currentModelSpaceTransform = jointModelSpaceTransform * sphereCollider.m_offsetTransform;
UpdateSphere(sphereCollider);
}
for (auto& capsuleCollider : m_capsuleColliders)
{
AZ::Transform jointModelSpaceTransform = AZ::Transform::Identity();
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
jointModelSpaceTransform, m_entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetJointTransform,
static_cast<size_t>(capsuleCollider.m_jointIndex), EMotionFX::Integration::Space::ModelSpace);
capsuleCollider.m_currentModelSpaceTransform = jointModelSpaceTransform * capsuleCollider.m_offsetTransform;
UpdateCapsule(capsuleCollider);
}
}
void ActorClothColliders::UpdateSphere(const SphereCollider& sphere)
{
const AZ::Vector3 spherePosition = sphere.m_currentModelSpaceTransform.GetTranslation();
AZ_Assert(sphere.m_nvSphereIndex != InvalidIndex, "Sphere collider has invalid index");
m_spheres[sphere.m_nvSphereIndex].Set(spherePosition, sphere.m_radius);
}
void ActorClothColliders::UpdateCapsule(const CapsuleCollider& capsule)
{
const float halfHeightExclusive = 0.5f * capsule.m_height - capsule.m_radius;
const AZ::Vector3 basisZ = capsule.m_currentModelSpaceTransform.GetBasisZ() * halfHeightExclusive;
const AZ::Vector3 capsulePosition = capsule.m_currentModelSpaceTransform.GetTranslation();
const AZ::Vector3 sphereAPosition = capsulePosition + basisZ;
const AZ::Vector3 sphereBPosition = capsulePosition - basisZ;
AZ_Assert(capsule.m_sphereAIndex != InvalidIndex, "Capsule collider has an invalid index for its first sphere");
AZ_Assert(capsule.m_sphereBIndex != InvalidIndex, "Capsule collider has an invalid index for its second sphere");
m_spheres[capsule.m_sphereAIndex].Set(sphereAPosition, capsule.m_radius);
m_spheres[capsule.m_sphereBIndex].Set(sphereBPosition, capsule.m_radius);
}
const AZStd::vector<SphereCollider>& ActorClothColliders::GetSphereColliders() const
{
return m_sphereColliders;
}
const AZStd::vector<CapsuleCollider>& ActorClothColliders::GetCapsuleColliders() const
{
return m_capsuleColliders;
}
const AZStd::vector<AZ::Vector4>& ActorClothColliders::GetSpheres() const
{
return m_spheres;
}
const AZStd::vector<uint32_t>& ActorClothColliders::GetCapsuleIndices() const
{
return m_capsuleIndices;
}
} // namespace NvCloth
@@ -0,0 +1,104 @@
/*
* 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/std/containers/vector.h>
#include <AzCore/Math/Transform.h>
namespace NvCloth
{
extern const int InvalidIndex;
//! Base collider class with transform and joint information.
struct Collider
{
//! Offset transform relative to the joint attached.
AZ::Transform m_offsetTransform = AZ::Transform::CreateIdentity();
//! Current transform in model space after animation applied.
AZ::Transform m_currentModelSpaceTransform = AZ::Transform::CreateIdentity();
//! Joint this collider is attached to.
int m_jointIndex = InvalidIndex;
};
//! Describes the shape on an sphere collider.
struct SphereCollider
: public Collider
{
//! Radius of the sphere.
float m_radius = 0.0f;
int m_nvSphereIndex = InvalidIndex; //!< Identifies the sphere within m_spheres in ActorClothColliders.
};
//! Describes the shape on an sphere collider.
struct CapsuleCollider
: public Collider
{
//! Height of the capsule.
float m_height = 0.0f;
//! Radius of the capsule.
float m_radius = 0.0f;
int m_capsuleIndex = InvalidIndex; //!< Identifies first index of the capsule within m_capsuleIndices in ActorClothColliders.
int m_sphereAIndex = InvalidIndex; //!< Identifies the first sphere within m_spheres in ActorClothColliders.
int m_sphereBIndex = InvalidIndex; //!< Identifies the second sphere within m_spheres in ActorClothColliders.
};
//! Class to retrieve cloth colliders information from an actor on the same entity
//! and updates their transform from skinning animation.
//!
//! @note There is a limit of 32 sphere colliders and 32 capsule colliders.
//! In the case that all capsules use unique spheres then the maximum
//! number of capsule would go down to 16, limited by the maximum number of spheres (32).
class ActorClothColliders
{
public:
AZ_TYPE_INFO(ActorClothColliders, "{EA2D9B6A-2493-4B6A-972E-BB639E16798E}");
static AZStd::unique_ptr<ActorClothColliders> Create(AZ::EntityId entityId);
explicit ActorClothColliders(AZ::EntityId entityId);
//! Updates the colliders' transforms with the current pose of the actor.
void Update();
const AZStd::vector<SphereCollider>& GetSphereColliders() const;
const AZStd::vector<CapsuleCollider>& GetCapsuleColliders() const;
const AZStd::vector<AZ::Vector4>& GetSpheres() const;
const AZStd::vector<uint32_t>& GetCapsuleIndices() const;
private:
void UpdateSphere(const SphereCollider& sphere);
void UpdateCapsule(const CapsuleCollider& capsule);
AZ::EntityId m_entityId;
// Configuration data of spheres and capsules, describing their shape and transforms relative to joints.
AZStd::vector<SphereCollider> m_sphereColliders;
AZStd::vector<CapsuleCollider> m_capsuleColliders;
// The current positions and radius of sphere colliders.
// Every update, these positions are computed with the current pose of the actor.
// Note: The spheres used to formed capsules are also part of this list.
AZStd::vector<AZ::Vector4> m_spheres;
// The sphere collider indices associated with capsules.
// Each capsule is 2 indices within the list.
AZStd::vector<uint32_t> m_capsuleIndices;
};
} // namespace NvCloth
@@ -0,0 +1,462 @@
/*
* 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 <Cry_Math.h> // Needed for DualQuat
#include <MathConversion.h>
#include <Integration/ActorComponentBus.h>
// Needed to access the Mesh information inside Actor.
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Source/SkinningInfoVertexAttributeLayer.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <Components/ClothComponentMesh/ActorClothSkinning.h>
namespace NvCloth
{
namespace Internal
{
bool ObtainSkinningData(
AZ::EntityId entityId,
const AZStd::string& meshNode,
const size_t numSimParticles,
const AZStd::vector<int>& meshRemappedVertices,
AZStd::vector<SkinningInfo>& skinningData)
{
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return false;
}
const EMotionFX::Actor* actor = actorInstance->GetActor();
if (!actor)
{
return false;
}
const uint32 numNodes = actor->GetNumNodes();
const uint32 numLODs = actor->GetNumLODLevels();
const EMotionFX::Mesh* emfxMesh = nullptr;
// Find the render data of the mesh node
for (uint32 lodLevel = 0; lodLevel < numLODs; ++lodLevel)
{
for (uint32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
const EMotionFX::Mesh* mesh = actor->GetMesh(lodLevel, nodeIndex);
if (!mesh || mesh->GetIsCollisionMesh())
{
// Skip invalid and collision meshes.
continue;
}
const EMotionFX::Node* node = actor->GetSkeleton()->GetNode(nodeIndex);
if (meshNode != node->GetNameString())
{
// Skip nodes other than the one we're looking for.
continue;
}
emfxMesh = mesh;
break;
}
if (emfxMesh)
{
break;
}
}
if (!emfxMesh)
{
return false;
}
const AZ::u32* sourceOriginalVertex = static_cast<AZ::u32*>(emfxMesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_ORGVTXNUMBERS));
EMotionFX::SkinningInfoVertexAttributeLayer* sourceSkinningInfo =
static_cast<EMotionFX::SkinningInfoVertexAttributeLayer*>(
emfxMesh->FindSharedVertexAttributeLayer(EMotionFX::SkinningInfoVertexAttributeLayer::TYPE_ID));
if (!sourceOriginalVertex || !sourceSkinningInfo)
{
return false;
}
const int numVertices = emfxMesh->GetNumVertices();
if (numVertices == 0)
{
AZ_Error("ActorClothSkinning", false, "Invalid mesh data");
return false;
}
if (meshRemappedVertices.size() != numVertices)
{
AZ_Error("ActorClothSkinning", false,
"Number of vertices (%d) doesn't match the mesh remapping size (%zu)",
numVertices, meshRemappedVertices.size());
return false;
}
skinningData.resize(numSimParticles);
for (int index = 0; index < numVertices; ++index)
{
const int skinnedDataIndex = meshRemappedVertices[index];
if (skinnedDataIndex < 0)
{
// Removed particle
continue;
}
SkinningInfo& skinningInfo = skinningData[skinnedDataIndex];
const AZ::u32 originalVertex = sourceOriginalVertex[index];
const AZ::u32 influenceCount = AZ::GetMin<AZ::u32>(MaxSkinningBones, sourceSkinningInfo->GetNumInfluences(originalVertex));
AZ::u32 influenceIndex = 0;
AZ::u8 weightError = 255;
for (; influenceIndex < influenceCount; ++influenceIndex)
{
EMotionFX::SkinInfluence* influence = sourceSkinningInfo->GetInfluence(originalVertex, influenceIndex);
skinningInfo.m_jointIndices[influenceIndex] = influence->GetNodeNr();
skinningInfo.m_jointWeights[influenceIndex] = static_cast<AZ::u8>(AZ::GetClamp<float>(influence->GetWeight() * 255.0f, 0.0f, 255.0f));
if (skinningInfo.m_jointWeights[influenceIndex] >= weightError)
{
skinningInfo.m_jointWeights[influenceIndex] = weightError;
weightError = 0;
influenceIndex++;
break;
}
else
{
weightError -= skinningInfo.m_jointWeights[influenceIndex];
}
}
skinningInfo.m_jointWeights[0] += weightError;
for (; influenceIndex < MaxSkinningBones; ++influenceIndex)
{
skinningInfo.m_jointIndices[influenceIndex] = 0;
skinningInfo.m_jointWeights[influenceIndex] = 0;
}
}
return true;
}
EMotionFX::Integration::SkinningMethod ObtainSkinningMethod(AZ::EntityId entityId)
{
EMotionFX::Integration::SkinningMethod skinningMethod =
EMotionFX::Integration::SkinningMethod::DualQuat;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(skinningMethod, entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetSkinningMethod);
return skinningMethod;
}
const AZ::Matrix3x4* ObtainSkinningMatrices(AZ::EntityId entityId)
{
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return nullptr;
}
const EMotionFX::TransformData* transformData = actorInstance->GetTransformData();
if (!transformData)
{
return nullptr;
}
return transformData->GetSkinningMatrices();
}
AZStd::unordered_map<AZ::u16, DualQuat> ObtainSkinningDualQuaternions(
AZ::EntityId entityId,
const AZStd::vector<AZ::u16>& jointIndices)
{
const AZ::Matrix3x4* skinningMatrices = ObtainSkinningMatrices(entityId);
if (!skinningMatrices)
{
return {};
}
AZStd::unordered_map<AZ::u16, DualQuat> skinningDualQuaternions;
for (AZ::u16 jointIndex : jointIndices)
{
skinningDualQuaternions.emplace(jointIndex, AZMatrix3x4ToLYMatrix3x4(skinningMatrices[jointIndex]));
}
return skinningDualQuaternions;
}
}
// Specialized class that applies linear blending skinning
class ActorClothSkinningLinear
: public ActorClothSkinning
{
public:
explicit ActorClothSkinningLinear(AZ::EntityId entityId)
: ActorClothSkinning(entityId)
{
}
// ActorClothSkinning overrides ...
void UpdateSkinning() override;
void ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions) override;
private:
AZ::Vector3 ComputeSkinnedPosition(
const AZ::Vector3& originalPosition,
const SkinningInfo& skinningInfo,
const AZ::Matrix3x4* skinningMatrices);
const AZ::Matrix3x4* m_skinningMatrices = nullptr;
};
void ActorClothSkinningLinear::UpdateSkinning()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_skinningMatrices = Internal::ObtainSkinningMatrices(m_entityId);
}
void ActorClothSkinningLinear::ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions)
{
if (!m_skinningMatrices)
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
for (size_t index = 0; index < originalPositions.size(); ++index)
{
const AZ::Vector3 skinnedPosition = ComputeSkinnedPosition(
originalPositions[index].GetAsVector3(),
m_skinningData[index],
m_skinningMatrices);
// Avoid overwriting the w component
positions[index].Set(skinnedPosition, positions[index].GetW());
}
}
AZ::Vector3 ActorClothSkinningLinear::ComputeSkinnedPosition(
const AZ::Vector3& originalPosition,
const SkinningInfo& skinningInfo,
const AZ::Matrix3x4* skinningMatrices)
{
AZ::Matrix3x4 clothSkinningMatrix = AZ::Matrix3x4::CreateZero();
for (int weightIndex = 0; weightIndex < MaxSkinningBones; ++weightIndex)
{
if (skinningInfo.m_jointWeights[weightIndex] == 0)
{
continue;
}
const AZ::u16 jointIndex = skinningInfo.m_jointIndices[weightIndex];
const float jointWeight = skinningInfo.m_jointWeights[weightIndex] / 255.0f;
// Blending matrices the same way done in GPU shaders, by adding each weighted matrix element by element.
// This way the skinning results are much similar to the skinning performed in GPU.
for (int i = 0; i < 3; ++i)
{
clothSkinningMatrix.SetRow(i, clothSkinningMatrix.GetRow(i) + skinningMatrices[jointIndex].GetRow(i) * jointWeight);
}
}
return clothSkinningMatrix * originalPosition;
}
// Specialized class that applies dual quaternion blending skinning
class ActorClothSkinningDualQuaternion
: public ActorClothSkinning
{
public:
explicit ActorClothSkinningDualQuaternion(AZ::EntityId entityId)
: ActorClothSkinning(entityId)
{
}
// ActorClothSkinning overrides ...
void UpdateSkinning() override;
void ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions) override;
private:
AZ::Vector3 ComputeSkinnedPosition(
const AZ::Vector3& originalPosition,
const SkinningInfo& skinningInfo,
const AZStd::unordered_map<AZ::u16, DualQuat>& skinningDualQuaternions);
AZStd::unordered_map<AZ::u16, DualQuat> m_skinningDualQuaternions;
};
void ActorClothSkinningDualQuaternion::UpdateSkinning()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_skinningDualQuaternions = Internal::ObtainSkinningDualQuaternions(m_entityId, m_jointIndices);
}
void ActorClothSkinningDualQuaternion::ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions)
{
if (m_skinningDualQuaternions.empty())
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
for (size_t index = 0; index < originalPositions.size(); ++index)
{
const AZ::Vector3 skinnedPosition = ComputeSkinnedPosition(
originalPositions[index].GetAsVector3(),
m_skinningData[index],
m_skinningDualQuaternions);
// Avoid overwriting the w component
positions[index].Set(skinnedPosition, positions[index].GetW());
}
}
AZ::Vector3 ActorClothSkinningDualQuaternion::ComputeSkinnedPosition(
const AZ::Vector3& originalPosition,
const SkinningInfo& skinningInfo,
const AZStd::unordered_map<AZ::u16, DualQuat>& skinningDualQuaternions)
{
DualQuat clothSkinningDualQuaternion(type_zero::ZERO);
for (int weightIndex = 0; weightIndex < MaxSkinningBones; ++weightIndex)
{
if (skinningInfo.m_jointWeights[weightIndex] == 0)
{
continue;
}
const AZ::u16 jointIndex = skinningInfo.m_jointIndices[weightIndex];
const float jointWeight = skinningInfo.m_jointWeights[weightIndex] / 255.0f;
clothSkinningDualQuaternion += skinningDualQuaternions.at(jointIndex) * jointWeight;
}
clothSkinningDualQuaternion.Normalize();
return LYVec3ToAZVec3(clothSkinningDualQuaternion * AZVec3ToLYVec3(originalPosition));
}
AZStd::unique_ptr<ActorClothSkinning> ActorClothSkinning::Create(
AZ::EntityId entityId,
const AZStd::string& meshNode,
const size_t numSimParticles,
const AZStd::vector<int>& meshRemappedVertices)
{
AZStd::vector<SkinningInfo> skinningData;
if (!Internal::ObtainSkinningData(entityId, meshNode, numSimParticles, meshRemappedVertices, skinningData))
{
return nullptr;
}
if (numSimParticles != skinningData.size())
{
AZ_Error("ActorClothSkinning", false,
"Number of simulation particles (%zu) doesn't match with skinning data obtained (%zu)",
numSimParticles, skinningData.size());
return nullptr;
}
AZStd::unique_ptr<ActorClothSkinning> actorClothSkinning;
const auto skinningMethod = Internal::ObtainSkinningMethod(entityId);
switch (skinningMethod)
{
case EMotionFX::Integration::SkinningMethod::DualQuat:
actorClothSkinning = AZStd::make_unique<ActorClothSkinningDualQuaternion>(entityId);
break;
case EMotionFX::Integration::SkinningMethod::Linear:
actorClothSkinning = AZStd::make_unique<ActorClothSkinningLinear>(entityId);
break;
default:
AZ_Error("ActorClothSkinning", false,
"Unknown skinning method (%u).", static_cast<AZ::u32>(skinningMethod));
return nullptr;
}
// Insert the indices of the joints that influence the particle (weight is not 0)
AZStd::set<AZ::u16> jointIndices;
for (size_t particleIndex = 0; particleIndex < numSimParticles; ++particleIndex)
{
for (int weightIndex = 0; weightIndex < MaxSkinningBones; ++weightIndex)
{
if (skinningData[particleIndex].m_jointWeights[weightIndex] == 0)
{
continue;
}
const AZ::u16 jointIndex = skinningData[particleIndex].m_jointIndices[weightIndex];
jointIndices.insert(jointIndex);
}
}
actorClothSkinning->m_jointIndices.assign(jointIndices.begin(), jointIndices.end());
actorClothSkinning->m_skinningData = AZStd::move(skinningData);
return actorClothSkinning;
}
ActorClothSkinning::ActorClothSkinning(AZ::EntityId entityId)
: m_entityId(entityId)
{
}
void ActorClothSkinning::UpdateActorVisibility()
{
bool isVisible = true;
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(actorInstance, m_entityId,
&EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (actorInstance)
{
isVisible = actorInstance->GetIsVisible();
}
m_wasActorVisible = m_isActorVisible;
m_isActorVisible = isVisible;
}
bool ActorClothSkinning::IsActorVisible() const
{
return m_isActorVisible;
}
bool ActorClothSkinning::WasActorVisible() const
{
return m_wasActorVisible;
}
} // namespace NvCloth
@@ -0,0 +1,82 @@
/*
* 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/Entity.h>
#include <NvCloth/Types.h>
namespace NvCloth
{
//! Maximum number of bones that can influence a particle.
static const int MaxSkinningBones = 4;
//! Skinning information of a particle.
struct SkinningInfo
{
//! Weights of each joint that influence the particle.
AZStd::array<AZ::u8, MaxSkinningBones> m_jointWeights;
//! List of joints that influence the particle.
AZStd::array<AZ::u16, MaxSkinningBones> m_jointIndices;
};
//! Class to retrieve skinning information from an actor on the same entity
//! and use that data to apply skinning to vertices.
class ActorClothSkinning
{
public:
AZ_TYPE_INFO(ActorClothSkinning, "{3E7C664D-096B-4126-8553-3241BA965533}");
virtual ~ActorClothSkinning() = default;
static AZStd::unique_ptr<ActorClothSkinning> Create(
AZ::EntityId entityId,
const AZStd::string& meshNode,
const size_t numSimParticles,
const AZStd::vector<int>& meshRemappedVertices);
explicit ActorClothSkinning(AZ::EntityId entityId);
//! Updates skinning with the current pose of the actor.
virtual void UpdateSkinning() = 0;
//! Applies skinning to a list of positions.
//! @note w components are not affected.
virtual void ApplySkinning(
const AZStd::vector<AZ::Vector4>& originalPositions,
AZStd::vector<AZ::Vector4>& positions) = 0;
//! Updates visibility variables.
void UpdateActorVisibility();
//! Returns true if actor is currently visible on screen.
bool IsActorVisible() const;
//! Returns true if actor was visible on screen in previous update.
bool WasActorVisible() const;
protected:
AZ::EntityId m_entityId;
// Skinning information of all particles
AZStd::vector<SkinningInfo> m_skinningData;
// Collection of skeleton joint indices that influence the particles
AZStd::vector<AZ::u16> m_jointIndices;
// Visibility variables
bool m_wasActorVisible = false;
bool m_isActorVisible = false;
};
}// namespace NvCloth
@@ -0,0 +1,753 @@
/*
* 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/World.h>
#include <AzFramework/Physics/WindBus.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;
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_config.m_meshNode, 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);
// Calculate normals of the cloth particles (simplified mesh).
AZStd::vector<AZ::Vector3> normals;
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.
auto& renderData = GetRenderData();
renderData.m_particles.resize_no_construct(m_meshRemappedVertices.size());
renderData.m_normals.resize_no_construct(m_meshRemappedVertices.size());
for (size_t index = 0; index < m_meshRemappedVertices.size(); ++index)
{
const int remappedIndex = m_meshRemappedVertices[index];
if (remappedIndex < 0)
{
// Removed particle. Assign initial values to have something valid during tangents and bitangents calculation.
renderData.m_particles[index] = m_meshClothInfo.m_particles[index];
renderData.m_normals[index] = AZ::Vector3::CreateAxisZ();
}
else
{
renderData.m_particles[index] = particles[remappedIndex];
renderData.m_normals[index] = normals[remappedIndex];
}
}
// Calculate tangents and bitangents for the full mesh.
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;
AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset;
AZ::Render::MeshComponentRequestBus::EventResult(modelAsset, m_entityId,
&AZ::Render::MeshComponentRequestBus::Events::GetModelAsset);
if (!modelAsset.GetId().IsValid())
{
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];
int numVertices = subMeshInfo.m_numVertices;
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;
if (m_meshRemappedVertices[renderVertexIndex] < 0)
{
// Removed particle from simulation
continue;
}
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,
// [TODO LYN-1890]
// Since blend weights cannot be controlled per instance with Atom,
// this additional mesh optimization is not possible at the moment.
false /*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(0.0f, 0.0f, -9.81f);
Physics::WorldRequestBus::EventResult(gravity, Physics::DefaultPhysicsWorldId, &Physics::WorldRequests::GetGravity);
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
@@ -0,0 +1,144 @@
/*
* 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/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Physics/WindBus.h>
#include <NvCloth/ICloth.h>
#include <Components/ClothConfiguration.h>
#include <Utils/AssetHelper.h>
namespace NvCloth
{
class ActorClothColliders;
class ActorClothSkinning;
class ClothConstraints;
class ClothDebugDisplay;
//! Class that applies cloth simulation to Static Meshes and Actors
//! by reading their data and modifying the render nodes in real time.
class ClothComponentMesh
: public AZ::TransformNotificationBus::Handler
, public AZ::TickBus::Handler
, public Physics::WindNotificationsBus::Handler
{
public:
AZ_RTTI(ClothComponentMesh, "{15A0F10C-6248-4CE4-A6FD-0E2D8AFCFEE8}");
ClothComponentMesh(AZ::EntityId entityId, const ClothConfiguration& config);
~ClothComponentMesh();
AZ_DISABLE_COPY_MOVE(ClothComponentMesh);
// Rendering data.
// It stores the tangent space information of each vertex, which is calculated every frame.
struct RenderData
{
AZStd::vector<SimParticleFormat> m_particles;
AZStd::vector<AZ::Vector3> m_tangents;
AZStd::vector<AZ::Vector3> m_bitangents;
AZStd::vector<AZ::Vector3> m_normals;
};
const RenderData& GetRenderData() const;
RenderData& GetRenderData();
void UpdateConfiguration(AZ::EntityId entityId, const ClothConfiguration& config);
protected:
// Functions used to setup and tear down cloth component mesh
void Setup(AZ::EntityId entityId, const ClothConfiguration& config);
void TearDown();
// ICloth notifications
void OnPreSimulation(ClothId clothId, float deltaTime);
void OnPostSimulation(ClothId clothId, float deltaTime, const AZStd::vector<SimParticleFormat>& updatedParticles);
// AZ::TransformNotificationBus::Handler overrides ...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
// Physics::WindNotificationsBus::Handler overrides ...
void OnGlobalWindChanged() override;
void OnWindChanged(const AZ::Aabb& aabb) override;
private:
void UpdateSimulationCollisions();
void UpdateSimulationSkinning();
void UpdateSimulationConstraints();
void UpdateRenderData(const AZStd::vector<SimParticleFormat>& particles);
void CopyRenderDataToModel();
bool CreateCloth();
void ApplyConfigurationToCloth();
void MoveCloth(const AZ::Transform& worldTransform);
void TeleportCloth(const AZ::Transform& worldTransform);
AZ::Vector3 GetWindBusVelocity();
// Entity Id of the cloth component
AZ::EntityId m_entityId;
// Current position in world space
AZ::Vector3 m_worldPosition;
// Configuration parameters for cloth simulation
ClothConfiguration m_config;
// Instance of cloth simulation
ICloth* m_cloth = nullptr;
// Cloth event handlers
ICloth::PreSimulationEvent::Handler m_preSimulationEventHandler;
ICloth::PostSimulationEvent::Handler m_postSimulationEventHandler;
// Use a double buffer of render data to always have access to the previous frame's data.
// The previous frame's data is used to workaround that debug draw is one frame delayed.
static const AZ::u32 RenderDataBufferSize = 2;
AZ::u32 m_renderDataBufferIndex = 0;
AZStd::array<RenderData, RenderDataBufferSize> m_renderDataBuffer;
// Vertex mapping between full mesh and simplified mesh used in cloth simulation.
// Negative elements means the vertex has been removed.
AZStd::vector<int> m_meshRemappedVertices;
// Information to map the simulation particles to render mesh nodes.
MeshNodeInfo m_meshNodeInfo;
// Original cloth information from the mesh.
MeshClothInfo m_meshClothInfo;
// Cloth Colliders from the character
AZStd::unique_ptr<ActorClothColliders> m_actorClothColliders;
// Cloth Skinning from the character
AZStd::unique_ptr<ActorClothSkinning> m_actorClothSkinning;
AZ::u32 m_numberOfClothSkinningUpdates = 0;
// Cloth Constraints
AZStd::unique_ptr<ClothConstraints> m_clothConstraints;
AZStd::vector<AZ::Vector4> m_motionConstraints;
AZStd::vector<AZ::Vector4> m_separationConstraints;
AZStd::unique_ptr<ClothDebugDisplay> m_clothDebugDisplay;
friend class ClothDebugDisplay; // Give access to data to draw debug information
};
} // namespace NvCloth
@@ -0,0 +1,194 @@
/*
* 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 <Components/ClothComponentMesh/ClothConstraints.h>
#include <NvCloth/ITangentSpaceHelper.h>
namespace NvCloth
{
AZStd::unique_ptr<ClothConstraints> ClothConstraints::Create(
const AZStd::vector<float>& motionConstraintsData,
const float motionConstraintsMaxDistance,
const AZStd::vector<AZ::Vector2>& backstopData,
const float backstopMaxRadius,
const float backstopMaxBackOffset,
const float backstopMaxFrontOffset,
const AZStd::vector<SimParticleFormat>& simParticles,
const AZStd::vector<SimIndexType>& simIndices,
const AZStd::vector<int>& meshRemappedVertices)
{
AZStd::unique_ptr<ClothConstraints> clothConstraints = AZStd::make_unique<ClothConstraints>();
clothConstraints->m_motionConstraintsData.resize(simParticles.size(), AZ::Constants::FloatMax);
clothConstraints->m_motionConstraintsMaxDistance = motionConstraintsMaxDistance;
clothConstraints->m_motionConstraints.resize(simParticles.size());
for (size_t i = 0; i < motionConstraintsData.size(); ++i)
{
const int remappedIndex = meshRemappedVertices[i];
if (remappedIndex < 0)
{
// Removed particle
continue;
}
// Keep the minimum distance for remapped duplicated vertices
if (motionConstraintsData[i] < clothConstraints->m_motionConstraintsData[remappedIndex])
{
clothConstraints->m_motionConstraintsData[remappedIndex] = motionConstraintsData[i];
}
}
const bool hasBackstopData = AZStd::any_of(
backstopData.cbegin(),
backstopData.cend(),
[](const AZ::Vector2& backstop)
{
const float radius = backstop.GetY();
return radius > 0.0f;
});
if (hasBackstopData)
{
clothConstraints->m_backstopData.resize(simParticles.size(), AZ::Vector2(0.0f, AZ::Constants::FloatMax));
clothConstraints->m_backstopMaxRadius = backstopMaxRadius;
clothConstraints->m_backstopMaxBackOffset = backstopMaxBackOffset;
clothConstraints->m_backstopMaxFrontOffset = backstopMaxFrontOffset;
clothConstraints->m_separationConstraints.resize(simParticles.size());
for (size_t i = 0; i < backstopData.size(); ++i)
{
const int remappedIndex = meshRemappedVertices[i];
if (remappedIndex < 0)
{
// Removed particle
continue;
}
// Keep the minimum radius for remapped duplicated vertices
if (backstopData[i].GetY() < clothConstraints->m_backstopData[remappedIndex].GetY())
{
clothConstraints->m_backstopData[remappedIndex] = backstopData[i];
}
}
}
// Calculates the current constraints and fills the data as nvcloth needs them,
// ready to be queried by the cloth component.
clothConstraints->CalculateConstraints(simParticles, simIndices);
return clothConstraints;
}
void ClothConstraints::CalculateConstraints(
const AZStd::vector<SimParticleFormat>& simParticles,
const AZStd::vector<SimIndexType>& simIndices)
{
if (simParticles.size() != m_motionConstraints.size())
{
return;
}
m_simParticles = simParticles;
CalculateMotionConstraints();
if (!m_separationConstraints.empty())
{
bool normalsCalculated = AZ::Interface<ITangentSpaceHelper>::Get()->CalculateNormals(simParticles, simIndices, m_normals);
AZ_Assert(normalsCalculated, "Cloth constraints failed to calculate normals.");
CalculateSeparationConstraints();
}
}
const AZStd::vector<AZ::Vector4>& ClothConstraints::GetMotionConstraints() const
{
return m_motionConstraints;
}
const AZStd::vector<AZ::Vector4>& ClothConstraints::GetSeparationConstraints() const
{
return m_separationConstraints;
}
void ClothConstraints::SetMotionConstraintMaxDistance(float distance)
{
m_motionConstraintsMaxDistance = distance;
CalculateMotionConstraints();
}
void ClothConstraints::SetBackstopMaxRadius(float radius)
{
m_backstopMaxRadius = radius;
CalculateSeparationConstraints();
}
void ClothConstraints::SetBackstopMaxOffsets(float backOffset, float frontOffset)
{
m_backstopMaxBackOffset = backOffset;
m_backstopMaxFrontOffset = frontOffset;
CalculateSeparationConstraints();
}
void ClothConstraints::CalculateMotionConstraints()
{
for (size_t i = 0; i < m_motionConstraints.size(); ++i)
{
const float maxDistance = (m_simParticles[i].GetW() > 0.0f)
? m_motionConstraintsData[i] * m_motionConstraintsMaxDistance
: 0.0f;
m_motionConstraints[i].Set(m_simParticles[i].GetAsVector3(), maxDistance);
}
}
void ClothConstraints::CalculateSeparationConstraints()
{
for (size_t i = 0; i < m_separationConstraints.size(); ++i)
{
const float offsetScale = m_backstopData[i].GetX();
const float offset = offsetScale * ((offsetScale >= 0.0f) ? m_backstopMaxBackOffset : m_backstopMaxFrontOffset);
const float radiusScale = m_backstopData[i].GetY();
const float radius = radiusScale * m_backstopMaxRadius;
const AZ::Vector3 position = CalculateBackstopSpherePosition(
m_simParticles[i].GetAsVector3(), m_normals[i], offset, radius);
m_separationConstraints[i].Set(position, radius);
}
}
AZ::Vector3 ClothConstraints::CalculateBackstopSpherePosition(
const AZ::Vector3& position,
const AZ::Vector3& normal,
float offset,
float radius) const
{
AZ::Vector3 spherePosition = position;
if (offset >= 0.0f)
{
spherePosition -= normal * (radius + offset); // Place sphere behind the particle
}
else
{
spherePosition += normal * (radius - offset); // Place sphere in front of the particle
}
return spherePosition;
}
} // namespace NvCloth
@@ -0,0 +1,81 @@
/*
* 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/std/smart_ptr/unique_ptr.h>
#include <AzCore/Component/EntityId.h>
#include <NvCloth/Types.h>
namespace NvCloth
{
//! Manages motion and separation constraints for cloth.
class ClothConstraints
{
public:
AZ_TYPE_INFO(ClothConstraints, "{EB14ED7C-37FD-4CA3-9137-EC6590712E50}");
static AZStd::unique_ptr<ClothConstraints> Create(
const AZStd::vector<float>& motionConstraintsData,
const float motionConstraintsMaxDistance,
const AZStd::vector<AZ::Vector2>& backstopData,
const float backstopMaxRadius,
const float backstopMaxBackOffset,
const float backstopMaxFrontOffset,
const AZStd::vector<SimParticleFormat>& simParticles,
const AZStd::vector<SimIndexType>& simIndices,
const AZStd::vector<int>& meshRemappedVertices);
ClothConstraints() = default;
void CalculateConstraints(
const AZStd::vector<SimParticleFormat>& simParticles,
const AZStd::vector<SimIndexType>& simIndices);
const AZStd::vector<AZ::Vector4>& GetMotionConstraints() const;
const AZStd::vector<AZ::Vector4>& GetSeparationConstraints() const;
void SetMotionConstraintMaxDistance(float distance);
void SetBackstopMaxRadius(float radius);
void SetBackstopMaxOffsets(float backOffset, float frontOffset);
private:
void CalculateMotionConstraints();
void CalculateSeparationConstraints();
AZ::Vector3 CalculateBackstopSpherePosition(
const AZ::Vector3& position,
const AZ::Vector3& normal,
float offset,
float radius) const;
// Simulation Particles
AZStd::vector<SimParticleFormat> m_simParticles;
// Motion constraints data
AZStd::vector<float> m_motionConstraintsData;
float m_motionConstraintsMaxDistance = 0.0f;
// Backstop data
AZStd::vector<AZ::Vector2> m_backstopData;
float m_backstopMaxRadius = 0.0f;
float m_backstopMaxBackOffset = 0.0f;
float m_backstopMaxFrontOffset = 0.0f;
AZStd::vector<AZ::Vector3> m_normals;
// The current positions and radius of motion constraints.
AZStd::vector<AZ::Vector4> m_motionConstraints;
// The current positions and radius of separation constraints.
AZStd::vector<AZ::Vector4> m_separationConstraints;
};
} // namespace NvCloth
@@ -0,0 +1,313 @@
/*
* 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/Console/IConsole.h>
#include <AzFramework/Viewport/ViewportColors.h>
#include <LmbrCentral/Geometry/GeometrySystemComponentBus.h>
#include <Components/ClothComponentMesh/ClothDebugDisplay.h>
#include <Components/ClothComponentMesh/ActorClothColliders.h>
#include <Components/ClothComponentMesh/ClothConstraints.h>
#include <Components/ClothComponentMesh/ClothComponentMesh.h>
#include <NvCloth/ICloth.h>
namespace NvCloth
{
AZ_CVAR(int32_t, cloth_DebugDraw, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
"Draw cloth wireframe mesh:\n"
" 0 - Disabled\n"
" 1 - Cloth wireframe and particle weights");
AZ_CVAR(int32_t, cloth_DebugDrawNormals, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
"Draw cloth normals:\n"
" 0 - Disabled\n"
" 1 - Cloth normals\n"
" 2 - Cloth normals, tangents and bitangents");
AZ_CVAR(int32_t, cloth_DebugDrawColliders, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
"Draw cloth colliders:\n"
" 0 - Disabled\n"
" 1 - Cloth colliders");
AZ_CVAR(int32_t, cloth_DebugDrawMotionConstraints, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
"Draw cloth motion constraints:\n"
" 0 - Disabled\n"
" 1 - Cloth motion constraints");
AZ_CVAR(int32_t, cloth_DebugDrawBackstop, 0, nullptr, AZ::ConsoleFunctorFlags::Null,
"Draw cloth backstop:\n"
" 0 - Disabled\n"
" 1 - Cloth backstop");
ClothDebugDisplay::ClothDebugDisplay(ClothComponentMesh* clothComponentMesh)
: m_clothComponentMesh(clothComponentMesh)
{
AZ_Assert(m_clothComponentMesh, "Invalid cloth component mesh");
AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(m_clothComponentMesh->m_entityId);
}
ClothDebugDisplay::~ClothDebugDisplay()
{
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
}
bool ClothDebugDisplay::IsDebugDrawEnabled() const
{
return cloth_DebugDraw > 0
|| cloth_DebugDrawNormals > 0
|| cloth_DebugDrawColliders > 0
|| cloth_DebugDrawMotionConstraints > 0
|| cloth_DebugDrawBackstop > 0;
}
void ClothDebugDisplay::DisplayEntityViewport(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay)
{
AZ_UNUSED(viewportInfo);
if (!IsDebugDrawEnabled() || !m_clothComponentMesh->m_cloth)
{
return;
}
AZ::Transform entityTransform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(entityTransform, m_clothComponentMesh->m_entityId, &AZ::TransformInterface::GetWorldTM);
debugDisplay.PushMatrix(entityTransform);
if (cloth_DebugDraw > 0)
{
DisplayParticles(debugDisplay);
DisplayWireCloth(debugDisplay);
}
if (cloth_DebugDrawNormals > 0)
{
bool showTangents = (cloth_DebugDrawNormals > 1);
DisplayNormals(debugDisplay, showTangents);
}
if (cloth_DebugDrawColliders > 0)
{
DisplayColliders(debugDisplay);
}
if (cloth_DebugDrawMotionConstraints > 0)
{
DisplayMotionConstraints(debugDisplay);
}
if (cloth_DebugDrawBackstop > 0)
{
DisplaySeparationConstraints(debugDisplay);
}
debugDisplay.PopMatrix();
}
void ClothDebugDisplay::DisplayParticles(AzFramework::DebugDisplayRequests& debugDisplay)
{
const float particleAlpha = 1.0f;
const float particleRadius = 0.007f;
const auto& clothRenderParticles = m_clothComponentMesh->m_cloth->GetParticles();
for(const auto& particle : clothRenderParticles)
{
const AZ::Vector4 color = AZ::Vector4::CreateFromVector3AndFloat(AZ::Vector3(particle.GetW()), particleAlpha);
debugDisplay.SetColor(color);
debugDisplay.DrawBall(particle.GetAsVector3(), particleRadius, false/*drawShaded*/);
}
}
void ClothDebugDisplay::DisplayWireCloth(AzFramework::DebugDisplayRequests& debugDisplay)
{
const float lineAlpha = 1.0f;
const auto& clothIndices = m_clothComponentMesh->m_cloth->GetInitialIndices();
const auto& clothRenderParticles = m_clothComponentMesh->m_cloth->GetParticles();
const size_t numIndices = clothIndices.size();
if (numIndices % 3 != 0)
{
AZ_Warning("ClothDebugDisplay", false,
"Cloth indices contains a list of triangles but its count (%zu) is not a multiple of 3.", numIndices);
return;
}
for (size_t index = 0; index < numIndices; index += 3)
{
const SimIndexType& vertexIndex0 = clothIndices[index + 0];
const SimIndexType& vertexIndex1 = clothIndices[index + 1];
const SimIndexType& vertexIndex2 = clothIndices[index + 2];
const SimParticleFormat& particle0 = clothRenderParticles[vertexIndex0];
const SimParticleFormat& particle1 = clothRenderParticles[vertexIndex1];
const SimParticleFormat& particle2 = clothRenderParticles[vertexIndex2];
const AZ::Vector3 position0 = particle0.GetAsVector3();
const AZ::Vector3 position1 = particle1.GetAsVector3();
const AZ::Vector3 position2 = particle2.GetAsVector3();
const AZ::Vector4 color0 = AZ::Vector4::CreateFromVector3AndFloat(AZ::Vector3(particle0.GetW()), lineAlpha);
const AZ::Vector4 color1 = AZ::Vector4::CreateFromVector3AndFloat(AZ::Vector3(particle1.GetW()), lineAlpha);
const AZ::Vector4 color2 = AZ::Vector4::CreateFromVector3AndFloat(AZ::Vector3(particle2.GetW()), lineAlpha);
debugDisplay.DrawLine(position0, position1, color0, color1);
debugDisplay.DrawLine(position1, position2, color1, color2);
debugDisplay.DrawLine(position2, position0, color2, color0);
}
}
void ClothDebugDisplay::DisplayNormals(AzFramework::DebugDisplayRequests& debugDisplay, bool showTangents)
{
const auto& clothRenderData = m_clothComponentMesh->GetRenderData();
const auto& clothRenderParticles = clothRenderData.m_particles;
const auto& clothRenderTangents = clothRenderData.m_tangents;
const auto& clothRenderBitangents = clothRenderData.m_bitangents;
const auto& clothRenderNormals = clothRenderData.m_normals;
if (clothRenderParticles.size() != clothRenderNormals.size())
{
AZ_Warning("ClothDebugDisplay", false,
"Number of cloth particles (%zu) doesn't match with the number of normals (%zu).",
clothRenderParticles.size(), clothRenderNormals.size());
return;
}
const float normalLength = 0.05f;
const float tangentLength = 0.05f;
const float bitangentLength = 0.05f;
const AZ::Vector4 colorNormal = AZ::Colors::Blue.GetAsVector4();
const AZ::Vector4 colorTangent = AZ::Colors::Red.GetAsVector4();
const AZ::Vector4 colorBitangent = AZ::Colors::Green.GetAsVector4();
for (size_t i = 0; i < clothRenderParticles.size(); ++i)
{
if (m_clothComponentMesh->m_meshRemappedVertices[i] < 0)
{
// Removed particle
continue;
}
const AZ::Vector3 position = clothRenderParticles[i].GetAsVector3();
debugDisplay.DrawLine(position, position + normalLength * clothRenderNormals[i], colorNormal, colorNormal);
if (showTangents)
{
debugDisplay.DrawLine(position, position + tangentLength * clothRenderTangents[i], colorTangent, colorTangent);
debugDisplay.DrawLine(position, position + bitangentLength * clothRenderBitangents[i], colorBitangent, colorBitangent);
}
}
}
void ClothDebugDisplay::DisplayColliders(AzFramework::DebugDisplayRequests& debugDisplay)
{
if (!m_clothComponentMesh->m_actorClothColliders)
{
return;
}
for (const SphereCollider& collider : m_clothComponentMesh->m_actorClothColliders->GetSphereColliders())
{
DrawSphere(debugDisplay, collider.m_radius, collider.m_currentModelSpaceTransform.GetTranslation(), AzFramework::ViewportColors::DeselectedColor);
}
for (const CapsuleCollider& collider : m_clothComponentMesh->m_actorClothColliders->GetCapsuleColliders())
{
DrawCapsule(debugDisplay, collider.m_radius, collider.m_height, collider.m_currentModelSpaceTransform, AzFramework::ViewportColors::DeselectedColor);
}
}
void ClothDebugDisplay::DisplayMotionConstraints(AzFramework::DebugDisplayRequests& debugDisplay)
{
const AZ::Vector4 particleColor = AZ::Colors::Green.GetAsVector4();
const AZ::Vector4 staticPraticleColor = AZ::Colors::Black.GetAsVector4();
const AZ::Vector4 lineColor = AZ::Colors::Magenta.GetAsVector4();
const float ballsize = 0.008f;
for (const auto& constraint : m_clothComponentMesh->m_motionConstraints)
{
const AZ::Vector3 position = constraint.GetAsVector3();
const float radius = constraint.GetW();
debugDisplay.SetColor((radius > 0.0f) ? particleColor : staticPraticleColor);
debugDisplay.DrawBall(position, ballsize, false/*drawShaded*/);
debugDisplay.DrawLine(position, position + AZ::Vector3::CreateAxisY(radius), lineColor, lineColor);
}
}
void ClothDebugDisplay::DisplaySeparationConstraints(AzFramework::DebugDisplayRequests& debugDisplay)
{
if (m_clothComponentMesh->m_separationConstraints.empty())
{
return;
}
const AZ::Vector4 sphereColor = AZ::Colors::Red.GetAsVector4();
const AZ::Vector4 lineColor = AZ::Colors::Aqua.GetAsVector4();
const auto& particles = m_clothComponentMesh->m_cloth->GetParticles();
for (size_t i = 0; i < particles.size(); ++i)
{
const AZ::Vector3 position = m_clothComponentMesh->m_separationConstraints[i].GetAsVector3();
const float radius = m_clothComponentMesh->m_separationConstraints[i].GetW();
DrawSphere(debugDisplay, radius, position, sphereColor);
debugDisplay.DrawLine(position, particles[i].GetAsVector3(), lineColor, lineColor);
}
}
void ClothDebugDisplay::DrawSphere(
AzFramework::DebugDisplayRequests& debugDisplay,
float radius,
const AZ::Vector3& position,
const AZ::Color& color)
{
debugDisplay.SetColor(color);
debugDisplay.DrawBall(position, radius, false/*drawShaded*/);
debugDisplay.SetColor(AzFramework::ViewportColors::WireColor);
debugDisplay.DrawWireSphere(position, radius);
}
void ClothDebugDisplay::DrawCapsule(
AzFramework::DebugDisplayRequests& debugDisplay,
float radius, float height,
const AZ::Transform& transform,
const AZ::Color& color)
{
debugDisplay.PushMatrix(transform);
AZStd::vector<AZ::Vector3> capsuleVertexBuffer;
AZStd::vector<AZ::u32> capsuleIndexBuffer;
AZStd::vector<AZ::Vector3> capsuleLineBuffer;
const AZ::u32 sides = 16;
const AZ::u32 capSegments = 8;
LmbrCentral::CapsuleGeometrySystemRequestBus::Broadcast(
&LmbrCentral::CapsuleGeometrySystemRequestBus::Events::GenerateCapsuleMesh,
radius,
height,
sides, capSegments,
capsuleVertexBuffer,
capsuleIndexBuffer,
capsuleLineBuffer
);
debugDisplay.DrawTrianglesIndexed(capsuleVertexBuffer, capsuleIndexBuffer, color);
debugDisplay.DrawLines(capsuleLineBuffer, AzFramework::ViewportColors::WireColor);
debugDisplay.PopMatrix();
}
} // namespace NvCloth
@@ -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.
*
*/
#pragma once
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
namespace NvCloth
{
class ClothComponentMesh;
//! Manages the debug display of a ClothComponentMesh.
class ClothDebugDisplay
: protected AzFramework::EntityDebugDisplayEventBus::Handler
{
public:
AZ_TYPE_INFO(ClothDebugDisplay, "{306A2A30-8BB1-4D0F-9776-324CA1D90ABE}");
ClothDebugDisplay(ClothComponentMesh* clothComponentMesh);
~ClothDebugDisplay();
//! Returns true when any debug cloth information must be displayed.
bool IsDebugDrawEnabled() const;
protected:
// AzFramework::EntityDebugDisplayEventBus::Handler overrides ...
void DisplayEntityViewport(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay) override;
private:
void DisplayParticles(AzFramework::DebugDisplayRequests& debugDisplay);
void DisplayWireCloth(AzFramework::DebugDisplayRequests& debugDisplay);
void DisplayNormals(AzFramework::DebugDisplayRequests& debugDisplay, bool showTangents);
void DisplayColliders(AzFramework::DebugDisplayRequests& debugDisplay);
void DisplayMotionConstraints(AzFramework::DebugDisplayRequests& debugDisplay);
void DisplaySeparationConstraints(AzFramework::DebugDisplayRequests& debugDisplay);
void DrawSphere(AzFramework::DebugDisplayRequests& debugDisplay, float radius, const AZ::Vector3& position, const AZ::Color& color);
void DrawCapsule(AzFramework::DebugDisplayRequests& debugDisplay, float radius, float height, const AZ::Transform& transform, const AZ::Color& color);
ClothComponentMesh* m_clothComponentMesh = nullptr;
};
} // namespace NvCloth
@@ -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.
*
*/
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Components/ClothConfiguration.h>
namespace NvCloth
{
void ClothConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ClothConfiguration>()
->Version(2)
->Field("Mesh Node", &ClothConfiguration::m_meshNode)
->Field("Mass", &ClothConfiguration::m_mass)
->Field("Use Custom Gravity", &ClothConfiguration::m_useCustomGravity)
->Field("Custom Gravity", &ClothConfiguration::m_customGravity)
->Field("Gravity Scale", &ClothConfiguration::m_gravityScale)
->Field("Stiffness Frequency", &ClothConfiguration::m_stiffnessFrequency)
->Field("Motion Constraints Max Distance", &ClothConfiguration::m_motionConstraintsMaxDistance)
->Field("Motion Constraints Scale", &ClothConfiguration::m_motionConstraintsScale)
->Field("Motion Constraints Bias", &ClothConfiguration::m_motionConstraintsBias)
->Field("Motion Constraints Stiffness", &ClothConfiguration::m_motionConstraintsStiffness)
->Field("Backstop Radius", &ClothConfiguration::m_backstopRadius)
->Field("Backstop Back Offset", &ClothConfiguration::m_backstopBackOffset)
->Field("Backstop Front Offset", &ClothConfiguration::m_backstopFrontOffset)
->Field("Damping", &ClothConfiguration::m_damping)
->Field("Linear Drag", &ClothConfiguration::m_linearDrag)
->Field("Angular Drag", &ClothConfiguration::m_angularDrag)
->Field("Linear Inertia", &ClothConfiguration::m_linearInteria)
->Field("Angular Inertia", &ClothConfiguration::m_angularInteria)
->Field("Centrifugal Inertia", &ClothConfiguration::m_centrifugalInertia)
->Field("Use Custom Wind Velocity", &ClothConfiguration::m_useCustomWindVelocity)
->Field("Wind Velocity", &ClothConfiguration::m_windVelocity)
->Field("Air Drag Coefficient", &ClothConfiguration::m_airDragCoefficient)
->Field("Air Lift Coefficient", &ClothConfiguration::m_airLiftCoefficient)
->Field("Fluid Density", &ClothConfiguration::m_fluidDensity)
->Field("Collision Friction", &ClothConfiguration::m_collisionFriction)
->Field("Collision Mass Scale", &ClothConfiguration::m_collisionMassScale)
->Field("Continuous Collision Detection", &ClothConfiguration::m_continuousCollisionDetection)
->Field("Collision Affects Static Particles", &ClothConfiguration::m_collisionAffectsStaticParticles)
->Field("Self Collision Distance", &ClothConfiguration::m_selfCollisionDistance)
->Field("Self Collision Stiffness", &ClothConfiguration::m_selfCollisionStiffness)
->Field("Horizontal Stiffness", &ClothConfiguration::m_horizontalStiffness)
->Field("Horizontal Stiffness Multiplier", &ClothConfiguration::m_horizontalStiffnessMultiplier)
->Field("Horizontal Compression Limit", &ClothConfiguration::m_horizontalCompressionLimit)
->Field("Horizontal Stretch Limit", &ClothConfiguration::m_horizontalStretchLimit)
->Field("Vertical Stiffness", &ClothConfiguration::m_verticalStiffness)
->Field("Vertical Stiffness Multiplier", &ClothConfiguration::m_verticalStiffnessMultiplier)
->Field("Vertical Compression Limit", &ClothConfiguration::m_verticalCompressionLimit)
->Field("Vertical Stretch Limit", &ClothConfiguration::m_verticalStretchLimit)
->Field("Bending Stiffness", &ClothConfiguration::m_bendingStiffness)
->Field("Bending Stiffness Multiplier", &ClothConfiguration::m_bendingStiffnessMultiplier)
->Field("Bending Compression Limit", &ClothConfiguration::m_bendingCompressionLimit)
->Field("Bending Stretch Limit", &ClothConfiguration::m_bendingStretchLimit)
->Field("Shearing Stiffness", &ClothConfiguration::m_shearingStiffness)
->Field("Shearing Stiffness Multiplier", &ClothConfiguration::m_shearingStiffnessMultiplier)
->Field("Shearing Compression Limit", &ClothConfiguration::m_shearingCompressionLimit)
->Field("Shearing Stretch Limit", &ClothConfiguration::m_shearingStretchLimit)
->Field("Tether Constraint Stiffness", &ClothConfiguration::m_tetherConstraintStiffness)
->Field("Tether Constraint Scale", &ClothConfiguration::m_tetherConstraintScale)
->Field("Solver Frequency", &ClothConfiguration::m_solverFrequency)
->Field("Acceleration Filter Iterations", &ClothConfiguration::m_accelerationFilterIterations)
->Field("Remove Static Triangles", &ClothConfiguration::m_removeStaticTriangles)
;
}
}
MeshNodeList ClothConfiguration::PopulateMeshNodeList()
{
if (m_populateMeshNodeListCallback)
{
return m_populateMeshNodeListCallback();
}
return {};
}
bool ClothConfiguration::HasBackstopData()
{
if (m_hasBackstopDataCallback)
{
return m_hasBackstopDataCallback();
}
return false;
}
AZ::EntityId ClothConfiguration::GetEntityId()
{
if (m_getEntityIdCallback)
{
return m_getEntityIdCallback();
}
return AZ::EntityId();
}
} // namespace NvCloth
@@ -0,0 +1,133 @@
/*
* 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/RTTI/RTTI.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/function/function_template.h>
#include <Utils/AssetHelper.h>
namespace AZ
{
class ReflectContext;
}
namespace NvCloth
{
//! Configuration data for Cloth.
struct ClothConfiguration
{
AZ_CLASS_ALLOCATOR(ClothConfiguration, AZ::SystemAllocator, 0);
AZ_TYPE_INFO(ClothConfiguration, "{96E2AF5E-3C98-4872-8F90-F56302A44F2A}");
static void Reflect(AZ::ReflectContext* context);
virtual ~ClothConfiguration() = default;
bool IsUsingWorldBusGravity() const { return !m_useCustomGravity; }
bool IsUsingWindBus() const { return !m_useCustomWindVelocity; }
AZStd::string m_meshNode;
// Mass and Gravity parameters
float m_mass = 1.0f;
bool m_useCustomGravity = false;
AZ::Vector3 m_customGravity = AZ::Vector3(0.0f, 0.0f, -9.81f);
float m_gravityScale = 1.0f;
// Global stiffness frequency
float m_stiffnessFrequency = 10.0f;
// Motion constraints Parameters
float m_motionConstraintsMaxDistance = 10.0f;
float m_motionConstraintsScale = 1.0f;
float m_motionConstraintsBias = 0.0f;
float m_motionConstraintsStiffness = 1.0f;
// Backstop Parameters
float m_backstopRadius = 0.1f;
float m_backstopBackOffset = 0.0f;
float m_backstopFrontOffset = 0.0f;
// Damping parameters
AZ::Vector3 m_damping = AZ::Vector3(0.2f, 0.2f, 0.2f);
AZ::Vector3 m_linearDrag = AZ::Vector3(0.2f, 0.2f, 0.2f);
AZ::Vector3 m_angularDrag = AZ::Vector3(0.2f, 0.2f, 0.2f);
// Inertia parameters
AZ::Vector3 m_linearInteria = AZ::Vector3::CreateOne();
AZ::Vector3 m_angularInteria = AZ::Vector3::CreateOne();
AZ::Vector3 m_centrifugalInertia = AZ::Vector3::CreateOne();
// Wind parameters
bool m_useCustomWindVelocity = true;
AZ::Vector3 m_windVelocity = AZ::Vector3(0.0f, 20.0f, 0.0f);
float m_airDragCoefficient = 0.0f;
float m_airLiftCoefficient = 0.0f;
float m_fluidDensity = 1.0f;
// Collision parameters
float m_collisionFriction = 0.0f;
float m_collisionMassScale = 0.0f;
bool m_continuousCollisionDetection = false;
bool m_collisionAffectsStaticParticles = false;
// Self Collision parameters
float m_selfCollisionDistance = 0.0f;
float m_selfCollisionStiffness = 0.2f;
// Tether Constraints parameters
float m_tetherConstraintStiffness = 1.0f;
float m_tetherConstraintScale = 1.0f;
// Quality parameters
float m_solverFrequency = 300.0f;
uint32_t m_accelerationFilterIterations = 30;
bool m_removeStaticTriangles = true;
// Fabric phases parameters
float m_horizontalStiffness = 1.0f;
float m_horizontalStiffnessMultiplier = 0.0f;
float m_horizontalCompressionLimit = 0.0f;
float m_horizontalStretchLimit = 0.0f;
float m_verticalStiffness = 1.0f;
float m_verticalStiffnessMultiplier = 0.0f;
float m_verticalCompressionLimit = 0.0f;
float m_verticalStretchLimit = 0.0f;
float m_bendingStiffness = 1.0f;
float m_bendingStiffnessMultiplier = 0.0f;
float m_bendingCompressionLimit = 0.0f;
float m_bendingStretchLimit = 0.0f;
float m_shearingStiffness = 1.0f;
float m_shearingStiffnessMultiplier = 0.0f;
float m_shearingCompressionLimit = 0.0f;
float m_shearingStretchLimit = 0.0f;
private:
// Making private functionality related with the Editor Context reflection,
// it's unnecessary for the clients using ClothConfiguration.
friend class EditorClothComponent;
// Callback functions set by the EditorClothComponent.
AZStd::function<MeshNodeList()> m_populateMeshNodeListCallback;
AZStd::function<bool()> m_hasBackstopDataCallback;
AZStd::function<AZ::EntityId()> m_getEntityIdCallback;
// Used by data elements in EditorClothComponent edit context.
MeshNodeList PopulateMeshNodeList();
bool HasBackstopData();
AZ::EntityId GetEntityId();
};
} // namespace NvCloth
@@ -0,0 +1,594 @@
/*
* 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/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Editor/PropertyTypes.h>
#include <Components/EditorClothComponent.h>
#include <Components/ClothComponent.h>
#include <Components/ClothComponentMesh/ClothComponentMesh.h>
#include <Utils/AssetHelper.h>
namespace NvCloth
{
namespace Internal
{
extern const char* const StatusMessageSelectNode = "Select a node";
extern const char* const StatusMessageNoAsset = "<No asset>";
extern const char* const StatusMessageNoClothNodes = "<No cloth modifiers>";
const char* const AttributeSuffixMetersUnit = " m";
}
void EditorClothComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorClothComponent, AzToolsFramework::Components::EditorComponentBase>()
->Field("Configuration", &EditorClothComponent::m_config)
->Version(0)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorClothComponent>(
"Cloth", "The mesh node behaves like a piece of cloth.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Cloth.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Cloth.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-cloth.html")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->UIElement(AZ::Edit::UIHandlers::CheckBox, "Simulate in editor",
"Enables cloth simulation in editor when set.")
->Attribute(AZ::Edit::Attributes::CheckboxDefaultValue, &EditorClothComponent::IsSimulatedInEditor)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorClothComponent::OnSimulatedInEditorToggled)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorClothComponent::m_config)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorClothComponent::OnConfigurationChanged)
;
editContext->Class<ClothConfiguration>("Cloth Configuration", "Configuration for cloth simulation.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
// Mesh Node
->DataElement(Editor::MeshNodeSelector, &ClothConfiguration::m_meshNode, "Mesh node",
"List of mesh nodes with cloth simulation data. These are the nodes selected inside Cloth Modifiers in FBX Editor Settings.")
->Attribute(AZ::Edit::UIHandlers::EntityId, &ClothConfiguration::GetEntityId)
->Attribute(AZ::Edit::Attributes::StringList, &ClothConfiguration::PopulateMeshNodeList)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
// Mass and Gravity
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_mass, "Mass",
"Mass scale applied to all particles.")
->Attribute(AZ::Edit::Attributes::Min, 0.1f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_useCustomGravity, "Custom Gravity",
"When enabled it allows to set a custom gravity value for this cloth.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_customGravity, "Gravity",
"Gravity applied to particles.")
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothConfiguration::IsUsingWorldBusGravity)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_gravityScale, "Gravity Scale",
"Use this parameter to scale the gravity applied to particles.")
// Global stiffness frequency
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_stiffnessFrequency, "Stiffness frequency",
"Stiffness exponent per second applied to damping, damping dragging, wind dragging, wind lifting, self collision stiffness, fabric stiffness, fabric compression, fabric stretch and tether constraint stiffness.")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
// Motion Constraints
->ClassElement(AZ::Edit::ClassElements::Group, "Motion constraints")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_motionConstraintsMaxDistance, "Max Distance",
"Maximum distance for motion constraints to limit particles movement during simulation.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Suffix, Internal::AttributeSuffixMetersUnit)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_motionConstraintsScale, "Scale",
"Scale value applied to all motion constraints.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_motionConstraintsBias, "Bias",
"Bias value added to all motion constraints.")
->Attribute(AZ::Edit::Attributes::Suffix, Internal::AttributeSuffixMetersUnit)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_motionConstraintsStiffness, "Stiffness",
"Stiffness for motion constraints.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
// Backstop
->ClassElement(AZ::Edit::ClassElements::Group, "Backstop")
->Attribute(AZ::Edit::Attributes::Visibility, &ClothConfiguration::HasBackstopData)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_backstopRadius, "Radius",
"Maximum radius that will prevent the associated cloth particle from moving into that area.")
->Attribute(AZ::Edit::Attributes::Min, 0.001f)
->Attribute(AZ::Edit::Attributes::Suffix, Internal::AttributeSuffixMetersUnit)
->Attribute(AZ::Edit::Attributes::Visibility, &ClothConfiguration::HasBackstopData)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_backstopBackOffset, "Back offset",
"Maximum offset for backstop spheres behind the cloth.")
->Attribute(AZ::Edit::Attributes::Suffix, Internal::AttributeSuffixMetersUnit)
->Attribute(AZ::Edit::Attributes::Visibility, &ClothConfiguration::HasBackstopData)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_backstopFrontOffset, "Front offset",
"Maximum offset for backstop spheres in front of the cloth.")
->Attribute(AZ::Edit::Attributes::Suffix, Internal::AttributeSuffixMetersUnit)
->Attribute(AZ::Edit::Attributes::Visibility, &ClothConfiguration::HasBackstopData)
// Damping
->ClassElement(AZ::Edit::ClassElements::Group, "Damping")
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_damping, "Damping",
"Damping of particle velocity.\n"
"0: Velocity is unaffected\n"
"1: Velocity is zeroed")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_linearDrag, "Linear drag",
"Portion of velocity applied to particles.\n"
"0: Particles is unaffected\n"
"1: Damped global particle velocity")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_angularDrag, "Angular drag",
"Portion of angular velocity applied to turning particles.\n"
"0: Particles is unaffected\n"
"1: Damped global particle angular velocity")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
// Inertia
->ClassElement(AZ::Edit::ClassElements::Group, "Inertia")
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_linearInteria, "Linear",
"Portion of acceleration applied to particles.\n"
"0: Particles are unaffected\n"
"1: Physically correct")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_angularInteria, "Angular",
"Portion of angular acceleration applied to turning particles.\n"
"0: Particles are unaffected\n"
"1: Physically correct")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_centrifugalInertia, "Centrifugal",
"Portion of angular velocity applied to turning particles.\n"
"0: Particles are unaffected\n"
"1: Physically correct")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
// Wind
->ClassElement(AZ::Edit::ClassElements::Group, "Wind")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_useCustomWindVelocity, "Enable local wind velocity",
"When enabled it allows to set a custom wind velocity value for this cloth, otherwise using wind velocity from Physics::WindBus.\n"
"Wind is disabled when both air coefficients are zero.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_windVelocity, "Local velocity",
"Wind in global coordinates acting on cloth's triangles. Disabled when both air coefficients are zero.\n"
"NOTE: A combination of high values in wind properties can cause unstable results.")
->Attribute(AZ::Edit::Attributes::Min, -50.0f)
->Attribute(AZ::Edit::Attributes::Max, 50.0f)
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothConfiguration::IsUsingWindBus )
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_airDragCoefficient, "Air drag coefficient",
"Amount of air dragging.\n"
"NOTE: A combination of high values in wind properties can cause unstable results.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_airLiftCoefficient, "Air lift coefficient",
"Amount of air lifting.\n"
"NOTE: A combination of high values in wind properties can cause unstable results.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_fluidDensity, "Air Density",
"Density of air used for air drag and lift calculations.\n"
"NOTE: A combination of high values in wind properties can cause unstable results.")
->Attribute(AZ::Edit::Attributes::Min, 0.01f)
// Collision
->ClassElement(AZ::Edit::ClassElements::Group, "Collision")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_collisionFriction, "Friction",
"Amount of friction with colliders.\n"
"0: No friction\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_collisionMassScale, "Mass scale",
"Controls how quickly mass is increased during collisions.\n"
"0: No mass scaling\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_continuousCollisionDetection, "Continuous detection",
"Continuous collision detection improves collision by computing time of impact between cloth particles and colliders."
"The increase in quality comes with a cost in performance, it's recommended to use only when required.")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_collisionAffectsStaticParticles, "Affects static particles",
"When enabled colliders will move static particles (inverse mass 0).")
// Self collision
->ClassElement(AZ::Edit::ClassElements::Group, "Self collision")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_selfCollisionDistance, "Distance",
"Meters that particles need to be separated from each other.\n"
"0: No self collision\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_selfCollisionStiffness, "Stiffness",
"Stiffness for the self collision constraints.\n"
"0: No self collision\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
// Fabric stiffness
->ClassElement(AZ::Edit::ClassElements::Group, "Fabric stiffness")
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_horizontalStiffness, "Horizontal",
"Stiffness value for horizontal constraints.\n"
"0: no horizontal constraints\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_horizontalStiffnessMultiplier, "Horizontal multiplier",
"Scale value for horizontal fabric compression and stretch limits.\n"
"0: No horizontal compression and stretch limits applied\n"
"1: Fully apply horizontal compression and stretch limits\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_verticalStiffness, "Vertical",
"Stiffness value for vertical constraints.\n"
"0: no vertical constraints\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_verticalStiffnessMultiplier, "Vertical multiplier",
"Scale value for vertical fabric compression and stretch limits.\n"
"0: No vertical compression and stretch limits applied\n"
"1: Fully apply vertical compression and stretch limits\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_bendingStiffness, "Bending",
"Stiffness value for bending constraints.\n"
"0: no bending constraints\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_bendingStiffnessMultiplier, "Bending multiplier",
"Scale value for bending fabric compression and stretch limits.\n"
"0: No bending compression and stretch limits applied\n"
"1: Fully apply bending compression and stretch limits\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_shearingStiffness, "Shearing",
"Stiffness value for shearing constraints.\n"
"0: no shearing constraints\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_shearingStiffnessMultiplier, "Shearing multiplier",
"Scale value for shearing fabric compression and stretch limits.\n"
"0: No shearing compression and stretch limits applied\n"
"1: Fully apply shearing compression and stretch limits\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
// Fabric compression
->ClassElement(AZ::Edit::ClassElements::Group, "Fabric compression")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_horizontalCompressionLimit, "Horizontal limit",
"Compression limit for horizontal constraints. It's affected by fabric horizontal stiffness multiplier.\n"
"0: No compression\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_verticalCompressionLimit, "Vertical limit",
"Compression limit for vertical constraints. It's affected by fabric vertical stiffness multiplier.\n"
"0: No compression\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_bendingCompressionLimit, "Bending limit",
"Compression limit for bending constraints. It's affected by fabric bending stiffness multiplier.\n"
"0: No compression\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_shearingCompressionLimit, "Shearing limit",
"Compression limit for shearing constraints. It's affected by fabric shearing stiffness multiplier.\n"
"0: No compression\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
// Fabric stretch
->ClassElement(AZ::Edit::ClassElements::Group, "Fabric stretch")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_horizontalStretchLimit, "Horizontal limit",
"Stretch limit for horizontal constraints. It's affected by fabric horizontal stiffness multiplier."
"Reduce stiffness of tether constraints (or increase its scale) to allow cloth to stretch.\n"
"0: No stretching\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_verticalStretchLimit, "Vertical limit",
"Stretch limit for vertical constraints. It's affected by fabric vertical stiffness multiplier."
"Reduce stiffness of tether constraints (or increase its scale) to allow cloth to stretch.\n"
"0: No stretching\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_bendingStretchLimit, "Bending limit",
"Stretch limit for bending constraints. It's affected by fabric bending stiffness multiplier."
"Reduce stiffness of tether constraints (or increase its scale) to allow cloth to stretch.\n"
"0: No stretching\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_shearingStretchLimit, "Shearing limit",
"Stretch limit for shearing constraints. It's affected by fabric shearing stiffness multiplier."
"Reduce stiffness of tether constraints (or increase its scale) to allow cloth to stretch.\n"
"0: No stretching\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
// Tether constraints
->ClassElement(AZ::Edit::ClassElements::Group, "Tether constraints")
->DataElement(AZ::Edit::UIHandlers::Slider, &ClothConfiguration::m_tetherConstraintStiffness, "Stiffness",
"Stiffness for tether constraints. Tether constraints are generated when the inverse mass data of the cloth (selected in the cloth modifier) has static particles.\n"
"0: No tether constraints applied\n"
"1: Makes the constraints behave springy\n")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->Attribute(AZ::Edit::Attributes::Max, 1.0f)
->Attribute(AZ::Edit::Attributes::Step, 0.0001f)
->Attribute(AZ::Edit::Attributes::Decimals, 6)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_tetherConstraintScale, "Scale",
"Tether constraint scale")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
// Quality
->ClassElement(AZ::Edit::ClassElements::Group, "Quality")
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_solverFrequency, "Solver frequency",
"Target solver iterations per second. At least 1 iteration per frame will be solved regardless of the value set.")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_accelerationFilterIterations, "Acceleration filter Iterations",
"Number of iterations to average delta time factor used for gravity and external acceleration.")
->Attribute(AZ::Edit::Attributes::Min, 1)
->DataElement(AZ::Edit::UIHandlers::Default, &ClothConfiguration::m_removeStaticTriangles, "Remove static triangles",
"Removing static triangles improves performance by not taking into account triangles whose particles are all static.\n"
"The removed static particles will not be present for collision or self collision during simulation.")
;
}
}
}
EditorClothComponent::EditorClothComponent()
{
m_meshNodeList = { {Internal::StatusMessageNoAsset} };
m_config.m_populateMeshNodeListCallback = [this]()
{
return m_meshNodeList;
};
m_config.m_hasBackstopDataCallback = [this]()
{
auto meshNodeIt = m_meshNodesWithBackstopData.find(m_config.m_meshNode);
return meshNodeIt != m_meshNodesWithBackstopData.end();
};
m_config.m_getEntityIdCallback = [this]()
{
return GetEntityId();
};
}
EditorClothComponent::~EditorClothComponent() = default;
void EditorClothComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ClothMeshService", 0x6ffcbca5));
}
void EditorClothComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("MeshService", 0x71d8a455));
}
const MeshNodeList& EditorClothComponent::GetMeshNodeList() const
{
return m_meshNodeList;
}
const AZStd::unordered_set<AZStd::string>& EditorClothComponent::GetMeshNodesWithBackstopData() const
{
return m_meshNodesWithBackstopData;
}
void EditorClothComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
gameEntity->CreateComponent<ClothComponent>(m_config);
}
void EditorClothComponent::Activate()
{
AzToolsFramework::Components::EditorComponentBase::Activate();
LmbrCentral::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId());
}
void EditorClothComponent::Deactivate()
{
LmbrCentral::MeshComponentNotificationBus::Handler::BusDisconnect();
AzToolsFramework::Components::EditorComponentBase::Deactivate();
m_clothComponentMesh.reset();
}
void EditorClothComponent::OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (!asset.IsReady())
{
return;
}
m_meshNodeList.clear();
m_meshNodesWithBackstopData.clear();
AZStd::unique_ptr<AssetHelper> assetHelper = AssetHelper::CreateAssetHelper(GetEntityId());
if (assetHelper)
{
// Gather cloth mesh node list
assetHelper->GatherClothMeshNodes(m_meshNodeList);
for (const auto& meshNode : m_meshNodeList)
{
if (ContainsBackstopData(assetHelper.get(), meshNode))
{
m_meshNodesWithBackstopData.insert(meshNode);
}
}
}
if (m_meshNodeList.empty())
{
m_meshNodeList.emplace_back(Internal::StatusMessageNoClothNodes);
m_config.m_meshNode = Internal::StatusMessageNoClothNodes;
}
else
{
bool foundNode = AZStd::find(m_meshNodeList.cbegin(), m_meshNodeList.cend(), m_config.m_meshNode) != m_meshNodeList.cend();
if (!foundNode && !m_previousMeshNode.empty())
{
// Check the if the mesh node previously selected is still part of the mesh list
// to keep using it and avoid the user to select it again in the combo box.
foundNode = AZStd::find(m_meshNodeList.cbegin(), m_meshNodeList.cend(), m_previousMeshNode) != m_meshNodeList.cend();
if (foundNode)
{
m_config.m_meshNode = m_previousMeshNode;
}
}
// If the mesh node is not in the list then add and use an option
// that tells the user to select the node.
if (!foundNode)
{
m_meshNodeList.insert(m_meshNodeList.begin(), Internal::StatusMessageSelectNode);
m_config.m_meshNode = Internal::StatusMessageSelectNode;
}
}
m_previousMeshNode = "";
if (m_simulateInEditor)
{
m_clothComponentMesh = AZStd::make_unique<ClothComponentMesh>(GetEntityId(), m_config);
}
// Refresh UI
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_EntireTree);
}
void EditorClothComponent::OnMeshDestroyed()
{
m_previousMeshNode = m_config.m_meshNode;
m_meshNodeList = { {Internal::StatusMessageNoAsset} };
m_config.m_meshNode = Internal::StatusMessageNoAsset;
m_clothComponentMesh.reset();
m_meshNodesWithBackstopData.clear();
// Refresh UI
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_EntireTree);
}
bool EditorClothComponent::IsSimulatedInEditor() const
{
return m_simulateInEditor;
}
AZ::u32 EditorClothComponent::OnSimulatedInEditorToggled()
{
m_clothComponentMesh.reset();
m_simulateInEditor = !m_simulateInEditor;
if (m_simulateInEditor)
{
m_clothComponentMesh = AZStd::make_unique<ClothComponentMesh>(GetEntityId(), m_config);
}
else
{
// Force MeshComponent to reload current mesh asset in order to restore original mesh
AZ::Data::Asset<AZ::Data::AssetData> meshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(meshAsset, GetEntityId(), &LmbrCentral::MeshComponentRequests::GetMeshAsset);
LmbrCentral::MeshComponentRequestBus::Event(GetEntityId(), &LmbrCentral::MeshComponentRequests::SetMeshAsset, meshAsset.GetId());
}
return AZ::Edit::PropertyRefreshLevels::None;
}
void EditorClothComponent::OnConfigurationChanged()
{
if (m_clothComponentMesh)
{
m_clothComponentMesh->UpdateConfiguration(GetEntityId(), m_config);
}
}
bool EditorClothComponent::ContainsBackstopData(AssetHelper* assetHelper, const AZStd::string& meshNode) const
{
if (!assetHelper)
{
return false;
}
// Obtain cloth mesh info
MeshNodeInfo meshNodeInfo;
MeshClothInfo meshClothInfo;
bool clothInfoObtained = assetHelper->ObtainClothMeshNodeInfo(meshNode,
meshNodeInfo, meshClothInfo);
if (!clothInfoObtained)
{
return false;
}
return AZStd::any_of(
meshClothInfo.m_backstopData.cbegin(),
meshClothInfo.m_backstopData.cend(),
[](const AZ::Vector2& backstop)
{
const float backstopRadius = backstop.GetY();
return backstopRadius > 0.0f;
});
}
} // namespace NvCloth
@@ -0,0 +1,79 @@
/*
* 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/std/containers/unordered_set.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <Components/ClothConfiguration.h>
namespace NvCloth
{
class ClothComponentMesh;
//! Class for in-editor Cloth Component.
class EditorClothComponent
: public AzToolsFramework::Components::EditorComponentBase
, public LmbrCentral::MeshComponentNotificationBus::Handler
{
public:
AZ_EDITOR_COMPONENT(EditorClothComponent, "{2C99B4EF-8A5F-4585-89F9-86D50754DF7E}", AzToolsFramework::Components::EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
EditorClothComponent();
~EditorClothComponent();
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
const MeshNodeList& GetMeshNodeList() const;
const AZStd::unordered_set<AZStd::string>& GetMeshNodesWithBackstopData() const;
// EditorComponentBase overrides ...
void BuildGameEntity(AZ::Entity* gameEntity) override;
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
// LmbrCentral::MeshComponentNotificationBus::Handler overrides ...
void OnMeshCreated(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
void OnMeshDestroyed() override;
private:
bool IsSimulatedInEditor() const;
AZ::u32 OnSimulatedInEditorToggled();
void OnConfigurationChanged();
bool ContainsBackstopData(AssetHelper* assetHelper, const AZStd::string& meshNode) const;
ClothConfiguration m_config;
AZStd::unique_ptr<ClothComponentMesh> m_clothComponentMesh;
// List of mesh nodes from the asset that contains cloth data.
// This list is not serialized, it's compiled when the asset has been received via MeshComponentNotificationBus.
MeshNodeList m_meshNodeList;
AZStd::string m_previousMeshNode;
AZStd::unordered_set<AZStd::string> m_meshNodesWithBackstopData;
bool m_simulateInEditor = false;
};
} // namespace NvCloth
@@ -0,0 +1,65 @@
/*
* 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 <Editor/ComboBoxEditButtonPair.h>
namespace NvCloth
{
namespace Editor
{
ComboBoxEditButtonPair::ComboBoxEditButtonPair(QWidget* parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
m_comboBox = new QComboBox();
m_comboBox->installEventFilter(this);
m_editButton = new QToolButton();
m_editButton->setAutoRaise(true);
m_editButton->setToolTip(QString("Edit"));
m_editButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg"));
layout->addWidget(m_comboBox);
layout->addWidget(m_editButton);
}
bool ComboBoxEditButtonPair::eventFilter(QObject *object, QEvent *event)
{
AZ_UNUSED(object);
return event->type() == QEvent::Wheel;
}
QComboBox* ComboBoxEditButtonPair::GetComboBox()
{
return m_comboBox;
}
QToolButton* ComboBoxEditButtonPair::GetEditButton()
{
return m_editButton;
}
void ComboBoxEditButtonPair::SetEntityId(AZ::EntityId entityId)
{
m_entityId = entityId;
}
AZ::EntityId ComboBoxEditButtonPair::GetEntityId() const
{
return m_entityId;
}
} // namespace Editor
} // namespace NvCloth
@@ -0,0 +1,53 @@
/*
* 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>
// Disable warnings generated by QT headers:
// 4251: needs to have dll-interface to be used by clients
// 4800: 'uint': forcing value to bool 'true' or 'false'
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QBoxLayout>
#include <QToolButton>
#include <QComboBox>
#include <QEvent>
AZ_POP_DISABLE_WARNING
namespace NvCloth
{
namespace Editor
{
//! Wrapper widget for a combo box with a button at the end.
class ComboBoxEditButtonPair
: public QWidget
{
public:
explicit ComboBoxEditButtonPair(QWidget* parent);
QComboBox* GetComboBox();
QToolButton* GetEditButton();
void SetEntityId(AZ::EntityId entityId);
AZ::EntityId GetEntityId() const;
private:
bool eventFilter(QObject *object, QEvent *event) override;
QComboBox* m_comboBox = nullptr;
QToolButton* m_editButton = nullptr;
AZ::EntityId m_entityId;
};
} // namespace Editor
} // namespace NvCloth
@@ -0,0 +1,53 @@
/*
* 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 <Editor/EditorSystemComponent.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Editor/PropertyTypes.h>
namespace NvCloth
{
void EditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorSystemComponent, AZ::Component>()
->Version(0);
}
}
void EditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NvClothEditorService"));
}
void EditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NvClothEditorService"));
}
void EditorSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("NvClothService"));
}
void EditorSystemComponent::Activate()
{
m_propertyHandlers = Editor::RegisterPropertyTypes();
}
void EditorSystemComponent::Deactivate()
{
Editor::UnregisterPropertyTypes(m_propertyHandlers);
}
} // namespace NvCloth
@@ -0,0 +1,44 @@
/*
* 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/Component.h>
namespace AzToolsFramework
{
class PropertyHandlerBase;
}
namespace NvCloth
{
class EditorSystemComponent
: public AZ::Component
{
public:
AZ_COMPONENT(EditorSystemComponent, "{4EABD010-B50D-45C6-AE3D-A617B26B14CA}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
protected:
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
private:
AZStd::vector<AzToolsFramework::PropertyHandlerBase*> m_propertyHandlers;
};
} // namespace NvCloth
@@ -0,0 +1,131 @@
/*
* 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 <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <Editor/PropertyTypes.h>
#include <Editor/MeshNodeHandler.h>
namespace NvCloth
{
namespace Editor
{
AZ::u32 MeshNodeHandler::GetHandlerName() const
{
return MeshNodeSelector;
}
QWidget* MeshNodeHandler::CreateGUI(QWidget* parent)
{
widget_t* picker = new widget_t(parent);
// Set edit button appearance to go to FBX Settings dialog
picker->GetEditButton()->setToolTip("Open FBX Settings to setup Cloth Modifiers");
picker->GetEditButton()->setText("");
picker->GetEditButton()->setEnabled(false);
connect(picker->GetComboBox(),
&QComboBox::currentTextChanged, this,
[picker]()
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestWrite, picker);
});
connect(picker->GetEditButton(),
&QToolButton::clicked, this,
[this, picker]()
{
OnEditButtonClicked(picker);
});
return picker;
}
bool MeshNodeHandler::IsDefaultHandler() const
{
return true;
}
void MeshNodeHandler::ConsumeAttribute(widget_t* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, [[maybe_unused]] const char* debugName)
{
if (attrib == AZ::Edit::UIHandlers::EntityId)
{
AZ::EntityId value;
if (attrValue->Read<AZ::EntityId>(value))
{
GUI->SetEntityId(value);
}
else
{
AZ_WarningOnce("MeshNodeHandler", false, "Failed to read 'EntityId' attribute from property '%s'. Expected entity id.", debugName);
}
}
else if (attrib == AZ::Edit::Attributes::StringList)
{
AZStd::vector<AZStd::string> value;
if (attrValue->Read<AZStd::vector<AZStd::string>>(value))
{
QSignalBlocker signalBlocker(GUI->GetComboBox());
GUI->GetComboBox()->clear();
for (const auto& item : value)
{
GUI->GetComboBox()->addItem(item.c_str());
}
bool hasAsset = GetMeshAsset(GUI->GetEntityId()).Get() != nullptr;
GUI->GetEditButton()->setEnabled(hasAsset);
}
else
{
AZ_WarningOnce("MeshNodeHandler", false, "Failed to read 'StringList' attribute from property '%s'. Expected string vector.", debugName);
}
}
}
void MeshNodeHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, widget_t* GUI, property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
{
instance = GUI->GetComboBox()->currentText().toUtf8().data();
}
bool MeshNodeHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, widget_t* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
{
QSignalBlocker signalBlocker(GUI->GetComboBox());
GUI->GetComboBox()->setCurrentText(instance.c_str());
return true;
}
void MeshNodeHandler::OnEditButtonClicked(widget_t* GUI)
{
AZ::Data::Asset<AZ::Data::AssetData> meshAsset = GetMeshAsset(GUI->GetEntityId());
if (meshAsset)
{
// Open the asset with the preferred asset editor, which for Mesh and Actor Assets it's FBX Settings.
bool handled = false;
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Broadcast(
&AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotifications::OpenAssetInAssociatedEditor, meshAsset.GetId(), handled);
}
}
AZ::Data::Asset<AZ::Data::AssetData> MeshNodeHandler::GetMeshAsset(const AZ::EntityId entityId) const
{
AZ::Data::Asset<AZ::Data::AssetData> meshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(
meshAsset, entityId, &LmbrCentral::MeshComponentRequestBus::Events::GetMeshAsset);
return meshAsset;
}
} // namespace Editor
} // namespace NvCloth
#include <Source/Editor/moc_MeshNodeHandler.cpp>
@@ -0,0 +1,65 @@
/*
* 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
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <Editor/ComboBoxEditButtonPair.h>
#include <QObject>
#endif
namespace NvCloth
{
namespace Editor
{
/*
=============================================================
= Handler Documentation =
=============================================================
Custom handler for the Cloth Component's Mesh Node property as a ComboBoxEditButtonPair widget.
Handler Name: "MeshNodeSelector"
Available Attributes:
EntityId - Entity identifier used to query the mesh asset via MeshComponentRequestBus.
StringList - List of mesh node names that contain cloth data.
NOTE: EntityId must be the first attribute set so it's available when consuming StringList.
*/
class MeshNodeHandler
: public QObject
, public AzToolsFramework::PropertyHandler<AZStd::string, ComboBoxEditButtonPair>
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(MeshNodeHandler, AZ::SystemAllocator, 0);
MeshNodeHandler() = default;
// AzToolsFramework::PropertyHandler overrides ...
AZ::u32 GetHandlerName() const override;
QWidget* CreateGUI(QWidget* parent) override;
bool IsDefaultHandler() const override;
void ConsumeAttribute(widget_t* widget, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
void WriteGUIValuesIntoProperty(size_t index, widget_t* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
bool ReadValuesIntoGUI(size_t index, widget_t* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
private:
void OnEditButtonClicked(widget_t* GUI);
AZ::Data::Asset<AZ::Data::AssetData> GetMeshAsset(const AZ::EntityId entityId) const;
};
} // namespace Editor
} // namespace NvCloth
@@ -0,0 +1,48 @@
/*
* 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 <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <Editor/MeshNodeHandler.h>
namespace NvCloth
{
namespace Editor
{
AZStd::vector<AzToolsFramework::PropertyHandlerBase*> RegisterPropertyTypes()
{
AZStd::vector<AzToolsFramework::PropertyHandlerBase*> propertyHandlers =
{
aznew MeshNodeHandler()
};
for (const auto& handler : propertyHandlers)
{
AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(&AzToolsFramework::PropertyTypeRegistrationMessages::RegisterPropertyType, handler);
}
return propertyHandlers;
}
void UnregisterPropertyTypes(AZStd::vector<AzToolsFramework::PropertyHandlerBase*>& handlers)
{
for (auto handler : handlers)
{
if (!handler->AutoDelete())
{
AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(&AzToolsFramework::PropertyTypeRegistrationMessages::UnregisterPropertyType, handler);
delete handler;
}
}
handlers.clear();
}
} // namespace Editor
} // namespace NvCloth
@@ -0,0 +1,32 @@
/*
* 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/Crc.h>
#include <AzCore/std/containers/vector.h>
namespace AzToolsFramework
{
class PropertyHandlerBase;
}
namespace NvCloth
{
namespace Editor
{
const static AZ::Crc32 MeshNodeSelector = AZ_CRC("MeshNodeSelector", 0x50f06073);
AZStd::vector<AzToolsFramework::PropertyHandlerBase*> RegisterPropertyTypes();
void UnregisterPropertyTypes(AZStd::vector<AzToolsFramework::PropertyHandlerBase*>& handlers);
} // namespace Editor
} // namespace NvCloth
+122
View File
@@ -0,0 +1,122 @@
/*
* 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 <CrySystemBus.h>
#include <ISystem.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Module/Module.h>
#include <System/SystemComponent.h>
#include <System/FabricCooker.h>
#include <System/TangentSpaceHelper.h>
#include <Components/ClothComponent.h>
#ifdef NVCLOTH_EDITOR
#include <Editor/EditorSystemComponent.h>
#include <Components/EditorClothComponent.h>
#include <Pipeline/SceneAPIExt/ClothRuleBehavior.h>
#include <Pipeline/RCExt/CgfClothExporter.h>
#endif //NVCLOTH_EDITOR
namespace NvCloth
{
class Module
: public AZ::Module
, protected CrySystemEventBus::Handler
{
public:
AZ_RTTI(Module, "{34C529D4-688F-4B51-BF60-75425754A7E6}", AZ::Module);
AZ_CLASS_ALLOCATOR(Module, AZ::SystemAllocator, 0);
Module()
: AZ::Module()
{
SystemComponent::InitializeNvClothLibrary();
// IFabricCooker and ITangentSpaceHelper interfaces will be available
// at both runtime and asset build time.
m_fabricCooker = AZStd::make_unique<FabricCooker>();
m_tangentSpaceHelper = AZStd::make_unique<TangentSpaceHelper>();
CrySystemEventBus::Handler::BusConnect();
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
SystemComponent::CreateDescriptor(),
ClothComponent::CreateDescriptor(),
#ifdef NVCLOTH_EDITOR
EditorSystemComponent::CreateDescriptor(),
EditorClothComponent::CreateDescriptor(),
Pipeline::ClothRuleBehavior::CreateDescriptor(),
Pipeline::CgfClothExporter::CreateDescriptor(),
#endif //NVCLOTH_EDITOR
});
}
~Module()
{
CrySystemEventBus::Handler::BusDisconnect();
m_tangentSpaceHelper.reset();
m_fabricCooker.reset();
SystemComponent::TearDownNvClothLibrary();
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<SystemComponent>()
#ifdef NVCLOTH_EDITOR
, azrtti_typeid<EditorSystemComponent>()
#endif //NVCLOTH_EDITOR
};
}
protected:
// CrySystemEventBus ...
void OnCrySystemPreInitialize(ISystem& system, const SSystemInitParams& systemInitParams) override;
void OnCrySystemPostShutdown() override;
private:
AZStd::unique_ptr<FabricCooker> m_fabricCooker;
AZStd::unique_ptr<TangentSpaceHelper> m_tangentSpaceHelper;
};
void Module::OnCrySystemPreInitialize(
[[maybe_unused]] ISystem& system,
[[maybe_unused]] const SSystemInitParams& systemInitParams)
{
#if !defined(AZ_MONOLITHIC_BUILD)
// When module is linked dynamically, we must set our gEnv pointer.
// When module is linked statically, we'll share the application's gEnv pointer.
gEnv = system.GetGlobalEnvironment();
#endif
}
void Module::OnCrySystemPostShutdown()
{
#if !defined(AZ_MONOLITHIC_BUILD)
gEnv = nullptr;
#endif
}
} // namespace NvCloth
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_NvCloth, NvCloth::Module)
@@ -0,0 +1,18 @@
/*
* 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/Module/Module.h>
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_NvCloth, AZ::Module)
@@ -0,0 +1,120 @@
/*
* 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 <AzToolsFramework/Debug/TraceContext.h>
#include <Cry_Geo.h> // Needed for CGFContent.h
#include <CGFContent.h>
#include <AzCore/Math/MathUtils.h>
#include <AzCore/Math/Color.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexColorData.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IClothRule.h>
#include <RC/ResourceCompilerScene/Common/CommonExportContexts.h>
#include <Pipeline/RCExt/CgfClothExporter.h>
namespace NvCloth
{
namespace Pipeline
{
namespace
{
// Index for the Vertex color stream that contains the cloth inverse masses.
const int ClothVertexBufferStreamIndex = 1;
}
CgfClothExporter::CgfClothExporter()
{
// Binding the processing functions so when exporters call
// SceneAPI::Events::Process<Context>() these functions will
// get called if their Context was used.
BindToCall(&CgfClothExporter::ProcessMeshNodeContext);
BindToCall(&CgfClothExporter::ProcessContainerContext);
}
void CgfClothExporter::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<CgfClothExporter, AZ::SceneAPI::SceneCore::RCExportingComponent>()->Version(1);
}
}
AZ::SceneAPI::Events::ProcessingResult CgfClothExporter::ProcessContainerContext(AZ::RC::ContainerExportContext& context) const
{
if (!context.m_group.GetRuleContainerConst().ContainsRuleOfType<AZ::SceneAPI::DataTypes::IClothRule>())
{
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
if (context.m_phase == AZ::RC::Phase::Finalizing)
{
if (context.m_container.GetExportInfo()->bMergeAllNodes)
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow,
"Mesh group '%s' has cloth rules and trying to merge all nodes.",
context.m_group.GetName().c_str());
return AZ::SceneAPI::Events::ProcessingResult::Failure;
}
}
else
{
// If the current mesh group contains a cloth rule it should not merge all the nodes.
context.m_container.GetExportInfo()->bMergeAllNodes = false;
}
return AZ::SceneAPI::Events::ProcessingResult::Success;
}
AZ::SceneAPI::Events::ProcessingResult CgfClothExporter::ProcessMeshNodeContext(AZ::RC::MeshNodeExportContext& context) const
{
if (context.m_phase != AZ::RC::Phase::Filling)
{
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
AZStd::vector<AZ::Color> clothData =
AZ::SceneAPI::DataTypes::IClothRule::FindClothData(
context.m_scene.GetGraph(),
context.m_nodeIndex,
static_cast<size_t>(context.m_mesh.GetVertexCount()),
context.m_group.GetRuleContainerConst());
if (!clothData.empty())
{
const int numVertices = context.m_mesh.GetVertexCount();
// Allocate and get the vertex color stream for cloth
context.m_mesh.ReallocStream(CMesh::COLORS, ClothVertexBufferStreamIndex, numVertices);
auto meshColorStream = context.m_mesh.GetStreamPtr<SMeshColor>(CMesh::COLORS, ClothVertexBufferStreamIndex);
AZ_Assert(meshColorStream, "Mesh color stream is invalid");
for (int i = 0; i < numVertices; ++i)
{
const auto& clothVertexData = clothData[i];
meshColorStream[i] = SMeshColor(
clothVertexData.GetR8(),
clothVertexData.GetG8(),
clothVertexData.GetB8(),
clothVertexData.GetA8());
}
}
return AZ::SceneAPI::Events::ProcessingResult::Success;
}
} // namespace Pipeline
} // namespace NvCloth
@@ -0,0 +1,50 @@
/*
* 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 <SceneAPI/SceneCore/Components/RCExportingComponent.h>
namespace AZ
{
namespace RC
{
struct MeshNodeExportContext;
struct ContainerExportContext;
}
}
namespace NvCloth
{
namespace Pipeline
{
//! This class processes the Scene graph to export cloth data into CGF.
class CgfClothExporter
: public AZ::SceneAPI::SceneCore::RCExportingComponent
{
public:
AZ_COMPONENT(CgfClothExporter, "{3D7287BB-1109-4220-AC44-AEBA59E03FFF}", AZ::SceneAPI::SceneCore::RCExportingComponent);
CgfClothExporter();
static void Reflect(AZ::ReflectContext* context);
//! Process call at CGF Container level.
//! This function gets called once per Mesh Group from CGF Group Exporter when it's processing meshes.
AZ::SceneAPI::Events::ProcessingResult ProcessContainerContext(AZ::RC::ContainerExportContext& context) const;
//! Process call at Mesh Node level.
//! This function gets called once per Mesh Node inside a Mesh Group from CGF Group Exporter when it's processing meshes.
AZ::SceneAPI::Events::ProcessingResult ProcessMeshNodeContext(AZ::RC::MeshNodeExportContext& context) const;
};
} // namespace Pipeline
} // namespace NvCloth
@@ -0,0 +1,349 @@
/*
* 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/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexColorData.h>
#include <Pipeline/SceneAPIExt/ClothRule.h>
namespace NvCloth
{
namespace Pipeline
{
// It's necessary for the rule to specify the system allocator, otherwise
// the editor crashes when deleting the cloth modifier from FBX Settings.
AZ_CLASS_ALLOCATOR_IMPL(ClothRule, AZ::SystemAllocator, 0)
const char* const ClothRule::DefaultChooseNodeName = "Choose a node";
const char* const ClothRule::DefaultInverseMassesString = "Default: 1.0";
const char* const ClothRule::DefaultMotionConstraintsString = "Default: 1.0";
const char* const ClothRule::DefaultBackstopString = "None";
const AZStd::string& ClothRule::GetMeshNodeName() const
{
return m_meshNodeName;
}
AZStd::vector<AZ::Color> ClothRule::ExtractClothData(
const AZ::SceneAPI::Containers::SceneGraph& graph,
const size_t numVertices) const
{
const auto meshNodeIndex = graph.Find(GetMeshNodeName());
if (!meshNodeIndex.IsValid())
{
return {};
}
const float defaultInverseMass = 1.0f;
const float defaultMotionConstraint = 1.0f;
const float defaultBackstopOffset = 0.5f; // 0.5 means offset 0 once the range is converted from [0,1] -> [-1,1]
const float defaultBackstopRadius = 0.0f;
using GetterFunction = AZStd::function<float(size_t index)>;
GetterFunction getInverseMass = [defaultInverseMass]([[maybe_unused]] size_t index) { return defaultInverseMass; };
GetterFunction getMotionConstraint = [defaultMotionConstraint]([[maybe_unused]] size_t index) { return defaultMotionConstraint; };
GetterFunction getBackstopOffset = [defaultBackstopOffset]([[maybe_unused]] size_t index) { return defaultBackstopOffset; };
GetterFunction getBackstopRadius = [defaultBackstopRadius]([[maybe_unused]] size_t index) { return defaultBackstopRadius; };
auto getColorChannelSafe = [](
const AZ::SceneAPI::DataTypes::IMeshVertexColorData* data,
size_t index,
AZ::SceneAPI::DataTypes::ColorChannel channel)
{
const float colorChannel = data->GetColor(index).GetChannel(channel);
return AZ::GetClamp(colorChannel, 0.0f, 1.0f);
};
if (!IsInverseMassesStreamDisabled())
{
if (auto data = FindVertexColorData(graph, meshNodeIndex, m_inverseMassesStreamName, numVertices))
{
getInverseMass =
[&getColorChannelSafe, data, channel = m_inverseMassesChannel](size_t index)
{
return getColorChannelSafe(data.get(), index, channel);
};
}
}
if (!IsMotionConstraintsStreamDisabled())
{
if (auto data = FindVertexColorData(graph, meshNodeIndex, m_motionConstraintsStreamName, numVertices))
{
getMotionConstraint =
[&getColorChannelSafe, data, channel = m_motionConstraintsChannel](size_t index)
{
return getColorChannelSafe(data.get(), index, channel);
};
}
}
if (!IsBackstopStreamDisabled())
{
if (auto data = FindVertexColorData(graph, meshNodeIndex, m_backstopStreamName, numVertices))
{
getBackstopOffset =
[&getColorChannelSafe, data, channel = m_backstopOffsetChannel](size_t index)
{
return getColorChannelSafe(data.get(), index, channel);
};
getBackstopRadius =
[&getColorChannelSafe, data, channel = m_backstopRadiusChannel](size_t index)
{
return getColorChannelSafe(data.get(), index, channel);
};
}
}
AZStd::vector<AZ::Color> clothData;
clothData.resize_no_construct(numVertices);
// Compile all the data to the vertex color stream of the mesh.
for (size_t i = 0; i < numVertices; ++i)
{
clothData[i].Set(
getInverseMass(i), // Store inverse masses in red channel
getMotionConstraint(i), // Store motion constraints in green channel
getBackstopOffset(i), // Store backstop offsets in blue channel
getBackstopRadius(i)); // Store backstop radius in alpha channel
}
return clothData;
}
const AZStd::string& ClothRule::GetInverseMassesStreamName() const
{
return m_inverseMassesStreamName;
}
const AZStd::string& ClothRule::GetMotionConstraintsStreamName() const
{
return m_motionConstraintsStreamName;
}
const AZStd::string& ClothRule::GetBackstopStreamName() const
{
return m_backstopStreamName;
}
void ClothRule::SetMeshNodeName(const AZStd::string& name)
{
m_meshNodeName = name;
}
void ClothRule::SetInverseMassesStreamName(const AZStd::string& name)
{
m_inverseMassesStreamName = name;
}
void ClothRule::SetMotionConstraintsStreamName(const AZStd::string& name)
{
m_motionConstraintsStreamName = name;
}
void ClothRule::SetBackstopStreamName(const AZStd::string& name)
{
m_backstopStreamName = name;
}
bool ClothRule::IsInverseMassesStreamDisabled() const
{
return m_inverseMassesStreamName == DefaultInverseMassesString;
}
bool ClothRule::IsMotionConstraintsStreamDisabled() const
{
return m_motionConstraintsStreamName == DefaultMotionConstraintsString;
}
bool ClothRule::IsBackstopStreamDisabled() const
{
return m_backstopStreamName == DefaultBackstopString;
}
AZ::SceneAPI::DataTypes::ColorChannel ClothRule::GetInverseMassesStreamChannel() const
{
return m_inverseMassesChannel;
}
AZ::SceneAPI::DataTypes::ColorChannel ClothRule::GetMotionConstraintsStreamChannel() const
{
return m_motionConstraintsChannel;
}
AZ::SceneAPI::DataTypes::ColorChannel ClothRule::GetBackstopOffsetStreamChannel() const
{
return m_backstopOffsetChannel;
}
AZ::SceneAPI::DataTypes::ColorChannel ClothRule::GetBackstopRadiusStreamChannel() const
{
return m_backstopRadiusChannel;
}
void ClothRule::SetInverseMassesStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel)
{
m_inverseMassesChannel = channel;
}
void ClothRule::SetMotionConstraintsStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel)
{
m_motionConstraintsChannel = channel;
}
void ClothRule::SetBackstopOffsetStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel)
{
m_backstopOffsetChannel = channel;
}
void ClothRule::SetBackstopRadiusStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel)
{
m_backstopRadiusChannel = channel;
}
AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IMeshVertexColorData> ClothRule::FindVertexColorData(
const AZ::SceneAPI::Containers::SceneGraph& graph,
const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& meshNodeIndex,
const AZStd::string& vertexColorName,
const size_t numVertices) const
{
if (vertexColorName.empty())
{
return nullptr;
}
const auto vertexColorNodeIndex = graph.Find(meshNodeIndex, vertexColorName);
auto vertexColorData = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshVertexColorData*>(graph.GetNodeContent(vertexColorNodeIndex));
if (vertexColorData)
{
if (numVertices != vertexColorData->GetCount())
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow,
"Number of vertices in the mesh node '%s' (%zu) doesn't match with the number of stored vertex color stream '%s' (%zu).",
GetMeshNodeName().c_str(), numVertices, vertexColorName.c_str(), vertexColorData->GetCount());
vertexColorData.reset();
}
}
else
{
AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow,
"Vertex color stream '%s' not found for mesh node '%s'.",
vertexColorName.c_str(),
GetMeshNodeName().c_str());
}
return vertexColorData;
}
void ClothRule::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (!serializeContext)
{
return;
}
serializeContext->Class<AZ::SceneAPI::DataTypes::IClothRule, AZ::SceneAPI::DataTypes::IRule>()->Version(1);
serializeContext->Class<ClothRule, AZ::SceneAPI::DataTypes::IClothRule>()
->Version(2, &VersionConverter)
->Field("meshNodeName", &ClothRule::m_meshNodeName)
->Field("inverseMassesStreamName", &ClothRule::m_inverseMassesStreamName)
->Field("inverseMassesChannel", &ClothRule::m_inverseMassesChannel)
->Field("motionConstraintsStreamName", &ClothRule::m_motionConstraintsStreamName)
->Field("motionConstraintsChannel", &ClothRule::m_motionConstraintsChannel)
->Field("backstopStreamName", &ClothRule::m_backstopStreamName)
->Field("backstopOffsetChannel", &ClothRule::m_backstopOffsetChannel)
->Field("backstopRadiusChannel", &ClothRule::m_backstopRadiusChannel);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<ClothRule>("Cloth", "Adds cloth data to the exported CGF asset. The cloth data will be used to determine what meshes to use for cloth simulation.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->DataElement("NodeListSelection", &ClothRule::m_meshNodeName, "Select Cloth Mesh", "Mesh used for cloth simulation.")
->Attribute("ClassTypeIdFilter", AZ::SceneAPI::DataTypes::IMeshData::TYPEINFO_Uuid())
->Attribute("DisabledOption", DefaultChooseNodeName)
->DataElement("NodeListSelection", &ClothRule::m_inverseMassesStreamName, "Inverse Masses",
"Select the 'vertex color' stream that contains cloth inverse masses or 'Default: 1.0' to use mass 1.0 for all vertices.")
->Attribute("ClassTypeIdFilter", AZ::SceneAPI::DataTypes::IMeshVertexColorData::TYPEINFO_Uuid())
->Attribute("DisabledOption", DefaultInverseMassesString)
->Attribute("UseShortNames", true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ClothRule::m_inverseMassesChannel, "Inverse Masses Channel",
"Select which color channel to obtain the inverse mass information from.")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Red, "Red")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Green, "Green")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Blue, "Blue")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Alpha, "Alpha")
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothRule::IsInverseMassesStreamDisabled)
->DataElement("NodeListSelection", &ClothRule::m_motionConstraintsStreamName, "Motion Constraints",
"Select the 'vertex color' stream that contains cloth motion constraints or 'Default: 1.0' to use 1.0 for all vertices.")
->Attribute("ClassTypeIdFilter", AZ::SceneAPI::DataTypes::IMeshVertexColorData::TYPEINFO_Uuid())
->Attribute("DisabledOption", DefaultMotionConstraintsString)
->Attribute("UseShortNames", true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ClothRule::m_motionConstraintsChannel, "Motion Constraints Channel",
"Select which color channel to obtain the motion constraints information from.")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Red, "Red")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Green, "Green")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Blue, "Blue")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Alpha, "Alpha")
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothRule::IsMotionConstraintsStreamDisabled)
->DataElement("NodeListSelection", &ClothRule::m_backstopStreamName, "Backstop",
"Select the 'vertex color' stream that contains cloth backstop data.")
->Attribute("ClassTypeIdFilter", AZ::SceneAPI::DataTypes::IMeshVertexColorData::TYPEINFO_Uuid())
->Attribute("DisabledOption", DefaultBackstopString)
->Attribute("UseShortNames", true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ClothRule::m_backstopOffsetChannel, "Backstop Offset Channel",
"Select which color channel to obtain the backstop offset from.")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Red, "Red")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Green, "Green")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Blue, "Blue")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Alpha, "Alpha")
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothRule::IsBackstopStreamDisabled)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ClothRule::m_backstopRadiusChannel, "Backstop Radius Channel",
"Select which color channel to obtain the backstop radius from.")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Red, "Red")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Green, "Green")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Blue, "Blue")
->EnumAttribute(AZ::SceneAPI::DataTypes::ColorChannel::Alpha, "Alpha")
->Attribute(AZ::Edit::Attributes::ReadOnly, &ClothRule::IsBackstopStreamDisabled);
}
}
bool ClothRule::VersionConverter(
AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement)
{
if (classElement.GetVersion() <= 1)
{
AZStd::string vertexColorStreamName;
classElement.FindSubElementAndGetData(AZ_CRC("vertexColorStreamName", 0xc5921188), vertexColorStreamName);
classElement.RemoveElementByName(AZ_CRC("vertexColorStreamName", 0xc5921188));
classElement.AddElementWithData(context, "inverseMassesStreamName", vertexColorStreamName.empty() ? AZStd::string(DefaultInverseMassesString) : vertexColorStreamName);
classElement.AddElementWithData(context, "motionConstraintsStreamName", AZStd::string(DefaultMotionConstraintsString));
classElement.AddElementWithData(context, "backstopStreamName", AZStd::string(DefaultBackstopString));
}
return true;
}
} // namespace Pipeline
} // namespace NvCloth
@@ -0,0 +1,93 @@
/*
* 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/Serialization/SerializeContext.h>
#include <SceneAPI/SceneCore/DataTypes/Rules/IClothRule.h>
#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexColorData.h>
namespace AZ
{
class ReflectContext;
}
namespace NvCloth
{
namespace Pipeline
{
//! This class represents the data of a cloth rule (aka cloth modifier).
class ClothRule
: public AZ::SceneAPI::DataTypes::IClothRule
{
public:
AZ_RTTI(ClothRule, "{2F5AC324-314A-4C53-AFFF-DDFA46605DDB}", AZ::SceneAPI::DataTypes::IClothRule);
AZ_CLASS_ALLOCATOR_DECL
static void Reflect(AZ::ReflectContext* context);
static bool VersionConverter(
AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement);
static const char* const DefaultChooseNodeName;
static const char* const DefaultInverseMassesString;
static const char* const DefaultMotionConstraintsString;
static const char* const DefaultBackstopString;
// IClothRule overrides ...
const AZStd::string& GetMeshNodeName() const override;
AZStd::vector<AZ::Color> ExtractClothData(const AZ::SceneAPI::Containers::SceneGraph& graph, const size_t numVertices) const override;
const AZStd::string& GetInverseMassesStreamName() const;
const AZStd::string& GetMotionConstraintsStreamName() const;
const AZStd::string& GetBackstopStreamName() const;
void SetMeshNodeName(const AZStd::string& name);
void SetInverseMassesStreamName(const AZStd::string& name);
void SetMotionConstraintsStreamName(const AZStd::string& name);
void SetBackstopStreamName(const AZStd::string& name);
bool IsInverseMassesStreamDisabled() const;
bool IsMotionConstraintsStreamDisabled() const;
bool IsBackstopStreamDisabled() const;
AZ::SceneAPI::DataTypes::ColorChannel GetInverseMassesStreamChannel() const;
AZ::SceneAPI::DataTypes::ColorChannel GetMotionConstraintsStreamChannel() const;
AZ::SceneAPI::DataTypes::ColorChannel GetBackstopOffsetStreamChannel() const;
AZ::SceneAPI::DataTypes::ColorChannel GetBackstopRadiusStreamChannel() const;
void SetInverseMassesStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel);
void SetMotionConstraintsStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel);
void SetBackstopOffsetStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel);
void SetBackstopRadiusStreamChannel(AZ::SceneAPI::DataTypes::ColorChannel channel);
protected:
AZStd::shared_ptr<const AZ::SceneAPI::DataTypes::IMeshVertexColorData> FindVertexColorData(
const AZ::SceneAPI::Containers::SceneGraph& graph,
const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& meshNodeIndex,
const AZStd::string& vertexColorName,
const size_t numVertices) const;
AZStd::string m_meshNodeName;
AZStd::string m_inverseMassesStreamName;
AZStd::string m_motionConstraintsStreamName;
AZStd::string m_backstopStreamName;
AZ::SceneAPI::DataTypes::ColorChannel m_inverseMassesChannel = AZ::SceneAPI::DataTypes::ColorChannel::Red;
AZ::SceneAPI::DataTypes::ColorChannel m_motionConstraintsChannel = AZ::SceneAPI::DataTypes::ColorChannel::Red;
AZ::SceneAPI::DataTypes::ColorChannel m_backstopOffsetChannel = AZ::SceneAPI::DataTypes::ColorChannel::Red;
AZ::SceneAPI::DataTypes::ColorChannel m_backstopRadiusChannel = AZ::SceneAPI::DataTypes::ColorChannel::Green;
};
} // namespace Pipeline
} // namespace NvCloth
@@ -0,0 +1,228 @@
/*
* 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 <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <EMotionFX/Pipeline/SceneAPIExt/Groups/IActorGroup.h>
#include <Pipeline/SceneAPIExt/ClothRuleBehavior.h>
#include <Pipeline/SceneAPIExt/ClothRule.h>
namespace NvCloth
{
namespace Pipeline
{
void ClothRuleBehavior::Activate()
{
AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler::BusConnect();
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusConnect();
}
void ClothRuleBehavior::Deactivate()
{
AZ::SceneAPI::Events::AssetImportRequestBus::Handler::BusDisconnect();
AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler::BusDisconnect();
}
void ClothRuleBehavior::Reflect(AZ::ReflectContext* context)
{
ClothRule::Reflect(context);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<ClothRuleBehavior, BehaviorComponent>()->Version(1);
}
}
void ClothRuleBehavior::GetAvailableModifiers(
AZ::SceneAPI::Events::ManifestMetaInfo::ModifiersList& modifiers,
const AZ::SceneAPI::Containers::Scene& scene,
const AZ::SceneAPI::DataTypes::IManifestObject& target)
{
AZ_UNUSED(scene);
if (target.RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::ISceneNodeGroup::TYPEINFO_Uuid()))
{
const AZ::SceneAPI::DataTypes::ISceneNodeGroup* group = azrtti_cast<const AZ::SceneAPI::DataTypes::ISceneNodeGroup*>(&target);
if (IsValidGroupType(*group))
{
modifiers.push_back(ClothRule::TYPEINFO_Uuid());
}
}
}
void ClothRuleBehavior::InitializeObject(
[[maybe_unused]] const AZ::SceneAPI::Containers::Scene& scene,
AZ::SceneAPI::DataTypes::IManifestObject& target)
{
// When a cloth rule is created in the FBX Editor Settings...
if (target.RTTI_IsTypeOf(ClothRule::TYPEINFO_Uuid()))
{
ClothRule* clothRule = azrtti_cast<ClothRule*>(&target);
// Set default values
clothRule->SetMeshNodeName(ClothRule::DefaultChooseNodeName);
clothRule->SetInverseMassesStreamName(ClothRule::DefaultInverseMassesString);
clothRule->SetMotionConstraintsStreamName(ClothRule::DefaultMotionConstraintsString);
clothRule->SetBackstopStreamName(ClothRule::DefaultBackstopString);
}
}
AZ::SceneAPI::Events::ProcessingResult ClothRuleBehavior::UpdateManifest(
AZ::SceneAPI::Containers::Scene& scene,
ManifestAction action,
RequestingApplication requester)
{
AZ_UNUSED(requester);
// When the manifest is updated let's check the content is still valid for cloth rules
if (action == ManifestAction::Update)
{
bool updated = UpdateClothRules(scene);
return updated ? AZ::SceneAPI::Events::ProcessingResult::Success : AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
else
{
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
}
}
bool ClothRuleBehavior::IsValidGroupType(const AZ::SceneAPI::DataTypes::ISceneNodeGroup& group) const
{
// Cloth rules are available in Mesh and Actor Groups
return group.RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IMeshGroup::TYPEINFO_Uuid())
|| group.RTTI_IsTypeOf(EMotionFX::Pipeline::Group::IActorGroup::TYPEINFO_Uuid());
}
bool ClothRuleBehavior::UpdateClothRules(AZ::SceneAPI::Containers::Scene& scene)
{
bool rulesUpdated = false;
auto& manifest = scene.GetManifest();
auto valueStorage = manifest.GetValueStorage();
auto view = AZ::SceneAPI::Containers::MakeDerivedFilterView<AZ::SceneAPI::DataTypes::ISceneNodeGroup>(valueStorage);
// For each scene group...
for (auto& group : view)
{
bool isValidGroupType = IsValidGroupType(group);
AZStd::vector<size_t> rulesToRemove;
auto& groupRules = group.GetRuleContainer();
for (size_t index = 0; index < groupRules.GetRuleCount(); ++index)
{
ClothRule* clothRule = azrtti_cast<ClothRule*>(groupRules.GetRule(index).get());
if (clothRule)
{
if (isValidGroupType)
{
rulesUpdated = UpdateClothRule(scene.GetGraph(), group, *clothRule) || rulesUpdated;
}
else
{
// Cloth rule found in a group that shouldn't have cloth rules, add for removal.
rulesToRemove.push_back(index);
rulesUpdated = true;
}
}
}
// Remove in reversed order, as otherwise the indices will be wrong. For example if we remove index 3, then index 6 would really be 5 afterwards.
// By doing this in reversed order we remove items at the end of the list first so it won't impact the indices of previous ones.
for (AZStd::vector<size_t>::reverse_iterator it = rulesToRemove.rbegin(); it != rulesToRemove.rend(); ++it)
{
groupRules.RemoveRule(*it);
}
}
return rulesUpdated;
}
bool ClothRuleBehavior::UpdateClothRule(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::DataTypes::ISceneNodeGroup& group, ClothRule& clothRule)
{
bool ruleUpdated = false;
if (clothRule.GetMeshNodeName() != ClothRule::DefaultChooseNodeName)
{
bool foundMeshNode = false;
const AZStd::string& meshNodeName = clothRule.GetMeshNodeName();
if (!meshNodeName.empty())
{
const auto& selectedNodesList = group.GetSceneNodeSelectionList();
for (size_t i = 0; i < selectedNodesList.GetSelectedNodeCount(); ++i)
{
if (meshNodeName == selectedNodesList.GetSelectedNode(i))
{
foundMeshNode = true;
break;
}
}
}
// Mesh node selected in the cloth rule is not part of the list of selected nodes anymore, set the default value.
if (!foundMeshNode)
{
clothRule.SetMeshNodeName(ClothRule::DefaultChooseNodeName);
ruleUpdated = true;
}
}
// If the Vertex Color Stream selected for the inverse masses doesn't exist anymore, set the default value.
if (!clothRule.IsInverseMassesStreamDisabled() &&
!ContainsVertexColorStream(graph, clothRule.GetInverseMassesStreamName()))
{
clothRule.SetInverseMassesStreamName(ClothRule::DefaultInverseMassesString);
ruleUpdated = true;
}
// If the Vertex Color Stream selected for the motion constraints doesn't exist anymore, set the default value.
if (!clothRule.IsMotionConstraintsStreamDisabled() &&
!ContainsVertexColorStream(graph, clothRule.GetMotionConstraintsStreamName()))
{
clothRule.SetMotionConstraintsStreamName(ClothRule::DefaultMotionConstraintsString);
ruleUpdated = true;
}
// If the Vertex Color Stream selected for the backstop doesn't exist anymore, set the default value.
if (!clothRule.IsBackstopStreamDisabled() &&
!ContainsVertexColorStream(graph, clothRule.GetBackstopStreamName()))
{
clothRule.SetBackstopStreamName(ClothRule::DefaultBackstopString);
ruleUpdated = true;
}
return ruleUpdated;
}
bool ClothRuleBehavior::ContainsVertexColorStream(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZStd::string& streamName) const
{
if (streamName.empty())
{
return false;
}
auto graphNames = graph.GetNameStorage();
auto graphNameIt = AZStd::find_if(graphNames.cbegin(), graphNames.cend(),
[&streamName](const AZ::SceneAPI::Containers::SceneGraph::NameStorageType& graphName)
{
return streamName == graphName.GetName();
});
return graphNameIt != graphNames.cend();
}
} // namespace Pipeline
} // namespace NvCloth
@@ -0,0 +1,81 @@
/*
* 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 <SceneAPI/SceneCore/Components/BehaviorComponent.h>
#include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
namespace AZ
{
namespace SceneAPI
{
namespace DataTypes
{
class ISceneNodeGroup;
}
namespace Containers
{
class Scene;
class SceneGraph;
}
}
}
namespace NvCloth
{
namespace Pipeline
{
class ClothRule;
//! This class defines the behavior of how to treat the cloth rule data
//! through the SceneAPI.
//! It specifies the valid Scene Groups that are allowed to have
//! cloth rules (aka cloth modifiers), these are Mesh and Actor groups.
//! It also validates the cloth rules data for the manifest (asset containing
//! all the Scene information from the FBX Editor Settings).
class ClothRuleBehavior
: public AZ::SceneAPI::SceneCore::BehaviorComponent
, public AZ::SceneAPI::Events::ManifestMetaInfoBus::Handler
, public AZ::SceneAPI::Events::AssetImportRequestBus::Handler
{
public:
AZ_COMPONENT(ClothRuleBehavior, "{00FA6C8A-27D2-4C0E-B601-6917950432E5}", AZ::SceneAPI::SceneCore::BehaviorComponent);
static void Reflect(AZ::ReflectContext* context);
// BehaviorComponent overrides ...
void Activate() override;
void Deactivate() override;
// ManifestMetaInfoBus::Handler overrides ...
void GetAvailableModifiers(
AZ::SceneAPI::Events::ManifestMetaInfo::ModifiersList& modifiers,
const AZ::SceneAPI::Containers::Scene& scene,
const AZ::SceneAPI::DataTypes::IManifestObject& target) override;
void InitializeObject(const AZ::SceneAPI::Containers::Scene& scene, AZ::SceneAPI::DataTypes::IManifestObject& target) override;
// AssetImportRequestBus::Handler overrides ....
AZ::SceneAPI::Events::ProcessingResult UpdateManifest(AZ::SceneAPI::Containers::Scene& scene, ManifestAction action, RequestingApplication requester) override;
protected:
bool IsValidGroupType(const AZ::SceneAPI::DataTypes::ISceneNodeGroup& group) const;
bool UpdateClothRules(AZ::SceneAPI::Containers::Scene& scene);
bool UpdateClothRule(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::DataTypes::ISceneNodeGroup& group, ClothRule& clothRule);
bool ContainsVertexColorStream(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZStd::string& streamName) const;
};
} // namespace Pipeline
} // namespace NvCloth
+694
View File
@@ -0,0 +1,694 @@
/*
* 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/Math/Transform.h>
#include <AzCore/Math/Quaternion.h>
#include <System/Cloth.h>
#include <System/Fabric.h>
#include <System/Solver.h>
// NvCloth library includes
#include <NvCloth/Cloth.h>
#include <NvClothExt/ClothFabricCooker.h>
#include <foundation/PxVec3.h>
#include <foundation/PxVec4.h>
#include <foundation/PxQuat.h>
namespace NvCloth
{
namespace Internal
{
// Returns AZ::Vector3 as physx::PxVec3 using the same memory.
// It's safe to reinterpret AZ::Vector3 as physx::PxVec3 because they have the same memory layout
// and AZ::Vector3 has more memory alignment restrictions than physx::PxVec3.
// The opposite operation would NOT be safe.
physx::PxVec3& AsPxVec3(AZ::Vector3& azVec)
{
return *reinterpret_cast<physx::PxVec3*>(&azVec);
}
const physx::PxVec3& AsPxVec3(const AZ::Vector3& azVec)
{
return *reinterpret_cast<const physx::PxVec3*>(&azVec);
}
// Returns AZ::Quaternion as physx::PxQuat using the same memory.
// It's safe to reinterpret AZ::Quaternion as physx::PxQuat because they have the same memory layout
// and AZ::Quaternion has more memory alignment restrictions than physx::PxQuat.
// The opposite operation would NOT be safe.
physx::PxQuat& AsPxQuat(AZ::Quaternion& azQuat)
{
return *reinterpret_cast<physx::PxQuat*>(&azQuat);
}
const physx::PxQuat& AsPxQuat(const AZ::Quaternion& azQuat)
{
return *reinterpret_cast<const physx::PxQuat*>(&azQuat);
}
// Copies an AZ vector of AZ::Vector4 elements as a NvCloth Range of physx::PxVec4 elements.
//
// It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout.
// Each one has its own memory with their appropriate alignments.
void FastCopy(const AZStd::vector<AZ::Vector4>& azVector, nv::cloth::Range<physx::PxVec4>& nvRange)
{
AZ_Assert(azVector.size() == nvRange.size(),
"Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size());
static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes");
// Reinterpret cast to floats so it does a fast copy.
AZStd::copy(
reinterpret_cast<const float*>(azVector.data()),
reinterpret_cast<const float*>(azVector.data() + azVector.size()),
reinterpret_cast<float*>(nvRange.begin()));
}
// Copies a NvCloth Range of physx::PxVec4 elements as an AZ vector of AZ::Vector4 elements.
//
// It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout.
// Each one has its own memory with their appropriate alignments.
void FastCopy(const nv::cloth::Range<physx::PxVec4>& nvRange, AZStd::vector<AZ::Vector4>& azVector)
{
AZ_Assert(azVector.size() == nvRange.size(),
"Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size());
static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes");
// Reinterpret cast to floats so it does a fast copy.
AZStd::copy(
reinterpret_cast<const float*>(nvRange.begin()),
reinterpret_cast<const float*>(nvRange.end()),
reinterpret_cast<float*>(azVector.begin()));
}
// Moves an AZ vector of AZ::Vector4 elements as a NvCloth Range of physx::PxVec4 elements.
//
// It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout.
// Each one has its own memory with their appropriate alignments.
void FastMove(AZStd::vector<AZ::Vector4>&& azVector, nv::cloth::Range<physx::PxVec4>& nvRange)
{
AZ_Assert(azVector.size() == nvRange.size(),
"Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size());
static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes");
// Reinterpret cast to floats so it does a fast move.
AZStd::move(
reinterpret_cast<float*>(azVector.data()),
reinterpret_cast<float*>(azVector.data() + azVector.size()),
reinterpret_cast<float*>(nvRange.begin()));
}
// Moves a NvCloth Range of physx::PxVec4 elements as an AZ vector of AZ::Vector4 elements.
//
// It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout.
// Each one has its own memory with their appropriate alignments.
void FastMove(nv::cloth::Range<physx::PxVec4>&& nvRange, AZStd::vector<AZ::Vector4>& azVector)
{
AZ_Assert(azVector.size() == nvRange.size(),
"Mismatch in number of elements. AZ vector: %zu Nv Range: %u", azVector.size(), nvRange.size());
static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "physx::PxVec4 and AZ::Vector4 types have different sizes");
// Reinterpret cast to floats so it does a fast copy.
AZStd::move(
reinterpret_cast<float*>(nvRange.begin()),
reinterpret_cast<float*>(nvRange.end()),
reinterpret_cast<float*>(azVector.begin()));
}
}
Cloth::Cloth(
ClothId id,
const AZStd::vector<SimParticleFormat>& initialParticles,
Fabric* fabric,
NvClothUniquePtr nvCloth)
: m_id(id)
, m_nvCloth(AZStd::move(nvCloth))
, m_fabric(fabric)
, m_initialParticles(initialParticles)
, m_initialParticlesWithMassApplied(initialParticles)
{
m_simParticles = initialParticles;
// Construct the default list of phase configurations
const size_t numPhaseTypes = m_fabric->GetPhaseTypes().size();
m_nvPhaseConfigs.reserve(numPhaseTypes);
for (size_t phaseIndex = 0; phaseIndex < numPhaseTypes; phaseIndex++)
{
m_nvPhaseConfigs.emplace_back(static_cast<uint16_t>(phaseIndex));
}
ApplyPhaseConfigs();
// Set default gravity
const AZ::Vector3 gravity(0.0f, 0.0f, -9.81f);
SetGravity(gravity);
// One more cloth instance using the fabric
m_fabric->m_numClothsUsingFabric++;
}
Cloth::~Cloth()
{
// If cloth is still part of a solver, remove it
if (m_solver)
{
m_solver->RemoveCloth(this);
}
// One less cloth instance using the fabric
m_fabric->m_numClothsUsingFabric--;
}
void Cloth::Update()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
ResolveStaticParticles();
if (!RetrieveSimulationResults())
{
RestoreSimulation();
}
}
ClothId Cloth::GetId() const
{
return m_id;
}
const AZStd::vector<SimParticleFormat>& Cloth::GetInitialParticles() const
{
return m_initialParticles;
}
const AZStd::vector<SimIndexType>& Cloth::GetInitialIndices() const
{
return m_fabric->m_cookedData.m_indices;
}
const AZStd::vector<SimParticleFormat>& Cloth::GetParticles() const
{
return m_simParticles;
}
void Cloth::SetParticles(const AZStd::vector<SimParticleFormat>& particles)
{
if (m_simParticles.size() != particles.size())
{
AZ_Warning("Cloth", false, "Unable to set cloth particles as it doesn't match the number of elements. Number of particles passed %zu, expected %zu.",
particles.size(), m_simParticles.size());
return;
}
m_simParticles = particles;
CopySimParticlesToNvCloth();
}
void Cloth::SetParticles(AZStd::vector<SimParticleFormat>&& particles)
{
if (m_simParticles.size() != particles.size())
{
AZ_Warning("Cloth", false, "Unable to set cloth particles as it doesn't match the number of elements. Number of particles passed %zu, expected %zu.",
particles.size(), m_simParticles.size());
return;
}
m_simParticles = AZStd::move(particles);
CopySimParticlesToNvCloth();
}
void Cloth::DiscardParticleDelta()
{
const nv::cloth::MappedRange<const physx::PxVec4> currentParticles = nv::cloth::readCurrentParticles(*m_nvCloth);
nv::cloth::MappedRange<physx::PxVec4> previousParticles = m_nvCloth->getPreviousParticles();
// Reinterpret cast to floats so it does a fast copy.
AZStd::copy(
reinterpret_cast<const float*>(currentParticles.begin()),
reinterpret_cast<const float*>(currentParticles.end()),
reinterpret_cast<float*>(previousParticles.begin()));
}
const FabricCookedData& Cloth::GetFabricCookedData() const
{
return m_fabric->m_cookedData;
}
IClothConfigurator* Cloth::GetClothConfigurator()
{
return this;
}
void Cloth::SetTransform(const AZ::Transform& transformWorld)
{
m_nvCloth->setTranslation(Internal::AsPxVec3(transformWorld.GetTranslation()));
m_nvCloth->setRotation(Internal::AsPxQuat(transformWorld.GetRotation()));
}
void Cloth::ClearInertia()
{
m_nvCloth->clearInertia();
}
void Cloth::SetMass(float mass)
{
if (AZ::IsClose(m_mass, mass, std::numeric_limits<float>::epsilon()))
{
return;
}
m_mass = mass;
const float inverseMass = (m_mass > 0.0f) ? (1.0f / m_mass) : 0.0f;
for (size_t i = 0; i < m_simParticles.size(); ++i)
{
const float particleInvMass = m_initialParticles[i].GetW() * inverseMass;
m_simParticles[i].SetW(particleInvMass);
m_initialParticlesWithMassApplied[i].SetW(particleInvMass);
}
CopySimInverseMassesToNvCloth();
}
void Cloth::SetGravity(const AZ::Vector3& gravity)
{
m_nvCloth->setGravity(Internal::AsPxVec3(gravity));
}
void Cloth::SetStiffnessFrequency(float frequency)
{
m_nvCloth->setStiffnessFrequency(frequency);
}
void Cloth::SetDamping(const AZ::Vector3& damping)
{
m_nvCloth->setDamping(Internal::AsPxVec3(damping));
}
void Cloth::SetDampingLinearDrag(const AZ::Vector3& linearDrag)
{
m_nvCloth->setLinearDrag(Internal::AsPxVec3(linearDrag));
}
void Cloth::SetDampingAngularDrag(const AZ::Vector3& angularDrag)
{
m_nvCloth->setAngularDrag(Internal::AsPxVec3(angularDrag));
}
void Cloth::SetLinearInertia(const AZ::Vector3& linearInertia)
{
m_nvCloth->setLinearInertia(Internal::AsPxVec3(linearInertia));
}
void Cloth::SetAngularInertia(const AZ::Vector3& angularInertia)
{
m_nvCloth->setAngularInertia(Internal::AsPxVec3(angularInertia));
}
void Cloth::SetCentrifugalInertia(const AZ::Vector3& centrifugalInertia)
{
m_nvCloth->setCentrifugalInertia(Internal::AsPxVec3(centrifugalInertia));
}
void Cloth::SetWindVelocity(const AZ::Vector3& velocity)
{
m_nvCloth->setWindVelocity(Internal::AsPxVec3(velocity));
}
void Cloth::SetWindDragCoefficient(float drag)
{
const float airDragPerc = 0.97f; // To improve cloth stability
m_nvCloth->setDragCoefficient(airDragPerc * drag);
}
void Cloth::SetWindLiftCoefficient(float lift)
{
const float airLiftPerc = 0.8f; // To improve cloth stability
m_nvCloth->setLiftCoefficient(airLiftPerc * lift);
}
void Cloth::SetWindFluidDensity(float density)
{
m_nvCloth->setFluidDensity(density);
}
void Cloth::SetCollisionFriction(float friction)
{
m_nvCloth->setFriction(friction);
}
void Cloth::SetCollisionMassScale(float scale)
{
m_nvCloth->setCollisionMassScale(scale);
}
void Cloth::EnableContinuousCollision(bool value)
{
m_nvCloth->enableContinuousCollision(value);
}
void Cloth::SetCollisionAffectsStaticParticles(bool value)
{
m_collisionAffectsStaticParticles = value;
}
void Cloth::SetSelfCollisionDistance(float distance)
{
m_nvCloth->setSelfCollisionDistance(distance);
}
void Cloth::SetSelfCollisionStiffness(float stiffness)
{
m_nvCloth->setSelfCollisionStiffness(stiffness);
}
void Cloth::SetVerticalPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit)
{
SetPhaseConfig(
nv::cloth::ClothFabricPhaseType::eVERTICAL,
stiffness,
stiffnessMultiplier,
compressionLimit,
stretchLimit);
}
void Cloth::SetHorizontalPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit)
{
SetPhaseConfig(
nv::cloth::ClothFabricPhaseType::eHORIZONTAL,
stiffness,
stiffnessMultiplier,
compressionLimit,
stretchLimit);
}
void Cloth::SetBendingPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit)
{
SetPhaseConfig(
nv::cloth::ClothFabricPhaseType::eBENDING,
stiffness,
stiffnessMultiplier,
compressionLimit,
stretchLimit);
}
void Cloth::SetShearingPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit)
{
SetPhaseConfig(
nv::cloth::ClothFabricPhaseType::eSHEARING,
stiffness,
stiffnessMultiplier,
compressionLimit,
stretchLimit);
}
void Cloth::SetTetherConstraintStiffness(float stiffness)
{
m_nvCloth->setTetherConstraintStiffness(stiffness);
}
void Cloth::SetTetherConstraintScale(float scale)
{
m_nvCloth->setTetherConstraintScale(scale);
}
void Cloth::SetSolverFrequency(float frequency)
{
m_nvCloth->setSolverFrequency(frequency);
}
void Cloth::SetAcceleationFilterWidth(AZ::u32 width)
{
m_nvCloth->setAcceleationFilterWidth(width);
}
void Cloth::SetSphereColliders(const AZStd::vector<AZ::Vector4>& spheres)
{
m_nvCloth->setSpheres(
ToPxVec4NvRange(spheres),
0, m_nvCloth->getNumSpheres());
}
void Cloth::SetSphereColliders(AZStd::vector<AZ::Vector4>&& spheres)
{
SetSphereColliders(spheres); // NvCloth does not offer a move overload for setSpheres, calling the const reference one.
}
void Cloth::SetCapsuleColliders(const AZStd::vector<AZ::u32>& capsuleIndices)
{
m_nvCloth->setCapsules(
ToNvRange(capsuleIndices),
0, m_nvCloth->getNumCapsules());
}
void Cloth::SetCapsuleColliders(AZStd::vector<AZ::u32>&& capsuleIndices)
{
SetCapsuleColliders(capsuleIndices); // NvCloth does not offer a move overload for setCapsules, calling the const reference one.
}
void Cloth::SetMotionConstraints(const AZStd::vector<AZ::Vector4>& constraints)
{
if (m_simParticles.size() != constraints.size())
{
AZ_Warning("Cloth", false, "Unable to set motions constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.",
constraints.size(), m_simParticles.size());
return;
}
m_motionConstraints = constraints;
nv::cloth::Range<physx::PxVec4> motionConstraints = m_nvCloth->getMotionConstraints();
Internal::FastCopy(m_motionConstraints, motionConstraints);
}
void Cloth::SetMotionConstraints(AZStd::vector<AZ::Vector4>&& constraints)
{
if (m_simParticles.size() != constraints.size())
{
AZ_Warning("Cloth", false, "Unable to set motions constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.",
constraints.size(), m_simParticles.size());
return;
}
m_motionConstraints = AZStd::move(constraints);
nv::cloth::Range<physx::PxVec4> motionConstraints = m_nvCloth->getMotionConstraints();
Internal::FastCopy(m_motionConstraints, motionConstraints);
}
void Cloth::ClearMotionConstraints()
{
m_motionConstraints.clear();
m_nvCloth->clearMotionConstraints();
}
void Cloth::SetMotionConstraintsScale(float scale)
{
m_nvCloth->setMotionConstraintScaleBias(scale, m_nvCloth->getMotionConstraintBias());
}
void Cloth::SetMotionConstraintsBias(float bias)
{
m_nvCloth->setMotionConstraintScaleBias(m_nvCloth->getMotionConstraintScale(), bias);
}
void Cloth::SetMotionConstraintsStiffness(float stiffness)
{
m_nvCloth->setMotionConstraintStiffness(stiffness);
}
void Cloth::SetSeparationConstraints(const AZStd::vector<AZ::Vector4>& constraints)
{
if (m_simParticles.size() != constraints.size())
{
AZ_Warning("Cloth", false, "Unable to set separation constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.",
constraints.size(), m_simParticles.size());
return;
}
nv::cloth::Range<physx::PxVec4> separationConstraints = m_nvCloth->getSeparationConstraints();
Internal::FastCopy(constraints, separationConstraints);
}
void Cloth::SetSeparationConstraints(AZStd::vector<AZ::Vector4>&& constraints)
{
if (m_simParticles.size() != constraints.size())
{
AZ_Warning("Cloth", false, "Unable to set separation constraints as it doesn't match the number of particles. Numbers of constraints passed %zu, expected %zu.",
constraints.size(), m_simParticles.size());
return;
}
nv::cloth::Range<physx::PxVec4> separationConstraints = m_nvCloth->getSeparationConstraints();
Internal::FastMove(AZStd::move(constraints), separationConstraints);
}
void Cloth::ClearSeparationConstraints()
{
m_nvCloth->clearSeparationConstraints();
}
void Cloth::ResolveStaticParticles()
{
if (m_collisionAffectsStaticParticles)
{
// Nothing to do as in NvCloth colliders affect static particles.
return;
}
// During simulation static particle are always affected by colliders and motion constraints.
// To remove the effect of colliders on Static Particles we will restore their positions,
// either with the motion constraints (if existent) or the last simulated particles.
nv::cloth::MappedRange<physx::PxVec4> particles = m_nvCloth->getCurrentParticles();
const AZStd::vector<AZ::Vector4>& positions = m_motionConstraints.empty()
? m_simParticles
: m_motionConstraints;
for (AZ::u32 i = 0; i < particles.size(); ++i)
{
// Checking NvCloth current particles is important because their W component will
// have the result left by the simulation applying both inverse masses and motion constraints.
if (particles[i].w == 0.0f)
{
auto& particle = particles[i];
const auto& position = positions[i];
particle.x = position.GetX();
particle.y = position.GetY();
particle.z = position.GetZ();
}
}
}
bool Cloth::RetrieveSimulationResults()
{
const nv::cloth::MappedRange<const physx::PxVec4> particles = nv::cloth::readCurrentParticles(*m_nvCloth);
bool validCloth =
AZStd::all_of(particles.begin(), particles.end(), [](const physx::PxVec4& particle)
{
return particle.isFinite();
})
&&
// On some platforms when cloth simulation gets corrupted it puts all particles' position to (0,0,0)
AZStd::any_of(particles.begin(), particles.end(), [](const physx::PxVec4& particle)
{
return particle.x != 0.0f || particle.y != 0.0f || particle.z != 0.0f;
});
if (validCloth)
{
for (AZ::u32 i = 0; i < particles.size(); ++i)
{
m_simParticles[i].SetX(particles[i].x);
m_simParticles[i].SetY(particles[i].y);
m_simParticles[i].SetZ(particles[i].z);
// Not copying inverse masses on purpose since they could be different after running the simulation.
// This solves a problem when using a value of zero in the motion constraints distance
// or scale. All inverse masses would go to zero and since we were copying them back,
// the original data got lost and it was not able to return to a normal state after
// changing the values back to values other than zero.
}
m_numInvalidSimulations = 0; // Reset counter as the results were valid
}
return validCloth;
}
void Cloth::RestoreSimulation()
{
nv::cloth::MappedRange<physx::PxVec4> previousParticles = m_nvCloth->getPreviousParticles();
nv::cloth::MappedRange<physx::PxVec4> currentParticles = m_nvCloth->getCurrentParticles();
const AZ::u32 maxAttemptsToRestoreCloth = 15;
if (m_numInvalidSimulations <= maxAttemptsToRestoreCloth)
{
// Leave the NvCloth simulation particles in their last known good position.
Internal::FastCopy(m_simParticles, previousParticles);
Internal::FastCopy(m_simParticles, currentParticles);
}
else
{
// Reset NvCloth simulation particles to their initial position if after a number of
// attempts cloth has not been restored to a stable state.
Internal::FastCopy(m_initialParticlesWithMassApplied, previousParticles);
Internal::FastCopy(m_initialParticlesWithMassApplied, currentParticles);
}
m_nvCloth->clearInertia();
m_nvCloth->clearInterpolation();
m_numInvalidSimulations++;
}
void Cloth::CopySimParticlesToNvCloth()
{
// The positions must be copied into the current particles inside NvCloth.
// Note: Inverse masses are copied as well to do a fast copy,
// but inverse masses copied to current particles have no effect.
nv::cloth::MappedRange<physx::PxVec4> currentParticles = m_nvCloth->getCurrentParticles();
Internal::FastCopy(m_simParticles, currentParticles);
CopySimInverseMassesToNvCloth();
}
void Cloth::CopySimInverseMassesToNvCloth()
{
// The inverse masses must be copied into the previous particles inside NvCloth
// to take effect for the next simulation update.
nv::cloth::MappedRange<physx::PxVec4> previousParticles = m_nvCloth->getPreviousParticles();
for (AZ::u32 i = 0; i < previousParticles.size(); ++i)
{
previousParticles[i].w = m_simParticles[i].GetW();
}
}
void Cloth::SetPhaseConfig(
int32_t phaseType,
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit)
{
const auto& phaseTypes = m_fabric->GetPhaseTypes();
for (size_t i = 0; i < phaseTypes.size(); ++i)
{
if (phaseTypes[i] == phaseType)
{
m_nvPhaseConfigs[i].mStiffness = stiffness;
m_nvPhaseConfigs[i].mStiffnessMultiplier = 1.0f - AZ::GetClamp(stiffnessMultiplier, 0.0f, 1.0f); // Internally a value of 1 means no scale inside nvcloth.
m_nvPhaseConfigs[i].mCompressionLimit = 1.0f + compressionLimit; // A value of 1.0f is no compression inside nvcloth. From [0.0, INF] to [1.0, INF].
m_nvPhaseConfigs[i].mStretchLimit = 1.0f + stretchLimit; // A value of 1.0f is no stretch inside nvcloth. From [0.0, INF] to [1.0, INF].
}
}
ApplyPhaseConfigs();
}
void Cloth::ApplyPhaseConfigs()
{
m_nvCloth->setPhaseConfig(
ToNvRange(m_nvPhaseConfigs));
}
} // namespace NvCloth
+186
View File
@@ -0,0 +1,186 @@
/*
* 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/RTTI/RTTI.h>
#include <NvCloth/ICloth.h>
#include <NvCloth/IClothConfigurator.h>
#include <System/NvTypes.h>
// NvCloth library includes
#include <NvCloth/PhaseConfig.h>
namespace NvCloth
{
class Solver;
class Fabric;
//! Implementation of the ICloth and IClothConfigurator interfaces.
class Cloth
: public ICloth
, public IClothConfigurator
{
public:
AZ_RTTI(Cloth, "{D9DEED18-FEF2-440B-8639-A080F8C1F6DB}", ICloth);
Cloth(
ClothId id,
const AZStd::vector<SimParticleFormat>& initialParticles,
Fabric* fabric,
NvClothUniquePtr nvCloth);
~Cloth();
//! Returns the fabric used to create this cloth.
Fabric* GetFabric() { return m_fabric; }
//! Returns the solver this cloth is added to or nullptr if it's not part of any solver.
Solver* GetSolver() { return m_solver; }
//! Retrieves the latest simulation data from NvCloth and updates the particles.
void Update();
// ICloth overrides ...
ClothId GetId() const override;
const AZStd::vector<SimParticleFormat>& GetInitialParticles() const override;
const AZStd::vector<SimIndexType>& GetInitialIndices() const override;
const AZStd::vector<SimParticleFormat>& GetParticles() const override;
void SetParticles(const AZStd::vector<SimParticleFormat>& particles) override;
void SetParticles(AZStd::vector<SimParticleFormat>&& particles) override;
void DiscardParticleDelta() override;
const FabricCookedData& GetFabricCookedData() const override;
IClothConfigurator* GetClothConfigurator() override;
// IClothConfigurator overrides ...
void SetTransform(const AZ::Transform& transformWorld) override;
void ClearInertia() override;
void SetMass(float mass) override;
void SetGravity(const AZ::Vector3& gravity) override;
void SetStiffnessFrequency(float frequency) override;
void SetDamping(const AZ::Vector3& damping) override;
void SetDampingLinearDrag(const AZ::Vector3& linearDrag) override;
void SetDampingAngularDrag(const AZ::Vector3& angularDrag) override;
void SetLinearInertia(const AZ::Vector3& linearInertia) override;
void SetAngularInertia(const AZ::Vector3& angularInertia) override;
void SetCentrifugalInertia(const AZ::Vector3& centrifugalInertia) override;
void SetWindVelocity(const AZ::Vector3& velocity) override;
void SetWindDragCoefficient(float drag) override;
void SetWindLiftCoefficient(float lift) override;
void SetWindFluidDensity(float density) override;
void SetCollisionFriction(float friction) override;
void SetCollisionMassScale(float scale) override;
void EnableContinuousCollision(bool value) override;
void SetCollisionAffectsStaticParticles(bool value) override;
void SetSelfCollisionDistance(float distance) override;
void SetSelfCollisionStiffness(float stiffness) override;
void SetVerticalPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit) override;
void SetHorizontalPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit) override;
void SetBendingPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit) override;
void SetShearingPhaseConfig(
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit) override;
void SetTetherConstraintStiffness(float stiffness) override;
void SetTetherConstraintScale(float scale) override;
void SetSolverFrequency(float frequency) override;
void SetAcceleationFilterWidth(AZ::u32 width) override;
void SetSphereColliders(const AZStd::vector<AZ::Vector4>& spheres) override;
void SetSphereColliders(AZStd::vector<AZ::Vector4>&& spheres) override;
void SetCapsuleColliders(const AZStd::vector<AZ::u32>& capsuleIndices) override;
void SetCapsuleColliders(AZStd::vector<AZ::u32>&& capsuleIndices) override;
void SetMotionConstraints(const AZStd::vector<AZ::Vector4>& constraints) override;
void SetMotionConstraints(AZStd::vector<AZ::Vector4>&& constraints) override;
void ClearMotionConstraints() override;
void SetMotionConstraintsScale(float scale) override;
void SetMotionConstraintsBias(float bias) override;
void SetMotionConstraintsStiffness(float stiffness) override;
void SetSeparationConstraints(const AZStd::vector<AZ::Vector4>& constraints) override;
void SetSeparationConstraints(AZStd::vector<AZ::Vector4>&& constraints) override;
void ClearSeparationConstraints() override;
private:
void ResolveStaticParticles();
bool RetrieveSimulationResults();
void RestoreSimulation();
// Copies up current particles to NvCloth.
void CopySimParticlesToNvCloth();
// Copies up current inverse masses to NvCloth.
void CopySimInverseMassesToNvCloth();
void SetPhaseConfig(
int32_t phaseType,
float stiffness,
float stiffnessMultiplier,
float compressionLimit,
float stretchLimit);
void ApplyPhaseConfigs();
// Cloth unique identifier.
ClothId m_id;
// NvCloth cloth object.
NvClothUniquePtr m_nvCloth;
// Fabric used to create this cloth.
Fabric* m_fabric = nullptr;
// Current solver this cloth is added to.
Solver* m_solver = nullptr;
// Initial data from cloth creation.
AZStd::vector<SimParticleFormat> m_initialParticles;
AZStd::vector<SimParticleFormat> m_initialParticlesWithMassApplied; // Needed by RestoreSimulation
// Current simulation particles (positions + inverse masses).
AZStd::vector<SimParticleFormat> m_simParticles;
// Current mass value applied to all particles.
float m_mass = 1.0f;
// When true, colliders affect static particles.
bool m_collisionAffectsStaticParticles = false;
// Current phases configuration data.
AZStd::vector<nv::cloth::PhaseConfig> m_nvPhaseConfigs;
// Current motion constraints.
// Caching it to be used in ResolveStaticParticles(), having it available avoids
// having to call m_nvCloth->getMotionConstraints(), which there is no const version
// and would wake the simulation.
AZStd::vector<AZ::Vector4> m_motionConstraints;
// Number of continuous invalid simulations.
// That's when NvCloth provided invalid data when retrieving simulation results.
AZ::u32 m_numInvalidSimulations = 0;
// Solver has the responsibility of adding/removing cloths to solvers,
// so it needs exclusive access to m_solver and m_nvCloth members.
friend class Solver;
};
} // namespace NvCloth
+60
View File
@@ -0,0 +1,60 @@
/*
* 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 <NvCloth/Types.h>
#include <System/NvTypes.h>
namespace NvCloth
{
//! Fabric objects are the instances of FabricCookedData.
//! There will be only one Fabric created per FabricCookedData,
//! hold by SystemComponent and identified by FabricId.
//!
//! It has a counter of how many Cloth instances have been created using this fabric,
//! the moment the counter is zero (when the last cloth using this fabric has been destroyed)
//! th fabric will be automatically destroyed.
class Fabric
{
public:
Fabric(
const FabricCookedData& cookedData,
NvFabricUniquePtr nvFabric)
: m_id(cookedData.m_id)
, m_nvFabric(AZStd::move(nvFabric))
, m_cookedData(cookedData)
{
}
//! Returns the list of phase types (horizontal, vertical, bending or shearing)
//! created for the fabric when it was cooked.
const AZStd::vector<int32_t>& GetPhaseTypes() const
{
return m_cookedData.m_internalData.m_phaseTypes;
}
//! Fabric unique id.
//! @note It is the same id from its FabricCookedData.
FabricId m_id;
//! NvCloth fabric object.
NvFabricUniquePtr m_nvFabric;
//! Fabric cooked data used to construct this fabric.
FabricCookedData m_cookedData;
//! Counter of Cloth instances created with this fabric.
int m_numClothsUsingFabric = 0;
};
} // namespace NvCloth
@@ -0,0 +1,358 @@
/*
* 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/unique_ptr.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <System/FabricCooker.h>
// NvCloth library includes
#include <NvCloth/Range.h>
#include <NvClothExt/ClothFabricCooker.h>
namespace NvCloth
{
namespace Internal
{
FabricId ComputeFabricId(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity,
bool useGeodesicTether)
{
AZ::Crc32 upperCrc32(particles.data(), sizeof(SimParticleFormat)*particles.size());
upperCrc32.Add(&fabricGravity, sizeof(fabricGravity));
AZ::Crc32 lowerCrc32(indices.data(), sizeof(SimIndexType)*indices.size());
lowerCrc32.Add(&useGeodesicTether, sizeof(useGeodesicTether));
const AZ::u32 upper = static_cast<AZ::u32>(upperCrc32);
const AZ::u32 lower = static_cast<AZ::u32>(lowerCrc32);
const AZ::u64 id =
static_cast<AZ::u64>(lower) |
(static_cast<AZ::u64>(upper) << 32);
return FabricId(id);
}
nv::cloth::BoundedData ToNvBoundedData(const void* data, size_t stride, size_t count)
{
nv::cloth::BoundedData boundedData;
boundedData.data = data;
boundedData.stride = static_cast<physx::PxU32>(stride);
boundedData.count = static_cast<physx::PxU32>(count);
return boundedData;
}
template <typename T>
void CopyNvRange(const nv::cloth::Range<const T>& nvRange, AZStd::vector<T>& azVector)
{
azVector.resize(nvRange.size());
AZStd::copy(nvRange.begin(), nvRange.end(), azVector.begin());
}
void CopyCookedData(FabricCookedData::InternalCookedData& azCookedData, const nv::cloth::CookedData& nvCookedData)
{
azCookedData.m_numParticles = nvCookedData.mNumParticles;
// All these are fast copies
CopyNvRange(nvCookedData.mPhaseIndices, azCookedData.m_phaseIndices);
CopyNvRange(nvCookedData.mPhaseTypes, azCookedData.m_phaseTypes);
CopyNvRange(nvCookedData.mSets, azCookedData.m_sets);
CopyNvRange(nvCookedData.mRestvalues, azCookedData.m_restValues);
CopyNvRange(nvCookedData.mStiffnessValues, azCookedData.m_stiffnessValues);
CopyNvRange(nvCookedData.mIndices, azCookedData.m_indices);
CopyNvRange(nvCookedData.mAnchors, azCookedData.m_anchors);
CopyNvRange(nvCookedData.mTetherLengths, azCookedData.m_tetherLengths);
CopyNvRange(nvCookedData.mTriangles, azCookedData.m_triangles);
}
AZStd::optional<FabricCookedData> Cook(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity,
bool useGeodesicTether)
{
// Check if all the particles are static (inverse masses are all 0)
const bool fullyStaticFabric = AZStd::all_of(particles.cbegin(), particles.cend(),
[](const SimParticleFormat& particle)
{
return particle.GetW() == 0.0f;
});
const int numIndicesPerTriangle = 3;
const AZStd::vector<float> defaultInvMasses(particles.size(), 1.0f);
nv::cloth::ClothMeshDesc meshDesc;
meshDesc.setToDefault();
meshDesc.points = ToNvBoundedData(particles.data(), sizeof(SimParticleFormat), particles.size());
if (!fullyStaticFabric)
{
const int offsetToW = 3;
meshDesc.invMasses = ToNvBoundedData(reinterpret_cast<const float*>(particles.data()) + offsetToW, sizeof(SimParticleFormat), particles.size());
}
else
{
// NvCloth doesn't support cooking a fabric where all its simulation particles are static (inverse masses are all 0.0).
// In this situation we will cook the fabric with the default inverse masses (all 1.0). At runtime, inverse masses are
// provided to the cloth when created, and they will override the fabric ones. NvCloth does support the cloth instance
// to be fully static, but not the fabric.
meshDesc.invMasses = ToNvBoundedData(defaultInvMasses.data(), sizeof(float), defaultInvMasses.size());
}
meshDesc.triangles = ToNvBoundedData(indices.data(), sizeof(SimIndexType) * numIndicesPerTriangle, indices.size() / numIndicesPerTriangle);
meshDesc.flags = (sizeof(SimIndexType) == 2) ? nv::cloth::MeshFlag::e16_BIT_INDICES : 0;
AZStd::unique_ptr<nv::cloth::ClothFabricCooker> cooker(NvClothCreateFabricCooker());
if (!cooker ||
!cooker->cook(meshDesc, *reinterpret_cast<const physx::PxVec3*>(&fabricGravity), useGeodesicTether))
{
return AZStd::nullopt;
}
FabricId fabricId = ComputeFabricId(particles, indices, fabricGravity, useGeodesicTether);
if (!fabricId.IsValid())
{
return AZStd::nullopt;
}
FabricCookedData fabricData;
fabricData.m_id = fabricId;
fabricData.m_particles = particles;
fabricData.m_indices = indices;
fabricData.m_gravity = fabricGravity;
fabricData.m_useGeodesicTether = useGeodesicTether;
CopyCookedData(fabricData.m_internalData, cooker->getCookedData());
return AZStd::optional<FabricCookedData>(AZStd::move(fabricData));
}
void WeldVertices(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& weldedParticles,
AZStd::vector<SimIndexType>& weldedIndices,
AZStd::vector<int>& remappedVertices,
float weldingDistance = AZ::Constants::FloatEpsilon)
{
// Comparison functor for simulation particles based on the position.
// Inverse mass is not involved in the comparison.
struct ParticlesCompareLess
{
bool operator()(const SimParticleFormat& lhs, const SimParticleFormat& rhs) const
{
if (!AZ::IsClose(lhs.GetX(), rhs.GetX(), m_weldingDistance))
{
return lhs.GetX() < rhs.GetX();
}
else if (!AZ::IsClose(lhs.GetY(), rhs.GetY(), m_weldingDistance))
{
return lhs.GetY() < rhs.GetY();
}
else if (!AZ::IsClose(lhs.GetZ(), rhs.GetZ(), m_weldingDistance))
{
return lhs.GetZ() < rhs.GetZ();
}
return false;
}
float m_weldingDistance = AZ::Constants::FloatEpsilon;
};
using ParticleToIndicesMap = AZStd::map<SimParticleFormat, AZStd::vector<size_t>, ParticlesCompareLess>;
ParticleToIndicesMap particleToIndicesMap({ weldingDistance });
for (size_t originalIndex = 0; originalIndex < particles.size(); ++originalIndex)
{
// To weld vertices with the same position we use a map where the key is the particle itself.
// When inserting the particle to the map it will pick up the particle with the same position.
auto insertedIt = particleToIndicesMap.insert({ particles[originalIndex], {} }).first;
insertedIt->second.push_back(originalIndex);
// Keep the minimum inverse mass value when welding particles.
// It's OK to modify the W of the key element from the map because it's not involved in the comparison functor.
insertedIt->first.SetW(
AZStd::min(
insertedIt->first.GetW(),
particles[originalIndex].GetW()));
}
// Compose welded particles and remapped vertices.
int remappedIndex = 0;
const int invalidIndex = -1;
weldedParticles.resize_no_construct(particleToIndicesMap.size());
remappedVertices.resize(particles.size(), invalidIndex);
for (const auto& particleToIndicesPair : particleToIndicesMap)
{
weldedParticles[remappedIndex] = particleToIndicesPair.first;
for (const size_t& originalIndex : particleToIndicesPair.second)
{
remappedVertices[originalIndex] = remappedIndex;
}
++remappedIndex;
}
// Compose welded indices.
weldedIndices.resize_no_construct(indices.size());
for (size_t i = 0; i < indices.size(); ++i)
{
const int remappedVertexIndex = remappedVertices[indices[i]];
AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", indices[i]);
weldedIndices[i] = static_cast<SimIndexType>(remappedVertexIndex);
}
}
void RemoveStaticTriangles(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& simplifiedParticles,
AZStd::vector<SimIndexType>& simplifiedIndices,
AZStd::vector<int>& remappedVertices)
{
using ParticleIndexSet = AZStd::set<size_t>;
using TriangleIndices = AZStd::array<SimIndexType, 3>;
ParticleIndexSet particleIndexSet;
const size_t numTriangles = indices.size() / 3;
size_t simplifiedNumTriangles = 0;
auto isTriangleStatic = [&particles](const TriangleIndices& triangleIndices)
{
return (particles[triangleIndices[0]].GetW() == 0.0f)
&& (particles[triangleIndices[1]].GetW() == 0.0f)
&& (particles[triangleIndices[2]].GetW() == 0.0f);
};
// Collect all the vertices that belongs to non-static triangles
for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
{
const TriangleIndices triangleIndices =
{{
indices[triangleIndex * 3 + 0],
indices[triangleIndex * 3 + 1],
indices[triangleIndex * 3 + 2]
}};
if (isTriangleStatic(triangleIndices))
{
continue;
}
for (const auto& vertexIndex : triangleIndices)
{
particleIndexSet.insert({ vertexIndex });
}
++simplifiedNumTriangles;
}
// Compose simplified particles and remapped vertices.
int remappedIndex = 0;
const int invalidIndex = -1;
simplifiedParticles.resize_no_construct(particleIndexSet.size());
remappedVertices.resize(particles.size(), invalidIndex);
for (const auto& particleIndex : particleIndexSet)
{
simplifiedParticles[remappedIndex] = particles[particleIndex];
remappedVertices[particleIndex] = remappedIndex;
++remappedIndex;
}
// Compose simplified indices.
size_t simplifiedIndex = 0;
simplifiedIndices.resize_no_construct(simplifiedNumTriangles * 3);
for (size_t triangleIndex = 0; triangleIndex < numTriangles; ++triangleIndex)
{
const TriangleIndices triangleIndices =
{{
indices[triangleIndex * 3 + 0],
indices[triangleIndex * 3 + 1],
indices[triangleIndex * 3 + 2]
}};
if (isTriangleStatic(triangleIndices))
{
continue;
}
for (const auto& vertexIndex : triangleIndices)
{
const int remappedVertexIndex = remappedVertices[vertexIndex];
AZ_Assert(remappedVertexIndex >= 0, "Vertex Index %u has an invalid remapping", vertexIndex);
simplifiedIndices[simplifiedIndex++] = static_cast<SimIndexType>(remappedVertexIndex);
}
}
AZ_Assert(simplifiedIndex == simplifiedIndices.size(),
"Number of indices after removing static particles is %zu, but it's expected %zu.", simplifiedIndex, simplifiedIndices.size());
}
}
AZStd::optional<FabricCookedData> FabricCooker::CookFabric(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity,
bool useGeodesicTether)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
return Internal::Cook(particles, indices, fabricGravity, useGeodesicTether);
}
void FabricCooker::SimplifyMesh(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& simplifiedParticles,
AZStd::vector<SimIndexType>& simplifiedIndices,
AZStd::vector<int>& remappedVertices,
bool removeStaticTriangles)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
// Weld vertices together
AZStd::vector<SimParticleFormat> weldedParticles;
AZStd::vector<SimIndexType> weldedIndices;
AZStd::vector<int> weldedRemappedVertices;
Internal::WeldVertices(
particles, indices,
weldedParticles, weldedIndices,
weldedRemappedVertices);
if (!removeStaticTriangles)
{
simplifiedParticles = AZStd::move(weldedParticles);
simplifiedIndices = AZStd::move(weldedIndices);
remappedVertices = AZStd::move(weldedRemappedVertices);
return;
}
// Remove static particles
AZStd::vector<int> simplifiedRemappedVertices;
Internal::RemoveStaticTriangles(
weldedParticles, weldedIndices,
simplifiedParticles, simplifiedIndices,
simplifiedRemappedVertices);
// Compose final remapped vertices
remappedVertices.resize_no_construct(particles.size());
for (size_t i = 0; i < particles.size(); ++i)
{
const int weldedRemappedIndex = weldedRemappedVertices[i];
remappedVertices[i] = simplifiedRemappedVertices[weldedRemappedIndex];
}
}
} // namespace NvCloth
@@ -0,0 +1,44 @@
/*
* 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/RTTI/RTTI.h>
#include <AzCore/Interface/Interface.h>
#include <NvCloth/IFabricCooker.h>
namespace NvCloth
{
//! Implementation of the IFabricCooker interface.
class FabricCooker
: public AZ::Interface<IFabricCooker>::Registrar
{
public:
AZ_RTTI(FabricCooker, "{14EC2D3E-A36C-466E-BBDB-462A9194586E}", IFabricCooker);
protected:
// IFabricCooker overrides ...
AZStd::optional<FabricCookedData> CookFabric(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
const AZ::Vector3& fabricGravity = AZ::Vector3(0.0f, 0.0f, -9.81f),
bool useGeodesicTether = true) override;
void SimplifyMesh(
const AZStd::vector<SimParticleFormat>& particles,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<SimParticleFormat>& simplifiedParticles,
AZStd::vector<SimIndexType>& simplifiedIndices,
AZStd::vector<int>& remappedVertices,
bool removeStaticTriangles = true) override;
};
} // namespace NvCloth
+145
View File
@@ -0,0 +1,145 @@
/*
* 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 <System/Factory.h>
#include <System/Solver.h>
#include <System/Fabric.h>
#include <System/Cloth.h>
#include <System/SystemComponent.h>
// NvCloth library includes
#include <NvCloth/Range.h>
#include <NvCloth/Factory.h>
namespace NvCloth
{
namespace
{
AZ::u64 ClothIdCounter = 1;
}
void Factory::Init()
{
// Create a CPU NvCloth Factory
if (!m_nvFactory)
{
m_nvFactory = NvFactoryUniquePtr(NvClothCreateFactoryCPU());
AZ_Assert(m_nvFactory, "Failed to create CPU cloth factory");
if (SystemComponent::CheckLastClothError())
{
AZ_Printf("Cloth", "NVIDIA NvCloth Gem using CPU for cloth simulation.\n");
}
else
{
AZ_Error("Cloth", false, "NvCloth library failed to create CPU factory.");
}
}
}
void Factory::Destroy()
{
m_nvFactory.reset();
}
AZStd::unique_ptr<Solver> Factory::CreateSolver(const AZStd::string& name)
{
if (name.empty())
{
AZ_Warning("NvCloth", false, "Factory failed to create solver because name passed is empty.");
return nullptr;
}
NvSolverUniquePtr nvSolver(
m_nvFactory->createSolver());
if (!nvSolver)
{
AZ_Warning("NvCloth", false, "Factory failed to create solver %s.", name.c_str());
return nullptr;
}
return AZStd::make_unique<Solver>(
name,
AZStd::move(nvSolver));
}
AZStd::unique_ptr<Fabric> Factory::CreateFabric(const FabricCookedData& fabricCookedData)
{
if (!fabricCookedData.m_id.IsValid())
{
AZ_Warning("NvCloth", false, "Factory failed to create fabric because the id of the fabric cooked data passed is not valid.");
return nullptr;
}
NvFabricUniquePtr nvFabric(
m_nvFactory->createFabric(
fabricCookedData.m_internalData.m_numParticles,
ToNvRange(fabricCookedData.m_internalData.m_phaseIndices),
ToNvRange(fabricCookedData.m_internalData.m_sets),
ToNvRange(fabricCookedData.m_internalData.m_restValues),
ToNvRange(fabricCookedData.m_internalData.m_stiffnessValues),
ToNvRange(fabricCookedData.m_internalData.m_indices),
ToNvRange(fabricCookedData.m_internalData.m_anchors),
ToNvRange(fabricCookedData.m_internalData.m_tetherLengths),
ToNvRange(fabricCookedData.m_internalData.m_triangles)));
if (!nvFabric)
{
AZ_Warning("NvCloth", false, "Factory failed to create fabric.");
return nullptr;
}
return AZStd::make_unique<Fabric>(
fabricCookedData,
AZStd::move(nvFabric));
}
AZStd::unique_ptr<Cloth> Factory::CreateCloth(
const AZStd::vector<SimParticleFormat>& initialParticles,
Fabric* fabric)
{
if (initialParticles.empty())
{
AZ_Warning("NvCloth", false, "Factory failed to create cloth because no particles were provided.");
return nullptr;
}
if (!fabric)
{
AZ_Warning("NvCloth", false, "Factory failed to create cloth because fabric provided is invalid.");
return nullptr;
}
if (initialParticles.size() != fabric->m_cookedData.m_particles.size())
{
AZ_Warning("NvCloth", false, "Factory failed to create cloth because the number of initial particles provided (%d) didn't match the fabric's (%d).",
initialParticles.size(), fabric->m_cookedData.m_particles.size());
return nullptr;
}
NvClothUniquePtr nvCloth(
m_nvFactory->createCloth(
ToPxVec4NvRange(initialParticles),
*fabric->m_nvFabric.get()));
if (!nvCloth)
{
AZ_Warning("NvCloth", false, "Factory failed to create cloth.");
return nullptr;
}
return AZStd::make_unique<Cloth>(
ClothId(ClothIdCounter++),
initialParticles,
fabric,
AZStd::move(nvCloth));
}
} // namespace NvCloth
+53
View File
@@ -0,0 +1,53 @@
/*
* 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/RTTI/RTTI.h>
#include <NvCloth/Types.h>
#include <System/NvTypes.h>
namespace NvCloth
{
class Solver;
class Fabric;
class Cloth;
//! This class knows how to construct Solver, Cloth and Fabric objects.
//!
//! All objects constructed by this factory will run on CPU.
class Factory
{
public:
AZ_RTTI(Factory, "{ABA9A937-2FE2-44A3-A143-E1594B479BE6}");
virtual ~Factory() = default;
virtual void Init();
virtual void Destroy();
AZStd::unique_ptr<Solver> CreateSolver(const AZStd::string& name);
AZStd::unique_ptr<Fabric> CreateFabric(const FabricCookedData& fabricCookedData);
AZStd::unique_ptr<Cloth> CreateCloth(
const AZStd::vector<SimParticleFormat>& initialParticles,
Fabric* fabric);
protected:
//! NvCloth factory object.
NvFactoryUniquePtr m_nvFactory;
};
} // namespace NvCloth
@@ -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.
*
*/
#include <System/NvTypes.h>
// NvCloth library includes
#include <NvCloth/Factory.h>
#include <NvCloth/Solver.h>
#include <NvCloth/Fabric.h>
#include <NvCloth/Cloth.h>
namespace NvCloth
{
void NvClothTypesDeleter::operator()(nv::cloth::Factory* factory) const
{
NvClothDestroyFactory(factory);
}
void NvClothTypesDeleter::operator()(nv::cloth::Solver* solver) const
{
// Any cloth instance remaining in the solver must be removed before deleting it.
while (solver->getNumCloths() > 0)
{
solver->removeCloth(*solver->getClothList());
}
NV_CLOTH_DELETE(solver);
}
void NvClothTypesDeleter::operator()(nv::cloth::Fabric* fabric) const
{
fabric->decRefCount();
}
void NvClothTypesDeleter::operator()(nv::cloth::Cloth* cloth) const
{
NV_CLOTH_DELETE(cloth);
}
} // namespace NvCloth
+74
View File
@@ -0,0 +1,74 @@
/*
* 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/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Math/Vector4.h>
// NvCloth library includes
#include <NvCloth/Range.h>
#include <foundation/PxVec4.h>
namespace nv
{
namespace cloth
{
class Factory;
class Solver;
class Fabric;
class Cloth;
}
}
namespace NvCloth
{
//! Defines deleters for NvCloth types to destroy them appropriately,
//! allowing to handle them with unique pointers.
struct NvClothTypesDeleter
{
void operator()(nv::cloth::Factory* factory) const;
void operator()(nv::cloth::Solver* solver) const;
void operator()(nv::cloth::Fabric* fabric) const;
void operator()(nv::cloth::Cloth* cloth) const;
};
using NvFactoryUniquePtr = AZStd::unique_ptr<nv::cloth::Factory, NvClothTypesDeleter>;
using NvSolverUniquePtr = AZStd::unique_ptr<nv::cloth::Solver, NvClothTypesDeleter>;
using NvFabricUniquePtr = AZStd::unique_ptr<nv::cloth::Fabric, NvClothTypesDeleter>;
using NvClothUniquePtr = AZStd::unique_ptr<nv::cloth::Cloth, NvClothTypesDeleter>;
//! Returns an AZ vector as a NvCloth Range, which points to vector's memory.
template <typename T>
inline nv::cloth::Range<const T> ToNvRange(const AZStd::vector<T>& azVector)
{
return nv::cloth::Range<const T>(
azVector.data(),
azVector.data() + azVector.size());
}
//! Returns an AZ vector of AZ::Vector4 elements as a NvCloth Range of physx::PxVec4 elements.
//! The memory on the NvCloth Range points to the AZ vector's memory.
//!
//! It's safe to reinterpret AZ::Vector4 as physx::PxVec4 because they have the same memory layout
//! and AZ::Vector4 has more memory alignment restrictions than physx::PxVec4.
//! The opposite operation would NOT be safe.
inline nv::cloth::Range<const physx::PxVec4> ToPxVec4NvRange(const AZStd::vector<AZ::Vector4>& azVector)
{
static_assert(sizeof(physx::PxVec4) == sizeof(AZ::Vector4), "Incompatible types");
return nv::cloth::Range<const physx::PxVec4>(
reinterpret_cast<const physx::PxVec4*>(azVector.data()),
reinterpret_cast<const physx::PxVec4*>(azVector.data() + azVector.size()));
}
} // namespace NvCloth
+288
View File
@@ -0,0 +1,288 @@
/*
* 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 <System/Solver.h>
#include <System/Cloth.h>
#include <AzCore/Jobs/JobFunction.h>
// NvCloth library includes
#include <NvCloth/Solver.h>
namespace NvCloth
{
Solver::Solver(const AZStd::string& name, NvSolverUniquePtr nvSolver)
: m_name(name)
, m_nvSolver(AZStd::move(nvSolver))
{
}
Solver::~Solver()
{
AZ_Assert(!m_isSimulating, "Please make sure the ongoing simulation is finished");
// Remove any remaining cloths from the solver
while (!m_cloths.empty())
{
RemoveClothInternal(m_cloths.begin());
}
}
void Solver::AddCloth(Cloth* cloth)
{
AZ_Assert(!m_isSimulating, "Please make sure the ongoing simulation is finished before attempting to add cloth");
// If the cloth was already added to a solver then remove it first.
if (Solver* previousSolver = cloth->GetSolver())
{
// If it's already added to this solver then don't do anything.
if (previousSolver->GetName() == GetName())
{
return;
}
previousSolver->RemoveCloth(cloth);
}
m_cloths.push_back(cloth);
cloth->m_solver = this;
m_nvSolver->addCloth(cloth->m_nvCloth.get());
}
void Solver::RemoveCloth(Cloth* cloth)
{
AZ_Assert(!m_isSimulating, "Please make sure the ongoing simulation is finished before attempting to remove cloth");
if (cloth->GetSolver() != nullptr &&
cloth->GetSolver()->GetName() == GetName())
{
auto clothIt = AZStd::find(m_cloths.begin(), m_cloths.end(), cloth);
AZ_Assert(clothIt != m_cloths.end(), "Cloth indicates it is part of solver %s, but the solver doesn't contain it.", GetName().c_str());
RemoveClothInternal(clothIt);
}
}
size_t Solver::GetNumCloths() const
{
return m_cloths.size();
}
const AZStd::string& Solver::GetName() const
{
return m_name;
}
void Solver::Enable(bool value)
{
m_enabled = value;
}
bool Solver::IsEnabled() const
{
return m_enabled;
}
void Solver::SetUserSimulated(bool value)
{
m_userSimulated = value;
}
bool Solver::IsUserSimulated() const
{
return m_userSimulated;
}
void Solver::StartSimulation(float deltaTime)
{
if (!IsEnabled() || m_cloths.empty())
{
return;
}
AZ_Assert(!m_isSimulating, "Please make sure the ongoing simulation is finished before attempting to start a new one");
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
m_deltaTime = deltaTime;
m_simulationCompletion.Reset(true /*isClearDependent*/);
m_preSimulationEvent.Signal(m_name, deltaTime);
// Set isSimulating flag after the pre-simulation event is sent in case if there are handlers adding/removing cloth from the solver.
m_isSimulating = true;
// Setup the chain of jobs for the simulation pass
// Post simulation jobs will unlock the entire simulation pass completion.
ClothsPostSimulationJob* clothsPostSimulationJob = aznew ClothsPostSimulationJob(&m_cloths, m_deltaTime, &m_simulationCompletion);
clothsPostSimulationJob->SetDependent(&m_simulationCompletion);
// Simulation jobs will unlock the post simulation job.
ClothsSimulationJob* clothsSimulationJob = aznew ClothsSimulationJob(m_nvSolver.get(), m_deltaTime, clothsPostSimulationJob);
clothsSimulationJob->SetDependent(clothsPostSimulationJob);
// Pre-simulation jobs will unlock the simulation job.
ClothsPreSimulationJob* clothsPreSimulationJob = aznew ClothsPreSimulationJob(&m_cloths, m_deltaTime, clothsSimulationJob);
clothsPreSimulationJob->SetDependent(clothsSimulationJob);
// Start the jobs.
clothsPreSimulationJob->Start();
clothsSimulationJob->Start();
clothsPostSimulationJob->Start();
}
void Solver::FinishSimulation()
{
if (!m_isSimulating)
{
return;
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
// Waiting for the simulation pass completition.
m_simulationCompletion.StartAndWaitForCompletion();
m_isSimulating = false;
m_postSimulationEvent.Signal(m_name, m_deltaTime);
}
void Solver::SetInterCollisionDistance(float distance)
{
m_nvSolver->setInterCollisionDistance(distance);
}
void Solver::SetInterCollisionStiffness(float stiffness)
{
m_nvSolver->setInterCollisionStiffness(stiffness);
}
void Solver::SetInterCollisionIterations(AZ::u32 iterations)
{
m_nvSolver->setInterCollisionNbIterations(iterations);
}
// Note: Requires a valid cloth iterator that does not point to end()
void Solver::RemoveClothInternal(Cloths::iterator clothIt)
{
m_nvSolver->removeCloth((*clothIt)->m_nvCloth.get());
(*clothIt)->m_solver = nullptr;
m_cloths.erase(clothIt);
}
Solver::ClothsSimulationJob::ClothsSimulationJob(nv::cloth::Solver* solver, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context) : Job(true /*isAutoDelete*/, context)
, m_solver(solver)
, m_continuationJob(continuationJob)
, m_deltaTime(deltaTime)
{
}
void Solver::ClothsSimulationJob::Process()
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Cloth, "NvCloth::BeginSimulationJob");
if (m_solver->beginSimulation(m_deltaTime))
{
// Setup the end simulation job.
AZ::Job* endSimulationJob = AZ::CreateJobFunction([solver = m_solver]
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Cloth, "NvCloth::EndSimulationJob");
solver->endSimulation();
}, true /*isAutoDelete*/);
// Setup chunk simulation jobs.
const int simulationChunkCount = m_solver->getSimulationChunkCount();
for (int chunkIndex = 0; chunkIndex < simulationChunkCount; ++chunkIndex)
{
AZ::Job* chunkSimulationJob = AZ::CreateJobFunction([solver = m_solver, chunkIndex]
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Cloth, "NvCloth::ChunkSimulationJob");
solver->simulateChunk(chunkIndex);
}, true /*isAutoDelete*/);
// Setup job dependency to make sure the End simulation job runs _after_ all chunks are finished simulating
chunkSimulationJob->SetDependent(endSimulationJob);
chunkSimulationJob->Start();
}
// After the end simulation job is done, the next job in the chain is allowed to run
endSimulationJob->SetDependentStarted(m_continuationJob);
endSimulationJob->Start();
}
// Note that if beginSimulation returns false, we don't block the continuation job from running.
// This is expected behavior.
}
Solver::ClothsPostSimulationJob::ClothsPostSimulationJob(const Cloths* cloths, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context) : Job(true /*isAutoDelete*/, context)
, m_cloths(cloths)
, m_continuationJob(continuationJob)
, m_deltaTime(deltaTime)
{
}
void Solver::ClothsPostSimulationJob::Process()
{
for (Cloth* cloth : *m_cloths)
{
AZ::Job* eventSignalJob = AZ::CreateJobFunction([cloth, deltaTime = m_deltaTime]
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Cloth, "NvCloth::PostSimulationJob");
// Update the cloth data after the simulation
cloth->Update();
// Issue post-simulation events
cloth->m_postSimulationEvent.Signal(cloth->GetId(), deltaTime, cloth->GetParticles());
}, true /*isAutoDelete*/);
eventSignalJob->SetDependentStarted(m_continuationJob);
eventSignalJob->Start();
}
}
Solver::ClothsPreSimulationJob::ClothsPreSimulationJob(const Cloths* cloths, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context) : Job(true /*isAutoDelete*/, context)
, m_cloths(cloths)
, m_continuationJob(continuationJob)
, m_deltaTime(deltaTime)
{
}
void Solver::ClothsPreSimulationJob::Process()
{
for (Cloth* cloth : *m_cloths)
{
AZ::Job* eventSignalJob = AZ::CreateJobFunction([cloth, deltaTime = m_deltaTime]
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Cloth, "NvCloth::PreSimulationJob");
// Issue pre-simulation events
cloth->m_preSimulationEvent.Signal(cloth->GetId(), deltaTime);
}, true /*isAutoDelete*/);
eventSignalJob->SetDependentStarted(m_continuationJob);
eventSignalJob->Start();
}
}
} // namespace NvCloth
+153
View File
@@ -0,0 +1,153 @@
/*
* 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/Jobs/Job.h>
#include <AzCore/Jobs/JobCompletion.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/unordered_set.h>
#include <NvCloth/ISolver.h>
#include <System/NvTypes.h>
namespace NvCloth
{
class Cloth;
//! Implementation of the ISolver interface.
//!
//! When enabled, it runs the simulation on all its cloths and sends
//! notifications before and after the simulation has been executed.
class Solver
: public ISolver
{
public:
AZ_RTTI(Solver, "{111055FC-F590-4BCD-A7B9-D96B1C44E3E8}", ISolver);
Solver(const AZStd::string& name, NvSolverUniquePtr nvSolver);
~Solver();
void AddCloth(Cloth* cloth);
void RemoveCloth(Cloth* cloth);
size_t GetNumCloths() const;
// ISolver overrides ...
const AZStd::string& GetName() const override;
void Enable(bool value) override;
bool IsEnabled() const override;
void SetUserSimulated(bool value) override;
bool IsUserSimulated() const override;
void StartSimulation(float deltaTime) override;
void FinishSimulation() override;
void SetInterCollisionDistance(float distance) override;
void SetInterCollisionStiffness(float stiffness) override;
void SetInterCollisionIterations(AZ::u32 iterations) override;
private:
using Cloths = AZStd::vector<Cloth*>;
class ClothsPreSimulationJob
: public AZ::Job
{
public:
AZ_CLASS_ALLOCATOR(ClothsPreSimulationJob, AZ::ThreadPoolAllocator, 0);
ClothsPreSimulationJob(const Cloths* cloths, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context = nullptr);
void Process() override;
private:
// List of cloths to do the pre-simulation work for.
const Cloths* m_cloths = nullptr;
// The job to run after all pre-simulation jobs are completed.
AZ::Job* m_continuationJob = nullptr;
// Delta time for the current simulation pass.
float m_deltaTime = 0.0f;
};
class ClothsSimulationJob
: public AZ::Job
{
public:
AZ_CLASS_ALLOCATOR(Solver::ClothsSimulationJob, AZ::ThreadPoolAllocator, 0)
ClothsSimulationJob(nv::cloth::Solver* solver, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context = nullptr);
void Process() override;
private:
// NvCloth solver object to simulate.
nv::cloth::Solver* m_solver = nullptr;
// The job to run after all simulation jobs are completed.
AZ::Job* m_continuationJob = nullptr;
// Delta time for the current simulation pass.
float m_deltaTime = 0.0f;
};
class ClothsPostSimulationJob
: public AZ::Job
{
public:
AZ_CLASS_ALLOCATOR(ClothsPostSimulationJob, AZ::ThreadPoolAllocator, 0);
ClothsPostSimulationJob(const Cloths* cloths, float deltaTime,
AZ::Job* continuationJob, AZ::JobContext* context = nullptr);
void Process() override;
private:
// List of cloths to do the post-simulation work for.
const Cloths* m_cloths = nullptr;
// The job to run after all post-simulation jobs are completed.
AZ::Job* m_continuationJob = nullptr;
// Delta time for the current simulation pass.
float m_deltaTime = 0.0f;
};
void RemoveClothInternal(Cloths::iterator clothIt);
// Name of the solver.
AZStd::string m_name;
// NvCloth solver object.
NvSolverUniquePtr m_nvSolver;
// When enabled the solver will be simulated and its events signaled.
bool m_enabled = true;
// When user-simulated the user will have the responsibility of calling Simulate function.
bool m_userSimulated = false;
// List of Cloth instances added to this solver.
Cloths m_cloths;
// Stored delta time during the simulation.
float m_deltaTime = 0.0f;
// Flag indicating if the simulation jobs are currently running.
bool m_isSimulating = false;
// Simulation synchronization job
AZ::JobCompletion m_simulationCompletion;
};
} // namespace NvCloth
@@ -0,0 +1,463 @@
/*
* 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 <ISystem.h>
#include <IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <System/SystemComponent.h>
#include <Utils/Allocators.h>
// NvCloth library includes
#include <foundation/PxAllocatorCallback.h>
#include <foundation/PxErrorCallback.h>
#include <NvCloth/Callbacks.h>
#include <NvCloth/Solver.h>
namespace NvCloth
{
namespace
{
// Implementation of the memory allocation callback interface using nvcloth allocator.
class AzClothAllocatorCallback
: public physx::PxAllocatorCallback
{
// NvCloth requires 16-byte alignment
static const size_t alignment = 16;
void* allocate(size_t size, [[maybe_unused]] const char* typeName, const char* filename, int line) override
{
void* ptr = AZ::AllocatorInstance<AzClothAllocator>::Get().Allocate(size, alignment, 0, "NvCloth", filename, line);
AZ_Assert((reinterpret_cast<size_t>(ptr) & (alignment-1)) == 0, "NvCloth requires %zu-byte aligned memory allocations.", alignment);
return ptr;
}
void deallocate(void* ptr) override
{
AZ::AllocatorInstance<AzClothAllocator>::Get().DeAllocate(ptr);
}
};
// Implementation of the error callback interface directing nvcloth library errors to Lumberyard error output.
class AzClothErrorCallback
: public physx::PxErrorCallback
{
public:
void reportError(physx::PxErrorCode::Enum code, [[maybe_unused]] const char* message, [[maybe_unused]] const char* file, [[maybe_unused]] int line) override
{
switch (code)
{
case physx::PxErrorCode::eDEBUG_INFO:
case physx::PxErrorCode::eNO_ERROR:
AZ_TracePrintf("NvCloth", "PxErrorCode %i: %s (line %i in %s)", code, message, line, file);
break;
case physx::PxErrorCode::eDEBUG_WARNING:
case physx::PxErrorCode::ePERF_WARNING:
AZ_Warning("NvCloth", false, "PxErrorCode %i: %s (line %i in %s)", code, message, line, file);
break;
default:
AZ_Error("NvCloth", false, "PxErrorCode %i: %s (line %i in %s)", code, message, line, file);
m_lastError = code;
break;
}
}
physx::PxErrorCode::Enum GetLastError() const
{
return m_lastError;
}
void ResetLastError()
{
m_lastError = physx::PxErrorCode::eNO_ERROR;
}
private:
physx::PxErrorCode::Enum m_lastError = physx::PxErrorCode::eNO_ERROR;
};
// Implementation of the assert handler interface directing nvcloth asserts to Lumberyard assertion system.
class AzClothAssertHandler
: public nv::cloth::PxAssertHandler
{
public:
void operator()([[maybe_unused]] const char* exp, [[maybe_unused]] const char* file, [[maybe_unused]] int line, bool& ignore) override
{
AZ_UNUSED(ignore);
AZ_Assert(false, "NvCloth library assertion failed in file %s:%d: %s", file, line, exp);
}
};
// Implementation of the profiler callback interface for NvCloth.
class AzClothProfilerCallback
: public physx::PxProfilerCallback
{
public:
void* zoneStart(const char* eventName, bool detached,
[[maybe_unused]] uint64_t contextId) override
{
if (detached)
{
AZ_PROFILE_INTERVAL_START(AZ::Debug::ProfileCategory::Cloth, AZ::Crc32(eventName), eventName);
}
else
{
AZ_PROFILE_EVENT_BEGIN(AZ::Debug::ProfileCategory::Cloth, eventName);
}
return nullptr;
}
void zoneEnd([[maybe_unused]] void* profilerData,
const char* eventName, bool detached,
[[maybe_unused]] uint64_t contextId) override
{
if (detached)
{
AZ_PROFILE_INTERVAL_END(AZ::Debug::ProfileCategory::Cloth, AZ::Crc32(eventName));
}
else
{
AZ_PROFILE_EVENT_END(AZ::Debug::ProfileCategory::Cloth);
}
}
};
AZStd::unique_ptr<AzClothAllocatorCallback> ClothAllocatorCallback;
AZStd::unique_ptr<AzClothErrorCallback> ClothErrorCallback;
AZStd::unique_ptr<AzClothAssertHandler> ClothAssertHandler;
AZStd::unique_ptr<AzClothProfilerCallback> ClothProfilerCallback;
}
void SystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<SystemComponent, AZ::Component>()
->Version(0);
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<SystemComponent>("NvCloth", "Provides functionality for simulating cloth using NvCloth")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
void SystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NvClothService"));
}
void SystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NvClothService"));
}
void SystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
}
void SystemComponent::InitializeNvClothLibrary()
{
AZ::AllocatorInstance<AzClothAllocator>::Create();
ClothAllocatorCallback = AZStd::make_unique<AzClothAllocatorCallback>();
ClothErrorCallback = AZStd::make_unique<AzClothErrorCallback>();
ClothAssertHandler = AZStd::make_unique<AzClothAssertHandler>();
ClothProfilerCallback = AZStd::make_unique<AzClothProfilerCallback>();
nv::cloth::InitializeNvCloth(
ClothAllocatorCallback.get(),
ClothErrorCallback.get(),
ClothAssertHandler.get(),
ClothProfilerCallback.get());
AZ_Assert(CheckLastClothError(), "Failed to initialize NvCloth library");
}
void SystemComponent::TearDownNvClothLibrary()
{
// NvCloth library doesn't need any destruction
ClothProfilerCallback.reset();
ClothAssertHandler.reset();
ClothErrorCallback.reset();
ClothAllocatorCallback.reset();
AZ::AllocatorInstance<AzClothAllocator>::Destroy();
}
bool SystemComponent::CheckLastClothError()
{
if (ClothErrorCallback)
{
return ClothErrorCallback->GetLastError() == physx::PxErrorCode::eNO_ERROR;
}
return false;
}
void SystemComponent::ResetLastClothError()
{
if (ClothErrorCallback)
{
return ClothErrorCallback->ResetLastError();
}
}
void SystemComponent::Activate()
{
InitializeSystem();
}
void SystemComponent::Deactivate()
{
DestroySystem();
}
ISolver* SystemComponent::FindOrCreateSolver(const AZStd::string& name)
{
if (ISolver* solver = GetSolver(name))
{
return solver;
}
if (AZStd::unique_ptr<Solver> newSolver = m_factory->CreateSolver(name))
{
m_solvers.push_back(AZStd::move(newSolver));
return m_solvers.back().get();
}
return nullptr;
}
void SystemComponent::DestroySolver(ISolver*& solver)
{
if (solver)
{
const AZStd::string& solverName = solver->GetName();
auto solverIt = AZStd::find_if(m_solvers.begin(), m_solvers.end(),
[&solverName](const auto& solverInstance)
{
return solverInstance->GetName() == solverName;
});
if (solverIt != m_solvers.end())
{
// The solver will remove all its remaining cloths from it when destroyed
m_solvers.erase(solverIt);
solver = nullptr;
}
}
}
ISolver* SystemComponent::GetSolver(const AZStd::string& name)
{
auto solverIt = AZStd::find_if(m_solvers.begin(), m_solvers.end(),
[&name](const auto& solverInstance)
{
return solverInstance->GetName() == name;
});
if (solverIt != m_solvers.end())
{
return solverIt->get();
}
return nullptr;
}
FabricId SystemComponent::FindOrCreateFabric(const FabricCookedData& fabricCookedData)
{
if (m_fabrics.count(fabricCookedData.m_id) != 0)
{
return fabricCookedData.m_id;
}
if (AZStd::unique_ptr<Fabric> newFabric = m_factory->CreateFabric(fabricCookedData))
{
m_fabrics[fabricCookedData.m_id] = AZStd::move(newFabric);
return fabricCookedData.m_id;
}
return {}; // Returns invalid fabric id
}
void SystemComponent::DestroyFabric(FabricId fabricId)
{
if (auto fabricIt = m_fabrics.find(fabricId);
fabricIt != m_fabrics.end())
{
// Destroy the fabric only if not used by any cloth
if (fabricIt->second->m_numClothsUsingFabric <= 0)
{
m_fabrics.erase(fabricIt);
}
}
}
ICloth* SystemComponent::CreateCloth(
const AZStd::vector<SimParticleFormat>& initialParticles,
const FabricCookedData& fabricCookedData)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
FabricId fabricId = FindOrCreateFabric(fabricCookedData);
if (!fabricId.IsValid())
{
AZ_Warning("NvCloth", false, "Failed to create cloth because it couldn't create the fabric.");
return nullptr;
}
if (auto newCloth = m_factory->CreateCloth(initialParticles, m_fabrics[fabricId].get()))
{
ClothId newClothId = newCloth->GetId();
auto newClothIt = m_cloths.insert({ newClothId, AZStd::move(newCloth) }).first;
return newClothIt->second.get();
}
else
{
DestroyFabric(fabricId);
}
return nullptr;
}
void SystemComponent::DestroyCloth(ICloth*& cloth)
{
if (cloth)
{
FabricId fabricId = cloth->GetFabricCookedData().m_id;
// Cloth will decrement its fabric's counter on destruction.
// In addition, if the cloth still remains added into a solver, it will remove itself from it.
m_cloths.erase(cloth->GetId());
cloth = nullptr;
DestroyFabric(fabricId);
}
}
ICloth* SystemComponent::GetCloth(ClothId clothId)
{
if (auto clothIt = m_cloths.find(clothId);
clothIt != m_cloths.end())
{
return clothIt->second.get();
}
else
{
return nullptr;
}
}
bool SystemComponent::AddCloth(ICloth* cloth, const AZStd::string& solverName)
{
if (cloth)
{
ISolver* solver = GetSolver(solverName);
if (!solver)
{
return false;
}
Cloth* clothInstance = azdynamic_cast<Cloth*>(cloth);
AZ_Assert(clothInstance, "Dynamic casting from ICloth to Cloth failed.");
Solver* solverInstance = azdynamic_cast<Solver*>(solver);
AZ_Assert(solverInstance, "Dynamic casting from ISolver to Solver failed.");
solverInstance->AddCloth(clothInstance);
return true;
}
return false;
}
void SystemComponent::RemoveCloth(ICloth* cloth)
{
if (cloth)
{
Cloth* clothInstance = azdynamic_cast<Cloth*>(cloth);
AZ_Assert(clothInstance, "Dynamic casting from ICloth to Cloth failed.");
Solver* solverInstance = clothInstance->GetSolver();
if (solverInstance)
{
solverInstance->RemoveCloth(clothInstance);
}
}
}
void SystemComponent::OnTick(
float deltaTime,
[[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
for (auto& solverIt : m_solvers)
{
if (!solverIt->IsUserSimulated())
{
solverIt->StartSimulation(deltaTime);
solverIt->FinishSimulation();
}
}
}
int SystemComponent::GetTickOrder()
{
return AZ::TICK_PHYSICS;
}
void SystemComponent::InitializeSystem()
{
// Create Factory
m_factory = AZStd::make_unique<Factory>();
m_factory->Init();
// Create Default Solver
ISolver* solver = FindOrCreateSolver(DefaultSolverName);
AZ_Assert(solver, "Error: Default solver failed to be created");
AZ::Interface<IClothSystem>::Register(this);
AZ::TickBus::Handler::BusConnect();
}
void SystemComponent::DestroySystem()
{
AZ::TickBus::Handler::BusDisconnect();
AZ::Interface<IClothSystem>::Unregister(this);
// Destroy Cloths
m_cloths.clear();
// Destroy Fabrics
m_fabrics.clear();
// Destroy Solvers
m_solvers.clear();
// Destroy Factory
m_factory->Destroy();
m_factory.reset();
}
} // namespace NvCloth
@@ -0,0 +1,97 @@
/*
* 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/Component.h>
#include <AzCore/Component/TickBus.h>
#include <NvCloth/IClothSystem.h>
#include <NvCloth/ICloth.h>
#include <NvCloth/ISolver.h>
#include <System/Factory.h>
#include <System/Solver.h>
#include <System/Fabric.h>
#include <System/Cloth.h>
namespace NvCloth
{
//! Implementation of the IClothSystem interface.
//!
//! This class has the responsibility to initialize and tear down NvCloth library.
//! It owns all Solvers, Cloths and Fabrics, and it manages their creation and destruction.
//! It's also the responsible for updating (on Physics Tick) all the solvers that are not flagged as "user simulated".
class SystemComponent
: public AZ::Component
, protected IClothSystem
, protected AZ::TickBus::Handler
{
public:
AZ_COMPONENT(SystemComponent, "{89DF5C48-64AC-4B8E-9E61-0D4C7A7B5491}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void InitializeNvClothLibrary();
static void TearDownNvClothLibrary();
//! Returns true when there is no error reported.
static bool CheckLastClothError();
//! Resets the last error reported by NvCloth.
static void ResetLastClothError();
protected:
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
// IClothSystem overrides ...
ISolver* FindOrCreateSolver(const AZStd::string& name) override;
void DestroySolver(ISolver*& solver) override;
ISolver* GetSolver(const AZStd::string& name) override;
ICloth* CreateCloth(
const AZStd::vector<SimParticleFormat>& initialParticles,
const FabricCookedData& fabricCookedData) override;
void DestroyCloth(ICloth*& cloth) override;
ICloth* GetCloth(ClothId clothId) override;
bool AddCloth(ICloth* cloth, const AZStd::string& solverName = DefaultSolverName) override;
void RemoveCloth(ICloth* cloth) override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
private:
void InitializeSystem();
void DestroySystem();
FabricId FindOrCreateFabric(const FabricCookedData& fabricCookedData);
void DestroyFabric(FabricId fabricId);
// Factory that creates all the solvers, fabric and cloths.
AZStd::unique_ptr<Factory> m_factory;
// List of all the solvers created.
AZStd::vector<AZStd::unique_ptr<Solver>> m_solvers;
// List of all the fabrics created.
AZStd::unordered_map<FabricId, AZStd::unique_ptr<Fabric>> m_fabrics;
// List of all the cloths created.
AZStd::unordered_map<ClothId, AZStd::unique_ptr<Cloth>> m_cloths;
};
} // namespace NvCloth
@@ -0,0 +1,390 @@
/*
* 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 <System/TangentSpaceHelper.h>
namespace NvCloth
{
namespace
{
const float Tolerance = 0.0001f;
}
bool TangentSpaceHelper::CalculateNormals(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<AZ::Vector3>& outNormals)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
if ((indices.size() % 3) != 0)
{
AZ_Error("TangentSpaceHelper", false,
"Size of list of indices (%zu) is not a multiple of 3.",
indices.size());
return false;
}
const size_t triangleCount = indices.size() / 3;
const size_t vertexCount = vertices.size();
// Reset results
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
// calculate the normals per triangle
for (size_t i = 0; i < triangleCount; ++i)
{
TriangleIndices triangleIndices;
TrianglePositions trianglePositions;
TriangleEdges triangleEdges;
GetTriangleData(
i, indices, vertices,
triangleIndices, trianglePositions, triangleEdges);
AZ::Vector3 normal;
ComputeNormal(triangleEdges, normal);
// distribute the normals to the vertices.
for (AZ::u32 vertexIndexInTriangle = 0; vertexIndexInTriangle < 3; ++vertexIndexInTriangle)
{
const float weight = GetVertexWeightInTriangle(vertexIndexInTriangle, trianglePositions);
const SimIndexType vertexIndex = triangleIndices[vertexIndexInTriangle];
outNormals[vertexIndex] += normal * AZStd::max(weight, Tolerance);
}
}
// adjust the normals per vertex
for (auto& outNormal : outNormals)
{
outNormal.NormalizeSafe(Tolerance);
// Safety check for situations where simulation gets out of control.
// Particles' positions can have huge floating point values that
// could lead to non-finite numbers when calculating tangent spaces.
if (!outNormal.IsFinite())
{
outNormal = AZ::Vector3::CreateAxisZ();
}
}
return true;
}
bool TangentSpaceHelper::CalculateTangentsAndBitagents(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimUVType>& uvs,
const AZStd::vector<AZ::Vector3>& normals,
AZStd::vector<AZ::Vector3>& outTangents,
AZStd::vector<AZ::Vector3>& outBitangents)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
if ((indices.size() % 3) != 0)
{
AZ_Error("TangentSpaceHelper", false,
"Size of list of indices (%zu) is not a multiple of 3.",
indices.size());
return false;
}
if (vertices.size() != uvs.size())
{
AZ_Error("TangentSpaceHelper", false,
"Number of vertices (%zu) does not match the number of uvs (%zu).",
vertices.size(), uvs.size());
return false;
}
if (vertices.size() != normals.size())
{
AZ_Error("TangentSpaceHelper", false,
"Number of vertices (%zu) does not match the number of normals (%zu).",
vertices.size(), normals.size());
return false;
}
const size_t triangleCount = indices.size() / 3;
const size_t vertexCount = vertices.size();
// Reset results
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
// calculate the base vectors per triangle
for (size_t i = 0; i < triangleCount; ++i)
{
TriangleIndices triangleIndices;
TrianglePositions trianglePositions;
TriangleEdges triangleEdges;
TriangleUVs triangleUVs;
GetTriangleData(
i, indices, vertices, uvs,
triangleIndices, trianglePositions, triangleEdges, triangleUVs);
AZ::Vector3 tangent, bitangent;
ComputeTangentAndBitangent(triangleUVs, triangleEdges, tangent, bitangent);
// distribute the uv vectors to the vertices.
for (AZ::u32 vertexIndexInTriangle = 0; vertexIndexInTriangle < 3; ++vertexIndexInTriangle)
{
const float weight = GetVertexWeightInTriangle(vertexIndexInTriangle, trianglePositions);
const SimIndexType vertexIndex = triangleIndices[vertexIndexInTriangle];
outTangents[vertexIndex] += tangent * weight;
outBitangents[vertexIndex] += bitangent * weight;
}
}
// adjust the base vectors per vertex
for (size_t i = 0; i < vertexCount; ++i)
{
AdjustTangentAndBitangent(normals[i], outTangents[i], outBitangents[i]);
// Safety check for situations where simulation gets out of control.
// Particles' positions can have huge floating point values that
// could lead to non-finite numbers when calculating tangent spaces.
if (!outTangents[i].IsFinite() ||
!outBitangents[i].IsFinite())
{
outTangents[i] = AZ::Vector3::CreateAxisX();
outBitangents[i] = AZ::Vector3::CreateAxisY();
}
}
return true;
}
bool TangentSpaceHelper::CalculateTangentSpace(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimUVType>& uvs,
AZStd::vector<AZ::Vector3>& outTangents,
AZStd::vector<AZ::Vector3>& outBitangents,
AZStd::vector<AZ::Vector3>& outNormals)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
if ((indices.size() % 3) != 0)
{
AZ_Error("TangentSpaceHelper", false,
"Size of list of indices (%zu) is not a multiple of 3.",
indices.size());
return false;
}
if (vertices.size() != uvs.size())
{
AZ_Error("TangentSpaceHelper", false,
"Number of vertices (%zu) does not match the number of uvs (%zu).",
vertices.size(), uvs.size());
return false;
}
const size_t triangleCount = indices.size() / 3;
const size_t vertexCount = vertices.size();
// Reset results
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
// calculate the base vectors per triangle
for (size_t i = 0; i < triangleCount; ++i)
{
TriangleIndices triangleIndices;
TrianglePositions trianglePositions;
TriangleEdges triangleEdges;
TriangleUVs triangleUVs;
GetTriangleData(
i, indices, vertices, uvs,
triangleIndices, trianglePositions, triangleEdges, triangleUVs);
AZ::Vector3 tangent, bitangent, normal;
if (ComputeNormal(triangleEdges, normal))
{
ComputeTangentAndBitangent(triangleUVs, triangleEdges, tangent, bitangent);
}
else
{
// Use the identity base with low influence to leave other valid triangles to
// affect these vertices. In case no other triangle affects the vertices the base
// will still be valid with identity values as it gets normalized later.
const float identityInfluence = 0.01f;
tangent = AZ::Vector3::CreateAxisX(identityInfluence);
bitangent = AZ::Vector3::CreateAxisY(identityInfluence);
}
// distribute the normals and uv vectors to the vertices.
for (AZ::u32 vertexIndexInTriangle = 0; vertexIndexInTriangle < 3; ++vertexIndexInTriangle)
{
const float weight = GetVertexWeightInTriangle(vertexIndexInTriangle, trianglePositions);
const SimIndexType vertexIndex = triangleIndices[vertexIndexInTriangle];
outNormals[vertexIndex] += normal * AZStd::max(weight, Tolerance);
outTangents[vertexIndex] += tangent * weight;
outBitangents[vertexIndex] += bitangent * weight;
}
}
// adjust the base vectors per vertex
for (size_t i = 0; i < vertexCount; ++i)
{
outNormals[i].NormalizeSafe(Tolerance);
AdjustTangentAndBitangent(outNormals[i], outTangents[i], outBitangents[i]);
// Safety check for situations where simulation gets out of control.
// Particles' positions can have huge floating point values that
// could lead to non-finite numbers when calculating tangent spaces.
if (!outNormals[i].IsFinite() ||
!outTangents[i].IsFinite() ||
!outBitangents[i].IsFinite())
{
outTangents[i] = AZ::Vector3::CreateAxisX();
outBitangents[i] = AZ::Vector3::CreateAxisY();
outNormals[i] = AZ::Vector3::CreateAxisZ();
}
}
return true;
}
void TangentSpaceHelper::GetTriangleData(
size_t triangleIndex,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimParticleFormat>& vertices,
TriangleIndices& triangleIndices,
TrianglePositions& trianglePositions,
TriangleEdges& triangleEdges)
{
triangleIndices =
{{
indices[triangleIndex * 3 + 0],
indices[triangleIndex * 3 + 1],
indices[triangleIndex * 3 + 2]
}};
trianglePositions =
{{
vertices[triangleIndices[0]].GetAsVector3(),
vertices[triangleIndices[1]].GetAsVector3(),
vertices[triangleIndices[2]].GetAsVector3()
}};
triangleEdges =
{{
trianglePositions[1] - trianglePositions[0],
trianglePositions[2] - trianglePositions[0]
}};
}
void TangentSpaceHelper::GetTriangleData(
size_t triangleIndex,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimUVType>& uvs,
TriangleIndices& triangleIndices,
TrianglePositions& trianglePositions,
TriangleEdges& triangleEdges,
TriangleUVs& triangleUVs)
{
GetTriangleData(
triangleIndex, indices, vertices,
triangleIndices, trianglePositions, triangleEdges);
triangleUVs =
{{
uvs[triangleIndices[0]],
uvs[triangleIndices[1]],
uvs[triangleIndices[2]]
}};
}
bool TangentSpaceHelper::ComputeNormal(const TriangleEdges& triangleEdges, AZ::Vector3& normal)
{
normal = triangleEdges[0].Cross(triangleEdges[1]);
// Avoid situations where the edges are parallel resulting in an invalid normal.
// This can happen if the simulation moves particles of triangle to the same spot or very far away.
if (normal.IsZero(Tolerance))
{
// Use the identity base with low influence to leave other valid triangles to
// affect these vertices. In case no other triangle affects the vertices the base
// will still be valid with identity values as it gets normalized later.
const float identityInfluence = 0.01f;
normal = AZ::Vector3::CreateAxisZ(identityInfluence);
return false;
}
normal.Normalize();
return true;
}
bool TangentSpaceHelper::ComputeTangentAndBitangent(
const TriangleUVs& triangleUVs, const TriangleEdges& triangleEdges,
AZ::Vector3& tangent, AZ::Vector3& bitangent)
{
const float deltaU1 = triangleUVs[1].GetX() - triangleUVs[0].GetX();
const float deltaU2 = triangleUVs[2].GetX() - triangleUVs[0].GetX();
const float deltaV1 = triangleUVs[1].GetY() - triangleUVs[0].GetY();
const float deltaV2 = triangleUVs[2].GetY() - triangleUVs[0].GetY();
const float div = (deltaU1 * deltaV2 - deltaU2 * deltaV1);
if (AZ::IsClose(div, 0.0f, Tolerance))
{
tangent = AZ::Vector3::CreateAxisX();
bitangent = AZ::Vector3::CreateAxisY();
return false;
}
// 2D triangle area = (u1*v2-u2*v1)/2
const float a = deltaV2; // /div was removed - no required because of normalize()
const float b = -deltaV1;
const float c = -deltaU2;
const float d = deltaU1;
const float signDiv = AZ::GetSign(div);
// /fAreaMul2*fAreaMul2 was optimized away -> small triangles in UV should contribute less and
// less artifacts (no divide and multiply)
tangent = (triangleEdges[0] * a + triangleEdges[1] * b) * signDiv;
bitangent = (triangleEdges[0] * c + triangleEdges[1] * d) * signDiv;
return true;
}
void TangentSpaceHelper::AdjustTangentAndBitangent(
const AZ::Vector3& normal, AZ::Vector3& tangent, AZ::Vector3& bitangent)
{
// Calculate handedness of the bitangent
AZ::Vector3 bitangentReference = normal.Cross(tangent);
const float handedness = (bitangentReference.Dot(bitangent) < 0.0f) ? -1.0f : 1.0f;
// Apply Gram-Schmidt method to make tangent perpendicular to normal.
tangent -= normal * normal.Dot(tangent);
tangent.NormalizeSafe(Tolerance);
bitangent = normal.Cross(tangent) * handedness;
}
float TangentSpaceHelper::GetVertexWeightInTriangle(AZ::u32 vertexIndexInTriangle, const TrianglePositions& trianglePositions)
{
// weight by angle to fix the L-Shape problem
const AZ::Vector3 edgeA = trianglePositions[(vertexIndexInTriangle + 2) % 3] - trianglePositions[vertexIndexInTriangle];
const AZ::Vector3 edgeB = trianglePositions[(vertexIndexInTriangle + 1) % 3] - trianglePositions[vertexIndexInTriangle];
return edgeA.AngleSafe(edgeB);
}
} // namespace NvCloth
@@ -0,0 +1,86 @@
/*
* 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/RTTI/RTTI.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/containers/array.h>
#include <NvCloth/ITangentSpaceHelper.h>
namespace NvCloth
{
//! Implementation of the ITangentSpaceHelper interface.
class TangentSpaceHelper
: public AZ::Interface<ITangentSpaceHelper>::Registrar
{
public:
AZ_RTTI(TangentSpaceHelper, "{2F8400BF-045A-49C3-B9D1-356011907E62}", ITangentSpaceHelper);
protected:
// ITangentSpace overrides ...
bool CalculateNormals(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
AZStd::vector<AZ::Vector3>& outNormals) override;
bool CalculateTangentsAndBitagents(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimUVType>& uvs,
const AZStd::vector<AZ::Vector3>& normals,
AZStd::vector<AZ::Vector3>& outTangents,
AZStd::vector<AZ::Vector3>& outBitangents) override;
bool CalculateTangentSpace(
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimUVType>& uvs,
AZStd::vector<AZ::Vector3>& outTangents,
AZStd::vector<AZ::Vector3>& outBitangents,
AZStd::vector<AZ::Vector3>& outNormals) override;
private:
using TriangleIndices = AZStd::array<SimIndexType, 3>;
using TrianglePositions = AZStd::array<AZ::Vector3, 3>;
using TriangleUVs = AZStd::array<SimUVType, 3>;
using TriangleEdges = AZStd::array<AZ::Vector3, 2>;
void GetTriangleData(
size_t triangleIndex,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimParticleFormat>& vertices,
TriangleIndices& triangleIndices,
TrianglePositions& trianglePositions,
TriangleEdges& triangleEdges);
void GetTriangleData(
size_t triangleIndex,
const AZStd::vector<SimIndexType>& indices,
const AZStd::vector<SimParticleFormat>& vertices,
const AZStd::vector<SimUVType>& uvs,
TriangleIndices& triangleIndices,
TrianglePositions& trianglePositions,
TriangleEdges& triangleEdges,
TriangleUVs& triangleUVs);
bool ComputeNormal(const TriangleEdges& triangleEdges, AZ::Vector3& normal);
bool ComputeTangentAndBitangent(
const TriangleUVs& triangleUVs, const TriangleEdges& triangleEdges,
AZ::Vector3& tangent, AZ::Vector3& bitangent);
void AdjustTangentAndBitangent(
const AZ::Vector3& normal, AZ::Vector3& tangent, AZ::Vector3& bitangent);
float GetVertexWeightInTriangle(AZ::u32 vertexIndexInTriangle, const TrianglePositions& trianglePositions);
};
} // namespace NvCloth
@@ -0,0 +1,229 @@
/*
* 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 <Integration/ActorComponentBus.h>
// Needed to access the Mesh information inside Actor.
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/Mesh.h>
#include <EMotionFX/Source/SubMesh.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <Utils/ActorAssetHelper.h>
namespace NvCloth
{
ActorAssetHelper::ActorAssetHelper(AZ::EntityId entityId)
: AssetHelper(entityId)
{
}
void ActorAssetHelper::GatherClothMeshNodes(MeshNodeList& meshNodes)
{
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
actorInstance, m_entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return;
}
const EMotionFX::Actor* actor = actorInstance->GetActor();
if (!actor)
{
return;
}
const uint32 numNodes = actor->GetNumNodes();
const uint32 numLODs = actor->GetNumLODLevels();
for (uint32 lodLevel = 0; lodLevel < numLODs; ++lodLevel)
{
for (uint32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
const EMotionFX::Mesh* mesh = actor->GetMesh(lodLevel, nodeIndex);
if (!mesh)
{
continue;
}
const bool hasClothData = (mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_CLOTH_DATA) != nullptr);
if (hasClothData)
{
const EMotionFX::Node* node = actor->GetSkeleton()->GetNode(nodeIndex);
AZ_Assert(node, "Invalid node %u in actor '%s'", nodeIndex, actor->GetFileNameString().c_str());
meshNodes.push_back(node->GetNameString());
}
}
}
}
bool ActorAssetHelper::ObtainClothMeshNodeInfo(
const AZStd::string& meshNode,
MeshNodeInfo& meshNodeInfo,
MeshClothInfo& meshClothInfo)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
actorInstance, m_entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (!actorInstance)
{
return false;
}
const EMotionFX::Actor* actor = actorInstance->GetActor();
if (!actor)
{
return false;
}
const uint32 numNodes = actor->GetNumNodes();
const uint32 numLODs = actor->GetNumLODLevels();
const EMotionFX::Mesh* emfxMesh = nullptr;
uint32 meshFirstPrimitiveIndex = 0;
// Find the render data of the mesh node
for (uint32 lodLevel = 0; lodLevel < numLODs; ++lodLevel)
{
meshFirstPrimitiveIndex = 0;
for (uint32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
const EMotionFX::Mesh* mesh = actor->GetMesh(lodLevel, nodeIndex);
if (!mesh || mesh->GetIsCollisionMesh())
{
// Skip invalid and collision meshes.
continue;
}
const EMotionFX::Node* node = actor->GetSkeleton()->GetNode(nodeIndex);
if (meshNode != node->GetNameString())
{
// Skip. Increase the index of all primitives of the mesh we're skipping.
meshFirstPrimitiveIndex += mesh->GetNumSubMeshes();
continue;
}
// Mesh found, save the lod in mesh info
meshNodeInfo.m_lodLevel = lodLevel;
emfxMesh = mesh;
break;
}
if (emfxMesh)
{
break;
}
}
bool infoObtained = false;
if (emfxMesh)
{
bool dataCopied = CopyDataFromEMotionFXMesh(*emfxMesh, meshClothInfo);
if (dataCopied)
{
const uint32 numSubMeshes = emfxMesh->GetNumSubMeshes();
for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
const EMotionFX::SubMesh* emfxSubMesh = emfxMesh->GetSubMesh(subMeshIndex);
MeshNodeInfo::SubMesh subMesh;
subMesh.m_primitiveIndex = static_cast<int>(meshFirstPrimitiveIndex + subMeshIndex);
subMesh.m_verticesFirstIndex = emfxSubMesh->GetStartVertex();
subMesh.m_numVertices = emfxSubMesh->GetNumVertices();
subMesh.m_indicesFirstIndex = emfxSubMesh->GetStartIndex();
subMesh.m_numIndices = emfxSubMesh->GetNumIndices();
meshNodeInfo.m_subMeshes.push_back(subMesh);
}
infoObtained = true;
}
else
{
AZ_Error("ActorAssetHelper", false, "Failed to extract data from node %s in actor %s",
meshNode.c_str(), actor->GetFileNameString().c_str());
}
}
return infoObtained;
}
bool ActorAssetHelper::CopyDataFromEMotionFXMesh(
const EMotionFX::Mesh& emfxMesh,
MeshClothInfo& meshClothInfo)
{
const int numVertices = emfxMesh.GetNumVertices();
const int numIndices = emfxMesh.GetNumIndices();
if (numVertices == 0 || numIndices == 0)
{
return false;
}
const uint32* sourceIndices = emfxMesh.GetIndices();
const AZ::Vector3* sourcePositions = static_cast<AZ::Vector3*>(emfxMesh.FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
const AZ::u32* sourceClothData = static_cast<AZ::u32*>(emfxMesh.FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_CLOTH_DATA));
const AZ::Vector2* sourceUVs = static_cast<AZ::Vector2*>(emfxMesh.FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 0)); // first UV set
if (!sourceIndices || !sourcePositions || !sourceClothData)
{
return false;
}
const SimUVType uvZero(0.0f, 0.0f);
meshClothInfo.m_particles.resize_no_construct(numVertices);
meshClothInfo.m_uvs.resize_no_construct(numVertices);
meshClothInfo.m_motionConstraints.resize_no_construct(numVertices);
meshClothInfo.m_backstopData.resize_no_construct(numVertices);
for (int index = 0; index < numVertices; ++index)
{
AZ::Color clothVertexData;
clothVertexData.FromU32(sourceClothData[index]);
const float inverseMass = clothVertexData.GetR();
const float motionConstraint = clothVertexData.GetG();
const float backstopRadius = clothVertexData.GetA();
const float backstopOffset = ConvertBackstopOffset(clothVertexData.GetB());
meshClothInfo.m_particles[index].Set(
sourcePositions[index],
inverseMass);
meshClothInfo.m_motionConstraints[index] = motionConstraint;
meshClothInfo.m_backstopData[index].Set(backstopOffset, backstopRadius);
meshClothInfo.m_uvs[index] = (sourceUVs) ? SimUVType(sourceUVs[index].GetX(), sourceUVs[index].GetY()) : uvZero;
}
meshClothInfo.m_indices.resize_no_construct(numIndices);
// Fast copy when SimIndexType is the same size as the EMFX indices type.
if constexpr (sizeof(SimIndexType) == sizeof(uint32))
{
memcpy(meshClothInfo.m_indices.data(), sourceIndices, numIndices * sizeof(SimIndexType));
}
else
{
for (int index = 0; index < numIndices; ++index)
{
meshClothInfo.m_indices[index] = static_cast<SimIndexType>(sourceIndices[index]);
}
}
return true;
}
} // namespace NvCloth
@@ -0,0 +1,49 @@
/*
* 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 <Utils/AssetHelper.h>
namespace EMotionFX
{
class Mesh;
}
namespace NvCloth
{
//! Helper class to obtain cloth information from an Actor Asset.
class ActorAssetHelper
: public AssetHelper
{
public:
AZ_RTTI(ActorAssetHelper, "{3246EAC6-595F-4AFB-BA10-44EB0B824398}", AssetHelper);
explicit ActorAssetHelper(AZ::EntityId entityId);
// AssetHelper overrides ...
void GatherClothMeshNodes(MeshNodeList& meshNodes) override;
bool ObtainClothMeshNodeInfo(
const AZStd::string& meshNode,
MeshNodeInfo& meshNodeInfo,
MeshClothInfo& meshClothInfo) override;
bool DoesSupportSkinnedAnimation() const override
{
return true;
}
private:
bool CopyDataFromEMotionFXMesh(
const EMotionFX::Mesh& emfxMesh,
MeshClothInfo& meshClothInfo);
};
} // namespace NvCloth
@@ -0,0 +1,30 @@
/*
* 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/Memory/SystemAllocator.h>
namespace NvCloth
{
//! System allocator to be used for all nvcloth library allocations.
class AzClothAllocator
: public AZ::SystemAllocator
{
friend class AZ::AllocatorInstance<AzClothAllocator>;
public:
AZ_TYPE_INFO(AzClothAllocator, "{F2C6C61F-587E-4EBB-A377-A5E57BB6B849}");
// AZ::SystemAllocator overrides ...
const char* GetName() const override { return "NvCloth System Allocator"; }
const char* GetDescription() const override { return "NvCloth library memory allocator"; }
};
} // namespace NvCloth
@@ -0,0 +1,76 @@
/*
* 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 <Utils/AssetHelper.h>
#include <Utils/MeshAssetHelper.h>
#include <Utils/ActorAssetHelper.h>
#include <platform.h> // Needed for MeshAsset.h
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <Integration/ActorComponentBus.h>
namespace NvCloth
{
const int InvalidIndex = -1;
AssetHelper::AssetHelper(AZ::EntityId entityId)
: m_entityId(entityId)
{
}
AZStd::unique_ptr<AssetHelper> AssetHelper::CreateAssetHelper(AZ::EntityId entityId)
{
// Does the entity have an Actor Asset?
EMotionFX::ActorInstance* actorInstance = nullptr;
EMotionFX::Integration::ActorComponentRequestBus::EventResult(
actorInstance, entityId, &EMotionFX::Integration::ActorComponentRequestBus::Events::GetActorInstance);
if (actorInstance)
{
return AZStd::make_unique<ActorAssetHelper>(entityId);
}
AZ::Data::Asset<AZ::Data::AssetData> meshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(
meshAsset, entityId, &LmbrCentral::MeshComponentRequestBus::Events::GetMeshAsset);
if (!meshAsset)
{
return nullptr;
}
// Does the entity have a Mesh Asset?
if (meshAsset.GetType() == AZ::AzTypeInfo<LmbrCentral::MeshAsset>::Uuid())
{
return AZStd::make_unique<MeshAssetHelper>(entityId);
}
AZ_Warning("AssetHelper", false, "Unexpected asset type");
return nullptr;
}
float AssetHelper::ConvertBackstopOffset(float backstopOffset)
{
constexpr float ToleranceU8 = 1.0f / 255.0f;
// Convert range from [0,1] -> [-1,1]
backstopOffset = AZ::GetClamp(backstopOffset * 2.0f - 1.0f, -1.0f, 1.0f);
// Since the color was stored as U32 in the mesh, the low precision makes values
// of 0.5f becoming an small negative number in the conversion from [0,1] to [-1,1].
// So this sets the value to 0 when it is smaller than the tolerance of U8.
backstopOffset = (std::fabs(backstopOffset) < ToleranceU8) ? 0.0f : backstopOffset;
return backstopOffset;
}
} // namespace NvCloth
@@ -0,0 +1,99 @@
/*
* 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/Asset/AssetCommon.h>
#include <NvCloth/Types.h>
namespace NvCloth
{
extern const int InvalidIndex;
//! List of mesh nodes (names) inside an Asset.
using MeshNodeList = AZStd::vector<AZStd::string>;
//! Structure holding information about the submeshes of a render mesh node.
//! While the simulation data is a single buffer for vertices and indices,
//! this structure knows how to separate them in different submeshes, this will be
//! be used when the MeshModificationNotificationBus request the modification
//! of an specific submesh (lodLevel and primitiveIndex).
struct MeshNodeInfo
{
//! LOD level of the mesh node inside the asset.
int m_lodLevel = InvalidIndex;
//! Identifies a submesh inside the render mesh.
struct SubMesh
{
//! Primitive index inside the asset.
int m_primitiveIndex = InvalidIndex;
//! First vertex of the submesh.
int m_verticesFirstIndex = InvalidIndex;
//! Number of vertices of the submesh after the first vertex.
int m_numVertices = 0;
//! First index inside the asset.
int m_indicesFirstIndex = InvalidIndex;
//! Number of indices of the submesh after the first index.
int m_numIndices = 0;
};
//! List of submeshes.
AZStd::vector<SubMesh> m_subMeshes;
};
//! Structure with all the cloth information asset helper can obtain from the mesh.
struct MeshClothInfo
{
AZStd::vector<SimParticleFormat> m_particles;
AZStd::vector<SimIndexType> m_indices;
AZStd::vector<SimUVType> m_uvs;
AZStd::vector<float> m_motionConstraints;
AZStd::vector<AZ::Vector2> m_backstopData; //!< X contains offset, Y contains radius.
};
//! Interface to obtain cloth information from inside an Asset.
class AssetHelper
{
public:
AZ_RTTI(AssetHelper, "{8BBDFB6C-4615-4092-B38A-A1FEFEBD1A1F}");
explicit AssetHelper(AZ::EntityId entityId);
virtual ~AssetHelper() = default;
//! Creates the appropriate asset helper depending on the entity's render service.
static AZStd::unique_ptr<AssetHelper> CreateAssetHelper(AZ::EntityId entityId);
//! Populates the list of mesh nodes inside the Asset that contains cloth information.
virtual void GatherClothMeshNodes(MeshNodeList& meshNodes) = 0;
//! Extracts the cloth mesh information of a node inside the Asset.
virtual bool ObtainClothMeshNodeInfo(
const AZStd::string& meshNode,
MeshNodeInfo& meshNodeInfo,
MeshClothInfo& meshClothInfo) = 0;
//! Returns whether the asset has support for skinned animation or not.
virtual bool DoesSupportSkinnedAnimation() const = 0;
protected:
static float ConvertBackstopOffset(float backstopOffset);
AZ::EntityId m_entityId;
};
} // namespace NvCloth
@@ -0,0 +1,234 @@
/*
* 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 <platform.h> // Needed for MeshAsset.h
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <LmbrCentral/Rendering/MeshAsset.h>
#include <IRenderer.h>
#include <IIndexedMesh.h> // Needed for SMeshColor
#include <AzCore/Math/Color.h>
#include <Utils/MeshAssetHelper.h>
namespace NvCloth
{
MeshAssetHelper::MeshAssetHelper(AZ::EntityId entityId)
: AssetHelper(entityId)
{
}
void MeshAssetHelper::GatherClothMeshNodes(MeshNodeList& meshNodes)
{
AZ::Data::Asset<LmbrCentral::MeshAsset> meshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(
meshAsset, m_entityId, &LmbrCentral::MeshComponentRequestBus::Events::GetMeshAsset);
if (!meshAsset.IsReady())
{
return;
}
IStatObj* statObj = meshAsset.Get()->m_statObj.get();
if (!statObj)
{
return;
}
if (!statObj->GetClothData().empty())
{
meshNodes.push_back(statObj->GetCGFNodeName().c_str());
}
const int subObjectCount = statObj->GetSubObjectCount();
for (int i = 0; i < subObjectCount; ++i)
{
IStatObj::SSubObject* subObject = statObj->GetSubObject(i);
if (subObject &&
subObject->pStatObj &&
!subObject->pStatObj->GetClothData().empty())
{
meshNodes.push_back(subObject->pStatObj->GetCGFNodeName().c_str());
}
}
}
bool MeshAssetHelper::ObtainClothMeshNodeInfo(
const AZStd::string& meshNode,
MeshNodeInfo& meshNodeInfo,
MeshClothInfo& meshClothInfo)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Cloth);
AZ::Data::Asset<LmbrCentral::MeshAsset> meshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(
meshAsset, m_entityId, &LmbrCentral::MeshComponentRequestBus::Events::GetMeshAsset);
if (!meshAsset.IsReady())
{
return false;
}
IStatObj* statObj = meshAsset.Get()->m_statObj.get();
if (!statObj)
{
return false;
}
IStatObj* selectedStatObj = nullptr;
int primitiveIndex = 0;
// Find the render data of the mesh node
if (meshNode == statObj->GetCGFNodeName().c_str())
{
selectedStatObj = statObj;
}
else
{
const int subObjectCount = statObj->GetSubObjectCount();
for (int i = 0; i < subObjectCount; ++i)
{
IStatObj::SSubObject* subObject = statObj->GetSubObject(i);
if (subObject &&
subObject->pStatObj &&
subObject->pStatObj->GetRenderMesh() &&
meshNode == subObject->pStatObj->GetCGFNodeName().c_str())
{
selectedStatObj = subObject->pStatObj;
primitiveIndex = i;
break;
}
}
}
bool infoObtained = false;
if (selectedStatObj)
{
bool dataCopied = false;
if (IRenderMesh* renderMesh = selectedStatObj->GetRenderMesh())
{
dataCopied = CopyDataFromRenderMesh(
*renderMesh, selectedStatObj->GetClothData(),
meshClothInfo);
}
if (dataCopied)
{
// subStatObj contains the buffers for all its submeshes
// so only 1 submesh is necessary.
MeshNodeInfo::SubMesh subMesh;
subMesh.m_primitiveIndex = primitiveIndex;
subMesh.m_verticesFirstIndex = 0;
subMesh.m_numVertices = meshClothInfo.m_particles.size();
subMesh.m_indicesFirstIndex = 0;
subMesh.m_numIndices = meshClothInfo.m_indices.size();
meshNodeInfo.m_lodLevel = 0; // Cloth is alway in LOD 0
meshNodeInfo.m_subMeshes.push_back(subMesh);
infoObtained = true;
}
else
{
AZ_Error("MeshAssetHelper", false, "Failed to extract data from node %s in mesh %s",
meshNode.c_str(), statObj->GetFileName().c_str());
}
}
return infoObtained;
}
bool MeshAssetHelper::CopyDataFromRenderMesh(
IRenderMesh& renderMesh,
const AZStd::vector<SMeshColor>& renderMeshClothData,
MeshClothInfo& meshClothInfo)
{
const int numVertices = renderMesh.GetNumVerts();
const int numIndices = renderMesh.GetNumInds();
if (numVertices == 0 || numIndices == 0)
{
return false;
}
else if (numVertices != renderMeshClothData.size())
{
AZ_Error("MeshAssetHelper", false,
"Number of vertices (%d) doesn't match the number of cloth data (%zu)",
numVertices,
renderMeshClothData.size());
return false;
}
{
IRenderMesh::ThreadAccessLock lockRenderMesh(&renderMesh);
vtx_idx* renderMeshIndices = nullptr;
strided_pointer<Vec3> renderMeshVertices;
strided_pointer<Vec2> renderMeshUVs;
renderMeshIndices = renderMesh.GetIndexPtr(FSL_READ);
renderMeshVertices.data = reinterpret_cast<Vec3*>(renderMesh.GetPosPtr(renderMeshVertices.iStride, FSL_READ));
renderMeshUVs.data = reinterpret_cast<Vec2*>(renderMesh.GetUVPtr(renderMeshUVs.iStride, FSL_READ, 0)); // first UV set
const SimUVType uvZero(0.0f, 0.0f);
meshClothInfo.m_particles.resize_no_construct(numVertices);
meshClothInfo.m_uvs.resize_no_construct(numVertices);
meshClothInfo.m_motionConstraints.resize_no_construct(numVertices);
meshClothInfo.m_backstopData.resize_no_construct(numVertices);
for (int index = 0; index < numVertices; ++index)
{
const ColorB clothVertexDataColorB = renderMeshClothData[index].GetRGBA();
const AZ::Color clothVertexData(
clothVertexDataColorB.r,
clothVertexDataColorB.g,
clothVertexDataColorB.b,
clothVertexDataColorB.a);
const float inverseMass = clothVertexData.GetR();
const float motionConstraint = clothVertexData.GetG();
const float backstopRadius = clothVertexData.GetA();
const float backstopOffset = ConvertBackstopOffset(clothVertexData.GetB());
meshClothInfo.m_particles[index].Set(
renderMeshVertices[index].x,
renderMeshVertices[index].y,
renderMeshVertices[index].z,
inverseMass);
meshClothInfo.m_motionConstraints[index] = motionConstraint;
meshClothInfo.m_backstopData[index].Set(backstopOffset, backstopRadius);
meshClothInfo.m_uvs[index] = (renderMeshUVs.data) ? SimUVType(renderMeshUVs[index].x, renderMeshUVs[index].y) : uvZero;
}
meshClothInfo.m_indices.resize_no_construct(numIndices);
// Fast copy when SimIndexType is the same size as the vtx_idx.
if constexpr (sizeof(SimIndexType) == sizeof(vtx_idx))
{
memcpy(meshClothInfo.m_indices.data(), renderMeshIndices, numIndices * sizeof(SimIndexType));
}
else
{
for (int index = 0; index < numIndices; ++index)
{
meshClothInfo.m_indices[index] = static_cast<SimIndexType>(renderMeshIndices[index]);
}
}
renderMesh.UnlockStream(VSF_GENERAL);
renderMesh.UnlockIndexStream();
}
return true;
}
} // namespace NvCloth
@@ -0,0 +1,48 @@
/*
* 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 <Utils/AssetHelper.h>
struct IRenderMesh;
struct SMeshColor;
namespace NvCloth
{
//! Helper class to obtain cloth information from a Mesh Asset.
class MeshAssetHelper
: public AssetHelper
{
public:
AZ_RTTI(MeshAssetHelper, "{292066E4-DEB8-47C6-94CA-7BF1D75129F7}", AssetHelper);
explicit MeshAssetHelper(AZ::EntityId entityId);
// AssetHelper overrides ...
void GatherClothMeshNodes(MeshNodeList& meshNodes) override;
bool ObtainClothMeshNodeInfo(
const AZStd::string& meshNode,
MeshNodeInfo& meshNodeInfo,
MeshClothInfo& meshClothInfo) override;
bool DoesSupportSkinnedAnimation() const override
{
return false;
}
private:
bool CopyDataFromRenderMesh(
IRenderMesh& renderMesh,
const AZStd::vector<SMeshColor>& renderMeshClothData,
MeshClothInfo& meshClothInfo);
};
} // namespace NvCloth