/* * 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 "StdAfx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BLAST_EDITOR #include #endif namespace Blast { static const char* const DefaultConfigurationPath = "default.blastconfiguration"; void BlastSystemComponent::Reflect(AZ::ReflectContext* context) { BlastGlobalConfiguration::Reflect(context); if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class()->Version(1); if (AZ::EditContext* ec = serialize->GetEditContext()) { ec->Class("Blast", "Adds support for the NVIDIA Blast destruction system") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true); } } } void BlastGlobalConfiguration::Reflect(AZ::ReflectContext* context) { BlastMaterialLibraryAsset::Reflect(context); BlastMaterialConfiguration::Reflect(context); if (AZ::SerializeContext* serialize = azrtti_cast(context)) { serialize->Class() ->Field("BlastMaterialLibrary", &BlastGlobalConfiguration::m_materialLibrary) ->Field("StressSolverIterations", &BlastGlobalConfiguration::m_stressSolverIterations) ->Version(1); if (AZ::EditContext* ec = serialize->GetEditContext()) { ec->Class( "Blast global configuration", "Set of configuration that are applied globally within Blast gem.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement( AZ::Edit::UIHandlers::Default, &BlastGlobalConfiguration::m_materialLibrary, "Blast material library", "Material library asset to be used globally.") ->DataElement( AZ::Edit::UIHandlers::Default, &BlastGlobalConfiguration::m_stressSolverIterations, "Stress solver iterations", "Number of iterations stress solver on each family runs for each tick.") ->Attribute(AZ::Edit::Attributes::Min, 0) ->Attribute(AZ::Edit::Attributes::Max, 50000); } } } void BlastSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("BlastService")); } void BlastSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC_CE("BlastService")); } void BlastSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC_CE("PhysXService")); } void BlastSystemComponent::Init() { // Route NvBlast allocations through the AZ system allocator NvBlastGlobalSetAllocatorCallback(&m_blastAllocatorCallback); m_debugRenderMode = DebugRenderDisabled; } void BlastSystemComponent::Activate() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::System); auto blastAssetHandler = aznew BlastAssetHandler(); blastAssetHandler->Register(); m_assetHandlers.emplace_back(blastAssetHandler); auto materialAsset = aznew AzFramework::GenericAssetHandler( "Blast Material", "Blast", "blastmaterial"); materialAsset->Register(); m_assetHandlers.emplace_back(materialAsset); // Add asset types and extensions to AssetCatalog. Uses "AssetCatalogService". auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler(); if (assetCatalog) { assetCatalog->EnableCatalogForAsset(AZ::AzTypeInfo::Uuid()); assetCatalog->AddExtension("blast"); } m_registered = false; BlastSystemRequestBus::Handler::BusConnect(); AZ::TickBus::Handler::BusConnect(); CrySystemEventBus::Handler::BusConnect(); InitPhysics(); } void BlastSystemComponent::Deactivate() { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::System); CrySystemEventBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect(); BlastSystemRequestBus::Handler::BusDisconnect(); SaveConfiguration(); DeactivatePhysics(); m_assetHandlers.clear(); }; void BlastSystemComponent::InitPhysics() { // Create blast singletons m_tkFramework.reset(NvBlastTkFrameworkCreate()); physx::PxCpuDispatcher* dispatcher; PhysX::SystemRequestsBus::BroadcastResult(dispatcher, &PhysX::SystemRequests::GetCpuDispatcher); physx::PxCooking* cooking = nullptr; PhysX::SystemRequestsBus::BroadcastResult(cooking, &PhysX::SystemRequests::GetCooking); m_defaultTaskManager.reset(physx::PxTaskManager::createTaskManager(NvBlastGetPxErrorCallback(), dispatcher)); m_extSerialization.reset(NvBlastExtSerializationCreate()); if (m_extSerialization != nullptr) { NvBlastExtPxSerializerLoadSet(*m_tkFramework, PxGetPhysics(), *cooking, *m_extSerialization); NvBlastExtTkSerializerLoadSet(*m_tkFramework, *m_extSerialization); } NvBlastProfilerSetCallback(&m_blastProfilerCallback); NvBlastProfilerSetDetail(Nv::Blast::ProfilerDetail::HIGH); } void BlastSystemComponent::DeactivatePhysics() { m_extSerialization = nullptr; m_defaultTaskManager = nullptr; m_tkFramework = nullptr; } void BlastSystemComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Physics); AZ::JobCompletion jobCompletion; BlastFamilyComponentRequestBus::EnumerateHandlers( [&jobCompletion](BlastFamilyComponentRequests* handler) { auto stressDamageJob = AZ::CreateJobFunction( [handler]() -> void { handler->ApplyStressDamage(); }, true); stressDamageJob->SetDependent(&jobCompletion); stressDamageJob->Start(); return true; }); BlastFamilyComponentRequestBus::EnumerateHandlers( [](BlastFamilyComponentRequests* handler) { handler->SyncMeshes(); return true; }); jobCompletion.StartAndWaitForCompletion(); // Run groups for (auto i = 0; i < m_groups.size(); ++i) { if (m_groups[i].m_tkGroup->getActorCount() == 0) { AZStd::swap(m_groups[i], m_groups.back()); m_groups.pop_back(); } } for (auto& group : m_groups) { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Physics, "ExtGroupTaskManager::process"); group.m_extGroupTaskManager->process(); } for (auto& group : m_groups) { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Physics, "ExtGroupTaskManager::wait"); group.m_extGroupTaskManager->wait(); } // Clean up damage descriptions and program params now that groups have run. { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Physics, "BlastSystemComponent::OnTick::Cleanup"); m_radialDamageDescs.clear(); m_capsuleDamageDescs.clear(); m_shearDamageDescs.clear(); m_triangleDamageDescs.clear(); m_impactDamageDescs.clear(); m_programParams.clear(); } if (gEnv && m_debugRenderMode) { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::Physics, "BlastSystemComponent::OnTick::DebugRender"); DebugRenderBuffer buffer; BlastFamilyComponentRequestBus::Broadcast( &BlastFamilyComponentRequests::FillDebugRenderBuffer, buffer, m_debugRenderMode); for (DebugLine& line : buffer.m_lines) { Vec3 p0(line.m_p0.GetX(), line.m_p0.GetY(), line.m_p0.GetZ()); Vec3 p1(line.m_p1.GetX(), line.m_p1.GetY(), line.m_p1.GetZ()); ColorF color(line.m_color.GetR(), line.m_color.GetG(), line.m_color.GetB(), line.m_color.GetA()); gEnv->pRenderer->GetIRenderAuxGeom()->DrawLine(p0, color, p1, color); } } } void BlastSystemComponent::OnAssetReloaded(AZ::Data::Asset asset) { if (m_configuration.m_materialLibrary == asset) { m_configuration.m_materialLibrary = asset; } } void BlastSystemComponent::LoadConfiguration() { BlastGlobalConfiguration globalConfiguration; bool loaded = AZ::Utils::LoadObjectFromFileInPlace( DefaultConfigurationPath, globalConfiguration); AZ_Warning("Blast", loaded, "Failed to load Blast configuration, initializing with default configs."); SetGlobalConfiguration(globalConfiguration); SaveConfiguration(); } void BlastSystemComponent::SaveConfiguration() { auto assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@"); if (!assetRoot) { return; } AZStd::string fullPath; AzFramework::StringFunc::Path::Join(assetRoot, DefaultConfigurationPath, fullPath); bool saved = AZ::Utils::SaveObjectToFile( fullPath.c_str(), AZ::DataStream::ST_XML, &m_configuration); AZ_Warning("BlastSystemComponent", saved, "Failed to save Blast configuration"); } void BlastSystemComponent::CheckoutConfiguration() { const auto assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@"); AZStd::string fullPath; AzFramework::StringFunc::Path::Join(assetRoot, DefaultConfigurationPath, fullPath); AzToolsFramework::SourceControlCommandBus::Broadcast( &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, fullPath.c_str(), true, [](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& info) { // File is checked out }); } void BlastSystemComponent::OnCrySystemInitialized(ISystem&, const SSystemInitParams&) { LoadConfiguration(); RegisterCommands(); } void BlastSystemComponent::OnCryEditorInitialized() { CheckoutConfiguration(); } Nv::Blast::TkFramework* BlastSystemComponent::GetTkFramework() const { return m_tkFramework.get(); } Nv::Blast::ExtSerialization* BlastSystemComponent::GetExtSerialization() const { return m_extSerialization.get(); } Nv::Blast::TkGroup* BlastSystemComponent::GetTkGroup() { m_groups.emplace_back(); Nv::Blast::TkGroupDesc groupDesc; groupDesc.workerCount = m_defaultTaskManager->getCpuDispatcher()->getWorkerCount(); m_groups.back().m_tkGroup.reset(m_tkFramework->createGroup(groupDesc)); m_groups.back().m_extGroupTaskManager.reset( Nv::Blast::ExtGroupTaskManager::create(*m_defaultTaskManager, m_groups.back().m_tkGroup.get())); return m_groups.back().m_tkGroup.get(); } void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr desc) { m_radialDamageDescs.push_back(AZStd::move(desc)); } void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr desc) { m_capsuleDamageDescs.push_back(AZStd::move(desc)); } void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr desc) { m_shearDamageDescs.push_back(AZStd::move(desc)); } void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr desc) { m_triangleDamageDescs.push_back(AZStd::move(desc)); } void BlastSystemComponent::AddDamageDesc(AZStd::unique_ptr desc) { m_impactDamageDescs.push_back(AZStd::move(desc)); } void BlastSystemComponent::AddProgramParams(AZStd::unique_ptr program) { m_programParams.emplace_back(AZStd::move(program)); } const BlastGlobalConfiguration& BlastSystemComponent::GetGlobalConfiguration() const { return m_configuration; } void BlastSystemComponent::SetGlobalConfiguration(const BlastGlobalConfiguration& globalConfiguration) { m_configuration = globalConfiguration; SaveConfiguration(); { AZ::Data::Asset& materialLibrary = m_configuration.m_materialLibrary; if (!materialLibrary.GetId().IsValid()) { AZ_Warning("Blast", false, "LoadDefaultMaterialLibrary: Default Material Library asset ID is invalid."); return; } const bool queueLoadData = true; const AZ::Data::AssetFilterCB assetLoadFilterCB = nullptr; const bool loadBlocking = true; materialLibrary = AZ::Data::AssetManager::Instance().GetAsset( materialLibrary.GetId(), queueLoadData, assetLoadFilterCB, loadBlocking); // Listen for material library asset modification events if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(materialLibrary.GetId())) { AZ::Data::AssetBus::MultiHandler::BusDisconnect(); AZ::Data::AssetBus::MultiHandler::BusConnect(materialLibrary.GetId()); } AZ_Warning( "Blast", (materialLibrary.GetData() != nullptr), "LoadDefaultMaterialLibrary: Default Material Library asset data is invalid."); } #ifdef BLAST_EDITOR AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); #endif } void BlastSystemComponent::AZBlastProfilerCallback::zoneStart(const char* eventName) { AZ_PROFILE_EVENT_BEGIN(AZ::Debug::ProfileCategory::Physics, eventName); } void BlastSystemComponent::AZBlastProfilerCallback::zoneEnd() { AZ_PROFILE_EVENT_END(AZ::Debug::ProfileCategory::Physics); } static void CmdToggleBlastDebugVisualization(IConsoleCmdArgs* args) { using namespace CryStringUtils; const int argumentCount = args->GetArgCount(); if (argumentCount == 2) { const auto userPreference = static_cast(strtol(args->GetArg(1), nullptr, 10)); BlastSystemRequestBus::Broadcast(&BlastSystemRequests::SetDebugRenderMode, userPreference); } else { AZ_Warning( "Blast", false, "Invalid blast_debug Arguments. Please use blast_debug 1 to enable, blast_debug 0 to disable."); } } void BlastSystemComponent::RegisterCommands() { if (m_registered) { return; } if (gEnv) { IConsole* console = gEnv->pSystem->GetIConsole(); if (console) { console->AddCommand("blast_debug", CmdToggleBlastDebugVisualization); } m_registered = true; } } void BlastSystemComponent::SetDebugRenderMode(DebugRenderMode debugRenderMode) { m_debugRenderMode = debugRenderMode; } } // namespace Blast