You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1544 lines
52 KiB
C++
1544 lines
52 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ClothingAssetFactory.h"
|
|
|
|
#include "Factories.h"
|
|
#include "PhysXPublic.h"
|
|
#include "PhysicsPublic.h"
|
|
#include "ClothingAsset.h"
|
|
#if WITH_APEX_CLOTHING
|
|
#include "ClothingAssetAuthoring.h"
|
|
#include "ApexClothingUtils.h"
|
|
#include "ClothConfigNv.h"
|
|
#endif // WITH_APEX_CLOTHING
|
|
#include "ContentBrowserModule.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Engine/SkeletalMesh.h"
|
|
#include "Misc/MessageDialog.h"
|
|
#include "ObjectTools.h"
|
|
#include "PhysicsEngine/PhysicsAsset.h"
|
|
#include "PhysicsEngine/SphereElem.h"
|
|
#include "ComponentReregisterContext.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Utils/ClothingMeshUtils.h"
|
|
#include "Rendering/SkeletalMeshModel.h"
|
|
#include "ClothPhysicalMeshData.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "ClothingAssetFactory"
|
|
DEFINE_LOG_CATEGORY(LogClothingAssetFactory)
|
|
|
|
using namespace nvidia::apex;
|
|
|
|
namespace ClothingFactoryConstants
|
|
{
|
|
// For verifying the file
|
|
static const char ClothingAssetClass[] = "ClothingAssetParameters";
|
|
|
|
// Import transformation params
|
|
static const char ParamName_BoneActors[] = "boneActors";
|
|
static const char ParamName_BoneSpheres[] = "boneSpheres";
|
|
static const char ParamName_GravityDirection[] = "simulation.gravityDirection";
|
|
static const char ParamName_UvOrigin[] = "textureUVOrigin";
|
|
|
|
// UV flip params
|
|
static const char ParamName_SubmeshArray[] = "submeshes";
|
|
static const char ParamName_SubmeshBufferFormats[] = "vertexBuffer.vertexFormat.bufferFormats";
|
|
static const char ParamName_VertexBuffers[] = "vertexBuffer.buffers";
|
|
static const char ParamName_Semantic[] = "semantic";
|
|
static const char ParamName_BufferData[] = "data";
|
|
|
|
static const char ParamName_GLOD_Platforms[] = "platforms";
|
|
static const char ParamName_GLOD_LOD[] = "lod";
|
|
static const char ParamName_GLOD_PhysMeshID[] = "physicalMeshId";
|
|
static const char ParamName_GLOD_RenderMeshAsset[] = "renderMeshAsset";
|
|
static const char ParamName_GLOD_ImmediateClothMap[] = "immediateClothMap";
|
|
static const char ParamName_GLOD_SkinClothMapB[] = "SkinClothMapB";
|
|
static const char ParamName_GLOD_SkinClothMap[] = "SkinClothMap";
|
|
static const char ParamName_GLOD_SkinClothMapThickness[] = "skinClothMapThickness";
|
|
static const char ParamName_GLOD_SkinClothMapOffset[] = "skinClothMapOffset";
|
|
static const char ParamName_GLOD_TetraMap[] = "tetraMap";
|
|
static const char ParamName_GLOD_RenderMeshAssetSorting[] = "renderMeshAssetSorting";
|
|
static const char ParamName_GLOD_PhysicsMeshPartitioning[] = "physicsMeshPartitioning";
|
|
|
|
static const char ParamName_Partition_GraphicalSubmesh[] = "graphicalSubmesh";
|
|
static const char ParamName_Partition_NumSimVerts[] = "numSimulatedVertices";
|
|
static const char ParamName_Partition_NumSimVertsAdditional[] = "numSimulatedVerticesAdditional";
|
|
static const char ParamName_Partition_NumSimIndices[] = "numSimulatedIndices";
|
|
}
|
|
|
|
void LogAndToastWarning(const FText& Error)
|
|
{
|
|
FNotificationInfo Info(Error);
|
|
Info.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
|
|
UE_LOG(LogClothingAssetFactory, Warning, TEXT("%s"), *Error.ToString());
|
|
}
|
|
|
|
UClothingAssetFactory::UClothingAssetFactory(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::Import
|
|
(
|
|
const FString& Filename,
|
|
USkeletalMesh* TargetMesh,
|
|
FName InName
|
|
)
|
|
{
|
|
#if WITH_APEX_CLOTHING
|
|
if(!TargetMesh)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UClothingAssetCommon* NewClothingAsset = nullptr;
|
|
|
|
TArray<uint8> FileBuffer;
|
|
if(FFileHelper::LoadFileToArray(FileBuffer, *Filename, FILEREAD_Silent))
|
|
{
|
|
ClothingAsset* ApexAsset = ApexClothingUtils::CreateApexClothingAssetFromBuffer(FileBuffer.GetData(), FileBuffer.Num());
|
|
ApexAsset = ConvertApexAssetCoordSystem(ApexAsset);
|
|
|
|
if(InName == NAME_None)
|
|
{
|
|
InName = *FPaths::GetBaseFilename(Filename);
|
|
}
|
|
|
|
// Create an unreal clothing asset
|
|
NewClothingAsset = Cast<UClothingAssetCommon>(CreateFromApexAsset(ApexAsset, TargetMesh, InName));
|
|
|
|
if(NewClothingAsset)
|
|
{
|
|
// Store import path
|
|
NewClothingAsset->ImportedFilePath = Filename;
|
|
|
|
// Push to the target mesh
|
|
TargetMesh->AddClothingAsset(NewClothingAsset);
|
|
}
|
|
}
|
|
|
|
return NewClothingAsset;
|
|
#else
|
|
return nullptr;
|
|
#endif
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::Reimport(const FString& Filename, USkeletalMesh* TargetMesh, UClothingAssetBase* OriginalAsset)
|
|
{
|
|
#if WITH_APEX_CLOTHING
|
|
if(!TargetMesh)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
int32 OldIndex = INDEX_NONE;
|
|
if(TargetMesh->GetMeshClothingAssets().Find(OriginalAsset, OldIndex))
|
|
{
|
|
TArray<UActorComponent*> ComponentsToReregister;
|
|
for(TObjectIterator<USkeletalMeshComponent> It; It; ++It)
|
|
{
|
|
if(USkeletalMesh* UsedMesh = (*It)->SkeletalMesh)
|
|
{
|
|
if(UsedMesh == TargetMesh)
|
|
{
|
|
ComponentsToReregister.Add(*It);
|
|
}
|
|
}
|
|
}
|
|
|
|
FMultiComponentReregisterContext ReregisterContext(ComponentsToReregister);
|
|
|
|
UClothingAssetCommon* OldClothingAsset = Cast<UClothingAssetCommon>(TargetMesh->GetMeshClothingAssets()[OldIndex]);
|
|
UClothingAssetCommon* NewClothingAsset = nullptr;
|
|
FName AssetName = NAME_None;
|
|
|
|
if(!OldClothingAsset || !TargetMesh->GetMeshClothingAssets().IsValidIndex(OldIndex))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
TArray<uint8> FileBuffer;
|
|
if(FFileHelper::LoadFileToArray(FileBuffer, *Filename, FILEREAD_Silent))
|
|
{
|
|
ClothingAsset* ApexAsset = ApexClothingUtils::CreateApexClothingAssetFromBuffer(FileBuffer.GetData(), FileBuffer.Num());
|
|
ApexAsset = ConvertApexAssetCoordSystem(ApexAsset);
|
|
AssetName = *FPaths::GetBaseFilename(Filename);
|
|
|
|
TArray<ClothingAssetUtils::FClothingAssetMeshBinding> AssetBindings;
|
|
ClothingAssetUtils::GetMeshClothingAssetBindings(TargetMesh, AssetBindings);
|
|
|
|
OldClothingAsset->UnbindFromSkeletalMesh(TargetMesh);
|
|
|
|
// Create an unreal clothing asset
|
|
NewClothingAsset = Cast<UClothingAssetCommon>(CreateFromApexAsset(ApexAsset, TargetMesh, AssetName));
|
|
|
|
if(NewClothingAsset)
|
|
{
|
|
// Store import path
|
|
NewClothingAsset->ImportedFilePath = Filename;
|
|
|
|
TargetMesh->GetMeshClothingAssets()[OldIndex] = NewClothingAsset;
|
|
|
|
for(ClothingAssetUtils::FClothingAssetMeshBinding& Binding : AssetBindings)
|
|
{
|
|
NewClothingAsset->BindToSkeletalMesh(TargetMesh, Binding.LODIndex, Binding.SectionIndex, Binding.AssetInternalLodIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NewClothingAsset;
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::CreateFromSkeletalMesh(USkeletalMesh* TargetMesh, FSkeletalMeshClothBuildParams& Params)
|
|
{
|
|
// Need a valid skel mesh
|
|
if(!TargetMesh)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FSkeletalMeshModel* Mesh = TargetMesh->GetImportedModel();
|
|
|
|
// Need a valid resource
|
|
if(!Mesh)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Need a valid LOD model
|
|
if(!Mesh->LODModels.IsValidIndex(Params.LodIndex))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FSkeletalMeshLODModel& LodModel = Mesh->LODModels[Params.LodIndex];
|
|
|
|
// Need a valid section
|
|
if(!LodModel.Sections.IsValidIndex(Params.SourceSection))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Ok, we have a valid mesh and section, we can now extract it as a sim mesh
|
|
FSkelMeshSection& SourceSection = LodModel.Sections[Params.SourceSection];
|
|
|
|
// Can't convert to a clothing asset if bound to clothing
|
|
if(SourceSection.HasClothingData())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
FString SanitizedName = ObjectTools::SanitizeObjectName(Params.AssetName);
|
|
FName ObjectName = MakeUniqueObjectName(TargetMesh, UClothingAssetCommon::StaticClass(), FName(*SanitizedName));
|
|
|
|
UClothingAssetCommon* NewAsset = NewObject<UClothingAssetCommon>(TargetMesh, ObjectName);
|
|
NewAsset->SetFlags(RF_Transactional);
|
|
|
|
// Adding a new LOD from this skeletal mesh
|
|
NewAsset->AddNewLod();
|
|
FClothLODDataCommon& LodData = NewAsset->LodData.Last();
|
|
|
|
if(ImportToLodInternal(TargetMesh, Params.LodIndex, Params.SourceSection, NewAsset, LodData))
|
|
{
|
|
FScopedSkeletalMeshPostEditChange ScopedSkeletalMeshPostEditChange(TargetMesh);
|
|
if(Params.bRemoveFromMesh)
|
|
{
|
|
// User doesn't want the section anymore as a renderable, get rid of it
|
|
TargetMesh->RemoveMeshSection(Params.LodIndex, Params.SourceSection);
|
|
}
|
|
|
|
// Set asset guid
|
|
NewAsset->AssetGuid = FGuid::NewGuid();
|
|
|
|
// Set physics asset, will be used when building actors for cloth collisions
|
|
NewAsset->PhysicsAsset = Params.PhysicsAsset.LoadSynchronous();
|
|
|
|
// Build the final bone map
|
|
NewAsset->RefreshBoneMapping(TargetMesh);
|
|
|
|
// Invalidate cached data as the mesh has changed
|
|
NewAsset->InvalidateCachedData();
|
|
|
|
return NewAsset;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::CreateFromExistingCloth(USkeletalMesh* TargetMesh, USkeletalMesh* SourceMesh, UClothingAssetBase* SourceAsset)
|
|
{
|
|
UClothingAssetCommon* SourceClothingAsset = Cast<UClothingAssetCommon>(SourceAsset);
|
|
|
|
if (!SourceClothingAsset)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
//Duplicating the clothing asset using the existing asset as a template
|
|
UClothingAssetCommon* NewAsset = DuplicateObject<UClothingAssetCommon>(SourceClothingAsset, TargetMesh, SourceClothingAsset->GetFName());
|
|
|
|
NewAsset->AssetGuid = FGuid::NewGuid();
|
|
//Need to empty LODMap to remove previous mappings from cloth LOD to SkelMesh LOD
|
|
NewAsset->LodMap.Empty();
|
|
NewAsset->RefreshBoneMapping(TargetMesh);
|
|
NewAsset->InvalidateCachedData();
|
|
|
|
return NewAsset;
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::ImportLodToClothing(USkeletalMesh* TargetMesh, FSkeletalMeshClothBuildParams& Params)
|
|
{
|
|
if(!TargetMesh)
|
|
{
|
|
// Invalid target - can't continue.
|
|
LogAndToastWarning(LOCTEXT("Warning_InvalidLodMesh", "Failed to import clothing LOD, invalid target mesh specified"));
|
|
return nullptr;
|
|
}
|
|
|
|
if(!Params.TargetAsset.IsValid())
|
|
{
|
|
// Invalid target - can't continue.
|
|
LogAndToastWarning(LOCTEXT("Warning_InvalidClothTarget", "Failed to import clothing LOD, invalid target clothing object"));
|
|
return nullptr;
|
|
}
|
|
|
|
FSkeletalMeshModel* MeshResource = TargetMesh->GetImportedModel();
|
|
check(MeshResource);
|
|
|
|
const int32 NumMeshLods = MeshResource->LODModels.Num();
|
|
|
|
if(UClothingAssetBase* TargetClothing = Params.TargetAsset.Get())
|
|
{
|
|
// Find the clothing asset in the mesh to verify the params are correct
|
|
int32 MeshAssetIndex = INDEX_NONE;
|
|
if(TargetMesh->GetMeshClothingAssets().Find(TargetClothing, MeshAssetIndex))
|
|
{
|
|
// Everything looks good, continue to actual import
|
|
UClothingAssetCommon* ConcreteTarget = CastChecked<UClothingAssetCommon>(TargetClothing);
|
|
|
|
const FClothLODDataCommon* RemapSource = nullptr;
|
|
|
|
if(Params.bRemapParameters)
|
|
{
|
|
if(Params.TargetLod == ConcreteTarget->GetNumLods())
|
|
{
|
|
// New LOD, remap from previous
|
|
RemapSource = &ConcreteTarget->LodData.Last();
|
|
}
|
|
else
|
|
{
|
|
// This is a replacement, remap from current LOD
|
|
check(ConcreteTarget->LodData.IsValidIndex(Params.TargetLod));
|
|
RemapSource = &ConcreteTarget->LodData[Params.TargetLod];
|
|
}
|
|
}
|
|
|
|
if(Params.TargetLod == ConcreteTarget->GetNumLods())
|
|
{
|
|
ConcreteTarget->AddNewLod();
|
|
}
|
|
else if(!ConcreteTarget->LodData.IsValidIndex(Params.TargetLod))
|
|
{
|
|
LogAndToastWarning(LOCTEXT("Warning_InvalidLodTarget", "Failed to import clothing LOD, invalid target LOD."));
|
|
return nullptr;
|
|
}
|
|
|
|
FClothLODDataCommon& NewLod = ConcreteTarget->LodData[Params.TargetLod];
|
|
|
|
if(Params.TargetLod > 0 && Params.bRemapParameters)
|
|
{
|
|
RemapSource = &ConcreteTarget->LodData[Params.TargetLod - 1];
|
|
}
|
|
|
|
if(ImportToLodInternal(TargetMesh, Params.LodIndex, Params.SourceSection, ConcreteTarget, NewLod, RemapSource))
|
|
{
|
|
if(Params.bRemoveFromMesh)
|
|
{
|
|
// User doesn't want the section anymore as a renderable, get rid of it
|
|
TargetMesh->RemoveMeshSection(Params.LodIndex, Params.SourceSection);
|
|
}
|
|
|
|
// Rebuild the final bone map
|
|
ConcreteTarget->RefreshBoneMapping(TargetMesh);
|
|
|
|
// Build Lod skinning map for smooth transitions
|
|
ConcreteTarget->BuildLodTransitionData();
|
|
|
|
// Invalidate cached data as the mesh has changed
|
|
ConcreteTarget->InvalidateCachedData();
|
|
|
|
return ConcreteTarget;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UClothingAssetBase* UClothingAssetFactory::CreateFromApexAsset(nvidia::apex::ClothingAsset* InApexAsset, USkeletalMesh* TargetMesh, FName InName)
|
|
{
|
|
#if WITH_APEX_CLOTHING
|
|
UClothingAssetCommon* NewClothingAsset = NewObject<UClothingAssetCommon>(TargetMesh, InName);
|
|
NewClothingAsset->SetFlags(RF_Transactional);
|
|
NewClothingAsset->PostUpdateAllAssets(); // Ensure that the required configs, including shared configs, are created
|
|
|
|
const NvParameterized::Interface* AssetParams = InApexAsset->getAssetNvParameterized();
|
|
NvParameterized::Handle GraphicalLodArrayHandle(*AssetParams, "graphicalLods");
|
|
|
|
int32 NumSuccessfulLods = 0;
|
|
|
|
int32 NumLodsToBuild = 0;
|
|
GraphicalLodArrayHandle.getArraySize(NumLodsToBuild);
|
|
|
|
for(int32 CurrLodIdx = 0; CurrLodIdx < NumLodsToBuild; ++CurrLodIdx)
|
|
{
|
|
NewClothingAsset->AddNewLod();
|
|
FClothLODDataCommon& CurrentLodData = NewClothingAsset->LodData[CurrLodIdx];
|
|
|
|
TArray<FApexVertData> ApexVertData;
|
|
|
|
ExtractLodPhysicalData(NewClothingAsset, *InApexAsset, CurrLodIdx, CurrentLodData, ApexVertData);
|
|
ExtractSphereCollisions(NewClothingAsset, *InApexAsset, CurrLodIdx, CurrentLodData);
|
|
ExtractMaterialParameters(NewClothingAsset, *InApexAsset);
|
|
|
|
// Set to use legacy wind calculations, which is what APEX would normally have used
|
|
UClothConfigNv* const ConfigNv = NewClothingAsset->GetClothConfig<UClothConfigNv>();
|
|
if (!ensure(ConfigNv))
|
|
{
|
|
continue;
|
|
}
|
|
ConfigNv->ClothingWindMethod = EClothingWindMethodNv::Legacy;
|
|
|
|
// Fixup unreal-side bone indices
|
|
const int32 NumBoneDatas = CurrentLodData.PhysicalMeshData.BoneData.Num();
|
|
check(NumBoneDatas == ApexVertData.Num());
|
|
for(int32 BoneDataIndex = 0; BoneDataIndex < NumBoneDatas; ++BoneDataIndex)
|
|
{
|
|
FClothVertBoneData& BoneData = CurrentLodData.PhysicalMeshData.BoneData[BoneDataIndex];
|
|
FApexVertData& CurrentVertData = ApexVertData[BoneDataIndex];
|
|
|
|
for(int32 BoneInfluenceIdx = 0; BoneInfluenceIdx < MAX_TOTAL_INFLUENCES; ++BoneInfluenceIdx)
|
|
{
|
|
uint16 ApexBoneIndex = CurrentVertData.BoneIndices[BoneInfluenceIdx];
|
|
BoneData.BoneIndices[BoneInfluenceIdx] = ApexBoneIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExtractBoneData(NewClothingAsset, *InApexAsset);
|
|
|
|
// Now that we've extracted the APEX bone data, we need to fill the generic asset
|
|
// data with bone data for Unreal rather than APEX internal representations
|
|
const int32 NumUsedBones = NewClothingAsset->UsedBoneNames.Num();
|
|
|
|
NewClothingAsset->UsedBoneIndices.AddDefaulted(NumUsedBones);
|
|
for(int32 UsedBoneIndex = 0; UsedBoneIndex < NumUsedBones; ++UsedBoneIndex)
|
|
{
|
|
const FName& BoneName = NewClothingAsset->UsedBoneNames[UsedBoneIndex];
|
|
const int32 UnrealBoneIndex = TargetMesh->GetRefSkeleton().FindBoneIndex(BoneName);
|
|
|
|
// If we find an invalid bone then the asset is invalid, as it cannot be skinned to this mesh
|
|
if(UnrealBoneIndex == INDEX_NONE)
|
|
{
|
|
FText ErrorText = FText::Format(LOCTEXT("InvalidBoneError", "Imported asset requires bone \"{0}\", which is not present in the skeletal mesh ({1})"), FText::FromName(BoneName), FText::FromString(TargetMesh->GetName()));
|
|
LogAndToastWarning(ErrorText);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
NewClothingAsset->UsedBoneIndices[UsedBoneIndex] = UnrealBoneIndex;
|
|
}
|
|
|
|
uint32 AssetInternalRootBoneIndex;
|
|
verify(NvParameterized::getParamU32(*AssetParams, "rootBoneIndex", AssetInternalRootBoneIndex));
|
|
FName ConvertedBoneName(*FString(InApexAsset->getBoneName(AssetInternalRootBoneIndex)).Replace(TEXT(" "), TEXT("-")));
|
|
|
|
NewClothingAsset->AssetGuid = FGuid::NewGuid();
|
|
NewClothingAsset->InvalidateCachedData();
|
|
|
|
NewClothingAsset->BuildLodTransitionData();
|
|
NewClothingAsset->BuildSelfCollisionData();
|
|
NewClothingAsset->CalculateReferenceBoneIndex();
|
|
|
|
// Add masks for parameters
|
|
for (FClothLODDataCommon& LodData : NewClothingAsset->LodData)
|
|
{
|
|
const FClothPhysicalMeshData& PhysMesh = LodData.PhysicalMeshData;
|
|
|
|
// Didn't do anything previously - clear out in case there's something in there
|
|
// so we can use it correctly now.
|
|
LodData.PointWeightMaps.Reset(3);
|
|
|
|
// Max distances
|
|
LodData.PointWeightMaps.AddDefaulted();
|
|
FPointWeightMap& MaxDistanceMask = LodData.PointWeightMaps.Last();
|
|
const FPointWeightMap& MaxDistances = PhysMesh.GetWeightMap(EWeightMapTargetCommon::MaxDistance);
|
|
MaxDistanceMask.Initialize(MaxDistances, EWeightMapTargetCommon::MaxDistance);
|
|
|
|
const FPointWeightMap* const BackstopRadiuses = PhysMesh.FindWeightMap(EWeightMapTargetCommon::BackstopRadius);
|
|
if (BackstopRadiuses && !BackstopRadiuses->IsZeroed())
|
|
{
|
|
// Backstop radii
|
|
LodData.PointWeightMaps.AddDefaulted();
|
|
FPointWeightMap& BackstopRadiusMask = LodData.PointWeightMaps.Last();
|
|
BackstopRadiusMask.Initialize(*BackstopRadiuses, EWeightMapTargetCommon::BackstopRadius);
|
|
|
|
// Backstop distances
|
|
LodData.PointWeightMaps.AddDefaulted();
|
|
FPointWeightMap& BackstopDistanceMask = LodData.PointWeightMaps.Last();
|
|
const FPointWeightMap& BackstopDistances = PhysMesh.GetWeightMap(EWeightMapTargetCommon::BackstopDistance);
|
|
BackstopDistanceMask.Initialize(BackstopDistances, EWeightMapTargetCommon::BackstopDistance);
|
|
}
|
|
}
|
|
|
|
return NewClothingAsset;
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
bool UClothingAssetFactory::CanImport(const FString& Filename)
|
|
{
|
|
#if WITH_APEX_CLOTHING
|
|
// Need to read in the file and try to create an asset to get it's type
|
|
TArray<uint8> FileBuffer;
|
|
if(FFileHelper::LoadFileToArray(FileBuffer, *Filename, FILEREAD_Silent))
|
|
{
|
|
physx::PxFileBuf* Stream = GApexSDK->createMemoryReadStream(FileBuffer.GetData(), FileBuffer.Num());
|
|
if(Stream)
|
|
{
|
|
NvParameterized::Serializer::SerializeType SerializeType = GApexSDK->getSerializeType(*Stream);
|
|
if(NvParameterized::Serializer* Serializer = GApexSDK->createSerializer(SerializeType))
|
|
{
|
|
NvParameterized::Serializer::DeserializedData DeserializedData;
|
|
Serializer->deserialize(*Stream, DeserializedData);
|
|
|
|
if(DeserializedData.size() > 0)
|
|
{
|
|
NvParameterized::Interface* AssetInterface = DeserializedData[0];
|
|
|
|
int32 StringLength = StringCast<TCHAR>(AssetInterface->className()).Length();
|
|
FString ClassName(StringLength, StringCast<TCHAR>(AssetInterface->className()).Get());
|
|
|
|
if(ClassName == ClothingFactoryConstants::ClothingAssetClass)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
GApexSDK->releaseMemoryReadStream(*Stream);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
#if WITH_APEX_CLOTHING
|
|
ClothingAsset* UClothingAssetFactory::ConvertApexAssetCoordSystem(ClothingAsset* InAsset)
|
|
{
|
|
// Build new asset interface to store the transformed asset
|
|
const NvParameterized::Interface* OriginalInterface = InAsset->getAssetNvParameterized();
|
|
NvParameterized::Interface* NewInterface = GApexSDK->getParameterizedTraits()->createNvParameterized(OriginalInterface->className());
|
|
check(NewInterface);
|
|
|
|
// Copy asset data
|
|
NewInterface->copy(*OriginalInterface);
|
|
|
|
ClothingAssetAuthoring* AssetAuthoring = (ClothingAssetAuthoring*)GApexSDK->createAssetAuthoring(NewInterface, nullptr);
|
|
|
|
check(AssetAuthoring);
|
|
|
|
// Need to check for bone actors and spheres, we can't have both
|
|
PxI32 NumBoneActors = 0;
|
|
PxI32 NumBoneSpheres = 0;
|
|
|
|
verify(NvParameterized::getParamArraySize(*OriginalInterface, ClothingFactoryConstants::ParamName_BoneActors, NumBoneActors));
|
|
verify(NvParameterized::getParamArraySize(*OriginalInterface, ClothingFactoryConstants::ParamName_BoneSpheres, NumBoneSpheres));
|
|
|
|
// Remove collision if we have spheres and actors (actors will remain)
|
|
if(NumBoneActors > 0 && NumBoneSpheres > 0)
|
|
{
|
|
AssetAuthoring->clearCollision();
|
|
}
|
|
|
|
// Y direction needs to be inverted
|
|
PxMat44 YInvertMatrix(PxIdentity);
|
|
YInvertMatrix.column1.y = -1.0f;
|
|
|
|
// Matrix holding the coordinate space conversion required for the mesh
|
|
PxMat44 ConversionTransform(PxIdentity);
|
|
|
|
// Get gravity direction, as that should be -up
|
|
PxVec3 GravityDirection;
|
|
verify(NvParameterized::getParamVec3(*OriginalInterface, ClothingFactoryConstants::ParamName_GravityDirection, GravityDirection));
|
|
|
|
// Y-up, needs conversion to z-up
|
|
if(GravityDirection.z == 0.0f && FMath::Abs(GravityDirection.y) > 0.0f)
|
|
{
|
|
PxVec3 NewGravityDirection(GravityDirection.x, GravityDirection.z, GravityDirection.y);
|
|
AssetAuthoring->setSimulationGravityDirection(NewGravityDirection);
|
|
|
|
// Invert Y + 90 deg rotation on x
|
|
ConversionTransform.column1.y = 0.0f;
|
|
ConversionTransform.column1.z = 1.0f;
|
|
ConversionTransform.column2.y = 1.0f;
|
|
ConversionTransform.column2.z = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
ConversionTransform = YInvertMatrix;
|
|
}
|
|
|
|
AssetAuthoring->applyTransformation(ConversionTransform, 1.0f, true, true);
|
|
|
|
// Transform bind poses
|
|
const uint32 NumUsedBones = InAsset->getNumUsedBones();
|
|
|
|
TArray<PxMat44> TransformedBindPoses;
|
|
TransformedBindPoses.Reserve(NumUsedBones);
|
|
|
|
for(uint32 Idx = 0; Idx < NumUsedBones; ++Idx)
|
|
{
|
|
PxMat44 CurrentBindPose(PxIdentity);
|
|
AssetAuthoring->getBoneBindPose(Idx, CurrentBindPose);
|
|
TransformedBindPoses.Add(CurrentBindPose * YInvertMatrix);
|
|
}
|
|
|
|
AssetAuthoring->updateBindPoses(TransformedBindPoses.GetData(), TransformedBindPoses.Num(), true, true);
|
|
|
|
const uint32 NumLods = AssetAuthoring->getNumLods();
|
|
for(uint32 Idx = 0; Idx < NumLods; ++Idx)
|
|
{
|
|
if(NvParameterized::Interface* RenderMeshAuthoringInterface = AssetAuthoring->getRenderMeshAssetAuthoring(Idx))
|
|
{
|
|
NvParameterized::Handle RenderMeshAuthoringHandle(RenderMeshAuthoringInterface);
|
|
|
|
bool bFlipU = false;
|
|
bool bFlipV = false;
|
|
|
|
NvParameterized::Interface* UVOriginParameter = NvParameterized::findParam(*RenderMeshAuthoringInterface, ClothingFactoryConstants::ParamName_UvOrigin, RenderMeshAuthoringHandle);
|
|
if(UVOriginParameter)
|
|
{
|
|
uint32 UvOrigin = 0;
|
|
RenderMeshAuthoringHandle.getParamU32(UvOrigin);
|
|
|
|
switch(UvOrigin)
|
|
{
|
|
case TextureUVOrigin::ORIGIN_TOP_LEFT:
|
|
bFlipU = false;
|
|
bFlipV = false;
|
|
break;
|
|
case TextureUVOrigin::ORIGIN_TOP_RIGHT:
|
|
bFlipU = true;
|
|
bFlipV = false;
|
|
break;
|
|
case TextureUVOrigin::ORIGIN_BOTTOM_LEFT:
|
|
bFlipU = false;
|
|
bFlipV = true;
|
|
break;
|
|
case TextureUVOrigin::ORIGIN_BOTTOM_RIGHT:
|
|
bFlipU = false;
|
|
bFlipV = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
RenderMeshAuthoringHandle.setParamU32(TextureUVOrigin::ORIGIN_TOP_LEFT);
|
|
}
|
|
|
|
// Flip UVs
|
|
FlipAuthoringUvs(RenderMeshAuthoringInterface, bFlipU, bFlipV);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
char AssetName[MAX_SPRINTF] = {0};
|
|
FCStringAnsi::Strncpy(AssetName, InAsset->getName(), MAX_SPRINTF);
|
|
|
|
InAsset->release();
|
|
InAsset = nullptr;
|
|
|
|
ClothingAsset* NewAsset = (ClothingAsset*)GApexSDK->createAsset(*AssetAuthoring, AssetName);
|
|
|
|
check(NewAsset);
|
|
|
|
AssetAuthoring->release();
|
|
|
|
return NewAsset;
|
|
}
|
|
|
|
void UClothingAssetFactory::FlipAuthoringUvs(NvParameterized::Interface* InRenderMeshAuthoringInterface, bool bFlipU, bool bFlipV)
|
|
{
|
|
if(!bFlipU && !bFlipV)
|
|
{
|
|
// Don't need to do anything
|
|
return;
|
|
}
|
|
|
|
NvParameterized::Handle SubmeshArrayHandle(*InRenderMeshAuthoringInterface, ClothingFactoryConstants::ParamName_SubmeshArray);
|
|
|
|
if(SubmeshArrayHandle.isValid())
|
|
{
|
|
int32 ArraySize = 0;
|
|
SubmeshArrayHandle.getArraySize(ArraySize, 0);
|
|
|
|
for(int32 SubmeshIdx = 0; SubmeshIdx < ArraySize; ++SubmeshIdx)
|
|
{
|
|
NvParameterized::Handle SubmeshHandle(SubmeshArrayHandle);
|
|
SubmeshArrayHandle.getChildHandle(SubmeshIdx, SubmeshHandle);
|
|
|
|
if(!SubmeshHandle.isValid())
|
|
{
|
|
// No submesh, move to next array entry
|
|
continue;
|
|
}
|
|
|
|
NvParameterized::Interface* SubmeshInterface = nullptr;
|
|
SubmeshHandle.getParamRef(SubmeshInterface);
|
|
|
|
NvParameterized::Handle BufferFormatsHandle(SubmeshHandle);
|
|
NvParameterized::findParam(*SubmeshInterface, ClothingFactoryConstants::ParamName_SubmeshBufferFormats, BufferFormatsHandle);
|
|
|
|
if(!BufferFormatsHandle.isValid())
|
|
{
|
|
// No valid format array, move to next submesh
|
|
continue;
|
|
}
|
|
|
|
int32 FormatArraySize = 0;
|
|
BufferFormatsHandle.getArraySize(FormatArraySize);
|
|
|
|
for(int32 FormatIdx = 0; FormatIdx < FormatArraySize; ++FormatIdx)
|
|
{
|
|
NvParameterized::Handle FormatHandle(BufferFormatsHandle);
|
|
FormatHandle.set(FormatIdx);
|
|
|
|
NvParameterized::Handle SemanticHandle(FormatHandle);
|
|
FormatHandle.getChildHandle(FormatHandle.getInterface(), "semantic", SemanticHandle);
|
|
|
|
if(!SemanticHandle.isValid())
|
|
{
|
|
// No valid semantic, move to next buffer format
|
|
continue;
|
|
}
|
|
|
|
PxI32 BufferSemantic = -1;
|
|
SemanticHandle.getParamI32(BufferSemantic);
|
|
|
|
if(BufferSemantic >= RenderVertexSemantic::TEXCOORD0 &&
|
|
BufferSemantic <= RenderVertexSemantic::TEXCOORD3)
|
|
{
|
|
NvParameterized::Handle BufferArrayHandle(SubmeshHandle);
|
|
NvParameterized::findParam(*SubmeshInterface, ClothingFactoryConstants::ParamName_VertexBuffers, BufferArrayHandle);
|
|
|
|
int32 BufferArraySize = -1;
|
|
BufferArrayHandle.getArraySize(BufferArraySize);
|
|
check(BufferSemantic < BufferArraySize);
|
|
|
|
if(BufferArraySize == -1)
|
|
{
|
|
// Failed to find array, move to next format
|
|
continue;
|
|
}
|
|
|
|
BufferArrayHandle.set(BufferSemantic);
|
|
|
|
NvParameterized::Handle DataHandle(BufferArrayHandle);
|
|
NvParameterized::Interface* BufferInterface = nullptr;
|
|
BufferArrayHandle.getParamRef(BufferInterface);
|
|
|
|
check(BufferInterface);
|
|
|
|
NvParameterized::findParam(*BufferInterface, ClothingFactoryConstants::ParamName_BufferData, DataHandle);
|
|
|
|
if(!DataHandle.isValid())
|
|
{
|
|
// No data array, move to next format
|
|
continue;
|
|
}
|
|
|
|
int32 DataArraySize = -1;
|
|
DataHandle.getArraySize(DataArraySize);
|
|
|
|
float MaxU = -MAX_flt;
|
|
float MaxV = -MAX_flt;
|
|
for(int32 DataIdx = 0; DataIdx < DataArraySize; ++DataIdx)
|
|
{
|
|
// Push to data entry
|
|
DataHandle.set(DataIdx);
|
|
|
|
// UV coord storage
|
|
float Coord[2];
|
|
|
|
DataHandle.set(0); // Inside data entry, get first element (U Coord)
|
|
DataHandle.getParamF32(Coord[0]);
|
|
DataHandle.popIndex(); // Back out to data entry
|
|
|
|
DataHandle.set(1); // Inside data entry, get second element (V Coord)
|
|
DataHandle.getParamF32(Coord[1]);
|
|
DataHandle.popIndex(); // Back out to data entry
|
|
|
|
MaxU = FMath::Max(MaxU, Coord[0] - DELTA);
|
|
MaxV = FMath::Max(MaxV, Coord[1] - DELTA);
|
|
|
|
DataHandle.popIndex(); // Back out to data array
|
|
}
|
|
|
|
MaxU = FMath::FloorToFloat(MaxU) + 1.0f;
|
|
MaxV = FMath::FloorToFloat(MaxV) + 1.0f;
|
|
|
|
for(int32 DataIdx = 0; DataIdx < DataArraySize; ++DataIdx)
|
|
{
|
|
DataHandle.set(DataIdx);
|
|
|
|
float CoordPart = 0.0f;
|
|
|
|
if(bFlipU)
|
|
{
|
|
DataHandle.set(0);
|
|
DataHandle.getParamF32(CoordPart);
|
|
DataHandle.setParamF32(MaxU - CoordPart);
|
|
DataHandle.popIndex();
|
|
}
|
|
|
|
if(bFlipV)
|
|
{
|
|
DataHandle.set(1);
|
|
DataHandle.getParamF32(CoordPart);
|
|
DataHandle.setParamF32(MaxV - CoordPart);
|
|
DataHandle.popIndex();
|
|
}
|
|
|
|
DataHandle.popIndex();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UClothingAssetFactory::ExtractBoneData(UClothingAssetCommon* NewAsset, ClothingAsset &InApexAsset)
|
|
{
|
|
const uint32 NumApexUsedBones = InApexAsset.getNumUsedBones();
|
|
|
|
NewAsset->UsedBoneNames.Empty(NumApexUsedBones);
|
|
|
|
for(uint32 BoneIdx = 0; BoneIdx < NumApexUsedBones; ++BoneIdx)
|
|
{
|
|
FString BoneName = FString(InApexAsset.getBoneName(BoneIdx)).Replace(TEXT(" "), TEXT("-"));
|
|
NewAsset->UsedBoneNames.Add(*BoneName);
|
|
}
|
|
}
|
|
|
|
void UClothingAssetFactory::ExtractSphereCollisions(UClothingAssetCommon* NewAsset, nvidia::apex::ClothingAsset &InApexAsset, int32 InLodIdx, FClothLODDataCommon& InLodData)
|
|
{
|
|
const NvParameterized::Interface* AssetParams = InApexAsset.getAssetNvParameterized();
|
|
|
|
NvParameterized::Handle BoneSphereHandle(*AssetParams, "boneSpheres");
|
|
|
|
int32 NumBoneSpheres = 0;
|
|
BoneSphereHandle.getArraySize(NumBoneSpheres);
|
|
|
|
FClothCollisionData& CollisionData = InLodData.CollisionData;
|
|
CollisionData.Spheres.AddDefaulted(NumBoneSpheres);
|
|
|
|
// Load the bone spheres
|
|
for(int32 BoneSphereIndex = 0; BoneSphereIndex < NumBoneSpheres; ++BoneSphereIndex)
|
|
{
|
|
FClothCollisionPrim_Sphere& CurrentSphere = CollisionData.Spheres[BoneSphereIndex];
|
|
|
|
BoneSphereHandle.set(BoneSphereIndex);
|
|
|
|
NvParameterized::Handle ChildHandle(BoneSphereHandle);
|
|
|
|
BoneSphereHandle.getChildHandle(BoneSphereHandle.getInterface(), "boneIndex", ChildHandle);
|
|
ChildHandle.getParamI32(CurrentSphere.BoneIndex);
|
|
|
|
BoneSphereHandle.getChildHandle(BoneSphereHandle.getInterface(), "radius", ChildHandle);
|
|
ChildHandle.getParamF32(CurrentSphere.Radius);
|
|
|
|
BoneSphereHandle.getChildHandle(BoneSphereHandle.getInterface(), "localPos", ChildHandle);
|
|
physx::PxVec3 PxLocalPos;
|
|
ChildHandle.getParamVec3(PxLocalPos);
|
|
CurrentSphere.LocalPosition = P2UVector(PxLocalPos);
|
|
|
|
BoneSphereHandle.popIndex();
|
|
}
|
|
|
|
// Next load "connections". A connection is used to turn 2 spheres into a capsule by connecting them
|
|
NvParameterized::Handle BoneSphereConnectionHandle(*AssetParams, "boneSphereConnections");
|
|
|
|
int32 NumConnections = 0;
|
|
BoneSphereConnectionHandle.getArraySize(NumConnections);
|
|
check(NumConnections % 2 == 0); // Needs to be even
|
|
CollisionData.SphereConnections.AddDefaulted(NumConnections / 2);
|
|
|
|
for(int32 ConnectionIndex = 0; ConnectionIndex < NumConnections; ConnectionIndex += 2)
|
|
{
|
|
FClothCollisionPrim_SphereConnection& CurrentConnection = CollisionData.SphereConnections[ConnectionIndex / 2];
|
|
|
|
uint16 FirstSphereIndex;
|
|
uint16 SecondSphereIndex;
|
|
|
|
BoneSphereConnectionHandle.set(ConnectionIndex);
|
|
BoneSphereConnectionHandle.getParamU16(FirstSphereIndex);
|
|
BoneSphereConnectionHandle.popIndex();
|
|
|
|
BoneSphereConnectionHandle.set(ConnectionIndex + 1);
|
|
BoneSphereConnectionHandle.getParamU16(SecondSphereIndex);
|
|
BoneSphereConnectionHandle.popIndex();
|
|
|
|
CurrentConnection.SphereIndices[0] = FirstSphereIndex;
|
|
CurrentConnection.SphereIndices[1] = SecondSphereIndex;
|
|
}
|
|
|
|
// Load boneactors. Bone actors are a different way to handle capsules
|
|
// By defining a capsule height and radius.
|
|
NvParameterized::Handle BoneActorHandle(*AssetParams, "boneActors");
|
|
|
|
int32 NumActors = 0;
|
|
BoneActorHandle.getArraySize(NumActors);
|
|
|
|
for(int32 ActorIndex = 0; ActorIndex < NumActors; ++ActorIndex)
|
|
{
|
|
BoneActorHandle.set(ActorIndex);
|
|
|
|
NvParameterized::Handle ChildHandle(BoneActorHandle);
|
|
BoneActorHandle.getChildHandle(BoneActorHandle.getInterface(), "convexVerticesCount", ChildHandle);
|
|
|
|
uint32 NumConvexVerts = 0;
|
|
ChildHandle.getParamU32(NumConvexVerts);
|
|
|
|
if(NumConvexVerts > 0)
|
|
{
|
|
// Convex mesh, extract the data
|
|
}
|
|
else
|
|
{
|
|
int32 BoneIndex;
|
|
float Radius;
|
|
float Height;
|
|
physx::PxMat44 PxPoseMatrix;
|
|
FMatrix PoseMatrix;
|
|
|
|
BoneActorHandle.getChildHandle(BoneActorHandle.getInterface(), "boneIndex", ChildHandle);
|
|
ChildHandle.getParamI32(BoneIndex);
|
|
|
|
BoneActorHandle.getChildHandle(BoneActorHandle.getInterface(), "capsuleRadius", ChildHandle);
|
|
ChildHandle.getParamF32(Radius);
|
|
|
|
BoneActorHandle.getChildHandle(BoneActorHandle.getInterface(), "capsuleHeight", ChildHandle);
|
|
ChildHandle.getParamF32(Height);
|
|
|
|
BoneActorHandle.getChildHandle(BoneActorHandle.getInterface(), "localPose", ChildHandle);
|
|
ChildHandle.getParamMat44(PxPoseMatrix);
|
|
|
|
PoseMatrix = P2UMatrix(PxPoseMatrix);
|
|
|
|
FVector HalfVector(0.0f, Height * 0.5f, 0.0f);
|
|
FVector Sphere0Position = PoseMatrix.TransformPosition(HalfVector);
|
|
FVector Sphere1Position = PoseMatrix.TransformPosition(-HalfVector);
|
|
|
|
CollisionData.Spheres.AddDefaulted(2);
|
|
FClothCollisionPrim_Sphere& Sphere0 = CollisionData.Spheres.Last(1);
|
|
FClothCollisionPrim_Sphere& Sphere1 = CollisionData.Spheres.Last(0);
|
|
|
|
Sphere0.LocalPosition = Sphere0Position;
|
|
Sphere0.Radius = Radius;
|
|
Sphere0.BoneIndex = BoneIndex;
|
|
|
|
Sphere1.LocalPosition = Sphere1Position;
|
|
Sphere1.Radius = Radius;
|
|
Sphere1.BoneIndex = BoneIndex;
|
|
|
|
CollisionData.SphereConnections.AddDefaulted();
|
|
FClothCollisionPrim_SphereConnection& Connection = CollisionData.SphereConnections.Last();
|
|
|
|
Connection.SphereIndices[0] = CollisionData.Spheres.Num() - 2;
|
|
Connection.SphereIndices[1] = CollisionData.Spheres.Num() - 1;
|
|
}
|
|
|
|
BoneActorHandle.popIndex();
|
|
}
|
|
}
|
|
|
|
void UClothingAssetFactory::ExtractMaterialParameters(UClothingAssetCommon* NewAsset, nvidia::apex::ClothingAsset &InApexAsset)
|
|
{
|
|
const NvParameterized::Interface* AssetParams = InApexAsset.getAssetNvParameterized();
|
|
|
|
uint32 MaterialIndex = INDEX_NONE;
|
|
NvParameterized::getParamU32(*AssetParams, "materialIndex", MaterialIndex);
|
|
|
|
NvParameterized::Interface* MaterialLibraryParams = nullptr;
|
|
NvParameterized::getParamRef(*AssetParams, "materialLibrary", MaterialLibraryParams);
|
|
|
|
NvParameterized::Handle MaterialArrayHandle(*MaterialLibraryParams);
|
|
MaterialLibraryParams->getParameterHandle("materials", MaterialArrayHandle);
|
|
|
|
int32 NumMaterials = INDEX_NONE;
|
|
MaterialArrayHandle.getArraySize(NumMaterials);
|
|
|
|
check(MaterialIndex < (uint32)NumMaterials);
|
|
|
|
MaterialArrayHandle.set(MaterialIndex);
|
|
|
|
if(UClothConfigNv* const ConfigPtr = NewAsset->GetClothConfig<UClothConfigNv>())
|
|
{
|
|
UClothConfigNv& Config = *ConfigPtr;
|
|
NvParameterized::Handle ChildHandle(MaterialArrayHandle);
|
|
|
|
// Read out material params
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "selfcollisionThickness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.SelfCollisionRadius);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "selfcollisionStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.SelfCollisionStiffness);
|
|
|
|
float ApexDamping;
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "damping", ChildHandle);
|
|
ChildHandle.getParamF32(ApexDamping);
|
|
Config.Damping = FVector(ApexDamping);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "friction", ChildHandle);
|
|
ChildHandle.getParamF32(Config.Friction);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "gravityScale", ChildHandle);
|
|
ChildHandle.getParamF32(Config.GravityScale);
|
|
|
|
// Tether parameters
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "tetherLimit", ChildHandle);
|
|
ChildHandle.getParamF32(Config.TetherLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "tetherStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.TetherStiffness);
|
|
|
|
// Drag and inertia have 2 components but APEX only uses one
|
|
float Drag = 1.0f;
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "drag", ChildHandle);
|
|
ChildHandle.getParamF32(Drag);
|
|
Config.LinearDrag = FVector(Drag);
|
|
Config.AngularDrag = FVector(Drag);
|
|
|
|
float InertiaScale = 1.0f;
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "inertiaScale", ChildHandle);
|
|
ChildHandle.getParamF32(InertiaScale);
|
|
Config.LinearInertiaScale = FVector(InertiaScale);
|
|
Config.AngularInertiaScale = FVector(InertiaScale);
|
|
|
|
// Simulation frequencies
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "stiffnessFrequency", ChildHandle);
|
|
ChildHandle.getParamF32(Config.StiffnessFrequency);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "solverFrequency", ChildHandle);
|
|
ChildHandle.getParamF32(Config.SolverFrequency);
|
|
|
|
// Vertical constraint params
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "verticalStretchingStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.VerticalConstraint.Stiffness);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "verticalStiffnessScaling.compressionRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.VerticalConstraint.CompressionLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "verticalStiffnessScaling.stretchRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.VerticalConstraint.StretchLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "verticalStiffnessScaling.scale", ChildHandle);
|
|
ChildHandle.getParamF32(Config.VerticalConstraint.StiffnessMultiplier);
|
|
|
|
// Horizontal constraint params
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "horizontalStretchingStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.HorizontalConstraint.Stiffness);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "horizontalStiffnessScaling.compressionRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.HorizontalConstraint.CompressionLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "horizontalStiffnessScaling.stretchRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.HorizontalConstraint.StretchLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "horizontalStiffnessScaling.scale", ChildHandle);
|
|
ChildHandle.getParamF32(Config.HorizontalConstraint.StiffnessMultiplier);
|
|
|
|
// Bend constraint params
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "bendingStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.BendConstraint.Stiffness);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "bendingStiffnessScaling.compressionRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.BendConstraint.CompressionLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "bendingStiffnessScaling.stretchRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.BendConstraint.StretchLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "bendingStiffnessScaling.scale", ChildHandle);
|
|
ChildHandle.getParamF32(Config.BendConstraint.StiffnessMultiplier);
|
|
|
|
// Shear constraint params
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "shearingStiffness", ChildHandle);
|
|
ChildHandle.getParamF32(Config.ShearConstraint.Stiffness);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "shearingStiffnessScaling.compressionRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.ShearConstraint.CompressionLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "shearingStiffnessScaling.stretchRange", ChildHandle);
|
|
ChildHandle.getParamF32(Config.ShearConstraint.StretchLimit);
|
|
|
|
MaterialArrayHandle.getChildHandle(MaterialArrayHandle.getInterface(), "shearingStiffnessScaling.scale", ChildHandle);
|
|
ChildHandle.getParamF32(Config.ShearConstraint.StiffnessMultiplier);
|
|
|
|
// UE just used the vertical config for everything, so stomp the other configs
|
|
Config.HorizontalConstraint.CompressionLimit = Config.VerticalConstraint.CompressionLimit;
|
|
Config.HorizontalConstraint.StretchLimit = Config.VerticalConstraint.StretchLimit;
|
|
Config.HorizontalConstraint.StiffnessMultiplier = Config.VerticalConstraint.StiffnessMultiplier;
|
|
|
|
Config.BendConstraint.CompressionLimit = Config.VerticalConstraint.CompressionLimit;
|
|
Config.BendConstraint.StretchLimit = Config.VerticalConstraint.StretchLimit;
|
|
Config.BendConstraint.StiffnessMultiplier = Config.VerticalConstraint.StiffnessMultiplier;
|
|
|
|
Config.ShearConstraint.CompressionLimit = Config.VerticalConstraint.CompressionLimit;
|
|
Config.ShearConstraint.StretchLimit = Config.VerticalConstraint.StretchLimit;
|
|
Config.ShearConstraint.StiffnessMultiplier = Config.VerticalConstraint.StiffnessMultiplier;
|
|
|
|
}
|
|
}
|
|
|
|
#endif // WITH_APEX_CLOTHING
|
|
|
|
|
|
bool UClothingAssetFactory::ImportToLodInternal(
|
|
USkeletalMesh* SourceMesh,
|
|
int32 SourceLodIndex,
|
|
int32 SourceSectionIndex,
|
|
UClothingAssetCommon* DestAsset,
|
|
FClothLODDataCommon& DestLod,
|
|
const FClothLODDataCommon* InParameterRemapSource)
|
|
{
|
|
if(!SourceMesh || !SourceMesh->GetImportedModel())
|
|
{
|
|
// Invalid mesh
|
|
return false;
|
|
}
|
|
|
|
FSkeletalMeshModel* SkeletalResource = SourceMesh->GetImportedModel();
|
|
|
|
if(!SkeletalResource->LODModels.IsValidIndex(SourceLodIndex))
|
|
{
|
|
// Invalid LOD
|
|
return false;
|
|
}
|
|
|
|
FSkeletalMeshLODModel& SourceLod = SkeletalResource->LODModels[SourceLodIndex];
|
|
|
|
if(!SourceLod.Sections.IsValidIndex(SourceSectionIndex))
|
|
{
|
|
// Invalid Section
|
|
return false;
|
|
}
|
|
|
|
FSkelMeshSection& SourceSection = SourceLod.Sections[SourceSectionIndex];
|
|
|
|
const int32 NumVerts = SourceSection.SoftVertices.Num();
|
|
const int32 NumIndices = SourceSection.NumTriangles * 3;
|
|
const int32 BaseIndex = SourceSection.BaseIndex;
|
|
const int32 BaseVertexIndex = SourceSection.BaseVertexIndex;
|
|
|
|
// We need to weld the mesh verts to get rid of duplicates (happens for smoothing groups)
|
|
TArray<FVector> UniqueVerts;
|
|
TArray<uint32> OriginalIndexes;
|
|
|
|
TArray<uint32> IndexRemap;
|
|
IndexRemap.AddDefaulted(NumVerts);
|
|
{
|
|
static const float ThreshSq = SMALL_NUMBER * SMALL_NUMBER;
|
|
for(int32 VertIndex = 0; VertIndex < NumVerts; ++VertIndex)
|
|
{
|
|
const FSoftSkinVertex& SourceVert = SourceSection.SoftVertices[VertIndex];
|
|
|
|
bool bUnique = true;
|
|
int32 RemapIndex = INDEX_NONE;
|
|
|
|
const int32 NumUniqueVerts = UniqueVerts.Num();
|
|
for(int32 UniqueVertIndex = 0; UniqueVertIndex < NumUniqueVerts; ++UniqueVertIndex)
|
|
{
|
|
FVector& UniqueVert = UniqueVerts[UniqueVertIndex];
|
|
|
|
if((UniqueVert - SourceVert.Position).SizeSquared() <= ThreshSq)
|
|
{
|
|
// Not unique
|
|
bUnique = false;
|
|
RemapIndex = UniqueVertIndex;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(bUnique)
|
|
{
|
|
// Unique
|
|
UniqueVerts.Add(SourceVert.Position);
|
|
OriginalIndexes.Add(VertIndex);
|
|
IndexRemap[VertIndex] = UniqueVerts.Num() - 1;
|
|
}
|
|
else
|
|
{
|
|
IndexRemap[VertIndex] = RemapIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 NumUniqueVerts = UniqueVerts.Num();
|
|
|
|
// If we're going to remap the parameters we need to cache the remap source
|
|
// data. We copy it here incase the destination and remap source
|
|
// lod models are aliased (as in a reimport)
|
|
TArray<FVector3f> CachedPositions;
|
|
TArray<FVector3f> CachedNormals;
|
|
TArray<uint32> CachedIndices;
|
|
int32 NumSourceMasks = 0;
|
|
TArray<FPointWeightMap> SourceMaskCopy;
|
|
|
|
bool bPerformParamterRemap = false;
|
|
|
|
if(InParameterRemapSource)
|
|
{
|
|
const FClothPhysicalMeshData& RemapPhysMesh = InParameterRemapSource->PhysicalMeshData;
|
|
CachedPositions = RemapPhysMesh.Vertices;
|
|
CachedNormals = RemapPhysMesh.Normals;
|
|
CachedIndices = RemapPhysMesh.Indices;
|
|
SourceMaskCopy = InParameterRemapSource->PointWeightMaps;
|
|
NumSourceMasks = SourceMaskCopy.Num();
|
|
|
|
bPerformParamterRemap = true;
|
|
}
|
|
|
|
FClothPhysicalMeshData& PhysMesh = DestLod.PhysicalMeshData;
|
|
PhysMesh.Reset(NumUniqueVerts, NumIndices);
|
|
|
|
for(int32 VertexIndex = 0; VertexIndex < NumUniqueVerts; ++VertexIndex)
|
|
{
|
|
const FSoftSkinVertex& SourceVert = SourceSection.SoftVertices[OriginalIndexes[VertexIndex]];
|
|
|
|
PhysMesh.Vertices[VertexIndex] = SourceVert.Position;
|
|
PhysMesh.Normals[VertexIndex] = SourceVert.TangentZ;
|
|
PhysMesh.VertexColors[VertexIndex] = SourceVert.Color;
|
|
|
|
FClothVertBoneData& BoneData = PhysMesh.BoneData[VertexIndex];
|
|
for(int32 InfluenceIndex = 0; InfluenceIndex < MAX_TOTAL_INFLUENCES; ++InfluenceIndex)
|
|
{
|
|
const uint16 SourceIndex = SourceSection.BoneMap[SourceVert.InfluenceBones[InfluenceIndex]];
|
|
if(SourceIndex != INDEX_NONE)
|
|
{
|
|
FName BoneName = SourceMesh->GetRefSkeleton().GetBoneName(SourceIndex);
|
|
BoneData.BoneIndices[InfluenceIndex] = DestAsset->UsedBoneNames.AddUnique(BoneName);
|
|
BoneData.BoneWeights[InfluenceIndex] = (float)SourceVert.InfluenceWeights[InfluenceIndex] / 255.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a max distance parameter mask to the physics mesh
|
|
FPointWeightMap& PhysMeshMaxDistances = PhysMesh.AddWeightMap(EWeightMapTargetCommon::MaxDistance);
|
|
PhysMeshMaxDistances.Initialize(PhysMesh.Vertices.Num());
|
|
|
|
// Add a max distance parameter mask to the LOD
|
|
DestLod.PointWeightMaps.AddDefaulted();
|
|
FPointWeightMap& LodMaxDistances = DestLod.PointWeightMaps.Last();
|
|
LodMaxDistances.Initialize(PhysMeshMaxDistances, EWeightMapTargetCommon::MaxDistance);
|
|
|
|
PhysMesh.MaxBoneWeights = SourceSection.MaxBoneInfluences;
|
|
|
|
for(int32 IndexIndex = 0; IndexIndex < NumIndices; ++IndexIndex)
|
|
{
|
|
PhysMesh.Indices[IndexIndex] = SourceLod.IndexBuffer[BaseIndex + IndexIndex] - BaseVertexIndex;
|
|
PhysMesh.Indices[IndexIndex] = IndexRemap[PhysMesh.Indices[IndexIndex]];
|
|
}
|
|
|
|
// Validate the generated triangles. If the source mesh has colinear triangles then clothing simulation will fail
|
|
const int32 NumTriangles = PhysMesh.Indices.Num() / 3;
|
|
for(int32 TriIndex = 0; TriIndex < NumTriangles; ++TriIndex)
|
|
{
|
|
FVector A = PhysMesh.Vertices[PhysMesh.Indices[TriIndex * 3 + 0]];
|
|
FVector B = PhysMesh.Vertices[PhysMesh.Indices[TriIndex * 3 + 1]];
|
|
FVector C = PhysMesh.Vertices[PhysMesh.Indices[TriIndex * 3 + 2]];
|
|
|
|
FVector TriNormal = (B - A) ^ (C - A);
|
|
if(TriNormal.SizeSquared() <= SMALL_NUMBER)
|
|
{
|
|
// This triangle is colinear
|
|
LogAndToastWarning(FText::Format(LOCTEXT("Colinear_Error", "Failed to generate clothing sim mesh due to degenerate triangle, found conincident vertices in triangle A={0} B={1} C={2}"), FText::FromString(A.ToString()), FText::FromString(B.ToString()), FText::FromString(C.ToString())));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(bPerformParamterRemap)
|
|
{
|
|
ClothingMeshUtils::FVertexParameterMapper ParameterRemapper(PhysMesh.Vertices, PhysMesh.Normals, CachedPositions, CachedNormals, CachedIndices);
|
|
|
|
DestLod.PointWeightMaps.Reset(NumSourceMasks);
|
|
|
|
for(int32 MaskIndex = 0; MaskIndex < NumSourceMasks; ++MaskIndex)
|
|
{
|
|
const FPointWeightMap& SourceMask = SourceMaskCopy[MaskIndex];
|
|
|
|
DestLod.PointWeightMaps.AddDefaulted();
|
|
FPointWeightMap& DestMask = DestLod.PointWeightMaps.Last();
|
|
|
|
DestMask.Initialize(PhysMesh.Vertices.Num());
|
|
DestMask.CurrentTarget = SourceMask.CurrentTarget;
|
|
DestMask.bEnabled = SourceMask.bEnabled;
|
|
|
|
ParameterRemapper.Map(SourceMask.Values, DestMask.Values);
|
|
}
|
|
|
|
DestAsset->ApplyParameterMasks();
|
|
}
|
|
|
|
int32 LODVertexBudget;
|
|
if (GConfig->GetInt(TEXT("ClothSettings"), TEXT("LODVertexBudget"), LODVertexBudget, GEditorIni) && LODVertexBudget > 0 && NumUniqueVerts > LODVertexBudget)
|
|
{
|
|
LogAndToastWarning(FText::Format(LOCTEXT("LODVertexBudgetWarning", "This cloth LOD has {0} more vertices than what is budgeted on this project (current={1}, budget={2})"),
|
|
NumUniqueVerts - LODVertexBudget,
|
|
NumUniqueVerts,
|
|
LODVertexBudget));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if WITH_APEX_CLOTHING
|
|
|
|
void UClothingAssetFactory::ExtractLodPhysicalData(UClothingAssetCommon* NewAsset, ClothingAsset &InApexAsset, int32 InLodIdx, FClothLODDataCommon& InLodData, TArray<FApexVertData>& OutApexVertData)
|
|
{
|
|
const NvParameterized::Interface* AssetParams = InApexAsset.getAssetNvParameterized();
|
|
|
|
FClothPhysicalMeshData& PhysData = InLodData.PhysicalMeshData;
|
|
NvParameterized::Handle GraphicalMeshArrayHandle(*AssetParams, "graphicalLods");
|
|
|
|
int32 NumGraphicalLods = 0;
|
|
GraphicalMeshArrayHandle.getArraySize(NumGraphicalLods);
|
|
|
|
uint32 PhysicalMeshIndex = INDEX_NONE;
|
|
|
|
for(int32 GraphicalMeshIndex = 0; GraphicalMeshIndex < NumGraphicalLods; ++GraphicalMeshIndex)
|
|
{
|
|
NvParameterized::Handle GraphicalMeshHandle(GraphicalMeshArrayHandle);
|
|
GraphicalMeshArrayHandle.getChildHandle(GraphicalMeshIndex, GraphicalMeshHandle);
|
|
|
|
NvParameterized::Interface* MeshInterface = nullptr;
|
|
GraphicalMeshHandle.getParamRef(MeshInterface);
|
|
|
|
NvParameterized::Handle MeshPropertyHandle(MeshInterface);
|
|
|
|
MeshInterface->getParameterHandle("lod", MeshPropertyHandle);
|
|
|
|
uint32 MeshLodIndex = INDEX_NONE;
|
|
MeshPropertyHandle.getParamU32(MeshLodIndex);
|
|
|
|
if(MeshLodIndex == InLodIdx)
|
|
{
|
|
// This is the LOD we want
|
|
MeshInterface->getParameterHandle("physicalMeshId", MeshPropertyHandle);
|
|
MeshPropertyHandle.getParamU32(PhysicalMeshIndex);
|
|
}
|
|
}
|
|
|
|
check(PhysicalMeshIndex != INDEX_NONE);
|
|
|
|
NvParameterized::Handle PhysicalMeshArrayHandle(*AssetParams, "physicalMeshes");
|
|
|
|
int32 NumPhysicalMeshes = 0;
|
|
PhysicalMeshArrayHandle.getArraySize(NumPhysicalMeshes);
|
|
|
|
check(PhysicalMeshIndex < (uint32)NumPhysicalMeshes)
|
|
|
|
{
|
|
NvParameterized::Handle PhysMeshHandle(PhysicalMeshArrayHandle);
|
|
PhysicalMeshArrayHandle.getChildHandle(PhysicalMeshIndex, PhysMeshHandle);
|
|
|
|
NvParameterized::Interface* PhysicalMeshRef = nullptr;
|
|
PhysMeshHandle.getParamRef(PhysicalMeshRef);
|
|
|
|
NvParameterized::Handle TempHandle(PhysicalMeshRef);
|
|
|
|
uint32 NumVertices = 0;
|
|
uint32 NumIndices = 0;
|
|
TempHandle.getParameter("physicalMesh.numVertices");
|
|
TempHandle.getParam(NumVertices);
|
|
|
|
TempHandle.getParameter("physicalMesh.numIndices");
|
|
TempHandle.getParam(NumIndices);
|
|
|
|
PhysData.Vertices.Empty();
|
|
PhysData.Normals.Empty();
|
|
PhysData.Vertices.AddUninitialized(NumVertices);
|
|
PhysData.Normals.AddUninitialized(NumVertices);
|
|
|
|
// Extract verts
|
|
TempHandle.getParameter("physicalMesh.vertices");
|
|
int32 VertArraySize = 0;
|
|
TempHandle.getArraySize(VertArraySize);
|
|
|
|
check(VertArraySize == NumVertices);
|
|
|
|
NvParameterized::Handle IterHandle(TempHandle);
|
|
for(int32 Idx = 0; Idx < VertArraySize; ++Idx)
|
|
{
|
|
TempHandle.getChildHandle(Idx, IterHandle);
|
|
|
|
PxVec3 PxPosition;
|
|
IterHandle.getParamVec3(PxPosition);
|
|
|
|
PhysData.Vertices[Idx] = P2UVector(PxPosition);
|
|
}
|
|
|
|
// Extract normals
|
|
TempHandle.getParameter("physicalMesh.normals");
|
|
int32 NormalArraySize = 0;
|
|
TempHandle.getArraySize(NormalArraySize);
|
|
|
|
check(NormalArraySize == NumVertices);
|
|
|
|
IterHandle = NvParameterized::Handle(TempHandle);
|
|
for(int32 Idx = 0; Idx < VertArraySize; ++Idx)
|
|
{
|
|
TempHandle.getChildHandle(Idx, IterHandle);
|
|
|
|
PxVec3 PxNormal;
|
|
IterHandle.getParamVec3(PxNormal);
|
|
|
|
PhysData.Normals[Idx] = P2UVector(PxNormal);
|
|
}
|
|
|
|
// Extract indices
|
|
TempHandle.getParameter("physicalMesh.indices");
|
|
int32 IndexArraySize = 0;
|
|
TempHandle.getArraySize(IndexArraySize);
|
|
|
|
PhysData.Indices.AddZeroed(IndexArraySize);
|
|
|
|
IterHandle = NvParameterized::Handle(TempHandle);
|
|
for(int32 Idx = 0; Idx < IndexArraySize; ++Idx)
|
|
{
|
|
TempHandle.getChildHandle(Idx, IterHandle);
|
|
|
|
PxU32 Index;
|
|
IterHandle.getParamU32(Index);
|
|
|
|
PhysData.Indices[Idx] = Index;
|
|
}
|
|
|
|
// Bone data
|
|
NvParameterized::Handle IndexHandle(TempHandle);
|
|
|
|
TempHandle.getParameter("physicalMesh.boneWeights");
|
|
IndexHandle.getParameter("physicalMesh.boneIndices");
|
|
|
|
int32 BoneWeightArraySize = 0;
|
|
TempHandle.getArraySize(BoneWeightArraySize);
|
|
|
|
if(BoneWeightArraySize > 0)
|
|
{
|
|
int32 BoneIndexArraySize = 0;
|
|
IndexHandle.getArraySize(BoneIndexArraySize);
|
|
check(BoneIndexArraySize == BoneWeightArraySize);
|
|
|
|
int32 MaxWeights = BoneWeightArraySize / PhysData.Vertices.Num();
|
|
|
|
PhysData.MaxBoneWeights = MaxWeights;
|
|
PhysData.BoneData.AddZeroed(PhysData.Vertices.Num());
|
|
|
|
// Allocate apex only data
|
|
OutApexVertData.AddDefaulted(PhysData.Vertices.Num());
|
|
|
|
NvParameterized::Handle WeightChildHandle(TempHandle);
|
|
NvParameterized::Handle IndexChildHandle(IndexHandle);
|
|
|
|
for(int32 WeightIdx = 0; WeightIdx < BoneWeightArraySize; ++WeightIdx)
|
|
{
|
|
TempHandle.getChildHandle(WeightIdx, WeightChildHandle);
|
|
IndexHandle.getChildHandle(WeightIdx, IndexChildHandle);
|
|
|
|
int32 VertIdx = WeightIdx / MaxWeights;
|
|
int32 VertWeightIdx = WeightIdx % MaxWeights;
|
|
|
|
if(VertWeightIdx < MAX_TOTAL_INFLUENCES)
|
|
{
|
|
WeightChildHandle.getParamF32(PhysData.BoneData[VertIdx].BoneWeights[VertWeightIdx]);
|
|
IndexChildHandle.getParamU16(OutApexVertData[VertIdx].BoneIndices[VertWeightIdx]);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogClothingAssetFactory, Warning, TEXT("Warning, encountered a bone influence index greater than %d, skipping this influence."), MAX_TOTAL_INFLUENCES);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extract max distances and backstops
|
|
TempHandle.getParameter("physicalMesh.constrainCoefficients");
|
|
int32 CoeffArraySize = 0;
|
|
TempHandle.getArraySize(CoeffArraySize);
|
|
|
|
check(CoeffArraySize == NumVertices);
|
|
|
|
TArray<float>& MaxDistances = PhysData.FindOrAddWeightMap(EWeightMapTargetCommon::MaxDistance).Values;
|
|
TArray<float>& BackstopDistances = PhysData.FindOrAddWeightMap(EWeightMapTargetCommon::BackstopDistance).Values;
|
|
TArray<float>& BackstopRadiuses = PhysData.FindOrAddWeightMap(EWeightMapTargetCommon::BackstopRadius).Values;
|
|
MaxDistances.Reset(CoeffArraySize);
|
|
MaxDistances.AddZeroed(CoeffArraySize);
|
|
BackstopDistances.Reset(CoeffArraySize);
|
|
BackstopDistances.AddZeroed(CoeffArraySize);
|
|
BackstopRadiuses.Reset(CoeffArraySize);
|
|
BackstopRadiuses.AddZeroed(CoeffArraySize);
|
|
|
|
IterHandle = NvParameterized::Handle(TempHandle);
|
|
NvParameterized::Handle ChildHandle(PhysicalMeshRef);
|
|
for(int32 Idx = 0; Idx < CoeffArraySize; ++Idx)
|
|
{
|
|
TempHandle.getChildHandle(Idx, IterHandle);
|
|
|
|
IterHandle.getChildHandle(PhysicalMeshRef, "maxDistance", ChildHandle);
|
|
ChildHandle.getParamF32(MaxDistances[Idx]);
|
|
|
|
IterHandle.getChildHandle(AssetParams, "collisionSphereDistance", ChildHandle);
|
|
ChildHandle.getParamF32(BackstopDistances[Idx]);
|
|
|
|
IterHandle.getChildHandle(AssetParams, "collisionSphereRadius", ChildHandle);
|
|
ChildHandle.getParamF32(BackstopRadiuses[Idx]);
|
|
|
|
BackstopDistances[Idx] += BackstopRadiuses[Idx];
|
|
}
|
|
|
|
// Calculate how many fixed verts we have
|
|
PhysData.NumFixedVerts = 0;
|
|
for(float Distance : MaxDistances)
|
|
{
|
|
if(Distance == 0.0f)
|
|
{
|
|
++PhysData.NumFixedVerts;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogClothingAssetFactory, Log, TEXT("Finished physical mesh import"));
|
|
}
|
|
}
|
|
#endif // WITH_APEX_CLOTHING
|
|
|
|
#undef LOCTEXT_NAMESPACE
|