#include "TkBaseTest.h" #include #include #include #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 m_actorDescs; std::vector m_relTMs; std::vector m_jointDescs; }; template class TkCompositeTest : public TkBaseTest { public: virtual void* allocate(size_t size, const char* typeName, const char* filename, int line) override { void* ptr = TkBaseTest::allocate(size, typeName, filename, line); return ptr; } virtual void deallocate(void* ptr) override { return TkBaseTest::deallocate(ptr); } // Composite/joint tests void createAssembly(std::vector& actors, std::vector& 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 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& families, TestFamilyTracker& tracker) { TkFramework* fw = NvBlastTkFrameworkGet(); PsMemoryBuffer* membuf = PX_NEW(PsMemoryBuffer); EXPECT_TRUE(membuf != nullptr); if (membuf == nullptr) { return; } std::vector 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 actors(f->getActorCount()); f->getActors(actors.data(), static_cast(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(fw->deserialize(*membuf)); f->addListener(tracker); families[familyNum] = f; } for (size_t familyNum = 0; familyNum < families.size(); ++familyNum) { TkFamily* f = families[familyNum]; std::vector actors(f->getActorCount()); f->getActors(actors.data(), static_cast(actors.size())); for (auto a : actors) { tracker.insertActor(a); std::vector 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& families, std::vector& actors) { uint32_t totalActorCount = 0; for (auto family : families) { EXPECT_LE(family->getActorCount() + totalActorCount, actors.size()); totalActorCount += family->getActors(actors.data() + totalActorCount, static_cast(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 families1; std::vector families2; // Create one assembly std::vector actors1; std::vector joints1; createAssembly(actors1, joints1, createNRFJoints); tracker.joints.insert(joints1.begin(), joints1.end()); // Create another assembly std::vector actors2; std::vector 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 families; families.push_back(family); familySerialization(families, tracker); family = families[0]; std::vector actors(family->getActorCount()); family->getActors(actors.data(), static_cast(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 actors(family->getActorCount()); family->getActors(actors.data(), static_cast(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 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(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 families; // Create assembly std::vector actors; std::vector 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 a(family->getActorCount()); family->getActors(a.data(), static_cast(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::testAssets; using TkBaseTest::m_taskman; using TkBaseTest::createFramework; using TkBaseTest::releaseFramework; using TkBaseTest::createTestAssets; using TkBaseTest::releaseTestAssets; using TkBaseTest::getCubeSlicerProgram; using TkBaseTest::getDefaultMaterial; using TkBaseTest::getRadialDamageDesc; using TkBaseTest::getFalloffProgram; }; typedef TkCompositeTest TkCompositeTestAllowWarnings; typedef TkCompositeTest 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); }