aboutsummaryrefslogtreecommitdiff
path: root/NvBlast/test/src
diff options
context:
space:
mode:
Diffstat (limited to 'NvBlast/test/src')
-rw-r--r--NvBlast/test/src/AlignedAllocator.h69
-rw-r--r--NvBlast/test/src/BlastBaseTest.h159
-rw-r--r--NvBlast/test/src/TkBaseTest.h467
-rw-r--r--NvBlast/test/src/perf/BlastBasePerfTest.h374
-rw-r--r--NvBlast/test/src/perf/SolverPerfTests.cpp211
-rw-r--r--NvBlast/test/src/unit/APITests.cpp1354
-rw-r--r--NvBlast/test/src/unit/ActorTests.cpp1059
-rw-r--r--NvBlast/test/src/unit/AssetTests.cpp529
-rw-r--r--NvBlast/test/src/unit/CoreTests.cpp293
-rw-r--r--NvBlast/test/src/unit/FamilyGraphTests.cpp377
-rw-r--r--NvBlast/test/src/unit/MultithreadingTests.cpp395
-rw-r--r--NvBlast/test/src/unit/SyncTests.cpp309
-rw-r--r--NvBlast/test/src/unit/TkCompositeTests.cpp739
-rw-r--r--NvBlast/test/src/unit/TkTests.cpp1528
-rw-r--r--NvBlast/test/src/utils/TaskDispatcher.h191
-rw-r--r--NvBlast/test/src/utils/TestAssets.cpp288
-rw-r--r--NvBlast/test/src/utils/TestAssets.h40
-rw-r--r--NvBlast/test/src/utils/TestProfiler.h27
18 files changed, 8409 insertions, 0 deletions
diff --git a/NvBlast/test/src/AlignedAllocator.h b/NvBlast/test/src/AlignedAllocator.h
new file mode 100644
index 0000000..f8bf3a5
--- /dev/null
+++ b/NvBlast/test/src/AlignedAllocator.h
@@ -0,0 +1,69 @@
+/*
+* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef ALIGNEDALLOCATOR_H
+#define ALIGNEDALLOCATOR_H
+
+#include "NvPreprocessor.h"
+
+
+/**
+Aligned allocation. First template argument has the signature of stdlib malloc.
+
+Example using malloc and 16-byte alignment:
+
+// b will lie on a 16-byte boundary and point to 50 bytes of usable memory
+void* b = alignedAlloc<malloc,16>(50);
+*/
+template<void*(*allocFn)(size_t), int A>
+void* alignedAlloc(size_t size)
+{
+ NV_COMPILE_TIME_ASSERT(A > 0 && A <= 256);
+ unsigned char* mem = (unsigned char*)allocFn(size + A);
+ const unsigned char offset = (unsigned char)((uintptr_t)A - (uintptr_t)mem % A - 1);
+ mem += offset;
+ *mem++ = offset;
+ return mem;
+};
+
+
+/**
+Version of alignedAlloc specialized 16-byte aligned allocation.
+*/
+template<void*(*allocFn)(size_t)>
+void* alignedAlloc(size_t size)
+{
+ return alignedAlloc<allocFn, 16>(size);
+}
+
+
+/**
+Aligned deallocation. First template argument has the signature of stdlib free.
+
+Memory freed using this function MUST have been allocated using alignedAlloc.
+
+Example using free:
+
+// Using the memory pointer b from the example above (for alignedAlloc)
+alignedFree<free>(b);
+*/
+template<void(*freeFn)(void*)>
+void alignedFree(void* block)
+{
+ if (block != nullptr)
+ {
+ unsigned char* mem = (unsigned char*)block;
+ const unsigned char offset = *--mem;
+ freeFn(mem - offset);
+ }
+};
+
+
+#endif // ALIGNEDALLOCATOR_H
diff --git a/NvBlast/test/src/BlastBaseTest.h b/NvBlast/test/src/BlastBaseTest.h
new file mode 100644
index 0000000..2881645
--- /dev/null
+++ b/NvBlast/test/src/BlastBaseTest.h
@@ -0,0 +1,159 @@
+/*
+* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef BLASTBASETEST_H
+#define BLASTBASETEST_H
+
+
+#include "NvBlastTkFramework.h"
+
+#include "gtest/gtest.h"
+
+#include "NvBlast.h"
+
+#include "AlignedAllocator.h"
+
+#include "TestAssets.h"
+
+#include "PxErrorCallback.h"
+#include "PxAllocatorCallback.h"
+
+
+#include <ostream>
+
+
+template<int FailLevel, int Verbosity>
+class BlastBaseTest : public testing::Test, public physx::PxAllocatorCallback, public physx::PxErrorCallback
+{
+public:
+ static void* tkAlloc(size_t size)
+ {
+ Nv::Blast::TkFramework* fw = NvBlastTkFrameworkGet();
+ if (fw != nullptr)
+ {
+ return fw->getAllocatorCallback().allocate(size, nullptr, __FILE__, __LINE__);
+ }
+ else
+ {
+ return std::malloc(size);
+ }
+ }
+
+ static void tkFree(void* mem)
+ {
+ Nv::Blast::TkFramework* fw = NvBlastTkFrameworkGet();
+ if (fw != nullptr)
+ {
+ fw->getAllocatorCallback().deallocate(mem);
+ }
+ else
+ {
+ std::free(mem);
+ }
+ }
+
+ // A zeroing alloc with the same signature as malloc
+ static void* alloc(size_t size)
+ {
+ return memset(alignedAlloc<tkAlloc>(size), 0, size);
+ }
+
+ static void free(void* mem)
+ {
+ alignedFree<tkFree>(mem);
+ }
+
+ // Message log for blast functions
+ static void messageLog(int type, const char* msg, const char* file, int line)
+ {
+ if (FailLevel >= type)
+ {
+ switch (type)
+ {
+ case NvBlastMessage::Error: EXPECT_TRUE(false) << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Warning: EXPECT_TRUE(false) << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Info: EXPECT_TRUE(false) << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Debug: EXPECT_TRUE(false) << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break;
+ }
+ }
+ else
+ if (Verbosity > 0)
+ {
+ switch (type)
+ {
+ case NvBlastMessage::Error: std::cout << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Warning: std::cout << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Info: std::cout << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Debug: std::cout << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break;
+ }
+ }
+ }
+
+ // PxAllocatorCallback interface
+ virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) override
+ {
+ NV_UNUSED(typeName);
+ NV_UNUSED(filename);
+ NV_UNUSED(line);
+ return alignedAlloc<std::malloc>(size);
+ }
+
+ // PxAllocatorCallback interface
+ virtual void deallocate(void* ptr) override
+ {
+ alignedFree<std::free>(ptr);
+ }
+
+ // PxErrorCallback interface
+ virtual void reportError(physx::PxErrorCode::Enum code, const char* message, const char* file, int line) override
+ {
+ uint32_t failMask = 0;
+ switch (FailLevel)
+ {
+ case NvBlastMessage::Debug:
+ case NvBlastMessage::Info: failMask |= physx::PxErrorCode::eDEBUG_INFO;
+ case NvBlastMessage::Warning: failMask |= physx::PxErrorCode::eDEBUG_WARNING;
+ case NvBlastMessage::Error: failMask |= physx::PxErrorCode::eABORT | physx::PxErrorCode::eABORT | physx::PxErrorCode::eINTERNAL_ERROR | physx::PxErrorCode::eOUT_OF_MEMORY | physx::PxErrorCode::eINVALID_OPERATION | physx::PxErrorCode::eINVALID_PARAMETER;
+ }
+
+ if (!(failMask & code) && Verbosity <= 0)
+ {
+ return;
+ }
+
+ std::string output = "NvBlast Test ";
+ switch (code)
+ {
+ case physx::PxErrorCode::eNO_ERROR: break;
+ case physx::PxErrorCode::eDEBUG_INFO: output += "Debug Info"; break;
+ case physx::PxErrorCode::eDEBUG_WARNING: output += "Debug Warning"; break;
+ case physx::PxErrorCode::eINVALID_PARAMETER: output += "Invalid Parameter"; break;
+ case physx::PxErrorCode::eINVALID_OPERATION: output += "Invalid Operation"; break;
+ case physx::PxErrorCode::eOUT_OF_MEMORY: output += "Out of Memory"; break;
+ case physx::PxErrorCode::eINTERNAL_ERROR: output += "Internal Error"; break;
+ case physx::PxErrorCode::eABORT: output += "Abort"; break;
+ case physx::PxErrorCode::ePERF_WARNING: output += "Perf Warning"; break;
+ default: FAIL();
+ }
+ output += std::string(" message in ") + file + "(" + std::to_string(line) + "): " + message + "\n";
+
+ if (failMask & code)
+ {
+ EXPECT_TRUE(false) << output;
+ }
+ else
+ {
+ std::cout << output;
+ }
+ }
+};
+
+
+#endif // #ifndef BLASTBASETEST_H
diff --git a/NvBlast/test/src/TkBaseTest.h b/NvBlast/test/src/TkBaseTest.h
new file mode 100644
index 0000000..9ea632b
--- /dev/null
+++ b/NvBlast/test/src/TkBaseTest.h
@@ -0,0 +1,467 @@
+/*
+* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef TKBASETEST_H
+#define TKBASETEST_H
+
+
+#include "NvBlastTk.h"
+#include "NvBlastTkActor.h"
+
+#include "BlastBaseTest.h"
+
+#include "NvBlastExtDamageShaders.h"
+
+#include "NvBlastIndexFns.h"
+#include "NvBlastProfiler.h"
+#include "TestProfiler.h"
+
+#include "PxAllocatorCallback.h"
+#include "PxErrorCallback.h"
+#include "PxCpuDispatcher.h"
+#include "PxTask.h"
+#include "PxFoundation.h"
+#include "PxFoundationVersion.h"
+
+#include <thread>
+#include <algorithm>
+#include <queue>
+#include <mutex>
+#include <condition_variable>
+#include <atomic>
+
+
+#define USE_PHYSX_DISPATCHER 0
+
+#if USE_PHYSX_DISPATCHER
+#include "PxDefaultCpuDispatcher.h"
+#endif
+
+
+using namespace Nv::Blast;
+using namespace physx;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Helpers
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NV_INLINE void ExpectArrayMatch(TkObject** arr0, size_t size0, TkObject** arr1, size_t size1)
+{
+ EXPECT_TRUE(size0 == size1);
+ std::set<TkObject*> set0(arr0, arr0 + size0);
+ std::set<TkObject*> set1(arr1, arr1 + size1);
+ EXPECT_TRUE(set0 == set1);
+}
+
+class TestCpuDispatcher : public physx::PxCpuDispatcher
+{
+ struct SharedContext
+ {
+ std::queue<PxBaseTask*> workQueue;
+ std::condition_variable cv;
+ std::mutex mutex;
+ std::atomic<bool> quit;
+ };
+
+ void submitTask(PxBaseTask& task) override
+ {
+ if (m_threads.size() > 0)
+ {
+ std::unique_lock<std::mutex> lk(m_context.mutex);
+ m_context.workQueue.push(&task);
+ lk.unlock();
+ m_context.cv.notify_one();
+ }
+ else
+ {
+ TEST_ZONE_BEGIN(task.getName());
+ task.run();
+ TEST_ZONE_END(task.getName());
+ task.release();
+ }
+ }
+
+ uint32_t getWorkerCount() const override { return (uint32_t)m_threads.size(); }
+
+ static void execute(SharedContext& context)
+ {
+ while (!context.quit)
+ {
+ std::unique_lock<std::mutex> lk(context.mutex);
+ if (!context.workQueue.empty())
+ {
+ PxBaseTask& task = *context.workQueue.front();
+ context.workQueue.pop();
+ lk.unlock();
+ TEST_ZONE_BEGIN(task.getName());
+ task.run();
+ TEST_ZONE_END(task.getName());
+ task.release();
+ }
+ else
+ {
+ // shared variables must be modified under the mutex in order
+ // to correctly publish the modification to the waiting thread
+ context.cv.wait(lk, [&]{ return !context.workQueue.empty() || context.quit; });
+ }
+ }
+ }
+
+ SharedContext m_context;
+ std::vector<std::thread> m_threads;
+
+public:
+ TestCpuDispatcher(uint32_t numWorkers)
+ {
+ m_context.quit = false;
+ for (uint32_t i = 0; i < numWorkers; ++i)
+ {
+ m_threads.push_back(std::thread(execute, std::ref(m_context)));
+ }
+ }
+
+ void release()
+ {
+ std::unique_lock<std::mutex> lk(m_context.mutex);
+ m_context.quit = true;
+ lk.unlock();
+ m_context.cv.notify_all();
+ for (std::thread& t : m_threads)
+ {
+ t.join();
+ }
+ delete this;
+ }
+};
+
+
+struct CSParams
+{
+ CSParams(uint32_t axis_, float coord_) : axis(axis_), coord(coord_) {}
+ uint32_t axis;
+ float coord;
+};
+
+static void CubeSlicer(NvBlastFractureBuffers* outbuf, const NvBlastGraphShaderActor* actor, const NvBlastProgramParams* params)
+{
+ uint32_t bondFractureCount = 0;
+ uint32_t bondFractureCountMax = outbuf->bondFractureCount;
+
+ for (size_t i = 0; i < params->damageDescCount; ++i)
+ {
+ const CSParams& p = (reinterpret_cast<const CSParams*> (params->damageDescBuffer))[i];
+
+ uint32_t currentNodeIndex = actor->firstGraphNodeIndex;
+ while (!Nv::Blast::isInvalidIndex(currentNodeIndex))
+ {
+ for (uint32_t adj = actor->adjacencyPartition[currentNodeIndex]; adj < actor->adjacencyPartition[currentNodeIndex + 1]; ++adj)
+ {
+ if (currentNodeIndex < actor->adjacentNodeIndices[adj])
+ {
+ if (actor->assetBonds[actor->adjacentBondIndices[adj]].centroid[p.axis] == p.coord && bondFractureCount < bondFractureCountMax)
+ {
+ NvBlastBondFractureData& data = outbuf->bondFractures[bondFractureCount++];
+ data.userdata = 0;
+ data.nodeIndex0 = currentNodeIndex;
+ data.nodeIndex1 = actor->adjacentNodeIndices[adj];
+ data.health = 1.0f;
+ }
+ }
+ }
+ currentNodeIndex = actor->graphNodeIndexLinks[currentNodeIndex];
+ }
+ }
+
+ outbuf->bondFractureCount = bondFractureCount;
+ outbuf->chunkFractureCount = 0;
+
+ //printf("slicer outcount %d\n", bondFractureCount);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// TkBaseTest Class
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+template<int FailLevel, int Verbosity>
+class TkBaseTest : public BlastBaseTest<FailLevel, Verbosity>
+{
+public:
+ TkBaseTest() : m_cpuDispatcher(), m_taskman(nullptr), m_foundation(nullptr)
+ {
+ }
+
+ virtual void SetUp() override
+ {
+ m_foundation = PxCreateFoundation(PX_FOUNDATION_VERSION, *this, *this);
+
+ NvBlastProfilerEnablePlatform(true);
+
+#if USE_PHYSX_DISPATCHER
+ PxU32 affinity[] = { 1, 2, 4, 8 };
+ m_cpuDispatcher = PxDefaultCpuDispatcherCreate(4, affinity);
+ m_cpuDispatcher->setRunProfiled(false);
+#else
+ m_cpuDispatcher = new TestCpuDispatcher(4);
+#endif
+
+ m_taskman = PxTaskManager::createTaskManager(*this, m_cpuDispatcher, nullptr);
+ }
+
+ virtual void TearDown() override
+ {
+ m_cpuDispatcher->release();
+ if (m_taskman) m_taskman->release();
+ if (m_foundation) m_foundation->release();
+ }
+
+ void createFramework()
+ {
+ TkFrameworkDesc desc;
+ desc.allocatorCallback = this;
+ desc.errorCallback = this;
+ TkFramework* framework = NvBlastTkFrameworkCreate(desc);
+ EXPECT_TRUE(framework != nullptr);
+ EXPECT_EQ(framework, NvBlastTkFrameworkGet());
+ }
+
+ void releaseFramework()
+ {
+ TkFramework* framework = NvBlastTkFrameworkGet();
+ framework->release();
+ EXPECT_TRUE(NvBlastTkFrameworkGet() == nullptr);
+ }
+
+ void createTestAssets(bool addInternalJoints = false)
+ {
+ const uint8_t cube1BondDescFlags_internalJoints[12] =
+ {
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+ TkAssetDesc::NoFlags,
+
+ TkAssetDesc::BondJointed,
+ TkAssetDesc::BondJointed,
+ TkAssetDesc::BondJointed,
+ TkAssetDesc::BondJointed
+ };
+
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+ TkFramework* framework = NvBlastTkFrameworkGet();
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ TkAssetDesc desc;
+ reinterpret_cast<NvBlastAssetDesc&>(desc) = g_assetDescs[i];
+ desc.bondFlags = addInternalJoints ? cube1BondDescFlags_internalJoints : nullptr;
+ testAssets.push_back(framework->createAsset(desc));
+ EXPECT_TRUE(testAssets[i] != nullptr);
+ }
+ }
+
+ TkAsset* createCubeAsset(size_t maxDepth, size_t width, int32_t supportDepth = -1, bool addInternalJoints = false)
+ {
+ TkFramework* framework = NvBlastTkFrameworkGet();
+ GeneratorAsset cube;
+ TkAssetDesc assetDesc;
+ generateCube(cube, assetDesc, maxDepth, width, supportDepth);
+ std::vector<uint8_t> bondFlags(assetDesc.bondCount);
+ std::fill(bondFlags.begin(), bondFlags.end(), addInternalJoints ? 1 : 0);
+ assetDesc.bondFlags = bondFlags.data();
+ TkAsset* cubeAsset = framework->createAsset(assetDesc);
+ testAssets.push_back(cubeAsset);
+ return cubeAsset;
+ }
+
+ void releaseTestAssets()
+ {
+ for (uint32_t i = 0; i < testAssets.size(); ++i)
+ {
+ testAssets[i]->release();
+ }
+ testAssets.clear();
+ }
+
+ NvBlastExtRadialDamageDesc getRadialDamageDesc(float x, float y, float z, float minRadius = 10.0f, float maxRadius = 10.0f, float compressive = 10.0f)
+ {
+ NvBlastExtRadialDamageDesc desc;
+ desc.position[0] = x;
+ desc.position[1] = y;
+ desc.position[2] = z;
+
+ desc.minRadius = minRadius;
+ desc.maxRadius = maxRadius;
+ desc.compressive = compressive;
+ return desc;
+ }
+
+ NvBlastExtShearDamageDesc getShearDamageDesc(float x, float y, float z, float shearX = 1.0f, float shearY = 0.0f, float shearZ = 0.0f)
+ {
+ NvBlastExtShearDamageDesc desc;
+ desc.position[0] = x;
+ desc.position[1] = y;
+ desc.position[2] = z;
+
+ desc.shear[0] = shearX;
+ desc.shear[1] = shearY;
+ desc.shear[2] = shearZ;
+ return desc;
+ }
+
+ static const NvBlastDamageProgram& getCubeSlicerProgram()
+ {
+ static NvBlastDamageProgram program = { CubeSlicer, nullptr };
+ return program;
+ }
+
+ static const NvBlastDamageProgram& getFalloffProgram()
+ {
+ static NvBlastDamageProgram program = { NvBlastExtFalloffGraphShader, NvBlastExtFalloffSubgraphShader };
+ return program;
+ }
+
+ static const NvBlastDamageProgram& getShearProgram()
+ {
+ static NvBlastDamageProgram program = { NvBlastExtShearGraphShader, NvBlastExtShearSubgraphShader };
+ return program;
+ }
+
+ static const NvBlastExtMaterial* getDefaultMaterial()
+ {
+ static NvBlastExtMaterial material = {
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f
+ };
+ return &material;
+ };
+
+ TkFamily* familySerialization(TkFamily* family);
+
+
+ std::vector<TkAsset*> testAssets;
+
+#if USE_PHYSX_DISPATCHER
+ PxDefaultCpuDispatcher* m_cpuDispatcher;
+#else
+ TestCpuDispatcher* m_cpuDispatcher;
+#endif
+
+ PxTaskManager* m_taskman;
+ PxFoundation* m_foundation;
+};
+
+
+#define TkPxErrorMask (PxErrorCode::eINVALID_PARAMETER | PxErrorCode::eINVALID_OPERATION | PxErrorCode::eOUT_OF_MEMORY | PxErrorCode::eINTERNAL_ERROR | PxErrorCode::eABORT)
+#define TkPxWarningMask (PxErrorCode::eDEBUG_WARNING | PxErrorCode::ePERF_WARNING)
+
+typedef TkBaseTest<NvBlastMessage::Error, 1> TkTestAllowWarnings;
+typedef TkBaseTest<NvBlastMessage::Warning, 1> TkTestStrict;
+
+
+class TestFamilyTracker : public TkEventListener
+{
+public:
+ TestFamilyTracker() {}
+
+ typedef std::pair<TkFamily*, uint32_t> Actor;
+
+ virtual void receive(const TkEvent* events, uint32_t eventCount) override
+ {
+ TEST_ZONE_BEGIN("TestFamilyTracker");
+ for (size_t i = 0; i < eventCount; ++i)
+ {
+ const TkEvent& e = events[i];
+ switch (e.type)
+ {
+ case (TkEvent::Split):
+ {
+ const TkSplitEvent* splitEvent = e.getPayload<TkSplitEvent>();
+ EXPECT_EQ((size_t)1, actors.erase(Actor(splitEvent->parentData.family, splitEvent->parentData.index)));
+ for (size_t i = 0; i < splitEvent->numChildren; ++i)
+ {
+ TkActor* a = splitEvent->children[i];
+ EXPECT_TRUE(actors.insert(Actor(&a->getFamily(), a->getIndex())).second);
+ }
+ break;
+ }
+ case (TkEvent::FractureCommand):
+ {
+ const TkFractureCommands* fracEvent = e.getPayload<TkFractureCommands>();
+ EXPECT_TRUE(!isInvalidIndex(fracEvent->tkActorData.index));
+#if 0
+ printf("chunks broken: %d\n", fracEvent->buffers.chunkFractureCount);
+ printf("bonds broken: %d\n", fracEvent->buffers.bondFractureCount);
+ for (uint32_t t = 0; t < fracEvent->buffers.bondFractureCount; t++)
+ {
+ //printf("%x ", fracEvent->buffers.bondFractures[t].userdata);
+ }
+ //printf("\n");
+#endif
+ break;
+ }
+ case (TkEvent::FractureEvent):
+ {
+ const TkFractureEvents* fracEvent = e.getPayload<TkFractureEvents>();
+ EXPECT_TRUE(!isInvalidIndex(fracEvent->tkActorData.index));
+ break;
+ }
+ case (TkEvent::JointUpdate):
+ {
+ const TkJointUpdateEvent* jointEvent = e.getPayload<TkJointUpdateEvent>();
+ TkJoint* joint = jointEvent->joint;
+ EXPECT_TRUE(joint != nullptr);
+
+ switch (jointEvent->subtype)
+ {
+ case TkJointUpdateEvent::External:
+ EXPECT_TRUE(joints.end() == joints.find(joint)); // We should not have this joint yet
+ joints.insert(joint);
+ break;
+ case TkJointUpdateEvent::Changed:
+ break;
+ case TkJointUpdateEvent::Unreferenced:
+ EXPECT_EQ(1, joints.erase(joint));
+ joint->release();
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ TEST_ZONE_END("TestFamilyTracker");
+ }
+
+ void insertActor(const TkActor* actor)
+ {
+ actors.insert(TestFamilyTracker::Actor(&actor->getFamily(), actor->getIndex()));
+ }
+
+ void eraseActor(const TkActor* actor)
+ {
+ actors.erase(TestFamilyTracker::Actor(&actor->getFamily(), actor->getIndex()));
+ }
+
+ std::set<Actor> actors;
+ std::set<TkJoint*> joints;
+};
+
+
+#endif // #ifndef TKBASETEST_H
diff --git a/NvBlast/test/src/perf/BlastBasePerfTest.h b/NvBlast/test/src/perf/BlastBasePerfTest.h
new file mode 100644
index 0000000..2a4701d
--- /dev/null
+++ b/NvBlast/test/src/perf/BlastBasePerfTest.h
@@ -0,0 +1,374 @@
+/*
+* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef BLASTBASEPERFTEST_H
+#define BLASTBASEPERFTEST_H
+
+
+#include "BlastBaseTest.h"
+#include <fstream>
+
+#include <algorithm>
+#include <map>
+
+
+template<typename T>
+class DataCollection
+{
+public:
+ struct Stats
+ {
+ double m_mean;
+ double m_sdev;
+ double m_min;
+ double m_max;
+
+ Stats()
+ {
+ reset();
+ }
+
+ void reset()
+ {
+ m_mean = 0.0;
+ m_sdev = 0.0;
+ m_min = std::numeric_limits<double>().max();
+ m_max = -std::numeric_limits<double>().max();
+ }
+ };
+
+ struct DataSet
+ {
+ std::vector<T> m_data;
+ Stats m_stats;
+
+ void calculateStats()
+ {
+ m_stats.reset();
+ if (m_data.size() > 0)
+ {
+ if (m_data.size() > 1) // Remove top half of values to eliminate outliers
+ {
+ std::sort(m_data.begin(), m_data.end());
+ m_data.resize(m_data.size() / 2);
+ }
+ for (size_t i = 0; i < m_data.size(); ++i)
+ {
+ m_stats.m_mean += m_data[i];
+ m_stats.m_min = std::min(m_stats.m_min, (double)m_data[i]);
+ m_stats.m_max = std::max(m_stats.m_max, (double)m_data[i]);
+ }
+ m_stats.m_mean /= m_data.size();
+ if (m_data.size() > 1)
+ {
+ for (size_t i = 0; i < m_data.size(); ++i)
+ {
+ m_stats.m_sdev += pow(m_data[i] - m_stats.m_mean, 2);
+ }
+ m_stats.m_sdev = sqrt(m_stats.m_sdev / (m_data.size() - 1));
+ }
+ }
+ }
+ };
+
+ DataSet& getDataSet(const std::string& name)
+ {
+ auto entry = m_lookup.find(name);
+ if (entry != m_lookup.end())
+ {
+ return m_dataSets[entry->second];
+ }
+ m_lookup[name] = m_dataSets.size();
+ m_dataSets.push_back(DataSet());
+ return m_dataSets.back();
+ }
+
+ bool dataSetExists(const std::string& name) const
+ {
+ return m_lookup.find(name) != m_lookup.end();
+ }
+
+ void calculateStats()
+ {
+ for (size_t i = 0; i < m_dataSets.size(); ++i)
+ {
+ m_dataSets[i].calculateStats();
+ }
+ }
+
+ void test(DataCollection<int64_t>& calibration, double relativeThreshold = 0.10, double tickThreshold = 100.0)
+ {
+ for (auto entry = m_lookup.begin(); entry != m_lookup.end(); ++entry)
+ {
+ const std::string& name = entry->first;
+ DataCollection<int64_t>::DataSet& data = m_dataSets[entry->second];
+ data.calculateStats();
+
+ if (!calibration.dataSetExists(name))
+ {
+ FAIL() << "PerfTest is not calibrated!" << std::endl << "Missing DataSet: " << name << std::endl;
+ }
+ const DataCollection<int64_t>::DataSet& cal = calibration.getDataSet(name);
+ const double calMin = cal.m_stats.m_min;
+
+ if (data.m_stats.m_min > (1.0 + relativeThreshold) * calMin && data.m_stats.m_min - calMin > tickThreshold)
+ {
+ std::cout << name << ":" << std::endl;
+ std::cout << "PERF - : Timing (" << data.m_stats.m_min << ") exceeds recorded min (" << calMin << ") by more than allowed relative threshold (" << relativeThreshold*100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl;
+ EXPECT_FALSE(data.m_stats.m_min > (1.0 + relativeThreshold) * calMin && data.m_stats.m_min - calMin > tickThreshold)
+ << name << ":" << std::endl
+ << "PERF - : Timing (" << data.m_stats.m_min << ") exceeds recorded min (" << calMin << ") by more than allowed relative threshold (" << relativeThreshold * 100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl;
+ }
+ else
+ if (data.m_stats.m_min < (1.0 - relativeThreshold) * calMin && data.m_stats.m_min - calMin < -tickThreshold)
+ {
+ std::cout << name << ":" << std::endl;
+ std::cout << "PERF + : Timing (" << data.m_stats.m_min << ") is less than the recorded min (" << calMin << ") by more than the relative threshold (" << relativeThreshold * 100 << "%) and absolute threshold (" << tickThreshold << " ticks)." << std::endl;
+ }
+ }
+ }
+
+ size_t size() const
+ {
+ return m_dataSets.size();
+ }
+
+ void clear()
+ {
+ m_lookup.clear();
+ m_dataSets.clear();
+ }
+
+ template<class S>
+ friend std::istream& operator >> (std::istream& stream, DataCollection<S>& c);
+
+ template<class S>
+ friend std::ostream& operator << (std::ostream& stream, const DataCollection<S>& c);
+
+private:
+ std::map<std::string, size_t> m_lookup;
+ std::vector< DataSet > m_dataSets;
+};
+
+template<typename T>
+std::istream& operator >> (std::istream& stream, DataCollection<T>& c)
+{
+ std::string name;
+ while (!stream.eof())
+ {
+ std::getline(stream >> std::ws, name);
+ typename DataCollection<T>::DataSet& dataSet = c.getDataSet(name);
+ stream >> dataSet.m_stats.m_mean >> dataSet.m_stats.m_sdev >> dataSet.m_stats.m_min >> dataSet.m_stats.m_max >> std::ws;
+ }
+ return stream;
+}
+
+template<typename T>
+std::ostream& operator << (std::ostream& stream, const DataCollection<T>& c)
+{
+ for (auto entry = c.m_lookup.begin(); entry != c.m_lookup.end(); ++entry)
+ {
+ const std::string& name = entry->first;
+ stream << name.c_str() << std::endl;
+ const typename DataCollection<T>::DataSet& data = c.m_dataSets[entry->second];
+ stream << data.m_stats.m_mean << " " << data.m_stats.m_sdev << " " << data.m_stats.m_min << " " << data.m_stats.m_max << std::endl;
+ }
+ return stream;
+}
+
+
+static const char* getPlatformSuffix()
+{
+#if NV_WIN32
+ return "win32";
+#elif NV_WIN64
+ return "win64";
+#elif NV_XBOXONE
+ return "xb1";
+#elif NV_PS4
+ return "ps4";
+#elif NV_LINUX
+ #if NV_X64
+ return "linux64";
+ #else
+ return "linux32";
+ #endif
+#else
+ return "gen";
+#endif
+}
+
+static const char* getPlatformRoot()
+{
+#if NV_PS4
+ return "/app0/";
+#elif NV_XBOXONE
+ return "G:/";
+#else
+ return "../../";
+#endif
+}
+
+static std::string defaultRelativeDataPath()
+{
+ const char* dataDir = "test/data/";
+
+ std::string rootDir = getPlatformRoot();
+ return rootDir + dataDir + getPlatformSuffix() + "/";
+}
+
+class PerfTestEngine
+{
+public:
+ PerfTestEngine(const char* collectionName) : m_calibrate(false)
+ {
+ m_filename = defaultRelativeDataPath() + std::string(collectionName) + "_" + getPlatformSuffix() + ".cal";
+
+ auto argvs = testing::internal::GetArgvs();
+ size_t argCount = argvs.size();
+
+ for (size_t argNum = 0; argNum < argCount; ++argNum)
+ {
+ if (argvs[argNum] == "-calibrate")
+ {
+ m_calibrate = true;
+ }
+ else
+ if (argvs[argNum] == "-calPath")
+ {
+ if (++argNum < argCount)
+ {
+ m_filename = argvs[argNum];
+ }
+ }
+ }
+
+ if (!m_calibrate)
+ {
+ std::ifstream in;
+ in.open(m_filename);
+ if (in.is_open())
+ {
+ std::string name;
+ std::getline(in, name); // Eat header
+ std::getline(in, name); // Eat header (2 lines)
+ in >> m_dataCalibration;
+ in.close();
+ }
+ m_calibrate = m_dataCalibration.size() == 0;
+ }
+
+ if (m_calibrate)
+ {
+ std::ofstream out;
+ out.open(m_filename);
+ if (out.is_open())
+ {
+ out << "Format: timing name (whole line)" << std::endl << "timing mean s.d. min max" << std::endl; // Header (2 lines)
+ out.close();
+ }
+ }
+
+ if (m_calibrate)
+ {
+ std::cout << "******** Calibration Mode ********\n";
+ }
+ else
+ {
+ std::cout << "******** Test Mode ********\n";
+ std::cout << "Read calibration data from " << m_filename << std::endl;
+ }
+ }
+
+ void endTest()
+ {
+ if (m_calibrate)
+ {
+ m_dataTempCollection.calculateStats();
+ std::ofstream out;
+ out.open(m_filename, std::ofstream::app);
+ if (out.is_open())
+ {
+ out << m_dataTempCollection;
+ out.close();
+ std::cout << "Calibration stats written to " << m_filename << std::endl;
+ }
+ else
+ {
+ std::cout << "Failed to open calibration file " << m_filename << ". Stats not written." << std::endl;
+ FAIL() << "Failed to open calibration file " << m_filename << ". Stats not written." << std::endl;
+ }
+ }
+ else
+ {
+ m_dataTempCollection.test(m_dataCalibration);
+ }
+ m_dataTempCollection.clear();
+ }
+
+ void reportData(const std::string& name, int64_t data)
+ {
+ m_dataTempCollection.getDataSet(name).m_data.push_back(data);
+ }
+
+private:
+ std::string m_filename;
+ bool m_calibrate;
+ DataCollection<int64_t> m_dataTempCollection;
+ DataCollection<int64_t> m_dataCalibration;
+};
+
+
+template<int FailLevel, int Verbosity>
+class BlastBasePerfTest : public BlastBaseTest<FailLevel, Verbosity>
+{
+public:
+ /**
+ This function allows to create/destroy and get PerfTestEngine in local static variable (works header only).
+ It allows to have PeftTestEngine alive through life span of gtest TestCase.
+ */
+ static PerfTestEngine* getEngineDeadOrAlive(bool alive = true)
+ {
+ static PerfTestEngine* engine = nullptr;
+ if (alive && !engine)
+ {
+ engine = new PerfTestEngine(::testing::UnitTest::GetInstance()->current_test_case()->name());
+ }
+ else if (!alive && engine)
+ {
+ delete engine;
+ engine = nullptr;
+ }
+ return engine;
+ }
+
+ static void SetUpTestCase()
+ {
+ getEngineDeadOrAlive();
+ }
+
+ static void TearDownTestCase()
+ {
+ getEngineDeadOrAlive(false);
+ }
+
+ void TearDown() override
+ {
+ getEngineDeadOrAlive()->endTest();
+ }
+
+ void reportData(const std::string& name, int64_t data)
+ {
+ getEngineDeadOrAlive()->reportData(name, data);
+ }
+};
+
+
+#endif // #ifndef BLASTBASEPERFTEST_H
diff --git a/NvBlast/test/src/perf/SolverPerfTests.cpp b/NvBlast/test/src/perf/SolverPerfTests.cpp
new file mode 100644
index 0000000..8a53c97
--- /dev/null
+++ b/NvBlast/test/src/perf/SolverPerfTests.cpp
@@ -0,0 +1,211 @@
+#include "BlastBasePerfTest.h"
+#include "TestAssets.h"
+#include "NvBlastExtDamageShaders.h"
+#include <memory>
+
+
+static void blast
+(
+ std::set<NvBlastActor*>& actorsToDamage,
+ GeneratorAsset* testAsset,
+ GeneratorAsset::Vec3 localPos,
+ float minRadius, float maxRadius,
+ float compressiveDamage,
+ NvBlastTimers& timers
+)
+{
+ std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */
+ std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */
+ chunkEvents.resize(testAsset->solverChunks.size());
+ bondEvents.resize(testAsset->solverBonds.size());
+
+ NvBlastExtRadialDamageDesc damage[] = {
+ {
+ compressiveDamage,
+ { localPos.x, localPos.y, localPos.z },
+ minRadius,
+ maxRadius
+ }
+ };
+
+ NvBlastProgramParams programParams =
+ {
+ &damage,
+ 1,
+ nullptr
+ };
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ nullptr
+ };
+
+ std::vector<char> splitScratch;
+ std::vector<NvBlastActor*> newActors(testAsset->solverChunks.size());
+
+ size_t totalNewActorsCount = 0;
+ for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();)
+ {
+ NvBlastActor* actor = *k;
+
+ NvBlastFractureBuffers events = { (uint32_t)bondEvents.size(), (uint32_t)chunkEvents.size(), bondEvents.data(), chunkEvents.data() };
+
+ NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, &timers);
+ NvBlastActorApplyFracture(&events, actor, &events, nullptr, &timers);
+
+ bool removeActor = false;
+
+ if (events.bondFractureCount + events.chunkFractureCount > 0)
+ {
+ splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = &newActors[totalNewActorsCount];
+ const size_t bufferSize = newActors.size() - totalNewActorsCount;
+ const size_t newActorsCount = NvBlastActorSplit(&result, actor, (uint32_t)bufferSize, splitScratch.data(), nullptr, &timers);
+ totalNewActorsCount += newActorsCount;
+ removeActor = newActorsCount > 0;
+ }
+
+ if (removeActor)
+ {
+ k = actorsToDamage.erase(k);
+ }
+ else
+ {
+ ++k;
+ }
+ }
+
+ for (size_t i = 0; i < totalNewActorsCount; ++i)
+ {
+ actorsToDamage.insert(newActors[i]);
+ }
+}
+
+typedef BlastBasePerfTest<NvBlastMessage::Warning, 1> BlastBasePerfTestStrict;
+
+class PerfTest : public BlastBasePerfTestStrict
+{
+public:
+ void damageLeafSupportActors(const char* testName, uint32_t assetCount, uint32_t familyCount, uint32_t damageCount)
+ {
+ const float relativeDamageRadius = 0.2f;
+ const float compressiveDamage = 1.0f;
+ const uint32_t minChunkCount = 100;
+ const uint32_t maxChunkCount = 10000;
+
+ srand(0);
+
+ for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum)
+ {
+ CubeAssetGenerator::Settings settings;
+ settings.extents = GeneratorAsset::Vec3(1, 1, 1);
+ CubeAssetGenerator::DepthInfo depthInfo;
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1);
+ depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags;
+ settings.depths.push_back(depthInfo);
+ uint32_t chunkCount = 1;
+ while (chunkCount < minChunkCount)
+ {
+ uint32_t chunkMul;
+ do
+ {
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4));
+ chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z);
+ } while (chunkMul == 1);
+ if (chunkCount*chunkMul > maxChunkCount)
+ {
+ break;
+ }
+ chunkCount *= chunkMul;
+ settings.depths.push_back(depthInfo);
+ settings.extents = settings.extents * depthInfo.slicesPerAxis;
+ }
+ settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support
+
+ // Make largest direction unit size
+ settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z)));
+
+ // Create asset
+ GeneratorAsset testAsset;
+ CubeAssetGenerator::generate(testAsset, settings);
+
+ NvBlastAssetDesc desc;
+ desc.chunkDescs = &testAsset.solverChunks[0];
+ desc.chunkCount = (uint32_t)testAsset.solverChunks.size();
+ desc.bondDescs = &testAsset.solverBonds[0];
+ desc.bondCount = (uint32_t)testAsset.solverBonds.size();
+
+ {
+ std::vector<char> scratch;
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog));
+ void* mem = alloc(NvBlastGetAssetMemorySize(&desc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, &desc, &scratch[0], messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // Generate familes
+ for (uint32_t familyNum = 0; familyNum < familyCount; ++familyNum)
+ {
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = 1.0f;
+ actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* mem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(mem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ EXPECT_TRUE(family != nullptr);
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // Generate damage
+ std::set<NvBlastActor*> actors;
+ actors.insert(actor);
+ for (uint32_t damageNum = 0; damageNum < damageCount; ++damageNum)
+ {
+ GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f);
+
+ NvBlastTimers timers;
+ NvBlastTimersReset(&timers);
+ blast(actors, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage, timers);
+ const std::string timingName = std::string(testName) + " asset " + std::to_string(assetNum) + " family " + std::to_string(familyNum) + " damage " + std::to_string(damageNum);
+ BlastBasePerfTestStrict::reportData(timingName + " material", timers.material);
+ BlastBasePerfTestStrict::reportData(timingName + " fracture", timers.fracture);
+ BlastBasePerfTestStrict::reportData(timingName + " island", timers.island);
+ BlastBasePerfTestStrict::reportData(timingName + " partition", timers.partition);
+ BlastBasePerfTestStrict::reportData(timingName + " visibility", timers.visibility);
+ }
+
+ // Release remaining actors
+ std::for_each(actors.begin(), actors.end(), [](NvBlastActor* a){ NvBlastActorDeactivate(a, messageLog); });
+ actors.clear();
+
+ free(family);
+ }
+
+ // Release asset data
+ free(asset);
+ }
+ }
+ }
+};
+
+
+// Tests
+TEST_F(PerfTest, DamageLeafSupportActorsTestVisibility)
+{
+ const int trialCount = 1000;
+ std::cout << "Trial (of " << trialCount << "): ";
+ for (int trial = 1; trial <= trialCount; ++trial)
+ {
+ if (trial % 100 == 0)
+ {
+ std::cout << trial << ".. ";
+ std::cout.flush();
+ }
+ damageLeafSupportActors(test_info_->name(), 4, 4, 5);
+ }
+ std::cout << "done." << std::endl;
+}
diff --git a/NvBlast/test/src/unit/APITests.cpp b/NvBlast/test/src/unit/APITests.cpp
new file mode 100644
index 0000000..c2a4a04
--- /dev/null
+++ b/NvBlast/test/src/unit/APITests.cpp
@@ -0,0 +1,1354 @@
+#include "BlastBaseTest.h"
+#include "NvBlastIndexFns.h"
+#include "NvBlastExtDamageShaders.h"
+
+#include <algorithm>
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Utils / Tests Common
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+using namespace Nv::Blast;
+
+class APITest : public BlastBaseTest < NvBlastMessage::Error, 1 >
+{
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Tests
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+TEST_F(APITest, Basic)
+{
+ // create asset
+ const NvBlastAssetDesc& assetDesc = g_assetDescs[0];
+
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ NvBlastExtRadialDamageDesc damage[] = {
+ {
+ 10.0f, // compressive
+ { 0.0f, 0.0f, 0.0f }, // position
+ 4.0f, // min radius - maximum damage
+ 6.0f // max radius - zero damage
+ } // linear falloff
+ };
+
+ NvBlastBondFractureData outFracture[12]; /*num lower-support chunks + bonds?*/
+
+ NvBlastFractureBuffers events;
+ events.bondFractureCount = 12;
+ events.bondFractures = outFracture;
+ events.chunkFractureCount = 0;
+ events.chunkFractures = nullptr;
+
+ NvBlastProgramParams programParams;
+ programParams.damageDescCount = 1;
+ programParams.damageDescBuffer = &damage;
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ nullptr
+ };
+
+ NvBlastActorGenerateFracture(&events, actor, program, &programParams, messageLog, nullptr);
+ NvBlastActorApplyFracture(&events, actor, &events, messageLog, nullptr);
+ EXPECT_EQ(12, events.bondFractureCount);
+
+ NvBlastActor* newActors[8]; /* num lower-support chunks? plus space for deletedActor */
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors;
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, 8, scratch.data(), messageLog, nullptr);
+ EXPECT_EQ(8, newActorsCount);
+ EXPECT_EQ(true, result.deletedActor == actor);
+
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+ free(family);
+ free(asset);
+}
+
+TEST_F(APITest, DamageBondsCompressive)
+{
+ const size_t bondsCount = 6;
+
+ const NvBlastChunkDesc c_chunks[8] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 7 }
+ };
+
+ const NvBlastBondDesc c_bonds[bondsCount] =
+ {
+ { { 1, 2 }, { { -1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { -1.0f, 0.0f, 0.0f }, 1.0f, { -1.0f, 2.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -2.0f, 1.0f, 0.0f }, 0 } },
+ { { 4, 5 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -2.0f, -1.0f, 0.0f }, 0 } },
+ { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { -1.0f, -2.0f, 0.0f }, 0 } },
+ { { 6, 7 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, -2.0f, 0.0f }, 0 } }
+ };
+
+ // create asset
+ const NvBlastAssetDesc assetDesc = { 8, c_chunks, bondsCount, c_bonds };
+
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // get graph nodes check
+ std::vector<uint32_t> graphNodeIndices;
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr));
+ uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr);
+ EXPECT_EQ(graphNodesCount, 7);
+
+ NvBlastExtRadialDamageDesc damage = {
+ 1.0f, // compressive
+ { 4.0f, 2.0f, 0.0f }, // position
+ 4.0f, // min radius - maximum damage
+ 6.0f // max radius - zero damage
+ }; // linear falloff
+
+ NvBlastBondFractureData outCommands[bondsCount] = {
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0 },
+ };
+
+ NvBlastFractureBuffers commands = {
+ 6, 0, outCommands, nullptr
+ };
+
+ NvBlastProgramParams programParams;
+ programParams.damageDescCount = 1;
+ programParams.damageDescBuffer = &damage;
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ nullptr
+ };
+
+ NvBlastActorGenerateFracture(&commands, actor, program, &programParams, messageLog, nullptr);
+
+ ASSERT_EQ(3, commands.bondFractureCount);
+ ASSERT_EQ(0, commands.chunkFractureCount);
+
+ // node indices in _graph_ chunks
+ NvBlastBondFractureData expectedCommand[] = {
+ { 0, 0, 1, 1.0f },
+ { 0, 1, 2, 0.5f },
+ { 0, 5, 6, 0.5f }
+ };
+
+ for (int i = 0; i < 3; i++)
+ {
+ EXPECT_EQ(expectedCommand[i].nodeIndex0, outCommands[i].nodeIndex0);
+ EXPECT_EQ(expectedCommand[i].nodeIndex1, outCommands[i].nodeIndex1);
+ EXPECT_EQ(expectedCommand[i].health, outCommands[i].health);
+ }
+
+ const bool actorReleaseResult = NvBlastActorDeactivate(actor, messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ free(family);
+ free(asset);
+}
+
+TEST_F(APITest, DirectFractureKillsChunk)
+{
+ // 1--2
+ // | |
+ // 3--4 <-- kill 4
+
+ const NvBlastChunkDesc c_chunks[9] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 4, NvBlastChunkDesc::NoFlags, 8 },
+ };
+
+ const NvBlastBondDesc c_bonds[4] =
+ {
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, +1.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, -1.0f, 0.0f }, 0 } },
+ { { 1, 3 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { +1.0f, 0.0f, 0.0f }, 0 } },
+ };
+
+ NvBlastAssetDesc assetDesc;
+ assetDesc.chunkCount = 9;
+ assetDesc.chunkDescs = c_chunks;
+ assetDesc.bondCount = 4;
+ assetDesc.bondDescs = c_bonds;
+
+ // create asset
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ NvBlastChunkFractureData fractureCmd;
+ fractureCmd.chunkIndex = 4;
+ fractureCmd.health = 1.0f;
+
+ NvBlastFractureBuffers commands = { 0, 1, nullptr, &fractureCmd };
+
+ NvBlastChunkFractureData fractureEvt;
+ NvBlastFractureBuffers events = { 0, 1, nullptr, &fractureEvt };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ EXPECT_EQ(1, events.chunkFractureCount);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(5, newActorsCount);
+ EXPECT_EQ(actor, result.deletedActor);
+ // check newActors contain original actor
+ EXPECT_TRUE(std::any_of(newActors.begin(), newActors.end(), [&](const NvBlastActor* a) { return actor == a; }));
+
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+ free(family);
+ free(asset);
+}
+
+TEST_F(APITest, DirectFractureKillsIslandRootChunk)
+{
+ // 1--2 <-- kill 1
+ // | |
+ // 3--4
+
+ const NvBlastChunkDesc c_chunks[9] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 8 },
+ };
+
+ const NvBlastBondDesc c_bonds[4] =
+ {
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, +1.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, -1.0f, 0.0f }, 0 } },
+ { { 1, 3 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { -1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 4 }, { { 0.0f, -1.0f, 0.0f }, 1.0f, { +1.0f, 0.0f, 0.0f }, 0 } },
+ };
+
+ NvBlastAssetDesc assetDesc;
+ assetDesc.chunkCount = 9;
+ assetDesc.chunkDescs = c_chunks;
+ assetDesc.bondCount = 4;
+ assetDesc.bondDescs = c_bonds;
+
+ // create asset
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ NvBlastChunkFractureData fractureCmd;
+ fractureCmd.chunkIndex = 1;
+ fractureCmd.health = 1.0f;
+
+ NvBlastFractureBuffers commands = { 0, 1, nullptr, &fractureCmd };
+
+ NvBlastChunkFractureData fractureEvt;
+ NvBlastFractureBuffers events = { 0, 1, nullptr, &fractureEvt };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ EXPECT_EQ(1, events.chunkFractureCount);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(5, newActorsCount);
+ EXPECT_EQ(actor, result.deletedActor);
+ // check if newActors don't contain original actor
+ EXPECT_TRUE(!std::any_of(newActors.begin(), newActors.end(), [&](const NvBlastActor* a) { return actor == a; }));
+
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+ free(family);
+ free(asset);
+}
+
+TEST_F(APITest, SubsupportFracture)
+{
+ const NvBlastAssetDesc& assetDesc = g_assetDescs[1]; // cube with subsupport
+
+ // create asset with chunk map
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // first set of fracture commands
+ NvBlastChunkFractureData f1 = { 0, 1, 2.0f };
+ NvBlastChunkFractureData f3 = { 0, 3, 0.5f };
+ NvBlastChunkFractureData f5 = { 0, 5, 1.0f };
+ NvBlastChunkFractureData f7 = { 0, 7, 1.0f };
+
+ std::vector<NvBlastChunkFractureData> chunkFractureData;
+ chunkFractureData.reserve(assetDesc.chunkCount);
+ chunkFractureData.push_back(f1);
+ chunkFractureData.push_back(f3);
+ chunkFractureData.push_back(f5);
+ chunkFractureData.push_back(f7);
+ ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity());
+ ASSERT_EQ(4, chunkFractureData.size());
+
+ NvBlastFractureBuffers target = { 0, static_cast<uint32_t>(chunkFractureData.capacity()), nullptr, chunkFractureData.data() };
+ {
+ NvBlastFractureBuffers events = target;
+ NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() };
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ ASSERT_EQ(4 + 8, events.chunkFractureCount); // all requested chunks take damage, and the children of one of them
+ }
+
+ // re-apply same set of commands
+ chunkFractureData.clear();
+ chunkFractureData.reserve(assetDesc.chunkCount);
+ chunkFractureData.push_back(f1);
+ chunkFractureData.push_back(f3);
+ chunkFractureData.push_back(f5);
+ chunkFractureData.push_back(f7);
+ ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity());
+ ASSERT_EQ(4, chunkFractureData.size());
+
+ {
+ NvBlastFractureBuffers events = target;
+ NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() };
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ ASSERT_EQ(1, events.chunkFractureCount); // f3 has broken the chunk
+ }
+
+ // fracture all support chunks
+ // the chunks from the previous fractures must not be reported again (since they are all broken already)
+ NvBlastChunkFractureData f2 = { 0, 2, 2.0f }; // will damage chunk and children
+ NvBlastChunkFractureData f4 = { 0, 4, 0.5f }; // will damage chunk without creating children on split
+ NvBlastChunkFractureData f6 = { 0, 6, 2.0f }; // will damage chunk and children
+ NvBlastChunkFractureData f8 = { 0, 8, 1.0f }; // will damage chunk
+
+ chunkFractureData.clear();
+ chunkFractureData.reserve(assetDesc.chunkCount);
+ chunkFractureData.push_back(f1);
+ chunkFractureData.push_back(f2);
+ chunkFractureData.push_back(f3);
+ chunkFractureData.push_back(f4);
+ chunkFractureData.push_back(f5);
+ chunkFractureData.push_back(f6);
+ chunkFractureData.push_back(f7);
+ chunkFractureData.push_back(f8);
+ ASSERT_EQ(assetDesc.chunkCount, chunkFractureData.capacity());
+ ASSERT_EQ(8, chunkFractureData.size());
+
+ NvBlastFractureBuffers events = target;
+ {
+ NvBlastFractureBuffers commands = { 0, static_cast<uint32_t>(chunkFractureData.size()), nullptr, chunkFractureData.data() };
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ ASSERT_EQ(4 + 8 + 8, events.chunkFractureCount); // the new fracture commands all apply, plus two of them damage their children too
+ }
+
+ for (size_t i = 0; i < events.chunkFractureCount; i++)
+ {
+ const uint32_t chunkIndex = events.chunkFractures[i].chunkIndex;
+
+ ASSERT_TRUE(chunkIndex != 1);
+ ASSERT_TRUE(chunkIndex != 3);
+ ASSERT_TRUE(chunkIndex != 5);
+ ASSERT_TRUE(chunkIndex != 7);
+
+ // literal values come from g_cube2ChunkDescs
+ bool isInSupportRange = chunkIndex <= 8 && chunkIndex >= 1;
+ bool isChildOfTwo = chunkIndex <= 24 && chunkIndex >= 17;
+ bool isChildOfSix = chunkIndex <= 56 && chunkIndex >= 49;
+
+ ASSERT_TRUE(isInSupportRange || isChildOfTwo || isChildOfSix);
+ }
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, messageLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), messageLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(64 - 8 + 1, newActorsCount);
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+ free(family);
+ free(asset);
+}
+
+static bool hasWarned = false;
+static void myLog(int type, const char* msg, const char* file, int line)
+{
+ BlastBaseTest<-1, 0>::messageLog(type, msg, file, line);
+ hasWarned = true;
+}
+#define EXPECT_WARNING EXPECT_TRUE(hasWarned); hasWarned=false;
+#define EXPECT_NO_WARNING EXPECT_FALSE(hasWarned); hasWarned=false;
+
+TEST_F(APITest, FractureNoEvents)
+{
+ static const uint32_t GUARD = 0xb1a57;
+
+ const uint32_t chunksCount = 17;
+ const NvBlastChunkDesc c_chunks[chunksCount] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 },
+ };
+
+ const NvBlastBondDesc c_bonds[3] =
+ {
+ // chunks, normal, area, centroid, userdata
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } },
+ };
+
+ NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds };
+
+ // create asset with chunk map
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ std::vector<NvBlastChunkFractureData> cfData;
+ cfData.resize(0 + 1);
+ cfData[cfData.size() - 1].userdata = GUARD;
+ std::vector<NvBlastBondFractureData> bfData;
+
+ NvBlastChunkFractureData command[] =
+ {
+ { 0, 1, 10.0f },
+ { 0, 2, 10.0f },
+ };
+
+ NvBlastFractureBuffers commands = { 0, 2, nullptr, command };
+ NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr);
+
+ EXPECT_NO_WARNING; // events can be null
+ EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(9, newActorsCount);
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+
+ free(family);
+ free(asset);
+
+ EXPECT_NO_WARNING;
+}
+
+TEST_F(APITest, FractureBufferLimits)
+{
+ static const uint32_t GUARD = 0xb1a57;
+
+ const uint32_t chunksCount = 17;
+ const NvBlastChunkDesc c_chunks[chunksCount] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 },
+ };
+
+ const NvBlastBondDesc c_bonds[3] =
+ {
+ // chunks, normal, area, centroid, userdata
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } },
+ };
+
+ NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds };
+ {
+ // create asset with chunk map
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ for (uint32_t i = 0; i < 14; i++)
+ {
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ std::vector<NvBlastChunkFractureData> cfData;
+ cfData.resize(i + 1);
+ cfData[cfData.size() - 1].userdata = GUARD;
+ std::vector<NvBlastBondFractureData> bfData;
+
+ NvBlastChunkFractureData command[] =
+ {
+ { 0, 1, 10.0f },
+ { 0, 2, 10.0f },
+ };
+
+ NvBlastFractureBuffers commands = { 0, 2, nullptr, command };
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr);
+
+ EXPECT_WARNING;
+ EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata);
+ EXPECT_EQ(i, events.chunkFractureCount);
+ for (uint32_t i = 0; i < events.chunkFractureCount; i++)
+ {
+ EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata);
+ }
+
+ EXPECT_TRUE(NvBlastActorDeactivate(actor, myLog));
+ free(family);
+ }
+
+ {
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ std::vector<NvBlastChunkFractureData> cfData;
+ cfData.resize(14 + 1);
+ cfData[cfData.size() - 1].userdata = GUARD;
+ std::vector<NvBlastBondFractureData> bfData;
+
+ NvBlastChunkFractureData command[] =
+ {
+ { 0, 1, 10.0f },
+ { 0, 2, 10.0f },
+ };
+
+ NvBlastFractureBuffers commands = { 0, 2, nullptr, command };
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr);
+
+ EXPECT_NO_WARNING;
+ EXPECT_EQ(14, events.chunkFractureCount);
+ for (uint32_t i = 0; i < events.chunkFractureCount; i++)
+ {
+ EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata);
+ }
+ ASSERT_EQ(GUARD, cfData[cfData.size() - 1].userdata);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(9, newActorsCount);
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+
+ free(family);
+ }
+
+ free(asset);
+ }
+ EXPECT_NO_WARNING;
+}
+
+TEST_F(APITest, FractureBufferLimitsInSitu)
+{
+ static const uint32_t GUARD = 0xb1a57;
+
+ const uint32_t chunksCount = 17;
+ const NvBlastChunkDesc c_chunks[chunksCount] =
+ {
+ // cenroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 },
+
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 },
+ };
+
+ const NvBlastBondDesc c_bonds[3] =
+ {
+ // chunks, normal, area, centroid, userdata
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 0.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 3.0f, 0.0f, 0.0f }, 0 } },
+ };
+
+ NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 3, c_bonds };
+ {
+ // create asset with chunk map
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ for (uint32_t i = 0; i < 14 - 2; i++)
+ {
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ std::vector<NvBlastChunkFractureData> cfData;
+ cfData.resize(2 + i + 1);
+
+ std::vector<NvBlastBondFractureData> bfData;
+
+ cfData[0].userdata = 0;
+ cfData[0].chunkIndex = 1;
+ cfData[0].health = 10.0f;
+
+ cfData[1].userdata = 0;
+ cfData[1].chunkIndex = 2;
+ cfData[1].health = 10.0f;
+
+ cfData[2 + i].userdata = GUARD;
+
+ NvBlastFractureBuffers commands = { 0, 2, nullptr, cfData.data() };
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr);
+
+ EXPECT_WARNING;
+ EXPECT_EQ(GUARD, cfData[cfData.size() - 1].userdata);
+ EXPECT_EQ(2 + i, events.chunkFractureCount);
+ for (uint32_t i = 0; i < events.chunkFractureCount; i++)
+ {
+ EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata);
+ }
+
+ EXPECT_TRUE(NvBlastActorDeactivate(actor, myLog));
+
+ free(family);
+ }
+
+ {
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ std::vector<NvBlastChunkFractureData> cfData;
+ cfData.resize(14 + 1);
+ cfData[cfData.size() - 1].userdata = GUARD;
+ std::vector<NvBlastBondFractureData> bfData;
+
+ cfData[0].userdata = 0;
+ cfData[0].chunkIndex = 1;
+ cfData[0].health = 10.0f;
+
+ cfData[1].userdata = 0;
+ cfData[1].chunkIndex = 2;
+ cfData[1].health = 10.0f;
+
+ cfData[14].userdata = GUARD;
+
+ NvBlastFractureBuffers commands = { 0, 2, nullptr, cfData.data() };
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bfData.size()), static_cast<uint32_t>(cfData.size()) - 1, bfData.data(), cfData.data() };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, myLog, nullptr);
+
+ EXPECT_NO_WARNING;
+ EXPECT_EQ(14, events.chunkFractureCount);
+ for (uint32_t i = 0; i < events.chunkFractureCount; i++)
+ {
+ EXPECT_EQ(events.chunkFractures[i].chunkIndex, events.chunkFractures[i].userdata);
+ }
+ ASSERT_EQ(GUARD, cfData[cfData.size() - 1].userdata);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+ newActors.resize(newActorsCount);
+
+ EXPECT_EQ(9, newActorsCount);
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], myLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+ free(family);
+ }
+
+ free(asset);
+ }
+ EXPECT_NO_WARNING;
+}
+
+#if 0
+TEST(APITest, UserChunkMap)
+{
+ for (int i = 0; i < 2; ++i)
+ {
+ // Choose descriptor list
+ const NvBlastAssetDesc* descs = nullptr;
+ size_t size = 0;
+ switch (i)
+ {
+ case 0:
+ descs = g_assetDescs;
+ size = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+ break;
+ case 1:
+ descs = g_assetDescsMissingCoverage;
+ size = sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]);
+ break;
+ default:
+ continue;
+ }
+
+ // Iterate over list
+ for (size_t j = 0; j < size; ++j)
+ {
+ // Create asset
+ const NvBlastAssetDesc* desc = descs + j;
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(desc));
+ std::vector<uint32_t> chunkMap(desc->chunkCount);
+ NvBlastAsset* asset = NvBlastCreateAsset(&chunkMap[0], desc, alignedAlloc<malloc>, scratch.data(), nullptr);
+ EXPECT_TRUE(asset);
+
+ // Test map
+ Nv::Blast::Asset& a = static_cast<Nv::Blast::Asset&>(asset);
+ uint32_t supportChunkCount = 0;
+ uint32_t subsupportChunkCount = 0;
+ for (uint32_t i = 0; i < desc->chunkCount; ++i)
+ {
+ const uint32_t map = chunkMap[i];
+ if (Nv::Blast::isInvalidIndex(map))
+ {
+ continue;
+ }
+ else if (map < a.m_firstSubsupportChunkIndex)
+ {
+ EXPECT_LT(map, asset.m_graph.m_nodeCount);
+ ++supportChunkCount;
+ }
+ else
+ {
+ EXPECT_LT(map, asset.m_chunkCount);
+ EXPECT_GE(map, asset.m_graph.m_nodeCount);
+ ++subsupportChunkCount;
+ }
+ }
+ EXPECT_EQ(supportChunkCount, asset.m_graph.m_nodeCount);
+ EXPECT_EQ(subsupportChunkCount, a.getLowerSupportChunkCount() - asset.m_graph.m_nodeCount);
+
+ // Release asset
+ NvBlastAssetRelease(asset, free, nullptr);
+ }
+ }
+}
+#endif
+
+TEST_F(APITest, NoBondsSausage)
+{
+ // create asset
+ const NvBlastChunkDesc c_chunks[4] =
+ {
+ // centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 2 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 3 }
+ };
+
+ NvBlastAssetDesc assetDesc;
+ assetDesc.chunkCount = 4;
+ assetDesc.chunkDescs = c_chunks;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, messageLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), messageLog);
+ const NvBlastChunk* chunks = NvBlastAssetGetChunks(asset, messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // check visible chunk
+ {
+ EXPECT_EQ(NvBlastActorGetVisibleChunkCount(actor, messageLog), 1);
+ uint32_t chunkIndex;
+ NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, messageLog);
+ EXPECT_EQ(chunks[chunkIndex].userData, 0);
+ }
+
+ // damage
+ NvBlastExtRadialDamageDesc damage[] = {
+ {
+ 10.0f, // compressive
+ { 0.0f, 0.0f, 0.0f }, // position
+ 4.0f, // min radius - maximum damage
+ 6.0f // max radius - zero damage
+ } // linear falloff
+ };
+
+ NvBlastBondFractureData outBondFracture[2];
+ NvBlastChunkFractureData outChunkFracture[2];
+
+ NvBlastFractureBuffers events;
+ events.bondFractureCount = 2;
+ events.bondFractures = outBondFracture;
+ events.chunkFractureCount = 2;
+ events.chunkFractures = outChunkFracture;
+
+ NvBlastProgramParams programParams;
+ programParams.damageDescCount = 1;
+ programParams.damageDescBuffer = &damage;
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ NvBlastExtFalloffSubgraphShader
+ };
+
+ NvBlastActorGenerateFracture(&events, actor, program, &programParams, messageLog, nullptr);
+ NvBlastActorApplyFracture(&events, actor, &events, messageLog, nullptr);
+ EXPECT_EQ(0, events.bondFractureCount);
+ EXPECT_EQ(1, events.chunkFractureCount);
+
+ // split
+ NvBlastActor* newActors[8]; /* num lower-support chunks? plus space for deletedActor */
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors;
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, 8, scratch.data(), messageLog, nullptr);
+ EXPECT_EQ(1, newActorsCount);
+ EXPECT_EQ(true, result.deletedActor == actor);
+
+ // check visible chunk
+ {
+ EXPECT_EQ(NvBlastActorGetVisibleChunkCount(result.newActors[0], messageLog), 1);
+ uint32_t chunkIndex;
+ NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, result.newActors[0], messageLog);
+ EXPECT_EQ(chunks[chunkIndex].userData, 3);
+ }
+
+ // release all
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ const bool actorReleaseResult = NvBlastActorDeactivate(result.newActors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+
+ free(family);
+ free(asset);
+}
+
+TEST_F(APITest, SplitOnlyWhenNecessary)
+{
+ static const uint32_t GUARD = 0xb1a57;
+
+ const uint32_t chunksCount = 17;
+ const NvBlastChunkDesc c_chunks[chunksCount] =
+ {
+ // centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 5 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 1, NvBlastChunkDesc::NoFlags, 6 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 7 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 2, NvBlastChunkDesc::NoFlags, 8 },
+
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 5, NvBlastChunkDesc::NoFlags, 9 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 5, NvBlastChunkDesc::NoFlags, 10 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 6, NvBlastChunkDesc::NoFlags, 11 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 6, NvBlastChunkDesc::NoFlags, 12 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 7, NvBlastChunkDesc::NoFlags, 13 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 7, NvBlastChunkDesc::NoFlags, 14 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 8, NvBlastChunkDesc::NoFlags, 15 },
+ { { 0.0f, 0.0f, 0.0f }, 0.0f, 8, NvBlastChunkDesc::NoFlags, 16 },
+ };
+
+ const NvBlastBondDesc c_bonds[4] =
+ {
+ // chunks, normal, area, centroid, userdata
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } },
+ { { 1, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 1.0f, 0.0f, 0.0f }, 0 } }
+ };
+
+ NvBlastAssetDesc assetDesc = { chunksCount, c_chunks, 4, c_bonds };
+
+ // create asset with chunk map
+ std::vector<char> scratch((size_t)NvBlastGetRequiredScratchForCreateAsset(&assetDesc, myLog));
+ void* amem = alloc(NvBlastGetAssetMemorySize(&assetDesc, myLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &assetDesc, scratch.data(), myLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ // create actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, myLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, myLog);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, myLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, scratch.data(), myLog);
+ EXPECT_TRUE(actor != nullptr);
+
+
+ // damage health only (expect no split)
+ {
+ NvBlastBondFractureData command[] =
+ {
+ { 0, 0, 1, 0.99f },
+ { 0, 1, 2, 0.50f },
+ { 0, 2, 3, 0.01f }
+ };
+
+ NvBlastFractureBuffers commands = { 3, 0, command, nullptr };
+ NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+
+ EXPECT_EQ(0, newActorsCount);
+ EXPECT_EQ(nullptr, result.deletedActor);
+
+ EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog));
+ uint32_t chunkIndex;
+ NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog);
+ EXPECT_EQ(0, chunkIndex);
+ }
+
+ // break 1 bond (expect no split)
+ {
+ NvBlastBondFractureData command[] =
+ {
+ { 0, 0, 2, 10.0f },
+ };
+
+ NvBlastFractureBuffers commands = { 1, 0, command, nullptr };
+ NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+
+ EXPECT_EQ(0, newActorsCount);
+ EXPECT_EQ(nullptr, result.deletedActor);
+
+ EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog));
+ uint32_t chunkIndex;
+ NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog);
+ EXPECT_EQ(0, chunkIndex);
+ }
+
+ // split in 4
+ std::vector<NvBlastActor*> actors;
+ {
+ NvBlastBondFractureData command[] =
+ {
+ { 0, 0, 1, 10.0f },
+ { 0, 1, 2, 10.0f },
+ { 0, 2, 3, 10.0f }
+ };
+
+ NvBlastFractureBuffers commands = { 3, 0, command, nullptr };
+ NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+
+ EXPECT_EQ(4, newActorsCount);
+ EXPECT_EQ(actor, result.deletedActor);
+
+ actors.insert(actors.begin(), result.newActors, result.newActors + newActorsCount);
+ }
+
+ // damage chunk's health only (expect no split)
+ {
+ for (NvBlastActor* actor : actors)
+ {
+ uint32_t chunkToDamage;
+ NvBlastActorGetVisibleChunkIndices(&chunkToDamage, 1, actor, myLog);
+
+ NvBlastChunkFractureData command[] =
+ {
+ { 0, chunkToDamage, 0.9f },
+ };
+
+ NvBlastFractureBuffers commands = { 0, 1, nullptr, command };
+ NvBlastActorApplyFracture(nullptr, actor, &commands, myLog, nullptr);
+
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, myLog));
+ std::vector<NvBlastActor*> newActors(NvBlastActorGetMaxActorCountForSplit(actor, myLog));
+ NvBlastActorSplitEvent result;
+ result.deletedActor = nullptr;
+ result.newActors = newActors.data();
+ size_t newActorsCount = NvBlastActorSplit(&result, actor, static_cast<uint32_t>(newActors.size()), scratch.data(), myLog, nullptr);
+
+ EXPECT_EQ(0, newActorsCount);
+ EXPECT_EQ(nullptr, result.deletedActor);
+
+ EXPECT_EQ(1, NvBlastActorGetVisibleChunkCount(actor, myLog));
+ uint32_t chunkIndex;
+ NvBlastActorGetVisibleChunkIndices(&chunkIndex, 1, actor, myLog);
+ EXPECT_EQ(chunkToDamage, chunkIndex);
+ }
+ }
+
+ for (NvBlastActor* actor : actors)
+ {
+ NvBlastActorDeactivate(actor, myLog);
+ }
+
+ free(family);
+ free(asset);
+
+ EXPECT_NO_WARNING;
+}
+
+#if NV_WINDOWS_FAMILY
+#include <windows.h>
+TEST_F(APITest,CExportsNoNameMangling)
+{
+
+ //
+ // tests the lib-link-free approach using unmangled names (extern "C")
+ //
+
+#if NV_WIN32
+#if NV_DEBUG
+ const char* dllName = "NvBlastDebug_x86.dll";
+#elif NV_CHECKED
+ const char* dllName = "NvBlastChecked_x86.dll";
+#elif NV_PROFILE
+ const char* dllName = "NvBlastProfile_x86.dll";
+#else
+ const char* dllName = "NvBlast_x86.dll";
+#endif
+#elif NV_WIN64
+#if NV_DEBUG
+ const char* dllName = "NvBlastDebug_x64.dll";
+#elif NV_CHECKED
+ const char* dllName = "NvBlastChecked_x64.dll";
+#elif NV_PROFILE
+ const char* dllName = "NvBlastProfile_x64.dll";
+#else
+ const char* dllName = "NvBlast_x64.dll";
+#endif
+#endif
+
+ HMODULE dllHandle = LoadLibrary(TEXT(dllName));
+ DWORD error = GetLastError();
+ ASSERT_TRUE(dllHandle != nullptr);
+
+
+ // Asset functions
+ typedef size_t(*NvBlastGetRequiredScratchForCreateAsset)(const NvBlastAssetDesc* desc);
+ typedef size_t(*NvBlastGetAssetMemorySize)(const NvBlastAssetDesc* desc);
+ typedef NvBlastAsset*(*NvBlastCreateAsset)(void* mem, const NvBlastAssetDesc* desc, void* scratch, NvBlastLog logFn);
+
+ NvBlastGetRequiredScratchForCreateAsset assetCreateRequiredScratch = (NvBlastGetRequiredScratchForCreateAsset)GetProcAddress(dllHandle, TEXT("NvBlastGetRequiredScratchForCreateAsset"));
+ ASSERT_TRUE(assetCreateRequiredScratch != nullptr);
+
+ NvBlastGetAssetMemorySize assetGetMemorySize = (NvBlastGetAssetMemorySize)GetProcAddress(dllHandle, TEXT("NvBlastGetAssetMemorySize"));
+ ASSERT_TRUE(assetGetMemorySize != nullptr);
+
+ NvBlastCreateAsset assetCreate = (NvBlastCreateAsset)GetProcAddress(dllHandle, TEXT("NvBlastCreateAsset"));
+ ASSERT_TRUE(assetCreate != nullptr);
+
+ // Family functions
+ typedef NvBlastFamily* (*NvBlastAssetCreateFamily)(void* mem, const NvBlastAsset* asset, NvBlastLog logFn);
+ typedef size_t(*NVBLASTASSETGETFAMILYMEMORYSIZE)(const NvBlastAsset* asset);
+
+ NVBLASTASSETGETFAMILYMEMORYSIZE familyGetMemorySize = (NVBLASTASSETGETFAMILYMEMORYSIZE)GetProcAddress(dllHandle, TEXT("NvBlastAssetGetFamilyMemorySize"));
+ ASSERT_TRUE(familyGetMemorySize != nullptr);
+
+ NvBlastAssetCreateFamily familyCreate = (NvBlastAssetCreateFamily)GetProcAddress(dllHandle, TEXT("NvBlastAssetCreateFamily"));
+ ASSERT_TRUE(familyCreate != nullptr);
+
+
+ // Actor functions
+ typedef size_t(*NvBlastFamilyGetRequiredScratchForCreateFirstActor)(const NvBlastFamily* family);
+ typedef NvBlastActor* (*NvBlastFamilyCreateFirstActor)(NvBlastFamily* family, const NvBlastActorDesc* desc, void* scratch, NvBlastLog logFn);
+ typedef bool(*NVBLASTACTORDEACTIVATE)(NvBlastActor* actor);
+
+ NvBlastFamilyGetRequiredScratchForCreateFirstActor actorcreaterequiredscratch = (NvBlastFamilyGetRequiredScratchForCreateFirstActor)GetProcAddress(dllHandle, TEXT("NvBlastFamilyGetRequiredScratchForCreateFirstActor"));
+ ASSERT_TRUE(actorcreaterequiredscratch != nullptr);
+
+ NvBlastFamilyCreateFirstActor actorCreate = (NvBlastFamilyCreateFirstActor)GetProcAddress(dllHandle, TEXT("NvBlastFamilyCreateFirstActor"));
+ ASSERT_TRUE(actorCreate != nullptr);
+
+ NVBLASTACTORDEACTIVATE actorRelease = (NVBLASTACTORDEACTIVATE)GetProcAddress(dllHandle, TEXT("NvBlastActorDeactivate"));
+ ASSERT_TRUE(actorRelease != nullptr);
+
+
+ const NvBlastChunkDesc c_chunks[] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 0 },
+ };
+
+ NvBlastAssetDesc assetDesc;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.chunkCount = 4;
+ assetDesc.chunkDescs = c_chunks;
+
+ NvBlastAsset* asset;
+ {
+ size_t requiredsize = assetCreateRequiredScratch(&assetDesc);
+ std::vector<char>scratch(requiredsize);
+ void* mem = alloc(assetGetMemorySize(&assetDesc));
+ asset = assetCreate(mem, &assetDesc, scratch.data(), myLog);
+ ASSERT_TRUE(asset != nullptr);
+ }
+
+ void* fmem = alloc(familyGetMemorySize(asset));
+ NvBlastFamily* family = familyCreate(fmem, asset, myLog);
+
+ {
+ NvBlastActorDesc actorD;
+ actorD.initialBondHealths = actorD.initialSupportChunkHealths = nullptr;
+ actorD.uniformInitialBondHealth = actorD.uniformInitialLowerSupportChunkHealth = 1.0f;
+
+ size_t requiredsize = actorcreaterequiredscratch(family);
+ std::vector<char>scratch(requiredsize);
+ NvBlastActor* actor = actorCreate(family, &actorD, scratch.data(), myLog);
+ ASSERT_TRUE(actor != nullptr);
+
+ ASSERT_TRUE(actorRelease(actor));
+ }
+
+ free(family);
+ free(asset);
+
+ EXPECT_NO_WARNING;
+}
+#endif
diff --git a/NvBlast/test/src/unit/ActorTests.cpp b/NvBlast/test/src/unit/ActorTests.cpp
new file mode 100644
index 0000000..12dc19c
--- /dev/null
+++ b/NvBlast/test/src/unit/ActorTests.cpp
@@ -0,0 +1,1059 @@
+#include "BlastBaseTest.h"
+#include "AssetGenerator.h"
+
+#include <map>
+#include <algorithm>
+
+#include "NvBlastActor.h"
+#include "NvBlastExtDamageShaders.h"
+
+
+static bool chooseRandomGraphNodes(uint32_t* g, uint32_t count, const Nv::Blast::Actor& actor)
+{
+ const uint32_t graphNodeCount = actor.getGraphNodeCount();
+
+ if (graphNodeCount < count)
+ {
+ return false;
+ }
+
+ std::vector<uint32_t> graphNodeIndices(graphNodeCount);
+ uint32_t* index = graphNodeIndices.data();
+ for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i ; ++i)
+ {
+ *index++ = (uint32_t)i;
+ }
+ struct UserDataSorter
+ {
+ UserDataSorter(const Nv::Blast::Actor& actor) : m_asset(*actor.getAsset()) {}
+
+ bool operator () (uint32_t i0, uint32_t i1) const
+ {
+ const uint32_t c0 = m_asset.m_graph.getChunkIndices()[i0];
+ const uint32_t c1 = m_asset.m_graph.getChunkIndices()[i1];
+ return m_asset.getChunks()[c0].userData < m_asset.getChunks()[c1].userData;
+ }
+
+ const Nv::Blast::Asset& m_asset;
+ } userDataSorter(actor);
+ std::sort(graphNodeIndices.data(), graphNodeIndices.data() + graphNodeCount, userDataSorter);
+
+#if 0
+ std::vector<uint32_t> descUserData(graphNodeCount);
+ for (uint32_t i = 0; i < graphNodeCount; ++i)
+ {
+ descUserData[i] = actor.getAsset()->m_chunks[actor.getAsset()->m_graph.m_chunkIndices[graphNodeIndices[i]]].userData;
+ }
+#endif
+
+ uint32_t t = 0;
+ uint32_t m = 0;
+ for (uint32_t i = 0; i < graphNodeCount && m < count; ++i, ++t)
+ {
+ NVBLAST_ASSERT(t < graphNodeCount);
+ if (t >= graphNodeCount)
+ {
+ break;
+ }
+ const float U = (float)rand()/RAND_MAX; // U is uniform random number in [0,1)
+ if ((graphNodeCount - t)*U < count - m)
+ {
+ g[m++] = graphNodeIndices[i];
+ }
+ }
+
+ return m == count;
+}
+
+
+static void blast(std::set<NvBlastActor*>& actorsToDamage, GeneratorAsset* testAsset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage)
+{
+ std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */
+ std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */
+ chunkEvents.resize(testAsset->solverChunks.size());
+ bondEvents.resize(testAsset->solverBonds.size());
+
+
+ std::vector<char> splitScratch;
+ std::vector<NvBlastActor*> newActorsBuffer(testAsset->solverChunks.size());
+
+ NvBlastExtRadialDamageDesc damage[] = {
+ {
+ compressiveDamage,
+ { localPos.x, localPos.y, localPos.z },
+ minRadius,
+ maxRadius
+ }
+ };
+
+ NvBlastProgramParams programParams =
+ {
+ &damage,
+ 1,
+ nullptr
+ };
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ nullptr
+ };
+
+ size_t totalNewActorsCount = 0;
+ for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();)
+ {
+ NvBlastActor* actor = *k;
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bondEvents.size()), static_cast<uint32_t>(chunkEvents.size()), bondEvents.data(), chunkEvents.data() };
+
+ NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, nullptr);
+ NvBlastActorApplyFracture(&events, actor, &events, nullptr, nullptr);
+ bool removeActor = false;
+
+ if (events.bondFractureCount + events.chunkFractureCount > 0)
+ {
+ NvBlastActorSplitEvent splitEvent;
+ splitEvent.newActors = &newActorsBuffer.data()[totalNewActorsCount];
+ uint32_t newActorSize = (uint32_t)(newActorsBuffer.size() - totalNewActorsCount);
+
+ splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr));
+ const size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, newActorSize, splitScratch.data(), nullptr, nullptr);
+ totalNewActorsCount += newActorsCount;
+ removeActor = splitEvent.deletedActor != NULL;
+ }
+
+ if (removeActor)
+ {
+ k = actorsToDamage.erase(k);
+ }
+ else
+ {
+ ++k;
+ }
+ }
+
+ for (size_t i = 0; i < totalNewActorsCount; ++i)
+ {
+ actorsToDamage.insert(newActorsBuffer[i]);
+ }
+}
+
+
+template<int FailLevel, int Verbosity>
+class ActorTest : public BlastBaseTest<FailLevel, Verbosity>
+{
+public:
+ ActorTest()
+ {
+
+ }
+
+ static void messageLog(int type, const char* msg, const char* file, int line)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::messageLog(type, msg, file, line);
+ }
+
+ static void* alloc(size_t size)
+ {
+ return BlastBaseTest<FailLevel, Verbosity>::alloc(size);
+ }
+
+ static void free(void* mem)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::free(mem);
+ }
+
+ NvBlastAsset* buildAsset(const NvBlastAssetDesc& desc)
+ {
+ // fix desc if wrong order or missing coverage first
+ NvBlastAssetDesc fixedDesc = desc;
+ std::vector<NvBlastChunkDesc> chunkDescs(desc.chunkDescs, desc.chunkDescs + desc.chunkCount);
+ std::vector<NvBlastBondDesc> bondDescs(desc.bondDescs, desc.bondDescs + desc.bondCount);
+ std::vector<uint32_t> chunkReorderMap(desc.chunkCount);
+ std::vector<char> scratch(desc.chunkCount * sizeof(NvBlastChunkDesc));
+ NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), fixedDesc.chunkCount, scratch.data(), messageLog);
+ NvBlastReorderAssetDescChunks(chunkDescs.data(), fixedDesc.chunkCount, bondDescs.data(), fixedDesc.bondCount, chunkReorderMap.data(), scratch.data(), messageLog);
+ fixedDesc.chunkDescs = chunkDescs.data();
+ fixedDesc.bondDescs = bondDescs.empty() ? nullptr : bondDescs.data();
+
+ // create asset
+ m_scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&fixedDesc, messageLog));
+ void* mem = alloc(NvBlastGetAssetMemorySize(&fixedDesc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, &fixedDesc, &m_scratch[0], messageLog);
+ EXPECT_TRUE(asset != nullptr);
+ return asset;
+ }
+
+ void buildAssets()
+ {
+ m_assets.resize(getAssetDescCount());
+ for (uint32_t i = 0; i < m_assets.size(); ++i)
+ {
+ m_assets[i] = buildAsset(g_assetDescs[i]);
+ }
+ }
+
+ NvBlastActor* instanceActor(const NvBlastAsset& asset)
+ {
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(&asset, nullptr));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, &asset, nullptr);
+ std::vector<char> scratch((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog);
+ EXPECT_TRUE(actor != nullptr);
+ return actor;
+ }
+
+ void instanceActors()
+ {
+ m_actors.resize(m_assets.size());
+ for (uint32_t i = 0; i < m_actors.size(); ++i)
+ {
+ m_actors[i] = instanceActor(*m_assets[i]);
+ }
+ }
+
+ void releaseActors()
+ {
+ for (uint32_t i = 0; i < m_actors.size(); ++i)
+ {
+ NvBlastFamily* family = NvBlastActorGetFamily(m_actors[i], messageLog);
+
+ const bool actorReleaseResult = NvBlastActorDeactivate(m_actors[i], messageLog);
+ EXPECT_TRUE(actorReleaseResult);
+
+ free(family);
+ }
+ }
+
+ void destroyAssets()
+ {
+ for (uint32_t i = 0; i < m_assets.size(); ++i)
+ {
+ free(m_assets[i]);
+ }
+ }
+
+ void instanceAndPartitionRecursively
+ (
+ const NvBlastAsset& asset,
+ bool partitionToSubsupport,
+ void (*preSplitTest)(const Nv::Blast::Actor&, NvBlastLog),
+ void (*postSplitTest)(const std::vector<Nv::Blast::Actor*>&, uint32_t, uint32_t, bool)
+ )
+ {
+ const Nv::Blast::Asset& solverAsset = *static_cast<const Nv::Blast::Asset*>(&asset);
+
+ std::vector<Nv::Blast::Actor*> actors;
+ std::vector<Nv::Blast::Actor*> buffer(NvBlastAssetGetChunkCount(&asset, messageLog));
+
+ // Instance the first actor from the asset
+ actors.push_back(static_cast<Nv::Blast::Actor*>(instanceActor(asset)));
+
+ NvBlastFamily* family = NvBlastActorGetFamily(actors[0], messageLog);
+
+ const uint32_t supportChunkCount = actors[0]->getAsset()->m_graph.m_nodeCount;
+ const uint32_t leafChunkCount = actors[0]->getAsset()->m_leafChunkCount;
+
+ // Now randomly partition the actors in the array, and keep going until we're down to single support chunks
+ bool canFracture = true;
+
+ while (canFracture)
+ {
+ canFracture = false;
+
+ for (uint32_t actorToPartition = 0; actorToPartition < actors.size(); ++actorToPartition)
+ {
+ Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[actorToPartition];
+ if (a == nullptr)
+ {
+ continue;
+ }
+
+ m_scratch.reserve((size_t)NvBlastActorGetRequiredScratchForSplit(a, messageLog));
+
+ if (preSplitTest)
+ {
+ preSplitTest(*a, nullptr);
+ }
+
+ const bool singleLowerSupportChunk = a->getGraphNodeCount() <= 1;
+ uint32_t newActorCount = 0;
+
+ for (int damageNum = 0; newActorCount < 2 && damageNum < 100; ++damageNum) // Avoid infinite loops
+ {
+ if (!singleLowerSupportChunk)
+ {
+ uint32_t g[2];
+ chooseRandomGraphNodes(g, 2, *a);
+ const uint32_t bondIndex = solverAsset.m_graph.findBond(g[0], g[1]);
+ if (bondIndex != Nv::Blast::invalidIndex<uint32_t>())
+ {
+ a->damageBond(g[0], g[1], bondIndex, 100.0f);
+ a->findIslands(&m_scratch[0]);
+ }
+ }
+ else
+ if (!partitionToSubsupport)
+ {
+ continue;
+ }
+
+ // Split actor
+ newActorCount = a->partition((Nv::Blast::Actor**)&buffer[0], (uint32_t)buffer.size(), messageLog);
+
+ if (newActorCount >= 2)
+ {
+ actors[actorToPartition] = nullptr;
+ }
+ }
+
+ if (newActorCount > 1)
+ {
+ canFracture = true;
+ }
+
+ for (uint32_t i = 0; i < newActorCount; ++i)
+ {
+ actors.push_back(buffer[i]);
+ buffer[i]->updateVisibleChunksFromGraphNodes();
+ }
+ }
+ }
+
+ if (postSplitTest)
+ {
+ postSplitTest(actors, leafChunkCount, supportChunkCount, partitionToSubsupport);
+ }
+
+ for (auto actor : actors)
+ {
+ if (actor)
+ actor->release();
+ }
+
+ free(family);
+ }
+
+ static void recursivePartitionPostSplitTestCounts(const std::vector<Nv::Blast::Actor*>& actors, uint32_t leafChunkCount, uint32_t supportChunkCount, bool partitionToSubsupport)
+ {
+ // Test to see that all actors are split down to single support chunks
+ uint32_t remainingActorCount = 0;
+ for (uint32_t i = 0; i < actors.size(); ++i)
+ {
+ Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[i];
+ if (a == nullptr)
+ {
+ continue;
+ }
+
+ ++remainingActorCount;
+
+ NVBLAST_ASSERT(1 == a->getVisibleChunkCount());
+ EXPECT_EQ(1, a->getVisibleChunkCount());
+ if (!partitionToSubsupport)
+ {
+ EXPECT_EQ(1, a->getGraphNodeCount());
+ }
+
+ const bool actorReleaseResult = NvBlastActorDeactivate(actors[i], nullptr);
+ EXPECT_TRUE(actorReleaseResult);
+ }
+
+ if (partitionToSubsupport)
+ {
+ EXPECT_EQ(leafChunkCount, remainingActorCount);
+ }
+ else
+ {
+ EXPECT_EQ(supportChunkCount, remainingActorCount);
+ }
+ }
+
+ static void testActorVisibleChunks(const Nv::Blast::Actor& actor, NvBlastLog)
+ {
+ const Nv::Blast::Asset& asset = *actor.getAsset();
+ const NvBlastChunk* chunks = asset.getChunks();
+
+ if (actor.isSubSupportChunk())
+ {
+ EXPECT_EQ(1, actor.getVisibleChunkCount());
+
+ const uint32_t firstVisibleChunkIndex = (uint32_t)Nv::Blast::Actor::VisibleChunkIt(actor);
+
+ EXPECT_EQ(actor.getIndex() - asset.m_graph.m_nodeCount, firstVisibleChunkIndex - asset.m_firstSubsupportChunkIndex);
+
+ // Make sure the visible chunk is subsupport
+ // Array of support flags
+ std::vector<bool> isSupport(asset.m_chunkCount, false);
+ for (uint32_t i = 0; i < asset.m_graph.m_nodeCount; ++i)
+ {
+ isSupport[asset.m_graph.getChunkIndices()[i]] = true;
+ }
+
+ // Climb hierarchy to find support chunk
+ uint32_t chunkIndex = firstVisibleChunkIndex;
+ while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>())
+ {
+ if (isSupport[chunkIndex])
+ {
+ break;
+ }
+ chunkIndex = chunks[chunkIndex].parentChunkIndex;
+ }
+
+ EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex));
+ }
+ else
+ {
+ // Array of visibility flags
+ std::vector<bool> isVisible(asset.m_chunkCount, false);
+ for (Nv::Blast::Actor::VisibleChunkIt i = actor; (bool)i; ++i)
+ {
+ isVisible[(uint32_t)i] = true;
+ }
+
+ // Mark visible nodes representing graph chunks
+ std::vector<bool> visibleChunkFound(asset.m_chunkCount, false);
+
+ // Make sure every graph chunk is represented by a visible chunk
+ for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i; ++i)
+ {
+ const uint32_t graphNodeIndex = (uint32_t)i;
+ uint32_t chunkIndex = asset.m_graph.getChunkIndices()[graphNodeIndex];
+ // Climb hierarchy to find visible chunk
+ while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>())
+ {
+ // Check that chunk owners are accurate
+ EXPECT_EQ(actor.getIndex(), actor.getFamilyHeader()->getChunkActorIndices()[chunkIndex]);
+ if (isVisible[chunkIndex])
+ {
+ visibleChunkFound[chunkIndex] = true;
+ break;
+ }
+ chunkIndex = chunks[chunkIndex].parentChunkIndex;
+ }
+ EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex));
+ }
+
+ // Check that all visible chunks are accounted for
+ for (uint32_t i = 0; i < asset.m_chunkCount; ++i)
+ {
+ EXPECT_EQ(visibleChunkFound[i], isVisible[i]);
+ }
+
+ // Make sure that, if all siblings are intact, they are invisible
+ for (uint32_t i = 0; i < asset.m_chunkCount; ++i)
+ {
+ bool allIntact = true;
+ bool noneVisible = true;
+ if (chunks[i].firstChildIndex < asset.getUpperSupportChunkCount()) // Do not check subsupport
+ {
+ for (uint32_t j = chunks[i].firstChildIndex; j < chunks[i].childIndexStop; ++j)
+ {
+ allIntact = allIntact && actor.getFamilyHeader()->getChunkActorIndices()[j] == actor.getIndex();
+ noneVisible = noneVisible && !isVisible[j];
+ }
+ EXPECT_TRUE(!allIntact || noneVisible);
+ }
+ }
+ }
+ }
+
+ static void recursivePartitionPostSplitTestVisibleChunks(const std::vector<Nv::Blast::Actor*>& actors, uint32_t leafChunkCount, uint32_t supportChunkCount, bool partitionToSubsupport)
+ {
+ for (uint32_t i = 0; i < actors.size(); ++i)
+ {
+ Nv::Blast::Actor* a = (Nv::Blast::Actor*)actors[i];
+ if (a == nullptr)
+ {
+ continue;
+ }
+
+ testActorVisibleChunks(*a, nullptr);
+ }
+ }
+
+ void partitionActorsToSupportChunks
+ (
+ uint32_t assetDescCount,
+ const NvBlastAssetDesc* assetDescs,
+ void(*preSplitTest)(const Nv::Blast::Actor&, NvBlastLog),
+ void(*postSplitTest)(const std::vector<Nv::Blast::Actor*>&, uint32_t, uint32_t, bool),
+ bool partitionToSubsupport
+ )
+ {
+ srand(0);
+
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ // Create an asset
+ NvBlastAsset* asset = buildAsset(assetDescs[i]);
+
+ // Perform repeated partitioning
+ instanceAndPartitionRecursively(*asset, partitionToSubsupport, preSplitTest, postSplitTest);
+
+ // Free the asset
+ free(asset);
+ }
+ }
+
+ static void compareFamilies(const NvBlastFamily* family1, const NvBlastFamily* family2, size_t size, NvBlastLog logFn)
+ {
+ const char* block1 = reinterpret_cast<const char*>(family1);
+ const char* block2 = reinterpret_cast<const char*>(family2);
+#if 0
+ EXPECT_EQ(0, memcmp(block1, block2, size));
+#else
+ bool diffFound = false;
+ size_t startDiff = 0;
+ for (size_t i = 0; i < size; ++i)
+ {
+ if (block1[i] != block2[i])
+ {
+ diffFound = true;
+ startDiff = i;
+ break;
+ }
+ }
+ if (!diffFound)
+ {
+ return;
+ }
+ size_t endDiff = startDiff;
+ for (size_t i = size; i--;)
+ {
+ if (block1[i] != block2[i])
+ {
+ endDiff = i;
+ break;
+ }
+ }
+ std::ostringstream msg;
+ msg << "Block deserialization does not match current block in position range [" << startDiff << ", " << endDiff << "].";
+ logFn(NvBlastMessage::Error, msg.str().c_str(), __FILE__, __LINE__);
+#endif
+ }
+
+ static void testActorBlockSerialize(std::vector<NvBlastActor*>& actors, NvBlastLog logFn)
+ {
+ if (actors.size())
+ {
+ const NvBlastFamily* family = NvBlastActorGetFamily(actors[0], logFn);
+ const uint32_t size = NvBlastFamilyGetSize(family, logFn);
+ s_storage.insert(s_storage.end(), (char*)family, (char*)family + size);
+ }
+ }
+
+ static void testActorBlockDeserialize(std::vector<NvBlastActor*>& actors, NvBlastLog logFn)
+ {
+ if (actors.size())
+ {
+ EXPECT_LT(s_curr, s_storage.size());
+ const NvBlastFamily* family = reinterpret_cast<NvBlastFamily*>(&s_storage[s_curr]);
+ const uint32_t size = NvBlastFamilyGetSize(family, logFn);
+ EXPECT_LE(s_curr + size, s_storage.size());
+ s_curr += size;
+ const NvBlastFamily* actorFamily = NvBlastActorGetFamily(actors[0], logFn);
+ // Family may contain different assets pointers, copy into new family block and set the same asset before comparing
+ Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]);
+ const Nv::Blast::Asset* solverAsset = a.getAsset();
+ std::vector<char> storageFamilyCopy((char*)family, (char*)family + size);
+ NvBlastFamily* storageFamily = reinterpret_cast<NvBlastFamily*>(storageFamilyCopy.data());
+ NvBlastFamilySetAsset(storageFamily, solverAsset, logFn);
+ {
+ const uint32_t actorCountExpected = NvBlastFamilyGetActorCount(storageFamily, logFn);
+ std::vector<NvBlastActor*> blockActors(actorCountExpected);
+ const uint32_t actorCountReturned = NvBlastFamilyGetActors(&blockActors[0], actorCountExpected, storageFamily, logFn);
+ EXPECT_EQ(actorCountExpected, actorCountReturned);
+ }
+ compareFamilies(storageFamily, actorFamily, size, logFn);
+ }
+ }
+
+ // Serialize all actors and then deserialize back into a new family in a random order, and compare with the original family
+ static void testActorSerializationNewFamily(std::vector<NvBlastActor*>& actors, NvBlastLog logFn)
+ {
+ if (actors.size() == 0)
+ {
+ return;
+ }
+
+ Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]);
+ const Nv::Blast::Asset* solverAsset = a.getAsset();
+
+ const uint32_t serSizeBound = NvBlastAssetGetActorSerializationSizeUpperBound(solverAsset, logFn);
+
+ std::vector< std::vector<char> > streams(actors.size());
+ for (size_t i = 0; i < actors.size(); ++i)
+ {
+ const uint32_t serSize = NvBlastActorGetSerializationSize(actors[i], logFn);
+ EXPECT_GE(serSizeBound, serSize);
+ std::vector<char>& stream = streams[i];
+ stream.resize(serSize);
+ const uint32_t bytesWritten = NvBlastActorSerialize(&stream[0], serSize, actors[i], logFn);
+ EXPECT_EQ(serSize, bytesWritten);
+ }
+
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(solverAsset, logFn));
+ NvBlastFamily* newFamily = NvBlastAssetCreateFamily(fmem, solverAsset, logFn);
+
+ std::vector<size_t> order(actors.size());
+ for (size_t i = 0; i < order.size(); ++i)
+ {
+ order[i] = i;
+ }
+ std::random_shuffle(order.begin(), order.end());
+
+ for (size_t i = 0; i < actors.size(); ++i)
+ {
+ NvBlastActor* newActor = NvBlastFamilyDeserializeActor(newFamily, &streams[order[i]][0], logFn);
+ EXPECT_TRUE(newActor != nullptr);
+ }
+
+ const NvBlastFamily* oldFamily = NvBlastActorGetFamily(&a, logFn);
+ compareFamilies(oldFamily, newFamily, NvBlastFamilyGetSize(oldFamily, logFn), logFn);
+
+ free(newFamily);
+ }
+
+ // Copy the family and then serialize some subset of actors, deleting them afterwards.
+ // Then, deserialize back into the block and compare the original and new families.
+ static void testActorSerializationPartialBlock(std::vector<NvBlastActor*>& actors, NvBlastLog logFn)
+ {
+ if (actors.size() <= 1)
+ {
+ return;
+ }
+
+ Nv::Blast::Actor& a = *static_cast<Nv::Blast::Actor*>(actors[0]);
+ const Nv::Blast::Asset* solverAsset = a.getAsset();
+
+ const NvBlastFamily* oldFamily = NvBlastActorGetFamily(&a, logFn);
+ const uint32_t size = NvBlastFamilyGetSize(oldFamily, logFn);
+ std::vector<char> buffer((char*)oldFamily, (char*)oldFamily + size);
+ NvBlastFamily* familyCopy = reinterpret_cast<NvBlastFamily*>(&buffer[0]);
+
+ const uint32_t serCount = 1 + (rand() % actors.size() - 1);
+
+ const uint32_t actorCount = NvBlastFamilyGetActorCount(familyCopy, logFn);
+ std::vector<NvBlastActor*> actorsRemaining(actorCount);
+ const uint32_t actorsInFamily = NvBlastFamilyGetActors(&actorsRemaining[0], actorCount, familyCopy, logFn);
+ EXPECT_EQ(actorCount, actorsInFamily);
+
+ const uint32_t serSizeBound = NvBlastAssetGetActorSerializationSizeUpperBound(solverAsset, logFn);
+
+ std::vector< std::vector<char> > streams(serCount);
+ for (uint32_t i = 0; i < serCount; ++i)
+ {
+ std::vector<char>& stream = streams[i];
+ const uint32_t indexToStream = rand() % actorsRemaining.size();
+ NvBlastActor* actorToStream = actorsRemaining[indexToStream];
+ std::swap(actorsRemaining[indexToStream], actorsRemaining[actorsRemaining.size() - 1]);
+ actorsRemaining.pop_back();
+ const uint32_t serSize = NvBlastActorGetSerializationSize(actorToStream, logFn);
+ EXPECT_GE(serSizeBound, serSize);
+ stream.resize(serSize);
+ const uint32_t bytesWritten = NvBlastActorSerialize(&stream[0], serSize, actorToStream, logFn);
+ EXPECT_EQ(serSize, bytesWritten);
+ NvBlastActorDeactivate(actorToStream, logFn);
+ }
+
+ for (uint32_t i = 0; i < serCount; ++i)
+ {
+ NvBlastActor* newActor = NvBlastFamilyDeserializeActor(familyCopy, &streams[i][0], logFn);
+ EXPECT_TRUE(newActor != nullptr);
+ }
+
+ compareFamilies(oldFamily, familyCopy, size, logFn);
+ }
+
+ void damageLeafSupportActors
+ (
+ uint32_t assetCount,
+ uint32_t familyCount,
+ uint32_t damageCount,
+ bool simple,
+ void (*actorTest)(const Nv::Blast::Actor&, NvBlastLog),
+ void (*postDamageTest)(std::vector<NvBlastActor*>&, NvBlastLog),
+ CubeAssetGenerator::BondFlags bondFlags = CubeAssetGenerator::BondFlags::ALL_BONDS
+ )
+ {
+ const float relativeDamageRadius = simple ? 0.75f : 0.2f;
+ const float compressiveDamage = 1.0f;
+ const uint32_t minChunkCount = simple ? 9 : 100;
+ const uint32_t maxChunkCount = simple ? 9 : 10000;
+ const bool printActorCount = false;
+
+ srand(0);
+
+ std::cout << "Asset # (out of " << assetCount << "): ";
+ for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum)
+ {
+ std::cout << assetNum + 1 << ".. ";
+ CubeAssetGenerator::Settings settings;
+ settings.extents = GeneratorAsset::Vec3(1, 1, 1);
+ settings.bondFlags = bondFlags;
+ CubeAssetGenerator::DepthInfo depthInfo;
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1);
+ depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags;
+ settings.depths.push_back(depthInfo);
+ uint32_t chunkCount = 1;
+ while (chunkCount < minChunkCount)
+ {
+ uint32_t chunkMul;
+ do
+ {
+ depthInfo.slicesPerAxis = simple ? GeneratorAsset::Vec3(2, 2, 2) : GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4));
+ chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z);
+ } while (chunkMul == 1);
+ if (chunkCount*chunkMul > maxChunkCount)
+ {
+ break;
+ }
+ chunkCount *= chunkMul;
+ settings.depths.push_back(depthInfo);
+ settings.extents = settings.extents * depthInfo.slicesPerAxis;
+ }
+ settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support
+
+ // Make largest direction unit size
+ settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z)));
+
+ // Create asset
+ GeneratorAsset testAsset;
+ CubeAssetGenerator::generate(testAsset, settings);
+
+ NvBlastAssetDesc desc;
+ desc.chunkDescs = testAsset.solverChunks.data();
+ desc.chunkCount = (uint32_t)testAsset.solverChunks.size();
+ desc.bondDescs = testAsset.solverBonds.data();
+ desc.bondCount = (uint32_t)testAsset.solverBonds.size();
+
+ NvBlastAsset* asset = buildAsset(desc);
+ NvBlastID assetID = NvBlastAssetGetID(asset, messageLog);
+
+ // copy asset (for setAsset testing)
+ const char* data = (const char*)asset;
+ const uint32_t dataSize = NvBlastAssetGetSize(asset, messageLog);
+ char* duplicateData = (char*)alloc(dataSize);
+ memcpy(duplicateData, data, dataSize);
+ NvBlastAsset* assetDuplicate = (NvBlastAsset*)duplicateData;
+
+ // Generate families
+ for (uint32_t familyNum = 0; familyNum < familyCount; ++familyNum)
+ {
+ // family
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, messageLog); // Using zeroingAlloc in case actorTest compares memory blocks
+ NvBlastID id = NvBlastFamilyGetAssetID(family, messageLog);
+ EXPECT_TRUE(!memcmp(&assetID, &id, sizeof(NvBlastID)));
+ if (rand() % 2 == 0)
+ {
+ // replace asset with duplicate in half of cases to test setAsset
+ NvBlastFamilySetAsset(family, assetDuplicate, messageLog);
+ NvBlastID id2 = NvBlastFamilyGetAssetID(family, messageLog);
+ EXPECT_TRUE(!memcmp(&assetID, &id2, sizeof(NvBlastID)));
+ }
+
+ // actor
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ m_scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, m_scratch.data(), messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // Generate damage
+ std::set<NvBlastActor*> actors;
+ actors.insert(actor);
+ if (printActorCount) std::cout << "Actors: 1.. ";
+ for (uint32_t damageNum = 0; damageNum < damageCount; ++damageNum)
+ {
+ GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f);
+ blast(actors, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage);
+ if (printActorCount) std::cout << actors.size() << ".. ";
+ if (actors.size() > 0)
+ {
+ const NvBlastFamily* family = NvBlastActorGetFamily(*actors.begin(), messageLog);
+ const uint32_t actorCount = NvBlastFamilyGetActorCount(family, messageLog);
+ EXPECT_EQ((uint32_t)actors.size(), actorCount);
+ if ((uint32_t)actors.size() == actorCount)
+ {
+ std::vector<NvBlastActor*> buffer1(actorCount);
+ const uint32_t actorsWritten = NvBlastFamilyGetActors(&buffer1[0], actorCount, family, messageLog);
+ EXPECT_EQ(actorsWritten, actorCount);
+ std::vector<NvBlastActor*> buffer2(actors.begin(), actors.end());
+ EXPECT_EQ(0, memcmp(&buffer1[0], &buffer2[0], actorCount*sizeof(NvBlastActor*)));
+ }
+ }
+ // Test individual actors
+ if (actorTest != nullptr)
+ {
+ for (std::set<NvBlastActor*>::iterator k = actors.begin(); k != actors.end(); ++k)
+ {
+ actorTest(*static_cast<Nv::Blast::Actor*>(*k), messageLog);
+ }
+ }
+ }
+ if (printActorCount) std::cout << "\n";
+
+ // Test fractured actor set
+ if (postDamageTest)
+ {
+ std::vector<NvBlastActor*> actorArray(actors.begin(), actors.end());
+ postDamageTest(actorArray, messageLog);
+ }
+
+ // Release remaining actors
+ for (std::set<NvBlastActor*>::iterator k = actors.begin(); k != actors.end(); ++k)
+ {
+ NvBlastActorDeactivate(*k, messageLog);
+ }
+ actors.clear();
+
+ free(family);
+ }
+
+ // Release asset data
+ free(asset);
+ free(assetDuplicate);
+ }
+ std::cout << "done.\n";
+ }
+
+ std::vector<NvBlastAsset*> m_assets;
+ std::vector<NvBlastActor*> m_actors;
+ std::vector<char> m_scratch;
+ static std::vector<char> s_storage;
+
+ static size_t s_curr;
+};
+
+// Static values
+template<int FailLevel, int Verbosity>
+std::vector<char> ActorTest<FailLevel, Verbosity>::s_storage;
+
+template<int FailLevel, int Verbosity>
+size_t ActorTest<FailLevel, Verbosity>::s_curr;
+
+// Specializations
+typedef ActorTest<NvBlastMessage::Error, 1> ActorTestAllowWarnings;
+typedef ActorTest<NvBlastMessage::Warning, 1> ActorTestStrict;
+
+// Tests
+TEST_F(ActorTestStrict, InstanceActors)
+{
+ // Build assets and instance actors
+ buildAssets();
+ instanceActors();
+
+ // Release actors and destroy assets
+ releaseActors();
+ destroyAssets();
+}
+
+TEST_F(ActorTestAllowWarnings, ActorHealthInitialization)
+{
+ // Test all assets
+ std::vector<NvBlastAssetDesc> assetDescs;
+ assetDescs.insert(assetDescs.end(), g_assetDescs, g_assetDescs + getAssetDescCount());
+ assetDescs.insert(assetDescs.end(), g_assetDescsMissingCoverage, g_assetDescsMissingCoverage + getAssetDescMissingCoverageCount());
+
+ struct TestMode
+ {
+ enum Enum
+ {
+ Uniform,
+ Nonuniform,
+
+ Count
+ };
+ };
+
+ for (auto assetDesc : assetDescs)
+ {
+ NvBlastAsset* asset = buildAsset(assetDesc);
+ EXPECT_TRUE(asset != nullptr);
+
+ Nv::Blast::Asset& assetInt = static_cast<Nv::Blast::Asset&>(*asset);
+
+ NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr);
+
+ std::vector<float> supportChunkHealths(graph.nodeCount);
+ for (size_t i = 0; i < supportChunkHealths.size(); ++i)
+ {
+ supportChunkHealths[i] = 1.0f + (float)i;
+ }
+
+ std::vector<float> bondHealths(assetInt.getBondCount());
+ for (size_t i = 0; i < bondHealths.size(); ++i)
+ {
+ bondHealths[i] = 1.5f + (float)i;
+ }
+
+ for (int chunkTestMode = 0; chunkTestMode < TestMode::Count; ++chunkTestMode)
+ {
+ for (int bondTestMode = 0; bondTestMode < TestMode::Count; ++bondTestMode)
+ {
+ NvBlastActorDesc actorDesc;
+
+ switch (chunkTestMode)
+ {
+ default:
+ case TestMode::Uniform:
+ actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ break;
+ case TestMode::Nonuniform:
+ actorDesc.initialSupportChunkHealths = supportChunkHealths.data();
+ break;
+ }
+
+ switch (bondTestMode)
+ {
+ default:
+ case TestMode::Uniform:
+ actorDesc.initialBondHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = 2.0f;
+ break;
+ case TestMode::Nonuniform:
+ actorDesc.initialBondHealths = bondHealths.data();
+ break;
+ }
+
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr);
+ std::vector<char> scratch((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ Nv::Blast::Actor& actorInt = static_cast<Nv::Blast::Actor&>(*actor);
+ Nv::Blast::FamilyHeader* header = actorInt.getFamilyHeader();
+
+
+ for (uint32_t i = 0; i < graph.nodeCount; ++i)
+ {
+ const uint32_t supportChunkIndex = graph.chunkIndices[i];
+ for (Nv::Blast::Asset::DepthFirstIt it(assetInt, supportChunkIndex); (bool)it; ++it)
+ {
+ const uint32_t chunkIndex = (uint32_t)it;
+ const uint32_t lowerSupportIndex = assetInt.getContiguousLowerSupportIndex(chunkIndex);
+ NVBLAST_ASSERT(lowerSupportIndex < assetInt.getLowerSupportChunkCount());
+ const float health = header->getLowerSupportChunkHealths()[lowerSupportIndex];
+ switch (chunkTestMode)
+ {
+ default:
+ case TestMode::Uniform:
+ EXPECT_EQ(1.0f, health);
+ break;
+ case TestMode::Nonuniform:
+ EXPECT_EQ(supportChunkHealths[i], health);
+ break;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < assetInt.getBondCount(); ++i)
+ {
+ switch (bondTestMode)
+ {
+ default:
+ case TestMode::Uniform:
+ EXPECT_EQ(2.0f, header->getBondHealths()[i]);
+ break;
+ case TestMode::Nonuniform:
+ EXPECT_EQ(bondHealths[i], header->getBondHealths()[i]);
+ break;
+ }
+ }
+
+ NvBlastActorDeactivate(actor, messageLog);
+ free(family);
+ }
+ }
+
+ free(asset);
+ }
+}
+
+TEST_F(ActorTestStrict, PartitionActorsToSupportChunksTestCounts)
+{
+ partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, nullptr, recursivePartitionPostSplitTestCounts, false);
+}
+
+TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToSupportChunksTestCounts)
+{
+ partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, nullptr, recursivePartitionPostSplitTestCounts, false);
+}
+
+TEST_F(ActorTestStrict, PartitionActorsToLeafChunksTestCounts)
+{
+ partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, nullptr, recursivePartitionPostSplitTestCounts, true);
+}
+
+TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToLeafChunksTestCounts)
+{
+ partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, nullptr, recursivePartitionPostSplitTestCounts, true);
+}
+
+TEST_F(ActorTestStrict, PartitionActorsToSupportChunksTestVisibility)
+{
+ partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, false);
+}
+
+TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToSupportChunksTestVisibility)
+{
+ partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, false);
+}
+
+TEST_F(ActorTestStrict, PartitionActorsToLeafChunksTestVisibility)
+{
+ partitionActorsToSupportChunks(getAssetDescCount(), g_assetDescs, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, true);
+}
+
+TEST_F(ActorTestAllowWarnings, PartitionActorsFromBadDescriptorsToLeafChunksTestVisibility)
+{
+ partitionActorsToSupportChunks(getAssetDescMissingCoverageCount(), g_assetDescsMissingCoverage, testActorVisibleChunks, recursivePartitionPostSplitTestVisibleChunks, true);
+}
+
+TEST_F(ActorTestStrict, DamageLeafSupportActorsTestVisibility)
+{
+ damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr);
+}
+
+TEST_F(ActorTestStrict, DamageLeafSupportActorTestBlockSerialization)
+{
+ s_storage.resize(0);
+ damageLeafSupportActors(4, 4, 5, false, nullptr, testActorBlockSerialize);
+ s_curr = 0;
+ damageLeafSupportActors(4, 4, 5, false, nullptr, testActorBlockDeserialize);
+ s_storage.resize(0);
+}
+
+TEST_F(ActorTestStrict, DamageSimpleLeafSupportActorTestActorSerializationNewFamily)
+{
+ damageLeafSupportActors(1, 1, 4, true, nullptr, testActorSerializationNewFamily);
+}
+
+TEST_F(ActorTestStrict, DamageSimpleLeafSupportActorTestActorSerializationPartialBlock)
+{
+ damageLeafSupportActors(1, 1, 4, true, nullptr, testActorSerializationPartialBlock);
+}
+
+TEST_F(ActorTestStrict, DamageLeafSupportActorTestActorSerializationNewFamily)
+{
+ damageLeafSupportActors(4, 4, 4, false, nullptr, testActorSerializationNewFamily);
+}
+
+TEST_F(ActorTestStrict, DamageLeafSupportActorTestActorSerializationPartialBlock)
+{
+ damageLeafSupportActors(4, 4, 4, false, nullptr, testActorSerializationPartialBlock);
+}
+
+TEST_F(ActorTestStrict, DamageMultipleIslandLeafSupportActorsTestVisibility)
+{
+ damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::Y_BONDS | CubeAssetGenerator::BondFlags::Z_BONDS); // Only connect y-z plane islands
+ damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::Z_BONDS); // Only connect z-direction islands
+ damageLeafSupportActors(4, 4, 5, false, testActorVisibleChunks, nullptr, CubeAssetGenerator::BondFlags::NO_BONDS); // All support chunks disconnected (single-chunk islands)
+}
diff --git a/NvBlast/test/src/unit/AssetTests.cpp b/NvBlast/test/src/unit/AssetTests.cpp
new file mode 100644
index 0000000..24e5f77
--- /dev/null
+++ b/NvBlast/test/src/unit/AssetTests.cpp
@@ -0,0 +1,529 @@
+#include "NvBlastAsset.h"
+
+#include "BlastBaseTest.h"
+
+#include "NvBlastTkFramework.h"
+
+#include <algorithm>
+
+
+#if defined(_MSC_VER) && _MSC_VER < 1900 || defined(_XBOX_ONE) || defined(PS4) || PX_LINUX
+#define ENABLE_SERIALIZATION_TESTS 0
+#else
+#define ENABLE_SERIALIZATION_TESTS 1
+#endif
+
+#pragma warning( push )
+#pragma warning( disable : 4267 )
+// NOTE: Instead of excluding serialization and the tests when on VC12, should break the tests out into a separate C++ file.
+
+#if ENABLE_SERIALIZATION_TESTS
+#include "NvBlastExtSerializationInterface.h"
+
+#include "generated/NvBlastExtSerialization.capn.h"
+#endif
+
+#pragma warning( pop )
+
+#include <fstream>
+#include <iosfwd>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+template<int FailLevel, int Verbosity>
+class AssetTest : public BlastBaseTest<FailLevel, Verbosity>
+{
+public:
+
+ AssetTest()
+ {
+ Nv::Blast::TkFrameworkDesc desc;
+ desc.allocatorCallback = this;
+ desc.errorCallback = this;
+ NvBlastTkFrameworkCreate(desc);
+ }
+
+ ~AssetTest()
+ {
+ NvBlastTkFrameworkGet()->release();
+ }
+
+ static void messageLog(int type, const char* msg, const char* file, int line)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::messageLog(type, msg, file, line);
+ }
+
+ static void* alloc(size_t size)
+ {
+ return BlastBaseTest<FailLevel, Verbosity>::alloc(size);
+ }
+
+ static void free(void* mem)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::free(mem);
+ }
+
+ void testSubtreeLeafChunkCounts(const Nv::Blast::Asset& a)
+ {
+ const NvBlastChunk* chunks = a.getChunks();
+ const uint32_t* subtreeLeafChunkCounts = a.getSubtreeLeafChunkCounts();
+ uint32_t totalLeafChunkCount = 0;
+ for (uint32_t chunkIndex = 0; chunkIndex < a.m_chunkCount; ++chunkIndex)
+ {
+ const NvBlastChunk& chunk = chunks[chunkIndex];
+ if (Nv::Blast::isInvalidIndex(chunk.parentChunkIndex))
+ {
+ totalLeafChunkCount += subtreeLeafChunkCounts[chunkIndex];
+ }
+ const bool isLeafChunk = chunk.firstChildIndex >= chunk.childIndexStop;
+ uint32_t subtreeLeafChunkCount = isLeafChunk ? 1 : 0;
+ for (uint32_t childIndex = chunk.firstChildIndex; childIndex < chunk.childIndexStop; ++childIndex)
+ {
+ subtreeLeafChunkCount += subtreeLeafChunkCounts[childIndex];
+ }
+ EXPECT_EQ(subtreeLeafChunkCount, subtreeLeafChunkCounts[chunkIndex]);
+ }
+ EXPECT_EQ(totalLeafChunkCount, a.m_leafChunkCount);
+ }
+
+ void testChunkToNodeMap(const Nv::Blast::Asset& a)
+ {
+ for (uint32_t chunkIndex = 0; chunkIndex < a.m_chunkCount; ++chunkIndex)
+ {
+ const uint32_t nodeIndex = a.getChunkToGraphNodeMap()[chunkIndex];
+ if (!Nv::Blast::isInvalidIndex(nodeIndex))
+ {
+ EXPECT_LT(nodeIndex, a.m_graph.m_nodeCount);
+ EXPECT_EQ(chunkIndex, a.m_graph.getChunkIndices()[nodeIndex]);
+ }
+ else
+ {
+ const uint32_t* chunkIndexStop = a.m_graph.getChunkIndices() + a.m_graph.m_nodeCount;
+ const uint32_t* it = std::find<const uint32_t*, uint32_t>(a.m_graph.getChunkIndices(), chunkIndexStop, chunkIndex);
+ EXPECT_EQ(chunkIndexStop, it);
+ }
+ }
+ }
+
+ NvBlastAsset* buildAsset(const ExpectedAssetValues& expected, const NvBlastAssetDesc* desc)
+ {
+ std::vector<char> scratch;
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(desc, messageLog));
+ void* mem = alloc(NvBlastGetAssetMemorySize(desc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, desc, &scratch[0], messageLog);
+ EXPECT_TRUE(asset != nullptr);
+ if (asset == nullptr)
+ {
+ free(mem);
+ return nullptr;
+ }
+ Nv::Blast::Asset& a = *(Nv::Blast::Asset*)asset;
+ EXPECT_EQ(expected.totalChunkCount, a.m_chunkCount);
+ EXPECT_EQ(expected.graphNodeCount, a.m_graph.m_nodeCount);
+ EXPECT_EQ(expected.bondCount, a.m_graph.getAdjacencyPartition()[a.m_graph.m_nodeCount] / 2);
+ EXPECT_EQ(expected.leafChunkCount, a.m_leafChunkCount);
+ EXPECT_EQ(expected.subsupportChunkCount, a.m_chunkCount - a.m_firstSubsupportChunkIndex);
+ testSubtreeLeafChunkCounts(a);
+ testChunkToNodeMap(a);
+ return asset;
+ }
+
+ void checkAssetsExpected(Nv::Blast::Asset& asset, const ExpectedAssetValues& expected)
+ {
+ EXPECT_EQ(expected.totalChunkCount, asset.m_chunkCount);
+ EXPECT_EQ(expected.graphNodeCount, asset.m_graph.m_nodeCount);
+ EXPECT_EQ(expected.bondCount, asset.m_graph.getAdjacencyPartition()[asset.m_graph.m_nodeCount] / 2);
+ EXPECT_EQ(expected.leafChunkCount, asset.m_leafChunkCount);
+ EXPECT_EQ(expected.subsupportChunkCount, asset.m_chunkCount - asset.m_firstSubsupportChunkIndex);
+ testSubtreeLeafChunkCounts(asset);
+ testChunkToNodeMap(asset);
+ }
+
+ void buildAssetShufflingDescriptors(const NvBlastAssetDesc* desc, const ExpectedAssetValues& expected, uint32_t shuffleCount, bool useTk)
+ {
+ NvBlastAssetDesc shuffledDesc = *desc;
+ std::vector<NvBlastChunkDesc> chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount);
+ shuffledDesc.chunkDescs = &chunkDescs[0];
+ std::vector<NvBlastBondDesc> bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount);
+ shuffledDesc.bondDescs = &bondDescs[0];
+ if (!useTk)
+ {
+ std::vector<char> scratch(desc->chunkCount);
+ NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), desc->chunkCount, scratch.data(), messageLog);
+ }
+ else
+ {
+ NvBlastTkFrameworkGet()->ensureAssetExactSupportCoverage(chunkDescs.data(), desc->chunkCount);
+ }
+ for (uint32_t i = 0; i < shuffleCount; ++i)
+ {
+ shuffleAndFixChunkDescs(&chunkDescs[0], desc->chunkCount, &bondDescs[0], desc->bondCount, useTk);
+ NvBlastAsset* asset = buildAsset(expected, &shuffledDesc);
+ EXPECT_TRUE(asset != nullptr);
+ if (asset)
+ {
+ free(asset);
+ }
+ }
+ }
+
+ void shuffleAndFixChunkDescs(NvBlastChunkDesc* chunkDescs, uint32_t chunkDescCount, NvBlastBondDesc* bondDescs, uint32_t bondDescCount, bool useTk)
+ {
+ // Create reorder array and fill with identity map
+ std::vector<uint32_t> shuffledOrder(chunkDescCount);
+ for (uint32_t i = 0; i < chunkDescCount; ++i)
+ {
+ shuffledOrder[i] = i;
+ }
+
+ // An array into which to copy the reordered descs
+ std::vector<NvBlastChunkDesc> shuffledChunkDescs(chunkDescCount);
+
+ std::vector<char> scratch;
+ const uint32_t trials = 30;
+ uint32_t attempt = 0;
+ while(1)
+ {
+ // Shuffle the reorder array
+ std::random_shuffle(shuffledOrder.begin(), shuffledOrder.end());
+
+ // Save initial bonds
+ std::vector<NvBlastBondDesc> savedBondDescs(bondDescs, bondDescs + bondDescCount);
+
+ // Shuffle chunks and bonds
+ NvBlastApplyAssetDescChunkReorderMap(shuffledChunkDescs.data(), chunkDescs, chunkDescCount, bondDescs, bondDescCount, shuffledOrder.data(), nullptr);
+
+ // Check the results
+ for (uint32_t i = 0; i < chunkDescCount; ++i)
+ {
+ EXPECT_EQ(chunkDescs[i].userData, shuffledChunkDescs[shuffledOrder[i]].userData);
+ EXPECT_TRUE(chunkDescs[i].parentChunkIndex > chunkDescCount || shuffledChunkDescs[shuffledOrder[i]].parentChunkIndex == shuffledOrder[chunkDescs[i].parentChunkIndex]);
+ }
+ for (uint32_t i = 0; i < bondDescCount; ++i)
+ {
+ for (uint32_t k = 0; k < 2; ++k)
+ {
+ EXPECT_EQ(shuffledOrder[savedBondDescs[i].chunkIndices[k]], bondDescs[i].chunkIndices[k]);
+ }
+ }
+
+ // Try creating asset, usually it should fail (otherwise make another attempt)
+ NvBlastAssetDesc desc = { chunkDescCount, shuffledChunkDescs.data(), bondDescCount, bondDescs };
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, nullptr));
+ void* mem = alloc(NvBlastGetAssetMemorySize(&desc, nullptr));
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, &desc, scratch.data(), nullptr);
+ if (asset == nullptr)
+ {
+ free(mem);
+ break;
+ }
+ else
+ {
+ free(asset);
+ memcpy(bondDescs, savedBondDescs.data(), sizeof(NvBlastBondDesc) * bondDescCount);
+ attempt++;
+ if (attempt >= trials)
+ {
+ GTEST_NONFATAL_FAILURE_("Shuffled chunk descs should fail asset creation (most of the time).");
+ break;
+ }
+ }
+ }
+
+ // Now we want to fix that order
+ if (!useTk)
+ {
+ std::vector<uint32_t> chunkReorderMap(chunkDescCount);
+ std::vector<char> scratch2(2 * chunkDescCount * sizeof(uint32_t));
+ const bool isIdentity = NvBlastBuildAssetDescChunkReorderMap(chunkReorderMap.data(), shuffledChunkDescs.data(), chunkDescCount, scratch2.data(), messageLog);
+ EXPECT_FALSE(isIdentity);
+ NvBlastApplyAssetDescChunkReorderMap(chunkDescs, shuffledChunkDescs.data(), chunkDescCount, bondDescs, bondDescCount, chunkReorderMap.data(), messageLog);
+ }
+ else
+ {
+ memcpy(chunkDescs, shuffledChunkDescs.data(), chunkDescCount * sizeof(NvBlastChunkDesc));
+ const bool isIdentity = NvBlastTkFrameworkGet()->reorderAssetDescChunks(chunkDescs, chunkDescCount, bondDescs, bondDescCount);
+ EXPECT_FALSE(isIdentity);
+ }
+ }
+};
+
+typedef AssetTest<NvBlastMessage::Error, 0> AssetTestAllowWarningsSilently;
+typedef AssetTest<NvBlastMessage::Error, 1> AssetTestAllowWarnings;
+typedef AssetTest<NvBlastMessage::Warning, 1> AssetTestStrict;
+
+
+TEST_F(AssetTestStrict, BuildAssets)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<NvBlastAsset*> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ assets[i] = buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]);
+ }
+
+ // Destroy
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ if (assets[i])
+ {
+ free(assets[i]);
+ }
+ }
+}
+
+#if ENABLE_SERIALIZATION_TESTS
+// Restricting this test to windows since we don't have a handy cross platform temp file.
+#if defined(WIN32) || defined(WIN64)
+TEST_F(AssetTestStrict, SerializeAssetIntoFile)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<Nv::Blast::Asset *> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ assets[i] = reinterpret_cast<Nv::Blast::Asset*>(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]));
+ }
+
+ char tempPath[1024];
+ GetTempPathA(1024, tempPath);
+
+ char tempFilename[1024];
+
+ GetTempFileNameA(tempPath, nullptr, 0, tempFilename);
+
+ std::ofstream myFile(tempFilename, std::ios::out | std::ios::binary);
+
+ EXPECT_TRUE(serializeAssetIntoStream(assets[0], myFile));
+
+ myFile.flush();
+
+ // Load it back
+
+ std::ifstream myFileReader(tempFilename, std::ios::binary);
+
+ Nv::Blast::Asset* rtAsset = reinterpret_cast<Nv::Blast::Asset *>(deserializeAssetFromStream(myFileReader));
+ EXPECT_TRUE(rtAsset != nullptr);
+
+ checkAssetsExpected(*rtAsset, g_assetExpectedValues[0]);
+
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ free(assets[i]);
+ }
+ free(rtAsset);
+}
+#endif
+
+TEST_F(AssetTestStrict, SerializeAssetsNewBuffer)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<Nv::Blast::Asset *> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ assets[i] = reinterpret_cast<Nv::Blast::Asset*>(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]));
+ }
+
+ // Serialize them
+ for (Nv::Blast::Asset* asset : assets)
+ {
+ uint32_t size = 0;
+ unsigned char* buffer = nullptr;
+
+
+// auto result = Nv::Blast::BlastSerialization<Nv::Blast::Asset, Nv::Blast::Serialization::Asset::Reader, Nv::Blast::Serialization::Asset::Builder>::serializeIntoNewBuffer(asset, &buffer, size);
+ EXPECT_TRUE(serializeAssetIntoNewBuffer(asset, &buffer, size));
+
+ free(static_cast<void*>(buffer));
+ }
+
+ // Destroy
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ if (assets[i])
+ {
+ free(assets[i]);
+ }
+ }
+
+}
+
+TEST_F(AssetTestStrict, SerializeAssetsExistingBuffer)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<Nv::Blast::Asset *> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ assets[i] = reinterpret_cast<Nv::Blast::Asset*>(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]));
+ }
+
+ // How big does our buffer need to be? Guess.
+
+ uint32_t maxSize = 1024 * 1024;
+ void* buffer = alloc(maxSize);
+
+ // Serialize them
+ for (Nv::Blast::Asset* asset : assets)
+ {
+ uint32_t usedSize = 0;
+
+ EXPECT_TRUE(serializeAssetIntoExistingBuffer(asset, (unsigned char *)buffer, maxSize, usedSize));
+ }
+
+ free(static_cast<void*>(buffer));
+
+ // Destroy
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ if (assets[i])
+ {
+ free(assets[i]);
+ }
+ }
+
+}
+
+TEST_F(AssetTestStrict, SerializeAssetsRoundTrip)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<Nv::Blast::Asset *> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ assets[i] = reinterpret_cast<Nv::Blast::Asset*>(buildAsset(g_assetExpectedValues[i], &g_assetDescs[i]));
+ }
+
+ // Serialize them
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ Nv::Blast::Asset* asset = assets[i];
+ uint32_t size = 0;
+ unsigned char* buffer = nullptr;
+
+ EXPECT_TRUE(serializeAssetIntoNewBuffer(asset, &buffer, size));
+
+ // No release needed for this asset since it's never put into that system
+ Nv::Blast::Asset* rtAsset = reinterpret_cast<Nv::Blast::Asset*>(deserializeAsset(buffer, size));
+
+ //TODO: Compare assets
+ checkAssetsExpected(*rtAsset, g_assetExpectedValues[i]);
+
+ free(static_cast<void*>(buffer));
+ free(static_cast<void*>(rtAsset));
+ }
+
+ // Destroy
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ if (assets[i])
+ {
+ free(assets[i]);
+ }
+ }
+}
+#endif
+
+
+#if 0
+TEST_F(AssetTestStrict, AssociateAsset)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ // Build
+ NvBlastAsset asset;
+ if (!buildAsset(&asset, g_assetExpectedValues[i], &g_assetDescs[i]))
+ {
+ continue;
+ }
+
+ // Copy
+ const char* data = (const char*)NvBlastAssetGetData(&asset, messageLog);
+ const size_t dataSize = NvBlastAssetDataGetSize(data, messageLog);
+ NvBlastAsset duplicate;
+ char* duplicateData = (char*)alloc(dataSize);
+ memcpy(duplicateData, data, dataSize);
+ const bool assetAssociateResult = NvBlastAssetAssociateData(&duplicate, duplicateData, messageLog);
+ EXPECT_TRUE(assetAssociateResult);
+
+ // Destroy
+ NvBlastAssetFreeData(&asset, free, messageLog);
+ NvBlastAssetFreeData(&duplicate, free, messageLog);
+ }
+}
+#endif
+
+TEST_F(AssetTestAllowWarnings, BuildAssetsMissingCoverage)
+{
+ const uint32_t assetDescCount = sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]);
+
+ std::vector<NvBlastAsset*> assets(assetDescCount);
+
+ // Build
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ const NvBlastAssetDesc* desc = &g_assetDescsMissingCoverage[i];
+ NvBlastAssetDesc fixedDesc = *desc;
+ std::vector<NvBlastChunkDesc> chunkDescs(desc->chunkDescs, desc->chunkDescs + desc->chunkCount);
+ std::vector<NvBlastBondDesc> bondDescs(desc->bondDescs, desc->bondDescs + desc->bondCount);
+ std::vector<uint32_t> chunkReorderMap(desc->chunkCount);
+ std::vector<char> scratch(desc->chunkCount * sizeof(NvBlastChunkDesc));
+ const bool changedCoverage = !NvBlastEnsureAssetExactSupportCoverage(chunkDescs.data(), fixedDesc.chunkCount, scratch.data(), messageLog);
+ EXPECT_TRUE(changedCoverage);
+ NvBlastReorderAssetDescChunks(chunkDescs.data(), fixedDesc.chunkCount, bondDescs.data(), fixedDesc.bondCount, chunkReorderMap.data(), scratch.data(), messageLog);
+ fixedDesc.chunkDescs = chunkDescs.data();
+ fixedDesc.bondDescs = bondDescs.data();
+ assets[i] = buildAsset(g_assetsFromMissingCoverageExpectedValues[i], &fixedDesc);
+ }
+
+ // Destroy
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ if (assets[i])
+ {
+ free(assets[i]);
+ }
+ }
+}
+
+TEST_F(AssetTestAllowWarningsSilently, BuildAssetsShufflingChunkDescriptors)
+{
+ for (uint32_t i = 0; i < sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); ++i)
+ {
+ buildAssetShufflingDescriptors(&g_assetDescs[i], g_assetExpectedValues[i], 10, false);
+ }
+
+ for (uint32_t i = 0; i < sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); ++i)
+ {
+ buildAssetShufflingDescriptors(&g_assetDescsMissingCoverage[i], g_assetsFromMissingCoverageExpectedValues[i], 10, false);
+ }
+}
+
+TEST_F(AssetTestAllowWarningsSilently, BuildAssetsShufflingChunkDescriptorsUsingTk)
+{
+ for (uint32_t i = 0; i < sizeof(g_assetDescs) / sizeof(g_assetDescs[0]); ++i)
+ {
+ buildAssetShufflingDescriptors(&g_assetDescs[i], g_assetExpectedValues[i], 10, true);
+ }
+
+ for (uint32_t i = 0; i < sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]); ++i)
+ {
+ buildAssetShufflingDescriptors(&g_assetDescsMissingCoverage[i], g_assetsFromMissingCoverageExpectedValues[i], 10, true);
+ }
+}
diff --git a/NvBlast/test/src/unit/CoreTests.cpp b/NvBlast/test/src/unit/CoreTests.cpp
new file mode 100644
index 0000000..4aff4ae
--- /dev/null
+++ b/NvBlast/test/src/unit/CoreTests.cpp
@@ -0,0 +1,293 @@
+#include <algorithm>
+#include "gtest/gtest.h"
+
+//#include "NvBlast.h"
+#include "NvBlastActor.h"
+#include "NvBlastIndexFns.h"
+
+#include "AlignedAllocator.h"
+
+#include "TestAssets.h"
+#include "NvBlastActor.h"
+
+static void messageLog(int type, const char* msg, const char* file, int line)
+{
+ {
+ switch (type)
+ {
+ case NvBlastMessage::Error: std::cout << "NvBlast Error message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Warning: std::cout << "NvBlast Warning message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Info: std::cout << "NvBlast Info message in " << file << "(" << line << "): " << msg << "\n"; break;
+ case NvBlastMessage::Debug: std::cout << "NvBlast Debug message in " << file << "(" << line << "): " << msg << "\n"; break;
+ }
+ }
+}
+
+TEST(CoreTests, IndexStartLookup)
+{
+ uint32_t lookup[32];
+ uint32_t indices[] = {1,1,2,2,4,4,4};
+
+ Nv::Blast::createIndexStartLookup<uint32_t>(lookup, 0, 30, indices, 7, 4);
+
+ EXPECT_EQ(lookup[0], 0);
+ EXPECT_EQ(lookup[1], 0);
+ EXPECT_EQ(lookup[2], 2);
+ EXPECT_EQ(lookup[3], 4);
+ EXPECT_EQ(lookup[4], 4);
+ EXPECT_EQ(lookup[5], 7);
+ EXPECT_EQ(lookup[31], 7);
+}
+
+#include "NvBlastGeometry.h"
+
+TEST(CoreTests, FindChunkByPosition)
+{
+ std::vector<char> scratch;
+ const NvBlastAssetDesc& desc = g_assetDescs[0]; // 1-cube
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, nullptr));
+ void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, nullptr));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], nullptr);
+ ASSERT_TRUE(asset != nullptr);
+
+ uint32_t expectedNode[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+ const float positions[] = {
+ -2.0f, -2.0f, -2.0f,
+ +2.0f, -2.0f, -2.0f,
+ -2.0f, +2.0f, -2.0f,
+ +2.0f, +2.0f, -2.0f,
+ -2.0f, -2.0f, +2.0f,
+ +2.0f, -2.0f, +2.0f,
+ -2.0f, +2.0f, +2.0f,
+ +2.0f, +2.0f, +2.0f,
+ };
+ const float* pos = &positions[0];
+
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, nullptr));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, nullptr));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr);
+ ASSERT_TRUE(actor != nullptr);
+
+ std::vector<uint32_t> graphNodeIndices;
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr));
+ const float* bondHealths = NvBlastActorGetBondHealths(actor, messageLog);
+ uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr);
+
+ const NvBlastBond* bonds = NvBlastAssetGetBonds(asset, nullptr);
+ NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr);
+ for (int i = 0; i < 8; ++i, pos += 3)
+ {
+ EXPECT_EQ(expectedNode[i], Nv::Blast::findNodeByPosition(pos, graphNodesCount, graphNodeIndices.data(), graph, bonds, bondHealths));
+ EXPECT_EQ(expectedNode[i] + 1, NvBlastActorClosestChunk(pos, actor, nullptr)); // Works because (chunk index) = (node index) + 1 in these cases
+ }
+
+ EXPECT_TRUE(NvBlastActorDeactivate(actor, nullptr));
+ alignedFree<free>(family);
+ alignedFree<free>(asset);
+}
+
+TEST(CoreTests, FindChunkByPositionUShape)
+{
+ /*
+ considering this graph
+
+ 4->5->6
+ ^
+ |
+ 1->2->3
+
+ and trying to find chunks by some position
+ */
+ const NvBlastChunkDesc uchunks[7] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 }
+ };
+
+ const NvBlastBondDesc ubonds[5] =
+ {
+ // chunks normal area centroid userData
+ { { 2, 1 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 1.0f, 0.0f }, 0 } }, // index swap should not matter
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 1.0f, 0.0f }, 0 } },
+ { { 1, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } },
+ { { 4, 5 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 3.0f, 0.0f }, 0 } },
+ { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 3.0f, 0.0f }, 0 } },
+ };
+
+ const NvBlastAssetDesc desc = { 7, uchunks, 5, ubonds };
+ std::vector<char> scratch;
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog));
+ void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], messageLog);
+ ASSERT_TRUE(asset != nullptr);
+
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr);
+ ASSERT_TRUE(actor != nullptr);
+
+ std::vector<uint32_t> graphNodeIndices;
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr));
+ uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr);
+
+ const NvBlastBond* bonds = NvBlastAssetGetBonds(asset, nullptr);
+ NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr);
+
+ srand(100);
+ for (uint32_t i = 0; i < 100000; i++)
+ {
+ float rx = 20 * (float)(rand() - 1) / RAND_MAX - 10;
+ float ry = 20 * (float)(rand() - 1) / RAND_MAX - 10;
+ float rz = 0.0f;
+ float rpos[] = { rx, ry, rz };
+
+ // open boundaries
+ uint32_t col = std::max(0, std::min(2, int(rx / 2)));
+ uint32_t row = std::max(0, std::min(1, int(ry / 2)));
+ uint32_t expectedNode = col + row * 3;
+
+ //printf("iteration %i: %.1f %.1f %.1f expected: %d\n", i, rpos[0], rpos[1], rpos[2], expectedNode);
+ {
+ uint32_t returnedNode = Nv::Blast::findNodeByPosition(rpos, graphNodesCount, graphNodeIndices.data(), graph, bonds, NvBlastActorGetBondHealths(actor, messageLog));
+ if (expectedNode != returnedNode)
+ Nv::Blast::findNodeByPosition(rpos, graphNodesCount, graphNodeIndices.data(), graph, bonds, NvBlastActorGetBondHealths(actor, messageLog));
+ EXPECT_EQ(expectedNode, returnedNode);
+ }
+ {
+ // +1 to account for graph vs. asset indices
+ uint32_t expectedChunk = expectedNode + 1;
+ uint32_t returnedChunk = NvBlastActorClosestChunk(rpos, actor, nullptr);
+ if (expectedChunk != returnedChunk)
+ NvBlastActorClosestChunk(rpos, actor, nullptr);
+ EXPECT_EQ(expectedChunk, returnedChunk);
+ }
+
+ }
+
+ EXPECT_TRUE(NvBlastActorDeactivate(actor, messageLog));
+
+ alignedFree<free>(family);
+ alignedFree<free>(asset);
+}
+
+TEST(CoreTests, FindChunkByPositionLandlocked)
+{
+ const NvBlastChunkDesc chunks[10] =
+ {
+ // centroid volume parent idx flags ID
+ { {0.0f, 0.0f, 0.0f}, 0.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 8 },
+ { {0.0f, 0.0f, 0.0f}, 0.0f, 0, NvBlastChunkDesc::SupportFlag, 9 },
+ };
+
+ const NvBlastBondDesc bonds[12] =
+ {
+ // chunks normal area centroid userData
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 1.0f, 0.0f }, 0 } },
+ { { 2, 3 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 1.0f, 0.0f }, 0 } },
+ { { 4, 5 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 3.0f, 0.0f }, 0 } },
+ { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 3.0f, 0.0f }, 0 } },
+ { { 7, 8 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 2.0f, 5.0f, 0.0f }, 0 } },
+ { { 8, 9 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 4.0f, 5.0f, 0.0f }, 0 } },
+ { { 1, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 2.0f, 0.0f }, 0 } },
+ { { 2, 5 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 3.0f, 2.0f, 0.0f }, 0 } },
+ { { 3, 6 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 5.0f, 2.0f, 0.0f }, 0 } },
+ { { 4, 7 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 1.0f, 4.0f, 0.0f }, 0 } },
+ { { 5, 8 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 3.0f, 4.0f, 0.0f }, 0 } },
+ { { 6, 9 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 5.0f, 4.0f, 0.0f }, 0 } },
+ };
+
+ const NvBlastAssetDesc desc = { 10, chunks, 12, bonds };
+ std::vector<char> scratch;
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog));
+ void* amem = alignedAlloc<malloc>(NvBlastGetAssetMemorySize(&desc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(amem, &desc, &scratch[0], messageLog);
+ ASSERT_TRUE(asset != nullptr);
+
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alignedAlloc<malloc>(NvBlastAssetGetFamilyMemorySize(asset, nullptr));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr);
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, nullptr));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], nullptr);
+ ASSERT_TRUE(actor != nullptr);
+
+ const NvBlastBond* assetBonds = NvBlastAssetGetBonds(asset, nullptr);
+ NvBlastSupportGraph graph = NvBlastAssetGetSupportGraph(asset, nullptr);
+
+ float point[4] = { 3.0f, 3.0f, 0.0f };
+ EXPECT_EQ(5, NvBlastActorClosestChunk(point, actor, nullptr));
+ {
+ std::vector<uint32_t> graphNodeIndices;
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr));
+ uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr);
+
+ EXPECT_EQ(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, NvBlastActorGetBondHealths(actor, messageLog)));
+ }
+
+ NvBlastChunkFractureData chunkBuffer[1];
+ NvBlastFractureBuffers events = { 0, 1, nullptr, chunkBuffer };
+
+ NvBlastChunkFractureData chunkFracture = { 0, 5, 1.0f };
+ NvBlastFractureBuffers commands = { 0, 1, nullptr, &chunkFracture };
+
+ NvBlastActorApplyFracture(&events, actor, &commands, messageLog, nullptr);
+ EXPECT_EQ(1, events.chunkFractureCount);
+
+ NvBlastActor* newActors[5];
+ NvBlastActorSplitEvent splitEvent = { nullptr, newActors };
+ scratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, messageLog));
+ size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, 5, scratch.data(), messageLog, nullptr);
+
+ ASSERT_EQ(actor, newActors[1]);
+
+ EXPECT_NE(5, NvBlastActorClosestChunk(point, actor, nullptr));
+
+ float point2[4] = { 80.0f, 80.0f, 80.0f };
+ EXPECT_EQ(5, NvBlastActorClosestChunk(point2, newActors[0], nullptr));
+
+ {
+ const float* bondHealths = NvBlastActorGetBondHealths(actor, messageLog);
+ std::vector<uint32_t> graphNodeIndices;
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(actor, nullptr));
+ uint32_t graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), actor, nullptr);
+
+ EXPECT_NE(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, bondHealths));
+
+ graphNodeIndices.resize(NvBlastActorGetGraphNodeCount(newActors[0], nullptr));
+ graphNodesCount = NvBlastActorGetGraphNodeIndices(graphNodeIndices.data(), (uint32_t)graphNodeIndices.size(), newActors[0], nullptr);
+
+ EXPECT_EQ(4, Nv::Blast::findNodeByPosition(point, graphNodesCount, graphNodeIndices.data(), graph, assetBonds, bondHealths));
+ }
+
+
+ for (uint32_t i = 0; i < newActorsCount; ++i)
+ {
+ EXPECT_TRUE(NvBlastActorDeactivate(newActors[i], nullptr));
+ }
+
+ alignedFree<free>(family);
+ alignedFree<free>(asset);
+}
diff --git a/NvBlast/test/src/unit/FamilyGraphTests.cpp b/NvBlast/test/src/unit/FamilyGraphTests.cpp
new file mode 100644
index 0000000..fdb9738
--- /dev/null
+++ b/NvBlast/test/src/unit/FamilyGraphTests.cpp
@@ -0,0 +1,377 @@
+#include "BlastBaseTest.h"
+
+#include "NvBlastSupportGraph.h"
+#include "NvBlastFamilyGraph.h"
+#include "NvBlastAssert.h"
+#include "NvBlastIndexFns.h"
+
+#include <stdlib.h>
+#include <ostream>
+#include <stdint.h>
+#include <map>
+#include <algorithm>
+
+
+// ====================================================================================================================
+// HELPERS
+// ====================================================================================================================
+
+::testing::AssertionResult VectorMatch(const std::vector<uint32_t>& actual, const uint32_t* expected, uint32_t size)
+{
+ for (size_t i(0); i < size; ++i)
+ {
+ if (expected[i] != actual[i])
+ {
+ testing::Message msg;
+ msg << "array[" << i
+ << "] (" << actual[i] << ") != expected[" << i
+ << "] (" << expected[i] << ")";
+ return (::testing::AssertionFailure(msg));;
+ }
+ }
+
+ return ::testing::AssertionSuccess();
+}
+
+#define VECTOR_MATCH(actual, ...) \
+{ \
+ const uint32_t arr[] = { __VA_ARGS__ }; \
+ const uint32_t size = (sizeof(arr) / sizeof(arr[0])); \
+ EXPECT_EQ(size, actual.size()); \
+ EXPECT_TRUE(VectorMatch(actual, arr, size)); \
+}
+
+
+// ====================================================================================================================
+// TEST CLASS
+// ====================================================================================================================
+
+using namespace Nv::Blast;
+
+template<int FailLevel, int Verbosity>
+class FamilyGraphTest : public BlastBaseTest < FailLevel, Verbosity >
+{
+public:
+ FamilyGraphTest()
+ {
+ }
+
+protected:
+ FamilyGraph* buildFamilyGraph(uint32_t chunkCount, const uint32_t* adjacentChunkPartition, const uint32_t* adjacentChunkIndices)
+ {
+ NVBLAST_ASSERT(m_memoryBlock.size() == 0); // can't build twice per test
+
+ // Fill SupportGraph with data:
+ NvBlastCreateOffsetStart(sizeof(SupportGraph));
+ const size_t NvBlastCreateOffsetAlign16(chunkIndicesOffset, chunkCount*sizeof(uint32_t));
+ const size_t NvBlastCreateOffsetAlign16(adjacencyPartitionOffset, (chunkCount + 1)*sizeof(uint32_t));
+ const size_t NvBlastCreateOffsetAlign16(adjacentNodeIndicesOffset, adjacentChunkPartition[chunkCount] * sizeof(uint32_t));
+ const size_t NvBlastCreateOffsetAlign16(adjacentBondIndicesOffset, adjacentChunkPartition[chunkCount] * sizeof(uint32_t));
+ const size_t graphDataSize = NvBlastCreateOffsetEndAlign16();
+ m_graphMemory.resize(graphDataSize);
+ m_graph = reinterpret_cast<SupportGraph*>(m_graphMemory.data());
+
+ m_graph->m_nodeCount = chunkCount;
+ m_graph->m_chunkIndicesOffset = static_cast<uint32_t>(chunkIndicesOffset);
+ m_graph->m_adjacencyPartitionOffset = static_cast<uint32_t>(adjacencyPartitionOffset);
+ m_graph->m_adjacentNodeIndicesOffset = static_cast<uint32_t>(adjacentNodeIndicesOffset);
+ m_graph->m_adjacentBondIndicesOffset = static_cast<uint32_t>(adjacentBondIndicesOffset);
+
+ memcpy(m_graph->getAdjacencyPartition(), adjacentChunkPartition, (chunkCount + 1) * sizeof(uint32_t));
+ memcpy(m_graph->getAdjacentNodeIndices(), adjacentChunkIndices, adjacentChunkPartition[chunkCount] * sizeof(uint32_t));
+
+ // fill bondIndices by incrementing bondIndex and putting same bondIndex in mirror bond index for (n0, n1) == (n1, n0)
+ memset(m_graph->getAdjacentBondIndices(), (uint32_t)-1, adjacentChunkPartition[chunkCount] * sizeof(uint32_t));
+ uint32_t bondIndex = 0;
+ for (uint32_t chunk0 = 0; chunk0 < m_graph->m_nodeCount; chunk0++)
+ {
+ for (uint32_t i = m_graph->getAdjacencyPartition()[chunk0]; i < m_graph->getAdjacencyPartition()[chunk0 + 1]; i++)
+ {
+ if (m_graph->getAdjacentBondIndices()[i] == (uint32_t)-1)
+ {
+ m_graph->getAdjacentBondIndices()[i] = bondIndex;
+
+ uint32_t chunk1 = m_graph->getAdjacentNodeIndices()[i];
+ for (uint32_t j = m_graph->getAdjacencyPartition()[chunk1]; j < m_graph->getAdjacencyPartition()[chunk1 + 1]; j++)
+ {
+ if (m_graph->getAdjacentNodeIndices()[j] == chunk0)
+ {
+ m_graph->getAdjacentBondIndices()[j] = bondIndex;
+ }
+ }
+ bondIndex++;
+ }
+ }
+ }
+
+ // reserve memory for family graph and asset pointer
+ uint32_t familyGraphMemorySize = (uint32_t)FamilyGraph::requiredMemorySize(m_graph->m_nodeCount, bondIndex);
+ m_memoryBlock.resize(familyGraphMemorySize);
+ // placement new family graph
+ FamilyGraph* familyGraph = new(&m_memoryBlock[0]) FamilyGraph(m_graph);
+
+ return familyGraph;
+ }
+
+ struct IslandInfo
+ {
+ std::vector<NodeIndex> nodes;
+ };
+
+ /**
+ Function to gather islands info for tests and debug purposes
+ Returned islands sorted by nodes counts. Island nodes also sorted by NodeIndex.
+ */
+ void getIslandsInfo(const FamilyGraph& graph, std::vector<IslandInfo>& info)
+ {
+ IslandId* islandIds = graph.getIslandIds();
+
+ std::map<IslandId, IslandInfo> islandMap;
+
+ for (NodeIndex n = 0; n < m_graph->m_nodeCount; n++)
+ {
+ EXPECT_TRUE(islandIds[n] != invalidIndex<uint32_t>());
+ IslandId islandId = islandIds[n];
+ if (islandMap.find(islandId) == islandMap.end())
+ {
+ IslandInfo info;
+ info.nodes.push_back(n);
+ islandMap[islandId] = info;
+ }
+ else
+ {
+ islandMap[islandId].nodes.push_back(n);
+ }
+ }
+
+ for (auto it = islandMap.begin(); it != islandMap.end(); ++it)
+ {
+ std::sort(it->second.nodes.begin(), it->second.nodes.end());
+ info.push_back(it->second);
+ }
+
+ // sort islands by size ascending
+ std::sort(info.begin(), info.end(), [](const IslandInfo& i0, const IslandInfo& i1) -> bool
+ {
+ size_t s0 = i0.nodes.size();
+ size_t s1 = i1.nodes.size();
+ if (s0 == s1 && s0 > 0)
+ {
+ s0 = i0.nodes[0];
+ s1 = i1.nodes[0];
+ }
+ return s0 < s1;
+ });
+ }
+
+ static const uint32_t DEFAULT_ACTOR_INDEX = 0;
+
+ SupportGraph* m_graph;
+ std::vector<char> m_graphMemory;
+ std::vector<char> m_memoryBlock;
+};
+
+typedef FamilyGraphTest<NvBlastMessage::Error, 1> FamilyGraphTestAllowWarnings;
+typedef FamilyGraphTest<NvBlastMessage::Warning, 1> FamilyGraphTestStrict;
+
+
+// ====================================================================================================================
+// GRAPH DATA
+// ====================================================================================================================
+
+// Graph 0:
+//
+// 0 -- 1 -- 2 -- 3
+// | | | |
+// | | | |
+// 4 -- 5 6 -- 7
+//
+const uint32_t chunkCount0 = 8;
+const uint32_t adjacentChunkPartition0[] = { 0, 2, 5, 8, 10, 12, 14, 16, 18 };
+const uint32_t adjacentChunkIndices0[] = { /*0*/ 1, 4, /*1*/ 0, 2, 5, /*2*/ 1, 3, 6, /*3*/ 2, 7, /*4*/ 0, 5, /*5*/ 1, 4, /*6*/ 2, 7, /*7*/ 3, 6 };
+
+
+// Graph 1:
+//
+// 0 -- 1 -- 2 -- 3
+// | | | |
+// 4 -- 5 -- 6 -- 7
+// | | | |
+// 8 -- 9 -- 10-- 11
+//
+const uint32_t chunkCount1 = 12;
+const uint32_t adjacentChunkPartition1[] = { 0, 2, 5, 8, 10, 13, 17, 21, 24, 26, 29, 32, 34 };
+const uint32_t adjacentChunkIndices1[] = { /*0*/ 1, 4, /*1*/ 0, 2, 5, /*2*/ 1, 3, 6, /*3*/ 2, 7, /*4*/ 0, 5, 8, /*5*/ 1, 4, 6, 9, /*6*/ 2, 5, 7, 10,
+ /*7*/ 3, 6, 11, /*8*/ 4, 9, /*9*/ 5, 8, 10, /*10*/ 6, 9, 11, /*11*/ 7, 10 };
+
+
+// ====================================================================================================================
+// TESTS
+// ====================================================================================================================
+
+TEST_F(FamilyGraphTestStrict, Graph0FindIslands0)
+{
+ FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0);
+ graph->initialize(DEFAULT_ACTOR_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0));
+
+ EXPECT_EQ(9, graph->getEdgesCount(m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph);
+ EXPECT_EQ(8, graph->getEdgesCount(m_graph));
+ EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 2, m_graph);
+ EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ std::vector<IslandInfo> info;
+ getIslandsInfo(*graph, info);
+ EXPECT_EQ(2, info.size());
+ VECTOR_MATCH(info[0].nodes, 0, 1, 4, 5);
+ VECTOR_MATCH(info[1].nodes, 2, 3, 6, 7);
+}
+
+TEST_F(FamilyGraphTestStrict, Graph0FindIslands1)
+{
+ FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0);
+ graph->initialize(DEFAULT_ACTOR_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0));
+
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 4, 5, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 2, m_graph);
+ EXPECT_EQ(6, graph->getEdgesCount(m_graph));
+ EXPECT_EQ(3, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ std::vector<IslandInfo> info;
+ getIslandsInfo(*graph, info);
+ EXPECT_EQ(3, info.size());
+ VECTOR_MATCH(info[0].nodes, 4);
+ VECTOR_MATCH(info[1].nodes, 0, 1, 5);
+ VECTOR_MATCH(info[2].nodes, 2, 3, 6, 7);
+}
+
+TEST_F(FamilyGraphTestStrict, Graph0FindIslandsDifferentActors)
+{
+ const uint32_t ACTOR_0_INDEX = 5;
+ const uint32_t ACTOR_1_INDEX = 2;
+
+ FamilyGraph* graph = buildFamilyGraph(chunkCount0, adjacentChunkPartition0, adjacentChunkIndices0);
+ graph->initialize(ACTOR_0_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount0));
+
+ EXPECT_EQ(0, graph->findIslands(ACTOR_1_INDEX, &scratch[0], m_graph));
+ EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph));
+
+ graph->notifyEdgeRemoved(ACTOR_0_INDEX, 2, 1, m_graph);
+ EXPECT_EQ(8, graph->getEdgesCount(m_graph));
+
+ EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph));
+
+ graph->notifyEdgeRemoved(ACTOR_1_INDEX, 2, 6, m_graph);
+ graph->notifyEdgeRemoved(ACTOR_1_INDEX, 7, 3, m_graph);
+ EXPECT_EQ(1, graph->findIslands(ACTOR_1_INDEX, &scratch[0], m_graph));
+
+ graph->notifyEdgeRemoved(ACTOR_0_INDEX, 0, 1, m_graph);
+ graph->notifyEdgeRemoved(ACTOR_0_INDEX, 4, 5, m_graph);
+ EXPECT_EQ(1, graph->findIslands(ACTOR_0_INDEX, &scratch[0], m_graph));
+
+
+ std::vector<IslandInfo> info;
+ getIslandsInfo(*graph, info);
+ EXPECT_EQ(4, info.size());
+ VECTOR_MATCH(info[0].nodes, 0, 4);
+ VECTOR_MATCH(info[1].nodes, 1, 5);
+ VECTOR_MATCH(info[2].nodes, 2, 3);
+ VECTOR_MATCH(info[3].nodes, 6, 7);
+}
+
+TEST_F(FamilyGraphTestStrict, Graph1FindIslands0)
+{
+ FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1);
+ graph->initialize(DEFAULT_ACTOR_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1));
+
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 5, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 2, 6, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 3, 7, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 5, 6, m_graph);
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 9, 10, m_graph);
+ EXPECT_EQ(11, graph->getEdgesCount(m_graph));
+ EXPECT_EQ(3, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ std::vector<IslandInfo> info;
+ getIslandsInfo(*graph, info);
+ EXPECT_EQ(3, info.size());
+
+ VECTOR_MATCH(info[0].nodes, 0, 1, 2, 3);
+ VECTOR_MATCH(info[1].nodes, 4, 5, 8, 9);
+ VECTOR_MATCH(info[2].nodes, 6, 7, 10, 11);
+}
+
+TEST_F(FamilyGraphTestStrict, Graph1FindIslands1)
+{
+ FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1);
+ graph->initialize(DEFAULT_ACTOR_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1));
+
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 0, 4, m_graph);
+ EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 1, 5, m_graph);
+ EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 2, 6, m_graph);
+ EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 3, 7, m_graph);
+ EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 5, 6, m_graph);
+ EXPECT_EQ(0, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+ graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, 9, 10, m_graph);
+ EXPECT_EQ(1, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ std::vector<IslandInfo> info;
+ getIslandsInfo(*graph, info);
+ EXPECT_EQ(3, info.size());
+ VECTOR_MATCH(info[0].nodes, 0, 1, 2, 3);
+ VECTOR_MATCH(info[1].nodes, 4, 5, 8, 9);
+ VECTOR_MATCH(info[2].nodes, 6, 7, 10, 11);
+}
+
+TEST_F(FamilyGraphTestStrict, Graph1FindIslandsRemoveAllEdges)
+{
+ FamilyGraph* graph = buildFamilyGraph(chunkCount1, adjacentChunkPartition1, adjacentChunkIndices1);
+ graph->initialize(DEFAULT_ACTOR_INDEX, m_graph);
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)FamilyGraph::findIslandsRequiredScratch(chunkCount1));
+
+ uint32_t edges = graph->getEdgesCount(m_graph);
+ for (uint32_t node0 = 0; node0 < chunkCount1; node0++)
+ {
+ for (uint32_t i = adjacentChunkPartition1[node0]; i < adjacentChunkPartition1[node0 + 1]; i++)
+ {
+ if (graph->notifyEdgeRemoved(DEFAULT_ACTOR_INDEX, node0, adjacentChunkIndices1[i], m_graph))
+ {
+ edges--;
+ EXPECT_EQ(edges, graph->getEdgesCount(m_graph));
+ }
+ }
+ }
+ EXPECT_EQ(0, graph->getEdgesCount(m_graph));
+
+ EXPECT_EQ(12, graph->findIslands(DEFAULT_ACTOR_INDEX, &scratch[0], m_graph));
+
+ for (uint32_t node0 = 0; node0 < chunkCount1; node0++)
+ {
+ EXPECT_EQ(node0, graph->getIslandIds()[node0]);
+ }
+}
diff --git a/NvBlast/test/src/unit/MultithreadingTests.cpp b/NvBlast/test/src/unit/MultithreadingTests.cpp
new file mode 100644
index 0000000..f1ae176
--- /dev/null
+++ b/NvBlast/test/src/unit/MultithreadingTests.cpp
@@ -0,0 +1,395 @@
+#include "BlastBaseTest.h"
+#include "AssetGenerator.h"
+
+#include <iostream>
+#include <memory>
+#include "TaskDispatcher.h"
+
+#include "NvBlastActor.h"
+#include "NvBlastExtDamageShaders.h"
+
+#if NV_XBOXONE
+#undef min
+#undef max
+#endif
+
+typedef std::function<void(const Nv::Blast::Actor&, NvBlastLog)> ActorTestFunction;
+typedef std::function<void(std::vector<NvBlastActor*>&, NvBlastLog)> PostDamageTestFunction;
+
+
+static void blast(std::set<NvBlastActor*>& actorsToDamage, GeneratorAsset* testAsset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage)
+{
+ std::vector<NvBlastChunkFractureData> chunkEvents; /* num lower-support chunks + bonds */
+ std::vector<NvBlastBondFractureData> bondEvents; /* num lower-support chunks + bonds */
+ chunkEvents.resize(testAsset->solverChunks.size());
+ bondEvents.resize(testAsset->solverBonds.size());
+
+ NvBlastFractureBuffers events = { static_cast<uint32_t>(bondEvents.size()), static_cast<uint32_t>(chunkEvents.size()), bondEvents.data(), chunkEvents.data() };
+
+ std::vector<float> scratch(chunkEvents.size() + bondEvents.size(), 0.0f);
+
+ std::vector<char> splitScratch;
+ std::vector<NvBlastActor*> newActorsBuffer(testAsset->solverChunks.size());
+
+ NvBlastExtRadialDamageDesc damage[] = {
+ {
+ compressiveDamage,
+ { localPos.x, localPos.y, localPos.z },
+ minRadius,
+ maxRadius
+ }
+ };
+
+ NvBlastProgramParams programParams =
+ {
+ &damage,
+ 1,
+ nullptr
+ };
+
+ NvBlastDamageProgram program = {
+ NvBlastExtFalloffGraphShader,
+ nullptr
+ };
+
+ size_t totalNewActorsCount = 0;
+ for (std::set<NvBlastActor*>::iterator k = actorsToDamage.begin(); k != actorsToDamage.end();)
+ {
+ NvBlastActor* actor = *k;
+
+ NvBlastActorGenerateFracture(&events, actor, program, &programParams, nullptr, nullptr);
+ NvBlastActorApplyFracture(&events, actor, &events, nullptr, nullptr);
+
+ bool removeActor = false;
+
+ if (events.bondFractureCount + events.chunkFractureCount > 0)
+ {
+ NvBlastActorSplitEvent splitEvent;
+ splitEvent.newActors = &newActorsBuffer.data()[totalNewActorsCount];
+ uint32_t newActorSize = (uint32_t)(newActorsBuffer.size() - totalNewActorsCount);
+
+ splitScratch.resize((size_t)NvBlastActorGetRequiredScratchForSplit(actor, nullptr));
+ const size_t newActorsCount = NvBlastActorSplit(&splitEvent, actor, newActorSize, splitScratch.data(), nullptr, nullptr);
+ totalNewActorsCount += newActorsCount;
+ removeActor = splitEvent.deletedActor != NULL;
+ }
+
+ if (removeActor)
+ {
+ k = actorsToDamage.erase(k);
+ }
+ else
+ {
+ ++k;
+ }
+ }
+
+ for (size_t i = 0; i < totalNewActorsCount; ++i)
+ {
+ actorsToDamage.insert(newActorsBuffer[i]);
+ }
+}
+
+
+template<int FailLevel, int Verbosity>
+class MultithreadingTest : public BlastBaseTest<FailLevel, Verbosity>
+{
+public:
+ MultithreadingTest()
+ {
+
+ }
+
+ static void messageLog(int type, const char* msg, const char* file, int line)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::messageLog(type, msg, file, line);
+ }
+
+ static void* alloc(size_t size)
+ {
+ return BlastBaseTest<FailLevel, Verbosity>::alloc(size);
+ }
+
+ static void free(void* mem)
+ {
+ BlastBaseTest<FailLevel, Verbosity>::free(mem);
+ }
+
+ static void testActorVisibleChunks(const Nv::Blast::Actor& actor, NvBlastLog)
+ {
+ const Nv::Blast::Asset& asset = *actor.getAsset();
+ const NvBlastChunk* chunks = asset.getChunks();
+
+ if (actor.isSubSupportChunk())
+ {
+ EXPECT_EQ(1, actor.getVisibleChunkCount());
+
+ const uint32_t firstVisibleChunkIndex = (uint32_t)Nv::Blast::Actor::VisibleChunkIt(actor);
+
+ EXPECT_EQ(actor.getIndex() - asset.m_graph.m_nodeCount, firstVisibleChunkIndex - asset.m_firstSubsupportChunkIndex);
+
+ // Make sure the visible chunk is subsupport
+ // Array of support flags
+ std::vector<bool> isSupport(asset.m_chunkCount, false);
+ for (uint32_t i = 0; i < asset.m_graph.m_nodeCount; ++i)
+ {
+ isSupport[asset.m_graph.getChunkIndices()[i]] = true;
+ }
+
+ // Climb hierarchy to find support chunk
+ uint32_t chunkIndex = firstVisibleChunkIndex;
+ while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>())
+ {
+ if (isSupport[chunkIndex])
+ {
+ break;
+ }
+ chunkIndex = chunks[chunkIndex].parentChunkIndex;
+ }
+
+ EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex));
+ }
+ else
+ {
+ // Array of visibility flags
+ std::vector<bool> isVisible(asset.m_chunkCount, false);
+ for (Nv::Blast::Actor::VisibleChunkIt i = actor; (bool)i; ++i)
+ {
+ isVisible[(uint32_t)i] = true;
+ }
+
+ // Mark visible nodes representing graph chunks
+ std::vector<bool> visibleChunkFound(asset.m_chunkCount, false);
+
+ // Make sure every graph chunk is represented by a visible chunk
+ for (Nv::Blast::Actor::GraphNodeIt i = actor; (bool)i; ++i)
+ {
+ const uint32_t graphNodeIndex = (uint32_t)i;
+ uint32_t chunkIndex = asset.m_graph.getChunkIndices()[graphNodeIndex];
+ // Climb hierarchy to find visible chunk
+ while (chunkIndex != Nv::Blast::invalidIndex<uint32_t>())
+ {
+ // Check that chunk owners are accurate
+ EXPECT_EQ(actor.getIndex(), actor.getFamilyHeader()->getChunkActorIndices()[chunkIndex]);
+ if (isVisible[chunkIndex])
+ {
+ visibleChunkFound[chunkIndex] = true;
+ break;
+ }
+ chunkIndex = chunks[chunkIndex].parentChunkIndex;
+ }
+ EXPECT_FALSE(Nv::Blast::isInvalidIndex(chunkIndex));
+ }
+
+ // Check that all visible chunks are accounted for
+ for (uint32_t i = 0; i < asset.m_chunkCount; ++i)
+ {
+ EXPECT_EQ(visibleChunkFound[i], isVisible[i]);
+ }
+
+ // Make sure that, if all siblings are intact, they are invisible
+ for (uint32_t i = 0; i < asset.m_chunkCount; ++i)
+ {
+ bool allIntact = true;
+ bool noneVisible = true;
+ if (chunks[i].firstChildIndex < asset.getUpperSupportChunkCount()) // Do not check subsupport
+ {
+ for (uint32_t j = chunks[i].firstChildIndex; j < chunks[i].childIndexStop; ++j)
+ {
+ allIntact = allIntact && actor.getFamilyHeader()->getChunkActorIndices()[j] == actor.getIndex();
+ noneVisible = noneVisible && !isVisible[j];
+ }
+ EXPECT_TRUE(!allIntact || noneVisible);
+ }
+ }
+ }
+ }
+
+ class DamageActorTask : public TaskDispatcher::Task
+ {
+ public:
+ DamageActorTask(NvBlastActor* actor, GeneratorAsset* asset, GeneratorAsset::Vec3 localPos, float minRadius, float maxRadius, float compressiveDamage, ActorTestFunction testFunction)
+ : m_asset(asset)
+ , m_localPos(localPos)
+ , m_minRadius(minRadius)
+ , m_maxRadius(maxRadius)
+ , m_compressiveDamage(compressiveDamage)
+ , m_testFunction(testFunction)
+ {
+ m_actors.insert(actor);
+ }
+
+ virtual void process()
+ {
+ blast(m_actors, m_asset, m_localPos, m_minRadius, m_maxRadius, m_compressiveDamage);
+
+ // Test individual actors
+ if (m_testFunction != nullptr)
+ {
+ for (std::set<NvBlastActor*>::iterator k = m_actors.begin(); k != m_actors.end(); ++k)
+ {
+ m_testFunction(*static_cast<Nv::Blast::Actor*>(*k), messageLog);
+ }
+ }
+ }
+
+ const std::set<NvBlastActor*>& getResult() const { return m_actors; }
+
+ private:
+ std::set<NvBlastActor*> m_actors;
+ GeneratorAsset* m_asset;
+ GeneratorAsset::Vec3 m_localPos;
+ float m_minRadius;
+ float m_maxRadius;
+ float m_compressiveDamage;
+ ActorTestFunction m_testFunction;
+
+ std::vector<NvBlastActor*> m_resultActors;
+ };
+
+ void damageLeafSupportActorsParallelized
+ (
+ uint32_t assetCount,
+ uint32_t minChunkCount,
+ uint32_t damageCount,
+ uint32_t threadCount,
+ ActorTestFunction actorTestFunction,
+ PostDamageTestFunction postDamageTestFunction
+ )
+ {
+ const float relativeDamageRadius = 0.05f;
+ const float compressiveDamage = 1.0f;
+
+ srand(0);
+
+ std::cout << "Asset # (out of " << assetCount << "): ";
+ for (uint32_t assetNum = 0; assetNum < assetCount; ++assetNum)
+ {
+ std::cout << assetNum + 1 << ".. ";
+ CubeAssetGenerator::Settings settings;
+ settings.extents = GeneratorAsset::Vec3(1, 1, 1);
+ CubeAssetGenerator::DepthInfo depthInfo;
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1);
+ depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags;
+ settings.depths.push_back(depthInfo);
+ uint32_t chunkCount = 1;
+ while (chunkCount < minChunkCount)
+ {
+ uint32_t chunkMul;
+ do
+ {
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)(1 + rand() % 4), (float)(1 + rand() % 4), (float)(1 + rand() % 4));
+ chunkMul = (uint32_t)(depthInfo.slicesPerAxis.x * depthInfo.slicesPerAxis.y * depthInfo.slicesPerAxis.z);
+ } while (chunkMul == 1);
+ chunkCount *= chunkMul;
+ settings.depths.push_back(depthInfo);
+ settings.extents = settings.extents * depthInfo.slicesPerAxis;
+ }
+ settings.depths.back().flag = NvBlastChunkDesc::SupportFlag; // Leaves are support
+
+ // Make largest direction unit size
+ settings.extents = settings.extents * (1.0f / std::max(settings.extents.x, std::max(settings.extents.y, settings.extents.z)));
+
+ // Create asset
+ GeneratorAsset testAsset;
+ CubeAssetGenerator::generate(testAsset, settings);
+
+ NvBlastAssetDesc desc;
+ desc.chunkDescs = &testAsset.solverChunks[0];
+ desc.chunkCount = (uint32_t)testAsset.solverChunks.size();
+ desc.bondDescs = &testAsset.solverBonds[0];
+ desc.bondCount = (uint32_t)testAsset.solverBonds.size();
+
+ std::vector<char> scratch;
+ scratch.resize((size_t)NvBlastGetRequiredScratchForCreateAsset(&desc, messageLog));
+ void* mem = alloc(NvBlastGetAssetMemorySize(&desc, messageLog));
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, &desc, &scratch[0], messageLog);
+ EXPECT_TRUE(asset != nullptr);
+
+ NvBlastActorDesc actorDesc;
+ actorDesc.initialBondHealths = actorDesc.initialSupportChunkHealths = nullptr;
+ actorDesc.uniformInitialBondHealth = actorDesc.uniformInitialLowerSupportChunkHealth = 1.0f;
+ void* fmem = alloc(NvBlastAssetGetFamilyMemorySize(asset, messageLog));
+ NvBlastFamily* family = NvBlastAssetCreateFamily(fmem, asset, nullptr); // Using zeroingAlloc in case actorTest compares memory blocks
+ scratch.resize((size_t)NvBlastFamilyGetRequiredScratchForCreateFirstActor(family, messageLog));
+ NvBlastActor* actor = NvBlastFamilyCreateFirstActor(family, &actorDesc, &scratch[0], messageLog);
+ EXPECT_TRUE(actor != nullptr);
+
+ // Run parallelized damage through TaskDispatcher
+ std::set<NvBlastActor*> resultActors;
+ {
+ uint32_t damageNum = 0;
+
+ // create DamageActorTask and it to dispatcher helper function
+ auto addDamageTaskFunction = [&](TaskDispatcher& dispatcher, NvBlastActor* actor)
+ {
+ GeneratorAsset::Vec3 localPos = settings.extents*GeneratorAsset::Vec3((float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f, (float)rand() / RAND_MAX - 0.5f);
+ auto newTask = std::unique_ptr<DamageActorTask>(new DamageActorTask(actor, &testAsset, localPos, relativeDamageRadius, relativeDamageRadius*1.2f, compressiveDamage, actorTestFunction));
+ dispatcher.addTask(std::move(newTask));
+ };
+
+ // on task finished function for dispatcher (main thread)
+ TaskDispatcher::OnTaskFinishedFunction onTaskFinishedFunction = [&](TaskDispatcher& dispatcher, std::unique_ptr<TaskDispatcher::Task> task) {
+ const DamageActorTask* damageTask = static_cast<const DamageActorTask*>(task.get());
+ const std::set<NvBlastActor*>& actors = damageTask->getResult();
+ for (NvBlastActor* actor : actors)
+ {
+ if (damageNum >= damageCount)
+ {
+ resultActors.insert(actor);
+ }
+ else
+ {
+ damageNum++;
+ addDamageTaskFunction(dispatcher, actor);
+ }
+ }
+ };
+
+ // create dispatcher, add first task and run
+ TaskDispatcher dispatcher(threadCount, onTaskFinishedFunction);
+ addDamageTaskFunction(dispatcher, actor);
+ dispatcher.process();
+ }
+
+ // Test fractured actor set
+ if (postDamageTestFunction)
+ {
+ std::vector<NvBlastActor*> actorArray(resultActors.begin(), resultActors.end());
+ postDamageTestFunction(actorArray, messageLog);
+ }
+
+ // Release remaining actors
+ for (std::set<NvBlastActor*>::iterator k = resultActors.begin(); k != resultActors.end(); ++k)
+ {
+ NvBlastActorDeactivate(*k, messageLog);
+ }
+ resultActors.clear();
+
+ const uint32_t actorCount = NvBlastFamilyGetActorCount(family, messageLog);
+ EXPECT_TRUE(actorCount == 0);
+
+ free(family);
+
+ // Release asset data
+ free(asset);
+ }
+ std::cout << "done.\n";
+ }
+};
+
+
+// Specializations
+typedef MultithreadingTest<NvBlastMessage::Error, 1> MultithreadingTestAllowWarnings;
+typedef MultithreadingTest<NvBlastMessage::Error, 1> MultithreadingTestStrict;
+
+
+TEST_F(MultithreadingTestStrict, MultithreadingTestDamageLeafSupportActorsTestVisibility)
+{
+ damageLeafSupportActorsParallelized(1, 1000, 50, 4, testActorVisibleChunks, nullptr);
+}
+
+TEST_F(MultithreadingTestStrict, MultithreadingTestDamageLeafSupportActors)
+{
+ damageLeafSupportActorsParallelized(1, 3000, 1000, 4, nullptr, nullptr);
+}
diff --git a/NvBlast/test/src/unit/SyncTests.cpp b/NvBlast/test/src/unit/SyncTests.cpp
new file mode 100644
index 0000000..425210d
--- /dev/null
+++ b/NvBlast/test/src/unit/SyncTests.cpp
@@ -0,0 +1,309 @@
+#include "TkBaseTest.h"
+
+#include "NvBlastExtSync.h"
+#include "NvBlastTkEvent.h"
+
+#include <map>
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ExtSync Tests
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class Base
+{
+public:
+ Base(TkTestStrict* test) : m_test(test)
+ {
+
+ }
+
+ void run(std::stringstream& finalState)
+ {
+ //////// initial setup ////////
+
+ m_test->createTestAssets();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_test->m_taskman;
+ m_group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(m_group != nullptr);
+
+ TkActorDesc adesc(m_test->testAssets[0]);
+
+ NvBlastID id;
+
+ TkActor* actor0 = fwk->createActor(adesc);
+ EXPECT_TRUE(actor0 != nullptr);
+ families[0] = &actor0->getFamily();
+ memcpy(id.data, "Mumble Jumble Bumble", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string)
+ families[0]->setID(id);
+ m_group->addActor(*actor0);
+
+ TkActor* actor1 = fwk->createActor(adesc);
+ EXPECT_TRUE(actor1 != nullptr);
+ families[1] = &actor1->getFamily();
+ memcpy(id.data, "buzzkillerdiller", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string)
+ families[1]->setID(id);
+ m_group->addActor(*actor1);
+
+
+ //////// server/client specific impl ////////
+
+ impl();
+
+
+ //////// write out framework final state ////////
+
+ finalState.clear();
+ for (auto family : families)
+ {
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (auto actor : actors)
+ {
+ finalState << actor->getVisibleChunkCount();
+ finalState << actor->getGraphNodeCount();
+ std::vector<uint32_t> chunkIndices(actor->getGraphNodeCount());
+ actor->getVisibleChunkIndices(chunkIndices.data(), (uint32_t)chunkIndices.size());
+
+ for (uint32_t chunkIndex : chunkIndices)
+ finalState << chunkIndex;
+ const float* bondHealths = actor->getBondHealths();
+ for (uint32_t i = 0; i < actor->getAsset()->getBondCount(); ++i)
+ finalState << bondHealths[i];
+ }
+ }
+
+
+ //////// release ////////
+
+ m_group->release();
+
+ for (auto family : families)
+ {
+ family->release();
+ }
+
+ m_test->releaseTestAssets();
+ }
+
+protected:
+ virtual void impl() = 0;
+
+ TkTestStrict* m_test;
+ TkGroup* m_group;
+ TkFamily* families[2];
+};
+
+
+class Server : public Base
+{
+public:
+ Server(TkTestStrict* test, std::vector<ExtSyncEvent*>& syncBuffer) : Base(test), m_syncBuffer(syncBuffer) {}
+
+protected:
+ virtual void impl() override
+ {
+ // create sync ext
+ ExtSync* sync = ExtSync::create();
+
+ // add sync as listener to family #1
+ families[1]->addListener(*sync);
+
+ // damage family #0 (make it split)
+ {
+ TkActor* actor;
+ families[0]->getActors(&actor, 1);
+ CSParams p(1, 0.0f);
+ actor->damage(m_test->getCubeSlicerProgram(), &p, sizeof(p), m_test->getDefaultMaterial());
+ }
+
+ // process
+ m_group->process();
+ m_group->sync();
+ EXPECT_EQ(families[0]->getActorCount(), 2);
+
+ // sync family #0
+ sync->syncFamily(*families[0]);
+
+ // add sync as listener to family #0
+ families[0]->addListener(*sync);
+
+ // damage family #0 (make it split fully)
+ {
+ TkActor* actor;
+ families[0]->getActors(&actor, 1, 1);
+ NvBlastExtRadialDamageDesc radialDamage = m_test->getRadialDamageDesc(0, 0, 0);
+ actor->damage(m_test->getFalloffProgram(), &radialDamage, sizeof(radialDamage), m_test->getDefaultMaterial());
+ }
+
+
+ // damage family 1 (just damage bonds health)
+ {
+ TkActor* actor;
+ families[1]->getActors(&actor, 1);
+ NvBlastExtRadialDamageDesc radialDamage = m_test->getRadialDamageDesc(0, 0, 0, 10.0f, 10.0f, 0.1f);
+ actor->damage(m_test->getFalloffProgram(), &radialDamage, sizeof(radialDamage), m_test->getDefaultMaterial());
+ }
+
+ // process
+ m_group->process();
+ m_group->sync();
+ EXPECT_EQ(families[0]->getActorCount(), 5);
+ EXPECT_EQ(families[1]->getActorCount(), 1);
+
+ // take sync buffer from sync
+ {
+ const ExtSyncEvent*const* buffer;
+ uint32_t size;
+ sync->acquireSyncBuffer(buffer, size);
+
+ m_syncBuffer.resize(size);
+ for (size_t i = 0; i < size; ++i)
+ {
+ m_syncBuffer[i] = buffer[i]->clone();
+ }
+
+ sync->releaseSyncBuffer();
+ }
+
+ //
+ families[0]->removeListener(*sync);
+ families[1]->removeListener(*sync);
+
+ //
+ sync->release();
+ }
+
+private:
+ std::vector<ExtSyncEvent*>& m_syncBuffer;
+};
+
+
+class Client : public Base, public TkEventListener
+{
+public:
+ Client(TkTestStrict* test, std::vector<ExtSyncEvent*>& syncBuffer) : Base(test), m_syncBuffer(syncBuffer) {}
+
+protected:
+
+ virtual void impl() override
+ {
+ ExtSync* sync = ExtSync::create();
+
+ // fill map
+ for (auto& family : families)
+ {
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ auto& actorsSet = m_actorsPerFamily[family];
+ for (auto actor : actors)
+ EXPECT_TRUE(actorsSet.insert(actor->getIndex()).second);
+ }
+
+ // subscribe
+ for (auto& family : families)
+ {
+ family->addListener(*this);
+ }
+
+ // apply sync buffer
+ sync->applySyncBuffer(*NvBlastTkFrameworkGet(), (const Nv::Blast::ExtSyncEvent**)m_syncBuffer.data(), static_cast<uint32_t>(m_syncBuffer.size()), m_group);
+
+ // check map
+ for (auto& family : families)
+ {
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ std::set<uint32_t> actorsSet;
+ for (auto actor : actors)
+ EXPECT_TRUE(actorsSet.insert(actor->getIndex()).second);
+ EXPECT_TRUE(m_actorsPerFamily[family] == actorsSet);
+ }
+
+ // unsubscribe
+ for (auto& family : families)
+ {
+ family->removeListener(*this);
+ }
+
+ m_group->process();
+ m_group->sync();
+
+ sync->release();
+ }
+
+ // listen for Split event and update actors map
+ virtual void receive(const TkEvent* events, uint32_t eventCount) override
+ {
+ for (size_t i = 0; i < eventCount; ++i)
+ {
+ const TkEvent& e = events[i];
+ switch (e.type)
+ {
+ case (TkEvent::Split) :
+ {
+ const TkSplitEvent* splitEvent = e.getPayload<TkSplitEvent>();
+ auto& actorsSet = m_actorsPerFamily[splitEvent->parentData.family];
+ if (!isInvalidIndex(splitEvent->parentData.index))
+ {
+ EXPECT_EQ((size_t)1, actorsSet.erase(splitEvent->parentData.index));
+ }
+ for (size_t i = 0; i < splitEvent->numChildren; ++i)
+ {
+ TkActor* a = splitEvent->children[i];
+ EXPECT_TRUE(actorsSet.insert(a->getIndex()).second);
+ }
+ break;
+ }
+ case (TkEvent::FractureCommand) :
+ {
+ break;
+ }
+ case (TkEvent::JointUpdate) :
+ {
+ FAIL();
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+private:
+ std::map<TkFamily*, std::set<uint32_t>> m_actorsPerFamily;
+ std::vector<ExtSyncEvent*>& m_syncBuffer;
+};
+
+TEST_F(TkTestStrict, SyncTest1)
+{
+ this->createFramework();
+
+ std::vector<ExtSyncEvent*> syncBuffer;
+
+ std::stringstream serverFinalState;
+ {
+ Server s(this, syncBuffer);
+ s.run(serverFinalState);
+ }
+ EXPECT_TRUE(syncBuffer.size() > 0);
+
+ std::stringstream clientFinalState;
+ {
+ Client c(this, syncBuffer);
+ c.run(clientFinalState);
+ }
+
+ for (auto e : syncBuffer)
+ {
+ e->release();
+ }
+ syncBuffer.clear();
+
+ EXPECT_EQ(serverFinalState.str(), clientFinalState.str());
+
+ this->releaseFramework();
+}
diff --git a/NvBlast/test/src/unit/TkCompositeTests.cpp b/NvBlast/test/src/unit/TkCompositeTests.cpp
new file mode 100644
index 0000000..60fbe49
--- /dev/null
+++ b/NvBlast/test/src/unit/TkCompositeTests.cpp
@@ -0,0 +1,739 @@
+#include "TkBaseTest.h"
+
+#include <map>
+#include <random>
+#include <algorithm>
+
+#include "PsMemoryBuffer.h"
+
+#include "NvBlastTkSerializable.h"
+
+#include "NvBlastTime.h"
+
+
+/*
+Composite and joint tests:
+
+0) Test serialization of composites and assemblies
+
+1) Create assembly, actors and joints should be created automatically
+
+2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched
+
+3) Joint update events should be fired when attached actors change
+
+4) Joint delete events should be fired when at least one attached actor is deleted
+
+5) Creating a composite from assets with internal joints should have expected behaviors (1-4) above
+*/
+
+
+struct Composite
+{
+ std::vector<TkActorDesc> m_actorDescs;
+ std::vector<physx::PxTransform> m_relTMs;
+ std::vector<TkJointDesc> m_jointDescs;
+};
+
+
+template<int FailLevel, int Verbosity>
+class TkCompositeTest : public TkBaseTest<FailLevel, Verbosity>
+{
+public:
+ virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) override
+ {
+ void* ptr = TkBaseTest<FailLevel, Verbosity>::allocate(size, typeName, filename, line);
+ return ptr;
+ }
+
+ virtual void deallocate(void* ptr) override
+ {
+ return TkBaseTest<FailLevel, Verbosity>::deallocate(ptr);
+ }
+
+
+ // Composite/joint tests
+ void createAssembly(std::vector<TkActor*>& actors, std::vector<TkJoint*>& joints, bool createNRFJoints)
+ {
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ actors.resize(4, nullptr);
+ actors[0] = fw->createActor(TkActorDesc(testAssets[0]));
+ actors[1] = fw->createActor(TkActorDesc(testAssets[0]));
+ actors[2] = fw->createActor(TkActorDesc(testAssets[1]));
+ actors[3] = fw->createActor(TkActorDesc(testAssets[1]));
+
+ std::vector<TkFamily*> families(4);
+ families[0] = &actors[0]->getFamily();
+ families[1] = &actors[1]->getFamily();
+ families[2] = &actors[2]->getFamily();
+ families[3] = &actors[3]->getFamily();
+
+ EXPECT_FALSE(actors[0] == nullptr);
+ EXPECT_FALSE(actors[1] == nullptr);
+ EXPECT_FALSE(actors[2] == nullptr);
+ EXPECT_FALSE(actors[3] == nullptr);
+
+ const TkJointDesc jointDescsNoNRF[8] =
+ {
+ // Actor indices, chunk indices, attach position in the composite frame
+ { { families[0], families[1] }, { 6, 5 }, { PxVec3(0.0f, -1.5f, 0.5f), PxVec3(0.0f, -1.5f, 0.5f) } },
+ { { families[0], families[1] }, { 4, 3 }, { PxVec3(0.0f, -0.5f, -0.5f), PxVec3(0.0f, -0.5f, -0.5f) } },
+
+ { { families[0], families[2] }, { 8, 6 }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } },
+ { { families[0], families[2] }, { 3, 1 }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } },
+
+ { { families[1], families[3] }, { 7, 5 }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } },
+ { { families[1], families[3] }, { 4, 2 }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } },
+
+ { { families[2], families[3] }, { 8, 7 }, { PxVec3(0.0f, 1.5f, 0.5f), PxVec3(0.0f, 1.5f, 0.5f) } },
+ { { families[2], families[3] }, { 2, 1 }, { PxVec3(0.0f, 0.5f, -0.5f), PxVec3(0.0f, 0.5f, -0.5f) } }
+ };
+
+ const TkJointDesc jointDescsWithNRF[12] =
+ {
+ // Actor indices, chunk indices, attach position in the composite frame
+ { { families[0], families[1] }, { 6, 5 }, { PxVec3(0.0f, -1.5f, 0.5f), PxVec3(0.0f, -1.5f, 0.5f) } },
+ { { families[0], families[1] }, { 4, 3 }, { PxVec3(0.0f, -0.5f, -0.5f), PxVec3(0.0f, -0.5f, -0.5f) } },
+
+ { { families[0], nullptr }, { 8, 0xFFFFFFFF }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } },
+ { { families[0], nullptr }, { 3, 0xFFFFFFFF }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } },
+
+ { { nullptr, families[2] }, { 0xFFFFFFFF, 6 }, { PxVec3(-0.5f, 0.0f, 0.5f), PxVec3(-0.5f, 0.0f, 0.5f) } },
+ { { nullptr, families[2] }, { 0xFFFFFFFF, 1 }, { PxVec3(-1.5f, 0.0f, -0.5f), PxVec3(-1.5f, 0.0f, -0.5f) } },
+
+ { { families[1], nullptr }, { 7, 0xFFFFFFFF }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } },
+ { { families[1], nullptr }, { 4, 0xFFFFFFFF }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } },
+
+ { { nullptr, families[3] }, { 0xFFFFFFFF, 5 }, { PxVec3(0.5f, 0.0f, 0.5f), PxVec3(0.5f, 0.0f, 0.5f) } },
+ { { nullptr, families[3] }, { 0xFFFFFFFF, 2 }, { PxVec3(1.0f, 0.0f, -0.5f), PxVec3(1.0f, 0.0f, -0.5f) } },
+
+ { { families[2], families[3] }, { 8, 7 }, { PxVec3(0.0f, 1.5f, 0.5f), PxVec3(0.0f, 1.5f, 0.5f) } },
+ { { families[2], families[3] }, { 2, 1 }, { PxVec3(0.0f, 0.5f, -0.5f), PxVec3(0.0f, 0.5f, -0.5f), } }
+ };
+
+ const TkJointDesc* jointDescs = createNRFJoints ? jointDescsWithNRF : jointDescsNoNRF;
+ const int jointCount = createNRFJoints ? 12 : 8;
+
+ joints.resize(jointCount, nullptr);
+ for (int i = 0; i < jointCount; ++i)
+ {
+ joints[i] = fw->createJoint(jointDescs[i]);
+ EXPECT_FALSE(joints[i] == nullptr);
+ }
+ }
+
+ void familySerialization(std::vector<TkFamily*>& families, TestFamilyTracker& tracker)
+ {
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ PsMemoryBuffer* membuf = PX_NEW(PsMemoryBuffer);
+ EXPECT_TRUE(membuf != nullptr);
+ if (membuf == nullptr)
+ {
+ return;
+ }
+
+ std::vector<TkFamily*> oldFamilies = families;
+
+ for (size_t familyNum = 0; familyNum < families.size(); ++familyNum)
+ {
+ families[familyNum]->serialize(*membuf);
+ }
+
+ for (size_t familyNum = 0; familyNum < families.size(); ++familyNum)
+ {
+ TkFamily* f = families[familyNum];
+
+ std::vector<TkActor*> actors(f->getActorCount());
+ f->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (auto a : actors)
+ {
+ tracker.eraseActor(a);
+ }
+
+ f->release();
+ families[familyNum] = nullptr;
+ }
+
+ for (size_t familyNum = 0; familyNum < families.size(); ++familyNum)
+ {
+ TkFamily* f = reinterpret_cast<TkFamily*>(fw->deserialize(*membuf));
+ f->addListener(tracker);
+ families[familyNum] = f;
+ }
+
+ for (size_t familyNum = 0; familyNum < families.size(); ++familyNum)
+ {
+ TkFamily* f = families[familyNum];
+
+ std::vector<TkActor*> actors(f->getActorCount());
+ f->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (auto a : actors)
+ {
+ tracker.insertActor(a);
+
+ std::vector<TkJoint*> joints(a->getJointCount());
+ a->getJoints(joints.data(), (uint32_t)joints.size());
+
+ for (auto j : joints)
+ {
+ const TkJointData jd = j->getData();
+ if (jd.actors[0] != jd.actors[1])
+ {
+ tracker.joints.insert(j);
+ }
+ }
+ }
+ }
+
+ membuf->release();
+ }
+
+ void recollectActors(std::vector<TkFamily*>& families, std::vector<TkActor*>& actors)
+ {
+ uint32_t totalActorCount = 0;
+ for (auto family : families)
+ {
+ EXPECT_LE(family->getActorCount() + totalActorCount, actors.size());
+ totalActorCount += family->getActors(actors.data() + totalActorCount, static_cast<uint32_t>(actors.size()) - totalActorCount);
+ }
+ }
+
+ void assemblyCreateAndRelease(bool createNRFJoints, bool serializationTest)
+ {
+ createFramework();
+ createTestAssets();
+
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ const TkType* familyType = fw->getType(TkTypeIndex::Family);
+ EXPECT_TRUE(familyType != nullptr);
+
+ TestFamilyTracker tracker;
+
+ std::vector<TkFamily*> families1;
+ std::vector<TkFamily*> families2;
+
+ // Create one assembly
+ std::vector<TkActor*> actors1;
+ std::vector<TkJoint*> joints1;
+ createAssembly(actors1, joints1, createNRFJoints);
+ tracker.joints.insert(joints1.begin(), joints1.end());
+
+ // Create another assembly
+ std::vector<TkActor*> actors2;
+ std::vector<TkJoint*> joints2;
+ createAssembly(actors2, joints2, createNRFJoints);
+ tracker.joints.insert(joints2.begin(), joints2.end());
+
+ // Store families and fill group
+ for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum)
+ {
+ TkFamily& family = actors1[actorNum]->getFamily();
+ families1.push_back(&family);
+ family.addListener(tracker);
+ }
+ for (size_t actorNum = 0; actorNum < actors2.size(); ++actorNum)
+ {
+ TkFamily& family = actors2[actorNum]->getFamily();
+ families2.push_back(&family);
+ family.addListener(tracker);
+ }
+
+ if (serializationTest)
+ {
+ familySerialization(families1, tracker);
+ recollectActors(families1, actors1);
+ familySerialization(families2, tracker);
+ recollectActors(families2, actors2);
+ }
+
+ EXPECT_EQ(joints1.size() + joints2.size(), tracker.joints.size());
+
+ // Release 1st assembly's actors
+ for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum)
+ {
+ actors1[actorNum]->release();
+ }
+
+ if (serializationTest)
+ {
+ familySerialization(families2, tracker);
+ recollectActors(families2, actors2);
+ }
+
+ EXPECT_EQ(joints2.size(), tracker.joints.size());
+
+ // Release 2nd assembly's actors
+ for (size_t actorNum = 0; actorNum < actors1.size(); ++actorNum)
+ {
+ actors2[actorNum]->release();
+ }
+
+ EXPECT_EQ(0, tracker.joints.size());
+
+ releaseTestAssets();
+ releaseFramework();
+ }
+
+ void assemblyInternalJoints(bool testAssemblySerialization)
+ {
+ createFramework();
+ createTestAssets(true); // Create assets with internal joints
+
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ TestFamilyTracker tracker;
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fw->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ TkActorDesc adesc(testAssets[0]);
+
+ TkActor* actor1 = fw->createActor(adesc);
+ EXPECT_TRUE(actor1 != nullptr);
+ tracker.insertActor(actor1);
+
+ actor1->getFamily().addListener(tracker);
+
+ TkFamily* family = &actor1->getFamily();
+
+ group->addActor(*actor1);
+
+ CSParams p(2, 0.0f);
+ actor1->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial());
+
+ EXPECT_EQ((size_t)0, tracker.joints.size());
+
+ group->process();
+ group->sync();
+
+ if (testAssemblySerialization)
+ {
+ std::vector<TkFamily*> families;
+ families.push_back(family);
+ familySerialization(families, tracker);
+ family = families[0];
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (TkActor* actor : actors)
+ {
+ group->addActor(*actor);
+ }
+ }
+
+ EXPECT_EQ((size_t)2, family->getActorCount());
+ EXPECT_EQ((size_t)4, tracker.joints.size()); // 2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched
+
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+
+ for (TkJoint* joint : tracker.joints)
+ {
+ TkJointData jd = joint->getData();
+ EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[0]));
+ EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[1]));
+ }
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ for (TkActor* actor : actors)
+ {
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial());
+ }
+
+ group->process();
+ group->sync();
+
+ if (testAssemblySerialization)
+ {
+ std::vector<TkFamily*> families;
+ families.push_back(family);
+ familySerialization(families, tracker);
+ family = families[0];
+ }
+
+ EXPECT_EQ((size_t)8, family->getActorCount());
+ EXPECT_EQ((size_t)4, tracker.joints.size());
+
+ // 3) Joint update events should be fired when attached actors change
+
+ actors.resize(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+
+ for (TkJoint* joint : tracker.joints)
+ {
+ TkJointData jd = joint->getData();
+ EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[0]));
+ EXPECT_FALSE(actors.end() == std::find(actors.begin(), actors.end(), jd.actors[1]));
+ }
+
+ for (TkActor* actor : actors)
+ {
+ actor->release();
+ }
+
+ EXPECT_EQ((size_t)0, tracker.joints.size()); // 4) Joint delete events should be fired when at least one attached actor is deleted
+
+ group->release();
+
+ releaseTestAssets();
+ releaseFramework();
+ }
+
+ void assemblyCompositeWithInternalJoints(bool createNRFJoints, bool serializationTest)
+ {
+ createFramework();
+ createTestAssets(true); // Create assets with internal joints
+
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ const TkType* familyType = fw->getType(TkTypeIndex::Family);
+ EXPECT_TRUE(familyType != nullptr);
+
+ if (familyType == nullptr)
+ {
+ return;
+ }
+
+ TestFamilyTracker tracker;
+
+ std::vector<TkFamily*> families;
+
+ // Create assembly
+ std::vector<TkActor*> actors;
+ std::vector<TkJoint*> joints;
+ createAssembly(actors, joints, createNRFJoints);
+ tracker.joints.insert(joints.begin(), joints.end());
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fw->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ for (size_t i = 0; i < actors.size(); ++i)
+ {
+ TkFamily& family = actors[i]->getFamily();
+ families.push_back(&family);
+ family.addListener(tracker);
+ tracker.insertActor(actors[i]);
+ group->addActor(*actors[i]);
+ }
+
+ if (serializationTest)
+ {
+ familySerialization(families, tracker);
+ recollectActors(families, actors);
+ for (auto actor : actors)
+ {
+ group->addActor(*actor);
+ }
+ }
+
+ EXPECT_EQ((size_t)4, actors.size());
+
+ const size_t compJointCount = createNRFJoints ? (size_t)12 : (size_t)8;
+
+ EXPECT_EQ(compJointCount, tracker.joints.size());
+
+ size_t totalActorCount = 0;
+ for (uint32_t i = 0; i < 4; ++i)
+ {
+ CSParams p(2, 0.0f);
+ actors[i]->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ if (serializationTest)
+ {
+ familySerialization(families, tracker);
+ for (size_t j = 0; j < families.size(); ++j)
+ {
+ TkFamily* family = families[j];
+ std::vector<TkActor*> a(family->getActorCount());
+ family->getActors(a.data(), static_cast<uint32_t>(a.size()));
+ for (auto actor : a)
+ {
+ group->addActor(*actor);
+ }
+ EXPECT_TRUE(j <= i || a.size() == 1);
+ if (j > i && a.size() == 1)
+ {
+ actors[j] = a[0];
+ }
+ }
+ }
+
+ EXPECT_EQ((size_t)2, families[i]->getActorCount());
+ EXPECT_EQ((size_t)(compJointCount + 4 * (i + 1)), tracker.joints.size()); // Four joints created per actor
+
+ totalActorCount += families[i]->getActorCount();
+ }
+
+ actors.resize(totalActorCount);
+ totalActorCount = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ families[i]->getActors(actors.data() + totalActorCount, families[i]->getActorCount());
+ totalActorCount += families[i]->getActorCount();
+ }
+
+ for (TkJoint* joint : tracker.joints)
+ {
+ TkJointData jd = joint->getData();
+ EXPECT_TRUE(jd.actors[0] == nullptr || actors.end() != std::find(actors.begin(), actors.end(), jd.actors[0]));
+ EXPECT_TRUE(jd.actors[1] == nullptr || actors.end() != std::find(actors.begin(), actors.end(), jd.actors[1]));
+ }
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ for (TkActor* actor : actors)
+ {
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial());
+ }
+
+ group->process();
+ group->sync();
+
+ totalActorCount = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ totalActorCount += families[i]->getActorCount();
+ }
+
+ if (serializationTest)
+ {
+ familySerialization(families, tracker);
+ }
+
+ EXPECT_EQ((size_t)32, totalActorCount);
+ EXPECT_EQ(compJointCount + (size_t)16, tracker.joints.size());
+
+ actors.resize(totalActorCount);
+ totalActorCount = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ families[i]->getActors(actors.data() + totalActorCount, families[i]->getActorCount());
+ totalActorCount += families[i]->getActorCount();
+ }
+
+ // 3) Joint update events should be fired when attached actors change
+
+ for (TkActor* actor : actors)
+ {
+ actor->release();
+ }
+
+ EXPECT_EQ((size_t)0, tracker.joints.size()); // 4) Joint delete events should be fired when at least one attached actor is deleted
+
+ group->release();
+
+ releaseTestAssets();
+ releaseFramework();
+ }
+
+ void assemblyExternalJoints_MultiFamilyDamage(bool explicitJointRelease = true)
+ {
+ createFramework();
+
+ const NvBlastChunkDesc chunkDescs[3] =
+ {
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 4.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { { 0.0f,-1.0f, 0.0f }, 2.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.0f, 1.0f, 0.0f }, 2.0f, 0, NvBlastChunkDesc::SupportFlag, 2 }
+ };
+
+ const NvBlastBondDesc bondDesc =
+// chunks normal area centroid userData
+ { { 1, 2 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.0f, 0.0f, 0.0f }, 0 } };
+
+ TkFramework* framework = NvBlastTkFrameworkGet();
+
+ TestFamilyTracker tracker;
+
+ TkAssetDesc desc;
+ desc.chunkCount = 3;
+ desc.chunkDescs = chunkDescs;
+ desc.bondCount = 1;
+ desc.bondDescs = &bondDesc;
+ desc.bondFlags = nullptr;
+ TkAsset* asset = framework->createAsset(desc);
+ EXPECT_TRUE(asset != nullptr);
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = framework->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ TkActorDesc adesc(asset);
+ TkActor* actor1 = framework->createActor(adesc);
+ EXPECT_TRUE(actor1 != nullptr);
+ TkActor* actor2 = framework->createActor(adesc);
+ EXPECT_TRUE(actor2 != nullptr);
+
+ group->addActor(*actor1);
+ group->addActor(*actor2);
+
+ TkFamily* family1 = &actor1->getFamily();
+ TkFamily* family2 = &actor2->getFamily();
+
+ family1->addListener(tracker);
+ family2->addListener(tracker);
+ tracker.insertActor(actor1);
+ tracker.insertActor(actor2);
+
+ TkJointDesc jdesc;
+ jdesc.families[0] = family1;
+ jdesc.families[1] = family2;
+ jdesc.chunkIndices[0] = 2;
+ jdesc.chunkIndices[1] = 1;
+ jdesc.attachPositions[0] = PxVec3(0.0f, 1.0f, 0.0f);
+ jdesc.attachPositions[1] = PxVec3(0.0f, -1.0f, 0.0f);
+ TkJoint* joint = framework->createJoint(jdesc);
+ EXPECT_TRUE(joint != nullptr);
+ tracker.joints.insert(joint);
+
+ NvBlastExtRadialDamageDesc radialDamage1 = getRadialDamageDesc(0, 1, 0, 2, 2);
+ actor1->damage(getFalloffProgram(), &radialDamage1, sizeof(radialDamage1), getDefaultMaterial());
+ NvBlastExtRadialDamageDesc radialDamage2 = getRadialDamageDesc(0, -1, 0, 2, 2);
+ actor2->damage(getFalloffProgram(), &radialDamage2, sizeof(radialDamage2), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ TkActor* actors1[2];
+ TkActor* actors2[2];
+ EXPECT_EQ(2, family1->getActors(actors1, 2));
+ EXPECT_EQ(2, family2->getActors(actors2, 2));
+
+ const TkJointData jdata = joint->getData();
+ EXPECT_TRUE(jdata.actors[0] != nullptr);
+ EXPECT_TRUE(jdata.actors[1] != nullptr);
+ EXPECT_TRUE(&jdata.actors[0]->getFamily() == family1);
+ EXPECT_TRUE(&jdata.actors[1]->getFamily() == family2);
+
+ // Clean up
+ if (explicitJointRelease)
+ {
+ joint->release();
+ family2->release();
+ family1->release();
+ asset->release();
+ releaseFramework();
+ }
+ else
+ {
+ EXPECT_EQ(1, tracker.joints.size());
+ releaseFramework();
+ // Commenting these out - but shouldn't we be sending delete events when we release the framework?
+// EXPECT_EQ(0, tracker.joints.size());
+// EXPECT_EQ(0, tracker.actors.size());
+ }
+ }
+
+protected:
+ // http://clang.llvm.org/compatibility.html#dep_lookup_bases
+ // http://stackoverflow.com/questions/6592512/templates-parent-class-member-variables-not-visible-in-inherited-class
+
+ using TkBaseTest<FailLevel, Verbosity>::testAssets;
+ using TkBaseTest<FailLevel, Verbosity>::m_taskman;
+ using TkBaseTest<FailLevel, Verbosity>::createFramework;
+ using TkBaseTest<FailLevel, Verbosity>::releaseFramework;
+ using TkBaseTest<FailLevel, Verbosity>::createTestAssets;
+ using TkBaseTest<FailLevel, Verbosity>::releaseTestAssets;
+ using TkBaseTest<FailLevel, Verbosity>::getCubeSlicerProgram;
+ using TkBaseTest<FailLevel, Verbosity>::getDefaultMaterial;
+ using TkBaseTest<FailLevel, Verbosity>::getRadialDamageDesc;
+ using TkBaseTest<FailLevel, Verbosity>::getFalloffProgram;
+};
+
+
+typedef TkCompositeTest<NvBlastMessage::Error, 1> TkCompositeTestAllowWarnings;
+typedef TkCompositeTest<NvBlastMessage::Error, 1> TkCompositeTestStrict;
+
+
+/*
+1) Create assembly, actors and joints should be created automatically
+*/
+
+TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_NoNRFJoints_NoSerialization)
+{
+ assemblyCreateAndRelease(false, false);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_NoNRFJoints_AssemblySerialization)
+{
+ assemblyCreateAndRelease(false, true);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_WithNRFJoints_NoSerialization)
+{
+ assemblyCreateAndRelease(true, false);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCreateAndRelease_WithNRFJoints_AssemblySerialization)
+{
+ assemblyCreateAndRelease(true, true);
+}
+
+
+/**
+2) Create an actor with internal joints. Splitting the actor should cause joint create events to be dispatched
+
+3) Joint update events should be fired when attached actors change
+
+4) Joint delete events should be fired when at least one attached actor is deleted
+*/
+
+TEST_F(TkCompositeTestStrict, AssemblyInternalJoints_NoSerialization)
+{
+ assemblyInternalJoints(false);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyInternalJoints_AssemblySerialization)
+{
+ assemblyInternalJoints(true);
+}
+
+
+/**
+5) Creating a composite from assets with internal joints should have expected behaviors (1-4) above
+*/
+
+TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_NoNRFJoints_NoSerialization)
+{
+ assemblyCompositeWithInternalJoints(false, false);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_NoNRFJoints_AssemblySerialization)
+{
+ assemblyCompositeWithInternalJoints(false, true);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_WithNRFJoints_NoSerialization)
+{
+ assemblyCompositeWithInternalJoints(true, false);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyCompositeWithInternalJoints_WithNRFJoints_AssemblySerialization)
+{
+ assemblyCompositeWithInternalJoints(true, true);
+}
+
+
+/*
+More tests
+*/
+
+TEST_F(TkCompositeTestStrict, AssemblyExternalJoints_MultiFamilyDamage)
+{
+ assemblyExternalJoints_MultiFamilyDamage(true);
+}
+
+TEST_F(TkCompositeTestStrict, AssemblyExternalJoints_MultiFamilyDamage_AutoJointRelease)
+{
+ assemblyExternalJoints_MultiFamilyDamage(false);
+}
diff --git a/NvBlast/test/src/unit/TkTests.cpp b/NvBlast/test/src/unit/TkTests.cpp
new file mode 100644
index 0000000..045b0c9
--- /dev/null
+++ b/NvBlast/test/src/unit/TkTests.cpp
@@ -0,0 +1,1528 @@
+#include "TkBaseTest.h"
+
+#include <map>
+#include <random>
+#include <algorithm>
+
+#include "PsMemoryBuffer.h"
+
+#include "NvBlastTkSerializable.h"
+
+#include "NvBlastTime.h"
+
+
+struct ExpectedVisibleChunks
+{
+ ExpectedVisibleChunks() :numActors(0), numChunks(0) {}
+ ExpectedVisibleChunks(size_t a, size_t c) :numActors(a), numChunks(c) {}
+ size_t numActors; size_t numChunks;
+};
+
+void testResults(std::vector<TkFamily*>& families, std::map<TkFamily*, ExpectedVisibleChunks>& expectedVisibleChunks)
+{
+ size_t numActors = 0;
+ for (TkFamily* fam : families)
+ {
+ auto ex = expectedVisibleChunks[fam];
+ EXPECT_EQ(ex.numActors, fam->getActorCount());
+ numActors += ex.numActors;
+ std::vector<TkActor*> actors(fam->getActorCount());
+ fam->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (TkActor* actor : actors)
+ {
+ EXPECT_EQ(ex.numChunks, actor->getVisibleChunkCount());
+ }
+ }
+
+ size_t numActorsExpected = 0;
+ for (auto expected : expectedVisibleChunks)
+ {
+ numActorsExpected += expected.second.numActors;
+ }
+
+ EXPECT_EQ(numActorsExpected, numActors);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Tests
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+TEST_F(TkTestStrict, CreateFramework)
+{
+ createFramework();
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, CreateAsset)
+{
+ createFramework();
+
+ createTestAssets();
+ releaseTestAssets();
+
+ releaseFramework();
+}
+
+#if USE_PHYSX_DISPATCHER
+TEST_F(TkTestStrict, DISABLED_MemLeak)
+{
+ PxFoundation* pxFoundation = PxCreateFoundation(PX_FOUNDATION_VERSION, *this, *this);
+ PxU32 affinity[] = { 1, 2, 4, 8 };
+ PxDefaultCpuDispatcher* cpuDispatcher = PxDefaultCpuDispatcherCreate(4, affinity);
+ cpuDispatcher->setRunProfiled(false);
+ PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, cpuDispatcher, nullptr);
+
+ cpuDispatcher->release();
+ taskman->release();
+ pxFoundation->release();
+}
+#endif
+
+TEST_F(TkTestAllowWarnings, ActorDamageNoGroup)
+{
+ createFramework();
+ createTestAssets();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = testAssets[0];
+ TkActor* actor = fwk->createActor(actorDesc);
+
+ const size_t bondFractureCount = 4;
+ NvBlastFractureBuffers commands;
+ NvBlastBondFractureData bdata[bondFractureCount];
+ for (uint32_t i = 0; i < bondFractureCount; i++)
+ {
+ bdata[i].nodeIndex0 = 2 * i + 0;
+ bdata[i].nodeIndex1 = 2 * i + 1;
+ bdata[i].health = 1.0f;
+ }
+ commands.bondFractureCount = bondFractureCount;
+ commands.bondFractures = bdata;
+ commands.chunkFractureCount = 0;
+ commands.chunkFractures = nullptr;
+ actor->applyFracture(&commands, &commands);
+
+ TkFamily& family = actor->getFamily();
+
+ EXPECT_TRUE(commands.bondFractureCount == 4);
+ EXPECT_TRUE(actor->isPending());
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ group->addActor(*actor);
+
+ group->process();
+ group->sync(true);
+
+ EXPECT_FALSE(actor->isPending());
+ EXPECT_EQ(2, family.getActorCount());
+
+ releaseFramework();
+}
+
+TEST_F(TkTestAllowWarnings, ActorDamageGroup)
+{
+ TEST_ZONE_BEGIN("ActorDamageGroup");
+
+ createFramework();
+ createTestAssets();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ TestFamilyTracker ftrack1, ftrack2;
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0);
+
+ std::vector<TkFamily*> families;
+ TkFamily* trackedFamily;
+ std::map<TkFamily*, ExpectedVisibleChunks> expectedVisibleChunks;
+
+ {
+ TkActorDesc adesc(testAssets[0]);
+
+ TkActor* actor1 = fwk->createActor(adesc);
+ EXPECT_TRUE(actor1 != nullptr);
+
+ TkActor* actor2 = fwk->createActor(adesc);
+ EXPECT_TRUE(actor2 != nullptr);
+
+
+ expectedVisibleChunks[&actor1->getFamily()] = ExpectedVisibleChunks(8, 1); // full damage
+ expectedVisibleChunks[&actor2->getFamily()] = ExpectedVisibleChunks(1, 1); // not split
+
+ GeneratorAsset cube;
+ generateCube(cube, 5, 2);
+ TkAssetDesc assetDesc;
+ assetDesc.bondCount = (uint32_t)cube.solverBonds.size();
+ assetDesc.bondDescs = cube.solverBonds.data();
+ assetDesc.chunkCount = (uint32_t)cube.chunks.size();
+ assetDesc.chunkDescs = cube.solverChunks.data();
+ assetDesc.bondFlags = nullptr;
+
+ TkAsset* cubeAsset = fwk->createAsset(assetDesc);
+ testAssets.push_back(cubeAsset);
+
+ TkActorDesc cubeAD(cubeAsset);
+
+ TkActor* cubeActor1 = fwk->createActor(cubeAD);
+ EXPECT_TRUE(cubeActor1 != nullptr);
+
+ trackedFamily = &cubeActor1->getFamily();
+ cubeActor1->getFamily().addListener(ftrack1);
+
+ TkActor* cubeActor2 = fwk->createActor(cubeAD);
+ EXPECT_TRUE(cubeActor2 != nullptr);
+
+ CSParams p(0, 0.0f);
+
+ expectedVisibleChunks[&cubeActor1->getFamily()] = ExpectedVisibleChunks(2, 4); // split in 2, 4 chunks each
+ expectedVisibleChunks[&cubeActor2->getFamily()] = ExpectedVisibleChunks(1, 1); // not split
+
+ ftrack1.insertActor(cubeActor1);
+ ftrack2.insertActor(actor1);
+
+ actor1->getFamily().addListener(ftrack2);
+
+ TEST_ZONE_BEGIN("add to groups");
+ group->addActor(*cubeActor1);
+ group->addActor(*cubeActor2);
+ group->addActor(*actor1);
+ group->addActor(*actor2);
+ TEST_ZONE_END("add to groups");
+
+ families.push_back(&cubeActor1->getFamily());
+ families.push_back(&cubeActor2->getFamily());
+ families.push_back(&actor1->getFamily());
+ families.push_back(&actor2->getFamily());
+
+ cubeActor1->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial());
+ actor1->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial());
+ }
+
+ EXPECT_FALSE(group->sync(true));
+ EXPECT_FALSE(group->sync(false));
+
+ group->process();
+ group->sync();
+
+ testResults(families, expectedVisibleChunks);
+
+
+ {
+ std::vector<TkActor*> actors(trackedFamily->getActorCount());
+ trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (TkActor* actor : actors)
+ {
+ CSParams p(1, 0.0f);
+ actor->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial());
+ }
+ }
+ expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4, 2);
+
+ group->process();
+ group->sync();
+
+ testResults(families, expectedVisibleChunks);
+
+
+ {
+ std::vector<TkActor*> actors(trackedFamily->getActorCount());
+ trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (TkActor* actor : actors)
+ {
+ CSParams p(2, 0.0f);
+ actor->damage(getCubeSlicerProgram(), &p, sizeof(p), getDefaultMaterial());
+ }
+ }
+
+ expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(8, 1);
+
+ group->process();
+ group->sync();
+
+ testResults(families, expectedVisibleChunks);
+
+
+ {
+ std::vector<TkActor*> actors(trackedFamily->getActorCount());
+ trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ TEST_ZONE_BEGIN("damage");
+ for (TkActor* actor : actors)
+ {
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial());
+ }
+ TEST_ZONE_END("damage");
+ }
+ expectedVisibleChunks[trackedFamily] = ExpectedVisibleChunks(4096, 1);
+
+ group->process();
+ while (!group->sync(true));
+
+ testResults(families, expectedVisibleChunks);
+
+
+
+ {
+ std::vector<TkActor*> actors(trackedFamily->getActorCount());
+ trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ TEST_ZONE_BEGIN("damage");
+ for (TkActor* actor : actors)
+ {
+ actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial());
+ }
+ TEST_ZONE_END("damage");
+ }
+
+ group->process();
+ while (!group->sync(true))
+ ;
+
+
+
+ {
+ std::vector<TkActor*> actors(trackedFamily->getActorCount());
+ trackedFamily->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ TEST_ZONE_BEGIN("damage");
+ for (TkActor* actor : actors)
+ {
+ actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial());
+ }
+ TEST_ZONE_END("damage");
+ }
+
+ group->process();
+ while (!group->sync(true));
+
+ group->release();
+
+ TEST_ZONE_BEGIN("family release");
+ trackedFamily->release();
+ TEST_ZONE_END("family release");
+
+ releaseTestAssets();
+ releaseFramework();
+
+ TEST_ZONE_END("ActorDamageGroup");
+}
+
+
+TEST_F(TkTestAllowWarnings, ActorDamageMultiGroup)
+{
+ createFramework();
+ createTestAssets();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ TestFamilyTracker ftrack1, ftrack2;
+
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group0 = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group0 != nullptr);
+ TkGroup* group1 = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group1 != nullptr);
+
+ std::vector<TkFamily*> families(2);
+ std::map<TkFamily*, ExpectedVisibleChunks> expectedVisibleChunks;
+
+ // prepare 2 equal actors/families and damage
+ {
+ GeneratorAsset cube;
+ generateCube(cube, 6, 2, 5);
+ TkAssetDesc assetDesc;
+ assetDesc.bondCount = (uint32_t)cube.solverBonds.size();
+ assetDesc.bondDescs = cube.solverBonds.data();
+ assetDesc.chunkCount = (uint32_t)cube.chunks.size();
+ assetDesc.chunkDescs = cube.solverChunks.data();
+ assetDesc.bondFlags = nullptr;
+
+ TkAsset* cubeAsset = fwk->createAsset(assetDesc);
+ testAssets.push_back(cubeAsset);
+
+ TkActorDesc cubeAD(cubeAsset);
+
+ TkActor* cubeActor0 = fwk->createActor(cubeAD);
+ EXPECT_TRUE(cubeActor0 != nullptr);
+ cubeActor0->getFamily().addListener(ftrack1);
+
+ TkActor* cubeActor1 = fwk->createActor(cubeAD);
+ EXPECT_TRUE(cubeActor1 != nullptr);
+ cubeActor1->getFamily().addListener(ftrack2);
+
+ ftrack1.insertActor(cubeActor0);
+ ftrack2.insertActor(cubeActor1);
+
+ group0->addActor(*cubeActor0);
+ group1->addActor(*cubeActor1);
+
+ families[0] = (&cubeActor0->getFamily());
+ families[1] = (&cubeActor1->getFamily());
+
+ {
+ CSParams p0(0, 0.0f);
+ CSParams p1(1, 0.0f);
+ cubeActor0->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial());
+ cubeActor0->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial());
+
+ cubeActor1->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial());
+ }
+
+ expectedVisibleChunks[families[0]] = ExpectedVisibleChunks(4, 2); // split in 4, 2 chunks each
+ expectedVisibleChunks[families[1]] = ExpectedVisibleChunks(2, 4); // split in 2, 4 chunks each
+ }
+
+ // async process 2 groups
+ {
+ EXPECT_TRUE(group0->process());
+ EXPECT_TRUE(group1->process());
+ uint32_t completed = 0;
+ while (completed < 2)
+ {
+ if (group0->sync(false))
+ completed++;
+ if (group1->sync(false))
+ completed++;
+ }
+ }
+
+ // checks
+ testResults(families, expectedVisibleChunks);
+ EXPECT_EQ(families[0]->getActorCount(), 4);
+ EXPECT_EQ(group0->getActorCount(), 4);
+ EXPECT_EQ(families[1]->getActorCount(), 2);
+ EXPECT_EQ(group1->getActorCount(), 2);
+
+ // we have group0 with 4 actors 2 chunks:
+ // group0: [2]' [2]' [2]' [2]' (family0')
+ // group1: [4]'' [4]'' (family1'')
+ // rearrange:
+ // group0: [2]' [2]' [4]''
+ // group1: [4]'' [2]' [2]'
+ {
+ TkActor* group0Actors[2];
+ group0->getActors(group0Actors, 2, 1); // start index: 1, because..why not?
+ TkActor* group1Actors[2];
+ group1->getActors(group1Actors, 2, 0);
+ group0Actors[0]->removeFromGroup();
+ group1->addActor(*group0Actors[0]);
+ group0Actors[1]->removeFromGroup();
+ group1->addActor(*group0Actors[1]);
+ group1Actors[0]->removeFromGroup();
+ group0->addActor(*group1Actors[0]);
+ }
+
+ // checks
+ EXPECT_EQ(families[0]->getActorCount(), 4);
+ EXPECT_EQ(group0->getActorCount(), 3);
+ EXPECT_EQ(families[1]->getActorCount(), 2);
+ EXPECT_EQ(group1->getActorCount(), 3);
+
+ // damage all
+ {
+ TkActor* allActors[6];
+ families[0]->getActors(allActors, 4, 0);
+ families[1]->getActors(allActors + 4, 2, 0);
+
+ typedef std::pair<TkGroup*, TkFamily*> pair;
+ std::set<pair> combinations;
+ for (auto actor : allActors)
+ {
+ combinations.emplace(pair(actor->getGroup(), &actor->getFamily()));
+ if (actor->getVisibleChunkCount() == 4)
+ {
+ CSParams p1(1, 0.0f);
+ actor->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial());
+ }
+ CSParams p2(2, 0.0f);
+ actor->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial());
+ }
+ EXPECT_EQ(combinations.size(), 4);
+
+ expectedVisibleChunks[families[0]] = ExpectedVisibleChunks(8, 1); // split in 8, 1 chunks each
+ expectedVisibleChunks[families[1]] = ExpectedVisibleChunks(8, 1); // split in 8, 1 chunks each
+ }
+
+ // async process 2 groups
+ {
+ EXPECT_TRUE(group1->process());
+ EXPECT_TRUE(group0->process());
+ uint32_t completed = 0;
+ while (completed < 2)
+ {
+ if (group0->sync(false))
+ completed++;
+ if (group1->sync(false))
+ completed++;
+ }
+ }
+
+ // checks
+ testResults(families, expectedVisibleChunks);
+ EXPECT_EQ(families[0]->getActorCount(), 8);
+ EXPECT_EQ(ftrack1.actors.size(), 8);
+ EXPECT_EQ(group0->getActorCount(), 8);
+ EXPECT_EQ(families[1]->getActorCount(), 8);
+ EXPECT_EQ(ftrack2.actors.size(), 8);
+ EXPECT_EQ(group1->getActorCount(), 8);
+
+ // damage till the end, aggressively
+ std::default_random_engine re;
+ {
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ NvBlastExtShearDamageDesc shearDamage = getShearDamageDesc(0, 0, 0);
+
+ std::vector<TkActor*> actors;
+ while (1)
+ {
+ TEST_ZONE_BEGIN("damage loop");
+ uint32_t n0 = families[0]->getActorCount();
+ uint32_t n1 = families[1]->getActorCount();
+ actors.resize(n0 + n1);
+ families[0]->getActors(actors.data(), n0, 0);
+ families[1]->getActors(actors.data() + n0, n1, 0);
+
+ bool workTBD = false;
+ for (TkActor* actor : actors)
+ {
+ if (!NvBlastActorCanFracture(actor->getActorLL(), nullptr))
+ {
+ continue;
+ }
+
+ workTBD = true;
+
+ if (actor->getGraphNodeCount() > 1)
+ {
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(radialDamage), getDefaultMaterial());
+ }
+ else
+ {
+ actor->damage(getShearProgram(), &shearDamage, sizeof(shearDamage), getDefaultMaterial());
+ }
+
+ if (re() % 1000 < 500)
+ {
+ // switch group
+ TkGroup* newGroup = actor->getGroup() == group0 ? group1 : group0;
+ actor->removeFromGroup();
+ newGroup->addActor(*actor);
+ }
+ }
+
+ if (!workTBD)
+ break;
+
+ // async process 2 groups
+ {
+ EXPECT_TRUE(group1->process());
+ EXPECT_TRUE(group0->process());
+ uint32_t completed = 0;
+ while (completed < 2)
+ {
+ if (group0->sync(false))
+ completed++;
+ if (group1->sync(false))
+ completed++;
+ }
+ }
+ TEST_ZONE_END("damage loop");
+ }
+ }
+
+ // checks
+ EXPECT_EQ(families[0]->getActorCount(), ftrack1.actors.size());
+ EXPECT_EQ(families[1]->getActorCount(), ftrack2.actors.size());
+ EXPECT_EQ(65536, families[0]->getActorCount() + families[1]->getActorCount());
+ EXPECT_EQ(65536, group0->getActorCount() + group1->getActorCount());
+
+ group0->release();
+ group1->release();
+
+ for (auto f : families)
+ f->release();
+
+ releaseTestAssets();
+ releaseFramework();
+}
+
+TEST_F(TkTestAllowWarnings, ActorDamageBufferedDamage)
+{
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ // group
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ // random engine
+ std::default_random_engine re;
+
+ // cube asset
+ GeneratorAsset cube;
+ generateCube(cube, 4, 2, 3);
+ TkAssetDesc assetDesc;
+ assetDesc.bondCount = (uint32_t)cube.solverBonds.size();
+ assetDesc.bondDescs = cube.solverBonds.data();
+ assetDesc.chunkCount = (uint32_t)cube.chunks.size();
+ assetDesc.chunkDescs = cube.solverChunks.data();
+ assetDesc.bondFlags = nullptr;
+ TkAsset* cubeAsset = fwk->createAsset(assetDesc);
+ testAssets.push_back(cubeAsset);
+
+ // actor desc
+ TkActorDesc cubeAD(cubeAsset);
+
+ // test will be repated 'trials' times. Because of random shuffle inside.
+ const uint32_t trials = 100;
+ for (uint32_t i = 0; i < trials; i++)
+ {
+ // create actor
+ TkActor* actor = fwk->createActor(cubeAD);
+ EXPECT_TRUE(actor != nullptr);
+ TkFamily* family = (&actor->getFamily());
+ group->addActor(*actor);
+
+ // damage 3 times with CubeSlicer 2 * 2 * 2 = 8 actors
+ // damage 4 corners with falloff radial 4 * 2 = 8 actors
+ // total 16 actors
+ uint32_t expectedActorCount = 16;
+
+ // fallof params
+ const float P = 0.5f;
+ const float R = 0.35f;
+
+ // 2 of damage types would be through user's NvBlastDamageProgram, this pointer must live till group->sync()
+ NvBlastExtRadialDamageDesc userR0 = getRadialDamageDesc(P, P, 0, R, R);
+ NvBlastProgramParams userProgramParams0 =
+ {
+ &userR0, // damageDescBuffer
+ 1, // damageDescCount
+ nullptr, // material
+ };
+
+ NvBlastExtRadialDamageDesc userR1 = getRadialDamageDesc(-P, P, 0, R, R);
+ NvBlastProgramParams userProgramParams1 =
+ {
+ &userR1, // damageDescBuffer
+ 1, // damageDescCount
+ nullptr, // material
+ };
+
+ // fill damage functions, shuffle and apply
+ {
+ CSParams p0(0, 0.0f);
+ CSParams p1(1, 0.0f);
+ CSParams p2(2, 0.0f);
+ NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(P, -P, 0, R, R);
+ NvBlastExtRadialDamageDesc r1 = getRadialDamageDesc(-P, -P, 0, R, R);
+
+ const uint32_t damageCount = 7;
+ std::vector<std::function<void(void)>> damageFns(damageCount);
+ damageFns[0] = [&]() { actor->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); };
+ damageFns[1] = [&]() { actor->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); };
+ damageFns[2] = [&]() { actor->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial()); };
+ damageFns[3] = [&]() { actor->damage(getFalloffProgram(), &r0, sizeof(r0), getDefaultMaterial()); };
+ damageFns[4] = [&]() { actor->damage(getFalloffProgram(), &r1, sizeof(r1), getDefaultMaterial()); };
+ damageFns[5] = [&]() { actor->damage(getFalloffProgram(), &userProgramParams0); };
+ damageFns[6] = [&]() { actor->damage(getFalloffProgram(), &userProgramParams1); };
+
+ // shuffle order!
+ std::shuffle(std::begin(damageFns), std::end(damageFns), re);
+
+ for (uint32_t i = 0; i < damageCount; i++)
+ {
+ damageFns[i]();
+ }
+ }
+
+ // sync
+ EXPECT_TRUE(group->process());
+ group->sync(true);
+
+ const auto ac = family->getActorCount();
+
+ // check
+ EXPECT_EQ(family->getActorCount(), expectedActorCount);
+ EXPECT_EQ(group->getActorCount(), expectedActorCount);
+
+ // release
+ std::vector<TkActor*> actors(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+ for (auto a : actors)
+ a->removeFromGroup();
+ family->release();
+ }
+
+ group->release();
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, CreateActor)
+{
+ createFramework();
+ TkFramework* framework = NvBlastTkFrameworkGet();
+
+ const uint32_t assetDescCount = sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+
+ std::vector<TkAsset*> assets(assetDescCount);
+
+ // assets
+ for (uint32_t i = 0; i < assetDescCount; ++i)
+ {
+ TkAssetDesc desc;
+ reinterpret_cast<NvBlastAssetDesc&>(desc) = g_assetDescs[i];
+ desc.bondFlags = nullptr;
+ assets[i] = framework->createAsset(desc);
+ EXPECT_TRUE(assets[i] != nullptr);
+ }
+
+ // actors
+ std::vector<TkActor*> actors;;
+ std::vector<TkFamily*> actorFamilies;;
+ for (const TkAsset* asset : assets)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ TkActorDesc desc(asset);
+ TkActor* actor = framework->createActor(desc);
+ EXPECT_TRUE(actor != nullptr);
+ EXPECT_TRUE(actor->getActorLL() != nullptr);
+ //EXPECT_TRUE(&actor->getFamily() != nullptr);
+ EXPECT_TRUE(actor->getFamily().getActorCount() == 1);
+ actors.push_back(actor);
+ EXPECT_TRUE(std::find(actorFamilies.begin(), actorFamilies.end(), &actor->getFamily()) == actorFamilies.end());
+ actorFamilies.push_back(&actor->getFamily());
+
+ }
+ }
+
+ // framework checks
+ {
+ std::vector<TkObject*> objects;
+
+ // assets
+ {
+ const TkType* assetType = framework->getType(TkTypeIndex::Asset);
+ objects.resize(framework->getObjectCount(*assetType));
+ EXPECT_TRUE(framework->getObjects(reinterpret_cast<TkIdentifiable**>(objects.data()), static_cast<uint32_t>(objects.size()), *assetType) == static_cast<uint32_t>(objects.size()));
+ ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)assets.data(), assets.size());
+ }
+
+ // actors
+# if(0) // framework does not track actors explicitly anymore
+ {
+ const TkType* actorType = framework->getType(TkTypeIndex::Actor);
+ objects.resize(framework->getObjectCount(*actorType));
+ EXPECT_TRUE(framework->getObjects(reinterpret_cast<TkIdentifiable**>(objects.data()), objects.size(), *actorType) == objects.size());
+ ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)actors.data(), actors.size());
+ }
+# endif
+ // families
+ {
+ const TkType* familyType = framework->getType(TkTypeIndex::Family);
+ objects.resize(framework->getObjectCount(*familyType));
+ EXPECT_TRUE(framework->getObjects(reinterpret_cast<TkIdentifiable**>(objects.data()), static_cast<uint32_t>(objects.size()), *familyType) == static_cast<uint32_t>(objects.size()));
+ ExpectArrayMatch(objects.data(), objects.size(), (TkObject**)actorFamilies.data(), actorFamilies.size());
+ }
+ }
+
+ // release
+ for (TkActor* actor : actors)
+ {
+ actor->release();
+ }
+ for (TkAsset* asset : assets)
+ {
+ asset->release();
+ }
+
+ releaseFramework();
+}
+
+template<int FailMask, int Verbosity>
+TkFamily* TkBaseTest<FailMask, Verbosity>::familySerialization(TkFamily* family)
+{
+ TkFramework* fw = NvBlastTkFrameworkGet();
+
+ const TkType* familyType = fw->getType(TkTypeIndex::Family);
+ EXPECT_TRUE(familyType != nullptr);
+
+ PsMemoryBuffer* membuf = PX_NEW(PsMemoryBuffer);
+ EXPECT_TRUE(membuf != nullptr);
+ if (membuf != nullptr)
+ {
+ const bool result = family->serialize(*membuf);
+ EXPECT_EQ(true, result);
+ if (!result)
+ {
+ return family;
+ }
+ const size_t familyActorCount = family->getActorCount();
+ const TkAsset* familyAsset = family->getAsset();
+ family->release();
+ family = reinterpret_cast<TkFamily*>(fw->deserialize(*membuf));
+ EXPECT_TRUE(family != nullptr);
+ if (family != nullptr)
+ {
+ EXPECT_EQ(familyActorCount, family->getActorCount());
+ EXPECT_EQ(familyAsset, family->getAsset());
+ }
+ membuf->release();
+ }
+
+ return family;
+}
+
+TEST_F(TkTestAllowWarnings, FamilySerialization)
+{
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ // group
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ // random engine
+ std::default_random_engine re;
+
+ // cube asset
+ TkAsset* cubeAsset = createCubeAsset(4, 2, 3, false);
+
+ // actor desc
+ TkActorDesc cubeAD(cubeAsset);
+
+ // create actor
+ TkActor* actor = fwk->createActor(cubeAD);
+ EXPECT_TRUE(actor != nullptr);
+ TkFamily* family = (&actor->getFamily());
+
+ // set an ID
+ NvBlastID id;
+ memcpy(id.data, "Observer-expectancy effect", sizeof(NvBlastID)); // Stuffing an arbitrary 16 bytes (The prefix of the given string)
+ cubeAsset->setID(id);
+
+ // serialize/deserialize
+ family = familySerialization(family);
+
+ // fill damage functions, apply one by one and serialize family in between
+ {
+ // damage 3 times with CubeSlicer 2 * 2 * 2 = 8 actors
+ // damage 4 corners with falloff radial 4 * 2 = 8 actors
+ // total 16 actors
+ uint32_t expectedActorCount = 16;
+
+ // cube slicer params
+ CSParams p0(0, 0.0f);
+ CSParams p1(1, 0.0f);
+ CSParams p2(2, 0.0f);
+
+ // fallof params
+ const float P = 0.5f;
+ const float R = 0.35f;
+ NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(P, P, 0, R, R);
+ NvBlastExtRadialDamageDesc r1 = getRadialDamageDesc(-P, P, 0, R, R);
+ NvBlastExtRadialDamageDesc r2 = getRadialDamageDesc(P, -P, 0, R, R);
+ NvBlastExtRadialDamageDesc r3 = getRadialDamageDesc(-P, -P, 0, R, R);
+
+ const uint32_t damageCount = 7;
+ std::vector<std::function<void(TkActor* a)>> damageFns(damageCount);
+ damageFns[0] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p0, sizeof(p0), getDefaultMaterial()); };
+ damageFns[1] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p1, sizeof(p1), getDefaultMaterial()); };
+ damageFns[2] = [&](TkActor* a) { a->damage(getCubeSlicerProgram(), &p2, sizeof(p2), getDefaultMaterial()); };
+ damageFns[3] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r0, sizeof(r0), getDefaultMaterial()); };
+ damageFns[4] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r1, sizeof(r1), getDefaultMaterial()); };
+ damageFns[5] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r2, sizeof(r2), getDefaultMaterial()); };
+ damageFns[6] = [&](TkActor* a) { a->damage(getFalloffProgram(), &r3, sizeof(r3), getDefaultMaterial()); };
+
+ std::vector<TkActor*> actors(64);
+
+ for (uint32_t i = 0; i < damageCount; i++)
+ {
+ actors.resize(family->getActorCount());
+ family->getActors(actors.data(), static_cast<uint32_t>(actors.size()));
+
+ // damage
+ for (auto actor : actors)
+ {
+ group->addActor(*actor);
+ damageFns[i](actor);
+ }
+
+ // sync
+ EXPECT_TRUE(group->process());
+ group->sync(true);
+
+ family = familySerialization(family);
+ }
+
+ // check
+ EXPECT_EQ(family->getActorCount(), expectedActorCount);
+ }
+
+ // release
+ family->release();
+
+ group->release();
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, GroupStats)
+{
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ // group
+ TkGroupDesc gdesc;
+ gdesc.pxTaskManager = m_taskman;
+ TkGroup* group = fwk->createGroup(gdesc);
+ EXPECT_TRUE(group != nullptr);
+
+ TkAsset* cubeAsset = createCubeAsset(4, 2);
+ TkActorDesc cubeDesc(cubeAsset);
+
+ TkActor* cubeActor1 = fwk->createActor(cubeDesc);
+ TkActor* cubeActor2 = fwk->createActor(cubeDesc);
+ TkActor* cubeActor3 = fwk->createActor(cubeDesc);
+ TkActor* cubeActor4 = fwk->createActor(cubeDesc);
+
+ group->addActor(*cubeActor1);
+ group->addActor(*cubeActor2);
+ group->addActor(*cubeActor3);
+ group->addActor(*cubeActor4);
+
+ NvBlastExtRadialDamageDesc r0 = getRadialDamageDesc(0.0f, 0.0f, 0.0f);
+ cubeActor1->damage(getFalloffProgram(), &r0, sizeof(r0));
+ cubeActor2->damage(getFalloffProgram(), &r0, sizeof(r0));
+ cubeActor3->damage(getFalloffProgram(), &r0, sizeof(r0));
+ cubeActor4->damage(getFalloffProgram(), &r0, sizeof(r0));
+
+ Nv::Blast::Time time;
+ group->process();
+ group->sync(true);
+ int64_t groupTime = time.getElapsedTicks();
+
+ TkGroupStats gstats;
+ group->getStats(gstats);
+
+ int64_t total = gstats.timers.fracture + gstats.timers.island + gstats.timers.material + gstats.timers.partition + gstats.timers.visibility;
+
+#if NV_PROFILE
+ EXPECT_GT(total, 0); // some values are reported
+ EXPECT_LT(groupTime, total); // total LL time is higher than group time
+ EXPECT_GT((double)gstats.workerTime / groupTime, 2.0); // expect some minimal speedup (including overhead)
+ EXPECT_EQ(4, gstats.processedActorsCount); // actors processed
+#endif
+
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, FractureReportSupport)
+{
+ createFramework();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ NvBlastChunkDesc chunkDescs[] =
+ {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'prnt' },
+ { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'left' },
+ { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'rght' },
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* actor = fwk->createActor(actorDesc);
+ actor->userData = (void*)'root';
+
+ class Listener : public TkEventListener
+ {
+ void receive(const TkEvent* events, uint32_t eventCount) override
+ {
+ for (uint32_t i = 0; i < eventCount; i++)
+ {
+ const TkEvent& event = events[i];
+ switch (event.type)
+ {
+ case TkJointUpdateEvent::EVENT_TYPE:
+ FAIL() << "not expecting joints here";
+ break;
+
+ case TkFractureCommands::EVENT_TYPE:
+ {
+ const TkActorData& actor = event.getPayload<TkFractureCommands>()->tkActorData;
+
+ // Group::sync still needed the family for SharedMemory management.
+ EXPECT_TRUE(nullptr != actor.family);
+
+ EXPECT_EQ((void*)'root', actor.userData);
+ EXPECT_EQ(0, actor.index);
+ }
+ break;
+
+ case TkFractureEvents::EVENT_TYPE:
+ {
+ const TkActorData& actor = event.getPayload<TkFractureEvents>()->tkActorData;
+ EXPECT_EQ((void*)'root', actor.userData);
+ EXPECT_EQ(0, actor.index);
+ }
+ break;
+
+ case TkSplitEvent::EVENT_TYPE:
+ {
+ const TkSplitEvent* split = event.getPayload<TkSplitEvent>();
+
+ EXPECT_TRUE(nullptr != split->parentData.family);
+ EXPECT_EQ((void*)'root', split->parentData.userData);
+ EXPECT_EQ(0, split->parentData.index);
+
+ EXPECT_EQ(2, split->numChildren);
+ EXPECT_EQ(1, split->children[0]->getVisibleChunkCount());
+
+ uint32_t visibleChunkIndex;
+ // child order is not mandatory
+ {
+ TkActor* a = split->children[0];
+ a->getVisibleChunkIndices(&visibleChunkIndex, 1);
+ uint32_t li = a->getIndex();
+ EXPECT_EQ(1, li);
+ EXPECT_EQ(split->parentData.family, &a->getFamily());
+ EXPECT_EQ('left', a->getAsset()->getChunks()[visibleChunkIndex].userData);
+ }
+
+ {
+ TkActor*a = split->children[1];
+ a->getVisibleChunkIndices(&visibleChunkIndex, 1);
+ uint32_t ri = a->getIndex();
+ EXPECT_EQ(2, ri);
+ EXPECT_EQ(split->parentData.family, &a->getFamily());
+ EXPECT_EQ('rght', a->getAsset()->getChunks()[visibleChunkIndex].userData);
+ }
+ }
+ break;
+
+ default:
+ FAIL() << "should not get here";
+ }
+ }
+ }
+ } listener;
+ actor->getFamily().addListener(listener);
+
+ // expected state for the original actor, see Listener
+ EXPECT_EQ((void*)'root', actor->userData);
+ EXPECT_EQ(0, actor->getIndex());
+
+ TkGroupDesc groupDesc = { m_taskman };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*actor);
+
+ // this will trigger hierarchical chunk fracture
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, FractureReportGraph)
+{
+ createFramework();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ NvBlastBond bondToBreak = { { 1,0,0 }, 1,{ 0, 0, 0 }, 0 };
+ NvBlastBond bondToKeep = { { 1,0,0 }, 1,{ 10, 10, 10 }, 0 };
+ NvBlastBondDesc bondDescs[] =
+ {
+ { { 1,2 }, bondToKeep },
+ { { 2,3 }, bondToBreak },
+ };
+
+ NvBlastChunkDesc chunkDescs[] =
+ {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::NoFlags, 'root' },
+ { { -1,0,0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'A' },
+ { { +1,0,0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'B' },
+ { { +1,0,0 }, 1, 0, NvBlastChunkDesc::SupportFlag, 'C' },
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 2;
+ assetDesc.bondDescs = bondDescs;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* rootActor = fwk->createActor(actorDesc);
+ rootActor->userData = (void*)'root';
+
+ class Listener : public TkEventListener
+ {
+ void receive(const TkEvent* events, uint32_t eventCount) override
+ {
+ for (uint32_t i = 0; i < eventCount; i++)
+ {
+ const TkEvent& event = events[i];
+ switch (event.type)
+ {
+ case TkJointUpdateEvent::EVENT_TYPE:
+ FAIL() << "not expecting joints here";
+ break;
+
+ case TkFractureCommands::EVENT_TYPE:
+ {
+ const TkActorData& actor = event.getPayload<TkFractureCommands>()->tkActorData;
+
+ // Group::sync still needed the family for SharedMemory management.
+ EXPECT_TRUE(nullptr != actor.family);
+
+ // original actor state is not preserved, the last test will fail
+ EXPECT_EQ((void*)'root', actor.userData);
+ EXPECT_EQ(0, actor.index);
+
+ // this information was invalid anyway
+ //EXPECT_EQ(1, actor->getVisibleChunkCount()) << "state not preserved";
+ }
+ break;
+
+ case TkFractureEvents::EVENT_TYPE:
+ {
+ const TkActorData& actor = event.getPayload<TkFractureEvents>()->tkActorData;
+
+ // Group::sync still needed the family for SharedMemory management.
+ EXPECT_TRUE(nullptr != actor.family);
+
+ // original actor state is not preserved, the last test will fail
+ EXPECT_EQ((void*)'root', actor.userData);
+ EXPECT_EQ(0, actor.index);
+
+ // this information was invalid anyway
+ //EXPECT_EQ(1, actor->getVisibleChunkCount()) << "state not preserved";
+ }
+ break;
+
+ case TkSplitEvent::EVENT_TYPE:
+ {
+ const TkSplitEvent* split = event.getPayload<TkSplitEvent>();
+ EXPECT_EQ((void*)'root', split->parentData.userData);
+ EXPECT_EQ(0, split->parentData.index);
+ EXPECT_EQ(2, split->numChildren);
+
+ uint32_t visibleChunkIndex[2];
+ // child order is not mandatory
+ {
+ TkActor* a = split->children[1];
+ EXPECT_EQ(2, a->getVisibleChunkCount()); // chunks A and B
+ a->getVisibleChunkIndices(visibleChunkIndex, 2);
+ uint32_t actorIndex = a->getIndex();
+ EXPECT_EQ(0, actorIndex); // same index as the original actor
+
+ // visible chunk order is not mandatory
+ EXPECT_EQ('B', a->getAsset()->getChunks()[visibleChunkIndex[0]].userData);
+ EXPECT_EQ('A', a->getAsset()->getChunks()[visibleChunkIndex[1]].userData);
+ }
+
+ {
+ TkActor* a = split->children[0];
+ EXPECT_EQ(1, a->getVisibleChunkCount());
+ a->getVisibleChunkIndices(visibleChunkIndex, 1);
+ uint32_t actorIndex = a->getIndex();
+ EXPECT_EQ(2, actorIndex);
+ EXPECT_EQ('C', a->getAsset()->getChunks()[visibleChunkIndex[0]].userData);
+ }
+ }
+ break;
+
+ default:
+ FAIL() << "should not get here";
+ }
+ }
+ }
+ } listener;
+ rootActor->getFamily().addListener(listener);
+
+ // expected state for the original actor, see Listener
+ EXPECT_EQ((void*)'root', rootActor->userData);
+ EXPECT_EQ(0, rootActor->getIndex());
+ EXPECT_EQ(1, rootActor->getVisibleChunkCount());
+
+ TkGroupDesc groupDesc = { m_taskman };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*rootActor);
+
+ // this will trigger one bond to break
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0, 0.5f, 0.5f);
+ rootActor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ releaseFramework();
+}
+
+TEST_F(TkTestStrict, SplitWarning) // GWD-167
+{
+ createFramework();
+
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+
+ NvBlastChunkDesc chunkDescs[] =
+ {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' },
+ { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'A' },
+ { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'B' },
+ { { -1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'C' },
+ { { +1,0,0 }, 1, 0, NvBlastChunkDesc::NoFlags, 'D' },
+ { { -1,0,0 }, 1, 1, NvBlastChunkDesc::NoFlags, 'AAAA' },
+ { { +1,0,0 }, 1, 2, NvBlastChunkDesc::NoFlags, 'BBBB' },
+ { { -1,0,0 }, 1, 3, NvBlastChunkDesc::NoFlags, 'CCCC' },
+ { { +1,0,0 }, 1, 4, NvBlastChunkDesc::NoFlags, 'DDDD' },
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* actor = fwk->createActor(actorDesc);
+
+ TkGroupDesc groupDesc = { m_taskman };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*actor);
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ actor->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ releaseFramework();
+}
+
+
+TEST_F(TkTestAllowWarnings, ChangeThreadCountToZero)
+{
+ // tests that group still allocates memory for one worker
+ // by replacing to a 0 threads cpu dispatcher (warns)
+ // mainly relies on internal asserts
+
+ class EventCounter : public TkEventListener
+ {
+ public:
+ EventCounter() :fracCommands(0), fracEvents(0) {}
+
+ void receive(const TkEvent* events, uint32_t eventCount)
+ {
+ for (uint32_t i = 0; i < eventCount; i++)
+ {
+ const TkEvent& event = events[i];
+ switch (event.type)
+ {
+ case TkFractureCommands::EVENT_TYPE:
+ fracCommands++;
+ break;
+ case TkFractureEvents::EVENT_TYPE:
+ fracEvents++;
+ break;
+ default:
+ FAIL();
+ // no split due to single chunk
+ // no joints
+ }
+ }
+ }
+
+ uint32_t fracCommands, fracEvents;
+ } listener;
+
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+ NvBlastChunkDesc chunkDescs[] = {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' }
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* actor1 = fwk->createActor(actorDesc);
+ TkActor* actor2 = fwk->createActor(actorDesc);
+ TkActor* actor3 = fwk->createActor(actorDesc);
+ TkActor* actor4 = fwk->createActor(actorDesc);
+
+ actor1->getFamily().addListener(listener);
+ actor2->getFamily().addListener(listener);
+ actor3->getFamily().addListener(listener);
+ actor4->getFamily().addListener(listener);
+
+#if USE_PHYSX_DISPATCHER
+ PxU32 affinity[] = { 1, 2, 4, 8 };
+ PxDefaultCpuDispatcher* disp0 = PxDefaultCpuDispatcherCreate(0, affinity);
+ disp0->setRunProfiled(false);
+ PxDefaultCpuDispatcher* disp4 = PxDefaultCpuDispatcherCreate(4, affinity);
+ disp4->setRunProfiled(false);
+#else
+ TestCpuDispatcher* disp0 = new TestCpuDispatcher(0);
+ TestCpuDispatcher* disp4 = new TestCpuDispatcher(4);
+#endif
+
+ PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, disp4);
+
+ TkGroupDesc groupDesc = { taskman };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*actor1);
+ group->addActor(*actor2);
+ taskman->setCpuDispatcher(*disp0);
+ group->addActor(*actor3);
+ group->addActor(*actor4);
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ EXPECT_EQ(4, listener.fracCommands);
+ EXPECT_EQ(4, listener.fracEvents);
+
+ releaseFramework();
+
+ disp0->release();
+ disp4->release();
+ taskman->release();
+}
+
+TEST_F(TkTestAllowWarnings, ChangeThreadCountUp)
+{
+ // tests that group allocates more memory for additional workers
+ // by replacing to a higher thread count cpu dispatcher (warns)
+ // mainly relies on internal asserts
+
+ class EventCounter : public TkEventListener
+ {
+ public:
+ EventCounter() :fracCommands(0), fracEvents(0) {}
+
+ void receive(const TkEvent* events, uint32_t eventCount)
+ {
+ for (uint32_t i = 0; i < eventCount; i++)
+ {
+ const TkEvent& event = events[i];
+ switch (event.type)
+ {
+ case TkFractureCommands::EVENT_TYPE:
+ fracCommands++;
+ break;
+ case TkFractureEvents::EVENT_TYPE:
+ fracEvents++;
+ break;
+ default:
+ FAIL();
+ // no split due to single chunk
+ // no joints
+ }
+ }
+ }
+
+ uint32_t fracCommands, fracEvents;
+ } listener;
+
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+ NvBlastChunkDesc chunkDescs[] = {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' }
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* actor1 = fwk->createActor(actorDesc);
+ TkActor* actor2 = fwk->createActor(actorDesc);
+ TkActor* actor3 = fwk->createActor(actorDesc);
+ TkActor* actor4 = fwk->createActor(actorDesc);
+
+ actor1->getFamily().addListener(listener);
+ actor2->getFamily().addListener(listener);
+ actor3->getFamily().addListener(listener);
+ actor4->getFamily().addListener(listener);
+
+#if USE_PHYSX_DISPATCHER
+ PxU32 affinity[] = { 1, 2, 4, 8 };
+ PxDefaultCpuDispatcher* disp2 = PxDefaultCpuDispatcherCreate(2, affinity);
+ disp2->setRunProfiled(false);
+ PxDefaultCpuDispatcher* disp4 = PxDefaultCpuDispatcherCreate(4, affinity);
+ disp4->setRunProfiled(false);
+#else
+ TestCpuDispatcher* disp2 = new TestCpuDispatcher(2);
+ TestCpuDispatcher* disp4 = new TestCpuDispatcher(4);
+#endif
+
+ PxTaskManager* taskman = PxTaskManager::createTaskManager(*this, disp2);
+
+ TkGroupDesc groupDesc = { taskman };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*actor1);
+ group->addActor(*actor2);
+ group->addActor(*actor3);
+ group->addActor(*actor4);
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ taskman->setCpuDispatcher(*disp4);
+
+ group->process();
+ group->sync();
+
+ EXPECT_EQ(4, listener.fracCommands);
+ EXPECT_EQ(4, listener.fracEvents);
+
+ releaseFramework();
+
+ disp2->release();
+ disp4->release();
+ taskman->release();
+}
+
+TEST_F(TkTestAllowWarnings, GroupNoWorkers)
+{
+ // tests that group still works without a taskmanager
+ // a warnings is expected
+ // mainly relies on internal asserts
+
+ class EventCounter : public TkEventListener
+ {
+ public:
+ EventCounter() :fracCommands(0), fracEvents(0) {}
+
+ void receive(const TkEvent* events, uint32_t eventCount)
+ {
+ for (uint32_t i = 0; i < eventCount; i++)
+ {
+ const TkEvent& event = events[i];
+ switch (event.type)
+ {
+ case TkFractureCommands::EVENT_TYPE:
+ fracCommands++;
+ break;
+ case TkFractureEvents::EVENT_TYPE:
+ fracEvents++;
+ break;
+ default:
+ FAIL();
+ // no split due to single chunk
+ // no joints
+ }
+ }
+ }
+
+ uint32_t fracCommands, fracEvents;
+ } listener;
+
+ createFramework();
+ TkFramework* fwk = NvBlastTkFrameworkGet();
+ NvBlastChunkDesc chunkDescs[] = {
+ { { 0,0,0 }, 2, UINT32_MAX, NvBlastChunkDesc::SupportFlag, 'root' }
+ };
+
+ TkAssetDesc assetDesc;
+ assetDesc.chunkCount = sizeof(chunkDescs) / sizeof(NvBlastChunkDesc);
+ assetDesc.chunkDescs = chunkDescs;
+ assetDesc.bondCount = 0;
+ assetDesc.bondDescs = nullptr;
+ assetDesc.bondFlags = nullptr;
+ const TkAsset* asset = fwk->createAsset(assetDesc);
+
+ TkActorDesc actorDesc;
+ actorDesc.asset = asset;
+ TkActor* actor1 = fwk->createActor(actorDesc);
+ TkActor* actor2 = fwk->createActor(actorDesc);
+ TkActor* actor3 = fwk->createActor(actorDesc);
+ TkActor* actor4 = fwk->createActor(actorDesc);
+
+ actor1->getFamily().addListener(listener);
+ actor2->getFamily().addListener(listener);
+ actor3->getFamily().addListener(listener);
+ actor4->getFamily().addListener(listener);
+
+ TkGroupDesc groupDesc = { nullptr };
+ TkGroup* group = fwk->createGroup(groupDesc);
+
+ group->addActor(*actor1);
+ group->addActor(*actor2);
+ group->addActor(*actor3);
+ group->addActor(*actor4);
+
+ NvBlastExtRadialDamageDesc radialDamage = getRadialDamageDesc(0, 0, 0);
+ actor1->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor2->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor3->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+ actor4->damage(getFalloffProgram(), &radialDamage, sizeof(NvBlastExtRadialDamageDesc), getDefaultMaterial());
+
+ group->process();
+ group->sync();
+
+ EXPECT_EQ(4, listener.fracCommands);
+ EXPECT_EQ(4, listener.fracEvents);
+
+ releaseFramework();
+}
+
diff --git a/NvBlast/test/src/utils/TaskDispatcher.h b/NvBlast/test/src/utils/TaskDispatcher.h
new file mode 100644
index 0000000..bd8bfc8
--- /dev/null
+++ b/NvBlast/test/src/utils/TaskDispatcher.h
@@ -0,0 +1,191 @@
+#pragma once
+
+#include <thread>
+#include <mutex>
+#include <queue>
+#include <list>
+#include <future>
+#include <condition_variable>
+#include <memory>
+#include <atomic>
+
+class TaskDispatcher
+{
+public:
+ class Task
+ {
+ public:
+ virtual void process() = 0;
+ virtual ~Task() {};
+ };
+
+ typedef std::function<void(TaskDispatcher& dispatcher, std::unique_ptr<Task>)> OnTaskFinishedFunction;
+
+ TaskDispatcher(uint32_t threadCount, OnTaskFinishedFunction onTaskFinished) :
+ m_workingThreadsCount(0), m_onTaskFinished(onTaskFinished)
+ {
+ m_threads.resize(threadCount);
+ for (uint32_t i = 0; i < threadCount; i++)
+ {
+ m_threads[i] = std::unique_ptr<Thread>(new Thread(i, m_completionSemaphore));
+ m_threads[i]->start();
+ m_freeThreads.push(m_threads[i].get());
+ }
+ }
+
+ void addTask(std::unique_ptr<Task> task)
+ {
+ m_tasks.push(std::move(task));
+ }
+
+ void process()
+ {
+ // main loop
+ while (m_tasks.size() > 0 || m_workingThreadsCount > 0)
+ {
+ // assign tasks
+ while (!(m_tasks.empty() || m_freeThreads.empty()))
+ {
+ auto task = std::move(m_tasks.front());
+ m_tasks.pop();
+
+ Thread* freeThread = m_freeThreads.front();
+ m_freeThreads.pop();
+
+ freeThread->processTask(std::move(task));
+ m_workingThreadsCount++;
+ }
+
+ m_completionSemaphore.wait();
+
+ // check for completion
+ for (std::unique_ptr<Thread>& thread : m_threads)
+ {
+ if (thread->isTaskFinished())
+ {
+ std::unique_ptr<Task> task;
+ thread->collectTask(task);
+ m_onTaskFinished(*this, std::move(task));
+
+ m_freeThreads.push(thread.get());
+ m_workingThreadsCount--;
+ break;
+ }
+ }
+ }
+ }
+
+private:
+ class Semaphore
+ {
+ public:
+ Semaphore(int count_ = 0)
+ : m_count(count_) {}
+
+ inline void notify()
+ {
+ std::unique_lock<std::mutex> lock(m_mutex);
+ m_count++;
+ m_cv.notify_one();
+ }
+
+ inline void wait()
+ {
+ std::unique_lock<std::mutex> lock(m_mutex);
+
+ while (m_count == 0){
+ m_cv.wait(lock);
+ }
+ m_count--;
+ }
+
+
+ private:
+ std::mutex m_mutex;
+ std::condition_variable m_cv;
+ int m_count;
+ };
+
+ class Thread
+ {
+ public:
+ Thread(uint32_t id_, Semaphore& completionSemaphore) : m_id(id_), m_completionSemaphore(completionSemaphore), m_running(false), m_taskFinished(false) {}
+ virtual ~Thread() { stop(); }
+
+ void start()
+ {
+ if (!m_running)
+ {
+ m_running = true;
+ m_thread = std::thread(&Thread::body, this);
+ }
+ }
+
+ void stop()
+ {
+ if (m_running)
+ {
+ m_running = false;
+ m_newTaskSemaphore.notify();
+ m_thread.join();
+ }
+ }
+
+ void processTask(std::unique_ptr<Task> task)
+ {
+ m_task = std::move(task);
+ m_taskFinished = false;
+ m_newTaskSemaphore.notify();
+ }
+
+ void collectTask(std::unique_ptr<Task>& task)
+ {
+ task = std::move(m_task);
+ m_task = nullptr;
+ m_taskFinished = false;
+ }
+
+ bool hasTask() const { return m_task != nullptr; }
+
+ bool isTaskFinished() const { return m_taskFinished; }
+
+ private:
+ void body()
+ {
+ while (1)
+ {
+ m_newTaskSemaphore.wait();
+
+ if (!m_running)
+ return;
+
+ m_task->process();
+ m_taskFinished = true;
+
+ m_completionSemaphore.notify();
+ }
+ }
+
+ uint32_t m_id;
+ Semaphore& m_completionSemaphore;
+ std::thread m_thread;
+ bool m_running;
+
+ std::unique_ptr<Task> m_task;
+ std::atomic<bool> m_taskFinished;
+
+ Semaphore m_newTaskSemaphore;
+ };
+
+private:
+ uint32_t m_workingThreadsCount;
+
+ std::queue<std::unique_ptr<Task>> m_tasks;
+ OnTaskFinishedFunction m_onTaskFinished;
+
+ std::vector<std::unique_ptr<Thread>> m_threads;
+ std::queue<Thread*> m_freeThreads;
+
+ Semaphore m_completionSemaphore;
+};
+
diff --git a/NvBlast/test/src/utils/TestAssets.cpp b/NvBlast/test/src/utils/TestAssets.cpp
new file mode 100644
index 0000000..f2a1396
--- /dev/null
+++ b/NvBlast/test/src/utils/TestAssets.cpp
@@ -0,0 +1,288 @@
+#include "TestAssets.h"
+#include "AssetGenerator.h"
+
+const NvBlastChunkDesc g_cube1ChunkDescs[9] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 8 },
+};
+
+const NvBlastBondDesc g_cube1BondDescs[12] =
+{
+// chunks normal area centroid userData
+ { { 1, 2 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f,-0.5f,-0.5f }, 0 } },
+ { { 3, 4 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, 0.5f,-0.5f }, 0 } },
+ { { 5, 6 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f,-0.5f, 0.5f }, 0 } },
+ { { 7, 8 }, { { 1.0f, 0.0f, 0.0f }, 1.0f, { 0.0f, 0.5f, 0.5f }, 0 } },
+
+ { { 1, 3 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, {-0.5f, 0.0f,-0.5f }, 0 } },
+ { { 2, 4 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 0.5f, 0.0f,-0.5f }, 0 } },
+ { { 5, 7 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, {-0.5f, 0.0f, 0.5f }, 0 } },
+ { { 6, 8 }, { { 0.0f, 1.0f, 0.0f }, 1.0f, { 0.5f, 0.0f, 0.5f }, 0 } },
+
+ { { 1, 5 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, {-0.5f,-0.5f, 0.0f }, 0 } },
+ { { 2, 6 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, { 0.5f,-0.5f, 0.0f }, 0 } },
+ { { 3, 7 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, {-0.5f, 0.5f, 0.0f }, 0 } },
+ { { 4, 8 }, { { 0.0f, 0.0f, 1.0f }, 1.0f, { 0.5f, 0.5f, 0.0f }, 0 } },
+};
+
+const NvBlastChunkDesc g_cube2ChunkDescs[73] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {-0.5f, -0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.5f, -0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {-0.5f, 0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f, 0.5f, -0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f, -0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f, -0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 8 },
+ { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 },
+ { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 },
+ { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 },
+ { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 },
+ { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 },
+ { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 },
+ { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 },
+ { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 16 },
+ { {-0.25f+0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 17 },
+ { { 0.25f+0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 18 },
+ { {-0.25f+0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 19 },
+ { { 0.25f+0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 20 },
+ { {-0.25f+0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 21 },
+ { { 0.25f+0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 22 },
+ { {-0.25f+0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 23 },
+ { { 0.25f+0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 2, NvBlastChunkDesc::NoFlags, 24 },
+ { {-0.25f-0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 25 },
+ { { 0.25f-0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 26 },
+ { {-0.25f-0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 27 },
+ { { 0.25f-0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 28 },
+ { {-0.25f-0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 29 },
+ { { 0.25f-0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 30 },
+ { {-0.25f-0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 31 },
+ { { 0.25f-0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 3, NvBlastChunkDesc::NoFlags, 32 },
+ { {-0.25f+0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 33 },
+ { { 0.25f+0.5f,-0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 34 },
+ { {-0.25f+0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 35 },
+ { { 0.25f+0.5f, 0.25f+0.5f,-0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 36 },
+ { {-0.25f+0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 37 },
+ { { 0.25f+0.5f,-0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 38 },
+ { {-0.25f+0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 39 },
+ { { 0.25f+0.5f, 0.25f+0.5f, 0.25f-0.5f }, 0.125f, 4, NvBlastChunkDesc::NoFlags, 40 },
+ { {-0.25f-0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 41 },
+ { { 0.25f-0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 42 },
+ { {-0.25f-0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 43 },
+ { { 0.25f-0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 44 },
+ { {-0.25f-0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 45 },
+ { { 0.25f-0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 46 },
+ { {-0.25f-0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 47 },
+ { { 0.25f-0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 5, NvBlastChunkDesc::NoFlags, 48 },
+ { {-0.25f+0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 49 },
+ { { 0.25f+0.5f,-0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 50 },
+ { {-0.25f+0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 51 },
+ { { 0.25f+0.5f, 0.25f-0.5f,-0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 52 },
+ { {-0.25f+0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 53 },
+ { { 0.25f+0.5f,-0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 54 },
+ { {-0.25f+0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 55 },
+ { { 0.25f+0.5f, 0.25f-0.5f, 0.25f+0.5f }, 0.125f, 6, NvBlastChunkDesc::NoFlags, 56 },
+ { {-0.25f-0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 57 },
+ { { 0.25f-0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 58 },
+ { {-0.25f-0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 59 },
+ { { 0.25f-0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 60 },
+ { {-0.25f-0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 61 },
+ { { 0.25f-0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 62 },
+ { {-0.25f-0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 63 },
+ { { 0.25f-0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 7, NvBlastChunkDesc::NoFlags, 64 },
+ { {-0.25f+0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 65 },
+ { { 0.25f+0.5f,-0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 66 },
+ { {-0.25f+0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 67 },
+ { { 0.25f+0.5f, 0.25f+0.5f,-0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 68 },
+ { {-0.25f+0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 69 },
+ { { 0.25f+0.5f,-0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 70 },
+ { {-0.25f+0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 71 },
+ { { 0.25f+0.5f, 0.25f+0.5f, 0.25f+0.5f }, 0.125f, 8, NvBlastChunkDesc::NoFlags, 72 },
+};
+
+const NvBlastChunkDesc g_cube3ChunkDescs[11] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 4.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { { 0.0f, 0.0f, 0.0f }, 3.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 1 },
+ { { 0.0f, 0.0f, 0.0f }, 1.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 2 },
+ { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f, 0.5f,-0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f,-0.5f, 0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f,-0.5f, 0.5f }, 1.0f, 1, NvBlastChunkDesc::SupportFlag, 8 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 2, NvBlastChunkDesc::SupportFlag, 9 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 2, NvBlastChunkDesc::SupportFlag, 10 },
+};
+
+const NvBlastBondDesc g_cube3BondDescs[12] =
+{
+ // chunks normal area centroid userData
+ { { 3, 4 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f,-0.5f,-0.5f }, 0 } },
+ { { 5, 6 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f, 0.5f,-0.5f }, 0 } },
+ { { 7, 8 },{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f,-0.5f, 0.5f }, 0 } },
+ { { 9, 10},{ { 1.0f, 0.0f, 0.0f }, 1.0f,{ 0.0f, 0.5f, 0.5f }, 0 } },
+
+ { { 3, 5 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{-0.5f, 0.0f,-0.5f }, 0 } },
+ { { 4, 6 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.5f, 0.0f,-0.5f }, 0 } },
+ { { 7, 9 },{ { 0.0f, 1.0f, 0.0f }, 1.0f,{-0.5f, 0.0f, 0.5f }, 0 } },
+ { { 8, 10},{ { 0.0f, 1.0f, 0.0f }, 1.0f,{ 0.5f, 0.0f, 0.5f }, 0 } },
+
+ { { 3, 7 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{-0.5f,-0.5f, 0.0f }, 0 } },
+ { { 4, 8 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{ 0.5f,-0.5f, 0.0f }, 0 } },
+ { { 5, 9 },{ { 0.0f, 0.0f, 1.0f }, 1.0f,{-0.5f, 0.5f, 0.0f }, 0 } },
+ { { 6, 10},{ { 0.0f, 0.0f, 1.0f }, 1.0f,{ 0.5f, 0.5f, 0.0f }, 0 } },
+};
+
+const NvBlastAssetDesc g_assetDescs[3] =
+{
+ // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks
+ { sizeof(g_cube1ChunkDescs) / sizeof(g_cube1ChunkDescs[0]), g_cube1ChunkDescs, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs },
+
+ // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks which are then split into 8 depth-2 (1/2)x(1/2)x(1/2) child chunks each
+ // Support is at depth-1, so the g_cube1BondDescs are used
+ { sizeof(g_cube2ChunkDescs) / sizeof(g_cube2ChunkDescs[0]), g_cube2ChunkDescs, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs },
+
+ // 2x2x2 axis-aligned cube centered at the origin, split into 8 depth-1 1x1x1 child chunks with multiple roots
+ { sizeof(g_cube3ChunkDescs) / sizeof(g_cube3ChunkDescs[0]), g_cube3ChunkDescs, sizeof(g_cube3BondDescs) / sizeof(g_cube3BondDescs[0]), g_cube3BondDescs },
+};
+
+
+
+struct ExpectedValues
+{
+ uint32_t totalChunkCount;
+ uint32_t graphNodeCount;
+ uint32_t leafChunkCount;
+ uint32_t bondCount;
+ uint32_t subsupportChunkCount;
+};
+
+const ExpectedAssetValues g_assetExpectedValues[3] =
+{
+// total graph leaves bonds sub
+ { 9, 8, 8, 12, 0 },
+ { 73, 8, 64, 12, 64 },
+ { 11, 8, 8, 12, 0 }
+};
+
+
+///////////// Badly-formed asset descs below //////////////
+
+const NvBlastChunkDesc g_cube1ChunkDescsMissingCoverage[9] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 1 },
+ { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 },
+};
+
+
+const NvBlastChunkDesc g_cube2ChunkDescsMissingCoverage1[17] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 1 },
+ { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 },
+ { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 },
+ { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 },
+ { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 },
+ { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 },
+ { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 },
+ { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 },
+ { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 },
+ { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 16 },
+};
+
+
+const NvBlastChunkDesc g_cube2ChunkDescsMissingCoverage2[17] =
+{
+// centroid volume parent idx flags ID
+ { { 0.0f, 0.0f, 0.0f }, 8.0f, UINT32_MAX, NvBlastChunkDesc::NoFlags, 0 },
+ { {-0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 1 },
+ { { 0.5f,-0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 2 },
+ { {-0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 3 },
+ { { 0.5f, 0.5f,-0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 4 },
+ { {-0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 5 },
+ { { 0.5f,-0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 6 },
+ { {-0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::SupportFlag, 7 },
+ { { 0.5f, 0.5f, 0.5f }, 1.0f, 0, NvBlastChunkDesc::NoFlags, 8 },
+ { {-0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 9 },
+ { { 0.25f-0.5f,-0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 10 },
+ { {-0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 11 },
+ { { 0.25f-0.5f, 0.25f-0.5f,-0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 12 },
+ { {-0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 13 },
+ { { 0.25f-0.5f,-0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 14 },
+ { {-0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::NoFlags, 15 },
+ { { 0.25f-0.5f, 0.25f-0.5f, 0.25f-0.5f }, 0.125f, 1, NvBlastChunkDesc::SupportFlag, 16 },
+};
+
+
+const NvBlastAssetDesc g_assetDescsMissingCoverage[3] =
+{
+ { sizeof(g_cube1ChunkDescsMissingCoverage) / sizeof(g_cube1ChunkDescsMissingCoverage[0]), g_cube1ChunkDescsMissingCoverage, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs },
+ { sizeof(g_cube2ChunkDescsMissingCoverage1) / sizeof(g_cube2ChunkDescsMissingCoverage1[0]), g_cube2ChunkDescsMissingCoverage1, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs },
+ { sizeof(g_cube2ChunkDescsMissingCoverage2) / sizeof(g_cube2ChunkDescsMissingCoverage2[0]), g_cube2ChunkDescsMissingCoverage2, sizeof(g_cube1BondDescs) / sizeof(g_cube1BondDescs[0]), g_cube1BondDescs },
+};
+
+extern const ExpectedAssetValues g_assetsFromMissingCoverageExpectedValues[3] =
+{
+// total graph leaves bonds sub
+ { 9, 8, 8, 12, 0 },
+ { 17, 8, 15, 12, 8 },
+ { 17, 15, 15, 9, 0 },
+};
+
+
+void generateCube(GeneratorAsset& cubeAsset, size_t maxDepth, size_t width, int32_t supportDepth)
+{
+ CubeAssetGenerator::Settings settings;
+ settings.extents = GeneratorAsset::Vec3(1, 1, 1);
+ CubeAssetGenerator::DepthInfo depthInfo;
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3(1, 1, 1);
+ depthInfo.flag = NvBlastChunkDesc::Flags::NoFlags;
+ settings.depths.push_back(depthInfo);
+
+ for (uint32_t depth = 1; depth < maxDepth; ++depth)
+ {
+ depthInfo.slicesPerAxis = GeneratorAsset::Vec3((float)width, (float)width, (float)width);
+ settings.depths.push_back(depthInfo);
+ }
+ settings.depths[(supportDepth > 0 ? supportDepth : maxDepth) - 1].flag = NvBlastChunkDesc::SupportFlag; // Leaves are support
+
+ CubeAssetGenerator::generate(cubeAsset, settings);
+}
+
+void generateCube(GeneratorAsset& cubeAsset, NvBlastAssetDesc& assetDesc, size_t maxDepth, size_t width, int32_t supportDepth)
+{
+ generateCube(cubeAsset, maxDepth, width, supportDepth);
+ assetDesc.bondCount = (uint32_t)cubeAsset.solverBonds.size();
+ assetDesc.bondDescs = cubeAsset.solverBonds.data();
+ assetDesc.chunkCount = (uint32_t)cubeAsset.chunks.size();
+ assetDesc.chunkDescs = cubeAsset.solverChunks.data();
+} \ No newline at end of file
diff --git a/NvBlast/test/src/utils/TestAssets.h b/NvBlast/test/src/utils/TestAssets.h
new file mode 100644
index 0000000..e4a9c77
--- /dev/null
+++ b/NvBlast/test/src/utils/TestAssets.h
@@ -0,0 +1,40 @@
+#ifndef TESTASSETS_H
+#define TESTASSETS_H
+
+#include "NvBlast.h"
+#include "AssetGenerator.h"
+
+struct ExpectedAssetValues
+{
+ uint32_t totalChunkCount;
+ uint32_t graphNodeCount;
+ uint32_t leafChunkCount;
+ uint32_t bondCount;
+ uint32_t subsupportChunkCount;
+};
+
+
+// Indexable asset descriptors and expected values
+extern const NvBlastAssetDesc g_assetDescs[3];
+extern const ExpectedAssetValues g_assetExpectedValues[3];
+
+// Indexable asset descriptors for assets missing coverage and expected values
+extern const NvBlastAssetDesc g_assetDescsMissingCoverage[3];
+extern const ExpectedAssetValues g_assetsFromMissingCoverageExpectedValues[3];
+
+
+inline uint32_t getAssetDescCount()
+{
+ return sizeof(g_assetDescs) / sizeof(g_assetDescs[0]);
+}
+
+inline uint32_t getAssetDescMissingCoverageCount()
+{
+ return sizeof(g_assetDescsMissingCoverage) / sizeof(g_assetDescsMissingCoverage[0]);
+}
+
+
+void generateCube(GeneratorAsset& cubeAsset, size_t maxDepth, size_t width, int32_t supportDepth = -1);
+void generateCube(GeneratorAsset& cubeAsset, NvBlastAssetDesc& assetDesc, size_t maxDepth, size_t width, int32_t supportDepth = -1);
+
+#endif // #ifdef TESTASSETS_H
diff --git a/NvBlast/test/src/utils/TestProfiler.h b/NvBlast/test/src/utils/TestProfiler.h
new file mode 100644
index 0000000..318e6e6
--- /dev/null
+++ b/NvBlast/test/src/utils/TestProfiler.h
@@ -0,0 +1,27 @@
+#ifndef TESTPROFILER_H
+#define TESTPROFILER_H
+
+#include "NvBlastPreprocessor.h"
+
+#if NV_NVTX
+#include "nvToolsExt.h"
+NV_INLINE void platformZoneStart(const char* name) { nvtxRangePush(name); }
+NV_INLINE void platformZoneEnd(const char*) { nvtxRangePop(); }
+
+#elif NV_XBOXONE
+#define NOMINMAX
+#include "xboxone/NvBlastProfilerXB1.h"
+
+#elif NV_PS4
+#include "ps4/NvBlastProfilerPS4.h"
+
+#else
+NV_INLINE void platformZoneStart(const char*) { }
+NV_INLINE void platformZoneEnd(const char*) { }
+
+#endif
+
+#define TEST_ZONE_BEGIN(name) platformZoneStart(name)
+#define TEST_ZONE_END(name) platformZoneEnd(name)
+
+#endif // TESTPROFILER_H