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

457 lines
14 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/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 Open 3D Engine 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 Open 3D Engine 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(Cloth, AZ::Crc32(eventName), eventName);
}
else
{
AZ_PROFILE_BEGIN(Cloth, eventName);
}
return nullptr;
}
void zoneEnd([[maybe_unused]] void* profilerData,
[[maybe_unused]] const char* eventName, bool detached,
[[maybe_unused]] uint64_t contextId) override
{
if (detached)
{
AZ_PROFILE_INTERVAL_END(Cloth, AZ::Crc32(eventName));
}
else
{
AZ_PROFILE_END(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(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(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
[[maybe_unused]] 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