MeshExporter.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzFramework/StringFunc/StringFunc.h>
  9. #include <AzToolsFramework/Debug/TraceContext.h>
  10. #include <SceneAPI/SceneCore/Containers/Utilities/Filters.h>
  11. #include <SceneAPI/SceneCore/Containers/Utilities/SceneGraphUtilities.h>
  12. #include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
  13. #include <SceneAPI/SceneCore/Events/ExportEventContext.h>
  14. #include <SceneAPI/SceneCore/Events/ExportProductList.h>
  15. #include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
  16. #include <SceneAPI/SceneCore/Utilities/Reporting.h>
  17. #include <SceneAPI/SceneCore/Containers/Utilities/SceneUtilities.h>
  18. #include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
  19. #include <SceneAPI/SceneCore/DataTypes/GraphData/IMaterialData.h>
  20. #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
  21. #include <PhysX/MeshAsset.h>
  22. #include <Source/Pipeline/MeshAssetHandler.h>
  23. #include <Source/Pipeline/MeshExporter.h>
  24. #include <Source/Pipeline/PrimitiveShapeFitter/PrimitiveShapeFitter.h>
  25. #include <Source/Pipeline/MeshGroup.h>
  26. #include <Source/Utils.h>
  27. #include <PxPhysicsAPI.h>
  28. #include <VHACD.h>
  29. #include <Cry_Math.h>
  30. #include <MathConversion.h>
  31. #include <AzCore/IO/SystemFile.h>
  32. #include <AzCore/Math/Matrix3x3.h>
  33. #include <AzCore/XML/rapidxml.h>
  34. #include <AzCore/std/algorithm.h>
  35. #include <AzCore/std/smart_ptr/make_shared.h>
  36. // A utility macro helping set/clear bits in a single line
  37. #define SET_BITS(flags, condition, bits) flags = (condition) ? ((flags) | (bits)) : ((flags) & ~(bits))
  38. namespace PhysX
  39. {
  40. namespace Pipeline
  41. {
  42. namespace SceneContainers = AZ::SceneAPI::Containers;
  43. namespace SceneEvents = AZ::SceneAPI::Events;
  44. namespace SceneUtil = AZ::SceneAPI::Utilities;
  45. static physx::PxDefaultAllocator pxDefaultAllocatorCallback;
  46. static const char* const DefaultMaterialName = "default";
  47. // Implementation of the PhysX error callback interface directing errors to ErrorWindow output.
  48. static class PxExportErrorCallback
  49. : public physx::PxErrorCallback
  50. {
  51. public:
  52. void reportError([[maybe_unused]] physx::PxErrorCode::Enum code, [[maybe_unused]] const char* message, [[maybe_unused]] const char* file, [[maybe_unused]] int line) override final
  53. {
  54. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "PxErrorCode %i: %s (line %i in %s)", code, message, line, file);
  55. }
  56. } pxDefaultErrorCallback;
  57. // A struct to store the geometry data per scene node
  58. struct NodeCollisionGeomExportData
  59. {
  60. AZStd::vector<Vec3> m_vertices;
  61. AZStd::vector<vtx_idx> m_indices;
  62. AZStd::vector<AZ::u16> m_perFaceMaterialIndices;
  63. AZStd::string m_nodeName;
  64. };
  65. // Implementation of the V-HACD log callback interface directing all messages to LogWindow output.
  66. static class VHACDLogCallback
  67. : public VHACD::IVHACD::IUserLogger
  68. {
  69. void Log([[maybe_unused]] const char* const msg) override final
  70. {
  71. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "V-HACD: %s", msg);
  72. }
  73. } vhacdDefaultLogCallback;
  74. // Scope-guarded interface to VHACD library. Uses lazy initialization and RAII to free resources upon deletion.
  75. class ScopedVHACD
  76. {
  77. public:
  78. ScopedVHACD()
  79. : vhacdPtr(nullptr)
  80. {
  81. }
  82. ~ScopedVHACD()
  83. {
  84. if (vhacdPtr)
  85. {
  86. vhacdPtr->Clean();
  87. vhacdPtr->Release();
  88. }
  89. }
  90. ScopedVHACD(ScopedVHACD const&) = delete;
  91. ScopedVHACD& operator=(ScopedVHACD const&) = delete;
  92. VHACD::IVHACD& operator*()
  93. {
  94. return *GetImplementation();
  95. }
  96. VHACD::IVHACD* operator->()
  97. {
  98. return GetImplementation();
  99. }
  100. private:
  101. VHACD::IVHACD* GetImplementation()
  102. {
  103. if (!vhacdPtr)
  104. {
  105. vhacdPtr = VHACD::CreateVHACD();
  106. AZ_Assert(vhacdPtr, "Failed to create VHACD instance.");
  107. }
  108. return vhacdPtr;
  109. }
  110. VHACD::IVHACD* vhacdPtr;
  111. };
  112. MeshExporter::MeshExporter()
  113. {
  114. BindToCall(&MeshExporter::ProcessContext);
  115. }
  116. void MeshExporter::Reflect(AZ::ReflectContext* context)
  117. {
  118. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  119. if (serializeContext)
  120. {
  121. serializeContext->Class<MeshExporter, AZ::SceneAPI::SceneCore::ExportingComponent>()
  122. ->Version(7 + (1<<PX_PHYSICS_VERSION_MAJOR)); // Use PhysX version to trigger assets recompilation
  123. }
  124. }
  125. namespace Utils
  126. {
  127. // Utility function doing look-up in sourceSceneMaterialNames and inserting the name if it's not found
  128. AZ::u16 InsertMaterialIndexByName(const AZStd::string& materialName, AssetMaterialsData& materials)
  129. {
  130. AZStd::vector<AZStd::string>& sourceSceneMaterialNames = materials.m_sourceSceneMaterialNames;
  131. AZStd::unordered_map<AZStd::string, size_t>& materialIndexByName = materials.m_materialIndexByName;
  132. // Check if we have this material in the list
  133. auto materialIndexIter = materialIndexByName.find(materialName);
  134. if (materialIndexIter != materialIndexByName.end())
  135. {
  136. return static_cast<AZ::u16>(materialIndexIter->second);
  137. }
  138. // Add it to the list otherwise
  139. sourceSceneMaterialNames.push_back(materialName);
  140. AZ::u16 newIndex = static_cast<AZ::u16>(sourceSceneMaterialNames.size() - 1);
  141. materialIndexByName[materialName] = newIndex;
  142. return newIndex;
  143. }
  144. void UpdateAssetPhysicsMaterials(
  145. const AZStd::vector<AZStd::string>& newMaterials,
  146. Physics::MaterialSlots& physicsMaterialSlots)
  147. {
  148. Physics::MaterialSlots newSlots;
  149. newSlots.SetSlots(newMaterials);
  150. // The new material list could have different names or be in a different order,
  151. // because they are obtained from the current mesh nodes selected.
  152. // Go through the previous slots and keep the same physics material
  153. // association if the slot name is the same.
  154. // Example:
  155. //
  156. // Previous Material Slots from MeshGroup:
  157. // Material_A: glass.physicsmaterial
  158. // Material_B: sand.physicsmaterial
  159. // Material_C: gold.physicsmaterial
  160. //
  161. // Materials now extracted from mesh nodes selected:
  162. // Material_C
  163. // Material_A
  164. //
  165. // New Material Slots have to keep the same physics materials association:
  166. // Material_C: gold.physicsmaterial
  167. // Material_A: glass.physicsmaterial
  168. //
  169. for (size_t newSlotId = 0; newSlotId < newSlots.GetSlotsCount(); ++newSlotId)
  170. {
  171. for (size_t prevSlotId = 0; prevSlotId < physicsMaterialSlots.GetSlotsCount(); ++prevSlotId)
  172. {
  173. if (AZ::StringFunc::Equal(physicsMaterialSlots.GetSlotName(prevSlotId), newSlots.GetSlotName(newSlotId), false/*bCaseSensitive*/))
  174. {
  175. const auto materialAsset = physicsMaterialSlots.GetMaterialAsset(prevSlotId);
  176. // Note: Material asset is also valid if it's got a path hint (which will be resolved
  177. // by PhysX MeshAssetHandler after loading the MeshAsset).
  178. if (materialAsset.GetId().IsValid() || !materialAsset.GetHint().empty())
  179. {
  180. newSlots.SetMaterialAsset(newSlotId, materialAsset);
  181. }
  182. break;
  183. }
  184. }
  185. }
  186. // The material slots data come from MeshGroup. MeshGroup created from FBX Settings
  187. // will always generate Material Slots with a valid name in them (extracted from the mesh).
  188. // But when MeshGroup data is generated procedurally it's not possible to know what's the
  189. // material name from the mesh nodes. In order to cover this case, when the slot name is
  190. // default or empty the physics materials assigned will still be used in the new slots.
  191. // Example:
  192. //
  193. // Previous Material Slots from MeshGroup:
  194. // "": glass.physicsmaterial
  195. //
  196. // Materials now extracted from mesh nodes selected:
  197. // Material_C
  198. // Material_A
  199. //
  200. // New Material Slots will keep physics materials from empty slot names
  201. // Material_C: glass.physicsmaterial
  202. // Material_A:
  203. //
  204. for (size_t slotId = 0; slotId < physicsMaterialSlots.GetSlotsCount(); ++slotId)
  205. {
  206. if (physicsMaterialSlots.GetSlotName(slotId).empty() ||
  207. physicsMaterialSlots.GetSlotName(slotId) == Physics::MaterialSlots::EntireObjectSlotName)
  208. {
  209. const auto materialAsset = physicsMaterialSlots.GetMaterialAsset(slotId);
  210. // Note: Material asset is also valid if it's got a path hint (which will be resolved
  211. // by PhysX MeshAssetHandler after loading the MeshAsset).
  212. if (materialAsset.GetId().IsValid() || !materialAsset.GetHint().empty())
  213. {
  214. newSlots.SetMaterialAsset(slotId, materialAsset);
  215. }
  216. }
  217. }
  218. physicsMaterialSlots = AZStd::move(newSlots);
  219. }
  220. bool ValidateCookedTriangleMesh(void* assetData, AZ::u32 assetDataSize)
  221. {
  222. physx::PxDefaultMemoryInputData inpStream(static_cast<physx::PxU8*>(assetData), assetDataSize);
  223. physx::PxTriangleMesh* triangleMesh = PxGetPhysics().createTriangleMesh(inpStream);
  224. bool success = triangleMesh != nullptr;
  225. triangleMesh->release();
  226. return success;
  227. }
  228. bool ValidateCookedConvexMesh(void* assetData, AZ::u32 assetDataSize)
  229. {
  230. physx::PxDefaultMemoryInputData inpStream(static_cast<physx::PxU8*>(assetData), assetDataSize);
  231. physx::PxConvexMesh* convexMesh = PxGetPhysics().createConvexMesh(inpStream);
  232. bool success = convexMesh != nullptr;
  233. convexMesh->release();
  234. return success;
  235. }
  236. AZStd::vector<AZStd::string> GenerateLocalNodeMaterialMap(const AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex)
  237. {
  238. AZStd::vector<AZStd::string> materialNames;
  239. auto view = AZ::SceneAPI::Containers::Views::MakeSceneGraphChildView<AZ::SceneAPI::Containers::Views::AcceptEndPointsOnly>(
  240. graph,
  241. nodeIndex,
  242. graph.GetContentStorage().begin(),
  243. true
  244. );
  245. for (auto it = view.begin(), itEnd = view.end(); it != itEnd; ++it)
  246. {
  247. if ((*it) && (*it)->RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IMaterialData::TYPEINFO_Uuid()))
  248. {
  249. AZStd::string nodeName = graph.GetNodeName(graph.ConvertToNodeIndex(it.GetHierarchyIterator())).GetName();
  250. materialNames.push_back(nodeName);
  251. }
  252. }
  253. return materialNames;
  254. }
  255. AZStd::optional<AssetMaterialsData> GatherMaterialsFromMeshGroup(
  256. const MeshGroup& meshGroup,
  257. const AZ::SceneAPI::Containers::SceneGraph& sceneGraph)
  258. {
  259. AssetMaterialsData assetMaterialData;
  260. const auto& sceneNodeSelectionList = meshGroup.GetSceneNodeSelectionList();
  261. size_t selectedNodeCount = sceneNodeSelectionList.GetSelectedNodeCount();
  262. for (size_t index = 0; index < selectedNodeCount; index++)
  263. {
  264. AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = sceneGraph.Find(sceneNodeSelectionList.GetSelectedNode(index));
  265. if (!nodeIndex.IsValid())
  266. {
  267. AZ_TracePrintf(
  268. AZ::SceneAPI::Utilities::WarningWindow,
  269. "Node '%s' was not found in the scene graph.",
  270. sceneNodeSelectionList.GetSelectedNode(index).c_str()
  271. );
  272. continue;
  273. }
  274. auto nodeMesh = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*sceneGraph.ConvertToStorageIterator(nodeIndex));
  275. if (!nodeMesh)
  276. {
  277. continue;
  278. }
  279. AZStd::string_view nodeName = sceneGraph.GetNodeName(nodeIndex).GetName();
  280. const AZStd::vector<AZStd::string> localSourceSceneMaterialsList = GenerateLocalNodeMaterialMap(sceneGraph, nodeIndex);
  281. if (localSourceSceneMaterialsList.empty())
  282. {
  283. AZ_TracePrintf(
  284. AZ::SceneAPI::Utilities::WarningWindow,
  285. "Node '%.*s' does not have any material assigned to it. Material '%s' will be used.",
  286. AZ_STRING_ARG(nodeName), DefaultMaterialName
  287. );
  288. }
  289. const AZ::u32 faceCount = nodeMesh->GetFaceCount();
  290. assetMaterialData.m_nodesToPerFaceMaterialIndices.emplace(nodeName, AZStd::vector<AZ::u16>(faceCount));
  291. // Convex and primitive methods can only have 1 material per node.
  292. const bool limitToOneMaterial = meshGroup.GetExportAsConvex() || meshGroup.GetExportAsPrimitive();
  293. AZStd::string firstMaterial;
  294. AZStd::set<AZStd::string> nodeMaterials;
  295. for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
  296. {
  297. AZStd::string materialName = DefaultMaterialName;
  298. if (!localSourceSceneMaterialsList.empty())
  299. {
  300. const int materialId = nodeMesh->GetFaceMaterialId(faceIndex);
  301. if (materialId >= localSourceSceneMaterialsList.size())
  302. {
  303. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow,
  304. "materialId %d for face %d is out of bound for localSourceSceneMaterialsList (size %d).",
  305. materialId, faceIndex, localSourceSceneMaterialsList.size());
  306. return AZStd::nullopt;
  307. }
  308. materialName = localSourceSceneMaterialsList[materialId];
  309. // Use the first material found in the mesh when it has to be limited to one.
  310. if (limitToOneMaterial)
  311. {
  312. nodeMaterials.insert(materialName);
  313. if (firstMaterial.empty())
  314. {
  315. firstMaterial = materialName;
  316. }
  317. materialName = firstMaterial;
  318. }
  319. }
  320. const AZ::u16 materialIndex = InsertMaterialIndexByName(materialName, assetMaterialData);
  321. assetMaterialData.m_nodesToPerFaceMaterialIndices[nodeName][faceIndex] = materialIndex;
  322. }
  323. if (limitToOneMaterial && nodeMaterials.size() > 1)
  324. {
  325. AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow,
  326. "Node '%s' has %d materials, but cooking methods Convex and Primitive support one material per node. The first material '%s' will be used.",
  327. sceneNodeSelectionList.GetSelectedNode(index).c_str(), nodeMaterials.size(), firstMaterial.c_str());
  328. }
  329. }
  330. return assetMaterialData;
  331. }
  332. }
  333. static physx::PxMeshMidPhase::Enum GetMidPhaseStructureType([[maybe_unused]] const AZStd::string& platformIdentifier)
  334. {
  335. // Use by default 3.4 since 3.3 is being deprecated (despite being default)
  336. physx::PxMeshMidPhase::Enum ret = physx::PxMeshMidPhase::eBVH34;
  337. #if (PX_PHYSICS_VERSION_MAJOR < 5)
  338. // Fallback to 3.3 on Android and iOS platforms since they don't support SSE2, which is required for 3.4
  339. // Also fall back to 3.3 for Linux, since linux may support both x86 and arm64, and ARM64 does not support SSE2.
  340. if (platformIdentifier == "android" || platformIdentifier == "ios" || platformIdentifier == "linux")
  341. {
  342. ret = physx::PxMeshMidPhase::eBVH33;
  343. }
  344. #endif
  345. return ret;
  346. }
  347. // Checks that the entire mesh is assigned (at most) one material (required for convexes and primitives).
  348. static void RequireSingleFaceMaterial(const AZStd::vector<AZ::u16>& faceMaterials)
  349. {
  350. AZStd::unordered_set<AZ::u16> uniqueFaceMaterials(faceMaterials.begin(), faceMaterials.end());
  351. if (uniqueFaceMaterials.size() > 1)
  352. {
  353. AZ_TracePrintf(AZ::SceneAPI::Utilities::WarningWindow,
  354. "Should only have 1 material assigned to a non-triangle mesh. Assigned: %d", uniqueFaceMaterials.size());
  355. }
  356. }
  357. // Cooks the geometry provided into a memory buffer based on the rules set in MeshGroup
  358. bool CookPhysXMesh(
  359. const AZStd::vector<Vec3>& vertices,
  360. const AZStd::vector<AZ::u32>& indices,
  361. const AZStd::vector<AZ::u16>& faceMaterials,
  362. AZStd::vector<AZ::u8>* output,
  363. const MeshGroup& meshGroup,
  364. const AZStd::string& platformIdentifier)
  365. {
  366. bool cookingSuccessful = false;
  367. AZStd::string cookingResultErrorCodeString;
  368. const ConvexAssetParams& convexAssetParams = meshGroup.GetConvexAssetParams();
  369. const TriangleMeshAssetParams& triangleMeshAssetParams = meshGroup.GetTriangleMeshAssetParams();
  370. bool shouldExportAsConvex = meshGroup.GetExportAsConvex();
  371. physx::PxCookingParams pxCookingParams = physx::PxCookingParams(physx::PxTolerancesScale());
  372. pxCookingParams.buildGPUData = false;
  373. pxCookingParams.midphaseDesc.setToDefault(GetMidPhaseStructureType(platformIdentifier));
  374. if (shouldExportAsConvex)
  375. {
  376. if (convexAssetParams.GetCheckZeroAreaTriangles())
  377. {
  378. pxCookingParams.areaTestEpsilon = convexAssetParams.GetAreaTestEpsilon();
  379. }
  380. pxCookingParams.planeTolerance = convexAssetParams.GetPlaneTolerance();
  381. pxCookingParams.gaussMapLimit = convexAssetParams.GetGaussMapLimit();
  382. }
  383. else
  384. {
  385. pxCookingParams.midphaseDesc.mBVH34Desc.numPrimsPerLeaf = triangleMeshAssetParams.GetNumTrisPerLeaf();
  386. pxCookingParams.meshWeldTolerance = triangleMeshAssetParams.GetMeshWeldTolerance();
  387. pxCookingParams.buildTriangleAdjacencies = triangleMeshAssetParams.GetBuildTriangleAdjacencies();
  388. pxCookingParams.suppressTriangleMeshRemapTable = triangleMeshAssetParams.GetSuppressTriangleMeshRemapTable();
  389. if (triangleMeshAssetParams.GetWeldVertices())
  390. {
  391. pxCookingParams.meshPreprocessParams |= physx::PxMeshPreprocessingFlag::eWELD_VERTICES;
  392. }
  393. if (triangleMeshAssetParams.GetDisableCleanMesh())
  394. {
  395. pxCookingParams.meshPreprocessParams |= physx::PxMeshPreprocessingFlag::eDISABLE_CLEAN_MESH;
  396. }
  397. if (triangleMeshAssetParams.GetForce32BitIndices())
  398. {
  399. pxCookingParams.meshPreprocessParams |= physx::PxMeshPreprocessingFlag::eFORCE_32BIT_INDICES;
  400. }
  401. }
  402. physx::PxCooking* pxCooking = PxCreateCooking(PX_PHYSICS_VERSION, PxGetFoundation(), pxCookingParams);
  403. AZ_Assert(pxCooking, "Failed to create PxCooking");
  404. physx::PxBoundedData strideData;
  405. strideData.count = static_cast<physx::PxU32>(vertices.size());
  406. strideData.stride = sizeof(Vec3);
  407. strideData.data = vertices.data();
  408. physx::PxDefaultMemoryOutputStream cookedMeshData;
  409. if (shouldExportAsConvex)
  410. {
  411. physx::PxConvexMeshDesc convexDesc;
  412. convexDesc.points = strideData;
  413. convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
  414. SET_BITS(convexDesc.flags, convexAssetParams.GetUse16bitIndices(), physx::PxConvexFlag::e16_BIT_INDICES);
  415. SET_BITS(convexDesc.flags, convexAssetParams.GetCheckZeroAreaTriangles(), physx::PxConvexFlag::eCHECK_ZERO_AREA_TRIANGLES);
  416. SET_BITS(convexDesc.flags, convexAssetParams.GetQuantizeInput(), physx::PxConvexFlag::eQUANTIZE_INPUT);
  417. SET_BITS(convexDesc.flags, convexAssetParams.GetUsePlaneShifting(), physx::PxConvexFlag::ePLANE_SHIFTING);
  418. SET_BITS(convexDesc.flags, convexAssetParams.GetBuildGpuData(), physx::PxConvexFlag::eGPU_COMPATIBLE);
  419. SET_BITS(convexDesc.flags, convexAssetParams.GetShiftVertices(), physx::PxConvexFlag::eSHIFT_VERTICES);
  420. physx::PxConvexMeshCookingResult::Enum convexCookingResultCode = physx::PxConvexMeshCookingResult::eSUCCESS;
  421. cookingSuccessful =
  422. pxCooking->cookConvexMesh(convexDesc, cookedMeshData, &convexCookingResultCode)
  423. && Utils::ValidateCookedConvexMesh(cookedMeshData.getData(), cookedMeshData.getSize());
  424. cookingResultErrorCodeString = PhysX::Utils::ConvexCookingResultToString(convexCookingResultCode);
  425. // Check how many unique materials are assigned onto the convex mesh.
  426. // Report it to the user if there's more than 1 since PhysX only supports a single material assigned to a convex
  427. RequireSingleFaceMaterial(faceMaterials);
  428. }
  429. else
  430. {
  431. physx::PxTriangleMeshDesc meshDesc;
  432. meshDesc.points = strideData;
  433. meshDesc.triangles.count = static_cast<physx::PxU32>(indices.size() / 3);
  434. meshDesc.triangles.stride = sizeof(AZ::u32) * 3;
  435. meshDesc.triangles.data = indices.data();
  436. meshDesc.materialIndices.stride = sizeof(AZ::u16);
  437. meshDesc.materialIndices.data = faceMaterials.data();
  438. physx::PxTriangleMeshCookingResult::Enum trimeshCookingResultCode = physx::PxTriangleMeshCookingResult::eSUCCESS;
  439. cookingSuccessful =
  440. pxCooking->cookTriangleMesh(meshDesc, cookedMeshData, &trimeshCookingResultCode)
  441. && Utils::ValidateCookedTriangleMesh(cookedMeshData.getData(), cookedMeshData.getSize());
  442. cookingResultErrorCodeString = PhysX::Utils::TriMeshCookingResultToString(trimeshCookingResultCode);
  443. }
  444. if (cookingSuccessful)
  445. {
  446. output->insert(output->end(), cookedMeshData.getData(), cookedMeshData.getData() + cookedMeshData.getSize());
  447. }
  448. else
  449. {
  450. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Cooking Mesh failed: %s", cookingResultErrorCodeString.c_str());
  451. }
  452. pxCooking->release();
  453. return cookingSuccessful;
  454. }
  455. // Processes the collected data and writes into a file
  456. static AZ::SceneAPI::Events::ProcessingResult WritePxMeshAsset(
  457. AZ::SceneAPI::Events::ExportEventContext& context,
  458. const AZStd::vector<NodeCollisionGeomExportData>& totalExportData,
  459. const Utils::AssetMaterialsData &assetMaterialsData,
  460. const MeshGroup& meshGroup)
  461. {
  462. SceneEvents::ProcessingResult result = SceneEvents::ProcessingResult::Ignored;
  463. AZStd::string assetName = meshGroup.GetName();
  464. AZStd::string filename = SceneUtil::FileUtilities::CreateOutputFileName(
  465. assetName, context.GetOutputDirectory(), MeshAssetHandler::s_assetFileExtension, context.GetScene().GetSourceExtension());
  466. MeshAssetData assetData;
  467. // Assign the materials into cooked data
  468. assetData.m_materialSlots = meshGroup.GetMaterialSlots();
  469. // Updating materials lists from new materials gathered from the source scene file
  470. // because this exporter runs when the source scene is being processed, which
  471. // could have a different content from when the mesh group info was
  472. // entered in Scene Settings Editor.
  473. Utils::UpdateAssetPhysicsMaterials(assetMaterialsData.m_sourceSceneMaterialNames, assetData.m_materialSlots);
  474. for (const NodeCollisionGeomExportData& subMesh : totalExportData)
  475. {
  476. MeshAssetData::ShapeConfigurationPair shape;
  477. if (meshGroup.GetExportAsPrimitive())
  478. {
  479. // Only one material can be assigned to a primitive collider, so report a warning if the mesh has
  480. // multiple materials assigned to it.
  481. RequireSingleFaceMaterial(subMesh.m_perFaceMaterialIndices);
  482. const PrimitiveAssetParams& primitiveAssetParams = meshGroup.GetPrimitiveAssetParams();
  483. shape = FitPrimitiveShape(
  484. subMesh.m_nodeName,
  485. subMesh.m_vertices,
  486. primitiveAssetParams.GetVolumeTermCoefficient(),
  487. primitiveAssetParams.GetPrimitiveShapeTarget()
  488. );
  489. }
  490. else
  491. {
  492. // Cook the mesh into a binary buffer.
  493. AZStd::vector<AZ::u8> physxData;
  494. bool success = CookPhysXMesh(subMesh.m_vertices, subMesh.m_indices, subMesh.m_perFaceMaterialIndices,
  495. &(physxData), meshGroup, context.GetPlatformIdentifier());
  496. if (success)
  497. {
  498. AZStd::shared_ptr<Physics::CookedMeshShapeConfiguration> shapeConfig =
  499. AZStd::make_shared<Physics::CookedMeshShapeConfiguration>();
  500. shapeConfig->SetCookedMeshData(
  501. physxData.data(),
  502. physxData.size(),
  503. meshGroup.GetExportAsConvex() ? Physics::CookedMeshShapeConfiguration::MeshType::Convex
  504. : Physics::CookedMeshShapeConfiguration::MeshType::TriangleMesh
  505. );
  506. shape.second = shapeConfig;
  507. }
  508. else
  509. {
  510. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Mesh cooking terminated unsuccessfully.");
  511. }
  512. }
  513. if (shape.second)
  514. {
  515. assetData.m_colliderShapes.push_back(shape);
  516. }
  517. else
  518. {
  519. AZ_TracePrintf(
  520. AZ::SceneAPI::Utilities::ErrorWindow,
  521. "WritePxMeshAsset: Failed to create asset. Node: %s",
  522. subMesh.m_nodeName.c_str()
  523. );
  524. return SceneEvents::ProcessingResult::Failure;
  525. }
  526. if (meshGroup.GetExportAsTriMesh())
  527. {
  528. assetData.m_materialIndexPerShape.push_back(MeshAssetData::TriangleMeshMaterialIndex);
  529. }
  530. else
  531. {
  532. AZ_Assert(
  533. !subMesh.m_perFaceMaterialIndices.empty(),
  534. "WritePxMeshAsset: m_perFaceMaterialIndices must be not empty! Please make sure you have a material assigned to the geometry. Node: %s",
  535. subMesh.m_nodeName.c_str()
  536. );
  537. AZ_Assert(
  538. subMesh.m_perFaceMaterialIndices[0] != MeshAssetData::TriangleMeshMaterialIndex,
  539. "WritePxMeshAsset: m_perFaceMaterialIndices has invalid material index! Node: %s",
  540. subMesh.m_nodeName.c_str()
  541. );
  542. assetData.m_materialIndexPerShape.push_back(subMesh.m_perFaceMaterialIndices[0]);
  543. }
  544. }
  545. if (PhysX::Utils::WriteCookedMeshToFile(filename, assetData))
  546. {
  547. AZStd::string productUuidString = meshGroup.GetId().ToString<AZStd::string>();
  548. AZ::Uuid productUuid = AZ::Uuid::CreateName(productUuidString);
  549. auto& meshProduct = context.GetProductList().AddProduct(
  550. AZStd::move(filename), productUuid, AZ::AzTypeInfo<MeshAsset>::Uuid(), AZStd::nullopt, AZStd::nullopt);
  551. // Add product dependencies for every valid physics material used by this physics mesh.
  552. for (int materialIndex = 0; materialIndex < assetData.m_materialSlots.GetSlotsCount(); materialIndex++)
  553. {
  554. auto& material = assetData.m_materialSlots.GetMaterialAsset(materialIndex);
  555. if (material.GetId().IsValid())
  556. {
  557. AZ::SceneAPI::Events::ExportProduct materialProduct;
  558. materialProduct.m_filename = material.GetHint();
  559. materialProduct.m_id = material.GetId().m_guid;
  560. materialProduct.m_subId = material.GetId().m_subId;
  561. materialProduct.m_assetType = material.GetType();
  562. meshProduct.m_productDependencies.push_back(materialProduct);
  563. }
  564. }
  565. result = SceneEvents::ProcessingResult::Success;
  566. }
  567. else
  568. {
  569. AZ_TracePrintf(AZ::SceneAPI::Utilities::ErrorWindow, "Unable to write to a file for a PhysX mesh asset. AssetName: %s, filename: %s", assetName.c_str(), filename.c_str());
  570. result = SceneEvents::ProcessingResult::Failure;
  571. }
  572. return result;
  573. }
  574. void DecomposeAndAppendMeshes(
  575. ScopedVHACD& decomposer,
  576. VHACD::IVHACD::Parameters vhacdParams,
  577. AZStd::vector<NodeCollisionGeomExportData>& totalExportData,
  578. const NodeCollisionGeomExportData& nodeExportData
  579. )
  580. {
  581. RequireSingleFaceMaterial(nodeExportData.m_perFaceMaterialIndices);
  582. AZ_Assert(
  583. !nodeExportData.m_perFaceMaterialIndices.empty(),
  584. "DecomposeAndAppendMeshes: Empty per-face material vector. Node: %s",
  585. nodeExportData.m_nodeName.c_str()
  586. );
  587. decomposer->Clean();
  588. // Convert the vertices to a float array suitable for passing to V-HACD.
  589. AZStd::vector<float> vhacdVertices;
  590. vhacdVertices.reserve(nodeExportData.m_vertices.size() * 3);
  591. for (const Vec3& vertex : nodeExportData.m_vertices)
  592. {
  593. vhacdVertices.emplace_back(vertex[0]);
  594. vhacdVertices.emplace_back(vertex[1]);
  595. vhacdVertices.emplace_back(vertex[2]);
  596. }
  597. if constexpr (AZStd::is_same<decltype(nodeExportData.m_indices)::value_type, uint32_t>::value)
  598. {
  599. decomposer->Compute(
  600. vhacdVertices.data(),
  601. static_cast<uint32_t>(vhacdVertices.size() / 3),
  602. nodeExportData.m_indices.data(),
  603. static_cast<uint32_t>(nodeExportData.m_indices.size() / 3),
  604. vhacdParams
  605. );
  606. }
  607. else
  608. {
  609. // Convert the indices to an unsigned 32-bit integer array suitable for passing to V-HACD.
  610. AZStd::vector<uint32_t> vhacdIndices;
  611. vhacdIndices.reserve(nodeExportData.m_indices.size());
  612. for (auto index : nodeExportData.m_indices)
  613. {
  614. vhacdIndices.emplace_back(index);
  615. }
  616. decomposer->Compute(
  617. vhacdVertices.data(),
  618. static_cast<uint32_t>(vhacdVertices.size() / 3),
  619. vhacdIndices.data(),
  620. static_cast<uint32_t>(vhacdIndices.size() / 3),
  621. vhacdParams
  622. );
  623. }
  624. const AZ::u32 numberOfHulls = decomposer->GetNConvexHulls();
  625. AZ_Assert(numberOfHulls > 0, "V-HACD returned no convex hulls.");
  626. AZ_TracePrintf(AZ::SceneAPI::Utilities::LogWindow, "Convex decomposition returned %d hulls", numberOfHulls);
  627. for (AZ::u32 hullCounter = 0; hullCounter < numberOfHulls; ++hullCounter)
  628. {
  629. VHACD::IVHACD::ConvexHull convexHull;
  630. decomposer->GetConvexHull(hullCounter, convexHull);
  631. NodeCollisionGeomExportData nodeExportDataConvexPart;
  632. // Copy vertices.
  633. nodeExportDataConvexPart.m_vertices.reserve(convexHull.m_nPoints);
  634. for (AZ::u32 vertexCounter = 0; vertexCounter < convexHull.m_nPoints; ++vertexCounter)
  635. {
  636. double* vertex = convexHull.m_points + 3 * vertexCounter;
  637. nodeExportDataConvexPart.m_vertices.emplace_back(Vec3(
  638. static_cast<float>(vertex[0]),
  639. static_cast<float>(vertex[1]),
  640. static_cast<float>(vertex[2])
  641. ));
  642. }
  643. // Copy indices.
  644. nodeExportDataConvexPart.m_indices.reserve(convexHull.m_nTriangles * 3);
  645. for (AZ::u32 indexCounter = 0; indexCounter < convexHull.m_nTriangles * 3; ++indexCounter)
  646. {
  647. nodeExportDataConvexPart.m_indices.emplace_back(convexHull.m_triangles[indexCounter]);
  648. }
  649. // Set up single per-face material.
  650. nodeExportDataConvexPart.m_perFaceMaterialIndices = AZStd::vector<AZ::u16>(
  651. convexHull.m_nTriangles, nodeExportData.m_perFaceMaterialIndices[0]
  652. );
  653. nodeExportDataConvexPart.m_nodeName = nodeExportData.m_nodeName + "_" + AZStd::to_string(hullCounter);
  654. totalExportData.emplace_back(AZStd::move(nodeExportDataConvexPart));
  655. }
  656. }
  657. SceneEvents::ProcessingResult MeshExporter::ProcessContext(SceneEvents::ExportEventContext& context) const
  658. {
  659. AZ_TraceContext("Exporter", "PhysX");
  660. SceneEvents::ProcessingResultCombiner result;
  661. const AZ::SceneAPI::Containers::Scene& scene = context.GetScene();
  662. const AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
  663. const SceneContainers::SceneManifest& manifest = context.GetScene().GetManifest();
  664. SceneContainers::SceneManifest::ValueStorageConstData valueStorage = manifest.GetValueStorage();
  665. auto view = SceneContainers::MakeExactFilterView<MeshGroup>(valueStorage);
  666. ScopedVHACD decomposer;
  667. for (const MeshGroup& pxMeshGroup : view)
  668. {
  669. // Gather material data from asset for the mesh group
  670. AZStd::optional<Utils::AssetMaterialsData> assetMaterialData = Utils::GatherMaterialsFromMeshGroup(pxMeshGroup, graph);
  671. if (!assetMaterialData.has_value())
  672. {
  673. return SceneEvents::ProcessingResult::Failure;
  674. }
  675. // Export data per node
  676. AZStd::vector<NodeCollisionGeomExportData> totalExportData;
  677. const AZStd::string& groupName = pxMeshGroup.GetName();
  678. AZ_TraceContext("Group Name", groupName);
  679. const auto& sceneNodeSelectionList = pxMeshGroup.GetSceneNodeSelectionList();
  680. size_t selectedNodeCount = sceneNodeSelectionList.GetSelectedNodeCount();
  681. // Setup VHACD parameters if required.
  682. VHACD::IVHACD::Parameters vhacdParams;
  683. if (pxMeshGroup.GetDecomposeMeshes())
  684. {
  685. const ConvexDecompositionParams& convexDecompositionParams = pxMeshGroup.GetConvexDecompositionParams();
  686. vhacdParams.m_callback = nullptr;
  687. vhacdParams.m_logger = &vhacdDefaultLogCallback;
  688. vhacdParams.m_concavity = convexDecompositionParams.GetConcavity();
  689. vhacdParams.m_alpha = convexDecompositionParams.GetAlpha();
  690. vhacdParams.m_beta = convexDecompositionParams.GetBeta();
  691. vhacdParams.m_minVolumePerCH = convexDecompositionParams.GetMinVolumePerConvexHull();
  692. vhacdParams.m_resolution = convexDecompositionParams.GetResolution();
  693. vhacdParams.m_maxNumVerticesPerCH = convexDecompositionParams.GetMaxNumVerticesPerConvexHull();
  694. vhacdParams.m_planeDownsampling = convexDecompositionParams.GetPlaneDownsampling();
  695. vhacdParams.m_convexhullDownsampling = convexDecompositionParams.GetConvexHullDownsampling();
  696. vhacdParams.m_maxConvexHulls = convexDecompositionParams.GetMaxConvexHulls();
  697. vhacdParams.m_pca = convexDecompositionParams.GetPca();
  698. vhacdParams.m_mode = convexDecompositionParams.GetMode();
  699. vhacdParams.m_projectHullVertices = convexDecompositionParams.GetProjectHullVertices();
  700. }
  701. else
  702. {
  703. totalExportData.reserve(selectedNodeCount);
  704. }
  705. // Get the coordinate system conversion rule.
  706. AZ::SceneAPI::CoordinateSystemConverter coordSysConverter;
  707. AZStd::shared_ptr<AZ::SceneAPI::SceneData::CoordinateSystemRule> coordinateSystemRule =
  708. pxMeshGroup.GetRuleContainerConst().FindFirstByType<AZ::SceneAPI::SceneData::CoordinateSystemRule>();
  709. if (coordinateSystemRule)
  710. {
  711. coordinateSystemRule->UpdateCoordinateSystemConverter();
  712. coordSysConverter = coordinateSystemRule->GetCoordinateSystemConverter();
  713. }
  714. for (size_t index = 0; index < selectedNodeCount; index++)
  715. {
  716. AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.Find(sceneNodeSelectionList.GetSelectedNode(index));
  717. auto nodeMesh = azrtti_cast<const AZ::SceneAPI::DataTypes::IMeshData*>(*graph.ConvertToStorageIterator(nodeIndex));
  718. if (!nodeMesh)
  719. {
  720. continue;
  721. }
  722. const AZ::SceneAPI::Containers::SceneGraph::Name& nodeName = graph.GetNodeName(nodeIndex);
  723. // CoordinateSystemConverter covers the simple transformations of CoordinateSystemRule and
  724. // DetermineWorldTransform function covers the advanced mode of CoordinateSystemRule.
  725. const AZ::SceneAPI::DataTypes::MatrixType worldTransform = coordSysConverter.ConvertMatrix3x4(
  726. AZ::SceneAPI::Utilities::DetermineWorldTransform(scene, nodeIndex, pxMeshGroup.GetRuleContainerConst()));
  727. NodeCollisionGeomExportData nodeExportData;
  728. nodeExportData.m_nodeName = nodeName.GetName();
  729. const AZ::u32 vertexCount = nodeMesh->GetVertexCount();
  730. const AZ::u32 faceCount = nodeMesh->GetFaceCount();
  731. nodeExportData.m_vertices.resize(vertexCount);
  732. for (AZ::u32 vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
  733. {
  734. AZ::Vector3 pos = nodeMesh->GetPosition(vertexIndex);
  735. pos = worldTransform * pos;
  736. nodeExportData.m_vertices[vertexIndex] = AZVec3ToLYVec3(pos);
  737. }
  738. nodeExportData.m_indices.resize(faceCount * 3);
  739. nodeExportData.m_perFaceMaterialIndices = assetMaterialData->m_nodesToPerFaceMaterialIndices[nodeExportData.m_nodeName];
  740. if (nodeExportData.m_perFaceMaterialIndices.size() != faceCount)
  741. {
  742. AZ_TracePrintf(
  743. AZ::SceneAPI::Utilities::WarningWindow,
  744. "Node '%s' material information face count %d does not match the node's %d.",
  745. nodeExportData.m_nodeName.c_str(), nodeExportData.m_perFaceMaterialIndices.size(), faceCount
  746. );
  747. return SceneEvents::ProcessingResult::Failure;
  748. }
  749. for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
  750. {
  751. const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex);
  752. nodeExportData.m_indices[faceIndex * 3] = face.vertexIndex[0];
  753. nodeExportData.m_indices[faceIndex * 3 + 1] = face.vertexIndex[1];
  754. nodeExportData.m_indices[faceIndex * 3 + 2] = face.vertexIndex[2];
  755. }
  756. if (pxMeshGroup.GetDecomposeMeshes())
  757. {
  758. DecomposeAndAppendMeshes(decomposer, vhacdParams, totalExportData, nodeExportData);
  759. }
  760. else
  761. {
  762. totalExportData.emplace_back(AZStd::move(nodeExportData));
  763. }
  764. }
  765. // Merge triangle meshes if there's more than 1
  766. if (pxMeshGroup.GetExportAsTriMesh() && pxMeshGroup.GetTriangleMeshAssetParams().GetMergeMeshes() && totalExportData.size() > 1)
  767. {
  768. NodeCollisionGeomExportData mergedData;
  769. mergedData.m_nodeName = groupName;
  770. AZStd::vector<Vec3>& mergedVertices = mergedData.m_vertices;
  771. AZStd::vector<vtx_idx>& mergedIndices = mergedData.m_indices;
  772. AZStd::vector<AZ::u16>& mergedPerFaceMaterials = mergedData.m_perFaceMaterialIndices;
  773. // Here we add the geometry data for each node into a single merged one
  774. // Vertices & materials can be added directly but indices need to be incremented
  775. // by the amount of vertices already added in the last iteration
  776. for (const NodeCollisionGeomExportData& exportData : totalExportData)
  777. {
  778. vtx_idx startingIndex = static_cast<vtx_idx>(mergedVertices.size());
  779. mergedVertices.insert(mergedVertices.end(), exportData.m_vertices.begin(), exportData.m_vertices.end());
  780. mergedPerFaceMaterials.insert(mergedPerFaceMaterials.end(),
  781. exportData.m_perFaceMaterialIndices.begin(), exportData.m_perFaceMaterialIndices.end());
  782. mergedIndices.reserve(mergedIndices.size() + exportData.m_indices.size());
  783. AZStd::transform(exportData.m_indices.begin(), exportData.m_indices.end(),
  784. AZStd::back_inserter(mergedIndices), [startingIndex](vtx_idx index)
  785. {
  786. return index + startingIndex;
  787. });
  788. }
  789. // Clear the data per node and use only the merged one
  790. totalExportData.clear();
  791. totalExportData.emplace_back(AZStd::move(mergedData));
  792. }
  793. if (!totalExportData.empty())
  794. {
  795. result += WritePxMeshAsset(context, totalExportData, *assetMaterialData, pxMeshGroup);
  796. }
  797. }
  798. return result.GetResult();
  799. }
  800. }
  801. }