/** * Extract the material names from the Apex Render Mesh contained within an Apex Destructible Asset. * @param ImportData - SkeletalMesh import data into which we are extracting information * @param ApexDestructibleAsset - the Apex Destructible Asset */ static void ImportMaterialsForSkelMesh(FSkeletalMeshImportData &ImportData, const NxDestructibleAsset& ApexDestructibleAsset) { physx::PxU32 SubmeshCount = 0; // Get the submesh count from the Destructible Asset's Render Mesh const physx::NxRenderMeshAsset* ApexRenderMesh = ApexDestructibleAsset.getRenderMeshAsset(); if (ApexRenderMesh != NULL) { SubmeshCount = ApexRenderMesh->getSubmeshCount(); } if( SubmeshCount == 0 ) { // No material info, create a default material slot ++SubmeshCount; UE_LOG(LogApexDestructibleAssetImport, Warning,TEXT("No material associated with skeletal mesh - using default")); } else { UE_LOG(LogApexDestructibleAssetImport, Warning,TEXT("Using default materials for material slot")); } // Create material slots UMaterial* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface); if (DefaultMaterial) { for (uint32 MatIndex = 0; MatIndex < SubmeshCount; MatIndex++) { ImportData.Materials.Add( VMaterial() ); ImportData.Materials.Last().Material = DefaultMaterial; ImportData.Materials.Last().MaterialImportName = DefaultMaterial->GetName(); } } }
void FDestructibleMeshEditorViewportClient::Draw( const FSceneView* View,FPrimitiveDrawInterface* PDI ) { FEditorViewportClient::Draw(View, PDI); #if WITH_APEX const bool DrawChunkMarker = true; UDestructibleComponent* Comp = PreviewDestructibleComp.Get(); if (Comp) { if (Comp->DestructibleMesh != NULL && Comp->DestructibleMesh->FractureSettings != NULL) { if (Comp->DestructibleMesh->ApexDestructibleAsset != NULL) { NxDestructibleAsset* Asset = Comp->DestructibleMesh->ApexDestructibleAsset; const NxRenderMeshAsset* RenderMesh = Asset->getRenderMeshAsset(); for (uint32 i=0; i < Asset->getChunkCount(); ++i) { int32 PartIdx = Asset->getPartIndex(i); int32 BoneIdx = i+1; if ( SelectedChunkIndices.Contains(i) ) { PxBounds3 PBounds = RenderMesh->getBounds(PartIdx); FVector Center = P2UVector(PBounds.getCenter()) + Comp->GetBoneLocation(Comp->GetBoneName(BoneIdx)); FVector Extent = P2UVector(PBounds.getExtents()); FBox Bounds(Center - Extent, Center + Extent); DrawWireBox(PDI, Bounds, FColor::Blue, SDPG_World); } } } } } #endif // WITH_APEX }
/** * Fill an FSkeletalMeshImportData with data from an APEX Destructible Asset. * * @param ImportData - SkeletalMesh import data into which we are extracting information * @param ApexDestructibleAsset - the Apex Destructible Asset * @param bHaveAllNormals - if the function is successful, this value is true iff every submesh has a normal channel * @param bHaveAllTangents - if the function is successful, this value is true iff every submesh has a tangent channel * * @return Boolean true iff the operation is successful */ static bool FillSkelMeshImporterFromApexDestructibleAsset(FSkeletalMeshImportData& ImportData, const NxDestructibleAsset& ApexDestructibleAsset, bool& bHaveAllNormals, bool& bHaveAllTangents) { // The APEX Destructible Asset contains an APEX Render Mesh Asset, get a pointer to this const physx::NxRenderMeshAsset* ApexRenderMesh = ApexDestructibleAsset.getRenderMeshAsset(); if (ApexRenderMesh == NULL) { return false; } if (ApexDestructibleAsset.getChunkCount() != ApexRenderMesh->getPartCount()) { UE_LOG(LogApexDestructibleAssetImport, Warning,TEXT("Chunk count does not match part count. APEX Destructible Asset with chunk instancing not yet supported.")); return false; } // Apex Render Mesh uses triangle lists only, currently. No need to triangulate. // Assume there are no vertex colors ImportData.bHasVertexColors = false; // Different submeshes can have different UV counts. Get the max. uint32 UniqueUVCount = 0; // Count vertices and triangles uint32 VertexCount = 0; uint32 TriangleCount = 0; for (uint32 SubmeshIndex = 0; SubmeshIndex < ApexRenderMesh->getSubmeshCount(); ++SubmeshIndex) { const NxRenderSubmesh& Submesh = ApexRenderMesh->getSubmesh(SubmeshIndex); const NxVertexBuffer& VB = Submesh.getVertexBuffer(); const NxVertexFormat& VBFormat = VB.getFormat(); // Count UV channels in this VB uint32 UVNum; for (UVNum = 0; UVNum < NxVertexFormat::MAX_UV_COUNT; ++UVNum) { const NxVertexFormat::BufferID BufferID = VBFormat.getSemanticID((NxRenderVertexSemantic::Enum)(NxRenderVertexSemantic::TEXCOORD0 + UVNum)); if (VBFormat.getBufferIndexFromID(BufferID) < 0) { break; } } UniqueUVCount = FMath::Max<uint32>( UniqueUVCount, UVNum ); // See if this VB has a color channel const NxVertexFormat::BufferID BufferID = VBFormat.getSemanticID(NxRenderVertexSemantic::COLOR); if (VBFormat.getBufferIndexFromID(BufferID) >= 0) { ImportData.bHasVertexColors = true; } // Count vertices VertexCount += VB.getVertexCount(); // Count triangles uint32 IndexCount = 0; for (uint32 PartIndex = 0; PartIndex < ApexRenderMesh->getPartCount(); ++PartIndex) { IndexCount += Submesh.getIndexCount(PartIndex); } check(IndexCount%3 == 0); TriangleCount += IndexCount/3; } // One UV set is required but only import up to MAX_TEXCOORDS number of uv layers ImportData.NumTexCoords = FMath::Clamp<uint32>(UniqueUVCount, 1, MAX_TEXCOORDS); // Expand buffers in ImportData: ImportData.Points.AddUninitialized(VertexCount); ImportData.Influences.AddUninitialized(VertexCount); ImportData.Wedges.AddUninitialized(3*TriangleCount); uint32 WedgeIndex = 0; ImportData.Faces.AddUninitialized(TriangleCount); uint32 TriangleIndex = 0; uint32 VertexIndexBase = 0; // True until proven otherwise bHaveAllNormals = true; bHaveAllTangents = true; // APEX render meshes are organized by submesh (render elements) // Looping through submeshes first, can be done either way for (uint32 SubmeshIndex = 0; SubmeshIndex < ApexRenderMesh->getSubmeshCount(); ++SubmeshIndex) { // Submesh data const NxRenderSubmesh& Submesh = ApexRenderMesh->getSubmesh(SubmeshIndex); const NxVertexBuffer& VB = Submesh.getVertexBuffer(); const NxVertexFormat& VBFormat = VB.getFormat(); const physx::PxU32 SubmeshVertexCount = VB.getVertexCount(); // Get VB data semantic indices: // Positions const PxI32 PositionBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID(NxRenderVertexSemantic::POSITION)); if (!VB.getBufferData(&ImportData.Points[VertexIndexBase], physx::NxRenderDataFormat::FLOAT3, sizeof(FVector), PositionBufferIndex, 0, SubmeshVertexCount)) { return false; // Need a position buffer! } #if INVERT_Y_AND_V for (uint32 VertexNum = 0; VertexNum < SubmeshVertexCount; ++VertexNum) { ImportData.Points[VertexIndexBase + VertexNum].Y *= -1.0f; } #endif // Normals const PxI32 NormalBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID(NxRenderVertexSemantic::NORMAL)); TArray<FVector> Normals; Normals.AddUninitialized(SubmeshVertexCount); const bool bHaveNormals = VB.getBufferData(Normals.GetData(), physx::NxRenderDataFormat::FLOAT3, sizeof(FVector), NormalBufferIndex, 0, SubmeshVertexCount); if (!bHaveNormals) { FMemory::Memset(Normals.GetData(), 0, SubmeshVertexCount*sizeof(FVector)); // Fill with zeros } // Tangents const PxI32 TangentBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID(NxRenderVertexSemantic::TANGENT)); TArray<FVector> Tangents; Tangents.AddUninitialized(SubmeshVertexCount); const bool bHaveTangents = VB.getBufferData(Tangents.GetData(), physx::NxRenderDataFormat::FLOAT3, sizeof(FVector), TangentBufferIndex, 0, SubmeshVertexCount); if (!bHaveTangents) { FMemory::Memset(Tangents.GetData(), 0, SubmeshVertexCount*sizeof(FVector)); // Fill with zeros } // Update bHaveAllNormals and bHaveAllTangents bHaveAllNormals = bHaveAllNormals && bHaveNormals; bHaveAllTangents = bHaveAllTangents && bHaveTangents; // Binormomals const PxI32 BinormalBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID(NxRenderVertexSemantic::BINORMAL)); TArray<FVector> Binormals; Binormals.AddUninitialized(SubmeshVertexCount); bool bHaveBinormals = VB.getBufferData(Binormals.GetData(), physx::NxRenderDataFormat::FLOAT3, sizeof(FVector), BinormalBufferIndex, 0, SubmeshVertexCount); if (!bHaveBinormals) { bHaveBinormals = bHaveNormals && bHaveTangents; for (uint32 i = 0; i < SubmeshVertexCount; ++i) { Binormals[i] = Normals[i]^Tangents[i]; // Build from normals and tangents. If one of these doesn't exist we'll get (0,0,0)'s } } // Colors const PxI32 ColorBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID(NxRenderVertexSemantic::COLOR)); TArray<FColor> Colors; Colors.AddUninitialized(SubmeshVertexCount); const bool bHaveColors = VB.getBufferData(Colors.GetData(), physx::NxRenderDataFormat::B8G8R8A8, sizeof(FColor), ColorBufferIndex, 0, SubmeshVertexCount); if (!bHaveColors) { FMemory::Memset(Colors.GetData(), 0xFF, SubmeshVertexCount*sizeof(FColor)); // Fill with 0xFF } // UVs TArray<FVector2D> UVs[NxVertexFormat::MAX_UV_COUNT]; for (uint32 UVNum = 0; UVNum < ImportData.NumTexCoords; ++UVNum) { const PxI32 UVBufferIndex = VBFormat.getBufferIndexFromID(VBFormat.getSemanticID((NxRenderVertexSemantic::Enum)(NxRenderVertexSemantic::TEXCOORD0 + UVNum))); UVs[UVNum].AddUninitialized(SubmeshVertexCount); if (!VB.getBufferData(&UVs[UVNum][0].X, physx::NxRenderDataFormat::FLOAT2, sizeof(FVector2D), UVBufferIndex, 0, SubmeshVertexCount)) { FMemory::Memset(&UVs[UVNum][0].X, 0, SubmeshVertexCount*sizeof(FVector2D)); // Fill with zeros } } // Bone indices will not be imported - they're implicitly the PartIndex // Each submesh is partitioned into parts. Currently we're assuming a 1-1 correspondence between chunks and parts, // which means that instanced chunks are not supported. However, we will not assume that the chunk and part ordering is the same. // Therefore, instead of looping through parts, we loop through chunks here, and get the part index. for (uint32 ChunkIndex = 0; ChunkIndex < ApexDestructibleAsset.getChunkCount(); ++ChunkIndex) { const physx::PxU32 PartIndex = ApexDestructibleAsset.getPartIndex(ChunkIndex); const physx::PxU32* PartIndexBuffer = Submesh.getIndexBuffer(PartIndex); const physx::PxU32* PartIndexBufferStop = PartIndexBuffer + Submesh.getIndexCount(PartIndex); while (PartIndexBuffer < PartIndexBufferStop) { physx::PxU32 SubmeshVertexIndex[3]; #if !INVERT_Y_AND_V SubmeshVertexIndex[2] = *PartIndexBuffer++; SubmeshVertexIndex[1] = *PartIndexBuffer++; SubmeshVertexIndex[0] = *PartIndexBuffer++; #else SubmeshVertexIndex[0] = *PartIndexBuffer++; SubmeshVertexIndex[1] = *PartIndexBuffer++; SubmeshVertexIndex[2] = *PartIndexBuffer++; #endif // Fill triangle VTriangle& Triangle = ImportData.Faces[TriangleIndex++]; // set the face smoothing by default. It could be any number, but not zero Triangle.SmoothingGroups = 255; // Material index Triangle.MatIndex = SubmeshIndex; Triangle.AuxMatIndex = 0; // Per-vertex for (uint32 V = 0; V < 3; ++V) { // Tangent basis Triangle.TangentX[V] = Tangents[SubmeshVertexIndex[V]]; Triangle.TangentY[V] = Binormals[SubmeshVertexIndex[V]]; Triangle.TangentZ[V] = Normals[SubmeshVertexIndex[V]]; #if INVERT_Y_AND_V Triangle.TangentX[V].Y *= -1.0f; Triangle.TangentY[V].Y *= -1.0f; Triangle.TangentZ[V].Y *= -1.0f; #endif // Wedges Triangle.WedgeIndex[V] = WedgeIndex; VVertex& Wedge = ImportData.Wedges[WedgeIndex++]; Wedge.VertexIndex = VertexIndexBase + SubmeshVertexIndex[V]; Wedge.MatIndex = Triangle.MatIndex; Wedge.Color = Colors[SubmeshVertexIndex[V]]; Wedge.Reserved = 0; for (uint32 UVNum = 0; UVNum < ImportData.NumTexCoords; ++UVNum) { const FVector2D& UV = UVs[UVNum][SubmeshVertexIndex[V]]; #if !INVERT_Y_AND_V Wedge.UVs[UVNum] = UV; #else Wedge.UVs[UVNum] = FVector2D(UV.X, 1.0f-UV.Y); #endif } } } // Bone influences const physx::PxU32 PartVertexStart = Submesh.getFirstVertexIndex(PartIndex); const physx::PxU32 PartVertexStop = PartVertexStart + Submesh.getVertexCount(PartIndex); for (uint32 PartVertexIndex = PartVertexStart; PartVertexIndex < PartVertexStop; ++PartVertexIndex) { const physx::PxU32 VertexIndex = VertexIndexBase + PartVertexIndex; // Note, by using ChunkIndex instead of PartInedx we are effectively setting PartIndex = ChunkIndex, which is OK since we won't be supporting instancing with the SkeletalMesh. ImportData.Influences[VertexIndex].BoneIndex = ChunkIndex + 1; // Adding 1, since the 0 bone will have no geometry from the Apex Destructible Asset. ImportData.Influences[VertexIndex].Weight = 1.0; ImportData.Influences[VertexIndex].VertexIndex = VertexIndex; } } VertexIndexBase += SubmeshVertexCount; } // Create mapping from import to raw- @TODO trivial at the moment, do we need this info for destructibles? ImportData.PointToRawMap.AddUninitialized(ImportData.Points.Num()); for(int32 PointIdx=0; PointIdx<ImportData.PointToRawMap.Num(); PointIdx++) { ImportData.PointToRawMap[PointIdx] = PointIdx; } return true; }
void FDestructibleMeshEditorViewportClient::ProcessClick( class FSceneView& View, class HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY ) { #if WITH_APEX bool bKeepSelection = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); bool bSelectionChanged = false; if (Key == EKeys::LeftMouseButton && Event == EInputEvent::IE_Released) { UDestructibleComponent* Comp = PreviewDestructibleComp.Get(); NxDestructibleAsset* Asset = Comp->DestructibleMesh->ApexDestructibleAsset; const NxRenderMeshAsset* RenderMesh = Asset->getRenderMeshAsset(); FVector2D ScreenPos(HitX, HitY); FVector ClickOrigin, ViewDir; View.DeprojectFVector2D(ScreenPos, ClickOrigin, ViewDir); float NearestHitDistance = FLT_MAX; int32 ClickedChunk = -1; for (uint32 i=0; i < Asset->getChunkCount(); ++i) { int32 PartIdx = Asset->getPartIndex(i); int32 BoneIdx = i+1; if (!Comp->IsBoneHidden(BoneIdx)) { PxBounds3 PBounds = RenderMesh->getBounds(PartIdx); FVector Center = P2UVector(PBounds.getCenter()) + Comp->GetBoneLocation(Comp->GetBoneName(BoneIdx)); FVector Extent = P2UVector(PBounds.getExtents()); FBox Bounds(Center - Extent, Center + Extent); FVector HitLoc, HitNorm; float HitTime; if (FMath::LineExtentBoxIntersection(Bounds, ClickOrigin, ClickOrigin + ViewDir * 1000.0f, FVector(0,0,0), HitLoc, HitNorm, HitTime)) { float dist = (HitLoc - ClickOrigin).SizeSquared(); if (dist < NearestHitDistance) { NearestHitDistance = dist; ClickedChunk = i; } } } } if (ClickedChunk >= 0) { int32 Idx = SelectedChunkIndices.Find(ClickedChunk); if (Idx < 0) { if (!bKeepSelection) { SelectedChunkIndices.Empty(); } SelectedChunkIndices.Add(ClickedChunk); bSelectionChanged = true; } else { SelectedChunkIndices.RemoveAt(Idx); bSelectionChanged = true; } } else if (!bKeepSelection) { SelectedChunkIndices.Empty(); bSelectionChanged = true; } } if (bSelectionChanged) { UpdateChunkSelection(SelectedChunkIndices); } #endif // WITH_APEX }