2026-05-11 03:50:23 +08:00
|
|
|
// Copyright 2019-2026 pafuhana1213. All Rights Reserved.
|
2025-01-31 16:21:15 +08:00
|
|
|
|
|
|
|
|
#include "KawaiiPhysicsEditMode.h"
|
|
|
|
|
#include "CanvasItem.h"
|
|
|
|
|
#include "CanvasTypes.h"
|
|
|
|
|
#include "EditorModeManager.h"
|
|
|
|
|
#include "EditorViewportClient.h"
|
|
|
|
|
#include "IPersonaPreviewScene.h"
|
|
|
|
|
#include "KawaiiPhysics.h"
|
2026-05-11 03:50:23 +08:00
|
|
|
#include "ExternalForces/KawaiiPhysicsExternalForce.h"
|
2025-01-31 16:21:15 +08:00
|
|
|
#include "KawaiiPhysicsLimitsDataAsset.h"
|
|
|
|
|
#include "SceneManagement.h"
|
|
|
|
|
#include "Animation/DebugSkelMeshComponent.h"
|
|
|
|
|
#include "Materials/MaterialInstanceDynamic.h"
|
|
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 6
|
|
|
|
|
#include "SceneView.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
#define LOCTEXT_NAMESPACE "KawaiiPhysicsEditMode"
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogKawaiiPhysics);
|
|
|
|
|
|
|
|
|
|
struct HKawaiiPhysicsHitProxy : HHitProxy
|
|
|
|
|
{
|
|
|
|
|
DECLARE_HIT_PROXY()
|
|
|
|
|
|
|
|
|
|
HKawaiiPhysicsHitProxy(ECollisionLimitType InType, int32 InIndex,
|
|
|
|
|
ECollisionSourceType InSourceType = ECollisionSourceType::AnimNode)
|
|
|
|
|
: HHitProxy(HPP_Wireframe)
|
|
|
|
|
, CollisionType(InType)
|
|
|
|
|
, CollisionIndex(InIndex)
|
|
|
|
|
, SourceType(InSourceType)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
virtual EMouseCursor::Type GetMouseCursor() override
|
|
|
|
|
{
|
|
|
|
|
return EMouseCursor::Crosshairs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ECollisionLimitType CollisionType;
|
|
|
|
|
int32 CollisionIndex;
|
|
|
|
|
ECollisionSourceType SourceType = ECollisionSourceType::AnimNode;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_HIT_PROXY(HKawaiiPhysicsHitProxy, HHitProxy);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FKawaiiPhysicsEditMode::FKawaiiPhysicsEditMode()
|
|
|
|
|
: RuntimeNode(nullptr)
|
|
|
|
|
, GraphNode(nullptr)
|
|
|
|
|
, SelectCollisionSourceType(ECollisionSourceType::AnimNode)
|
|
|
|
|
, CurWidgetMode(UE_WIDGET::EWidgetMode::WM_Translate)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::EnterMode(UAnimGraphNode_Base* InEditorNode, FAnimNode_Base* InRuntimeNode)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode = static_cast<FAnimNode_KawaiiPhysics*>(InRuntimeNode);
|
|
|
|
|
GraphNode = CastChecked<UAnimGraphNode_KawaiiPhysics>(InEditorNode);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// for Sync DetailPanel
|
|
|
|
|
GraphNode->Node.SphericalLimitsData = RuntimeNode->SphericalLimitsData;
|
|
|
|
|
GraphNode->Node.CapsuleLimitsData = RuntimeNode->CapsuleLimitsData;
|
|
|
|
|
GraphNode->Node.BoxLimitsData = RuntimeNode->BoxLimitsData;
|
|
|
|
|
GraphNode->Node.PlanarLimitsData = RuntimeNode->PlanarLimitsData;
|
|
|
|
|
GraphNode->Node.BoneConstraintsData = RuntimeNode->BoneConstraintsData;
|
|
|
|
|
GraphNode->Node.MergedBoneConstraints = RuntimeNode->MergedBoneConstraints;
|
|
|
|
|
|
2026-05-11 03:50:23 +08:00
|
|
|
// SyncBone
|
|
|
|
|
GraphNode->Node.SyncBones = RuntimeNode->SyncBones;
|
|
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
NodePropertyDelegateHandle = GraphNode->OnNodePropertyChanged().AddSP(
|
|
|
|
|
this, &FKawaiiPhysicsEditMode::OnExternalNodePropertyChange);
|
|
|
|
|
if (RuntimeNode->LimitsDataAsset)
|
|
|
|
|
{
|
|
|
|
|
LimitsDataAssetPropertyDelegateHandle =
|
|
|
|
|
RuntimeNode->LimitsDataAsset->OnLimitsChanged.AddRaw(
|
|
|
|
|
this, &FKawaiiPhysicsEditMode::OnLimitDataAssetPropertyChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UMaterialInterface* BaseElemSelectedMaterial = LoadObject<UMaterialInterface>(
|
|
|
|
|
nullptr, TEXT("/Engine/EditorMaterials/PhAT_UnselectedMaterial.PhAT_UnselectedMaterial"), nullptr,
|
|
|
|
|
LOAD_None, nullptr);
|
|
|
|
|
PhysicsAssetBodyMaterial = UMaterialInstanceDynamic::Create(
|
|
|
|
|
BaseElemSelectedMaterial, GetTransientPackage());
|
|
|
|
|
PhysicsAssetBodyMaterial->SetScalarParameterValue(TEXT("Opacity"), 0.2f);
|
|
|
|
|
|
|
|
|
|
FAnimNodeEditMode::EnterMode(InEditorNode, InRuntimeNode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::ExitMode()
|
|
|
|
|
{
|
|
|
|
|
GraphNode->OnNodePropertyChanged().Remove(NodePropertyDelegateHandle);
|
|
|
|
|
if (RuntimeNode->LimitsDataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->OnLimitsChanged.Remove(LimitsDataAssetPropertyDelegateHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GraphNode = nullptr;
|
|
|
|
|
RuntimeNode = nullptr;
|
|
|
|
|
|
|
|
|
|
FAnimNodeEditMode::ExitMode();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI)
|
|
|
|
|
{
|
|
|
|
|
const USkeletalMeshComponent* SkelMeshComp = GetAnimPreviewScene().GetPreviewMeshComponent();
|
|
|
|
|
|
|
|
|
|
if (SkelMeshComp && SkelMeshComp->GetSkeletalMeshAsset() && SkelMeshComp->GetSkeletalMeshAsset()->GetSkeleton() &&
|
|
|
|
|
FAnimWeight::IsRelevant(RuntimeNode->GetAlpha() && RuntimeNode->IsRecentlyEvaluated()))
|
|
|
|
|
{
|
|
|
|
|
RenderModifyBones(PDI);
|
|
|
|
|
RenderLimitAngle(PDI);
|
2026-05-11 03:50:23 +08:00
|
|
|
RenderSyncBone(PDI);
|
2025-01-31 16:21:15 +08:00
|
|
|
RenderSphericalLimits(PDI);
|
|
|
|
|
RenderCapsuleLimit(PDI);
|
|
|
|
|
RenderBoxLimit(PDI);
|
|
|
|
|
RenderPlanerLimit(PDI);
|
|
|
|
|
RenderBoneConstraint(PDI);
|
|
|
|
|
RenderExternalForces(PDI);
|
|
|
|
|
|
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
|
|
|
|
|
|
if (IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
if (const FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime())
|
|
|
|
|
{
|
|
|
|
|
FTransform BoneTransform = FTransform::Identity;
|
|
|
|
|
if (Collision->DrivingBone.BoneIndex >= 0 && RuntimeNode->ForwardedPose.GetPose().GetNumBones() > 0)
|
|
|
|
|
{
|
|
|
|
|
BoneTransform = RuntimeNode->ForwardedPose.GetComponentSpaceTransform(
|
|
|
|
|
Collision->DrivingBone.GetCompactPoseIndex(
|
|
|
|
|
RuntimeNode->ForwardedPose.GetPose().GetBoneContainer()));
|
|
|
|
|
}
|
2025-07-15 00:36:32 +08:00
|
|
|
|
|
|
|
|
FVector CollisionLocation = Collision->Location;
|
|
|
|
|
FQuat CollisionRotation = Collision->Rotation;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
CollisionLocation = BaseBoneSpace2ComponentSpace.TransformPosition(CollisionLocation);
|
|
|
|
|
CollisionRotation = BaseBoneSpace2ComponentSpace.TransformRotation(CollisionRotation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
PDI->DrawPoint(BoneTransform.GetLocation(), FLinearColor::White, 10.0f, SDPG_Foreground);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawDashedLine(PDI, CollisionLocation, BoneTransform.GetLocation(),
|
2025-01-31 16:21:15 +08:00
|
|
|
FLinearColor::White, 1, SDPG_Foreground);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawCoordinateSystem(PDI, BoneTransform.GetLocation(), CollisionRotation.Rotator(), 20,
|
2025-01-31 16:21:15 +08:00
|
|
|
SDPG_World + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FAnimNodeEditMode::Render(View, Viewport, PDI);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderModifyBones(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (GraphNode->bEnableDebugDrawBone)
|
|
|
|
|
{
|
|
|
|
|
for (auto& Bone : RuntimeNode->ModifyBones)
|
|
|
|
|
{
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector BoneLocation = Bone.Location;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
BoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(BoneLocation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
PDI->DrawPoint(BoneLocation, FLinearColor::White, 5.0f, SDPG_Foreground);
|
2025-01-31 16:21:15 +08:00
|
|
|
|
|
|
|
|
if (Bone.PhysicsSettings.Radius > 0)
|
|
|
|
|
{
|
|
|
|
|
auto Color = Bone.bDummy ? FColor::Red : FColor::Yellow;
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawWireSphere(PDI, BoneLocation, Color, Bone.PhysicsSettings.Radius, 16, SDPG_Foreground);
|
2025-01-31 16:21:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const int32 ChildIndex : Bone.ChildIndices)
|
|
|
|
|
{
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector ChildBoneLocation = RuntimeNode->ModifyBones[ChildIndex].Location;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
ChildBoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(ChildBoneLocation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawDashedLine(PDI, BoneLocation, ChildBoneLocation,
|
2025-01-31 16:21:15 +08:00
|
|
|
FLinearColor::White, 1, SDPG_Foreground);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderLimitAngle(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (GraphNode->bEnableDebugDrawLimitAngle)
|
|
|
|
|
{
|
|
|
|
|
for (auto& Bone : RuntimeNode->ModifyBones)
|
|
|
|
|
{
|
|
|
|
|
if (!Bone.bSkipSimulate && Bone.PhysicsSettings.LimitAngle > 0.0f && Bone.HasParent())
|
|
|
|
|
{
|
|
|
|
|
FTransform BoneTransform = FTransform(Bone.PrevRotation, Bone.PrevLocation);
|
|
|
|
|
FTransform ParentBoneTransform = FTransform(RuntimeNode->ModifyBones[Bone.ParentIndex].PrevRotation,
|
|
|
|
|
RuntimeNode->ModifyBones[Bone.ParentIndex].PrevLocation);
|
|
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
BoneTransform = BoneTransform * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
ParentBoneTransform = ParentBoneTransform * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
const float Angle = FMath::DegreesToRadians(Bone.PhysicsSettings.LimitAngle);
|
|
|
|
|
DrawCone(PDI, FScaleMatrix(5.0f) * FTransform(
|
|
|
|
|
(BoneTransform.GetLocation() - ParentBoneTransform.GetLocation()).Rotation(),
|
|
|
|
|
ParentBoneTransform.GetLocation()).ToMatrixNoScale(),
|
|
|
|
|
Angle,
|
|
|
|
|
Angle, 24, true, FLinearColor::White,
|
|
|
|
|
GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), SDPG_World);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 03:50:23 +08:00
|
|
|
void FKawaiiPhysicsEditMode::RenderSyncBone(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (!GraphNode->bEnableDebugDrawSyncBone)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto ApplyDirectionFilterAndAlpha = [&](double& Delta, const float& Alpha,
|
|
|
|
|
const ESyncBoneDirection Direction)
|
|
|
|
|
{
|
|
|
|
|
if (Direction != ESyncBoneDirection::None &&
|
|
|
|
|
(Direction == ESyncBoneDirection::Both ||
|
|
|
|
|
(Direction == ESyncBoneDirection::Positive && Delta > 0) ||
|
|
|
|
|
(Direction == ESyncBoneDirection::Negative && Delta < 0)))
|
|
|
|
|
{
|
|
|
|
|
Delta = FMath::Lerp(0.0f, Delta, Alpha);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Delta = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto DrawForceArrow = [&](const FVector& Force, const FVector& Location)
|
|
|
|
|
{
|
|
|
|
|
const FRotator Rotation = FRotationMatrix::MakeFromX(Force.GetSafeNormal()).Rotator();
|
|
|
|
|
const FMatrix TransformMatrix = FRotationMatrix(Rotation) * FTranslationMatrix(Location);
|
|
|
|
|
DrawDirectionalArrow(PDI, TransformMatrix, FLinearColor::Green, Force.Length(), 2.0f, SDPG_Foreground);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (auto& SyncBone : RuntimeNode->SyncBones)
|
|
|
|
|
{
|
|
|
|
|
// InitialPoseLocation
|
|
|
|
|
DrawBox(PDI, FTranslationMatrix(SyncBone.InitialPoseLocation), FVector(1.0f),
|
|
|
|
|
GEngine->ConstraintLimitMaterialY->GetRenderProxy(), SDPG_World);
|
|
|
|
|
|
|
|
|
|
// Current SyncBone Location
|
|
|
|
|
DrawBox(PDI, FTranslationMatrix(SyncBone.InitialPoseLocation + SyncBone.DeltaDistance), FVector(1.0f),
|
|
|
|
|
GEngine->ConstraintLimitMaterialY->GetRenderProxy(), SDPG_World);
|
|
|
|
|
|
|
|
|
|
// DeltaMovement
|
|
|
|
|
DrawDashedLine(PDI, SyncBone.InitialPoseLocation,
|
|
|
|
|
SyncBone.InitialPoseLocation + SyncBone.DeltaDistance,
|
|
|
|
|
FLinearColor::Green, 0.1f, SDPG_World);
|
|
|
|
|
|
|
|
|
|
// Distance attenuation radii
|
|
|
|
|
if (SyncBone.bEnableDistanceAttenuation)
|
|
|
|
|
{
|
|
|
|
|
const FVector Center = SyncBone.InitialPoseLocation + SyncBone.DeltaDistance;
|
|
|
|
|
// current location in component space
|
|
|
|
|
if (SyncBone.AttenuationInnerRadius > 0.0f)
|
|
|
|
|
{
|
|
|
|
|
DrawWireSphere(PDI, Center, FLinearColor(0.0f, 0.8f, 1.0f), SyncBone.AttenuationInnerRadius, 24,
|
|
|
|
|
SDPG_World);
|
|
|
|
|
}
|
|
|
|
|
if (SyncBone.AttenuationOuterRadius > 0.0f)
|
|
|
|
|
{
|
|
|
|
|
DrawWireSphere(PDI, Center, FLinearColor(0.0f, 0.3f, 0.0f), SyncBone.AttenuationOuterRadius, 24,
|
|
|
|
|
SDPG_World);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Force By SyncForce
|
|
|
|
|
FVector Force = SyncBone.DeltaDistance;
|
|
|
|
|
ApplyDirectionFilterAndAlpha(Force.X, SyncBone.GlobalScale.X, SyncBone.ApplyDirectionX);
|
|
|
|
|
ApplyDirectionFilterAndAlpha(Force.Y, SyncBone.GlobalScale.Y, SyncBone.ApplyDirectionY);
|
|
|
|
|
ApplyDirectionFilterAndAlpha(Force.Z, SyncBone.GlobalScale.Z, SyncBone.ApplyDirectionZ);
|
|
|
|
|
DrawForceArrow(Force, SyncBone.InitialPoseLocation);
|
|
|
|
|
|
|
|
|
|
// Target Bone
|
|
|
|
|
for (auto& TargetRoot : SyncBone.TargetRoots)
|
|
|
|
|
{
|
|
|
|
|
TargetRoot.DebugDraw(PDI, RuntimeNode);
|
|
|
|
|
for (auto& ChildTarget : TargetRoot.ChildTargets)
|
|
|
|
|
{
|
|
|
|
|
ChildTarget.DebugDraw(PDI, RuntimeNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
void FKawaiiPhysicsEditMode::RenderSphericalLimits(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (!GraphNode->bEnableDebugDrawSphereLimit)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto DrawSphereLimit = [&](const auto& Sphere, int32 Index, const FMaterialRenderProxy* MaterialProxy, bool bUseHit)
|
|
|
|
|
{
|
|
|
|
|
if (Sphere.bEnable && Sphere.Radius > 0)
|
|
|
|
|
{
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector Location = Sphere.Location;
|
|
|
|
|
FQuat Rotation = Sphere.Rotation;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
Location = BaseBoneSpace2ComponentSpace.TransformPosition(Location);
|
|
|
|
|
Rotation = BaseBoneSpace2ComponentSpace.TransformRotation(Rotation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
PDI->SetHitProxy(bUseHit
|
|
|
|
|
? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Spherical, Index, Sphere.SourceType)
|
|
|
|
|
: nullptr);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawSphere(PDI, Location, FRotator::ZeroRotator, FVector(Sphere.Radius), 24, 6, MaterialProxy,
|
2025-01-31 16:21:15 +08:00
|
|
|
SDPG_World);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawWireSphere(PDI, Location, FLinearColor::Black, Sphere.Radius, 24, SDPG_World);
|
|
|
|
|
DrawCoordinateSystem(PDI, Location, Rotation.Rotator(), Sphere.Radius, SDPG_World + 1);
|
2025-01-31 16:21:15 +08:00
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->SphericalLimits.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
DrawSphereLimit(RuntimeNode->SphericalLimits[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(), true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->SphericalLimitsData.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeNode->SphericalLimitsData[i].SourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
DrawSphereLimit(RuntimeNode->SphericalLimitsData[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (PhysicsAssetBodyMaterial->IsValidLowLevel())
|
|
|
|
|
{
|
|
|
|
|
DrawSphereLimit(RuntimeNode->SphericalLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(),
|
|
|
|
|
false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderCapsuleLimit(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (!GraphNode->bEnableDebugDrawCapsuleLimit)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto DrawCapsule = [&](const auto& Capsule, int32 Index, const FMaterialRenderProxy* MaterialProxy,
|
|
|
|
|
bool bUseHit)
|
|
|
|
|
{
|
|
|
|
|
if (Capsule.bEnable && Capsule.Radius > 0 && Capsule.Length > 0)
|
|
|
|
|
{
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector Location = Capsule.Location;
|
|
|
|
|
FQuat Rotation = Capsule.Rotation;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
Location = BaseBoneSpace2ComponentSpace.TransformPosition(Location);
|
|
|
|
|
Rotation = BaseBoneSpace2ComponentSpace.TransformRotation(Rotation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector XAxis = Rotation.GetAxisX();
|
|
|
|
|
FVector YAxis = Rotation.GetAxisY();
|
|
|
|
|
FVector ZAxis = Rotation.GetAxisZ();
|
2025-01-31 16:21:15 +08:00
|
|
|
|
|
|
|
|
PDI->SetHitProxy(bUseHit
|
|
|
|
|
? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Capsule, Index, Capsule.SourceType)
|
|
|
|
|
: nullptr);
|
|
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawCylinder(PDI, Location, XAxis, YAxis, ZAxis, Capsule.Radius, 0.5f * Capsule.Length, 25,
|
2025-01-31 16:21:15 +08:00
|
|
|
MaterialProxy, SDPG_World);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawSphere(PDI, Location + ZAxis * Capsule.Length * 0.5f, Rotation.Rotator(),
|
2025-01-31 16:21:15 +08:00
|
|
|
FVector(Capsule.Radius), 24, 6, MaterialProxy, SDPG_World);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawSphere(PDI, Location - ZAxis * Capsule.Length * 0.5f, Rotation.Rotator(),
|
2025-01-31 16:21:15 +08:00
|
|
|
FVector(Capsule.Radius), 24, 6, MaterialProxy, SDPG_World);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawWireCapsule(PDI, Location, XAxis, YAxis, ZAxis, FLinearColor::Black, Capsule.Radius,
|
2025-01-31 16:21:15 +08:00
|
|
|
0.5f * Capsule.Length + Capsule.Radius, 25, SDPG_World);
|
2025-07-15 00:36:32 +08:00
|
|
|
DrawCoordinateSystem(PDI, Location, Rotation.Rotator(), Capsule.Radius, SDPG_World + 1);
|
2025-01-31 16:21:15 +08:00
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->CapsuleLimits.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
DrawCapsule(RuntimeNode->CapsuleLimits[i], i, GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy(),
|
|
|
|
|
true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->CapsuleLimitsData.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeNode->CapsuleLimitsData[i].SourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
DrawCapsule(RuntimeNode->CapsuleLimitsData[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialZ->GetRenderProxy(), true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (PhysicsAssetBodyMaterial->IsValidLowLevel())
|
|
|
|
|
{
|
|
|
|
|
DrawCapsule(RuntimeNode->CapsuleLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(), false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderBoxLimit(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (!GraphNode->bEnableDebugDrawBoxLimit)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto DrawBoxLimit = [&](const auto& Box, int32 Index, const FMaterialRenderProxy* MaterialProxy,
|
|
|
|
|
bool bUseHit = true)
|
|
|
|
|
{
|
|
|
|
|
if (Box.bEnable && Box.Extent.Size() > 0)
|
|
|
|
|
{
|
|
|
|
|
FTransform BoxTransform(Box.Rotation, Box.Location);
|
2025-07-15 00:36:32 +08:00
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
BoxTransform = BoxTransform * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
}
|
2025-01-31 16:21:15 +08:00
|
|
|
|
|
|
|
|
PDI->SetHitProxy(bUseHit
|
|
|
|
|
? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Box, Index, Box.SourceType)
|
|
|
|
|
: nullptr);
|
|
|
|
|
|
|
|
|
|
DrawBox(PDI, BoxTransform.ToMatrixWithScale(), Box.Extent, MaterialProxy, SDPG_World);
|
|
|
|
|
DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), FBox(-Box.Extent, Box.Extent), FLinearColor::Black,
|
|
|
|
|
SDPG_World);
|
2026-05-11 03:50:23 +08:00
|
|
|
DrawCoordinateSystem(PDI, BoxTransform.GetLocation(), BoxTransform.Rotator(), Box.Extent.Size(),
|
|
|
|
|
SDPG_World + 1);
|
2025-01-31 16:21:15 +08:00
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->BoxLimits.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
DrawBoxLimit(RuntimeNode->BoxLimits[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->BoxLimitsData.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeNode->BoxLimitsData[i].SourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
DrawBoxLimit(RuntimeNode->BoxLimitsData[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialZ->GetRenderProxy());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (PhysicsAssetBodyMaterial->IsValidLowLevel())
|
|
|
|
|
{
|
|
|
|
|
DrawBoxLimit(RuntimeNode->BoxLimitsData[i], i, PhysicsAssetBodyMaterial->GetRenderProxy(), false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderPlanerLimit(FPrimitiveDrawInterface* PDI)
|
|
|
|
|
{
|
|
|
|
|
if (GraphNode->bEnableDebugDrawPlanerLimit)
|
|
|
|
|
{
|
|
|
|
|
auto DrawPlanarLimit = [&](const auto& Plane, int32 Index, const FMaterialRenderProxy* MaterialProxy,
|
|
|
|
|
bool bUseHit = true)
|
|
|
|
|
{
|
|
|
|
|
FTransform PlaneTransform(Plane.Rotation, Plane.Location);
|
2025-07-15 00:36:32 +08:00
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
PlaneTransform = PlaneTransform * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
}
|
2025-01-31 16:21:15 +08:00
|
|
|
PlaneTransform.NormalizeRotation();
|
|
|
|
|
|
|
|
|
|
PDI->SetHitProxy(bUseHit
|
|
|
|
|
? new HKawaiiPhysicsHitProxy(ECollisionLimitType::Planar, Index, Plane.SourceType)
|
|
|
|
|
: nullptr);
|
|
|
|
|
|
|
|
|
|
DrawPlane10x10(PDI, PlaneTransform.ToMatrixWithScale(), 200.0f, FVector2D(0.0f, 0.0f),
|
|
|
|
|
FVector2D(1.0f, 1.0f), MaterialProxy, SDPG_World);
|
|
|
|
|
DrawDirectionalArrow(PDI, FRotationMatrix(FRotator(90.0f, 0.0f, 0.0f)) * PlaneTransform.ToMatrixWithScale(),
|
|
|
|
|
FLinearColor::Blue, 50.0f, 20.0f, SDPG_Foreground, 0.5f);
|
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->PlanarLimits.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
DrawPlanarLimit(RuntimeNode->PlanarLimits[i], i,
|
|
|
|
|
GEngine->ConstraintLimitMaterialPrismatic->GetRenderProxy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int32 i = 0; i < RuntimeNode->PlanarLimitsData.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
DrawPlanarLimit(RuntimeNode->PlanarLimitsData[i], i, GEngine->ConstraintLimitMaterialZ->GetRenderProxy());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderBoneConstraint(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (GraphNode->bEnableDebugDrawBoneConstraint)
|
|
|
|
|
{
|
|
|
|
|
for (const FModifyBoneConstraint& BoneConstraint : RuntimeNode->MergedBoneConstraints)
|
|
|
|
|
{
|
|
|
|
|
if (BoneConstraint.IsBoneReferenceValid() && !RuntimeNode->ModifyBones.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
FTransform BoneTransform1 = FTransform(
|
|
|
|
|
RuntimeNode->ModifyBones[BoneConstraint.ModifyBoneIndex1].PrevRotation,
|
|
|
|
|
RuntimeNode->ModifyBones[BoneConstraint.ModifyBoneIndex1].PrevLocation);
|
|
|
|
|
FTransform BoneTransform2 = FTransform(
|
|
|
|
|
RuntimeNode->ModifyBones[BoneConstraint.ModifyBoneIndex2].PrevRotation,
|
|
|
|
|
RuntimeNode->ModifyBones[BoneConstraint.ModifyBoneIndex2].PrevLocation);
|
|
|
|
|
|
2025-07-15 00:36:32 +08:00
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
BoneTransform1 = BoneTransform1 * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
BoneTransform2 = BoneTransform2 * BaseBoneSpace2ComponentSpace;
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
// 1 -> 2
|
|
|
|
|
FVector Dir = (BoneTransform2.GetLocation() - BoneTransform1.GetLocation()).GetSafeNormal();
|
|
|
|
|
FRotator LookAt = FRotationMatrix::MakeFromX(Dir).Rotator();
|
|
|
|
|
FTransform DrawArrowTransform = FTransform(LookAt, BoneTransform1.GetLocation(),
|
|
|
|
|
BoneTransform1.GetScale3D());
|
|
|
|
|
const float Distance = (BoneTransform1.GetLocation() - BoneTransform2.GetLocation()).Size();
|
|
|
|
|
DrawDirectionalArrow(PDI, DrawArrowTransform.ToMatrixNoScale(), FLinearColor::Red,
|
|
|
|
|
Distance, 1, SDPG_Foreground);
|
|
|
|
|
// 2 -> 1
|
|
|
|
|
LookAt = FRotationMatrix::MakeFromX(-Dir).Rotator();
|
|
|
|
|
DrawArrowTransform = FTransform(LookAt, BoneTransform2.GetLocation(), BoneTransform2.GetScale3D());
|
|
|
|
|
DrawDirectionalArrow(PDI, DrawArrowTransform.ToMatrixNoScale(), FLinearColor::Red,
|
|
|
|
|
Distance, 1, SDPG_Foreground);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::RenderExternalForces(FPrimitiveDrawInterface* PDI) const
|
|
|
|
|
{
|
|
|
|
|
if (GraphNode->bEnableDebugDrawExternalForce)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& Bone : RuntimeNode->ModifyBones)
|
|
|
|
|
{
|
|
|
|
|
for (auto& Force : RuntimeNode->ExternalForces)
|
|
|
|
|
{
|
|
|
|
|
if (Force.IsValid())
|
|
|
|
|
{
|
|
|
|
|
Force.GetMutablePtr<FKawaiiPhysics_ExternalForce>()->AnimDrawDebugForEditMode(
|
|
|
|
|
Bone, *RuntimeNode, PDI);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector FKawaiiPhysicsEditMode::GetWidgetLocation(ECollisionLimitType CollisionType, int32 Index) const
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
return GetAnimPreviewScene().GetPreviewMeshComponent()->GetComponentLocation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (const FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime())
|
|
|
|
|
{
|
|
|
|
|
return Collision->Location;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetAnimPreviewScene().GetPreviewMeshComponent()->GetComponentLocation();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector FKawaiiPhysicsEditMode::GetWidgetLocation() const
|
|
|
|
|
{
|
|
|
|
|
return GetWidgetLocation(SelectCollisionType, SelectCollisionIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::GetCustomDrawingCoordinateSystem(FMatrix& InMatrix, void* InData)
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FQuat Rotation = FQuat::Identity;
|
|
|
|
|
if (FCollisionLimitBase* Collision = GetSelectCollisionLimitRuntime())
|
|
|
|
|
{
|
|
|
|
|
Rotation = Collision->Rotation;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InMatrix = FTransform(Rotation).ToMatrixNoScale();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::GetWidgetMode() const
|
|
|
|
|
{
|
|
|
|
|
if (GetSelectCollisionLimitRuntime())
|
|
|
|
|
{
|
|
|
|
|
CurWidgetMode = FindValidWidgetMode(CurWidgetMode);
|
|
|
|
|
return CurWidgetMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_Translate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_WIDGET::EWidgetMode FKawaiiPhysicsEditMode::FindValidWidgetMode(UE_WIDGET::EWidgetMode InWidgetMode) const
|
|
|
|
|
{
|
|
|
|
|
if (InWidgetMode == UE_WIDGET::EWidgetMode::WM_None)
|
|
|
|
|
{
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_Translate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (InWidgetMode)
|
|
|
|
|
{
|
|
|
|
|
case UE_WIDGET::EWidgetMode::WM_Translate:
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_Rotate;
|
|
|
|
|
case UE_WIDGET::EWidgetMode::WM_Rotate:
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_Scale;
|
|
|
|
|
case UE_WIDGET::EWidgetMode::WM_Scale:
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_Translate;
|
|
|
|
|
default: ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return UE_WIDGET::EWidgetMode::WM_None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy,
|
|
|
|
|
const FViewportClick& Click)
|
|
|
|
|
{
|
|
|
|
|
bool bResult = FAnimNodeEditMode::HandleClick(InViewportClient, HitProxy, Click);
|
|
|
|
|
|
|
|
|
|
if (HitProxy != nullptr && HitProxy->IsA(HKawaiiPhysicsHitProxy::StaticGetType()))
|
|
|
|
|
{
|
|
|
|
|
HKawaiiPhysicsHitProxy* KawaiiPhysicsHitProxy = static_cast<HKawaiiPhysicsHitProxy*>(HitProxy);
|
|
|
|
|
SelectCollisionType = KawaiiPhysicsHitProxy->CollisionType;
|
|
|
|
|
SelectCollisionIndex = KawaiiPhysicsHitProxy->CollisionIndex;
|
|
|
|
|
SelectCollisionSourceType = KawaiiPhysicsHitProxy->SourceType;
|
|
|
|
|
bResult = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SelectCollisionType = ECollisionLimitType::None;
|
|
|
|
|
SelectCollisionIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey,
|
|
|
|
|
EInputEvent InEvent)
|
|
|
|
|
{
|
|
|
|
|
bool bHandled = false;
|
|
|
|
|
|
|
|
|
|
if ((InEvent == IE_Pressed) && !IsManipulatingWidget())
|
|
|
|
|
{
|
|
|
|
|
if (InKey == EKeys::SpaceBar)
|
|
|
|
|
{
|
|
|
|
|
GetModeManager()->SetWidgetMode(GetWidgetMode());
|
|
|
|
|
bHandled = true;
|
|
|
|
|
InViewportClient->Invalidate();
|
|
|
|
|
}
|
|
|
|
|
else if (InKey == EKeys::Q)
|
|
|
|
|
{
|
|
|
|
|
const auto CoordSystem = GetModeManager()->GetCoordSystem();
|
|
|
|
|
GetModeManager()->SetCoordSystem(CoordSystem == COORD_Local ? COORD_World : COORD_Local);
|
|
|
|
|
}
|
|
|
|
|
else if (InKey == EKeys::Delete && SelectCollisionSourceType != ECollisionSourceType::PhysicsAsset &&
|
|
|
|
|
IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
switch (SelectCollisionType)
|
|
|
|
|
{
|
|
|
|
|
case ECollisionLimitType::Spherical:
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->SphericalLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
RuntimeNode->LimitsDataAsset->MarkPackageDirty();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->SphericalLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
GraphNode->Node.SphericalLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::Capsule:
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->CapsuleLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
RuntimeNode->LimitsDataAsset->MarkPackageDirty();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->CapsuleLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
GraphNode->Node.CapsuleLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::Box:
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->BoxLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
RuntimeNode->LimitsDataAsset->MarkPackageDirty();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->BoxLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
GraphNode->Node.BoxLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ECollisionLimitType::Planar:
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->PlanarLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
RuntimeNode->LimitsDataAsset->MarkPackageDirty();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->PlanarLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
GraphNode->Node.PlanarLimits.RemoveAt(SelectCollisionIndex);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::None: break;
|
|
|
|
|
default: ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bHandled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ECoordSystem FKawaiiPhysicsEditMode::GetWidgetCoordinateSystem() const
|
|
|
|
|
{
|
|
|
|
|
return COORD_Local;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::OnExternalNodePropertyChange(FPropertyChangedEvent& InPropertyEvent)
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
SelectCollisionIndex = -1;
|
|
|
|
|
SelectCollisionType = ECollisionLimitType::None;
|
|
|
|
|
CurWidgetMode = UE_WIDGET::EWidgetMode::WM_None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (InPropertyEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(FAnimNode_KawaiiPhysics, LimitsDataAsset))
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeNode->LimitsDataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->OnLimitsChanged.AddRaw(
|
|
|
|
|
this, &FKawaiiPhysicsEditMode::OnLimitDataAssetPropertyChange);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::OnLimitDataAssetPropertyChange(FPropertyChangedEvent& InPropertyEvent)
|
|
|
|
|
{
|
|
|
|
|
GraphNode->Node.SphericalLimitsData = RuntimeNode->SphericalLimitsData;
|
|
|
|
|
GraphNode->Node.CapsuleLimitsData = RuntimeNode->CapsuleLimitsData;
|
|
|
|
|
GraphNode->Node.BoxLimitsData = RuntimeNode->BoxLimitsData;
|
|
|
|
|
GraphNode->Node.PlanarLimitsData = RuntimeNode->PlanarLimitsData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::IsSelectAnimNodeCollision() const
|
|
|
|
|
{
|
|
|
|
|
return SelectCollisionSourceType == ECollisionSourceType::AnimNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::IsValidSelectCollision() const
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeNode == nullptr || GraphNode == nullptr || SelectCollisionIndex < 0 || SelectCollisionType ==
|
|
|
|
|
ECollisionLimitType::None)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (SelectCollisionType)
|
|
|
|
|
{
|
|
|
|
|
case ECollisionLimitType::Spherical:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? RuntimeNode->SphericalLimitsData.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
: RuntimeNode->SphericalLimits.IsValidIndex(SelectCollisionIndex);
|
|
|
|
|
case ECollisionLimitType::Capsule:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? RuntimeNode->CapsuleLimitsData.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
: RuntimeNode->CapsuleLimits.IsValidIndex(SelectCollisionIndex);
|
|
|
|
|
case ECollisionLimitType::Box:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? RuntimeNode->BoxLimitsData.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
: RuntimeNode->BoxLimits.IsValidIndex(SelectCollisionIndex);
|
|
|
|
|
case ECollisionLimitType::Planar:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? RuntimeNode->PlanarLimitsData.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
: RuntimeNode->PlanarLimits.IsValidIndex(SelectCollisionIndex);
|
|
|
|
|
case ECollisionLimitType::None: break;
|
|
|
|
|
default: ;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCollisionLimitBase* FKawaiiPhysicsEditMode::GetSelectCollisionLimitRuntime() const
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (SelectCollisionType)
|
|
|
|
|
{
|
|
|
|
|
case ECollisionLimitType::Spherical:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? &(RuntimeNode->SphericalLimitsData[SelectCollisionIndex])
|
|
|
|
|
: &(RuntimeNode->SphericalLimits[SelectCollisionIndex]);
|
|
|
|
|
case ECollisionLimitType::Capsule:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? &(RuntimeNode->CapsuleLimitsData[SelectCollisionIndex])
|
|
|
|
|
: &(RuntimeNode->CapsuleLimits[SelectCollisionIndex]);
|
|
|
|
|
case ECollisionLimitType::Box:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? &(RuntimeNode->BoxLimitsData[SelectCollisionIndex])
|
|
|
|
|
: &(RuntimeNode->BoxLimits[SelectCollisionIndex]);
|
|
|
|
|
case ECollisionLimitType::Planar:
|
|
|
|
|
return !IsSelectAnimNodeCollision()
|
|
|
|
|
? &(RuntimeNode->PlanarLimitsData[SelectCollisionIndex])
|
|
|
|
|
: &(RuntimeNode->PlanarLimits[SelectCollisionIndex]);
|
|
|
|
|
case ECollisionLimitType::None: break;
|
|
|
|
|
default: ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCollisionLimitBase* FKawaiiPhysicsEditMode::GetSelectCollisionLimitGraph() const
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (SelectCollisionType)
|
|
|
|
|
{
|
|
|
|
|
case ECollisionLimitType::Spherical:
|
|
|
|
|
{
|
|
|
|
|
auto& CollisionLimits = !IsSelectAnimNodeCollision()
|
|
|
|
|
? GraphNode->Node.SphericalLimitsData
|
|
|
|
|
: GraphNode->Node.SphericalLimits;
|
|
|
|
|
return CollisionLimits.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
? &CollisionLimits[SelectCollisionIndex]
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
case ECollisionLimitType::Capsule:
|
|
|
|
|
{
|
|
|
|
|
auto& CollisionLimits = !IsSelectAnimNodeCollision()
|
|
|
|
|
? GraphNode->Node.CapsuleLimitsData
|
|
|
|
|
: GraphNode->Node.CapsuleLimits;
|
|
|
|
|
return CollisionLimits.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
? &CollisionLimits[SelectCollisionIndex]
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
case ECollisionLimitType::Box:
|
|
|
|
|
{
|
|
|
|
|
auto& CollisionLimits = !IsSelectAnimNodeCollision()
|
|
|
|
|
? GraphNode->Node.BoxLimitsData
|
|
|
|
|
: GraphNode->Node.BoxLimits;
|
|
|
|
|
return CollisionLimits.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
? &CollisionLimits[SelectCollisionIndex]
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
case ECollisionLimitType::Planar:
|
|
|
|
|
{
|
|
|
|
|
auto& CollisionLimits = !IsSelectAnimNodeCollision()
|
|
|
|
|
? GraphNode->Node.PlanarLimitsData
|
|
|
|
|
: GraphNode->Node.PlanarLimits;
|
|
|
|
|
return CollisionLimits.IsValidIndex(SelectCollisionIndex)
|
|
|
|
|
? &CollisionLimits[SelectCollisionIndex]
|
|
|
|
|
: nullptr;
|
|
|
|
|
}
|
|
|
|
|
case ECollisionLimitType::None: break;
|
|
|
|
|
default: ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::DoTranslation(FVector& InTranslation)
|
|
|
|
|
{
|
|
|
|
|
if (InTranslation.IsNearlyZero())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCollisionLimitBase* CollisionRuntime = GetSelectCollisionLimitRuntime();
|
|
|
|
|
FCollisionLimitBase* CollisionGraph = GetSelectCollisionLimitGraph();
|
|
|
|
|
if (!CollisionRuntime || !CollisionGraph)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Fail to edit limit." ));
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ),
|
|
|
|
|
*RuntimeNode->LimitsDataAsset.GetName());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FVector Offset;
|
|
|
|
|
if (CollisionRuntime->DrivingBone.BoneIndex >= 0)
|
|
|
|
|
{
|
|
|
|
|
const USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent();
|
|
|
|
|
Offset = ConvertCSVectorToBoneSpace(SkelComp, InTranslation, RuntimeNode->ForwardedPose,
|
|
|
|
|
CollisionRuntime->DrivingBone.BoneName, BCS_BoneSpace);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Offset = InTranslation;
|
|
|
|
|
}
|
|
|
|
|
CollisionRuntime->OffsetLocation += Offset;
|
|
|
|
|
CollisionGraph->OffsetLocation = CollisionRuntime->OffsetLocation;
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->UpdateLimit(CollisionRuntime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::DoRotation(FRotator& InRotation)
|
|
|
|
|
{
|
|
|
|
|
if (InRotation.IsNearlyZero())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCollisionLimitBase* CollisionRuntime = GetSelectCollisionLimitRuntime();
|
|
|
|
|
FCollisionLimitBase* CollisionGraph = GetSelectCollisionLimitGraph();
|
|
|
|
|
if (!CollisionRuntime || !CollisionGraph)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Fail to edit limit." ));
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ),
|
|
|
|
|
*RuntimeNode->LimitsDataAsset.GetName());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FQuat DeltaQuat;
|
|
|
|
|
if (CollisionRuntime->DrivingBone.BoneIndex >= 0)
|
|
|
|
|
{
|
|
|
|
|
const USkeletalMeshComponent* SkelComp = GetAnimPreviewScene().GetPreviewMeshComponent();
|
|
|
|
|
DeltaQuat = ConvertCSRotationToBoneSpace(SkelComp, InRotation, RuntimeNode->ForwardedPose,
|
|
|
|
|
CollisionRuntime->DrivingBone.BoneName, BCS_BoneSpace);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DeltaQuat = InRotation.Quaternion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CollisionRuntime->OffsetRotation = FRotator(DeltaQuat * CollisionRuntime->OffsetRotation.Quaternion());
|
|
|
|
|
CollisionGraph->OffsetRotation = CollisionRuntime->OffsetRotation;
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->UpdateLimit(CollisionRuntime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::DoScale(FVector& InScale)
|
|
|
|
|
{
|
|
|
|
|
if (!IsValidSelectCollision() || InScale.IsNearlyZero() || SelectCollisionType == ECollisionLimitType::Planar)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
FCollisionLimitBase* CollisionRuntime = GetSelectCollisionLimitRuntime();
|
|
|
|
|
FCollisionLimitBase* CollisionGraph = GetSelectCollisionLimitGraph();
|
|
|
|
|
if (!CollisionRuntime || !CollisionGraph)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Fail to edit limit." ));
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogKawaiiPhysics, Warning, TEXT( "Please try saving the DataAsset (%s) and compile this ABP." ),
|
|
|
|
|
*RuntimeNode->LimitsDataAsset.GetName());
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionType == ECollisionLimitType::Spherical)
|
|
|
|
|
{
|
|
|
|
|
FSphericalLimit& SphericalLimitRuntime = *static_cast<FSphericalLimit*>(CollisionRuntime);
|
|
|
|
|
FSphericalLimit& SphericalLimitGraph = *static_cast<FSphericalLimit*>(CollisionGraph);
|
|
|
|
|
|
|
|
|
|
SphericalLimitRuntime.Radius += InScale.X;
|
|
|
|
|
SphericalLimitRuntime.Radius += InScale.Y;
|
|
|
|
|
SphericalLimitRuntime.Radius += InScale.Z;
|
|
|
|
|
SphericalLimitRuntime.Radius = FMath::Max(SphericalLimitRuntime.Radius, 0.0f);
|
|
|
|
|
|
|
|
|
|
SphericalLimitGraph.Radius = SphericalLimitRuntime.Radius;
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->UpdateLimit(&SphericalLimitRuntime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (SelectCollisionType == ECollisionLimitType::Capsule)
|
|
|
|
|
{
|
|
|
|
|
FCapsuleLimit& CapsuleLimitRuntime = *static_cast<FCapsuleLimit*>(CollisionRuntime);
|
|
|
|
|
FCapsuleLimit& CapsuleLimitGraph = *static_cast<FCapsuleLimit*>(CollisionGraph);
|
|
|
|
|
|
|
|
|
|
CapsuleLimitRuntime.Radius += InScale.X;
|
|
|
|
|
CapsuleLimitRuntime.Radius += InScale.Y;
|
|
|
|
|
CapsuleLimitRuntime.Radius = FMath::Max(CapsuleLimitRuntime.Radius, 0.0f);
|
|
|
|
|
|
|
|
|
|
CapsuleLimitRuntime.Length += InScale.Z;
|
|
|
|
|
CapsuleLimitRuntime.Length = FMath::Max(CapsuleLimitRuntime.Length, 0.0f);
|
|
|
|
|
|
|
|
|
|
CapsuleLimitGraph.Radius = CapsuleLimitRuntime.Radius;
|
|
|
|
|
CapsuleLimitGraph.Length = CapsuleLimitRuntime.Length;
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->UpdateLimit(&CapsuleLimitRuntime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (SelectCollisionType == ECollisionLimitType::Box)
|
|
|
|
|
{
|
|
|
|
|
FBoxLimit& BoxLimitRuntime = *static_cast<FBoxLimit*>(CollisionRuntime);
|
|
|
|
|
FBoxLimit& BoxLimitGraph = *static_cast<FBoxLimit*>(CollisionGraph);
|
|
|
|
|
|
|
|
|
|
BoxLimitRuntime.Extent += InScale;
|
|
|
|
|
BoxLimitRuntime.Extent.X = FMath::Max(BoxLimitRuntime.Extent.X, 0.0f);
|
|
|
|
|
BoxLimitRuntime.Extent.Y = FMath::Max(BoxLimitRuntime.Extent.Y, 0.0f);
|
|
|
|
|
BoxLimitRuntime.Extent.Z = FMath::Max(BoxLimitRuntime.Extent.Z, 0.0f);
|
|
|
|
|
|
|
|
|
|
BoxLimitGraph.Extent = BoxLimitRuntime.Extent;
|
|
|
|
|
|
|
|
|
|
if (SelectCollisionSourceType == ECollisionSourceType::DataAsset)
|
|
|
|
|
{
|
|
|
|
|
RuntimeNode->LimitsDataAsset->UpdateLimit(&BoxLimitRuntime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool FKawaiiPhysicsEditMode::ShouldDrawWidget() const
|
|
|
|
|
{
|
|
|
|
|
if (IsValidSelectCollision())
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::DrawHUD(FEditorViewportClient* ViewportClient, FViewport* Viewport, const FSceneView* View,
|
|
|
|
|
FCanvas* Canvas)
|
|
|
|
|
{
|
|
|
|
|
float FontWidth, FontHeight;
|
|
|
|
|
GEngine->GetSmallFont()->GetCharSize(TEXT('L'), FontWidth, FontHeight);
|
|
|
|
|
constexpr float XOffset = 5.0f;
|
|
|
|
|
float DrawPositionY = Viewport->GetSizeXY().Y / Canvas->GetDPIScale() - (3 + FontHeight) - 100 / Canvas->
|
|
|
|
|
GetDPIScale();
|
|
|
|
|
|
|
|
|
|
if (!FAnimWeight::IsRelevant(RuntimeNode->GetAlpha()) || !RuntimeNode->IsRecentlyEvaluated())
|
|
|
|
|
{
|
|
|
|
|
DrawTextItem(
|
|
|
|
|
LOCTEXT("", "This node does not evaluate recently."), Canvas, XOffset, DrawPositionY,
|
|
|
|
|
FontHeight);
|
|
|
|
|
FAnimNodeEditMode::DrawHUD(ViewportClient, Viewport, View, Canvas);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DrawTextItem(LOCTEXT("", "Q : Cycle Transform Coordinate System"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
DrawTextItem(
|
|
|
|
|
LOCTEXT("", "Space : Cycle Between Translate, Rotate and Scale"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
DrawTextItem(LOCTEXT("", "R : Scale Mode"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
DrawTextItem(LOCTEXT("", "E : Rotate Mode"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
DrawTextItem(LOCTEXT("", "W : Translate Mode"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
DrawTextItem(LOCTEXT("", "------------------"), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FString CollisionDebugInfo = FString(TEXT("Select Collision : "));
|
|
|
|
|
switch (SelectCollisionType)
|
|
|
|
|
{
|
|
|
|
|
case ECollisionLimitType::Spherical:
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("Spherical")));
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::Capsule:
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("Capsule")));
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::Box:
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("Box")));
|
|
|
|
|
break;
|
|
|
|
|
case ECollisionLimitType::Planar:
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("Planar")));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("None")));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (SelectCollisionIndex >= 0)
|
|
|
|
|
{
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("[")));
|
|
|
|
|
CollisionDebugInfo.Append(FString::FromInt(SelectCollisionIndex));
|
|
|
|
|
CollisionDebugInfo.Append(FString(TEXT("]")));
|
|
|
|
|
}
|
|
|
|
|
DrawTextItem(FText::FromString(CollisionDebugInfo), Canvas, XOffset, DrawPositionY, FontHeight);
|
|
|
|
|
|
|
|
|
|
const UDebugSkelMeshComponent* PreviewMeshComponent = GetAnimPreviewScene().GetPreviewMeshComponent();
|
|
|
|
|
if (GraphNode->bEnableDebugBoneLengthRate)
|
|
|
|
|
{
|
|
|
|
|
if (PreviewMeshComponent != nullptr && PreviewMeshComponent->MeshObject != nullptr)
|
|
|
|
|
{
|
|
|
|
|
for (auto& Bone : RuntimeNode->ModifyBones)
|
|
|
|
|
{
|
2025-07-15 00:36:32 +08:00
|
|
|
FVector BoneLocation = Bone.Location;
|
|
|
|
|
if (RuntimeNode->SimulationSpace == EKawaiiPhysicsSimulationSpace::BaseBoneSpace)
|
|
|
|
|
{
|
|
|
|
|
const FTransform& BaseBoneSpace2ComponentSpace = RuntimeNode->GetBaseBoneSpace2ComponentSpace();
|
|
|
|
|
BoneLocation = BaseBoneSpace2ComponentSpace.TransformPosition(BoneLocation);
|
|
|
|
|
}
|
2026-05-11 03:50:23 +08:00
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
// Refer to FAnimationViewportClient::ShowBoneNames
|
2025-07-15 00:36:32 +08:00
|
|
|
const FVector BonePos = PreviewMeshComponent->GetComponentTransform().TransformPosition(BoneLocation);
|
2025-01-31 16:21:15 +08:00
|
|
|
Draw3DTextItem(FText::AsNumber(Bone.LengthRateFromRoot), Canvas, View,
|
|
|
|
|
Viewport, BonePos);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 03:50:23 +08:00
|
|
|
// SyncBone
|
|
|
|
|
if (GraphNode->bEnableDebugDrawSyncBone)
|
|
|
|
|
{
|
|
|
|
|
for (auto& SyncBone : RuntimeNode->SyncBones)
|
|
|
|
|
{
|
|
|
|
|
FString LenText = FString::Printf(TEXT("%.1f / %.1f"), SyncBone.ScaledDeltaDistance.Length(),
|
|
|
|
|
SyncBone.DeltaDistance.Length());
|
|
|
|
|
Draw3DTextItem(FText::FromString(LenText), Canvas, View,
|
|
|
|
|
Viewport, PreviewMeshComponent->GetComponentTransform().TransformPosition(SyncBone.InitialPoseLocation));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 16:21:15 +08:00
|
|
|
FAnimNodeEditMode::DrawHUD(ViewportClient, Viewport, View, Canvas);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::DrawTextItem(const FText& Text, FCanvas* Canvas, float X, float& Y, float FontHeight)
|
|
|
|
|
{
|
|
|
|
|
FCanvasTextItem TextItem(FVector2D::ZeroVector, Text, GEngine->GetSmallFont(), FLinearColor::White);
|
|
|
|
|
TextItem.EnableShadow(FLinearColor::Black);
|
|
|
|
|
Canvas->DrawItem(TextItem, X, Y);
|
|
|
|
|
Y -= (3 + FontHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FKawaiiPhysicsEditMode::Draw3DTextItem(const FText& Text, FCanvas* Canvas, const FSceneView* View,
|
|
|
|
|
const FViewport* Viewport, FVector Location)
|
|
|
|
|
{
|
|
|
|
|
const int32 HalfX = Viewport->GetSizeXY().X / 2 / Canvas->GetDPIScale();
|
|
|
|
|
const int32 HalfY = Viewport->GetSizeXY().Y / 2 / Canvas->GetDPIScale();
|
|
|
|
|
|
|
|
|
|
const FPlane proj = View->Project(Location);
|
|
|
|
|
if (proj.W > 0.f)
|
|
|
|
|
{
|
|
|
|
|
const int32 XPos = HalfX + (HalfX * proj.X);
|
|
|
|
|
const int32 YPos = HalfY + (HalfY * (proj.Y * -1));
|
|
|
|
|
FCanvasTextItem TextItem(FVector2D(XPos, YPos), Text, GEngine->GetSmallFont(), FLinearColor::White);
|
|
|
|
|
TextItem.EnableShadow(FLinearColor::Black);
|
|
|
|
|
Canvas->DrawItem(TextItem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|