/** * 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(); } } }
/** * Create the bones needed to hold the transforms for the destructible chunks associated with an Apex Destructible Asset. * @param ImportData - SkeletalMesh import data into which we are extracting information * @param ApexDestructibleAsset - the Apex Destructible Asset */ static void CreateBones(FSkeletalMeshImportData &ImportData, const NxDestructibleAsset& ApexDestructibleAsset) { // Just need to create ApexDestructibleAsset.getChunkCount() bones, all with identity transform poses const uint32 ChunkCount = ApexDestructibleAsset.getChunkCount(); if( ChunkCount == 0 ) { UE_LOG(LogApexDestructibleAssetImport, Warning,TEXT("%s has no chunks"), ANSI_TO_TCHAR(ApexDestructibleAsset.getName()) ); return; } const uint32 BoneCount = ChunkCount + 1; // Adding one more bone for the root bone, required by the skeletal mesh // Format for bone names uint32 Q = ChunkCount-1; int32 MaxNumberWidth = 1; while ((Q /= 10) != 0) { ++MaxNumberWidth; } // Turn parts into bones for (uint32 BoneIndex=0; BoneIndex<BoneCount; ++BoneIndex) { ImportData.RefBonesBinary.Add( VBone() ); // Set bone VBone& Bone = ImportData.RefBonesBinary[BoneIndex]; if (BoneIndex == 0) { // Bone 0 is the required root bone Bone.Name = TEXT("Root"); Bone.NumChildren = ChunkCount; Bone.ParentIndex = INDEX_NONE; } else { // The rest are the parts Bone.Name = FString::Printf( TEXT("Part%0*d"), MaxNumberWidth, BoneIndex-1); Bone.NumChildren = 0; // Creates a simple "flat" hierarchy Bone.ParentIndex = 0; } // Set transform to identity VJointPos& JointMatrix = Bone.BonePos; JointMatrix.Orientation = FQuat(0.0f,0.0f,0.0f,1.0f); JointMatrix.Position = FVector(0.0f,0.0f,0.0f); JointMatrix.Length = 1.0f; JointMatrix.XSize = 100.0f; JointMatrix.YSize = 100.0f; JointMatrix.ZSize = 100.0f; } }
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 }
bool SetApexDestructibleAsset(UDestructibleMesh& DestructibleMesh, NxDestructibleAsset& ApexDestructibleAsset, FSkeletalMeshImportData* OutData, EDestructibleImportOptions::Type Options) { DestructibleMesh.PreEditChange(NULL); ExistingDestMeshData * ExistDestMeshDataPtr = SaveExistingDestMeshData(&DestructibleMesh); // The asset is going away, which will destroy any actors created from it. We must destroy the physics state of any destructible mesh components before we release the asset. for(TObjectIterator<UDestructibleComponent> It; It; ++It) { UDestructibleComponent* DestructibleComponent = *It; if(DestructibleComponent->SkeletalMesh == &DestructibleMesh && DestructibleComponent->IsPhysicsStateCreated()) { DestructibleComponent->DestroyPhysicsState(); } } // Release old NxDestructibleAsset if it exists if (DestructibleMesh.ApexDestructibleAsset != NULL && DestructibleMesh.ApexDestructibleAsset != &ApexDestructibleAsset) { GPhysCommandHandler->DeferredRelease(DestructibleMesh.ApexDestructibleAsset); } // BRGTODO - need to remove the render data from the ApexDestructibleAsset, no longer need it // Removing const cast ... we'll have to make it non-const anyway when we modify it DestructibleMesh.ApexDestructibleAsset = &ApexDestructibleAsset; if ( !(Options&EDestructibleImportOptions::PreserveSettings) ) { // Resize the depth parameters array to the appropriate size DestructibleMesh.DefaultDestructibleParameters.DepthParameters.Init(FDestructibleDepthParameters(), ApexDestructibleAsset.getDepthCount()); // Resize the fracture effects array to the appropriate size DestructibleMesh.FractureEffects.AddZeroed(ApexDestructibleAsset.getDepthCount()); // Load the UnrealEd-editable parameters from the destructible asset DestructibleMesh.LoadDefaultDestructibleParametersFromApexAsset(); } // Create body setup for the destructible mesh DestructibleMesh.CreateBodySetup(); #if 0 // BRGTODO // warning for missing smoothing group info CheckSmoothingInfo(FbxMesh); #endif FSkeletalMeshImportData TempData; // Fill with data from buffer FSkeletalMeshImportData* SkelMeshImportDataPtr = &TempData; if( OutData ) { SkelMeshImportDataPtr = OutData; } // Get all material names here ImportMaterialsForSkelMesh(*SkelMeshImportDataPtr, ApexDestructibleAsset); // Import animation hierarchy, although this is trivial for an Apex Destructible Asset CreateBones(*SkelMeshImportDataPtr, ApexDestructibleAsset); // Import graphics data bool bHaveNormals, bHaveTangents; if (!FillSkelMeshImporterFromApexDestructibleAsset(*SkelMeshImportDataPtr, ApexDestructibleAsset, bHaveNormals, bHaveTangents)) { return false; } #if 0 // BRGTODO - what is this? if( SkelMeshImportDataPtr->Materials.Num() == FbxMatList.Num() ) { // reorder material according to "SKinXX" in material name SetMaterialSkinXXOrder(*SkelMeshImportDataPtr, FbxMatList ); } #endif #if 0 // BRGTODO - what is this? if( ImportOptions->bSplitNonMatchingTriangles ) { DoUnSmoothVerts(*SkelMeshImportDataPtr); } #endif // process materials from import data ProcessImportMeshMaterials( DestructibleMesh.Materials,*SkelMeshImportDataPtr ); // process reference skeleton from import data int32 SkeletalDepth=0; if(!ProcessImportMeshSkeleton(DestructibleMesh.RefSkeleton, SkeletalDepth, *SkelMeshImportDataPtr)) { return false; } UE_LOG(LogApexDestructibleAssetImport, Warning, TEXT("Bones digested - %i Depth of hierarchy - %i"), DestructibleMesh.RefSkeleton.GetNum(), SkeletalDepth); // process bone influences from import data ProcessImportMeshInfluences(*SkelMeshImportDataPtr); FSkeletalMeshResource& DestructibleMeshResource = *DestructibleMesh.GetImportedResource(); check(DestructibleMeshResource.LODModels.Num() == 0); DestructibleMeshResource.LODModels.Empty(); new(DestructibleMeshResource.LODModels)FStaticLODModel(); DestructibleMesh.LODInfo.Empty(); DestructibleMesh.LODInfo.AddZeroed(); DestructibleMesh.LODInfo[0].LODHysteresis = 0.02f; // Create initial bounding box based on expanded version of reference pose for meshes without physics assets. Can be overridden by artist. FBox BoundingBox(SkelMeshImportDataPtr->Points.GetData(), SkelMeshImportDataPtr->Points.Num()); DestructibleMesh.Bounds= FBoxSphereBounds(BoundingBox); // Store whether or not this mesh has vertex colors DestructibleMesh.bHasVertexColors = SkelMeshImportDataPtr->bHasVertexColors; FStaticLODModel& LODModel = DestructibleMeshResource.LODModels[0]; // Pass the number of texture coordinate sets to the LODModel. Ensure there is at least one UV coord LODModel.NumTexCoords = FMath::Max<uint32>(1,SkelMeshImportDataPtr->NumTexCoords); // if( bCreateRenderData ) // We always create render data { // copy vertex data needed to generate skinning streams for LOD TArray<FVector> LODPoints; TArray<FMeshWedge> LODWedges; TArray<FMeshFace> LODFaces; TArray<FVertInfluence> LODInfluences; TArray<int32> LODPointToRawMap; SkelMeshImportDataPtr->CopyLODImportData(LODPoints,LODWedges,LODFaces,LODInfluences,LODPointToRawMap); IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities"); // Create actual rendering data. if (!MeshUtilities.BuildSkeletalMesh(DestructibleMeshResource.LODModels[0], DestructibleMesh.RefSkeleton, LODInfluences,LODWedges,LODFaces,LODPoints,LODPointToRawMap,false,!bHaveNormals,!bHaveTangents)) { DestructibleMesh.MarkPendingKill(); return false; } // Presize the per-section shadow casting array with the number of sections in the imported LOD. const int32 NumSections = LODModel.Sections.Num(); for ( int32 SectionIndex = 0 ; SectionIndex < NumSections ; ++SectionIndex ) { DestructibleMesh.LODInfo[0].TriangleSortSettings.AddZeroed(); } if (ExistDestMeshDataPtr) { RestoreExistingDestMeshData(ExistDestMeshDataPtr, &DestructibleMesh); delete ExistDestMeshDataPtr; ExistDestMeshDataPtr = NULL; } DestructibleMesh.CalculateInvRefMatrices(); DestructibleMesh.PostEditChange(); DestructibleMesh.MarkPackageDirty(); #if 0 // BRGTODO : Check, we don't need this, do we? // We have to go and fix any AnimSetMeshLinkup objects that refer to this skeletal mesh, as the reference skeleton has changed. for(TObjectIterator<UAnimSet> It;It;++It) { UAnimSet* AnimSet = *It; // Get DestructibleMesh path name FName SkelMeshName = FName( *DestructibleMesh.GetPathName() ); // See if we have already cached this Skeletal Mesh. const int32* IndexPtr = AnimSet->SkelMesh2LinkupCache.Find( SkelMeshName ); if( IndexPtr ) { AnimSet->LinkupCache( *IndexPtr ).BuildLinkup( &DestructibleMesh, AnimSet ); } } #endif // Now iterate over all skeletal mesh components re-initialising them. for(TObjectIterator<UDestructibleComponent> It; It; ++It) { UDestructibleComponent* DestructibleComponent = *It; if(DestructibleComponent->SkeletalMesh == &DestructibleMesh) { FComponentReregisterContext ReregisterContext(DestructibleComponent); } } } #if INVERT_Y_AND_V // Apply transformation for Y inversion const physx::PxMat44 MirrorY = physx::PxMat44(physx::PxVec4(1.0f, -1.0f, 1.0f, 1.0f)); #if !USE_TEMPORARY_TRANSFORMATION_FUNCTION ApexDestructibleAsset.applyTransformation(MirrorY, 1.0f); #else ApplyTransformationToApexDestructibleAsset( ApexDestructibleAsset, MirrorY ); #endif #endif return true; }
/** * 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 }