/
redgpu
/
ezEngine
Обзор
Документация
Войти
/
redgpu
/
ezEngine
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
Code/EditorPlugins/Jolt/EditorPluginJolt/CollisionMeshAsset/JoltCollisionMeshAsset.cpp
523 строки
18 KB
Yuriy Balyuk
Added "Normal Weight" parameter to "Simplify Mesh" feature; ... (#1910)
21 апр 2026, 14:59
Не верифицирован
21 апр 2026, 14:59
f54587c
Код
Авторство
О чём код?
#include <EditorPluginJolt/EditorPluginJoltPCH.h> #include <EditorPluginAssets/Util/MeshImportUtils.h> #include <EditorPluginJolt/CollisionMeshAsset/JoltCollisionMeshAsset.h> #include <Foundation/IO/ChunkStream.h> #include <Foundation/Utilities/AssetFileHeader.h> #include <Foundation/Utilities/GraphicsUtils.h> #include <Foundation/Utilities/Progress.h> #include <JoltPlugin/Resources/JoltMeshResourceWriter.h> #include <ModelImporter2/ModelImporter.h> #include <RendererCore/Meshes/MeshResourceDescriptor.h> #ifdef BUILDSYSTEM_ENABLE_ZSTD_SUPPORT # include <Foundation/IO/CompressedStreamZstd.h> #endif // clang-format off EZ_BEGIN_DYNAMIC_REFLECTED_TYPE(ezJoltCollisionMeshAssetDocument, 10, ezRTTINoAllocator) EZ_END_DYNAMIC_REFLECTED_TYPE; // clang-format on static ezMat3 CalculateTransformationMatrix(const ezJoltCollisionMeshAssetProperties* pProp) { const float us = ezMath::Clamp(pProp->m_fUniformScaling, 0.0001f, 10000.0f); auto rightDir = ezMeshImportTransform::GetRightDir(pProp->m_ImportTransform, pProp->m_RightDir); auto upDir = ezMeshImportTransform::GetUpDir(pProp->m_ImportTransform, pProp->m_UpDir); auto flipFwd = ezMeshImportTransform::GetFlipForward(pProp->m_ImportTransform, pProp->m_bFlipForwardDir); const ezBasisAxis::Enum forwardDir = ezBasisAxis::GetOrthogonalAxis(rightDir, upDir, !flipFwd); return ezBasisAxis::CalculateTransformationMatrix(forwardDir, rightDir, upDir, us); } ezJoltCollisionMeshAssetDocument::ezJoltCollisionMeshAssetDocument(ezStringView sDocumentPath, bool bConvexMesh) : ezSimpleAssetDocument<ezJoltCollisionMeshAssetProperties>(sDocumentPath, ezAssetDocEngineConnection::Simple) { m_bIsConvexMesh = bConvexMesh; } void ezJoltCollisionMeshAssetDocument::InitializeAfterLoading(bool bFirstTimeCreation) { SUPER::InitializeAfterLoading(bFirstTimeCreation); // this logic is for backwards compatibility, to sync the convex state with existing data if (m_bIsConvexMesh) { GetPropertyObject()->GetTypeAccessor().SetValue("IsConvexMesh", m_bIsConvexMesh); } else { m_bIsConvexMesh = GetPropertyObject()->GetTypeAccessor().GetValue("IsConvexMesh").ConvertTo<bool>(); } // the GetProperties object seems distinct from the GetPropertyObject, so keep them in sync GetProperties()->m_bIsConvexMesh = m_bIsConvexMesh; } ////////////////////////////////////////////////////////////////////////// ezTransformStatus ezJoltCollisionMeshAssetDocument::InternalTransformAsset(ezStreamWriter& stream, ezStringView sOutputTag, const ezPlatformProfile* pAssetProfile, const ezAssetFileHeader& AssetHeader, ezBitflags<ezTransformFlags> transformFlags) { ezProgressRange range("Transforming Asset", 2, false); ezJoltCollisionMeshAssetProperties* pProp = GetProperties(); ezJoltMeshDesc meshDesc; if (pProp->m_bIsConvexMesh) { if (pProp->m_ConvexMeshType == ezJoltConvexCollisionMeshType::ConvexHullGroup) { meshDesc.m_Type = ezJoltMeshDesc::Type::ConvexHullGroup; } else if (pProp->m_ConvexMeshType == ezJoltConvexCollisionMeshType::ConvexDecomposition) { meshDesc.m_Type = ezJoltMeshDesc::Type::ConvexDecomposition; meshDesc.m_uiMaxConvexPieces = pProp->m_uiMaxConvexPieces; } else { EZ_ASSERT_DEV(pProp->m_ConvexMeshType == ezJoltConvexCollisionMeshType::ConvexHull || pProp->m_ConvexMeshType == ezJoltConvexCollisionMeshType::Cylinder, "Unknown convex mesh type"); meshDesc.m_Type = ezJoltMeshDesc::Type::ConvexHull; } } { range.BeginNextStep("Preparing Mesh"); if (pProp->m_ConvexMeshType == ezJoltConvexCollisionMeshType::Cylinder) { const ezMat3 mTransformation = CalculateTransformationMatrix(pProp); ezGeometry geom; ezGeometry::GeoOptions opt; opt.m_Transform = ezMat4(mTransformation, ezVec3::MakeZero()); meshDesc.m_bFlipNormals = ezGraphicsUtils::IsTriangleFlipRequired(mTransformation); geom.AddCylinderOnePiece(pProp->m_fRadius, pProp->m_fRadius2, pProp->m_fHeight * 0.5f, pProp->m_fHeight * 0.5f, ezMath::Clamp<ezUInt16>(pProp->m_uiDetail, 3, 32), opt); EZ_SUCCEED_OR_RETURN(CreateMeshFromGeom(geom, meshDesc)); } else { EZ_SUCCEED_OR_RETURN(CreateMeshFromFile(meshDesc)); } pProp = GetProperties(); // retrieve again in case they got re-created during mesh creation for (const auto& slot : pProp->m_Slots) { meshDesc.m_Surfaces.PushBack(slot.m_sResource); } // For triangle meshes: merge sub-meshes that share the same surface to reduce the material count, // since Jolt supports at most 32 different materials per triangle mesh. if (meshDesc.m_Type == ezJoltMeshDesc::Type::Triangle) { ezDynamicArray<ezUInt16> oldToNewIndex; oldToNewIndex.SetCountUninitialized(meshDesc.m_Surfaces.GetCount()); ezMap<ezString, ezUInt16> surfaceToNewIndex; ezDynamicArray<ezString> dedupSurfaces; for (ezUInt32 i = 0; i < meshDesc.m_Surfaces.GetCount(); ++i) { const ezString& sSurface = meshDesc.m_Surfaces[i]; auto it = surfaceToNewIndex.Find(sSurface); if (it.IsValid()) { oldToNewIndex[i] = it.Value(); } else { const ezUInt16 uiNewIdx = static_cast<ezUInt16>(dedupSurfaces.GetCount()); surfaceToNewIndex[sSurface] = uiNewIdx; oldToNewIndex[i] = uiNewIdx; dedupSurfaces.PushBack(sSurface); } } if (dedupSurfaces.GetCount() < meshDesc.m_Surfaces.GetCount()) { for (ezUInt16& surfaceID : meshDesc.m_TriangleSurfaceID) { if (surfaceID != 0xFFFF) { surfaceID = oldToNewIndex[surfaceID]; } } meshDesc.m_Surfaces = std::move(dedupSurfaces); } if (meshDesc.m_Surfaces.GetCount() > 32) { return ezTransformStatus(ezFmt("Collision mesh uses {} different surfaces. Jolt supports at most 32 per triangle mesh.", meshDesc.m_Surfaces.GetCount())); } } } // Please check that the code here is in sync with ezJoltMeshResourceWriter::WriteMeshResource() EZ_ASSERT_DEV(AssetHeader.GetFileVersion() == 10, "Version change"); range.BeginNextStep("Writing Result"); const bool bWriteAssetHeader = false; // already written outside of InternalTransformAsset return ezJoltMeshResourceWriter::WriteMeshResource(std::move(meshDesc), stream, bWriteAssetHeader); } ezStatus ezJoltCollisionMeshAssetDocument::CreateMeshFromFile(ezJoltMeshDesc& outMesh) { ezJoltCollisionMeshAssetProperties* pProp = GetProperties(); ezStringBuilder sAbsFilename = pProp->m_sMeshFile; if (!ezQtEditorApp::GetSingleton()->MakeDataDirectoryRelativePathAbsolute(sAbsFilename)) { return ezStatus(ezFmt("Couldn't make path absolute: '{0};", sAbsFilename)); } ezUniquePtr<ezModelImporter2::Importer> pImporter = ezModelImporter2::RequestImporterForFileType(sAbsFilename); if (pImporter == nullptr) return ezStatus("No known importer for this file type."); ezMeshResourceDescriptor meshDesc; ezModelImporter2::ImportOptions opt; opt.m_sSourceFile = sAbsFilename; opt.m_pMeshOutput = &meshDesc; opt.m_RootTransform = CalculateTransformationMatrix(pProp); // include tags { ezTempHybridArray<ezStringView, 8> tags; pProp->m_sMeshIncludeTags.Split(false, tags, ";"); for (ezStringView tag : tags) { tag.Trim(); opt.m_MeshIncludeTags.PushBack(tag); } } // exclude tags { ezTempHybridArray<ezStringView, 8> tags; pProp->m_sMeshExcludeTags.Split(false, tags, ";"); for (ezStringView tag : tags) { tag.Trim(); opt.m_MeshExcludeTags.PushBack(tag); } } if (pProp->m_bSimplifyMesh) { opt.m_uiMeshSimplification = pProp->m_uiMeshSimplification; opt.m_uiMaxSimplificationError = pProp->m_uiMaxSimplificationError; opt.m_fNormalWeight = pProp->m_fNormalWeight; opt.m_bAggressiveSimplification = pProp->m_bAggressiveSimplification; } if (pImporter->Import(opt).Failed()) return ezStatus("Model importer was unable to read this asset."); const auto& meshBuffer = meshDesc.MeshBufferDesc(); const ezUInt32 uiNumTriangles = meshBuffer.GetPrimitiveCount(); const ezUInt32 uiNumVertices = meshBuffer.GetVertexCount(); if (uiNumTriangles < 3 || uiNumVertices < 3) return ezStatus("Invalid collision mesh."); outMesh.m_TriangleSurfaceID.SetCountUninitialized(uiNumTriangles); for (ezUInt32 uiTriangle = 0; uiTriangle < uiNumTriangles; ++uiTriangle) { outMesh.m_TriangleSurfaceID[uiTriangle] = 0; // default value, will be updated below when extracting materials. } // Extract vertices { const ezVec3* pVertexData = meshBuffer.GetPositionData().GetPtr(); outMesh.m_Vertices.SetCountUninitialized(uiNumVertices); for (ezUInt32 v = 0; v < uiNumVertices; ++v) { outMesh.m_Vertices[v] = pVertexData[v]; } } // Extract indices { if (meshBuffer.Uses32BitIndices()) { ezArrayPtr<const ezUInt32> indices = ezMakeArrayPtr(reinterpret_cast<const ezUInt32*>(meshBuffer.GetIndexBufferData().GetPtr()), uiNumTriangles * 3); outMesh.m_TriangleIndices = indices; } else { outMesh.m_TriangleIndices.SetCountUninitialized(uiNumTriangles * 3); const ezUInt16* pIndices = reinterpret_cast<const ezUInt16*>(meshBuffer.GetIndexBufferData().GetPtr()); for (ezUInt32 tri = 0; tri < uiNumTriangles * 3; ++tri) { outMesh.m_TriangleIndices[tri] = pIndices[tri]; } } } const bool bUseSingleMaterial = m_bIsConvexMesh && (pProp->m_ConvexMeshType != ezJoltConvexCollisionMeshType::ConvexHullGroup); // Extract Material Information if (bUseSingleMaterial) { meshDesc.CollapseSubMeshes(); pProp->m_Slots.SetCount(1); pProp->m_Slots[0].m_sLabel = "Convex"; pProp->m_Slots[0].m_sResource = pProp->m_sConvexMeshSurface; const auto subMeshInfo = meshDesc.GetSubMeshes()[0]; for (ezUInt32 tri = 0; tri < subMeshInfo.m_uiPrimitiveCount; ++tri) { outMesh.m_TriangleSurfaceID[subMeshInfo.m_uiFirstPrimitive + tri] = 0; } } else { pProp->m_Slots.SetCount(meshDesc.GetSubMeshes().GetCount()); for (ezUInt32 matIdx = 0; matIdx < pImporter->m_OutputMaterials.GetCount(); ++matIdx) { const ezInt32 subMeshIdx = pImporter->m_OutputMaterials[matIdx].m_iReferencedByMesh; if (subMeshIdx < 0) continue; pProp->m_Slots[subMeshIdx].m_sLabel = pImporter->m_OutputMaterials[matIdx].m_sName; const auto subMeshInfo = meshDesc.GetSubMeshes()[subMeshIdx]; if (pProp->m_Slots[subMeshIdx].m_bExclude) { // update the triangle material information for (ezUInt32 tri = 0; tri < subMeshInfo.m_uiPrimitiveCount; ++tri) { outMesh.m_TriangleSurfaceID[subMeshInfo.m_uiFirstPrimitive + tri] = 0xFFFF; } } else { // update the triangle material information for (ezUInt32 tri = 0; tri < subMeshInfo.m_uiPrimitiveCount; ++tri) { outMesh.m_TriangleSurfaceID[subMeshInfo.m_uiFirstPrimitive + tri] = subMeshIdx; } } } ApplyNativePropertyChangesToObjectManager(); } return ezStatus(EZ_SUCCESS); } ezStatus ezJoltCollisionMeshAssetDocument::CreateMeshFromGeom(ezGeometry& geom, ezJoltMeshDesc& outMesh) { ezJoltCollisionMeshAssetProperties* pProp = GetProperties(); // Material setup. { // Ensure there is just one slot. if (pProp->m_Slots.GetCount() != 1) { GetObjectAccessor()->StartTransaction("Update Mesh Material Info"); pProp->m_Slots.SetCount(1); pProp->m_Slots[0].m_sLabel = "Default"; ApplyNativePropertyChangesToObjectManager(); GetObjectAccessor()->FinishTransaction(); // Need to reacquire pProp pointer since it might be reallocated. pProp = GetProperties(); EZ_IGNORE_UNUSED(pProp); } } geom.TriangulatePolygons(); // copy vertex positions { outMesh.m_Vertices.SetCountUninitialized(geom.GetVertices().GetCount()); for (ezUInt32 v = 0; v < geom.GetVertices().GetCount(); ++v) { outMesh.m_Vertices[v] = geom.GetVertices()[v].m_vPosition; } } // Copy Polygon Data { outMesh.m_TriangleSurfaceID.SetCountUninitialized(geom.GetPolygons().GetCount()); outMesh.m_TriangleIndices.Reserve(geom.GetPolygons().GetCount() * 3); for (ezUInt32 p = 0; p < geom.GetPolygons().GetCount(); ++p) { const auto& poly = geom.GetPolygons()[p]; EZ_ASSERT_DEBUG(poly.m_Vertices.GetCount() == 3, "Expected triangulated polygons."); outMesh.m_TriangleSurfaceID[p] = 0; for (ezUInt32 posIdx : poly.m_Vertices) { outMesh.m_TriangleIndices.PushBack(posIdx); } } } return ezStatus(EZ_SUCCESS); } ezTransformStatus ezJoltCollisionMeshAssetDocument::InternalCreateThumbnail(const ThumbnailInfo& ThumbnailInfo) { ezStatus status = ezAssetDocument::RemoteCreateThumbnail(ThumbnailInfo); return status; } void ezJoltCollisionMeshAssetDocument::UpdateAssetDocumentInfo(ezAssetDocumentInfo* pInfo) const { SUPER::UpdateAssetDocumentInfo(pInfo); if (GetProperties()->m_ConvexMeshType != ezJoltConvexCollisionMeshType::ConvexHull) { // remove the mesh file dependency, if it is not actually used const auto& sMeshFile = GetProperties()->m_sMeshFile; pInfo->m_TransformDependencies.Remove(sMeshFile); } else { // For glTF files, add any referenced external buffer files as dependencies ezMeshImportUtils::AddGltfBufferDependencies(GetProperties()->m_sMeshFile, pInfo->m_TransformDependencies); } } ////////////////////////////////////////////////////////////////////////// EZ_BEGIN_DYNAMIC_REFLECTED_TYPE(ezJoltCollisionMeshAssetDocumentGenerator, 1, ezRTTIDefaultAllocator<ezJoltCollisionMeshAssetDocumentGenerator>) EZ_END_DYNAMIC_REFLECTED_TYPE; ezJoltCollisionMeshAssetDocumentGenerator::ezJoltCollisionMeshAssetDocumentGenerator() { AddSupportedFileType("obj"); AddSupportedFileType("fbx"); AddSupportedFileType("gltf"); AddSupportedFileType("glb"); } ezJoltCollisionMeshAssetDocumentGenerator::~ezJoltCollisionMeshAssetDocumentGenerator() = default; void ezJoltCollisionMeshAssetDocumentGenerator::GetImportModes(ezStringView sAbsInputFile, ezDynamicArray<ezAssetDocumentGenerator::ImportMode>& out_modes) const { { ezAssetDocumentGenerator::ImportMode& info = out_modes.ExpandAndGetRef(); info.m_Priority = ezAssetDocGeneratorPriority::LowPriority; info.m_sName = "Jolt_Colmesh_Triangle"; info.m_sIcon = ":/AssetIcons/Jolt_Collision_Mesh.svg"; } } ezStatus ezJoltCollisionMeshAssetDocumentGenerator::Generate(ezStringView sInputFileAbs, ezStringView sMode, ezDynamicArray<ezDocument*>& out_generatedDocuments) { ezStringBuilder sOutFile = sInputFileAbs; sOutFile.ChangeFileExtension(GetDocumentExtension()); if (ezOSFile::ExistsFile(sOutFile)) { ezLog::Info("Skipping collision mesh import, file has been imported before: '{}'", sOutFile); return ezStatus(EZ_SUCCESS); } auto pApp = ezQtEditorApp::GetSingleton(); ezStringBuilder sInputFileRel = sInputFileAbs; pApp->MakePathDataDirectoryRelative(sInputFileRel); ezDocument* pDoc = pApp->CreateDocument(sOutFile, ezDocumentFlags::None); if (pDoc == nullptr) return ezStatus("Could not create target document"); out_generatedDocuments.PushBack(pDoc); ezJoltCollisionMeshAssetDocument* pAssetDoc = ezDynamicCast<ezJoltCollisionMeshAssetDocument*>(pDoc); if (pAssetDoc == nullptr) return ezStatus("Target document is not a valid ezJoltCollisionMeshAssetDocument"); auto& accessor = pAssetDoc->GetPropertyObject()->GetTypeAccessor(); accessor.SetValue("MeshFile", sInputFileRel.GetView()); ezLog::Success("Imported collision mesh: '{}'", sOutFile); return ezStatus(EZ_SUCCESS); } ////////////////////////////////////////////////////////////////////////// EZ_BEGIN_DYNAMIC_REFLECTED_TYPE(ezJoltConvexCollisionMeshAssetDocumentGenerator, 1, ezRTTIDefaultAllocator<ezJoltConvexCollisionMeshAssetDocumentGenerator>) EZ_END_DYNAMIC_REFLECTED_TYPE; ezJoltConvexCollisionMeshAssetDocumentGenerator::ezJoltConvexCollisionMeshAssetDocumentGenerator() { AddSupportedFileType("obj"); AddSupportedFileType("fbx"); AddSupportedFileType("gltf"); AddSupportedFileType("glb"); } ezJoltConvexCollisionMeshAssetDocumentGenerator::~ezJoltConvexCollisionMeshAssetDocumentGenerator() = default; void ezJoltConvexCollisionMeshAssetDocumentGenerator::GetImportModes(ezStringView sAbsInputFile, ezDynamicArray<ezAssetDocumentGenerator::ImportMode>& out_modes) const { { ezAssetDocumentGenerator::ImportMode& info = out_modes.ExpandAndGetRef(); info.m_Priority = ezAssetDocGeneratorPriority::LowPriority; info.m_sName = "Jolt_Colmesh_Convex"; info.m_sIcon = ":/AssetIcons/Jolt_Collision_Mesh_Convex.svg"; } } ezStatus ezJoltConvexCollisionMeshAssetDocumentGenerator::Generate(ezStringView sInputFileAbs, ezStringView sMode, ezDynamicArray<ezDocument*>& out_generatedDocuments) { ezStringBuilder sOutFile = sInputFileAbs; sOutFile.ChangeFileExtension(GetDocumentExtension()); if (ezOSFile::ExistsFile(sOutFile)) { ezLog::Info("Skipping convex collision mesh import, file has been imported before: '{}'", sOutFile); return ezStatus(EZ_SUCCESS); } auto pApp = ezQtEditorApp::GetSingleton(); ezStringBuilder sInputFileRel = sInputFileAbs; pApp->MakePathDataDirectoryRelative(sInputFileRel); ezDocument* pDoc = pApp->CreateDocument(sOutFile, ezDocumentFlags::None); if (pDoc == nullptr) return ezStatus("Could not create target document"); out_generatedDocuments.PushBack(pDoc); ezJoltCollisionMeshAssetDocument* pAssetDoc = ezDynamicCast<ezJoltCollisionMeshAssetDocument*>(pDoc); if (pAssetDoc == nullptr) return ezStatus("Target document is not a valid ezJoltCollisionMeshAssetDocument"); auto& accessor = pAssetDoc->GetPropertyObject()->GetTypeAccessor(); accessor.SetValue("MeshFile", sInputFileRel.GetView()); ezLog::Success("Imported convex collision mesh: '{}'", sOutFile); return ezStatus(EZ_SUCCESS); }