854 lines
33 KiB
C++
854 lines
33 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PhysicsAssetRenderUtils.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "Materials/Material.h"
|
|
#include "Preferences/PhysicsAssetEditorOptions.h"
|
|
#include "PhysicsEngine/PhysicsAsset.h"
|
|
#include "PhysicsEngine/PhysicsConstraintTemplate.h"
|
|
#include "PhysicsEngine/SkeletalBodySetup.h"
|
|
#include "Chaos/Core.h"
|
|
#include "Chaos/SkinnedTriangleMesh.h"
|
|
#include "SceneView.h"
|
|
#include "SkeletalMeshTypes.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Animation/DebugSkelMeshComponent.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "Internationalization/TextKey.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "PhysicsAssetRenderUtils"
|
|
|
|
// How large to make the constraint arrows.
|
|
// The factor of 60 was found experimentally, to look reasonable in comparison with the rest of the constraint visuals.
|
|
constexpr float ConstraintArrowScale = 60.0f;
|
|
|
|
////////////////////////////////////////
|
|
// Utility Functions
|
|
|
|
FTransform GetConstraintMatrix(const USkeletalMeshComponent* const SkeletalMeshComponent, const UPhysicsConstraintTemplate* const ConstraintSetup, const EConstraintFrame::Type Frame, const int32 BoneIndex)
|
|
{
|
|
if (!ConstraintSetup || !SkeletalMeshComponent)
|
|
{
|
|
return FTransform::Identity;
|
|
}
|
|
|
|
FTransform LFrame = ConstraintSetup->DefaultInstance.GetRefFrame(Frame);
|
|
FTransform BoneTM = SkeletalMeshComponent->GetBoneTransform(BoneIndex);
|
|
|
|
return LFrame * BoneTM;
|
|
}
|
|
|
|
void DrawWireStar(FPrimitiveDrawInterface* PDI, const FTransform& Transform, float Size, const FLinearColor& Color, uint8 DepthPriority, const float Thickness)
|
|
{
|
|
const FVector Position = Transform.GetLocation();
|
|
const FVector XAxis = Transform.GetUnitAxis(EAxis::X);
|
|
const FVector YAxis = Transform.GetUnitAxis(EAxis::Y);
|
|
const FVector ZAxis = Transform.GetUnitAxis(EAxis::Z);
|
|
|
|
PDI->DrawLine(Position + Size * XAxis, Position - Size * XAxis, Color, DepthPriority, Thickness);
|
|
PDI->DrawLine(Position + Size * YAxis, Position - Size * YAxis, Color, DepthPriority, Thickness);
|
|
PDI->DrawLine(Position + Size * ZAxis, Position - Size * ZAxis, Color, DepthPriority, Thickness);
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
// struct FPhysicsAssetRenderSettings
|
|
|
|
FPhysicsAssetRenderSettings::FPhysicsAssetRenderSettings()
|
|
: CenterOfMassViewMode(EPhysicsAssetEditorCenterOfMassViewMode::None)
|
|
, CollisionViewMode(EPhysicsAssetEditorCollisionViewMode::Solid)
|
|
, ConstraintViewMode(EPhysicsAssetEditorConstraintViewMode::AllLimits)
|
|
, ConstraintViewportManipulationFlags(EConstraintTransformComponentFlags::All)
|
|
, ConstraintTransformComponentDisplayRelativeToDefaultFlags(EConstraintTransformComponentFlags::None)
|
|
, ConstraintDrawSize(1.0f)
|
|
, PhysicsBlend(1.0f)
|
|
, bHideKinematicBodies(false)
|
|
, bHideSimulatedBodies(false)
|
|
, bHideBodyMass(false)
|
|
, bRenderOnlySelectedConstraints(false)
|
|
, bShowCOM_DEPRECATED(false)
|
|
, bShowConstraintsAsPoints(false)
|
|
, bHighlightOverlapingBodies(false)
|
|
, bDrawViolatedLimits(false)
|
|
, bHideCenterOfMassForKinematicBodies(true)
|
|
, BoneUnselectedColor(170, 155, 225)
|
|
, NoCollisionColor(200, 200, 200)
|
|
, COMRenderColor(255, 255, 100)
|
|
, COMRenderSize(2.0f)
|
|
, COMRenderLineThickness(0.2f)
|
|
, COMRenderMassTextOffsetScreenspace(8.0f)
|
|
, InfluenceLineLength(2.0f)
|
|
, BoneUnselectedMaterial(nullptr)
|
|
, BoneNoCollisionMaterial(nullptr)
|
|
{}
|
|
|
|
void FPhysicsAssetRenderSettings::InitPhysicsAssetRenderSettings(UMaterialInterface* InBoneUnselectedMaterial, UMaterialInterface* InBoneNoCollisionMaterial)
|
|
{
|
|
BoneUnselectedMaterial = InBoneUnselectedMaterial;
|
|
check(BoneUnselectedMaterial);
|
|
|
|
BoneNoCollisionMaterial = InBoneNoCollisionMaterial;
|
|
check(BoneNoCollisionMaterial);
|
|
}
|
|
|
|
bool FPhysicsAssetRenderSettings::IsBodyHidden(const int32 BodyIndex) const
|
|
{
|
|
return HiddenBodies.Contains(BodyIndex);
|
|
}
|
|
|
|
bool FPhysicsAssetRenderSettings::IsConstraintHidden(const int32 ConstraintIndex) const
|
|
{
|
|
return HiddenConstraints.Contains(ConstraintIndex);
|
|
}
|
|
|
|
bool FPhysicsAssetRenderSettings::AreAnyBodiesHidden() const
|
|
{
|
|
return HiddenBodies.Num() > 0;
|
|
}
|
|
|
|
bool FPhysicsAssetRenderSettings::AreAnyConstraintsHidden() const
|
|
{
|
|
return HiddenConstraints.Num() > 0;
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::HideBody(const int32 BodyIndex)
|
|
{
|
|
if (!HiddenBodies.Contains(BodyIndex))
|
|
{
|
|
HiddenBodies.Add(BodyIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ShowBody(const int32 BodyIndex)
|
|
{
|
|
if (HiddenBodies.Contains(BodyIndex))
|
|
{
|
|
HiddenBodies.RemoveSwap(BodyIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ShowAllBodies()
|
|
{
|
|
HiddenBodies.Reset();
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ShowAllConstraints()
|
|
{
|
|
HiddenConstraints.Reset();
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ShowAll()
|
|
{
|
|
ShowAllBodies();
|
|
ShowAllConstraints();
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::HideAllBodies(const UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
HiddenBodies.Reset();
|
|
|
|
if (PhysicsAsset != nullptr)
|
|
{
|
|
for (int32 i = 0; i < PhysicsAsset->SkeletalBodySetups.Num(); ++i)
|
|
{
|
|
HiddenBodies.Add(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::HideAllConstraints(const UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
HiddenConstraints.Reset();
|
|
|
|
if (PhysicsAsset != nullptr)
|
|
{
|
|
for (int32 i = 0; i < PhysicsAsset->ConstraintSetup.Num(); ++i)
|
|
{
|
|
HiddenConstraints.Add(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::HideAll(const UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
HideAllBodies(PhysicsAsset);
|
|
HideAllConstraints(PhysicsAsset);
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ToggleShowBody(const int32 BodyIndex)
|
|
{
|
|
if (IsBodyHidden(BodyIndex))
|
|
{
|
|
ShowBody(BodyIndex);
|
|
}
|
|
else
|
|
{
|
|
HideBody(BodyIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ToggleShowConstraint(const int32 ConstraintIndex)
|
|
{
|
|
if (IsConstraintHidden(ConstraintIndex))
|
|
{
|
|
ShowConstraint(ConstraintIndex);
|
|
}
|
|
else
|
|
{
|
|
HideConstraint(ConstraintIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ToggleShowAllBodies(const UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (AreAnyBodiesHidden())
|
|
{
|
|
ShowAllBodies();
|
|
}
|
|
else
|
|
{
|
|
HideAllBodies(PhysicsAsset);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ToggleShowAllConstraints(const UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (AreAnyConstraintsHidden())
|
|
{
|
|
ShowAllConstraints();
|
|
}
|
|
else
|
|
{
|
|
HideAllConstraints(PhysicsAsset);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::HideConstraint(const int32 ConstraintIndex)
|
|
{
|
|
if (!HiddenConstraints.Contains(ConstraintIndex))
|
|
{
|
|
HiddenConstraints.Add(ConstraintIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ShowConstraint(const int32 ConstraintIndex)
|
|
{
|
|
if (HiddenConstraints.Contains(ConstraintIndex))
|
|
{
|
|
HiddenConstraints.RemoveSwap(ConstraintIndex);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::SetHiddenBodies(const TArray<int32>& InHiddenBodies)
|
|
{
|
|
HiddenBodies.Reset();
|
|
HiddenBodies.Append(InHiddenBodies);
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::SetHiddenConstraints(const TArray<int32>& InHiddenConstraints)
|
|
{
|
|
HiddenConstraints.Reset();
|
|
HiddenConstraints.Append(InHiddenConstraints);
|
|
}
|
|
|
|
EConstraintTransformComponentFlags FPhysicsAssetRenderSettings::GetConstraintViewportManipulationFlags() const
|
|
{
|
|
return ConstraintViewportManipulationFlags;
|
|
}
|
|
|
|
bool FPhysicsAssetRenderSettings::IsDisplayingConstraintTransformComponentRelativeToDefault(const EConstraintTransformComponentFlags ComponentFlags) const
|
|
{
|
|
return EnumHasAllFlags(ConstraintTransformComponentDisplayRelativeToDefaultFlags, ComponentFlags);
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::SetDisplayConstraintTransformComponentRelativeToDefault(const EConstraintTransformComponentFlags ComponentFlags, const bool bShouldDisplayRelativeToDefault)
|
|
{
|
|
ConstraintTransformComponentDisplayRelativeToDefaultFlags = (bShouldDisplayRelativeToDefault) ? (ConstraintTransformComponentDisplayRelativeToDefaultFlags | ComponentFlags) : (ConstraintTransformComponentDisplayRelativeToDefaultFlags & ~ComponentFlags);
|
|
}
|
|
|
|
void FPhysicsAssetRenderSettings::ResetEditorViewportOptions()
|
|
{
|
|
const FPhysicsAssetRenderSettings DefaultObject = FPhysicsAssetRenderSettings();
|
|
|
|
CenterOfMassViewMode = DefaultObject.CenterOfMassViewMode;
|
|
CollisionViewMode = DefaultObject.CollisionViewMode;
|
|
COMRenderSize = DefaultObject.COMRenderSize;
|
|
ConstraintViewMode = DefaultObject.ConstraintViewMode;
|
|
ConstraintDrawSize = DefaultObject.ConstraintDrawSize;
|
|
PhysicsBlend = DefaultObject.PhysicsBlend;
|
|
bHideKinematicBodies = DefaultObject.bHideKinematicBodies;
|
|
bHideSimulatedBodies = DefaultObject.bHideSimulatedBodies;
|
|
bHideBodyMass = DefaultObject.bHideBodyMass;
|
|
bRenderOnlySelectedConstraints = DefaultObject.bRenderOnlySelectedConstraints;
|
|
bShowConstraintsAsPoints = DefaultObject.bShowConstraintsAsPoints;
|
|
bDrawViolatedLimits = DefaultObject.bDrawViolatedLimits;
|
|
bHideCenterOfMassForKinematicBodies = DefaultObject.bHideCenterOfMassForKinematicBodies;
|
|
}
|
|
|
|
namespace PhysicsAssetRender
|
|
{
|
|
void DebugDraw(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
// Draw Bodies.
|
|
{
|
|
auto TransformFn = [](const UPhysicsAsset* PhysicsAsset, const FTransform& BoneTM, const int32 BodyIndex, const EAggCollisionShape::Type PrimType, const int32 PrimIndex, const float Scale) { return GetPrimitiveTransform(PhysicsAsset, BoneTM, BodyIndex, PrimType, PrimIndex, Scale); };
|
|
auto ColorFn = [](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings) { return GetPrimitiveColor(BodyIndex, PrimitiveType, PrimitiveIndex, Settings); };
|
|
auto MaterialFn = [](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings) { return GetPrimitiveMaterial(BodyIndex, PrimitiveType, PrimitiveIndex, Settings); };
|
|
auto HitProxyFn = [](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex) { return nullptr; };
|
|
|
|
DebugDrawBodies(SkeletalMeshComponent, PhysicsAsset, PDI, ColorFn, MaterialFn, TransformFn, HitProxyFn);
|
|
}
|
|
|
|
// Draw Constraints.
|
|
{
|
|
auto HitProxyFunctor = [](const int32) { return nullptr; };
|
|
DebugDrawConstraints(SkeletalMeshComponent, PhysicsAsset, PDI, IsSelectedFn(), false, HitProxyFunctor);
|
|
}
|
|
}
|
|
|
|
// Contains info needed for drawing, in a form that can be sorted by distance.
|
|
struct DrawElement
|
|
{
|
|
DrawElement(
|
|
FTransform InTM,
|
|
HHitProxy* InHitProxy,
|
|
float InScale,
|
|
const FSceneView* InView,
|
|
const FKShapeElem* InShapeElem,
|
|
const FName& InBoneName
|
|
)
|
|
: TM(InTM), HitProxy(InHitProxy), Scale(InScale), ShapeElem(InShapeElem)
|
|
#ifdef UE_BUILD_DEBUG
|
|
, BoneName(InBoneName)
|
|
#endif
|
|
{
|
|
// The TM position is at the center of the objects, so can be used directly for sorting.
|
|
// Sorting by distance (rather than along the view direction) reduces flickering when
|
|
// the camera is rotated without moving it.
|
|
SortingMetric = static_cast<float>((TM.GetTranslation() - InView->ViewLocation).SquaredLength()); // Sorts by Distance
|
|
}
|
|
|
|
UMaterialInterface* Material = 0;
|
|
FColor Color = FColor(ForceInitToZero);
|
|
FTransform TM;
|
|
HHitProxy* HitProxy = nullptr;
|
|
float Scale;
|
|
const FKShapeElem* ShapeElem = nullptr;
|
|
float SortingMetric;
|
|
#ifdef UE_BUILD_DEBUG
|
|
FName BoneName;
|
|
#endif
|
|
};
|
|
|
|
template< typename TElementContainer > void CollectDrawElements(TArray<DrawElement>& DrawElements, const EAggCollisionShape::Type PrimitiveType, const TElementContainer& ElementContainer, const FTransform& BoneTM, const float Scale, const uint32 BodyIndex, const FPhysicsAssetRenderSettings* const RenderSettings, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI, GetPrimitiveRef< FColor > GetPrimitiveColor, GetPrimitiveRef< UMaterialInterface* > GetPrimitiveMaterial, GetPrimitiveTransformRef GetPrimitiveTransform, CreateBodyHitProxyFn CreateHitProxy)
|
|
{
|
|
if (PhysicsAsset && RenderSettings && PDI && PhysicsAsset->SkeletalBodySetups.IsValidIndex(BodyIndex))
|
|
{
|
|
for (int32 ElementIndex = 0; ElementIndex < ElementContainer.Num(); ++ElementIndex)
|
|
{
|
|
const FName BoneName = PhysicsAsset->SkeletalBodySetups[BodyIndex]->BoneName;
|
|
|
|
DrawElement DE(
|
|
GetPrimitiveTransform(PhysicsAsset, BoneTM, BodyIndex, PrimitiveType, ElementIndex, Scale),
|
|
CreateHitProxy(BodyIndex, PrimitiveType, ElementIndex),
|
|
Scale, PDI->View, &ElementContainer[ElementIndex], BoneName
|
|
);
|
|
|
|
if (RenderSettings->CollisionViewMode == EPhysicsAssetEditorCollisionViewMode::Solid || RenderSettings->CollisionViewMode == EPhysicsAssetEditorCollisionViewMode::SolidWireframe)
|
|
{
|
|
DE.Material = GetPrimitiveMaterial(BodyIndex, PrimitiveType, ElementIndex, *RenderSettings);
|
|
}
|
|
if (RenderSettings->CollisionViewMode == EPhysicsAssetEditorCollisionViewMode::SolidWireframe || RenderSettings->CollisionViewMode == EPhysicsAssetEditorCollisionViewMode::Wireframe)
|
|
{
|
|
DE.Color = GetPrimitiveColor(BodyIndex, PrimitiveType, ElementIndex, *RenderSettings);
|
|
}
|
|
if (DE.Material || DE.Color.A)
|
|
{
|
|
DrawElements.Add(DE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebugDrawBodies(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI, GetPrimitiveRef< FColor > GetPrimitiveColor, GetPrimitiveRef< UMaterialInterface* > GetPrimitiveMaterial, GetPrimitiveTransformRef GetPrimitiveTransform, CreateBodyHitProxyFn CreateHitProxy)
|
|
{
|
|
if (!SkeletalMeshComponent || !PhysicsAsset || !PhysicsAsset->PreviewSkeletalMesh)
|
|
{
|
|
// Nothing to draw without an asset, this can happen if the preview scene has no skeletal mesh
|
|
return;
|
|
}
|
|
|
|
const FPhysicsAssetRenderSettings* const RenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset);
|
|
|
|
if (!RenderSettings)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Draw bodies
|
|
for (int32 i = 0; i < PhysicsAsset->SkeletalBodySetups.Num(); ++i)
|
|
{
|
|
if (!ensure(PhysicsAsset->SkeletalBodySetups[i]))
|
|
{
|
|
continue;
|
|
}
|
|
if ((PhysicsAsset->SkeletalBodySetups[i]->PhysicsType == EPhysicsType::PhysType_Kinematic &&
|
|
RenderSettings->bHideKinematicBodies) ||
|
|
(PhysicsAsset->SkeletalBodySetups[i]->PhysicsType == EPhysicsType::PhysType_Simulated &&
|
|
RenderSettings->bHideSimulatedBodies)
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
if (RenderSettings->IsBodyHidden(i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int32 BoneIndex = SkeletalMeshComponent->GetBoneIndex(PhysicsAsset->SkeletalBodySetups[i]->BoneName);
|
|
|
|
// If we found a bone for it, draw the collision.
|
|
// The logic is as follows; always render in the ViewMode requested when not in hit mode - but if we are in hit mode and the right editing mode, render as solid
|
|
if (BoneIndex != INDEX_NONE)
|
|
{
|
|
FTransform BoneTM = SkeletalMeshComponent->GetBoneTransform(BoneIndex);
|
|
const float Scale = static_cast<float>(BoneTM.GetScale3D().GetAbsMax());
|
|
BoneTM.RemoveScaling();
|
|
|
|
FKAggregateGeom* const AggGeom = &PhysicsAsset->SkeletalBodySetups[i]->AggGeom;
|
|
|
|
TArray<DrawElement> DrawElements;
|
|
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::Sphere, AggGeom->SphereElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::Box, AggGeom->BoxElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::Sphyl, AggGeom->SphylElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::Convex, AggGeom->ConvexElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::TaperedCapsule, AggGeom->TaperedCapsuleElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::LevelSet, AggGeom->LevelSetElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::SkinnedLevelSet, AggGeom->SkinnedLevelSetElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::MLLevelSet, AggGeom->MLLevelSetElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
CollectDrawElements(DrawElements, EAggCollisionShape::SkinnedTriangleMesh, AggGeom->SkinnedTriangleMeshElems, BoneTM, Scale, i, RenderSettings, PhysicsAsset, PDI, GetPrimitiveColor, GetPrimitiveMaterial, GetPrimitiveTransform, CreateHitProxy);
|
|
|
|
// Sort elements
|
|
DrawElements.Sort([](const DrawElement& DE1, const DrawElement& DE2) {
|
|
return DE1.SortingMetric > DE2.SortingMetric;
|
|
});
|
|
|
|
// Render sorted elements.
|
|
for (const DrawElement& DE : DrawElements)
|
|
{
|
|
PDI->SetHitProxy(DE.HitProxy);
|
|
if (DE.Material)
|
|
{
|
|
DE.ShapeElem->DrawElemSolid(PDI, DE.TM, DE.Scale, DE.Material->GetRenderProxy());
|
|
}
|
|
if (DE.Color.A)
|
|
{
|
|
DE.ShapeElem->DrawElemWire(PDI, DE.TM, DE.Scale, DE.Color);
|
|
}
|
|
}
|
|
|
|
PDI->SetHitProxy(NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebugDrawCenterOfMass(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI, TFunctionRef< FVector(const uint32) > GetCoMPosition, TFunctionRef< bool(const uint32) > IsSelected, TFunctionRef< bool(const uint32) > IsHidden, CreateCoMHitProxyFn CreateHitProxy)
|
|
{
|
|
static const float SelectedItemRenderSizeMultiplier = 1.5f;
|
|
|
|
const FPhysicsAssetRenderSettings* const RenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset);
|
|
|
|
if (RenderSettings && (RenderSettings->CenterOfMassViewMode != EPhysicsAssetEditorCenterOfMassViewMode::None))
|
|
{
|
|
const bool bDrawSelectedOnly = RenderSettings->CenterOfMassViewMode == EPhysicsAssetEditorCenterOfMassViewMode::Selected;
|
|
|
|
for (int32 BodyIndex = 0, BodyCount = SkeletalMeshComponent->Bodies.Num(); BodyIndex < BodyCount; ++BodyIndex)
|
|
{
|
|
const bool bIsSelected = IsSelected(BodyIndex);
|
|
|
|
if (!IsHidden(BodyIndex) && (!bDrawSelectedOnly || bIsSelected))
|
|
{
|
|
const FBodyInstance* const BodyInstance = SkeletalMeshComponent->Bodies[BodyIndex];
|
|
|
|
if (BodyInstance && BodyInstance->IsValidBodyInstance() && !(RenderSettings->bHideCenterOfMassForKinematicBodies && IsBodyKinematic(PhysicsAsset, BodyIndex)))
|
|
{
|
|
float COMRenderSize = RenderSettings->COMRenderSize;
|
|
float COMRenderLineThickness = RenderSettings->COMRenderLineThickness;
|
|
|
|
if (bIsSelected)
|
|
{
|
|
COMRenderSize *= SelectedItemRenderSizeMultiplier;
|
|
COMRenderLineThickness *= SelectedItemRenderSizeMultiplier;
|
|
}
|
|
|
|
FTransform CoMMarkerTM(GetCoMPosition(BodyIndex));
|
|
|
|
if (BodyInstance->GetBodySetup())
|
|
{
|
|
const FName BoneName = BodyInstance->GetBodySetup()->BoneName;
|
|
CoMMarkerTM.SetRotation(SkeletalMeshComponent->GetBoneTransform(BoneName).GetRotation());
|
|
}
|
|
|
|
PDI->SetHitProxy(CreateHitProxy(BodyIndex));
|
|
DrawWireStar(PDI, CoMMarkerTM, COMRenderSize, RenderSettings->COMRenderColor, SDPG_Foreground, COMRenderLineThickness);
|
|
PDI->SetHitProxy(NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebugDrawConstraints(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI, TFunction< bool(const uint32) > IsSelected, const bool bRunningSimulation, CreateConstraintHitProxyFn CreateHitProxy)
|
|
{
|
|
check(SkeletalMeshComponent);
|
|
check(PhysicsAsset);
|
|
|
|
const FPhysicsAssetRenderSettings* const RenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset);
|
|
|
|
if (!RenderSettings)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (RenderSettings->ConstraintViewMode != EPhysicsAssetEditorConstraintViewMode::None)
|
|
{
|
|
const bool bIsSelectedStateAvailable = (IsSelected != nullptr);
|
|
const bool bRenderOnlySelected = bIsSelectedStateAvailable && RenderSettings->bRenderOnlySelectedConstraints;
|
|
|
|
for (int32 ConstraintIndex = 0; ConstraintIndex < PhysicsAsset->ConstraintSetup.Num(); ++ConstraintIndex)
|
|
{
|
|
const bool bConstraintSelected = bIsSelectedStateAvailable && IsSelected(ConstraintIndex);
|
|
|
|
if ((!bRenderOnlySelected || bConstraintSelected) &&
|
|
!RenderSettings->IsConstraintHidden(ConstraintIndex))
|
|
{
|
|
const bool bDrawLimits = (RenderSettings->ConstraintViewMode == EPhysicsAssetEditorConstraintViewMode::AllLimits) || bConstraintSelected;
|
|
const bool bDrawSelected = !bRunningSimulation && bConstraintSelected;
|
|
|
|
PDI->SetHitProxy(CreateHitProxy(ConstraintIndex));
|
|
|
|
UPhysicsConstraintTemplate* const ConstraintSetup = PhysicsAsset->ConstraintSetup[ConstraintIndex];
|
|
check(ConstraintSetup);
|
|
if (ConstraintSetup)
|
|
{
|
|
const int32 BoneIndex1 = SkeletalMeshComponent->GetBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone1);
|
|
const int32 BoneIndex2 = SkeletalMeshComponent->GetBoneIndex(ConstraintSetup->DefaultInstance.ConstraintBone2);
|
|
|
|
// if bone doesn't exist, do not draw it. It crashes in random points when we try to manipulate.
|
|
if ((BoneIndex1 != INDEX_NONE) && (BoneIndex2 != INDEX_NONE))
|
|
{
|
|
const float DrawScale = ConstraintArrowScale * RenderSettings->ConstraintDrawSize;
|
|
FTransform Con1Frame = GetConstraintMatrix(SkeletalMeshComponent, ConstraintSetup, EConstraintFrame::Frame1, BoneIndex1);
|
|
FTransform Con2Frame = GetConstraintMatrix(SkeletalMeshComponent, ConstraintSetup, EConstraintFrame::Frame2, BoneIndex2);
|
|
|
|
// Remove scaling from constraint frame transforms so that constraint limit cones etc are all drawn at the same scale.
|
|
Con1Frame.RemoveScaling();
|
|
Con2Frame.RemoveScaling();
|
|
|
|
ConstraintSetup->DefaultInstance.DrawConstraint(PDI, RenderSettings->ConstraintDrawSize, DrawScale, bDrawLimits, bDrawSelected, Con1Frame, Con2Frame, RenderSettings->bShowConstraintsAsPoints, RenderSettings->bDrawViolatedLimits);
|
|
|
|
}
|
|
}
|
|
|
|
PDI->SetHitProxy(NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FTransform GetPrimitiveTransform(const UPhysicsAsset* PhysicsAsset, const FTransform& BoneTM, const int32 BodyIndex, const EAggCollisionShape::Type PrimType, const int32 PrimIndex, const float Scale)
|
|
{
|
|
UBodySetup* SharedBodySetup = PhysicsAsset->SkeletalBodySetups[BodyIndex];
|
|
FVector Scale3D(Scale);
|
|
|
|
FTransform PrimTM = FTransform::Identity;
|
|
|
|
if (PrimType == EAggCollisionShape::Sphere)
|
|
{
|
|
PrimTM = SharedBodySetup->AggGeom.SphereElems[PrimIndex].GetTransform();
|
|
}
|
|
else if (PrimType == EAggCollisionShape::Box)
|
|
{
|
|
PrimTM = SharedBodySetup->AggGeom.BoxElems[PrimIndex].GetTransform();
|
|
}
|
|
else if (PrimType == EAggCollisionShape::Sphyl)
|
|
{
|
|
PrimTM = SharedBodySetup->AggGeom.SphylElems[PrimIndex].GetTransform();
|
|
}
|
|
else if (PrimType == EAggCollisionShape::Convex)
|
|
{
|
|
PrimTM = SharedBodySetup->AggGeom.ConvexElems[PrimIndex].GetTransform();
|
|
}
|
|
else if (PrimType == EAggCollisionShape::TaperedCapsule)
|
|
{
|
|
PrimTM = SharedBodySetup->AggGeom.TaperedCapsuleElems[PrimIndex].GetTransform();
|
|
}
|
|
else
|
|
{
|
|
// Should never reach here
|
|
check(0);
|
|
}
|
|
|
|
PrimTM.ScaleTranslation(Scale3D);
|
|
return PrimTM * BoneTM;
|
|
}
|
|
|
|
FColor GetPrimitiveColor(const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings)
|
|
{
|
|
return (PrimitiveType == EAggCollisionShape::TaperedCapsule) ? Settings.NoCollisionColor : Settings.BoneUnselectedColor;
|
|
}
|
|
|
|
UMaterialInterface* GetPrimitiveMaterial(const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings)
|
|
{
|
|
return (PrimitiveType == EAggCollisionShape::TaperedCapsule) ? Settings.BoneNoCollisionMaterial : Settings.BoneUnselectedMaterial;
|
|
}
|
|
}
|
|
|
|
UPhysicsAssetRenderUtilities::UPhysicsAssetRenderUtilities()
|
|
: PhysicsAssetRenderInterface(nullptr)
|
|
{}
|
|
|
|
UPhysicsAssetRenderUtilities::~UPhysicsAssetRenderUtilities()
|
|
{
|
|
delete PhysicsAssetRenderInterface;
|
|
}
|
|
|
|
|
|
void UPhysicsAssetRenderUtilities::Initialise()
|
|
{
|
|
if (UPhysicsAssetRenderUtilities* PhysicsAssetRenderUtilities = GetMutableDefault<UPhysicsAssetRenderUtilities>())
|
|
{
|
|
PhysicsAssetRenderUtilities->InitialiseImpl();
|
|
|
|
if (!PhysicsAssetRenderUtilities->PhysicsAssetRenderInterface)
|
|
{
|
|
PhysicsAssetRenderUtilities->PhysicsAssetRenderInterface = new FPhysicsAssetRenderInterface;
|
|
}
|
|
|
|
IModularFeatures::Get().RegisterModularFeature(IPhysicsAssetRenderInterface::GetModularFeatureName(),
|
|
PhysicsAssetRenderUtilities->PhysicsAssetRenderInterface);
|
|
}
|
|
}
|
|
|
|
void UPhysicsAssetRenderUtilities::OnAssetRenamed(FAssetData const& AssetInfo, const FString& InOldPhysicsAssetPathName)
|
|
{
|
|
FPhysicsAssetRenderSettings Temp;
|
|
|
|
const uint32 OldPhysicsAssetPathNameHash = GetPathNameHash(InOldPhysicsAssetPathName);
|
|
|
|
if (IdToSettingsMap.RemoveAndCopyValue(OldPhysicsAssetPathNameHash, Temp))
|
|
{
|
|
const uint32 PhysicsAssetPathNameHash = GetPathNameHash(AssetInfo.GetObjectPathString());
|
|
IdToSettingsMap.Add(PhysicsAssetPathNameHash, Temp);
|
|
SaveConfig();
|
|
}
|
|
}
|
|
|
|
void UPhysicsAssetRenderUtilities::OnAssetRemoved(UObject* Object)
|
|
{
|
|
if (Object)
|
|
{
|
|
const uint32 PhysicsAssetPathNameHash = GetPathNameHash(Object->GetPathName());
|
|
IdToSettingsMap.Remove(PhysicsAssetPathNameHash);
|
|
}
|
|
}
|
|
|
|
FPhysicsAssetRenderSettings* UPhysicsAssetRenderUtilities::GetSettings(const UPhysicsAsset* InPhysicsAsset)
|
|
{
|
|
const uint32 PhysicsAssetPathNameHash = GetPathNameHash(InPhysicsAsset);
|
|
|
|
if (PhysicsAssetPathNameHash)
|
|
{
|
|
return GetSettings(PhysicsAssetPathNameHash);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FPhysicsAssetRenderSettings* UPhysicsAssetRenderUtilities::GetSettings(const FString& InPhysicsAssetPathName)
|
|
{
|
|
const uint32 PhysicsAssetPathNameHash = GetPathNameHash(InPhysicsAssetPathName);
|
|
|
|
if (PhysicsAssetPathNameHash)
|
|
{
|
|
return GetSettings(PhysicsAssetPathNameHash);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FPhysicsAssetRenderSettings* UPhysicsAssetRenderUtilities::GetSettings(const uint32 InPhysicsAssetPathNameHash)
|
|
{
|
|
if (UPhysicsAssetRenderUtilities* PhysicsAssetRenderUtilities = GetMutableDefault<UPhysicsAssetRenderUtilities>())
|
|
{
|
|
return PhysicsAssetRenderUtilities->GetSettingsImpl(InPhysicsAssetPathNameHash);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void UPhysicsAssetRenderUtilities::InitialiseImpl()
|
|
{
|
|
LoadConfig();
|
|
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UPhysicsAssetRenderUtilities::OnAssetRenamed);
|
|
AssetRegistryModule.Get().OnInMemoryAssetDeleted().AddUObject(this, &UPhysicsAssetRenderUtilities::OnAssetRemoved);
|
|
|
|
BoneUnselectedMaterial = LoadObject<UMaterialInterface>(NULL, TEXT("/Engine/EditorMaterials/PhAT_UnselectedMaterial.PhAT_UnselectedMaterial"), NULL, LOAD_None, NULL);
|
|
BoneNoCollisionMaterial = LoadObject<UMaterialInterface>(NULL, TEXT("/Engine/EditorMaterials/PhAT_NoCollisionMaterial.PhAT_NoCollisionMaterial"), NULL, LOAD_None, NULL);
|
|
}
|
|
|
|
FPhysicsAssetRenderSettings* UPhysicsAssetRenderUtilities::GetSettingsImpl(const uint32 InPhysicsAssetPathNameHash)
|
|
{
|
|
FPhysicsAssetRenderSettings* Settings = IdToSettingsMap.Find(InPhysicsAssetPathNameHash);
|
|
|
|
if (!Settings)
|
|
{
|
|
FPhysicsAssetRenderSettings& NewSettings = IdToSettingsMap.Add(InPhysicsAssetPathNameHash, FPhysicsAssetRenderSettings());
|
|
NewSettings.InitPhysicsAssetRenderSettings(BoneUnselectedMaterial, BoneNoCollisionMaterial);
|
|
Settings = &NewSettings;
|
|
}
|
|
|
|
return Settings;
|
|
}
|
|
|
|
uint32 UPhysicsAssetRenderUtilities::GetPathNameHash(const UPhysicsAsset* InPhysicsAsset)
|
|
{
|
|
if (InPhysicsAsset)
|
|
{
|
|
return GetPathNameHash(InPhysicsAsset->GetPathName());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint32 UPhysicsAssetRenderUtilities::GetPathNameHash(const FString& InPhysicsAssetPathName)
|
|
{
|
|
return TextKeyUtil::HashString(InPhysicsAssetPathName);
|
|
}
|
|
|
|
// class FPhysicsAssetRenderInterface
|
|
void FPhysicsAssetRenderInterface::DebugDraw(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
PhysicsAssetRender::DebugDraw(SkeletalMeshComponent, PhysicsAsset, PDI);
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::DebugDrawBodies(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI, const FColor& PrimitiveColorOverride)
|
|
{
|
|
auto TransformFn = [](const UPhysicsAsset* PhysicsAsset, const FTransform& BoneTM, const int32 BodyIndex, const EAggCollisionShape::Type PrimType, const int32 PrimIndex, const float Scale) { return PhysicsAssetRender::GetPrimitiveTransform(PhysicsAsset, BoneTM, BodyIndex, PrimType, PrimIndex, Scale); };
|
|
auto HitProxyFn = [](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex) { return nullptr; };
|
|
|
|
auto MaterialFn = [](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings)
|
|
{
|
|
return PhysicsAssetRender::GetPrimitiveMaterial(BodyIndex, PrimitiveType, PrimitiveIndex, Settings);
|
|
};
|
|
|
|
auto ColorFn = [&PrimitiveColorOverride](const int32 BodyIndex, const EAggCollisionShape::Type PrimitiveType, const int32 PrimitiveIndex, const FPhysicsAssetRenderSettings& Settings)
|
|
{
|
|
if (PrimitiveColorOverride.A > (1.0f - KINDA_SMALL_NUMBER))
|
|
{
|
|
return PrimitiveColorOverride;
|
|
}
|
|
|
|
const FLinearColor LinearOverride = PrimitiveColorOverride.ReinterpretAsLinear();
|
|
return FMath::Lerp(PhysicsAssetRender::GetPrimitiveColor(BodyIndex, PrimitiveType, PrimitiveIndex, Settings).ReinterpretAsLinear(), LinearOverride, LinearOverride.A).QuantizeRound();
|
|
};
|
|
|
|
PhysicsAssetRender::DebugDrawBodies(SkeletalMeshComponent, PhysicsAsset, PDI, ColorFn, MaterialFn, TransformFn, HitProxyFn);
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::DebugDrawConstraints(USkeletalMeshComponent* const SkeletalMeshComponent, UPhysicsAsset* const PhysicsAsset, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
auto HitProxyFn = [](const int32) { return nullptr; };
|
|
PhysicsAssetRender::DebugDrawConstraints(SkeletalMeshComponent, PhysicsAsset, PDI, PhysicsAssetRender::IsSelectedFn(), false, HitProxyFn);
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::SaveConfig()
|
|
{
|
|
if (UPhysicsAssetRenderUtilities* PhysicsAssetRenderUtilities = GetMutableDefault<UPhysicsAssetRenderUtilities>())
|
|
{
|
|
PhysicsAssetRenderUtilities->SaveConfig();
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::ToggleShowAllBodies(class UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
PhysicsAssetRenderSettings->ToggleShowAllBodies(PhysicsAsset);
|
|
}
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::ToggleShowAllConstraints(class UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
PhysicsAssetRenderSettings->ToggleShowAllConstraints(PhysicsAsset);
|
|
}
|
|
}
|
|
|
|
bool FPhysicsAssetRenderInterface::AreAnyBodiesHidden(class UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
return PhysicsAssetRenderSettings->AreAnyBodiesHidden();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FPhysicsAssetRenderInterface::AreAnyConstraintsHidden(class UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
return PhysicsAssetRenderSettings->AreAnyConstraintsHidden();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
EConstraintTransformComponentFlags FPhysicsAssetRenderInterface::GetConstraintViewportManipulationFlags(class UPhysicsAsset* const PhysicsAsset)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
return PhysicsAssetRenderSettings->GetConstraintViewportManipulationFlags();
|
|
}
|
|
|
|
return EConstraintTransformComponentFlags::None;
|
|
}
|
|
|
|
bool FPhysicsAssetRenderInterface::IsDisplayingConstraintTransformComponentRelativeToDefault(class UPhysicsAsset* const PhysicsAsset, const EConstraintTransformComponentFlags ComponentFlags)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
return PhysicsAssetRenderSettings->IsDisplayingConstraintTransformComponentRelativeToDefault(ComponentFlags);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FPhysicsAssetRenderInterface::SetDisplayConstraintTransformComponentRelativeToDefault(class UPhysicsAsset* const PhysicsAsset, const EConstraintTransformComponentFlags ComponentFlags, const bool bShouldDisplayRelativeToDefault)
|
|
{
|
|
if (FPhysicsAssetRenderSettings* PhysicsAssetRenderSettings = UPhysicsAssetRenderUtilities::GetSettings(PhysicsAsset))
|
|
{
|
|
PhysicsAssetRenderSettings->SetDisplayConstraintTransformComponentRelativeToDefault(ComponentFlags, bShouldDisplayRelativeToDefault);
|
|
}
|
|
}
|
|
|
|
bool IsBodyKinematic(const UPhysicsAsset* const PhysicsAsset, const int32 BodyIndex)
|
|
{
|
|
if (PhysicsAsset && PhysicsAsset->SkeletalBodySetups.IsValidIndex(BodyIndex))
|
|
{
|
|
return PhysicsAsset->SkeletalBodySetups[BodyIndex]->PhysicsType == EPhysicsType::PhysType_Kinematic;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|