This commit is contained in:
2025-04-17 23:59:17 +08:00
commit 88536f22da
57 changed files with 8094 additions and 0 deletions

59
FLESH.uplugin Normal file
View File

@@ -0,0 +1,59 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "FLESH",
"Description": "Fully Locational Evisceration System for Humanoids",
"Category": "Gameplay",
"CreatedBy": "Virtuos Games",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "FLESH",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "FLESHEditor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
],
"Plugins": [
{
"Name": "Niagara",
"Enabled": true
},
{
"Name": "GeometryProcessing",
"Enabled": true
},
{
"Name": "GeometryScripting",
"Enabled": true
},
{
"Name": "ChaosCloth",
"Enabled": true
},
{
"Name": "ChaosClothEditor",
"Enabled": true
},
{
"Name": "ChaosNiagara",
"Enabled": true
},
{
"Name": "PhysicsControl",
"Enabled": true
}
]
}

98
Gaol.md Normal file
View File

@@ -0,0 +1,98 @@
#### FLESH, Fully Locational Evisceration System for Humanoids, 在UE5.5肢解系统插件
#### 目标功能:
实时布尔切割引擎编辑器;
多层实时布尔切割编辑器;
多层系统: 骨骼,内脏;
多层物理系统:内脏物理,骨骼物理;
血液系统Niagara,BloodPool, Decal
物理交互;
以及肢解系统相关的其他交互;
#### 引擎版本UE5.5
#### Key stages
- Core Function Modules
- Real-time Editor Architecture
- Visual Tool Chain
- Physical Simulation Subsystem
- Data Serialization Module
- Anatomical Structure Brush
- Damage Type Preset
- Real-time Effect Preview
- Soft Body Physics Parameters
- Fracture Threshold Curve
- Binary Asset Packaging
- Version Control Integration
- Key Technology Implementation
#### Performance Optimization Strategy
- Asynchronous Physical Calculation:
Assigning Non-critical Physical Simulation Tasks to Task Graph Thread Pool
- Incremental Resource Loading:
Using Streamable Manager to Dynamically Load High-precision Anatomical Resources
#### 资源参考:
-《死亡岛2》的肢解系统https://www.youtube.com/watch?v=d3VrPOm-KDE
- Dead Island 2 的 FLESH 系统如何程序性地破坏皮肤和骨骼https://www.youtube.com/watch?v=GW7N83E1NqY&t=542s
- Dead Island 2 - F.L.E.S.H. Gore System (Melee): https://www.youtube.com/watch?v=LQEVydDlFuk
- Full analysis of the gore effects: https://www.youtube.com/watch?v=Y23zTuc2drk
Hey there and welcome to Random Madness! This channel is about showcasing and discussing response mechanics in video games. Blood and gore effects, destruction, vehicle damage and so on. Here's an overarching video on this theme:
https://www.youtube.com/watch?v=U4-lOPreM0U
You can find the discussion videos of character responses in this playlist:
https://www.youtube.com/playlist?list=PLE8BZenM9K-AH6yrlnnWkhXmvhlQCsMIj
You can find videos on the topic of destruction and vehicle damage here:
https://www.youtube.com/playlist?list=PLE8BZenM9K-AExTsE9779PQjZ-CcCIh3g
If you like the music you hear on the channel, you can find it here. It mostly leans towards heavy metal, with elements of thrash, prog and other influences as well:
https://www.youtube.com/playlist?list=PLE8BZenM9K-ApYtWLdwAZhJoM44mLfLnQ
https://open.spotify.com/playlist/4C2tqInaRi8gxg7TI7gYDI
That was showcased in the 3rd demonstration, it will be mentioned in the full commentary too
https://www.youtube.com/watch?v=CjC6SAomB6E
### 参考资源
###### UE5 Bult-in
- Geometry Script:
https://dev.epicgames.com/documentation/en-us/unreal-engine/geometry-scripting-users-guide-in-unreal-engine
- Procedural Mesh components:
https://forums.unrealengine.com/t/converting-skeletal-mesh-to-procedural-mesh/1718443
https://zhuanlan.zhihu.com/p/358074568
###### ThirdPart
- Gore and Dismemberment Plugin (BlueprintExposed):
SourceCode: Games\Lyra\Plugins\FLESH\Reference\UEGoreSystem
Doc: https://github.com/Klian326/-Public-UEGoreSystem/wiki/4.-Procedural-Control-Rig#lyra
Setup process: https://github.com/Klian326/-Public-UEGoreSystem/wiki/4.-Procedural-Control-Rig#lyra
- Skinned Decal Component:
SourceCode: Games\Lyra\Plugins\FLESH\Reference\SkinnedDecalComponent
Fab link: https://www.fab.com/listings/7491af07-f541-493d-a78f-d7fa5d466a0d
Docshttps://ac.virtuosgames.com:8443/display/TSZO/Skinned+Decal+Component+Documentation
- EnGore Procedural Dismemberment:
SourceCode: Games\Lyra\Plugins\FLESH\Reference\EnGoreProceduralDismemberment
Fab: EnGore: https://www.fab.com/listings/cb680946-6bde-445f-9ebe-3cecf4bec21e
Demo: EnGore: https://www.youtube.com/watch?v=MN31jCDQ5zY
Doc: https://app.milanote.com/1MQHr51cI5RJ2H/dismemberment-system?p=XmVzKYwdfSs
- Dismemberment:
Github: Thyke/Dismemberment: Advanced dismemberment system for Unreal Engine, featuring dynamic limb separation, weapon dropping, and customizable body part data.
- Fake Flesh System TechArt:
Github: https://github.com/Vincent-Devine/Fake_Flesh_System_TechArt
- Slice Skeletal Mesh Now:
Github: https://drive.google.com/open?id=12VlbtXGcpTXCVoVphhX3ZawnmmvD_AfM
#### Hierarchical modeling of anatomical structures
Use multi-layered materials (4-6 layers) to simulate skin/muscle/bone, and control visibility through material masks
Add secondary collision bodies (such as muscle expansion bodies) to the skeletal system to achieve force deformation
Sample code:
```
// Add physical constraints to the bone modifier
FConstraintInstance* Constraint = SkeletalMesh->AddConstraint(
EConstraintType::Physics,
BoneName,
ParentBoneName,
FTransform::Identity
);
Constraint->SetLinearXMotion(ELinearConstraintMotion::LCM_Limited);
```
#### Real-time cutting algorithm
Plane cutting: Use UE5's APEX Destruction module and combine it with Signed Distance Field to calculate the cutting surface
Dynamic topology reconstruction: Use Procedural Mesh Component to generate a new mesh. Note:
- Vertex cache reuse (to avoid GC jamming)
- Dynamic calculation of normal smoothing groups (to prevent jagged sections)

186
Readme.md Normal file
View File

@@ -0,0 +1,186 @@
### Real-time Dismemberment Editor Development Guide
#### 引擎版本UE5.5
### 概述
FLESH 是一个用于 UE5.5 的高级肢解系统插件,提供实时布尔切割、多层系统和物理交互功能。该系统允许开发者创建逼真的肢解效果,适用于各种游戏类型。
### 核心功能
- 实时布尔切割引擎编辑器:支持通过矩阵变量输入定义切割角度和位置
- 多层实时布尔切割编辑器:可视化编辑多层切割效果
- 多层系统:包括骨骼和内脏的分层模拟
- 多层物理系统:内脏物理和骨骼物理的独立模拟
- 血液系统:集成 Niagara 粒子效果、血池和贴花
- 物理交互:断肢与环境的物理交互
- 蓝图组件:支持断肢、血液、骨骼、内脏等不同类型的组件插槽和属性分类
### 交互式编辑器
FLESH 插件包含一个可在 UE 编辑器中单独开启的交互式编辑器,具有以下特点:
- 支持交互式布尔切割操作
- 支持矩阵变量输入定义切割角度和位置
- 支持不同部位的 Patch 或自定义 Patch
- 提供实时预览和调试功能
- 分类管理断肢、血液、骨骼、内脏等不同类型的组件接口和属性
### 系统架构
FLESH 插件的系统架构分为以下几个主要模块:
#### 核心功能模块
- `DismembermentSystem`:主要肢解系统组件,处理骨骼切割和物理交互
- `AnatomicalLayerSystem`:多层系统管理,处理骨骼、内脏等不同层次
- `BloodSystem`:血液效果系统,管理血液粒子、血池和贴花
- `BooleanCutTool`:布尔切割工具,提供实时切割功能
#### 编辑器架构
- `DismembermentEditor`:主编辑器界面,提供可视化编辑工具
- `AnatomicalStructureBrush`:解剖结构笔刷,用于创建和编辑解剖结构
#### 物理模拟子系统
- 支持骨骼物理:使用 PhysicsAsset 进行骨骼物理模拟
- 支持内脏物理:使用软体物理模拟内脏变形和交互
- 支持断裂阈值曲线:基于物理力量的断裂模拟
#### 数据序列化模块
- 支持保存和加载肢解设置
- 支持导出和导入预设
### 使用指南
#### 安装
1. 将 FLESH 插件复制到项目的 Plugins 文件夹中
2. 在 UE 编辑器中启用插件
3. 重启编辑器
#### 基本使用
1. 向角色添加 `UDismembermentSystem` 组件
2. 配置骨骼映射和切割设置
3. 使用 `BloodSystem` 组件设置血液效果
4. 在游戏中调用相应函数触发肢解效果
#### 编辑器使用
1. 在编辑器中选择 "FLESH 编辑器" 打开交互式编辑器
2. 加载骨骼网格体进行编辑
3. 使用布尔切割工具定义切割平面
4. 设置多层系统参数
5. 预览效果并调整参数
6. 保存设置应用到游戏中
### 性能优化策略
- 异步物理计算:将非关键物理模拟任务分配到任务图线程池
- 增量资源加载:使用 Streamable Manager 动态加载高精度解剖资源
- LOD 系统:根据距离和性能需求调整物理和视觉细节
### 蓝图接口
FLESH 插件提供了全面的蓝图接口,允许开发者无需编写代码即可使用所有功能:
- 肢解系统接口:控制切割和肢解
- 血液系统接口:管理血液效果
- 物理系统接口:调整物理参数
- 编辑器接口:运行时修改设置
### 技术实现
- 使用 GeometryScript 进行实时布尔切割
- 使用 Niagara 系统进行高级粒子效果
- 使用 PhysicsAsset 和 ChaosPhysics 进行物理模拟
- 使用程序化网格体进行动态网格生成
### 版本信息
- 当前版本1.0
- UE 版本要求5.5+
- 许可证:[您的许可证信息]
### 参考资源
- 《死亡岛2》的肢解系统https://www.youtube.com/watch?v=d3VrPOm-KDE
- Dead Island 2 的 FLESH 系统https://www.youtube.com/watch?v=GW7N83E1NqY&t=542s
- UE5 内置 Geometry Scripthttps://dev.epicgames.com/documentation/en-us/unreal-engine/geometry-scripting-users-guide-in-unreal-engine
### 联系方式
如有问题或建议,请联系:[您的联系信息]
### Real-time Dismemberment Editor Development Guide
#### Key stages
- Core Function Modules
- Real-time Editor Architecture
- Visual Tool Chain
- Physical Simulation Subsystem
- Data Serialization Module
- Anatomical Structure Brush
- Damage Type Preset
- Real-time Effect Preview
- Soft Body Physics Parameters
- Fracture Threshold Curve
- Binary Asset Packaging
- Version Control Integration
- Key Technology Implementation
#### Performance Optimization Strategy
- Asynchronous Physical Calculation:
Assigning Non-critical Physical Simulation Tasks to Task Graph Thread Pool
- Incremental Resource Loading:
Using Streamable Manager to Dynamically Load High-precision Anatomical Resources
#### 资源参考:
-《死亡岛2》的肢解系统https://www.youtube.com/watch?v=d3VrPOm-KDE
- Dead Island 2 的 FLESH 系统如何程序性地破坏皮肤和骨骼https://www.youtube.com/watch?v=GW7N83E1NqY&t=542s
- Dead Island 2 - F.L.E.S.H. Gore System (Melee): https://www.youtube.com/watch?v=LQEVydDlFuk
- Full analysis of the gore effects: https://www.youtube.com/watch?v=Y23zTuc2drk
Hey there and welcome to Random Madness! This channel is about showcasing and discussing response mechanics in video games. Blood and gore effects, destruction, vehicle damage and so on. Here's an overarching video on this theme:
https://www.youtube.com/watch?v=U4-lOPreM0U
You can find the discussion videos of character responses in this playlist:
https://www.youtube.com/playlist?list=PLE8BZenM9K-AH6yrlnnWkhXmvhlQCsMIj
You can find videos on the topic of destruction and vehicle damage here:
https://www.youtube.com/playlist?list=PLE8BZenM9K-AExTsE9779PQjZ-CcCIh3g
If you like the music you hear on the channel, you can find it here. It mostly leans towards heavy metal, with elements of thrash, prog and other influences as well:
https://www.youtube.com/playlist?list=PLE8BZenM9K-ApYtWLdwAZhJoM44mLfLnQ
https://open.spotify.com/playlist/4C2tqInaRi8gxg7TI7gYDI
That was showcased in the 3rd demonstration, it will be mentioned in the full commentary too
https://www.youtube.com/watch?v=CjC6SAomB6E
### 参考资源
###### UE5 Bult-in
- Geometry Script:
https://dev.epicgames.com/documentation/en-us/unreal-engine/geometry-scripting-users-guide-in-unreal-engine
- Procedural Mesh components:
https://forums.unrealengine.com/t/converting-skeletal-mesh-to-procedural-mesh/1718443
https://zhuanlan.zhihu.com/p/358074568
###### ThirdPart
- Gore and Dismemberment Plugin (BlueprintExposed):
SourceCode: Games\Lyra\Plugins\FLESH\Reference\UEGoreSystem
Doc: https://github.com/Klian326/-Public-UEGoreSystem/wiki/4.-Procedural-Control-Rig#lyra
Setup process: https://github.com/Klian326/-Public-UEGoreSystem/wiki/4.-Procedural-Control-Rig#lyra
- Skinned Decal Component:
SourceCode: Games\Lyra\Plugins\FLESH\Reference\SkinnedDecalComponent
Fab link: https://www.fab.com/listings/7491af07-f541-493d-a78f-d7fa5d466a0d
Docshttps://ac.virtuosgames.com:8443/display/TSZO/Skinned+Decal+Component+Documentation
- EnGore Procedural Dismemberment:
SourceCode: Games\Lyra\Plugins\FLESH\Reference\EnGoreProceduralDismemberment
Fab: EnGore: https://www.fab.com/listings/cb680946-6bde-445f-9ebe-3cecf4bec21e
Demo: EnGore: https://www.youtube.com/watch?v=MN31jCDQ5zY
Doc: https://app.milanote.com/1MQHr51cI5RJ2H/dismemberment-system?p=XmVzKYwdfSs
- Dismemberment:
Github: Thyke/Dismemberment: Advanced dismemberment system for Unreal Engine, featuring dynamic limb separation, weapon dropping, and customizable body part data.
- Fake Flesh System TechArt:
Github: https://github.com/Vincent-Devine/Fake_Flesh_System_TechArt
- Slice Skeletal Mesh Now:
Github: https://drive.google.com/open?id=12VlbtXGcpTXCVoVphhX3ZawnmmvD_AfM
#### Hierarchical modeling of anatomical structures
Use multi-layered materials (4-6 layers) to simulate skin/muscle/bone, and control visibility through material masks
Add secondary collision bodies (such as muscle expansion bodies) to the skeletal system to achieve force deformation
Sample code:
```
// Add physical constraints to the bone modifier
FConstraintInstance* Constraint = SkeletalMesh->AddConstraint(
EConstraintType::Physics,
BoneName,
ParentBoneName,
FTransform::Identity
);
Constraint->SetLinearXMotion(ELinearConstraintMotion::LCM_Limited);
```
#### Real-time cutting algorithm
Plane cutting: Use UE5's APEX Destruction module and combine it with Signed Distance Field to calculate the cutting surface
Dynamic topology reconstruction: Use Procedural Mesh Component to generate a new mesh. Note:
- Vertex cache reuse (to avoid GC jamming)
- Dynamic calculation of normal smoothing groups (to prevent jagged sections)

BIN
Resources/Icon128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,61 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class FLESH : ModuleRules
{
public FLESH(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
"Niagara",
"GeometryCore",
"GeometryFramework",
"GeometryScriptingCore",
"ProceduralMeshComponent",
"PhysicsCore",
"ChaosCloth",
"ChaosNiagara",
"PhysicsControl",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,136 @@
#include "AnatomicalLayerSystem.h"
// Constructor
UAnatomicalLayerSystem::UAnatomicalLayerSystem()
{
}
// Add a new anatomical layer
int32 UAnatomicalLayerSystem::AddLayer(const FName& LayerName, EAnatomicalLayerType LayerType)
{
// Create new layer
FAnatomicalLayer NewLayer;
NewLayer.LayerName = LayerName;
NewLayer.LayerType = LayerType;
// Set default properties based on type
switch (LayerType)
{
case EAnatomicalLayerType::Skin:
NewLayer.Thickness = 0.5f;
NewLayer.Density = 1.0f;
NewLayer.Elasticity = 0.7f;
NewLayer.FractureThreshold = 50.0f;
break;
case EAnatomicalLayerType::Muscle:
NewLayer.Thickness = 1.0f;
NewLayer.Density = 1.2f;
NewLayer.Elasticity = 0.6f;
NewLayer.FractureThreshold = 80.0f;
break;
case EAnatomicalLayerType::Bone:
NewLayer.Thickness = 2.0f;
NewLayer.Density = 1.5f;
NewLayer.Elasticity = 0.3f;
NewLayer.FractureThreshold = 150.0f;
break;
case EAnatomicalLayerType::Organ:
NewLayer.Thickness = 1.5f;
NewLayer.Density = 1.1f;
NewLayer.Elasticity = 0.8f;
NewLayer.FractureThreshold = 30.0f;
break;
case EAnatomicalLayerType::Blood:
NewLayer.Thickness = 0.1f;
NewLayer.Density = 1.0f;
NewLayer.Elasticity = 0.9f;
NewLayer.FractureThreshold = 10.0f;
break;
default:
break;
}
// Add to array
Layers.Add(NewLayer);
// Return index
return Layers.Num() - 1;
}
// Remove anatomical layer at specified index
bool UAnatomicalLayerSystem::RemoveLayer(int32 LayerIndex)
{
// Check if index is valid
if (Layers.IsValidIndex(LayerIndex))
{
Layers.RemoveAt(LayerIndex);
return true;
}
return false;
}
// Get anatomical layer at specified index
FAnatomicalLayer UAnatomicalLayerSystem::GetLayer(int32 LayerIndex) const
{
// Check if index is valid
if (Layers.IsValidIndex(LayerIndex))
{
return Layers[LayerIndex];
}
// Return empty layer
return FAnatomicalLayer();
}
// Get all anatomical layers
TArray<FAnatomicalLayer> UAnatomicalLayerSystem::GetAllLayers() const
{
return Layers;
}
// Set anatomical layer data at specified index
bool UAnatomicalLayerSystem::SetLayer(int32 LayerIndex, const FAnatomicalLayer& Layer)
{
// Check if index is valid
if (Layers.IsValidIndex(LayerIndex))
{
Layers[LayerIndex] = Layer;
return true;
}
return false;
}
// Get layer count
int32 UAnatomicalLayerSystem::GetLayerCount() const
{
return Layers.Num();
}
// Get layers by type
TArray<FAnatomicalLayer> UAnatomicalLayerSystem::GetLayersByType(EAnatomicalLayerType LayerType) const
{
TArray<FAnatomicalLayer> Result;
// Iterate through all layers, find layers matching the type
for (const FAnatomicalLayer& Layer : Layers)
{
if (Layer.LayerType == LayerType)
{
Result.Add(Layer);
}
}
return Result;
}
// Set physics for all layers
void UAnatomicalLayerSystem::SetPhysicsForAllLayers(bool bEnable)
{
// Iterate through all layers, set physics property
for (FAnatomicalLayer& Layer : Layers)
{
Layer.bEnablePhysics = bEnable;
}
}

View File

@@ -0,0 +1,114 @@
#include "BloodPool.h"
#include "Components/BoxComponent.h"
#include "Components/DecalComponent.h"
#include "Materials/MaterialInterface.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "TimerManager.h"
// Sets default values
ABloodPool::ABloodPool()
{
// Set this actor to call Tick() every frame
PrimaryActorTick.bCanEverTick = false;
// Create and set up the collision component
Collision = CreateDefaultSubobject<UBoxComponent>(TEXT("Collision"));
RootComponent = Collision;
Collision->SetCollisionProfileName(TEXT("NoCollision"));
Collision->SetBoxExtent(FVector(1.0f, 100.0f, 100.0f));
// Create and set up the decal component
Decal = CreateDefaultSubobject<UDecalComponent>(TEXT("Decal"));
Decal->SetupAttachment(RootComponent);
Decal->SetRelativeRotation(FRotator(90.0f, 0.0f, 0.0f));
Decal->DecalSize = FVector(10.0f, 100.0f, 100.0f);
}
// Called when the game starts or when spawned
void ABloodPool::BeginPlay()
{
Super::BeginPlay();
// Set the initial decal material
if (DecalMaterial)
{
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(DecalMaterial, this);
if (DynamicMaterial)
{
// Set the color parameter if it exists
DynamicMaterial->SetVectorParameterValue(FName("Color"), PoolColor);
Decal->SetMaterial(0, DynamicMaterial);
}
}
// Update the pool size
UpdatePoolSize();
}
void ABloodPool::SetPoolSize(float NewSize)
{
PoolSize = FMath::Max(0.1f, NewSize);
UpdatePoolSize();
}
void ABloodPool::SetPoolColor(const FLinearColor& NewColor)
{
PoolColor = NewColor;
// Update the material color
if (Decal && Decal->GetMaterial(0))
{
UMaterialInstanceDynamic* DynamicMaterial = Cast<UMaterialInstanceDynamic>(Decal->GetMaterial(0));
if (DynamicMaterial)
{
DynamicMaterial->SetVectorParameterValue(FName("Color"), PoolColor);
}
}
}
void ABloodPool::StartExpansion(float ExpansionRate, float MaxSize)
{
this->ExpansionRate = ExpansionRate;
MaxPoolSize = MaxSize;
bIsExpanding = true;
// Start the expansion timer
GetWorldTimerManager().SetTimer(ExpansionTimerHandle, this, &ABloodPool::ExpandPool, 0.1f, true);
}
void ABloodPool::UpdatePoolSize()
{
// Update the decal size
if (Decal)
{
Decal->DecalSize = FVector(10.0f, 100.0f * PoolSize, 100.0f * PoolSize);
}
// Update the collision box size
if (Collision)
{
Collision->SetBoxExtent(FVector(1.0f, 100.0f * PoolSize, 100.0f * PoolSize));
}
}
void ABloodPool::ExpandPool()
{
if (!bIsExpanding)
{
return;
}
// Increase the pool size
PoolSize += ExpansionRate * 0.1f;
// Check if we've reached the maximum size
if (PoolSize >= MaxPoolSize)
{
PoolSize = MaxPoolSize;
bIsExpanding = false;
GetWorldTimerManager().ClearTimer(ExpansionTimerHandle);
}
// Update the pool size
UpdatePoolSize();
}

View File

@@ -0,0 +1,166 @@
#include "BloodSystem.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "Components/DecalComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"
// Sets default values for this component's properties
UBloodSystem::UBloodSystem()
{
// Set this component to be initialized when the game starts, and to be ticked every frame
PrimaryComponentTick.bCanEverTick = true;
}
// Called when the game starts
void UBloodSystem::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void UBloodSystem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Clean up finished blood effects
for (int32 i = ActiveBloodEffects.Num() - 1; i >= 0; --i)
{
if (!ActiveBloodEffects[i] || !ActiveBloodEffects[i]->IsActive())
{
ActiveBloodEffects.RemoveAt(i);
}
}
}
void UBloodSystem::SpawnBloodEffect(const FVector& Location, const FVector& Direction, float Intensity)
{
// Ensure we have a valid blood spray system
if (!BloodSpraySystem)
{
return;
}
// Manage the number of active blood effects
if (ActiveBloodEffects.Num() >= MaxBloodEffects)
{
// Remove the oldest blood effect
if (ActiveBloodEffects[0])
{
ActiveBloodEffects[0]->DestroyComponent();
}
ActiveBloodEffects.RemoveAt(0);
}
// Create a rotation from the direction
FRotator Rotation = Direction.Rotation();
// Spawn the blood effect
UNiagaraComponent* BloodEffect = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(),
BloodSpraySystem,
Location,
Rotation,
FVector(Intensity),
true,
true,
ENCPoolMethod::AutoRelease
);
if (BloodEffect)
{
// Set the intensity parameter if it exists
BloodEffect->SetFloatParameter(FName("Intensity"), Intensity);
// Add to active blood effects
ActiveBloodEffects.Add(BloodEffect);
}
}
AActor* UBloodSystem::CreateBloodPool(const FVector& Location, float Size)
{
// Ensure we have a valid blood pool class
if (!BloodPoolClass)
{
return nullptr;
}
// Manage the number of active blood pools
if (ActiveBloodPools.Num() >= MaxBloodPools)
{
// Remove the oldest blood pool
if (ActiveBloodPools[0])
{
ActiveBloodPools[0]->Destroy();
}
ActiveBloodPools.RemoveAt(0);
}
// Spawn the blood pool
FActorSpawnParameters SpawnParams;
SpawnParams.Owner = GetOwner();
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
// Trace downward to find the floor
FHitResult HitResult;
FVector TraceStart = Location;
FVector TraceEnd = Location - FVector(0, 0, 200.0f);
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActor(GetOwner());
if (GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_Visibility, QueryParams))
{
// Spawn at the hit location
AActor* BloodPool = GetWorld()->SpawnActor<AActor>(BloodPoolClass, HitResult.Location, FRotator::ZeroRotator, SpawnParams);
if (BloodPool)
{
// Set the size of the blood pool
BloodPool->SetActorScale3D(FVector(Size));
// Add to active blood pools
ActiveBloodPools.Add(BloodPool);
return BloodPool;
}
}
return nullptr;
}
UDecalComponent* UBloodSystem::ApplyBloodDecal(UPrimitiveComponent* Component, const FVector& Location, const FVector& Direction, float Size)
{
// Ensure we have a valid blood decal material
if (!BloodDecalMaterial || !Component)
{
return nullptr;
}
// Manage the number of active blood decals
if (ActiveBloodDecals.Num() >= MaxBloodDecals)
{
// Remove the oldest blood decal
if (ActiveBloodDecals[0])
{
ActiveBloodDecals[0]->DestroyComponent();
}
ActiveBloodDecals.RemoveAt(0);
}
// Create a rotation from the direction (opposite to the direction for the decal to face outward)
FRotator Rotation = (-Direction).Rotation();
// Create the decal component
UDecalComponent* DecalComponent = NewObject<UDecalComponent>(Component);
DecalComponent->SetupAttachment(Component);
DecalComponent->SetRelativeLocation(Component->GetComponentTransform().InverseTransformPosition(Location));
DecalComponent->SetRelativeRotation(Component->GetComponentTransform().InverseTransformRotation(Rotation.Quaternion()));
DecalComponent->SetDecalMaterial(BloodDecalMaterial);
DecalComponent->DecalSize = FVector(10.0f, Size * 100.0f, Size * 100.0f);
DecalComponent->RegisterComponent();
// Add to active blood decals
ActiveBloodDecals.Add(DecalComponent);
return DecalComponent;
}

View File

@@ -0,0 +1,283 @@
#include "BooleanCutTool.h"
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"
#include "ProceduralMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "GeometryScriptingCore.h"
#include "GeometryScript/MeshBooleanFunctions.h"
#include "GeometryScript/MeshPrimitiveFunctions.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "GeometryScript/MeshTransformFunctions.h"
// Constructor
UBooleanCutTool::UBooleanCutTool()
{
}
// Perform boolean cut on static mesh
TArray<UStaticMesh*> UBooleanCutTool::CutStaticMesh(UStaticMesh* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap)
{
TArray<UStaticMesh*> Result;
// Check if target mesh is valid
if (!TargetMesh)
{
return Result;
}
// Create cut plane mesh
UStaticMesh* CutPlaneMesh = CreateCutPlaneMesh(CutPlane);
if (!CutPlaneMesh)
{
return Result;
}
// Use GeometryScript to perform boolean cut
// Note: This is just a framework, actual implementation requires GeometryScript API
// Create positive part mesh
UStaticMesh* PositiveMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for positive part
// Create negative part mesh
UStaticMesh* NegativeMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for negative part
// If cap creation is needed
if (bCreateCap)
{
// TODO: Create cap for positive and negative parts
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
// Perform boolean cut on skeletal mesh
TArray<USkeletalMesh*> UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName, bool bCreateCap)
{
TArray<USkeletalMesh*> Result;
// Check if target mesh is valid
if (!TargetMesh)
{
return Result;
}
// Create cut plane mesh
UStaticMesh* CutPlaneMesh = CreateCutPlaneMesh(CutPlane);
if (!CutPlaneMesh)
{
return Result;
}
// Use GeometryScript to perform boolean cut
// Note: This is just a framework, actual implementation requires GeometryScript API
// If bone name is specified, only cut the part influenced by the bone
if (BoneName != NAME_None)
{
// TODO: Get vertices influenced by the bone, only cut these vertices
}
// Create positive part skeletal mesh
USkeletalMesh* PositiveMesh = NewObject<USkeletalMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for positive part
// Create negative part skeletal mesh
USkeletalMesh* NegativeMesh = NewObject<USkeletalMesh>();
// TODO: Use GeometryScript to perform boolean difference operation for negative part
// If cap creation is needed
if (bCreateCap)
{
// TODO: Create cap for positive and negative parts
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
// Perform boolean cut on procedural mesh
TArray<UProceduralMeshComponent*> UBooleanCutTool::CutProceduralMesh(UProceduralMeshComponent* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap)
{
TArray<UProceduralMeshComponent*> Result;
// Check if target mesh is valid
if (!TargetMesh)
{
return Result;
}
// Get mesh data
TArray<FVector> Vertices;
TArray<int32> Triangles;
TArray<FVector> Normals;
TArray<FVector2D> UVs;
TArray<FColor> VertexColors;
TArray<FProcMeshTangent> Tangents;
TargetMesh->GetSectionMeshData(0, Vertices, Triangles, Normals, UVs, VertexColors, Tangents);
// Calculate intersection points between cut plane and mesh
TArray<FVector> IntersectionPoints = CalculateIntersectionPoints(Vertices, Triangles, CutPlane);
// Create positive part procedural mesh
UProceduralMeshComponent* PositiveMesh = NewObject<UProceduralMeshComponent>(TargetMesh->GetOwner());
PositiveMesh->RegisterComponent();
// Create negative part procedural mesh
UProceduralMeshComponent* NegativeMesh = NewObject<UProceduralMeshComponent>(TargetMesh->GetOwner());
NegativeMesh->RegisterComponent();
// Split mesh
TArray<FVector> PositiveVertices;
TArray<int32> PositiveTriangles;
TArray<FVector> PositiveNormals;
TArray<FVector2D> PositiveUVs;
TArray<FColor> PositiveVertexColors;
TArray<FProcMeshTangent> PositiveTangents;
TArray<FVector> NegativeVertices;
TArray<int32> NegativeTriangles;
TArray<FVector> NegativeNormals;
TArray<FVector2D> NegativeUVs;
TArray<FColor> NegativeVertexColors;
TArray<FProcMeshTangent> NegativeTangents;
// TODO: Split mesh data based on cut plane
// Create positive part mesh
PositiveMesh->CreateMeshSection(0, PositiveVertices, PositiveTriangles, PositiveNormals, PositiveUVs, PositiveVertexColors, PositiveTangents, true);
// Create negative part mesh
NegativeMesh->CreateMeshSection(0, NegativeVertices, NegativeTriangles, NegativeNormals, NegativeUVs, NegativeVertexColors, NegativeTangents, true);
// If cap creation is needed
if (bCreateCap)
{
CreateCapMesh(IntersectionPoints, CutPlane, PositiveMesh);
CreateCapMesh(IntersectionPoints, CutPlane, NegativeMesh);
}
// Set material
if (CutMaterial)
{
PositiveMesh->SetMaterial(0, TargetMesh->GetMaterial(0));
PositiveMesh->SetMaterial(1, CutMaterial);
NegativeMesh->SetMaterial(0, TargetMesh->GetMaterial(0));
NegativeMesh->SetMaterial(1, CutMaterial);
}
// Add to result array
Result.Add(PositiveMesh);
Result.Add(NegativeMesh);
return Result;
}
// Create cut plane mesh
UStaticMesh* UBooleanCutTool::CreateCutPlaneMesh(const FCutPlane& CutPlane)
{
// Create a plane mesh
UStaticMesh* PlaneMesh = NewObject<UStaticMesh>();
// TODO: Use GeometryScript to create plane mesh
// Note: This is just a framework, actual implementation requires GeometryScript API
return PlaneMesh;
}
// Set cut material
void UBooleanCutTool::SetCutMaterial(UMaterialInterface* Material)
{
CutMaterial = Material;
}
// Get cut material
UMaterialInterface* UBooleanCutTool::GetCutMaterial() const
{
return CutMaterial;
}
// Calculate intersection points between cut plane and mesh
TArray<FVector> UBooleanCutTool::CalculateIntersectionPoints(const TArray<FVector>& Vertices, const TArray<int32>& Indices, const FCutPlane& CutPlane)
{
TArray<FVector> IntersectionPoints;
// Check if index array is for triangles
if (Indices.Num() % 3 != 0)
{
return IntersectionPoints;
}
// Iterate through all triangles
for (int32 i = 0; i < Indices.Num(); i += 3)
{
// Get three vertices of the triangle
FVector V0 = Vertices[Indices[i]];
FVector V1 = Vertices[Indices[i + 1]];
FVector V2 = Vertices[Indices[i + 2]];
// Calculate signed distance from each vertex to the cut plane
float D0 = FVector::DotProduct(V0 - CutPlane.Location, CutPlane.Normal);
float D1 = FVector::DotProduct(V1 - CutPlane.Location, CutPlane.Normal);
float D2 = FVector::DotProduct(V2 - CutPlane.Location, CutPlane.Normal);
// Check if triangle intersects with cut plane
if ((D0 * D1 <= 0.0f) || (D0 * D2 <= 0.0f) || (D1 * D2 <= 0.0f))
{
// Calculate intersection points of edges with cut plane
if (D0 * D1 <= 0.0f)
{
// Edge V0-V1 intersects with cut plane
float T = D0 / (D0 - D1);
FVector IntersectionPoint = V0 + T * (V1 - V0);
IntersectionPoints.Add(IntersectionPoint);
}
if (D0 * D2 <= 0.0f)
{
// Edge V0-V2 intersects with cut plane
float T = D0 / (D0 - D2);
FVector IntersectionPoint = V0 + T * (V2 - V0);
IntersectionPoints.Add(IntersectionPoint);
}
if (D1 * D2 <= 0.0f)
{
// Edge V1-V2 intersects with cut plane
float T = D1 / (D1 - D2);
FVector IntersectionPoint = V1 + T * (V2 - V1);
IntersectionPoints.Add(IntersectionPoint);
}
}
}
return IntersectionPoints;
}
// Create cap mesh
void UBooleanCutTool::CreateCapMesh(const TArray<FVector>& IntersectionPoints, const FCutPlane& CutPlane, UProceduralMeshComponent* TargetMesh)
{
// Check if there are enough intersection points
if (IntersectionPoints.Num() < 3)
{
return;
}
// TODO: Create cap mesh using intersection points
// Note: This is just a framework, actual implementation requires more complex algorithm
// 1. Project intersection points onto cut plane
// 2. Triangulate projected points
// 3. Create cap mesh
// 4. Add to target mesh
}

View File

@@ -0,0 +1,276 @@
#include "DismembermentSystem.h"
#include "ProceduralMeshComponent.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Components/SkeletalMeshComponent.h"
#include "NiagaraComponent.h"
#include "NiagaraFunctionLibrary.h"
#include "Materials/MaterialInterface.h"
#include "Kismet/GameplayStatics.h"
// Sets default values for this component's properties
UDismembermentSystem::UDismembermentSystem()
{
// Set this component to be initialized when the game starts, and to be ticked every frame
PrimaryComponentTick.bCanEverTick = true;
}
// Called when the game starts
void UDismembermentSystem::BeginPlay()
{
Super::BeginPlay();
// Get the skeletal mesh component from the owner
AActor* Owner = GetOwner();
if (Owner)
{
TargetMesh = Owner->FindComponentByClass<USkeletalMeshComponent>();
}
}
// Called every frame
void UDismembermentSystem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}
bool UDismembermentSystem::PerformCut(const FVector& CutLocation, const FVector& CutDirection, float CutWidth, float CutDepth)
{
// Ensure we have a valid target mesh
if (!TargetMesh)
{
return false;
}
// Implementation will use Geometry Script to perform boolean cutting operations
// This is a placeholder for the actual implementation
// Convert world location to local space
FVector LocalCutLocation = TargetMesh->GetComponentTransform().InverseTransformPosition(CutLocation);
FVector LocalCutDirection = TargetMesh->GetComponentTransform().InverseTransformVector(CutDirection);
// TODO: Implement real-time boolean cutting using Geometry Script
return true;
}
bool UDismembermentSystem::DismemberAtBone(const FName& BoneName, const FVector& CutDirection, EDismembermentType DismembermentType)
{
// Ensure we have a valid target mesh
if (!TargetMesh)
{
return false;
}
// Check if the bone exists
int32 BoneIndex = TargetMesh->GetBoneIndex(BoneName);
if (BoneIndex == INDEX_NONE)
{
return false;
}
// Find the bone data
int32 RegisteredBoneIndex = FindBoneIndex(BoneName);
if (RegisteredBoneIndex == INDEX_NONE)
{
// Bone not registered for dismemberment
return false;
}
// Check if the bone is already dismembered
if (RegisteredBones[RegisteredBoneIndex].bIsDismembered)
{
// Already dismembered
return false;
}
// Get the transform of the bone
FTransform BoneTransform = TargetMesh->GetBoneTransform(BoneIndex);
// Create a procedural mesh for the dismembered part
UProceduralMeshComponent* DismemberedPart = CreateProceduralMeshFromSkeletalMesh(TargetMesh, 0, 0);
if (!DismemberedPart)
{
return false;
}
// Add the dismembered part to the map
DismemberedParts.Add(BoneName, DismemberedPart);
// Mark the bone as dismembered
RegisteredBones[RegisteredBoneIndex].bIsDismembered = true;
// Spawn blood effect at the cut location
SpawnBloodEffect(BoneTransform.GetLocation(), CutDirection);
// Hide the bone in the original skeletal mesh
// This requires modifying the skeletal mesh's vertex weights or using a material to hide the bone
return true;
}
bool UDismembermentSystem::ApplyBoneDamage(const FName& BoneName, float Damage, const FVector& DamageLocation, const FVector& DamageDirection, EDismembermentType DismembermentType)
{
// Find the bone data
int32 RegisteredBoneIndex = FindBoneIndex(BoneName);
if (RegisteredBoneIndex == INDEX_NONE)
{
// Bone not registered for dismemberment
return false;
}
// Check if the bone is already dismembered
if (RegisteredBones[RegisteredBoneIndex].bIsDismembered)
{
// Already dismembered
return false;
}
// Apply damage to the bone
RegisteredBones[RegisteredBoneIndex].Health -= Damage;
// Check if the bone should be dismembered
if (RegisteredBones[RegisteredBoneIndex].Health <= RegisteredBones[RegisteredBoneIndex].DismembermentThreshold)
{
// Dismember the bone
return DismemberAtBone(BoneName, DamageDirection, DismembermentType);
}
// Spawn a smaller blood effect to indicate damage
if (Damage > 0)
{
SpawnBloodEffect(DamageLocation, DamageDirection, FMath::Clamp(Damage / 100.0f, 0.1f, 1.0f));
}
return true;
}
bool UDismembermentSystem::RegisterBone(const FDismembermentBoneData& BoneData)
{
// Check if the bone already exists
for (int32 i = 0; i < RegisteredBones.Num(); ++i)
{
if (RegisteredBones[i].BoneName == BoneData.BoneName)
{
// Update existing bone data
RegisteredBones[i] = BoneData;
return true;
}
}
// Add new bone data
RegisteredBones.Add(BoneData);
return true;
}
void UDismembermentSystem::SetBloodEffect(UNiagaraSystem* BloodEffect)
{
BloodEffectSystem = BloodEffect;
}
void UDismembermentSystem::SetCutMaterial(UMaterialInterface* CutMaterial)
{
CutSurfaceMaterial = CutMaterial;
}
bool UDismembermentSystem::GetBoneData(const FName& BoneName, FDismembermentBoneData& OutBoneData) const
{
int32 BoneIndex = FindBoneIndex(BoneName);
if (BoneIndex != INDEX_NONE)
{
OutBoneData = RegisteredBones[BoneIndex];
return true;
}
return false;
}
TArray<FDismembermentBoneData> UDismembermentSystem::GetAllBones() const
{
return RegisteredBones;
}
void UDismembermentSystem::ResetDismemberment()
{
// Reset all bones
for (int32 i = 0; i < RegisteredBones.Num(); ++i)
{
RegisteredBones[i].bIsDismembered = false;
RegisteredBones[i].Health = 100.0f;
}
// Destroy all dismembered parts
for (auto& Part : DismemberedParts)
{
if (Part.Value)
{
Part.Value->DestroyComponent();
}
}
DismemberedParts.Empty();
// Show all bones in the original skeletal mesh
// This requires restoring the skeletal mesh's vertex weights or material
}
UProceduralMeshComponent* UDismembermentSystem::CreateProceduralMeshFromSkeletalMesh(USkeletalMeshComponent* SkelMesh, int32 LODIndex, int32 SectionIndex)
{
// Ensure we have a valid skeletal mesh
if (!SkelMesh)
{
return nullptr;
}
// Create a new procedural mesh component
UProceduralMeshComponent* ProcMesh = NewObject<UProceduralMeshComponent>(GetOwner());
ProcMesh->RegisterComponent();
// TODO: Extract mesh data from the skeletal mesh and create a procedural mesh
// This will involve:
// 1. Getting vertex positions, normals, UVs, etc. from the skeletal mesh
// 2. Creating triangles for the procedural mesh
// 3. Setting up materials
// 4. Setting up collision
return ProcMesh;
}
void UDismembermentSystem::SpawnBloodEffect(const FVector& Location, const FVector& Direction, float Intensity)
{
// Ensure we have a valid blood effect system
if (!BloodEffectSystem)
{
return;
}
// Create a rotation from the direction
FRotator Rotation = Direction.Rotation();
// Spawn the blood effect
UNiagaraComponent* BloodEffect = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(),
BloodEffectSystem,
Location,
Rotation,
FVector(Intensity),
true,
true,
ENCPoolMethod::AutoRelease
);
if (BloodEffect)
{
// Set the intensity parameter if it exists
BloodEffect->SetFloatParameter(FName("Intensity"), Intensity);
}
}
int32 UDismembermentSystem::FindBoneIndex(const FName& BoneName) const
{
for (int32 i = 0; i < RegisteredBones.Num(); ++i)
{
if (RegisteredBones[i].BoneName == BoneName)
{
return i;
}
}
return INDEX_NONE;
}

View File

@@ -0,0 +1,20 @@
#include "FLESHModule.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "FFLESHModule"
void FFLESHModule::StartupModule()
{
// This code will execute after your module is loaded into memory
// The exact timing is specified in the .uplugin file per-module
}
void FFLESHModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module
// For modules that support dynamic reloading, we call this function before unloading the module
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FFLESHModule, FLESH)

View File

@@ -0,0 +1,159 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "AnatomicalLayerSystem.generated.h"
/**
* Anatomical layer type enumeration
*/
UENUM(BlueprintType)
enum class EAnatomicalLayerType : uint8
{
Skin,
Muscle,
Bone,
Organ,
Blood,
Custom
};
/**
* Anatomical layer data structure
*/
USTRUCT(BlueprintType)
struct FAnatomicalLayer
{
GENERATED_BODY()
// Layer name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
FName LayerName;
// Layer type
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
EAnatomicalLayerType LayerType;
// Layer mesh
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
TObjectPtr<UStaticMesh> LayerMesh;
// Layer material
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
TObjectPtr<UMaterialInterface> LayerMaterial;
// Layer thickness
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
float Thickness = 1.0f;
// Layer density
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
float Density = 1.0f;
// Layer elasticity
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
float Elasticity = 0.5f;
// Layer fracture threshold
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
float FractureThreshold = 100.0f;
// Enable physics
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
bool bEnablePhysics = true;
// Is visible
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Anatomy")
bool bVisible = true;
// Constructor
FAnatomicalLayer()
: LayerName(NAME_None)
, LayerType(EAnatomicalLayerType::Skin)
, LayerMesh(nullptr)
, LayerMaterial(nullptr)
{
}
};
/**
* Anatomical layer system class
* Manages multi-layer system for bones, organs and physics properties
*/
UCLASS(BlueprintType, Blueprintable)
class FLESH_API UAnatomicalLayerSystem : public UObject
{
GENERATED_BODY()
public:
// Constructor
UAnatomicalLayerSystem();
/**
* Add a new anatomical layer
* @param LayerName - Layer name
* @param LayerType - Layer type
* @return Index of the newly added layer
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
int32 AddLayer(const FName& LayerName, EAnatomicalLayerType LayerType);
/**
* Remove an anatomical layer by index
* @param LayerIndex - Layer index
* @return Whether the removal was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
bool RemoveLayer(int32 LayerIndex);
/**
* Get an anatomical layer by index
* @param LayerIndex - Layer index
* @return Anatomical layer data
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
FAnatomicalLayer GetLayer(int32 LayerIndex) const;
/**
* Get all anatomical layers
* @return Array of anatomical layers
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
TArray<FAnatomicalLayer> GetAllLayers() const;
/**
* Set an anatomical layer by index
* @param LayerIndex - Layer index
* @param Layer - New layer data
* @return Whether the setting was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
bool SetLayer(int32 LayerIndex, const FAnatomicalLayer& Layer);
/**
* Get the number of layers
* @return Number of layers
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
int32 GetLayerCount() const;
/**
* Get layers by type
* @param LayerType - Layer type
* @return Array of layers matching the type
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
TArray<FAnatomicalLayer> GetLayersByType(EAnatomicalLayerType LayerType) const;
/**
* Set physics for all layers
* @param bEnable - Whether to enable physics
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Anatomy")
void SetPhysicsForAllLayers(bool bEnable);
private:
// Array of anatomical layers
UPROPERTY(EditAnywhere, Category = "FLESH|Anatomy")
TArray<FAnatomicalLayer> Layers;
};

View File

@@ -0,0 +1,92 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "BloodPool.generated.h"
// Forward declarations
class UBoxComponent;
class UDecalComponent;
class UMaterialInterface;
/**
* Blood pool actor class, used to create blood pools on surfaces
*/
UCLASS()
class FLESH_API ABloodPool : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ABloodPool();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
/**
* Set the blood pool size
* @param NewSize - New size for the blood pool
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
void SetPoolSize(float NewSize);
/**
* Set the blood pool color
* @param NewColor - New color for the blood pool
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
void SetPoolColor(const FLinearColor& NewColor);
/**
* Start blood pool expansion
* @param ExpansionRate - Rate of expansion
* @param MaxSize - Maximum size
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
void StartExpansion(float ExpansionRate = 1.0f, float MaxSize = 3.0f);
private:
// Collision component for surface detection
UPROPERTY(VisibleAnywhere, Category = "Components")
TObjectPtr<UBoxComponent> Collision;
// Decal component for rendering the blood pool
UPROPERTY(VisibleAnywhere, Category = "Components")
TObjectPtr<UDecalComponent> Decal;
// Blood pool decal material
UPROPERTY(EditAnywhere, Category = "Appearance")
TObjectPtr<UMaterialInterface> DecalMaterial;
// Current blood pool size
UPROPERTY(EditAnywhere, Category = "Appearance")
float PoolSize = 1.0f;
// Blood pool color
UPROPERTY(EditAnywhere, Category = "Appearance")
FLinearColor PoolColor = FLinearColor::Red;
// Expansion rate
UPROPERTY(EditAnywhere, Category = "Expansion")
float ExpansionRate = 0.5f;
// Maximum blood pool size
UPROPERTY(EditAnywhere, Category = "Expansion")
float MaxPoolSize = 2.0f;
// Whether the pool is expanding
UPROPERTY()
bool bIsExpanding = false;
// Blood pool expansion timer handle
FTimerHandle ExpansionTimerHandle;
// Internal function to update the blood pool size
void UpdatePoolSize();
// Internal function to expand the blood pool
void ExpandPool();
};

View File

@@ -0,0 +1,98 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "NiagaraSystem.h"
#include "BloodSystem.generated.h"
// Forward declarations
class UNiagaraComponent;
class UDecalComponent;
/**
* Blood system component for the FLESH plugin
* Handles blood effects, pools, and decals
*/
UCLASS(ClassGroup=(FLESH), meta=(BlueprintSpawnableComponent))
class FLESH_API UBloodSystem : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UBloodSystem();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* Spawn blood effect at the specified location
* @param Location - World location to spawn the blood effect
* @param Direction - Direction of the blood spray
* @param Intensity - Intensity of the blood effect (0.0-1.0)
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
void SpawnBloodEffect(const FVector& Location, const FVector& Direction, float Intensity = 1.0f);
/**
* Create a blood pool at the specified location
* @param Location - World location to create the blood pool
* @param Size - Size of the blood pool
* @return The created blood pool actor
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
AActor* CreateBloodPool(const FVector& Location, float Size = 1.0f);
/**
* Apply a blood decal to the specified component
* @param Component - Component to apply the blood decal to
* @param Location - World location of the decal
* @param Direction - Direction the decal should face
* @param Size - Size of the decal
* @return The created decal component
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Blood")
UDecalComponent* ApplyBloodDecal(UPrimitiveComponent* Component, const FVector& Location, const FVector& Direction, float Size = 1.0f);
private:
// Niagara system for blood spray effects
UPROPERTY(EditAnywhere, Category = "FLESH|Blood", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UNiagaraSystem> BloodSpraySystem;
// Material for blood decals
UPROPERTY(EditAnywhere, Category = "FLESH|Blood", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UMaterialInterface> BloodDecalMaterial;
// Blood pool class to spawn
UPROPERTY(EditAnywhere, Category = "FLESH|Blood", meta = (AllowPrivateAccess = "true"))
TSubclassOf<AActor> BloodPoolClass;
// Active blood effects
UPROPERTY()
TArray<TObjectPtr<UNiagaraComponent>> ActiveBloodEffects;
// Active blood decals
UPROPERTY()
TArray<TObjectPtr<UDecalComponent>> ActiveBloodDecals;
// Active blood pools
UPROPERTY()
TArray<TObjectPtr<AActor>> ActiveBloodPools;
// Maximum number of active blood effects
UPROPERTY(EditAnywhere, Category = "FLESH|Blood|Limits", meta = (AllowPrivateAccess = "true"))
int32 MaxBloodEffects = 10;
// Maximum number of active blood decals
UPROPERTY(EditAnywhere, Category = "FLESH|Blood|Limits", meta = (AllowPrivateAccess = "true"))
int32 MaxBloodDecals = 30;
// Maximum number of active blood pools
UPROPERTY(EditAnywhere, Category = "FLESH|Blood|Limits", meta = (AllowPrivateAccess = "true"))
int32 MaxBloodPools = 5;
};

View File

@@ -0,0 +1,129 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "BooleanCutTool.generated.h"
// Forward declarations
class UStaticMesh;
class USkeletalMesh;
class UProceduralMeshComponent;
/**
* Cut plane definition
*/
USTRUCT(BlueprintType)
struct FCutPlane
{
GENERATED_BODY()
// Cut plane location
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Cutting")
FVector Location;
// Cut plane normal
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Cutting")
FVector Normal;
// Cut plane width
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Cutting")
float Width = 100.0f;
// Cut plane height
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Cutting")
float Height = 100.0f;
// Constructor
FCutPlane()
: Location(FVector::ZeroVector)
, Normal(FVector::UpVector)
{
}
// Constructor with parameters
FCutPlane(const FVector& InLocation, const FVector& InNormal, float InWidth = 100.0f, float InHeight = 100.0f)
: Location(InLocation)
, Normal(InNormal)
, Width(InWidth)
, Height(InHeight)
{
}
};
/**
* Boolean cut tool class
* Provides real-time boolean cutting functionality
*/
UCLASS(BlueprintType)
class FLESH_API UBooleanCutTool : public UObject
{
GENERATED_BODY()
public:
// Constructor
UBooleanCutTool();
/**
* Perform boolean cut on static mesh
* @param TargetMesh - Target static mesh
* @param CutPlane - Cut plane
* @param bCreateCap - Whether to create a cap
* @return Array of cut meshes [0] is positive side, [1] is negative side
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
TArray<UStaticMesh*> CutStaticMesh(UStaticMesh* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap = true);
/**
* Perform boolean cut on skeletal mesh
* @param TargetMesh - Target skeletal mesh
* @param CutPlane - Cut plane
* @param BoneName - Bone name, if specified only cuts the part influenced by this bone
* @param bCreateCap - Whether to create a cap
* @return Array of cut skeletal meshes [0] is positive side, [1] is negative side
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
TArray<USkeletalMesh*> CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName = NAME_None, bool bCreateCap = true);
/**
* Perform boolean cut on procedural mesh
* @param TargetMesh - Target procedural mesh
* @param CutPlane - Cut plane
* @param bCreateCap - Whether to create a cap
* @return Array of cut procedural meshes [0] is positive side, [1] is negative side
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
TArray<UProceduralMeshComponent*> CutProceduralMesh(UProceduralMeshComponent* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap = true);
/**
* Create cut plane mesh
* @param CutPlane - Cut plane
* @return Cut plane mesh
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
UStaticMesh* CreateCutPlaneMesh(const FCutPlane& CutPlane);
/**
* Set cut material
* @param Material - Cut material
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
void SetCutMaterial(UMaterialInterface* Material);
/**
* Get cut material
* @return Current cut material
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Cutting")
UMaterialInterface* GetCutMaterial() const;
private:
// Cut material
UPROPERTY()
TObjectPtr<UMaterialInterface> CutMaterial;
// Internal function to calculate intersection points between cut plane and mesh
TArray<FVector> CalculateIntersectionPoints(const TArray<FVector>& Vertices, const TArray<int32>& Indices, const FCutPlane& CutPlane);
// Internal function to create cap mesh
void CreateCapMesh(const TArray<FVector>& IntersectionPoints, const FCutPlane& CutPlane, UProceduralMeshComponent* TargetMesh);
};

View File

@@ -0,0 +1,87 @@
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "DismemberedAnimInstance.generated.h"
/**
* Animation instance class for dismembered body parts
* Handles animation of separated limbs and body parts
*/
UCLASS()
class FLESH_API UDismemberedAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
// Constructor
UDismemberedAnimInstance();
// Called when animation updates
virtual void NativeUpdateAnimation(float DeltaSeconds) override;
// Called to initialize animation
virtual void NativeInitializeAnimation() override;
/**
* Set the source bone name this dismembered part was cut from
* @param BoneName - Name of the source bone
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Animation")
void SetSourceBone(const FName& BoneName);
/**
* Set the cut type for this dismembered part
* @param CutType - Type of cut that created this part
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Animation")
void SetCutType(int32 CutType);
/**
* Apply an impulse to the dismembered part
* @param Impulse - Impulse vector to apply
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Animation")
void ApplyImpulse(const FVector& Impulse);
private:
// Name of the bone this part was cut from
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName SourceBoneName;
// Type of cut that created this part
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
int32 CutType;
// Root bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName RootBoneName;
// Head bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName HeadBoneName;
// Left arm bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName LeftArmBoneName;
// Right arm bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName RightArmBoneName;
// Left leg bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName LeftLegBoneName;
// Right leg bone name
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FName RightLegBoneName;
// Current angular velocity
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FVector AngularVelocity;
// Current linear velocity
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "FLESH|Animation", meta = (AllowPrivateAccess = "true"))
FVector LinearVelocity;
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DismembermentGraphAsset.generated.h"
/**
* Asset that contains a dismemberment graph
* Used for visual programming of dismemberment logic
*/
UCLASS(BlueprintType)
class FLESH_API UDismembermentGraphAsset : public UObject
{
GENERATED_BODY()
public:
UDismembermentGraphAsset();
// The graph owned by this asset
UPROPERTY()
class UDismembermentGraph* Graph;
// Compile the graph into executable logic
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool CompileGraph();
// Execute the compiled graph
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool ExecuteGraph(class AActor* TargetActor);
#if WITH_EDITOR
// Called when the asset is created
virtual void PostInitProperties() override;
// Called when the asset is duplicated
virtual void PostDuplicate(bool bDuplicateForPIE) override;
// Called when the asset is loaded
virtual void PostLoad() override;
#endif
};

View File

@@ -0,0 +1,204 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "DismembermentSystem.generated.h"
// Forward declarations
class USkeletalMeshComponent;
class UProceduralMeshComponent;
class UNiagaraSystem;
class UMaterialInterface;
/**
* Bone dismemberment type enumeration
*/
UENUM(BlueprintType)
enum class EDismembermentType : uint8
{
None,
Cut, // Clean cut
Tear, // Torn flesh
Crush, // Crushed bone
Blast, // Explosion damage
Burn, // Burn damage
Custom // Custom dismemberment type
};
/**
* Bone data structure for dismemberment
*/
USTRUCT(BlueprintType)
struct FDismembermentBoneData
{
GENERATED_BODY()
// Bone name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
FName BoneName;
// Parent bone name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
FName ParentBoneName;
// Bone health
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
float Health = 100.0f;
// Damage threshold before dismemberment
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
float DismembermentThreshold = 50.0f;
// Blood effect socket name
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
FName BloodSocketName;
// Whether this bone can be dismembered
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
bool bCanBeDismembered = true;
// Whether this bone is critical (death if dismembered)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
bool bIsCritical = false;
// Whether this bone is already dismembered
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Dismemberment")
bool bIsDismembered = false;
// Constructor
FDismembermentBoneData()
: BoneName(NAME_None)
, ParentBoneName(NAME_None)
, BloodSocketName(NAME_None)
{
}
};
/**
* Main component for the FLESH dismemberment system
* Handles real-time boolean cutting, multi-layer cutting, and physics interactions
*/
UCLASS(ClassGroup=(FLESH), meta=(BlueprintSpawnableComponent))
class FLESH_API UDismembermentSystem : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UDismembermentSystem();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* Perform a cut on the owner's mesh at the specified location and direction
* @param CutLocation - World location of the cut
* @param CutDirection - Direction of the cut
* @param CutWidth - Width of the cut
* @param CutDepth - Depth of the cut
* @return True if the cut was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool PerformCut(const FVector& CutLocation, const FVector& CutDirection, float CutWidth = 1.0f, float CutDepth = 10.0f);
/**
* Perform a dismemberment at the specified bone
* @param BoneName - Name of the bone to dismember
* @param CutDirection - Direction of the cut
* @param DismembermentType - Type of dismemberment to perform
* @return True if the dismemberment was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool DismemberAtBone(const FName& BoneName, const FVector& CutDirection, EDismembermentType DismembermentType = EDismembermentType::Cut);
/**
* Apply damage to a specific bone
* @param BoneName - Name of the bone to damage
* @param Damage - Amount of damage to apply
* @param DamageLocation - World location of the damage
* @param DamageDirection - Direction of the damage
* @param DismembermentType - Type of damage for potential dismemberment
* @return True if damage was applied
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool ApplyBoneDamage(const FName& BoneName, float Damage, const FVector& DamageLocation, const FVector& DamageDirection, EDismembermentType DismembermentType = EDismembermentType::Cut);
/**
* Register a bone for dismemberment
* @param BoneData - Bone data to register
* @return True if the bone was registered
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool RegisterBone(const FDismembermentBoneData& BoneData);
/**
* Set the blood effect for dismemberment
* @param BloodEffect - Niagara system for blood effects
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetBloodEffect(UNiagaraSystem* BloodEffect);
/**
* Set the cut material for dismemberment
* @param CutMaterial - Material to use for cut surfaces
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetCutMaterial(UMaterialInterface* CutMaterial);
/**
* Get the bone data for a specific bone
* @param BoneName - Name of the bone
* @param OutBoneData - Output bone data
* @return True if the bone data was found
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool GetBoneData(const FName& BoneName, FDismembermentBoneData& OutBoneData) const;
/**
* Get all registered bones
* @return Array of all registered bone data
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
TArray<FDismembermentBoneData> GetAllBones() const;
/**
* Reset all dismemberment (restore all bones)
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void ResetDismemberment();
private:
// The skeletal mesh to perform dismemberment on
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> TargetMesh;
// Map of bone names to their corresponding procedural mesh components
UPROPERTY()
TMap<FName, TObjectPtr<UProceduralMeshComponent>> DismemberedParts;
// Registered bones for dismemberment
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment")
TArray<FDismembermentBoneData> RegisteredBones;
// Niagara system for blood effects
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment")
TObjectPtr<UNiagaraSystem> BloodEffectSystem;
// Material for cut surfaces
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment")
TObjectPtr<UMaterialInterface> CutSurfaceMaterial;
// Internal function to create a procedural mesh from a skeletal mesh section
UProceduralMeshComponent* CreateProceduralMeshFromSkeletalMesh(USkeletalMeshComponent* SkelMesh, int32 LODIndex, int32 SectionIndex);
// Internal function to spawn blood effects
void SpawnBloodEffect(const FVector& Location, const FVector& Direction, float Intensity = 1.0f);
// Internal function to find bone index by name
int32 FindBoneIndex(const FName& BoneName) const;
};

View File

@@ -0,0 +1,245 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "NiagaraSystem.h"
#include "DismembermentSystem.h"
#include "FLESHDismembermentComponent.generated.h"
// Forward declarations
class USkeletalMeshComponent;
class UNiagaraSystem;
class UMaterialInterface;
/**
* Bone patch type for customizing dismemberment effects
*/
UENUM(BlueprintType)
enum class EBonePatchType : uint8
{
Default, // Default patch
Head, // Head specific patch
Arm, // Arm specific patch
Leg, // Leg specific patch
Torso, // Torso specific patch
Custom // Custom patch
};
/**
* Bone patch data structure for customizing dismemberment effects
*/
USTRUCT(BlueprintType)
struct FBonePatchData
{
GENERATED_BODY()
// Patch type
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
EBonePatchType PatchType = EBonePatchType::Default;
// Bone names affected by this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
TArray<FName> BoneNames;
// Custom cut material for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
TObjectPtr<UMaterialInterface> CustomCutMaterial = nullptr;
// Custom blood effect for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
TObjectPtr<UNiagaraSystem> CustomBloodEffect = nullptr;
// Damage multiplier for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment", meta = (ClampMin = "0.0", ClampMax = "10.0"))
float DamageMultiplier = 1.0f;
// Whether this patch has organs
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
bool bHasOrgans = false;
// Organ mesh for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment", meta = (EditCondition = "bHasOrgans"))
TObjectPtr<UStaticMesh> OrganMesh = nullptr;
// Organ material for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment", meta = (EditCondition = "bHasOrgans"))
TObjectPtr<UMaterialInterface> OrganMaterial = nullptr;
// Whether this patch has custom physics
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment")
bool bHasCustomPhysics = false;
// Physics asset for this patch
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Dismemberment", meta = (EditCondition = "bHasCustomPhysics"))
TObjectPtr<UPhysicsAsset> CustomPhysicsAsset = nullptr;
// Constructor
FBonePatchData()
{
}
};
/**
* Main component for the FLESH dismemberment system with blueprint support
* Provides easy-to-use blueprint interface for the dismemberment system
*/
UCLASS(ClassGroup=(FLESH), meta=(BlueprintSpawnableComponent))
class FLESH_API UFLESHDismembermentComponent : public UActorComponent
{
GENERATED_BODY()
public:
// Sets default values for this component's properties
UFLESHDismembermentComponent();
protected:
// Called when the game starts
virtual void BeginPlay() override;
public:
// Called every frame
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
/**
* Perform a cut at the specified location and direction
* @param CutLocation - World location of the cut
* @param CutDirection - Direction of the cut
* @param CutWidth - Width of the cut
* @param CutDepth - Depth of the cut
* @return True if the cut was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool PerformCut(const FVector& CutLocation, const FVector& CutDirection, float CutWidth = 1.0f, float CutDepth = 10.0f);
/**
* Perform a cut using a transformation matrix
* @param CutTransform - Transformation matrix for the cut
* @param CutWidth - Width of the cut
* @param CutDepth - Depth of the cut
* @return True if the cut was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool PerformCutWithMatrix(const FMatrix& CutTransform, float CutWidth = 1.0f, float CutDepth = 10.0f);
/**
* Dismember a specific bone
* @param BoneName - Name of the bone to dismember
* @param DismembermentType - Type of dismemberment to perform
* @return True if the dismemberment was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool DismemberBone(const FName& BoneName, EDismembermentType DismembermentType = EDismembermentType::Cut);
/**
* Apply damage to a specific bone
* @param BoneName - Name of the bone to damage
* @param Damage - Amount of damage to apply
* @param DamageLocation - World location of the damage
* @param DamageDirection - Direction of the damage
* @param DismembermentType - Type of damage for potential dismemberment
* @return True if damage was applied
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool ApplyBoneDamage(const FName& BoneName, float Damage, const FVector& DamageLocation, const FVector& DamageDirection, EDismembermentType DismembermentType = EDismembermentType::Cut);
/**
* Add a bone patch
* @param PatchData - Patch data to add
* @return True if the patch was added
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool AddBonePatch(const FBonePatchData& PatchData);
/**
* Remove a bone patch
* @param PatchType - Type of patch to remove
* @return True if the patch was removed
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool RemoveBonePatch(EBonePatchType PatchType);
/**
* Get a bone patch by type
* @param PatchType - Type of patch to get
* @param OutPatchData - Output patch data
* @return True if the patch was found
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
bool GetBonePatch(EBonePatchType PatchType, FBonePatchData& OutPatchData) const;
/**
* Get all bone patches
* @return Array of all bone patches
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
TArray<FBonePatchData> GetAllBonePatches() const;
/**
* Reset all dismemberment (restore all bones)
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void ResetDismemberment();
/**
* Set the blood effect for dismemberment
* @param BloodEffect - Niagara system for blood effects
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetBloodEffect(UNiagaraSystem* BloodEffect);
/**
* Set the cut material for dismemberment
* @param CutMaterial - Material to use for cut surfaces
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetCutMaterial(UMaterialInterface* CutMaterial);
/**
* Set whether to show organs
* @param bShow - Whether to show organs
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetShowOrgans(bool bShow);
/**
* Set whether to enable physics
* @param bEnable - Whether to enable physics
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
void SetEnablePhysics(bool bEnable);
private:
// The dismemberment system
UPROPERTY()
TObjectPtr<UDismembermentSystem> DismembermentSystem;
// The target skeletal mesh component
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> TargetMesh;
// Default blood effect
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UNiagaraSystem> DefaultBloodEffect;
// Default cut material
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment", meta = (AllowPrivateAccess = "true"))
TObjectPtr<UMaterialInterface> DefaultCutMaterial;
// Whether to show organs
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment", meta = (AllowPrivateAccess = "true"))
bool bShowOrgans = true;
// Whether to enable physics
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment", meta = (AllowPrivateAccess = "true"))
bool bEnablePhysics = true;
// Bone patches
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment", meta = (AllowPrivateAccess = "true"))
TArray<FBonePatchData> BonePatches;
// Internal function to find a bone patch by type
int32 FindBonePatchIndex(EBonePatchType PatchType) const;
// Internal function to apply a bone patch
void ApplyBonePatch(const FBonePatchData& PatchData);
};

View File

@@ -0,0 +1,21 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
/**
* FLESH Module - Fully Locational Evisceration System for Humanoids
*/
class FLESH_API FFLESHModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
/** Singleton getter */
static FFLESHModule& Get()
{
return FModuleManager::LoadModuleChecked<FFLESHModule>("FLESH");
}
};

View File

@@ -0,0 +1,67 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class FLESHEditor : ModuleRules
{
public FLESHEditor(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"FLESH"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"UnrealEd",
"PropertyEditor",
"EditorStyle",
"LevelEditor",
"GeometryCore",
"GeometryFramework",
"GeometryScriptingEditor",
"ToolMenus",
"GraphEditor",
"BlueprintGraph",
"KismetWidgets",
"ApplicationCore",
"InputCore",
"AssetTools",
"EditorWidgets",
"Kismet",
"AdvancedPreviewScene",
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,367 @@
#include "DismembermentEditor.h"
#include "Widgets/Docking/SDockTab.h"
#include "PropertyEditorModule.h"
#include "Modules/ModuleManager.h"
#include "IDetailsView.h"
#include "Widgets/Layout/SBorder.h"
#include "EditorStyleSet.h"
#include "Engine/SkeletalMesh.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ToolMenus.h"
// Define tab names
const FName FDismembermentEditor::ViewportTabId(TEXT("DismembermentEditor_Viewport"));
const FName FDismembermentEditor::DetailsTabId(TEXT("DismembermentEditor_Details"));
const FName FDismembermentEditor::LayerSystemTabId(TEXT("DismembermentEditor_LayerSystem"));
const FName FDismembermentEditor::PhysicsSettingsTabId(TEXT("DismembermentEditor_PhysicsSettings"));
// Constructor
FDismembermentEditor::FDismembermentEditor()
: SkeletalMesh(nullptr)
{
}
// Destructor
FDismembermentEditor::~FDismembermentEditor()
{
}
// Initialize the editor
void FDismembermentEditor::InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh)
{
// Set the skeletal mesh being edited
SkeletalMesh = InSkeletalMesh;
// Create the layout
CreateEditorLayout();
// Create the toolbar
CreateEditorToolbar();
// Initialize the asset editor
InitAssetEditor(Mode, InitToolkitHost, FName("DismembermentEditorApp"),
FTabManager::FLayout::NullLayout,
/*bCreateDefaultStandaloneMenu=*/ true,
/*bCreateDefaultToolbar=*/ true,
InSkeletalMesh);
// Register for undo/redo
GEditor->RegisterForUndo(this);
}
// Get the toolkit name
FName FDismembermentEditor::GetToolkitFName() const
{
return FName("DismembermentEditor");
}
// Get the base toolkit name
FText FDismembermentEditor::GetBaseToolkitName() const
{
return FText::FromString("Dismemberment Editor");
}
// Get the world centric tab prefix
FString FDismembermentEditor::GetWorldCentricTabPrefix() const
{
return "DismembermentEditor";
}
// Get the world centric tab color scale
FLinearColor FDismembermentEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
}
// Post undo handler
void FDismembermentEditor::PostUndo(bool bSuccess)
{
// Refresh the views
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
// Post redo handler
void FDismembermentEditor::PostRedo(bool bSuccess)
{
// Refresh the views
if (DetailsWidget.IsValid())
{
DetailsWidget->ForceRefresh();
}
}
// Create the editor layout
void FDismembermentEditor::CreateEditorLayout()
{
// Register tab spawners
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_DismembermentEditor_Layout_v1")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(GetToolbarTabId(), ETabState::OpenedTab)
->SetHideTabWell(true)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)->SetSizeCoefficient(0.9f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->AddTab(ViewportTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.3f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab(DetailsTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(LayerSystemTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(PhysicsSettingsTabId, ETabState::OpenedTab)
)
)
)
);
// Set the layout
FAssetEditorToolkit::InitAssetEditor(
EToolkitMode::Standalone,
TSharedPtr<IToolkitHost>(),
FName("DismembermentEditorApp"),
StandaloneDefaultLayout,
true,
true,
SkeletalMesh
);
}
// Create the editor toolbar
void FDismembermentEditor::CreateEditorToolbar()
{
// Create command list
TSharedPtr<FUICommandList> CommandList = MakeShareable(new FUICommandList);
// Get the toolbar builder
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
CommandList,
FToolBarExtensionDelegate::CreateLambda([this](FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.BeginSection("Dismemberment");
{
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PerformBooleanCut)),
NAME_None,
FText::FromString("Boolean Cut"),
FText::FromString("Perform a boolean cut operation"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.CurveBase"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::AddNewLayer)),
NAME_None,
FText::FromString("Add Layer"),
FText::FromString("Add a new anatomical layer"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Layer"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::PreviewEffects)),
NAME_None,
FText::FromString("Preview"),
FText::FromString("Preview the dismemberment effects"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.ParticleSystem"),
EUserInterfaceActionType::Button
);
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDismembermentEditor::SaveEdits)),
NAME_None,
FText::FromString("Save"),
FText::FromString("Save the dismemberment setup"),
FSlateIcon(FEditorStyle::GetStyleSetName(), "AssetEditor.SaveAsset"),
EUserInterfaceActionType::Button
);
}
ToolbarBuilder.EndSection();
})
);
// Add the extender
AddToolbarExtender(ToolbarExtender);
}
// Register tab spawners
void FDismembermentEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// Call the base implementation
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
// Register the tab spawners
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Viewport))
.SetDisplayName(FText::FromString("Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_Details))
.SetDisplayName(FText::FromString("Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(LayerSystemTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_LayerSystem))
.SetDisplayName(FText::FromString("Layer System"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.Layer"));
InTabManager->RegisterTabSpawner(PhysicsSettingsTabId, FOnSpawnTab::CreateSP(this, &FDismembermentEditor::SpawnTab_PhysicsSettings))
.SetDisplayName(FText::FromString("Physics Settings"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ClassIcon.PhysicsAsset"));
}
// Unregister tab spawners
void FDismembermentEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
// Call the base implementation
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
// Unregister the tab spawners
InTabManager->UnregisterTabSpawner(ViewportTabId);
InTabManager->UnregisterTabSpawner(DetailsTabId);
InTabManager->UnregisterTabSpawner(LayerSystemTabId);
InTabManager->UnregisterTabSpawner(PhysicsSettingsTabId);
}
// Spawn the viewport tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
// Create the viewport widget
ViewportWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Viewport will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Viewport"))
[
ViewportWidget.ToSharedRef()
];
}
// Spawn the details tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
{
// Create the details view
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = false;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
DetailsWidget->SetObject(SkeletalMesh);
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Details"))
[
DetailsWidget.ToSharedRef()
];
}
// Spawn the layer system tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_LayerSystem(const FSpawnTabArgs& Args)
{
// Create the layer system widget
LayerSystemWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Layer System will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Layer System"))
[
LayerSystemWidget.ToSharedRef()
];
}
// Spawn the physics settings tab
TSharedRef<SDockTab> FDismembermentEditor::SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args)
{
// Create the physics settings widget
PhysicsSettingsWidget = SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(FMargin(4.0f))
[
SNew(STextBlock)
.Text(FText::FromString("Physics Settings will be implemented here"))
];
// Create the tab
return SNew(SDockTab)
.Label(FText::FromString("Physics Settings"))
[
PhysicsSettingsWidget.ToSharedRef()
];
}
// Perform a boolean cut operation
void FDismembermentEditor::PerformBooleanCut()
{
// To be implemented
}
// Add a new layer
void FDismembermentEditor::AddNewLayer()
{
// To be implemented
}
// Save the edits
void FDismembermentEditor::SaveEdits()
{
// To be implemented
}
// Preview the effects
void FDismembermentEditor::PreviewEffects()
{
// To be implemented
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "EditorUndoClient.h"
class USkeletalMesh;
class SDockTab;
class IDetailsView;
class SBorder;
/**
* Dismemberment System Editor
* Provides real-time boolean cutting and multi-layer system editing functionality
*/
class FDismembermentEditor : public FAssetEditorToolkit, public FEditorUndoClient
{
public:
FDismembermentEditor();
virtual ~FDismembermentEditor();
void InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh);
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
virtual void PostUndo(bool bSuccess) override;
virtual void PostRedo(bool bSuccess) override;
private:
void CreateEditorLayout();
void CreateEditorToolbar();
void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager);
void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager);
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_LayerSystem(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args);
void PerformBooleanCut();
void AddNewLayer();
void SaveEdits();
void PreviewEffects();
private:
USkeletalMesh* SkeletalMesh;
TSharedPtr<SBorder> ViewportWidget;
TSharedPtr<IDetailsView> DetailsWidget;
TSharedPtr<SBorder> LayerSystemWidget;
TSharedPtr<SBorder> PhysicsSettingsWidget;
static const FName ViewportTabId;
static const FName DetailsTabId;
static const FName LayerSystemTabId;
static const FName PhysicsSettingsTabId;
};

View File

@@ -0,0 +1,22 @@
#include "DismembermentGraph/DismembermentGraphEditorFactory.h"
#include "DismembermentGraph/DismembermentGraphAsset.h"
UDismembermentGraphEditorFactory::UDismembermentGraphEditorFactory()
{
// Factory configuration
SupportedClass = UDismembermentGraphAsset::StaticClass();
bCreateNew = true;
bEditAfterNew = true;
}
UObject* UDismembermentGraphEditorFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
// Create a new dismemberment graph asset
UDismembermentGraphAsset* NewAsset = NewObject<UDismembermentGraphAsset>(InParent, InClass, InName, Flags);
return NewAsset;
}
bool UDismembermentGraphEditorFactory::ShouldShowInNewMenu() const
{
return true;
}

View File

@@ -0,0 +1,181 @@
#include "DismembermentGraph/DismembermentGraphEditor.h"
#include "DismembermentGraph/DismembermentGraphAsset.h"
#include "DismembermentGraph/DismembermentGraph.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "EdGraphUtilities.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "DismembermentGraphEditor"
/**
* Dismemberment graph schema actions factory
*/
class FDismembermentGraphSchemaActionMenuBuilder : public FGraphSchemaActionMenuBuilder
{
public:
// Constructor
FDismembermentGraphSchemaActionMenuBuilder(const UEdGraphSchema* InSchema)
: FGraphSchemaActionMenuBuilder(InSchema)
{
}
};
/**
* Dismemberment graph editor module
*/
class FDismembermentGraphEditorModule : public IModuleInterface
{
public:
// IModuleInterface interface
virtual void StartupModule() override
{
// Register asset type actions
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
// Register asset category
DismembermentGraphAssetCategoryBit = AssetTools.RegisterAdvancedAssetCategory(FName(TEXT("Dismemberment")), LOCTEXT("DismembermentGraphAssetCategory", "Dismemberment"));
// Register asset type actions
TSharedPtr<IAssetTypeActions> Action = MakeShareable(new FDismembermentGraphAssetTypeActions(DismembermentGraphAssetCategoryBit));
AssetTools.RegisterAssetTypeActions(Action);
RegisteredAssetTypeActions.Add(Action);
// Register graph editor factory
TSharedPtr<FGraphPanelNodeFactory> NodeFactory = MakeShareable(new FDismembermentGraphNodeFactory);
FEdGraphUtilities::RegisterVisualNodeFactory(NodeFactory);
// Register node factories
RegisterNodeFactory(UDismembermentGraphNodeCut::StaticClass(), LOCTEXT("CutNode", "Cut Node"), LOCTEXT("CutNodeTooltip", "Performs a cut operation"));
RegisterNodeFactory(UDismembermentGraphNodeBoneSelect::StaticClass(), LOCTEXT("BoneSelectNode", "Bone Select Node"), LOCTEXT("BoneSelectNodeTooltip", "Selects bones for operations"));
RegisterNodeFactory(UDismembermentGraphNodeBloodEffect::StaticClass(), LOCTEXT("BloodEffectNode", "Blood Effect Node"), LOCTEXT("BloodEffectNodeTooltip", "Creates blood effects"));
}
virtual void ShutdownModule() override
{
// Unregister asset type actions
if (FModuleManager::Get().IsModuleLoaded("AssetTools"))
{
IAssetTools& AssetTools = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
for (TSharedPtr<IAssetTypeActions> Action : RegisteredAssetTypeActions)
{
AssetTools.UnregisterAssetTypeActions(Action.ToSharedRef());
}
}
RegisteredAssetTypeActions.Empty();
// Unregister node factories
for (const auto& Factory : RegisteredNodeFactories)
{
FEdGraphUtilities::UnregisterVisualNodeFactory(Factory);
}
RegisteredNodeFactories.Empty();
}
// End of IModuleInterface interface
private:
// Asset category bit
EAssetTypeCategories::Type DismembermentGraphAssetCategoryBit;
// Registered asset type actions
TArray<TSharedPtr<IAssetTypeActions>> RegisteredAssetTypeActions;
// Registered node factories
TArray<TSharedPtr<FGraphPanelNodeFactory>> RegisteredNodeFactories;
// Register a node factory
void RegisterNodeFactory(UClass* NodeClass, const FText& DisplayName, const FText& Tooltip)
{
TSharedPtr<FDismembermentGraphNodeFactory> NodeFactory = MakeShareable(new FDismembermentGraphNodeFactory(NodeClass, DisplayName, Tooltip));
FEdGraphUtilities::RegisterVisualNodeFactory(NodeFactory);
RegisteredNodeFactories.Add(NodeFactory);
}
};
/**
* Dismemberment graph asset type actions
*/
class FDismembermentGraphAssetTypeActions : public FAssetTypeActions_Base
{
public:
// Constructor
FDismembermentGraphAssetTypeActions(EAssetTypeCategories::Type InAssetCategory)
: AssetCategory(InAssetCategory)
{
}
// FAssetTypeActions_Base interface
virtual FText GetName() const override { return LOCTEXT("DismembermentGraphAssetName", "Dismemberment Graph"); }
virtual FColor GetTypeColor() const override { return FColor(255, 64, 64); }
virtual UClass* GetSupportedClass() const override { return UDismembermentGraphAsset::StaticClass(); }
virtual uint32 GetCategories() override { return AssetCategory; }
virtual bool HasActions(const TArray<UObject*>& InObjects) const override { return false; }
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects, TSharedPtr<IToolkitHost> EditWithinLevelEditor) override
{
for (UObject* Object : InObjects)
{
if (UDismembermentGraphAsset* GraphAsset = Cast<UDismembermentGraphAsset>(Object))
{
TSharedRef<FDismembermentGraphEditor> NewEditor = MakeShareable(new FDismembermentGraphEditor());
NewEditor->InitDismembermentGraphEditor(EToolkitMode::Standalone, EditWithinLevelEditor, GraphAsset);
}
}
}
// End of FAssetTypeActions_Base interface
private:
// Asset category
EAssetTypeCategories::Type AssetCategory;
};
/**
* Dismemberment graph node factory
*/
class FDismembermentGraphNodeFactory : public FGraphPanelNodeFactory
{
public:
// Constructor
FDismembermentGraphNodeFactory()
: NodeClass(nullptr)
{
}
FDismembermentGraphNodeFactory(UClass* InNodeClass, const FText& InDisplayName, const FText& InTooltip)
: NodeClass(InNodeClass)
, DisplayName(InDisplayName)
, Tooltip(InTooltip)
{
}
// FGraphPanelNodeFactory interface
virtual TSharedPtr<SGraphNode> CreateNode(UEdGraphNode* Node) const override
{
if (NodeClass && Node->IsA(NodeClass))
{
return SNew(SDismembermentGraphNode, Node);
}
return nullptr;
}
// End of FGraphPanelNodeFactory interface
private:
// Node class
UClass* NodeClass;
// Display name
FText DisplayName;
// Tooltip
FText Tooltip;
};
IMPLEMENT_MODULE(FDismembermentGraphEditorModule, FLESHEditor)
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,459 @@
#include "DismembermentGraph/DismembermentGraphSchema.h"
#include "DismembermentGraph/DismembermentGraph.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "DismembermentGraph/DismembermentGraphNodePhysics.h"
#include "DismembermentGraph/DismembermentGraphNodeOrgan.h"
#include "DismembermentGraph/DismembermentGraphNodeWound.h"
#include "EdGraphNode_Comment.h"
#include "Framework/Commands/GenericCommands.h"
#include "GraphEditorActions.h"
#include "ToolMenus.h"
#define LOCTEXT_NAMESPACE "DismembermentGraphSchema"
// Pin categories
const FName UDismembermentGraphSchema::PC_Exec("Exec");
const FName UDismembermentGraphSchema::PC_Bone("Bone");
const FName UDismembermentGraphSchema::PC_Cut("Cut");
const FName UDismembermentGraphSchema::PC_Blood("Blood");
const FName UDismembermentGraphSchema::PC_Physics("Physics");
const FName UDismembermentGraphSchema::PC_Organ("Organ");
const FName UDismembermentGraphSchema::PC_Wound("Wound");
void UDismembermentGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
{
// Add node actions
const FText ToolTip = LOCTEXT("NewDismembermentNodeTooltip", "Add node here");
const FText MenuDesc = LOCTEXT("NewDismembermentNodeMenuDesc", "Add Node");
TArray<TSubclassOf<UDismembermentGraphNode>> NodeClasses;
NodeClasses.Add(UDismembermentGraphNodeCut::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeBoneSelect::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeBloodEffect::StaticClass());
NodeClasses.Add(UDismembermentGraphNodePhysics::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeOrgan::StaticClass());
NodeClasses.Add(UDismembermentGraphNodeWound::StaticClass());
for (TSubclassOf<UDismembermentGraphNode> NodeClass : NodeClasses)
{
UDismembermentGraphNode* NodeTemplate = NodeClass->GetDefaultObject<UDismembermentGraphNode>();
FText NodeCategory = NodeTemplate->NodeCategory;
FText NodeTitle = NodeTemplate->GetNodeTitle(ENodeTitleType::ListView);
FText NodeTooltip = NodeTemplate->GetTooltipText();
TSharedPtr<FDismembermentSchemaAction_NewNode> NewNodeAction(new FDismembermentSchemaAction_NewNode(
NodeCategory,
NodeTitle,
NodeTooltip,
0));
NewNodeAction->NodeClass = NodeClass;
ContextMenuBuilder.AddAction(NewNodeAction);
}
// Add comment node
TSharedPtr<FDismembermentSchemaAction_NewNode> NewCommentAction(new FDismembermentSchemaAction_NewNode(
FText::GetEmpty(),
LOCTEXT("NewComment", "Comment"),
LOCTEXT("NewCommentTooltip", "Add a comment node"),
0));
NewCommentAction->NodeClass = UEdGraphNode_Comment::StaticClass();
ContextMenuBuilder.AddAction(NewCommentAction);
}
void UDismembermentGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
if (Context && Context->Node)
{
FToolMenuSection& Section = Menu->AddSection("DismembermentGraphNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"));
Section.AddMenuEntry(FGenericCommands::Get().Delete);
Section.AddMenuEntry(FGenericCommands::Get().Cut);
Section.AddMenuEntry(FGenericCommands::Get().Copy);
Section.AddMenuEntry(FGenericCommands::Get().Duplicate);
// Add preview action
Section.AddMenuEntry(
"PreviewNode",
LOCTEXT("PreviewNode", "Preview Node"),
LOCTEXT("PreviewNodeTooltip", "Preview the effect of this node"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Context]()
{
if (UDismembermentGraphNode* Node = Cast<UDismembermentGraphNode>(Context->Node))
{
// TODO: Implement node preview
}
}),
FCanExecuteAction::CreateLambda([Context]()
{
return Context->Node != nullptr;
})
)
);
}
// Add general graph actions
{
FToolMenuSection& Section = Menu->AddSection("DismembermentGraphActions", LOCTEXT("GraphActionsMenuHeader", "Graph Actions"));
Section.AddMenuEntry(FGraphEditorCommands::Get().SelectAllNodes);
Section.AddMenuEntry(
"ArrangeNodes",
LOCTEXT("ArrangeNodes", "Arrange Nodes"),
LOCTEXT("ArrangeNodesTooltip", "Arrange the nodes in the graph"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Context]()
{
if (Context && Context->Graph)
{
// TODO: Implement node arrangement
}
}),
FCanExecuteAction::CreateLambda([Context]()
{
return Context && Context->Graph;
})
)
);
}
}
const FPinConnectionResponse UDismembermentGraphSchema::CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
{
// Make sure the pins are valid
if (!A || !B)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("PinError", "Invalid pins"));
}
// Make sure the pins are not on the same node
if (A->GetOwningNode() == B->GetOwningNode())
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("SameNode", "Can't connect pins on the same node"));
}
// Check pin directions
const UEdGraphPin* InputPin = nullptr;
const UEdGraphPin* OutputPin = nullptr;
if (A->Direction == EGPD_Input && B->Direction == EGPD_Output)
{
InputPin = A;
OutputPin = B;
}
else if (A->Direction == EGPD_Output && B->Direction == EGPD_Input)
{
InputPin = B;
OutputPin = A;
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("IncompatibleDirections", "Incompatible pin directions"));
}
// Check pin types
FDismembermentGraphPinType InputType = GetPinType(InputPin);
FDismembermentGraphPinType OutputType = GetPinType(OutputPin);
if (InputType != OutputType)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("IncompatibleTypes", "Incompatible pin types"));
}
// Check for cycles
if (WouldCreateCycle(OutputPin, InputPin))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("CycleDetected", "Connection would create a cycle"));
}
// Check if the pins are already connected
for (int32 i = 0; i < InputPin->LinkedTo.Num(); ++i)
{
if (InputPin->LinkedTo[i] == OutputPin)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("AlreadyConnected", "Pins are already connected"));
}
}
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, LOCTEXT("CreateConnection", "Create Connection"));
}
bool UDismembermentGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
{
// Check if the connection is valid
const FPinConnectionResponse Response = CanCreateConnection(A, B);
if (Response.Response != CONNECT_RESPONSE_MAKE)
{
return false;
}
// Break existing connections on input pins
if (A->Direction == EGPD_Input)
{
A->BreakAllPinLinks();
}
else if (B->Direction == EGPD_Input)
{
B->BreakAllPinLinks();
}
// Make the connection
A->MakeLinkTo(B);
return true;
}
bool UDismembermentGraphSchema::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const
{
return Pin && Pin->LinkedTo.Num() > 0;
}
FLinearColor UDismembermentGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
FDismembermentGraphPinType DismembermentPinType;
DismembermentPinType.PinCategory = PinType.PinCategory;
return GetPinTypeColor(DismembermentPinType);
}
void UDismembermentGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const
{
Super::BreakNodeLinks(TargetNode);
}
void UDismembermentGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const
{
Super::BreakPinLinks(TargetPin, bSendsNodeNotification);
}
void UDismembermentGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const
{
Super::BreakSinglePinLink(SourcePin, TargetPin);
}
void UDismembermentGraphSchema::DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
{
// TODO: Implement asset dropping
}
void UDismembermentGraphSchema::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const
{
// TODO: Implement asset dropping on nodes
}
void UDismembermentGraphSchema::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const
{
// TODO: Implement asset dropping on pins
}
void UDismembermentGraphSchema::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
{
// TODO: Implement asset hover message
OutTooltipText = TEXT("Drop to create a reference");
OutOkIcon = true;
}
void UDismembermentGraphSchema::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
{
// TODO: Implement asset hover message
OutTooltipText = TEXT("Drop to create a reference");
OutOkIcon = true;
}
bool UDismembermentGraphSchema::CanConnectPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, FDismembermentGraphConnectionResponse& OutResponse) const
{
// Check if the pins are valid
if (!PinA || !PinB)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("PinError", "Invalid pins");
return false;
}
// Check if the pins are on the same node
if (PinA->GetOwningNode() == PinB->GetOwningNode())
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_SELF_CONNECTION;
OutResponse.Message = LOCTEXT("SameNode", "Can't connect pins on the same node");
return false;
}
// Check pin directions
const UEdGraphPin* InputPin = nullptr;
const UEdGraphPin* OutputPin = nullptr;
if (PinA->Direction == EGPD_Input && PinB->Direction == EGPD_Output)
{
InputPin = PinA;
OutputPin = PinB;
}
else if (PinA->Direction == EGPD_Output && PinB->Direction == EGPD_Input)
{
InputPin = PinB;
OutputPin = PinA;
}
else
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("IncompatibleDirections", "Incompatible pin directions");
return false;
}
// Check pin types
FDismembermentGraphPinType InputType = GetPinType(InputPin);
FDismembermentGraphPinType OutputType = GetPinType(OutputPin);
if (InputType != OutputType)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_INCOMPATIBLE;
OutResponse.Message = LOCTEXT("IncompatibleTypes", "Incompatible pin types");
return false;
}
// Check for cycles
if (WouldCreateCycle(OutputPin, InputPin))
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_CYCLE;
OutResponse.Message = LOCTEXT("CycleDetected", "Connection would create a cycle");
return false;
}
// Check if the pins are already connected
for (int32 i = 0; i < InputPin->LinkedTo.Num(); ++i)
{
if (InputPin->LinkedTo[i] == OutputPin)
{
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::ERROR_DISALLOWED;
OutResponse.Message = LOCTEXT("AlreadyConnected", "Pins are already connected");
return false;
}
}
OutResponse.Response = FDismembermentGraphConnectionResponse_K2::OK;
return true;
}
bool UDismembermentGraphSchema::WouldCreateCycle(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
// Check if connecting these pins would create a cycle
const UEdGraphNode* NodeA = PinA->GetOwningNode();
const UEdGraphNode* NodeB = PinB->GetOwningNode();
// If the nodes are the same, there's a cycle
if (NodeA == NodeB)
{
return true;
}
// Depth-first search to check for cycles
TSet<const UEdGraphNode*> VisitedNodes;
TArray<const UEdGraphNode*> NodesToVisit;
NodesToVisit.Push(NodeB);
while (NodesToVisit.Num() > 0)
{
const UEdGraphNode* CurrentNode = NodesToVisit.Pop();
if (VisitedNodes.Contains(CurrentNode))
{
continue;
}
VisitedNodes.Add(CurrentNode);
// Check all output pins
for (int32 PinIndex = 0; PinIndex < CurrentNode->Pins.Num(); ++PinIndex)
{
const UEdGraphPin* Pin = CurrentNode->Pins[PinIndex];
if (Pin->Direction == EGPD_Output)
{
// Check all connections
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
const UEdGraphPin* LinkedPin = Pin->LinkedTo[LinkIndex];
const UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode();
// If we found NodeA, there's a cycle
if (LinkedNode == NodeA)
{
return true;
}
// Add the linked node to the nodes to visit
NodesToVisit.Push(LinkedNode);
}
}
}
}
return false;
}
FDismembermentGraphPinType UDismembermentGraphSchema::GetPinType(const UEdGraphPin* Pin)
{
return FDismembermentGraphPinType(Pin->PinType.PinCategory);
}
FLinearColor UDismembermentGraphSchema::GetPinTypeColor(const FDismembermentGraphPinType& PinType)
{
if (PinType.PinCategory == PC_Exec)
{
return FLinearColor::White;
}
else if (PinType.PinCategory == PC_Bone)
{
return FLinearColor(0.9f, 0.9f, 0.2f);
}
else if (PinType.PinCategory == PC_Cut)
{
return FLinearColor(1.0f, 0.0f, 0.0f);
}
else if (PinType.PinCategory == PC_Blood)
{
return FLinearColor(0.8f, 0.0f, 0.0f);
}
else if (PinType.PinCategory == PC_Physics)
{
return FLinearColor(0.0f, 0.8f, 0.8f);
}
else if (PinType.PinCategory == PC_Organ)
{
return FLinearColor(0.8f, 0.4f, 0.4f);
}
else if (PinType.PinCategory == PC_Wound)
{
return FLinearColor(0.6f, 0.0f, 0.0f);
}
return FLinearColor::Black;
}
UDismembermentGraphNode* UDismembermentGraphSchema::CreateNode(TSubclassOf<UDismembermentGraphNode> NodeClass, UEdGraph* ParentGraph, float NodePosX, float NodePosY, bool bSelectNewNode)
{
UDismembermentGraphNode* NewNode = NewObject<UDismembermentGraphNode>(ParentGraph, NodeClass);
NewNode->CreateNewGuid();
NewNode->PostPlacedNewNode();
NewNode->NodePosX = NodePosX;
NewNode->NodePosY = NodePosY;
NewNode->AllocateDefaultPins();
ParentGraph->AddNode(NewNode, true, bSelectNewNode);
return NewNode;
}
// Schema action for creating a new node
FDismembermentSchemaAction_NewNode::FDismembermentSchemaAction_NewNode(const FText& InNodeCategory, const FText& InMenuDesc, const FText& InToolTip, const int32 InGrouping)
: FEdGraphSchemaAction(InNodeCategory, InMenuDesc, InToolTip, InGrouping)
{
}
UEdGraphNode* FDismembermentSchemaAction_NewNode::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
{
UDismembermentGraphNode* NewNode = UDismembermentGraphSchema::CreateNode(NodeClass, ParentGraph, Location.X, Location.Y, bSelectNewNode);
return NewNode;
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,787 @@
#include "DismembermentGraph/DismembermentPreviewManager.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
#include "DismembermentGraph/DismembermentGraphNodePhysics.h"
#include "DismembermentGraph/DismembermentGraphNodeOrgan.h"
#include "DismembermentGraph/DismembermentGraphNodeWound.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/DecalComponent.h"
#include "NiagaraComponent.h"
#include "NiagaraSystem.h"
#include "Engine/StaticMesh.h"
#include "Materials/MaterialInterface.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
// Define log category
DEFINE_LOG_CATEGORY(LogFLESHPreview);
UDismembermentPreviewManager::UDismembermentPreviewManager()
: World(nullptr)
, TargetActor(nullptr)
, TargetSkeletalMesh(nullptr)
, PreviewedNode(nullptr)
, PreviewCutPlaneMesh(nullptr)
{
}
void UDismembermentPreviewManager::Initialize(TObjectPtr<UWorld> InWorld)
{
World = InWorld;
UE_LOG(LogFLESHPreview, Log, TEXT("Preview manager initialized"));
}
void UDismembermentPreviewManager::Cleanup()
{
UE_LOG(LogFLESHPreview, Log, TEXT("Cleaning up preview manager"));
ClearPreview();
World = nullptr;
TargetActor = nullptr;
TargetSkeletalMesh = nullptr;
}
void UDismembermentPreviewManager::SetTargetActor(TObjectPtr<AActor> InActor)
{
// Clear any existing preview
ClearPreview();
// Set the new target actor
TargetActor = InActor;
if (TargetActor)
{
UE_LOG(LogFLESHPreview, Log, TEXT("Set target actor: %s"), *TargetActor->GetName());
// Find the skeletal mesh component
FindTargetSkeletalMesh();
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Set empty target actor"));
}
}
bool UDismembermentPreviewManager::PreviewNode(TObjectPtr<UDismembermentGraphNode> Node)
{
// Clear any existing preview
ClearPreview();
// Set the new previewed node
PreviewedNode = Node;
// Check if we have a valid target
if (!TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Invalid target actor or skeletal mesh"));
return false;
}
if (!Node)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview node: Node is null"));
return false;
}
UE_LOG(LogFLESHPreview, Log, TEXT("Previewing node: %s"), *Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
// Preview based on node type
if (TObjectPtr<UDismembermentGraphNodeCut> CutNode = Cast<UDismembermentGraphNodeCut>(Node))
{
return PreviewCutNode(CutNode);
}
else if (TObjectPtr<UDismembermentGraphNodeBoneSelect> BoneSelectNode = Cast<UDismembermentGraphNodeBoneSelect>(Node))
{
return PreviewBoneSelectNode(BoneSelectNode);
}
else if (TObjectPtr<UDismembermentGraphNodeBloodEffect> BloodEffectNode = Cast<UDismembermentGraphNodeBloodEffect>(Node))
{
return PreviewBloodEffectNode(BloodEffectNode);
}
else if (TObjectPtr<UDismembermentGraphNodePhysics> PhysicsNode = Cast<UDismembermentGraphNodePhysics>(Node))
{
return PreviewPhysicsNode(PhysicsNode);
}
else if (TObjectPtr<UDismembermentGraphNodeOrgan> OrganNode = Cast<UDismembermentGraphNodeOrgan>(Node))
{
return PreviewOrganNode(OrganNode);
}
else if (TObjectPtr<UDismembermentGraphNodeWound> WoundNode = Cast<UDismembermentGraphNodeWound>(Node))
{
return PreviewWoundNode(WoundNode);
}
UE_LOG(LogFLESHPreview, Warning, TEXT("Unknown node type"));
return false;
}
void UDismembermentPreviewManager::ClearPreview()
{
// Clear the previewed node
PreviewedNode = nullptr;
// Clear all preview components
ClearPreviewComponents();
// Clear preview data
PreviewBoneSelections.Empty();
PreviewCutTransforms.Empty();
PreviewBloodEffectTransforms.Empty();
PreviewOrganTransforms.Empty();
PreviewWoundTransforms.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Preview cleared"));
}
void UDismembermentPreviewManager::Tick(float DeltaTime)
{
// Update preview effects
if (PreviewedNode && TargetActor && TargetSkeletalMesh)
{
// Update based on node type
if (UDismembermentGraphNodeCut* CutNode = Cast<UDismembermentGraphNodeCut>(PreviewedNode))
{
// Update cut preview
}
else if (UDismembermentGraphNodeBloodEffect* BloodEffectNode = Cast<UDismembermentGraphNodeBloodEffect>(PreviewedNode))
{
// Update blood effect preview
}
else if (UDismembermentGraphNodePhysics* PhysicsNode = Cast<UDismembermentGraphNodePhysics>(PreviewedNode))
{
// Update physics preview
}
}
}
bool UDismembermentPreviewManager::FindTargetSkeletalMesh()
{
TargetSkeletalMesh = nullptr;
if (TargetActor)
{
// Find the first skeletal mesh component
TargetSkeletalMesh = TargetActor->FindComponentByClass<USkeletalMeshComponent>();
if (TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Log, TEXT("Found target skeletal mesh: %s"), *TargetSkeletalMesh->GetName());
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("No skeletal mesh component found on actor %s"), *TargetActor->GetName());
}
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot find skeletal mesh: Target actor is null"));
}
return TargetSkeletalMesh != nullptr;
}
bool UDismembermentPreviewManager::PreviewCutNode(TObjectPtr<UDismembermentGraphNodeCut> CutNode)
{
if (!CutNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview cut node: Invalid parameters"));
return false;
}
// Get the cut parameters
float CutWidth = CutNode->CutWidth;
float CutDepth = CutNode->CutDepth;
UMaterialInterface* CutMaterial = CutNode->bUseCustomMaterial ? CutNode->CustomCutMaterial : nullptr;
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default cut direction if not connected to an input
FVector CutLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
FVector CutDirection = FVector(1.0f, 0.0f, 0.0f);
// Create the preview cut plane mesh
PreviewCutPlaneMesh = CreatePreviewCutPlaneMesh(CutLocation, CutDirection, CutWidth, CutDepth, CutMaterial);
if (!PreviewCutPlaneMesh)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane mesh"));
return false;
}
// Store the cut transform for later use
FTransform CutTransform;
CutTransform.SetLocation(CutLocation);
CutTransform.SetRotation(FQuat::MakeFromX(CutDirection));
CutTransform.SetScale3D(FVector(CutWidth, CutDepth, 1.0f));
PreviewCutTransforms.Add(CutTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Cut node preview created successfully"));
return PreviewCutPlaneMesh != nullptr;
}
bool UDismembermentPreviewManager::PreviewBoneSelectNode(TObjectPtr<UDismembermentGraphNodeBoneSelect> BoneSelectNode)
{
if (!BoneSelectNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview bone select node: Invalid parameters"));
return false;
}
// Get the bone selection parameters
TArray<FName> BoneNames = BoneSelectNode->BoneNames;
bool bUseRegex = BoneSelectNode->bUseRegex;
FString BoneNamePattern = BoneSelectNode->BoneNamePattern;
bool bIncludeChildren = BoneSelectNode->bIncludeChildren;
// Store the bone selections for later use
PreviewBoneSelections.Append(BoneNames);
UE_LOG(LogFLESHPreview, Log, TEXT("Bone select node preview created successfully, selected %d bones"), BoneNames.Num());
// TODO: Handle regex and child bones
return true;
}
bool UDismembermentPreviewManager::PreviewBloodEffectNode(TObjectPtr<UDismembermentGraphNodeBloodEffect> BloodEffectNode)
{
if (!BloodEffectNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect node: Invalid parameters"));
return false;
}
// Get the blood effect parameters
UNiagaraSystem* BloodEffect = BloodEffectNode->BloodEffect;
float BloodAmount = BloodEffectNode->BloodAmount;
float BloodPressure = BloodEffectNode->BloodPressure;
bool bCreateBloodPool = BloodEffectNode->bCreateBloodPool;
float BloodPoolSize = BloodEffectNode->BloodPoolSize;
UMaterialInterface* BloodPoolMaterial = BloodEffectNode->BloodPoolMaterial;
if (!BloodEffect)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview blood effect: No blood effect system specified"));
return false;
}
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default blood effect location if not connected to an input
FVector BloodLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
// Create the preview blood effect
TObjectPtr<UNiagaraComponent> BloodEffectComponent = CreatePreviewBloodEffect(BloodLocation, BloodEffect, BloodAmount, BloodPressure);
if (!BloodEffectComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component"));
return false;
}
// Create the preview blood pool if needed
TObjectPtr<UDecalComponent> BloodPoolComponent = nullptr;
if (bCreateBloodPool)
{
BloodPoolComponent = CreatePreviewBloodPool(BloodLocation, BloodPoolSize, BloodPoolMaterial);
if (!BloodPoolComponent)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create blood pool component"));
}
}
// Store the blood effect transform for later use
FTransform BloodEffectTransform;
BloodEffectTransform.SetLocation(BloodLocation);
PreviewBloodEffectTransforms.Add(BloodEffectTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Blood effect node preview created successfully"));
return BloodEffectComponent != nullptr;
}
bool UDismembermentPreviewManager::PreviewPhysicsNode(TObjectPtr<UDismembermentGraphNodePhysics> PhysicsNode)
{
if (!PhysicsNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview physics node: Invalid parameters"));
return false;
}
// Get the physics parameters
float Mass = PhysicsNode->Mass;
float LinearDamping = PhysicsNode->LinearDamping;
float AngularDamping = PhysicsNode->AngularDamping;
bool bEnableGravity = PhysicsNode->bEnableGravity;
bool bSimulatePhysics = PhysicsNode->bSimulatePhysics;
bool bGenerateOverlapEvents = PhysicsNode->bGenerateOverlapEvents;
UPhysicalMaterial* PhysicalMaterial = PhysicsNode->PhysicalMaterial;
float ImpulseForce = PhysicsNode->ImpulseForce;
float ImpulseRadius = PhysicsNode->ImpulseRadius;
// Apply physics settings to the target skeletal mesh
if (TargetSkeletalMesh)
{
// Store original values to restore later
TargetSkeletalMesh->SetMassOverrideInKg(NAME_None, Mass, true);
TargetSkeletalMesh->SetLinearDamping(LinearDamping);
TargetSkeletalMesh->SetAngularDamping(AngularDamping);
TargetSkeletalMesh->SetEnableGravity(bEnableGravity);
TargetSkeletalMesh->SetSimulatePhysics(bSimulatePhysics);
TargetSkeletalMesh->SetGenerateOverlapEvents(bGenerateOverlapEvents);
if (PhysicalMaterial)
{
TargetSkeletalMesh->SetPhysMaterialOverride(PhysicalMaterial);
}
UE_LOG(LogFLESHPreview, Log, TEXT("Physics node preview applied successfully, mass: %.2f, linear damping: %.2f, angular damping: %.2f"),
Mass, LinearDamping, AngularDamping);
}
return true;
}
bool UDismembermentPreviewManager::PreviewOrganNode(TObjectPtr<UDismembermentGraphNodeOrgan> OrganNode)
{
if (!OrganNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ node: Invalid parameters"));
return false;
}
// Get the organ parameters
UStaticMesh* OrganMesh = OrganNode->OrganMesh;
UMaterialInterface* OrganMaterial = OrganNode->OrganMaterial;
FName AttachBoneName = OrganNode->AttachBoneName;
FVector RelativeLocation = OrganNode->RelativeLocation;
FRotator RelativeRotation = OrganNode->RelativeRotation;
FVector RelativeScale = OrganNode->RelativeScale;
bool bSimulatePhysics = OrganNode->bSimulatePhysics;
float DamageMultiplier = OrganNode->DamageMultiplier;
bool bIsCriticalOrgan = OrganNode->bIsCriticalOrgan;
float BloodAmount = OrganNode->BloodAmount;
if (!OrganMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview organ: No organ mesh specified"));
return false;
}
// Create the preview organ
TObjectPtr<UStaticMeshComponent> OrganComponent = CreatePreviewOrgan(OrganMesh, OrganMaterial, AttachBoneName, RelativeLocation, RelativeRotation, RelativeScale);
if (!OrganComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component"));
return false;
}
// Store the organ transform for later use
FTransform OrganTransform;
if (TargetSkeletalMesh && !AttachBoneName.IsNone())
{
FTransform BoneTransform = TargetSkeletalMesh->GetBoneTransform(TargetSkeletalMesh->GetBoneIndex(AttachBoneName));
OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * BoneTransform;
}
else
{
OrganTransform = FTransform(RelativeRotation, RelativeLocation, RelativeScale) * TargetActor->GetActorTransform();
}
PreviewOrganTransforms.Add(OrganTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Organ node preview created successfully, attached to bone: %s"),
AttachBoneName.IsNone() ? TEXT("None") : *AttachBoneName.ToString());
return OrganComponent != nullptr;
}
bool UDismembermentPreviewManager::PreviewWoundNode(TObjectPtr<UDismembermentGraphNodeWound> WoundNode)
{
if (!WoundNode || !TargetActor || !TargetSkeletalMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot preview wound node: Invalid parameters"));
return false;
}
// Get the wound parameters
float WoundSize = WoundNode->WoundSize;
float WoundDepth = WoundNode->WoundDepth;
UMaterialInterface* WoundMaterial = WoundNode->WoundMaterial;
UNiagaraSystem* WoundEffect = WoundNode->WoundEffect;
bool bCreateDecal = WoundNode->bCreateDecal;
UMaterialInterface* DecalMaterial = WoundNode->DecalMaterial;
float DecalSize = WoundNode->DecalSize;
float DecalLifetime = WoundNode->DecalLifetime;
bool bAffectBoneHealth = WoundNode->bAffectBoneHealth;
float BoneDamage = WoundNode->BoneDamage;
// Get the actor's transform
FTransform ActorTransform = TargetActor->GetActorTransform();
// Create a default wound location if not connected to an input
FVector WoundLocation = ActorTransform.GetLocation() + FVector(0.0f, 0.0f, 100.0f);
// Create the preview wound effect
TObjectPtr<UNiagaraComponent> WoundEffectComponent = nullptr;
if (WoundEffect)
{
WoundEffectComponent = UNiagaraComponent::SpawnSystemAttached(
WoundEffect,
TargetSkeletalMesh,
NAME_None,
WoundLocation,
FRotator::ZeroRotator,
EAttachLocation::KeepWorldPosition,
false);
if (WoundEffectComponent)
{
PreviewNiagaraComponents.Add(WoundEffectComponent);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound effect component"));
}
}
// Create the preview wound decal if needed
TObjectPtr<UDecalComponent> WoundDecalComponent = nullptr;
if (bCreateDecal && DecalMaterial)
{
WoundDecalComponent = CreatePreviewWound(WoundLocation, DecalSize, DecalMaterial);
if (!WoundDecalComponent)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to create wound decal component"));
}
}
// Store the wound transform for later use
FTransform WoundTransform;
WoundTransform.SetLocation(WoundLocation);
PreviewWoundTransforms.Add(WoundTransform);
UE_LOG(LogFLESHPreview, Log, TEXT("Wound node preview created successfully, size: %.2f, depth: %.2f"), WoundSize, WoundDepth);
return WoundEffectComponent != nullptr || WoundDecalComponent != nullptr;
}
TObjectPtr<UStaticMeshComponent> UDismembermentPreviewManager::CreatePreviewCutPlaneMesh(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create cut plane: Invalid world or target actor"));
return nullptr;
}
// Create a static mesh component for the cut plane
TObjectPtr<UStaticMeshComponent> CutPlaneMeshComponent = NewObject<UStaticMeshComponent>(TargetActor);
if (!CutPlaneMeshComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create cut plane component"));
return nullptr;
}
// Register the component
CutPlaneMeshComponent->RegisterComponent();
// Set the mesh to a plane
UStaticMesh* PlaneMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Plane.Plane"));
if (PlaneMesh)
{
CutPlaneMeshComponent->SetStaticMesh(PlaneMesh);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load plane mesh"));
}
// Set the material
if (Material)
{
CutPlaneMeshComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
CutPlaneMeshComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
FRotator Rotation = FRotationMatrix::MakeFromX(Direction).Rotator();
CutPlaneMeshComponent->SetWorldLocationAndRotation(Location, Rotation);
CutPlaneMeshComponent->SetWorldScale3D(FVector(Width, Depth, 1.0f));
// Add to the preview components
PreviewStaticMeshComponents.Add(CutPlaneMeshComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created cut plane mesh, location: %s, direction: %s"),
*Location.ToString(), *Direction.ToString());
return CutPlaneMeshComponent;
}
TObjectPtr<UNiagaraComponent> UDismembermentPreviewManager::CreatePreviewBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure)
{
if (!World || !TargetActor || !BloodEffect)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood effect: Invalid parameters"));
return nullptr;
}
// Create a Niagara component for the blood effect
TObjectPtr<UNiagaraComponent> BloodEffectComponent = UNiagaraComponent::SpawnSystemAttached(
BloodEffect,
TargetSkeletalMesh,
NAME_None,
Location,
FRotator::ZeroRotator,
EAttachLocation::KeepWorldPosition,
false);
if (!BloodEffectComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood effect component"));
return nullptr;
}
// Set parameters
BloodEffectComponent->SetFloatParameter(TEXT("BloodAmount"), BloodAmount);
BloodEffectComponent->SetFloatParameter(TEXT("BloodPressure"), BloodPressure);
// Add to the preview components
PreviewNiagaraComponents.Add(BloodEffectComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood effect, location: %s, blood amount: %.2f, blood pressure: %.2f"),
*Location.ToString(), BloodAmount, BloodPressure);
return BloodEffectComponent;
}
TObjectPtr<UDecalComponent> UDismembermentPreviewManager::CreatePreviewBloodPool(const FVector& Location, float Size, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create blood pool: Invalid world or target actor"));
return nullptr;
}
// Create a decal component for the blood pool
TObjectPtr<UDecalComponent> BloodPoolComponent = NewObject<UDecalComponent>(TargetActor);
if (!BloodPoolComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create blood pool component"));
return nullptr;
}
// Register the component
BloodPoolComponent->RegisterComponent();
// Set the material
if (Material)
{
BloodPoolComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
BloodPoolComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
BloodPoolComponent->SetWorldLocation(Location);
BloodPoolComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f));
BloodPoolComponent->DecalSize = FVector(Size, Size, 10.0f);
// Add to the preview components
PreviewDecalComponents.Add(BloodPoolComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created blood pool decal, location: %s, size: %.2f"),
*Location.ToString(), Size);
return BloodPoolComponent;
}
TObjectPtr<UStaticMeshComponent> UDismembermentPreviewManager::CreatePreviewOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale)
{
if (!World || !TargetActor || !OrganMesh)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create organ: Invalid parameters"));
return nullptr;
}
// Create a static mesh component for the organ
TObjectPtr<UStaticMeshComponent> OrganComponent = NewObject<UStaticMeshComponent>(TargetActor);
if (!OrganComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create organ component"));
return nullptr;
}
// Register the component
OrganComponent->RegisterComponent();
// Set the mesh
OrganComponent->SetStaticMesh(OrganMesh);
// Set the material
if (OrganMaterial)
{
OrganComponent->SetMaterial(0, OrganMaterial);
}
// Attach to bone if specified
if (TargetSkeletalMesh && !AttachBoneName.IsNone())
{
if (TargetSkeletalMesh->GetBoneIndex(AttachBoneName) != INDEX_NONE)
{
OrganComponent->AttachToComponent(TargetSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform, AttachBoneName);
OrganComponent->SetRelativeLocationAndRotation(RelativeLocation, RelativeRotation);
OrganComponent->SetRelativeScale3D(RelativeScale);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Organ attached to bone: %s"), *AttachBoneName.ToString());
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Bone not found: %s, attaching to actor root component"), *AttachBoneName.ToString());
// Set the transform
OrganComponent->SetWorldLocationAndRotation(
TargetActor->GetActorLocation() + RelativeLocation,
TargetActor->GetActorRotation() + RelativeRotation);
OrganComponent->SetWorldScale3D(RelativeScale);
}
}
else
{
// Set the transform
OrganComponent->SetWorldLocationAndRotation(
TargetActor->GetActorLocation() + RelativeLocation,
TargetActor->GetActorRotation() + RelativeRotation);
OrganComponent->SetWorldScale3D(RelativeScale);
}
// Add to the preview components
PreviewStaticMeshComponents.Add(OrganComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created organ component, relative location: %s"), *RelativeLocation.ToString());
return OrganComponent;
}
TObjectPtr<UDecalComponent> UDismembermentPreviewManager::CreatePreviewWound(const FVector& Location, float Size, UMaterialInterface* Material)
{
if (!World || !TargetActor)
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Cannot create wound: Invalid world or target actor"));
return nullptr;
}
// Create a decal component for the wound
TObjectPtr<UDecalComponent> WoundComponent = NewObject<UDecalComponent>(TargetActor);
if (!WoundComponent)
{
UE_LOG(LogFLESHPreview, Error, TEXT("Failed to create wound component"));
return nullptr;
}
// Register the component
WoundComponent->RegisterComponent();
// Set the material
if (Material)
{
WoundComponent->SetMaterial(0, Material);
}
else
{
// Use a default material
UMaterialInterface* DefaultMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
if (DefaultMaterial)
{
WoundComponent->SetMaterial(0, DefaultMaterial);
}
else
{
UE_LOG(LogFLESHPreview, Warning, TEXT("Failed to load default material"));
}
}
// Set the transform
WoundComponent->SetWorldLocation(Location);
WoundComponent->SetWorldRotation(FRotator(-90.0f, 0.0f, 0.0f));
WoundComponent->DecalSize = FVector(Size, Size, 10.0f);
// Add to the preview components
PreviewDecalComponents.Add(WoundComponent);
UE_LOG(LogFLESHPreview, Verbose, TEXT("Created wound decal, location: %s, size: %.2f"),
*Location.ToString(), Size);
return WoundComponent;
}
void UDismembermentPreviewManager::ClearPreviewComponents()
{
// Destroy all preview components
for (TObjectPtr<UNiagaraComponent> Component : PreviewNiagaraComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewNiagaraComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d Niagara components"), PreviewNiagaraComponents.Num());
for (TObjectPtr<UDecalComponent> Component : PreviewDecalComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewDecalComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d decal components"), PreviewDecalComponents.Num());
for (TObjectPtr<UStaticMeshComponent> Component : PreviewStaticMeshComponents)
{
if (Component)
{
Component->DestroyComponent();
}
}
PreviewStaticMeshComponents.Empty();
UE_LOG(LogFLESHPreview, Verbose, TEXT("Cleared %d static mesh components"), PreviewStaticMeshComponents.Num());
// Clear the cut plane mesh
if (PreviewCutPlaneMesh)
{
PreviewCutPlaneMesh->DestroyComponent();
PreviewCutPlaneMesh = nullptr;
}
}

View File

@@ -0,0 +1,377 @@
#include "DismembermentGraph/SDismembermentGraphNode.h"
#include "DismembermentGraph/DismembermentGraphNode.h"
#include "GraphEditorSettings.h"
#include "SGraphPin.h"
#include "SlateOptMacros.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Text/SInlineEditableTextBlock.h"
#include "Widgets/Images/SImage.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SDismembermentGraphNode::Construct(const FArguments& InArgs, UEdGraphNode* InNode)
{
GraphNode = InNode;
UpdateGraphNode();
bIsHovered = false;
}
void SDismembermentGraphNode::UpdateGraphNode()
{
// Clear the widget
ContentScale.Bind(this, &SGraphNode::GetContentScale);
LeftNodeBox.Reset();
RightNodeBox.Reset();
OutputPinBox.Reset();
// Setup the node title
TSharedPtr<SNodeTitle> NodeTitle = SNew(SNodeTitle, GraphNode);
// Create the node body
this->ContentScale.Bind(this, &SGraphNode::GetContentScale);
this->GetOrAddSlot(ENodeZone::Center)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.Body"))
.BorderBackgroundColor(this, &SDismembermentGraphNode::GetNodeColor)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(0.0f)
[
SNew(SOverlay)
// Main node body
+ SOverlay::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
SNew(SVerticalBox)
// Title bar
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.ColorSpill"))
.BorderBackgroundColor(this, &SDismembermentGraphNode::GetNodeTitleColor)
.HAlign(HAlign_Fill)
.VAlign(VAlign_Center)
.Padding(FMargin(10.0f, 5.0f))
[
SNew(SHorizontalBox)
// Node title
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeTitle)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle")
.Margin(FMargin(0.0f, 0.0f, 4.0f, 0.0f))
]
// Node category
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeCategory)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle")
.ColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.8f))
.Margin(FMargin(4.0f, 0.0f, 0.0f, 0.0f))
]
]
]
// Node content
+ SVerticalBox::Slot()
.Padding(0.0f)
.AutoHeight()
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("NoBorder"))
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
.Padding(FMargin(10.0f, 0.0f))
[
SNew(SVerticalBox)
// Node description
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 4.0f))
[
SNew(STextBlock)
.Text(this, &SDismembermentGraphNode::GetNodeDescription)
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
.WrapTextAt(200.0f)
]
// Node preview
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 4.0f))
[
GetNodePreviewWidget()
]
]
]
// Input pins
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(LeftNodeBox, SVerticalBox)
]
// Output pins
+ SVerticalBox::Slot()
.AutoHeight()
[
SAssignNew(RightNodeBox, SVerticalBox)
]
]
]
];
// Create all the pins
CreatePinWidgets();
}
void SDismembermentGraphNode::CreatePinWidgets()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return;
}
// Create the input pins
for (int32 PinIndex = 0; PinIndex < DismembermentNode->Pins.Num(); PinIndex++)
{
UEdGraphPin* Pin = DismembermentNode->Pins[PinIndex];
if (!Pin->bHidden)
{
TSharedPtr<SGraphPin> NewPin = SNew(SGraphPin, Pin);
AddPin(NewPin.ToSharedRef());
}
}
}
void SDismembermentGraphNode::AddPin(const TSharedRef<SGraphPin>& PinToAdd)
{
PinToAdd->SetOwner(SharedThis(this));
const UEdGraphPin* PinObj = PinToAdd->GetPinObj();
const bool bAdvancedParameter = PinObj && PinObj->bAdvancedView;
if (bAdvancedParameter)
{
PinToAdd->SetVisibility(TAttribute<EVisibility>(PinToAdd, &SGraphPin::IsPinVisibleAsAdvanced));
}
if (PinToAdd->GetDirection() == EGPD_Input)
{
LeftNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Left)
.VAlign(VAlign_Center)
.Padding(10.0f, 4.0f)
[
PinToAdd
];
InputPins.Add(PinToAdd);
}
else // EGPD_Output
{
RightNodeBox->AddSlot()
.AutoHeight()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(10.0f, 4.0f)
[
PinToAdd
];
OutputPins.Add(PinToAdd);
}
}
TSharedPtr<SToolTip> SDismembermentGraphNode::GetComplexTooltip()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return nullptr;
}
return SNew(SToolTip)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 2.0f))
[
SNew(STextBlock)
.Text(DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(0.0f, 2.0f))
[
SNew(STextBlock)
.Text(DismembermentNode->GetTooltipText())
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
]
];
}
FReply SDismembermentGraphNode::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Handled().DetectDrag(SharedThis(this), EKeys::LeftMouseButton);
}
FReply SDismembermentGraphNode::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Handled();
}
FReply SDismembermentGraphNode::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
return FReply::Unhandled();
}
void SDismembermentGraphNode::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
bIsHovered = true;
SGraphNode::OnMouseEnter(MyGeometry, MouseEvent);
}
void SDismembermentGraphNode::OnMouseLeave(const FPointerEvent& MouseEvent)
{
bIsHovered = false;
SGraphNode::OnMouseLeave(MouseEvent);
}
UDismembermentGraphNode* SDismembermentGraphNode::GetDismembermentGraphNode() const
{
return Cast<UDismembermentGraphNode>(GraphNode);
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodeTitleWidget()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return SNew(STextBlock).Text(NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node"));
}
return SNew(STextBlock)
.Text(DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle))
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.NodeTitle");
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodeBodyWidget()
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return SNew(STextBlock).Text(NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node"));
}
return SNew(STextBlock)
.Text(DismembermentNode->GetTooltipText())
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
.WrapTextAt(200.0f);
}
TSharedRef<SWidget> SDismembermentGraphNode::GetNodePreviewWidget()
{
// This can be customized for different node types to show a preview
return SNew(SBox)
.WidthOverride(100.0f)
.HeightOverride(100.0f)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("Graph.StateNode.Body"))
.BorderBackgroundColor(FLinearColor(0.1f, 0.1f, 0.1f, 0.5f))
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(NSLOCTEXT("SDismembermentGraphNode", "Preview", "Preview"))
.TextStyle(FEditorStyle::Get(), "Graph.StateNode.Description")
]
];
}
FSlateColor SDismembermentGraphNode::GetNodeColor() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FLinearColor::Black;
}
FLinearColor NodeColor = DismembermentNode->NodeTitleColor;
if (IsNodeSelected())
{
NodeColor = FLinearColor(1.0f, 0.5f, 0.0f);
}
else if (IsNodeHovered())
{
NodeColor = NodeColor * 1.2f;
}
return NodeColor;
}
FSlateColor SDismembermentGraphNode::GetNodeTitleColor() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FLinearColor::Black;
}
return DismembermentNode->NodeTitleColor.LinearRGBToHSV();
}
FText SDismembermentGraphNode::GetNodeTitle() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return NSLOCTEXT("SDismembermentGraphNode", "InvalidNode", "Invalid Node");
}
return DismembermentNode->GetNodeTitle(ENodeTitleType::FullTitle);
}
FText SDismembermentGraphNode::GetNodeCategory() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FText::GetEmpty();
}
return DismembermentNode->NodeCategory;
}
FText SDismembermentGraphNode::GetNodeDescription() const
{
UDismembermentGraphNode* DismembermentNode = GetDismembermentGraphNode();
if (!DismembermentNode)
{
return FText::GetEmpty();
}
return DismembermentNode->NodeDescription;
}
bool SDismembermentGraphNode::IsNodeSelected() const
{
return GraphNode && GraphNode->IsSelected();
}
bool SDismembermentGraphNode::IsNodeHovered() const
{
return bIsHovered;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@@ -0,0 +1,477 @@
#include "DismembermentGraph/SDismembermentPreviewViewport.h"
#include "DismembermentGraph/DismembermentPreviewManager.h"
#include "EditorViewportClient.h"
#include "SEditorViewport.h"
#include "PreviewScene.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/StaticMesh.h"
#include "AdvancedPreviewScene.h"
#include "AssetViewerSettings.h"
#include "EditorModeManager.h"
#include "EngineUtils.h"
#include "GameFramework/Actor.h"
#include "Engine/World.h"
// Preview actor class
class ADismembermentPreviewActor : public AActor
{
public:
ADismembermentPreviewActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Create a skeletal mesh component
SkeletalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("SkeletalMeshComponent"));
RootComponent = SkeletalMeshComponent;
}
// Set the skeletal mesh
void SetSkeletalMesh(USkeletalMesh* InSkeletalMesh)
{
if (SkeletalMeshComponent)
{
SkeletalMeshComponent->SetSkeletalMesh(InSkeletalMesh);
}
}
// Get the skeletal mesh component
USkeletalMeshComponent* GetSkeletalMeshComponent() const
{
return SkeletalMeshComponent;
}
private:
// The skeletal mesh component
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent;
};
//////////////////////////////////////////////////////////////////////////
// SDismembermentPreviewViewport
void SDismembermentPreviewViewport::Construct(const FArguments& InArgs)
{
// Create the preview scene
PreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues()));
// Set up the preview scene
UAssetViewerSettings* Settings = UAssetViewerSettings::Get();
const int32 ProfileIndex = Settings->Profiles.IsValidIndex(0) ? 0 : INDEX_NONE;
if (ProfileIndex != INDEX_NONE)
{
Settings->Profiles[ProfileIndex].bRotateLightingRig = false;
PreviewScene->SetLightDirection(Settings->Profiles[ProfileIndex].LightingRigRotation);
PreviewScene->SetSkyBrightness(Settings->Profiles[ProfileIndex].EnvironmentCubeMapBrightness);
PreviewScene->SetLightColor(Settings->Profiles[ProfileIndex].LightColor);
PreviewScene->SetLightIntensity(Settings->Profiles[ProfileIndex].LightBrightness);
PreviewScene->SetEnvironmentCubeMap(Settings->Profiles[ProfileIndex].EnvironmentCubeMap);
}
// Create the preview actor
CreatePreviewActor();
// Call parent constructor
SEditorViewport::Construct(SEditorViewport::FArguments());
}
SDismembermentPreviewViewport::~SDismembermentPreviewViewport()
{
// Clean up the preview scene
if (PreviewScene.IsValid())
{
PreviewScene->RemoveComponent(PreviewActor);
PreviewScene.Reset();
}
// Clean up the preview actor
if (PreviewActor)
{
if (PreviewActor->GetWorld())
{
PreviewActor->Destroy();
}
PreviewActor = nullptr;
}
// Clean up the viewport client
ViewportClient.Reset();
}
void SDismembermentPreviewViewport::SetPreviewManager(UDismembermentPreviewManager* InPreviewManager)
{
PreviewManager = InPreviewManager;
// Update the viewport client
if (ViewportClient.IsValid())
{
ViewportClient->SetPreviewManager(PreviewManager);
}
// Update the preview actor
if (PreviewManager && PreviewActor)
{
PreviewManager->SetTargetActor(PreviewActor);
}
}
void SDismembermentPreviewViewport::SetPreviewSkeletalMesh(USkeletalMesh* InSkeletalMesh)
{
// Set the skeletal mesh on the preview actor
if (PreviewActor)
{
ADismembermentPreviewActor* PreviewActorCasted = Cast<ADismembermentPreviewActor>(PreviewActor);
if (PreviewActorCasted)
{
PreviewActorCasted->SetSkeletalMesh(InSkeletalMesh);
}
}
// Update the preview actor
UpdatePreviewActor();
}
AActor* SDismembermentPreviewViewport::GetPreviewActor() const
{
return PreviewActor;
}
void SDismembermentPreviewViewport::RefreshViewport()
{
// Invalidate the viewport
if (ViewportClient.IsValid())
{
ViewportClient->Invalidate();
}
}
TSharedRef<FEditorViewportClient> SDismembermentPreviewViewport::MakeEditorViewportClient()
{
// Create the viewport client
ViewportClient = MakeShareable(new SDismembermentPreviewViewportClient(PreviewScene.Get(), SharedThis(this)));
// Set the preview manager
ViewportClient->SetPreviewManager(PreviewManager);
// Set up the viewport client
ViewportClient->SetViewLocation(FVector(0.0f, 0.0f, 200.0f));
ViewportClient->SetViewRotation(FRotator(-20.0f, 0.0f, 0.0f));
ViewportClient->SetViewLocationForOrbiting(FVector(0.0f, 0.0f, 0.0f));
ViewportClient->bSetListenerPosition = false;
ViewportClient->EngineShowFlags.SetPostProcessing(true);
ViewportClient->EngineShowFlags.SetLighting(true);
ViewportClient->EngineShowFlags.SetIndirectLightingCache(true);
ViewportClient->EngineShowFlags.SetSeparateTranslucency(true);
ViewportClient->EngineShowFlags.SetTemporalAA(true);
ViewportClient->EngineShowFlags.SetGrid(false);
ViewportClient->EngineShowFlags.SetAtmosphere(true);
ViewportClient->EngineShowFlags.SetSkeletalMeshes(true);
ViewportClient->EngineShowFlags.SetDecals(true);
ViewportClient->EngineShowFlags.SetParticles(true);
ViewportClient->EngineShowFlags.SetVolumetricFog(true);
ViewportClient->EngineShowFlags.SetDynamicShadows(true);
ViewportClient->EngineShowFlags.SetSkyLighting(true);
ViewportClient->EngineShowFlags.SetAmbientOcclusion(true);
ViewportClient->EngineShowFlags.SetScreenSpaceReflections(true);
ViewportClient->EngineShowFlags.SetAntiAliasing(true);
ViewportClient->EngineShowFlags.SetMotionBlur(false);
ViewportClient->EngineShowFlags.SetBounds(false);
ViewportClient->EngineShowFlags.SetCollision(false);
ViewportClient->EngineShowFlags.SetBSP(false);
ViewportClient->EngineShowFlags.SetFog(true);
ViewportClient->EngineShowFlags.SetStaticMeshes(true);
ViewportClient->EngineShowFlags.SetLandscape(true);
ViewportClient->EngineShowFlags.SetTranslucency(true);
ViewportClient->EngineShowFlags.SetInstancedFoliage(true);
ViewportClient->EngineShowFlags.SetInstancedGrass(true);
ViewportClient->EngineShowFlags.SetInstancedStaticMeshes(true);
ViewportClient->EngineShowFlags.SetInstancedFoliage(true);
ViewportClient->EngineShowFlags.SetInstancedGrass(true);
ViewportClient->EngineShowFlags.SetInstancedStaticMeshes(true);
ViewportClient->EngineShowFlags.SetSplines(true);
ViewportClient->EngineShowFlags.SetSelectionOutline(true);
ViewportClient->EngineShowFlags.SetMeshEdges(false);
ViewportClient->EngineShowFlags.SetVertexColors(false);
ViewportClient->EngineShowFlags.SetLightComplexity(false);
ViewportClient->EngineShowFlags.SetShaderComplexity(false);
ViewportClient->EngineShowFlags.SetStaticMeshLODColoration(false);
ViewportClient->EngineShowFlags.SetLightMapDensity(false);
ViewportClient->EngineShowFlags.SetLightInfluences(false);
ViewportClient->EngineShowFlags.SetOptimizeVizibility(false);
ViewportClient->EngineShowFlags.SetTextRender(true);
ViewportClient->EngineShowFlags.SetTestImage(false);
ViewportClient->EngineShowFlags.SetVisualizeLightCulling(false);
ViewportClient->EngineShowFlags.SetPrecomputedVisibility(true);
ViewportClient->EngineShowFlags.SetPrecomputedVisibilityCells(false);
ViewportClient->EngineShowFlags.SetVolumes(false);
ViewportClient->EngineShowFlags.SetGame(false);
ViewportClient->EngineShowFlags.SetBSPTriangles(false);
ViewportClient->EngineShowFlags.SetHitProxies(false);
ViewportClient->EngineShowFlags.SetGBufferHints(false);
ViewportClient->EngineShowFlags.SetVisualizeShadingModels(false);
ViewportClient->EngineShowFlags.SetVisualizeAdaptiveDOF(false);
ViewportClient->EngineShowFlags.SetVisualizeSSR(false);
ViewportClient->EngineShowFlags.SetVisualizeSSS(false);
ViewportClient->EngineShowFlags.SetVolumetricLightmap(true);
ViewportClient->EngineShowFlags.SetVisualizeOutOfBoundsPixels(false);
ViewportClient->EngineShowFlags.SetHighResScreenshotMask(false);
ViewportClient->EngineShowFlags.SetHMDDistortion(false);
ViewportClient->EngineShowFlags.SetStereoRendering(false);
ViewportClient->EngineShowFlags.SetTonemapper(true);
ViewportClient->EngineShowFlags.SetLumenReflections(true);
ViewportClient->EngineShowFlags.SetLumenGlobalIllumination(true);
ViewportClient->EngineShowFlags.SetVirtualShadowMaps(true);
ViewportClient->EngineShowFlags.SetNanite(true);
return ViewportClient.ToSharedRef();
}
void SDismembermentPreviewViewport::OnFocusViewportToSelection()
{
// Focus the viewport on the preview actor
if (ViewportClient.IsValid() && PreviewActor)
{
ViewportClient->FocusViewportOnBox(PreviewActor->GetComponentsBoundingBox());
}
}
bool SDismembermentPreviewViewport::IsVisible() const
{
return SEditorViewport::IsVisible();
}
void SDismembermentPreviewViewport::CreatePreviewActor()
{
// Clean up any existing preview actor
if (PreviewActor)
{
if (PreviewActor->GetWorld())
{
PreviewActor->Destroy();
}
PreviewActor = nullptr;
}
// Create a new preview actor
if (PreviewScene.IsValid())
{
UWorld* World = PreviewScene->GetWorld();
if (World)
{
PreviewActor = World->SpawnActor<ADismembermentPreviewActor>();
if (PreviewActor)
{
// Add the actor to the preview scene
PreviewScene->AddComponent(PreviewActor->GetRootComponent(), FTransform::Identity);
// Update the preview manager
if (PreviewManager)
{
PreviewManager->SetTargetActor(PreviewActor);
}
}
}
}
}
void SDismembermentPreviewViewport::UpdatePreviewActor()
{
// Update the preview manager
if (PreviewManager && PreviewActor)
{
PreviewManager->SetTargetActor(PreviewActor);
}
// Refresh the viewport
RefreshViewport();
}
//////////////////////////////////////////////////////////////////////////
// SDismembermentPreviewViewportClient
SDismembermentPreviewViewportClient::SDismembermentPreviewViewportClient(FPreviewScene* InPreviewScene, const TWeakPtr<SDismembermentPreviewViewport>& InViewportWidget)
: FEditorViewportClient(nullptr, InPreviewScene, InViewportWidget)
, ViewportWidget(InViewportWidget)
, PreviewManager(nullptr)
{
// Set up the viewport client
SetRealtime(true);
bSetListenerPosition = false;
bShouldCheckHitProxy = true;
bShowGrid = false;
bDisableInput = false;
bAllowMatineePreview = false;
bUsingOrbitCamera = true;
bForceInitialFocus = true;
bShowBounds = false;
bShowBoundsActors = false;
bShowFloor = true;
bShowBoxes = false;
bShowWireframe = false;
bShowCollision = false;
bShowSockets = true;
bDrawAxes = false;
bShowNormals = false;
bShowTangents = false;
bShowBinormals = false;
bShowConstraints = false;
bShowCameras = false;
bShowLightRadius = false;
bShowLightVolumes = false;
bShowLightInfluences = false;
bShowLightFrustums = false;
bShowShadowFrustums = false;
bShowLightingOnlyOverlap = false;
bShowLightingVisualization = false;
bShowLightingStats = false;
bShowShadowDensity = false;
bShowPhysicalMaterialMasks = false;
bShowSpriteSockets = false;
bShowParticleSystemComponents = true;
bShowParticleSystems = true;
bShowLOD = false;
bShowHUD = false;
bShowDebugInfo = false;
bDrawPreviewShadowsInGame = false;
bEnableDirectLightMap = true;
bEnableIndirectLightMap = true;
bEnableColorizeDistancefieldLightingMask = false;
bEnableColorizeDistanceFieldLightingMaskGrayscale = false;
bEnableColorizeDistanceFieldLightingMaskColor = false;
bEnableColorizeDistanceFieldOcclusion = false;
bEnableColorizeDistanceFieldOcclusionGrayscale = false;
bEnableColorizeDistanceFieldOcclusionColor = false;
bEnableColorizeDistanceFieldLightingMaskGrayscale = false;
bEnableColorizeDistanceFieldLightingMaskColor = false;
bEnableColorizeDistanceFieldOcclusionGrayscale = false;
bEnableColorizeDistanceFieldOcclusionColor = false;
bEnableColorizeDistanceFieldLightingMask = false;
bEnableColorizeDistanceFieldOcclusion = false;
bEnableColorizeDistanceFieldGradient = false;
bEnableColorizeDistanceFieldGradientGrayscale = false;
bEnableColorizeDistanceFieldGradientColor = false;
bEnableColorizeDistanceFieldGradientGrayscale = false;
bEnableColorizeDistanceFieldGradientColor = false;
bEnableColorizeDistanceFieldGradient = false;
bEnableColorizeDistanceField = false;
bEnableColorizeDistanceFieldGrayscale = false;
bEnableColorizeDistanceFieldColor = false;
bEnableColorizeDistanceFieldGrayscale = false;
bEnableColorizeDistanceFieldColor = false;
bEnableColorizeDistanceField = false;
bEnableColorizeDistanceFieldMeshSDF = false;
bEnableColorizeDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeDistanceFieldMeshSDFColor = false;
bEnableColorizeDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeDistanceFieldMeshSDFColor = false;
bEnableColorizeDistanceFieldMeshSDF = false;
bEnableColorizeVolumetricLightmap = false;
bEnableColorizeVolumetricLightmapDensity = false;
bEnableColorizeVolumetricLightmapSamples = false;
bEnableColorizeGlobalDistanceField = false;
bEnableColorizeGlobalDistanceFieldGrayscale = false;
bEnableColorizeGlobalDistanceFieldColor = false;
bEnableColorizeGlobalDistanceFieldGrayscale = false;
bEnableColorizeGlobalDistanceFieldColor = false;
bEnableColorizeGlobalDistanceField = false;
bEnableColorizeGlobalDistanceFieldMeshSDF = false;
bEnableColorizeGlobalDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeGlobalDistanceFieldMeshSDFColor = false;
bEnableColorizeGlobalDistanceFieldMeshSDFGrayscale = false;
bEnableColorizeGlobalDistanceFieldMeshSDFColor = false;
bEnableColorizeGlobalDistanceFieldMeshSDF = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusion = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionColor = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusionColor = false;
bEnableColorizeVolumetricLightmapDirectionalOcclusion = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensity = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapDirectionalIndirectIntensity = false;
bEnableColorizeVolumetricLightmapIndirectIntensity = false;
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
bEnableColorizeVolumetricLightmapIndirectIntensity = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensity = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityColor = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityGrayscale = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensityColor = false;
bEnableColorizeVolumetricLightmapEnvironmentIntensity = false;
bEnableColorizeVolumetricLightmapSHBand0 = false;
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand0Color = false;
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand0Color = false;
bEnableColorizeVolumetricLightmapSHBand0 = false;
bEnableColorizeVolumetricLightmapSHBand1 = false;
bEnableColorizeVolumetricLightmapSHBand1Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand1Color = false;
bEnableColorizeVolumetricLightmapSHBand1Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand1Color = false;
bEnableColorizeVolumetricLightmapSHBand1 = false;
bEnableColorizeVolumetricLightmapSHBand2 = false;
bEnableColorizeVolumetricLightmapSHBand2Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand2Color = false;
bEnableColorizeVolumetricLightmapSHBand2Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand2Color = false;
bEnableColorizeVolumetricLightmapSHBand2 = false;
bEnableColorizeVolumetricLightmapSHBand3 = false;
bEnableColorizeVolumetricLightmapSHBand3Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand3Color = false;
bEnableColorizeVolumetricLightmapSHBand3Grayscale = false;
bEnableColorizeVolumetricLightmapSHBand3Color = false;
bEnableColorizeVolumetricLightmapSHBand3 = false;
}
SDismembermentPreviewViewportClient::~SDismembermentPreviewViewportClient()
{
// Clean up
PreviewManager = nullptr;
}
void SDismembermentPreviewViewportClient::Tick(float DeltaSeconds)
{
// Call parent tick
FEditorViewportClient::Tick(DeltaSeconds);
// Tick the preview manager
if (PreviewManager)
{
PreviewManager->Tick(DeltaSeconds);
}
}
void SDismembermentPreviewViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
// Call parent draw
FEditorViewportClient::Draw(View, PDI);
// Draw preview elements
if (PreviewManager)
{
// TODO: Draw preview elements
}
}
void SDismembermentPreviewViewportClient::DrawCanvas(FViewport& InViewport, FSceneView& View, FCanvas& Canvas)
{
// Call parent draw canvas
FEditorViewportClient::DrawCanvas(InViewport, View, Canvas);
// Draw preview elements
if (PreviewManager)
{
// TODO: Draw preview elements
}
}
void SDismembermentPreviewViewportClient::SetPreviewManager(UDismembermentPreviewManager* InPreviewManager)
{
PreviewManager = InPreviewManager;
}

View File

@@ -0,0 +1,546 @@
#include "FLESHEditor.h"
#include "FLESHEditorCommands.h"
#include "FLESHEditorStyle.h"
#include "MatrixInputWidget.h"
#include "DismembermentGraph/DismembermentGraphEditor.h"
#include "Framework/Docking/TabManager.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Input/SButton.h"
#include "PropertyEditorModule.h"
#include "EditorStyleSet.h"
#include "LevelEditor.h"
#include "ToolMenus.h"
#define LOCTEXT_NAMESPACE "FLESHEditor"
const FName FFLESHEditor::ViewportTabId(TEXT("FLESHEditor_Viewport"));
const FName FFLESHEditor::DetailsTabId(TEXT("FLESHEditor_Details"));
const FName FFLESHEditor::AssetBrowserTabId(TEXT("FLESHEditor_AssetBrowser"));
const FName FFLESHEditor::MatrixEditorTabId(TEXT("FLESHEditor_MatrixEditor"));
const FName FFLESHEditor::GraphEditorTabId(TEXT("FLESHEditor_GraphEditor"));
const FName FFLESHEditor::ToolbarTabId(TEXT("FLESHEditor_Toolbar"));
FFLESHEditor::FFLESHEditor()
{
}
FFLESHEditor::~FFLESHEditor()
{
}
void FFLESHEditor::InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost)
{
// Create command list
CreateCommandList();
// Create tab layout
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("FLESHEditorLayout_v1.0")
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.1f)
->AddTab(ToolbarTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.7f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7f)
->AddTab(ViewportTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.3f)
->AddTab(MatrixEditorTabId, ETabState::OpenedTab)
)
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.3f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.6f)
->AddTab(DetailsTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.4f)
->AddTab(AssetBrowserTabId, ETabState::OpenedTab)
)
)
)
);
// Initialize toolkit
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, FName("FLESHEditorApp"), StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar);
}
void FFLESHEditor::RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
// Register tab spawners
InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Viewport))
.SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports"));
InTabManager->RegisterTabSpawner(DetailsTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Details))
.SetDisplayName(LOCTEXT("DetailsTab", "Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner(AssetBrowserTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_AssetBrowser))
.SetDisplayName(LOCTEXT("AssetBrowserTab", "Asset Browser"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "ContentBrowser.TabIcon"));
InTabManager->RegisterTabSpawner(MatrixEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_MatrixEditor))
.SetDisplayName(LOCTEXT("MatrixEditorTab", "Matrix Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "PropertyEditor.Grid.TabIcon"));
InTabManager->RegisterTabSpawner(GraphEditorTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_GraphEditor))
.SetDisplayName(LOCTEXT("GraphEditorTab", "Graph Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner(ToolbarTabId, FOnSpawnTab::CreateSP(this, &FFLESHEditor::SpawnTab_Toolbar))
.SetDisplayName(LOCTEXT("ToolbarTab", "Toolbar"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Toolbar"));
}
void FFLESHEditor::UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
// Unregister tab spawners
InTabManager->UnregisterTabSpawner(ViewportTabId);
InTabManager->UnregisterTabSpawner(DetailsTabId);
InTabManager->UnregisterTabSpawner(AssetBrowserTabId);
InTabManager->UnregisterTabSpawner(MatrixEditorTabId);
InTabManager->UnregisterTabSpawner(GraphEditorTabId);
InTabManager->UnregisterTabSpawner(ToolbarTabId);
}
FName FFLESHEditor::GetToolkitFName() const
{
return FName("FLESHEditor");
}
FText FFLESHEditor::GetBaseToolkitName() const
{
return LOCTEXT("FLESHEditorAppLabel", "FLESH Editor");
}
FString FFLESHEditor::GetWorldCentricTabPrefix() const
{
return TEXT("FLESH ");
}
FLinearColor FFLESHEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.7f, 0.0f, 0.0f, 0.5f);
}
void FFLESHEditor::OpenEditor()
{
// Create new editor instance
TSharedRef<FFLESHEditor> NewEditor = MakeShareable(new FFLESHEditor());
NewEditor->InitFLESHEditor(EToolkitMode::Standalone, nullptr);
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("ViewportTitle", "Viewport"))
[
CreateViewportWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Details(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("DetailsTitle", "Details"))
[
CreateDetailsWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_AssetBrowser(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("AssetBrowserTitle", "Asset Browser"))
[
CreateAssetBrowserWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_MatrixEditor(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("MatrixEditorTitle", "Matrix Editor"))
[
CreateMatrixEditorWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_GraphEditor(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("GraphEditorTitle", "Graph Editor"))
[
CreateGraphEditorWidget()
];
}
TSharedRef<SDockTab> FFLESHEditor::SpawnTab_Toolbar(const FSpawnTabArgs& Args)
{
return SNew(SDockTab)
.Label(LOCTEXT("ToolbarTitle", "Toolbar"))
[
CreateToolbarWidget()
];
}
TSharedRef<SWidget> FFLESHEditor::CreateViewportWidget()
{
// Create viewport widget
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("ViewportHeader", "FLESH Viewport"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("ViewportPlaceholder", "Character model and dismemberment effects will be displayed here"))
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateDetailsWidget()
{
// Create details panel
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bUpdatesFromSelection = true;
DetailsViewArgs.bLockable = false;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
DetailsViewArgs.NotifyHook = nullptr;
DetailsViewArgs.bSearchInitialKeyFocus = false;
DetailsViewArgs.ViewIdentifier = NAME_None;
DetailsViewArgs.DefaultsOnlyVisibility = EEditDefaultsOnlyNodeVisibility::Automatic;
DetailsViewArgs.bShowOptions = true;
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
DetailsWidget = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("DetailsHeader", "Property Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
DetailsWidget.ToSharedRef()
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateAssetBrowserWidget()
{
// Create asset browser
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("AssetBrowserHeader", "Asset Browser"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportCharacterModel", "Import Character Model"))
.OnClicked_Lambda([this]() { OnImportCharacterModel(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportOrganModel", "Import Organ Model"))
.OnClicked_Lambda([this]() { OnImportOrganModel(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportSkeletonModel", "Import Skeleton Model"))
.OnClicked_Lambda([this]() { OnImportSkeletonModel(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("ImportPhysicsAsset", "Import Physics Asset"))
.OnClicked_Lambda([this]() { OnImportPhysicsAsset(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateMatrixEditorWidget()
{
// Create matrix editor
MatrixEditorWidget = SNew(SMatrixInputWidget)
.Matrix(FMatrix::Identity)
.OnMatrixChanged_Lambda([this](const FMatrix& NewMatrix) {
// Handle matrix change
});
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("MatrixEditorHeader", "Matrix Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
MatrixEditorWidget.ToSharedRef()
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("TestMatrix", "Test Matrix"))
.OnClicked_Lambda([this]() { OnTestMatrix(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateGraphEditorWidget()
{
// Create graph editor
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorHeader", "Graph Editor"))
.Font(FEditorStyle::GetFontStyle("HeadingFont"))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.DarkGroupBorder"))
.Padding(4.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("GraphEditorPlaceholder", "Dismemberment system logic graph will be displayed here"))
]
]
];
}
TSharedRef<SWidget> FFLESHEditor::CreateToolbarWidget()
{
// Create toolbar
return SNew(SBorder)
.BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(4.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("DismembermentGraphEditor", "Dismemberment Graph Editor"))
.OnClicked_Lambda([this]() { OnOpenDismembermentGraphEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("AnatomicalLayerEditor", "Anatomical Layer Editor"))
.OnClicked_Lambda([this]() { OnOpenAnatomicalLayerEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("BooleanCutTool", "Boolean Cut Tool"))
.OnClicked_Lambda([this]() { OnOpenBooleanCutTool(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2.0f)
[
SNew(SButton)
.Text(LOCTEXT("BloodSystemEditor", "Blood System Editor"))
.OnClicked_Lambda([this]() { OnOpenBloodSystemEditor(); return FReply::Handled(); })
.ButtonStyle(FFLESHEditorStyle::Get(), "FLESHEditor.RoundedButton")
]
];
}
void FFLESHEditor::CreateCommandList()
{
// Create command list
CommandList = MakeShareable(new FUICommandList);
// Bind commands
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenDismembermentGraphEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenDismembermentGraphEditor),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenAnatomicalLayerEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenAnatomicalLayerEditor),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenBooleanCutTool,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBooleanCutTool),
FCanExecuteAction());
CommandList->MapAction(
FFLESHEditorCommands::Get().OpenBloodSystemEditor,
FExecuteAction::CreateSP(this, &FFLESHEditor::OnOpenBloodSystemEditor),
FCanExecuteAction());
}
void FFLESHEditor::OnOpenDismembermentGraphEditor()
{
// Open dismemberment graph editor
FGlobalTabmanager::Get()->TryInvokeTab(GraphEditorTabId);
}
void FFLESHEditor::OnOpenAnatomicalLayerEditor()
{
// Open anatomical layer editor
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("AnatomicalLayerEditorNotImplemented", "Anatomical Layer Editor is not implemented yet"));
}
void FFLESHEditor::OnOpenBooleanCutTool()
{
// Open boolean cut tool
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BooleanCutToolNotImplemented", "Boolean Cut Tool is not implemented yet"));
}
void FFLESHEditor::OnOpenBloodSystemEditor()
{
// Open blood system editor
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("BloodSystemEditorNotImplemented", "Blood System Editor is not implemented yet"));
}
void FFLESHEditor::OnImportCharacterModel()
{
// Import character model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportCharacterModelNotImplemented", "Import Character Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportOrganModel()
{
// Import organ model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportOrganModelNotImplemented", "Import Organ Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportSkeletonModel()
{
// Import skeleton model
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportSkeletonModelNotImplemented", "Import Skeleton Model feature is not implemented yet"));
}
void FFLESHEditor::OnImportPhysicsAsset()
{
// Import physics asset
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ImportPhysicsAssetNotImplemented", "Import Physics Asset feature is not implemented yet"));
}
void FFLESHEditor::OnTestMatrix()
{
// Test matrix
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("TestMatrixNotImplemented", "Test Matrix feature is not implemented yet"));
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,14 @@
#include "FLESHEditorCommands.h"
#define LOCTEXT_NAMESPACE "FLESHEditorCommands"
void FFLESHEditorCommands::RegisterCommands()
{
UI_COMMAND(OpenFLESHEditor, "FLESH", "Open FLESH Dismemberment System Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenDismembermentGraphEditor, "Dismemberment Graph", "Open Dismemberment System Graph Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenAnatomicalLayerEditor, "Anatomical Layer", "Open Anatomical Layer Editor", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenBooleanCutTool, "Boolean Cut", "Open Boolean Cut Tool", EUserInterfaceActionType::Button, FInputChord());
UI_COMMAND(OpenBloodSystemEditor, "Blood System", "Open Blood System Editor", EUserInterfaceActionType::Button, FInputChord());
}
#undef LOCTEXT_NAMESPACE

View File

@@ -0,0 +1,74 @@
#include "FLESHEditorModule.h"
#include "Modules/ModuleManager.h"
#include "ToolMenus.h"
#include "FLESHEditorStyle.h"
#include "FLESHEditorCommands.h"
#include "FLESHEditor.h"
#include "LevelEditor.h"
#define LOCTEXT_NAMESPACE "FFLESHEditorModule"
void FFLESHEditorModule::StartupModule()
{
// This code will execute after your module is loaded into memory
// The exact timing is specified in the .uplugin file per-module
// Initialize style
FFLESHEditorStyle::Initialize();
FFLESHEditorStyle::ReloadTextures();
// Register commands
FFLESHEditorCommands::Register();
// Map commands
PluginCommands = MakeShareable(new FUICommandList);
PluginCommands->MapAction(
FFLESHEditorCommands::Get().OpenFLESHEditor,
FExecuteAction::CreateRaw(this, &FFLESHEditorModule::OpenFLESHEditor),
FCanExecuteAction());
// Register editor menus
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateLambda([this]()
{
// Add menu items
FToolMenuOwnerScoped OwnerScoped(this);
// Add to main menu
{
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
Section.AddMenuEntryWithCommandList(FFLESHEditorCommands::Get().OpenFLESHEditor, PluginCommands);
}
// Add to toolbar
{
UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");
FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FFLESHEditorCommands::Get().OpenFLESHEditor));
Entry.SetCommandList(PluginCommands);
}
}));
}
void FFLESHEditorModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module
// For modules that support dynamic reloading, we call this function before unloading the module
// Unregister style
FFLESHEditorStyle::Shutdown();
// Unregister editor menus
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
void FFLESHEditorModule::OpenFLESHEditor()
{
// Open FLESH editor
FFLESHEditor::OpenEditor();
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FFLESHEditorModule, FLESHEditor)

View File

@@ -0,0 +1,92 @@
#include "FLESHEditorStyle.h"
#include "Styling/SlateStyleRegistry.h"
#include "Styling/SlateTypes.h"
#include "Styling/CoreStyle.h"
#include "Interfaces/IPluginManager.h"
#include "SlateOptMacros.h"
TSharedPtr<FSlateStyleSet> FFLESHEditorStyle::StyleInstance = nullptr;
void FFLESHEditorStyle::Initialize()
{
if (!StyleInstance.IsValid())
{
StyleInstance = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
}
}
void FFLESHEditorStyle::Shutdown()
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
ensure(StyleInstance.IsUnique());
StyleInstance.Reset();
}
void FFLESHEditorStyle::ReloadTextures()
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}
}
FName FFLESHEditorStyle::GetStyleSetName()
{
static FName StyleSetName(TEXT("FLESHEditorStyle"));
return StyleSetName;
}
const ISlateStyle& FFLESHEditorStyle::Get()
{
return *StyleInstance;
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
TSharedRef<FSlateStyleSet> FFLESHEditorStyle::Create()
{
TSharedRef<FSlateStyleSet> StyleRef = MakeShareable(new FSlateStyleSet(GetStyleSetName()));
StyleRef->SetContentRoot(IPluginManager::Get().FindPlugin(TEXT("FLESH"))->GetBaseDir() / TEXT("Resources"));
// Button styles
const FVector2D Icon16x16(16.0f, 16.0f);
const FVector2D Icon20x20(20.0f, 20.0f);
const FVector2D Icon40x40(40.0f, 40.0f);
const FVector2D Icon64x64(64.0f, 64.0f);
const FVector2D Icon128x128(128.0f, 128.0f);
// Main FLESH editor button
StyleRef->Set("FLESHEditor.OpenFLESHEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenFLESHEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Dismemberment graph editor button
StyleRef->Set("FLESHEditor.OpenDismembermentGraphEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenDismembermentGraphEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Anatomical layer editor button
StyleRef->Set("FLESHEditor.OpenAnatomicalLayerEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenAnatomicalLayerEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Boolean cut tool button
StyleRef->Set("FLESHEditor.OpenBooleanCutTool", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenBooleanCutTool.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Blood system editor button
StyleRef->Set("FLESHEditor.OpenBloodSystemEditor", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon40x40));
StyleRef->Set("FLESHEditor.OpenBloodSystemEditor.Small", new FSlateImageBrush(StyleRef->RootToContentDir(TEXT("Icon128.png")), Icon20x20));
// Button style - rounded corners
const FButtonStyle RoundedButtonStyle = FButtonStyle()
.SetNormal(FSlateRoundedBoxBrush(FLinearColor(0.05f, 0.05f, 0.05f, 1.0f), 8.0f))
.SetHovered(FSlateRoundedBoxBrush(FLinearColor(0.1f, 0.1f, 0.1f, 1.0f), 8.0f))
.SetPressed(FSlateRoundedBoxBrush(FLinearColor(0.2f, 0.2f, 0.2f, 1.0f), 8.0f))
.SetNormalPadding(FMargin(4, 2, 4, 2))
.SetPressedPadding(FMargin(4, 3, 4, 1));
StyleRef->Set("FLESHEditor.RoundedButton", RoundedButtonStyle);
return StyleRef;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@@ -0,0 +1,223 @@
#include "MatrixInputWidget.h"
#include "SlateOptMacros.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "EditorStyleSet.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMatrixInputWidget::Construct(const FArguments& InArgs)
{
Matrix = InArgs._Matrix;
OnMatrixChanged = InArgs._OnMatrixChanged;
// Create a grid panel for the matrix elements
GridPanel = SNew(SGridPanel);
// Create numeric entry boxes for each matrix element
NumericEntryBoxes.SetNum(16); // 4x4 matrix
// Add labels for rows and columns
for (int32 Col = 0; Col < 4; ++Col)
{
GridPanel->AddSlot(Col + 1, 0)
[
SNew(STextBlock)
.Text(FText::Format(FText::FromString("Column {0}"), FText::AsNumber(Col)))
.Margin(FMargin(5.0f))
];
}
for (int32 Row = 0; Row < 4; ++Row)
{
GridPanel->AddSlot(0, Row + 1)
[
SNew(STextBlock)
.Text(FText::Format(FText::FromString("Row {0}"), FText::AsNumber(Row)))
.Margin(FMargin(5.0f))
];
}
// Add numeric entry boxes for each matrix element
for (int32 Row = 0; Row < 4; ++Row)
{
for (int32 Col = 0; Col < 4; ++Col)
{
TSharedPtr<SNumericEntryBox<float>> NumericEntryBox = CreateMatrixElementWidget(Row, Col);
NumericEntryBoxes[Row * 4 + Col] = NumericEntryBox;
GridPanel->AddSlot(Col + 1, Row + 1)
[
SNew(SBox)
.WidthOverride(80.0f)
.Padding(FMargin(2.0f))
[
NumericEntryBox.ToSharedRef()
]
];
}
}
// Create buttons for common operations
TSharedPtr<SUniformGridPanel> ButtonPanel = SNew(SUniformGridPanel)
.SlotPadding(FMargin(2.0f));
ButtonPanel->AddSlot(0, 0)
[
SNew(SButton)
.Text(FText::FromString("Reset to Identity"))
.ToolTipText(FText::FromString("Reset the matrix to identity"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
ResetToIdentity();
return FReply::Handled();
}))
];
ButtonPanel->AddSlot(1, 0)
[
SNew(SButton)
.Text(FText::FromString("Set from Rotation"))
.ToolTipText(FText::FromString("Set the matrix from rotation values (degrees)"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
SetFromEulerAngles(0.0f, 0.0f, 0.0f);
return FReply::Handled();
}))
];
ButtonPanel->AddSlot(2, 0)
[
SNew(SButton)
.Text(FText::FromString("Set from Translation"))
.ToolTipText(FText::FromString("Set the matrix from translation values"))
.OnClicked(FOnClicked::CreateLambda([this]()
{
SetFromTranslation(FVector::ZeroVector);
return FReply::Handled();
}))
];
// Main widget layout
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5.0f)
[
SNew(STextBlock)
.Text(FText::FromString("Matrix Input"))
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 14))
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(5.0f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
GridPanel.ToSharedRef()
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5.0f)
[
ButtonPanel.ToSharedRef()
]
];
// Update the UI with the initial matrix value
UpdateUI();
}
void SMatrixInputWidget::SetMatrix(const FMatrix& InMatrix)
{
Matrix = InMatrix;
UpdateUI();
// Notify listeners of the change
if (OnMatrixChanged.IsBound())
{
OnMatrixChanged.Execute(Matrix);
}
}
FMatrix SMatrixInputWidget::GetMatrix() const
{
return Matrix;
}
void SMatrixInputWidget::ResetToIdentity()
{
SetMatrix(FMatrix::Identity);
}
void SMatrixInputWidget::SetFromEulerAngles(float Roll, float Pitch, float Yaw)
{
FRotator Rotator(Pitch, Yaw, Roll);
FMatrix RotationMatrix = FRotationMatrix::Make(Rotator);
SetMatrix(RotationMatrix);
}
void SMatrixInputWidget::SetFromTranslation(const FVector& Translation)
{
FMatrix TranslationMatrix = FMatrix::Identity;
TranslationMatrix.SetOrigin(Translation);
SetMatrix(TranslationMatrix);
}
void SMatrixInputWidget::SetFromTransform(const FTransform& Transform)
{
SetMatrix(Transform.ToMatrixWithScale());
}
TSharedPtr<SNumericEntryBox<float>> SMatrixInputWidget::CreateMatrixElementWidget(int32 Row, int32 Col)
{
return SNew(SNumericEntryBox<float>)
.Value_Lambda([this, Row, Col]() -> float
{
return Matrix.M[Row][Col];
})
.OnValueChanged(SNumericEntryBox<float>::FOnValueChanged::CreateSP(this, &SMatrixInputWidget::OnMatrixElementChanged, Row, Col))
.AllowSpin(true)
.Delta(0.1f)
.MinValue(-10000.0f)
.MaxValue(10000.0f)
.MinSliderValue(-10.0f)
.MaxSliderValue(10.0f);
}
void SMatrixInputWidget::OnMatrixElementChanged(float NewValue, int32 Row, int32 Col)
{
// Update the matrix element
Matrix.M[Row][Col] = NewValue;
// Notify listeners of the change
if (OnMatrixChanged.IsBound())
{
OnMatrixChanged.Execute(Matrix);
}
}
void SMatrixInputWidget::UpdateUI()
{
// Update each numeric entry box with the current matrix values
for (int32 Row = 0; Row < 4; ++Row)
{
for (int32 Col = 0; Col < 4; ++Col)
{
int32 Index = Row * 4 + Col;
if (NumericEntryBoxes.IsValidIndex(Index) && NumericEntryBoxes[Index].IsValid())
{
NumericEntryBoxes[Index]->SetValue(Matrix.M[Row][Col]);
}
}
}
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@@ -0,0 +1,153 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "AnatomicalStructureBrush.generated.h"
// Forward declarations
class USkeletalMesh;
class UStaticMesh;
/**
* Anatomical brush type enumeration
*/
UENUM(BlueprintType)
enum class EAnatomicalBrushType : uint8
{
Bone, // Bone brush
Muscle, // Muscle brush
Organ, // Organ brush
Vessel, // Blood vessel brush
Nerve, // Nerve brush
Custom // Custom brush
};
/**
* Anatomical brush settings
*/
USTRUCT(BlueprintType)
struct FAnatomicalBrushSettings
{
GENERATED_BODY()
// Brush type
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush")
EAnatomicalBrushType BrushType = EAnatomicalBrushType::Bone;
// Brush size
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (ClampMin = "0.1", ClampMax = "100.0"))
float BrushSize = 10.0f;
// Brush strength
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float BrushStrength = 0.5f;
// Brush falloff
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float BrushFalloff = 0.5f;
// Brush material
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush")
TObjectPtr<UMaterialInterface> BrushMaterial = nullptr;
// Brush mesh
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush")
TObjectPtr<UStaticMesh> BrushMesh = nullptr;
// Enable physics
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush")
bool bEnablePhysics = true;
// Physics density
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (EditCondition = "bEnablePhysics"))
float PhysicsDensity = 1.0f;
// Physics elasticity
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (EditCondition = "bEnablePhysics"))
float PhysicsElasticity = 0.5f;
// Fracture threshold
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH|Brush", meta = (EditCondition = "bEnablePhysics"))
float FractureThreshold = 100.0f;
};
/**
* Anatomical structure brush tool class
* Used for creating and editing anatomical structures in the editor
*/
UCLASS(BlueprintType)
class FLESHEDITOR_API UAnatomicalStructureBrush : public UObject
{
GENERATED_BODY()
public:
// Constructor
UAnatomicalStructureBrush();
/**
* Initialize the brush
* @param InSettings - Brush settings
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
void Initialize(const FAnatomicalBrushSettings& InSettings);
/**
* Apply brush to skeletal mesh
* @param TargetMesh - Target skeletal mesh
* @param Location - Application location
* @param Direction - Application direction
* @param BoneName - Bone name, if specified, only apply to this bone
* @return Whether the application was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
bool ApplyToSkeletalMesh(USkeletalMesh* TargetMesh, const FVector& Location, const FVector& Direction, FName BoneName = NAME_None);
/**
* Apply brush to static mesh
* @param TargetMesh - Target static mesh
* @param Location - Application location
* @param Direction - Application direction
* @return Whether the application was successful
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
bool ApplyToStaticMesh(UStaticMesh* TargetMesh, const FVector& Location, const FVector& Direction);
/**
* Set brush type
* @param BrushType - Brush type
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
void SetBrushType(EAnatomicalBrushType BrushType);
/**
* Set brush size
* @param Size - Brush size
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
void SetBrushSize(float Size);
/**
* Set brush strength
* @param Strength - Brush strength
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
void SetBrushStrength(float Strength);
/**
* Get brush settings
* @return Current brush settings
*/
UFUNCTION(BlueprintCallable, Category = "FLESH|Brush")
FAnatomicalBrushSettings GetBrushSettings() const;
private:
// Brush settings
UPROPERTY(EditAnywhere, Category = "FLESH|Brush")
FAnatomicalBrushSettings BrushSettings;
// Internal function for creating anatomical structure
UStaticMesh* CreateAnatomicalStructure(const FVector& Location, const FVector& Direction, float Size);
// Internal function for applying physics properties
void ApplyPhysicsProperties(UStaticMesh* Mesh);
};

View File

@@ -0,0 +1,211 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DismembermentCompiler.generated.h"
class UDismembermentGraphNode;
class UDismembermentGraph;
/**
* Compiled node data structure
*/
USTRUCT()
struct FCompiledNodeData
{
GENERATED_BODY()
// Node reference
UPROPERTY()
TObjectPtr<UDismembermentGraphNode> Node;
// Input nodes
UPROPERTY()
TArray<int32> InputNodeIndices;
// Output nodes
UPROPERTY()
TArray<int32> OutputNodeIndices;
// Execution order index
int32 ExecutionOrder;
// Constructor
FCompiledNodeData()
: Node(nullptr)
, ExecutionOrder(-1)
{
}
};
/**
* Dismemberment compiler class
* Compiles a dismemberment graph into executable logic
*/
UCLASS()
class FLESHEDITOR_API UDismembermentCompiler : public UObject
{
GENERATED_BODY()
public:
// Constructor
UDismembermentCompiler();
/**
* Compile a dismemberment graph
* @param InGraph - The graph to compile
* @return True if compilation was successful
*/
bool CompileGraph(UDismembermentGraph* InGraph);
/**
* Get the compiled node data
* @return Array of compiled node data
*/
const TArray<FCompiledNodeData>& GetCompiledNodeData() const { return CompiledNodeData; }
/**
* Get the execution order
* @return Array of node indices in execution order
*/
const TArray<int32>& GetExecutionOrder() const { return ExecutionOrder; }
/**
* Add a bone selection
* @param BoneName - Name of the bone to select
*/
void AddBoneSelection(const FName& BoneName);
/**
* Add a cut operation
* @param Location - Location of the cut
* @param Direction - Direction of the cut
* @param Width - Width of the cut
* @param Depth - Depth of the cut
* @param Material - Material to use for the cut surface
*/
void AddCutOperation(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material);
/**
* Add a blood effect
* @param Location - Location of the blood effect
* @param BloodEffect - Niagara system for the blood effect
* @param BloodAmount - Amount of blood
* @param BloodPressure - Blood pressure
* @param CreateBloodPool - Whether to create a blood pool
* @param BloodPoolSize - Size of the blood pool
* @param BloodPoolMaterial - Material for the blood pool
*/
void AddBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure, bool CreateBloodPool, float BloodPoolSize, UMaterialInterface* BloodPoolMaterial);
/**
* Add a physics simulation
* @param Mass - Mass of the object
* @param LinearDamping - Linear damping
* @param AngularDamping - Angular damping
* @param EnableGravity - Whether to enable gravity
* @param SimulatePhysics - Whether to simulate physics
* @param GenerateOverlapEvents - Whether to generate overlap events
* @param PhysicalMaterial - Physical material to use
* @param ImpulseForce - Force of the impulse
* @param ImpulseRadius - Radius of the impulse
*/
void AddPhysicsSimulation(float Mass, float LinearDamping, float AngularDamping, bool EnableGravity, bool SimulatePhysics, bool GenerateOverlapEvents, UPhysicalMaterial* PhysicalMaterial, float ImpulseForce, float ImpulseRadius);
/**
* Add an organ
* @param OrganMesh - Mesh for the organ
* @param OrganMaterial - Material for the organ
* @param AttachBoneName - Name of the bone to attach to
* @param RelativeLocation - Relative location
* @param RelativeRotation - Relative rotation
* @param RelativeScale - Relative scale
* @param SimulatePhysics - Whether to simulate physics
* @param DamageMultiplier - Damage multiplier
* @param IsCriticalOrgan - Whether this is a critical organ
* @param BloodAmount - Amount of blood
*/
void AddOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale, bool SimulatePhysics, float DamageMultiplier, bool IsCriticalOrgan, float BloodAmount);
/**
* Add a wound effect
* @param WoundSize - Size of the wound
* @param WoundDepth - Depth of the wound
* @param WoundMaterial - Material for the wound
* @param WoundEffect - Effect for the wound
* @param CreateDecal - Whether to create a decal
* @param DecalMaterial - Material for the decal
* @param DecalSize - Size of the decal
* @param DecalLifetime - Lifetime of the decal
* @param AffectBoneHealth - Whether to affect bone health
* @param BoneDamage - Amount of bone damage
*/
void AddWoundEffect(float WoundSize, float WoundDepth, UMaterialInterface* WoundMaterial, UNiagaraSystem* WoundEffect, bool CreateDecal, UMaterialInterface* DecalMaterial, float DecalSize, float DecalLifetime, bool AffectBoneHealth, float BoneDamage);
private:
// The graph being compiled
UPROPERTY()
TObjectPtr<UDismembermentGraph> Graph;
// Compiled node data
UPROPERTY()
TArray<FCompiledNodeData> CompiledNodeData;
// Execution order
UPROPERTY()
TArray<int32> ExecutionOrder;
// Bone selections
UPROPERTY()
TArray<FName> BoneSelections;
// Cut operations
UPROPERTY()
TArray<FTransform> CutOperations;
// Cut materials
UPROPERTY()
TArray<TObjectPtr<UMaterialInterface>> CutMaterials;
// Cut widths
UPROPERTY()
TArray<float> CutWidths;
// Cut depths
UPROPERTY()
TArray<float> CutDepths;
// Blood effects
UPROPERTY()
TArray<FTransform> BloodEffectTransforms;
// Blood effect systems
UPROPERTY()
TArray<TObjectPtr<UNiagaraSystem>> BloodEffectSystems;
// Blood amounts
UPROPERTY()
TArray<float> BloodAmounts;
// Blood pressures
UPROPERTY()
TArray<float> BloodPressures;
// Create blood pools
UPROPERTY()
TArray<bool> CreateBloodPools;
// Blood pool sizes
UPROPERTY()
TArray<float> BloodPoolSizes;
// Blood pool materials
UPROPERTY()
TArray<TObjectPtr<UMaterialInterface>> BloodPoolMaterials;
// Topological sort the nodes
bool TopologicalSort();
// Visit node for topological sort
void VisitNode(int32 NodeIndex, TArray<bool>& Visited, TArray<bool>& TempMark, TArray<int32>& SortedNodes, bool& bHasCycle);
};

View File

@@ -0,0 +1,155 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DismembermentCompiler.h"
#include "DismembermentExecutor.generated.h"
class AActor;
class USkeletalMeshComponent;
class UDismembermentCompiler;
/**
* Dismemberment executor class
* Executes a compiled dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentExecutor : public UObject
{
GENERATED_BODY()
public:
// Constructor
UDismembermentExecutor();
/**
* Initialize the executor with a compiler
* @param InCompiler - The compiler containing the compiled graph
*/
void Initialize(UDismembermentCompiler* InCompiler);
/**
* Execute the compiled graph on a target actor
* @param TargetActor - The actor to apply the dismemberment effects to
* @return True if execution was successful
*/
bool Execute(AActor* TargetActor);
/**
* Get the target actor
* @return The target actor
*/
AActor* GetTargetActor() const { return TargetActor; }
/**
* Get the target skeletal mesh component
* @return The target skeletal mesh component
*/
USkeletalMeshComponent* GetTargetSkeletalMesh() const { return TargetSkeletalMesh; }
/**
* Get the selected bones
* @return Array of selected bone names
*/
const TArray<FName>& GetSelectedBones() const { return SelectedBones; }
/**
* Add a bone to the selection
* @param BoneName - Name of the bone to add
*/
void AddSelectedBone(const FName& BoneName);
/**
* Apply a cut to the target
* @param Location - Location of the cut
* @param Direction - Direction of the cut
* @param Width - Width of the cut
* @param Depth - Depth of the cut
* @param Material - Material to use for the cut surface
* @return True if the cut was successful
*/
bool ApplyCut(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material);
/**
* Spawn a blood effect
* @param Location - Location of the blood effect
* @param BloodEffect - Niagara system for the blood effect
* @param BloodAmount - Amount of blood
* @param BloodPressure - Blood pressure
* @param CreateBloodPool - Whether to create a blood pool
* @param BloodPoolSize - Size of the blood pool
* @param BloodPoolMaterial - Material for the blood pool
* @return True if the blood effect was created successfully
*/
bool SpawnBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure, bool CreateBloodPool, float BloodPoolSize, UMaterialInterface* BloodPoolMaterial);
/**
* Apply physics simulation
* @param Mass - Mass of the object
* @param LinearDamping - Linear damping
* @param AngularDamping - Angular damping
* @param EnableGravity - Whether to enable gravity
* @param SimulatePhysics - Whether to simulate physics
* @param GenerateOverlapEvents - Whether to generate overlap events
* @param PhysicalMaterial - Physical material to use
* @param ImpulseForce - Force of the impulse
* @param ImpulseRadius - Radius of the impulse
* @return True if the physics simulation was applied successfully
*/
bool ApplyPhysics(float Mass, float LinearDamping, float AngularDamping, bool EnableGravity, bool SimulatePhysics, bool GenerateOverlapEvents, UPhysicalMaterial* PhysicalMaterial, float ImpulseForce, float ImpulseRadius);
/**
* Spawn an organ
* @param OrganMesh - Mesh for the organ
* @param OrganMaterial - Material for the organ
* @param AttachBoneName - Name of the bone to attach to
* @param RelativeLocation - Relative location
* @param RelativeRotation - Relative rotation
* @param RelativeScale - Relative scale
* @param SimulatePhysics - Whether to simulate physics
* @param DamageMultiplier - Damage multiplier
* @param IsCriticalOrgan - Whether this is a critical organ
* @param BloodAmount - Amount of blood
* @return True if the organ was spawned successfully
*/
bool SpawnOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale, bool SimulatePhysics, float DamageMultiplier, bool IsCriticalOrgan, float BloodAmount);
/**
* Apply a wound effect
* @param WoundSize - Size of the wound
* @param WoundDepth - Depth of the wound
* @param WoundMaterial - Material for the wound
* @param WoundEffect - Effect for the wound
* @param CreateDecal - Whether to create a decal
* @param DecalMaterial - Material for the decal
* @param DecalSize - Size of the decal
* @param DecalLifetime - Lifetime of the decal
* @param AffectBoneHealth - Whether to affect bone health
* @param BoneDamage - Amount of bone damage
* @return True if the wound effect was applied successfully
*/
bool ApplyWoundEffect(float WoundSize, float WoundDepth, UMaterialInterface* WoundMaterial, UNiagaraSystem* WoundEffect, bool CreateDecal, UMaterialInterface* DecalMaterial, float DecalSize, float DecalLifetime, bool AffectBoneHealth, float BoneDamage);
private:
// The compiler containing the compiled graph
UPROPERTY()
TObjectPtr<UDismembermentCompiler> Compiler;
// The target actor
UPROPERTY()
TObjectPtr<AActor> TargetActor;
// The target skeletal mesh component
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> TargetSkeletalMesh;
// Selected bones
UPROPERTY()
TArray<FName> SelectedBones;
// Find the target skeletal mesh component
bool FindTargetSkeletalMesh();
// Execute a node
bool ExecuteNode(int32 NodeIndex);
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "DismembermentGraph.generated.h"
/**
* Dismemberment graph for visual logic design
* Allows for node-based editing of dismemberment system logic
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraph : public UEdGraph
{
GENERATED_BODY()
public:
UDismembermentGraph();
// The asset that owns this graph
UPROPERTY()
class UDismembermentGraphAsset* OwningAsset;
};

View File

@@ -0,0 +1,80 @@
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "GraphEditor.h"
class UDismembermentGraphAsset;
class UDismembermentGraph;
class SDockTab;
/**
* Dismemberment graph editor
* Provides a Mutable-like node editor for dismemberment system logic
*/
class FLESHEDITOR_API FDismembermentGraphEditor : public FAssetEditorToolkit
{
public:
FDismembermentGraphEditor();
virtual ~FDismembermentGraphEditor();
// Initialize the editor
void InitDismembermentGraphEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UDismembermentGraphAsset* InAsset);
// FAssetEditorToolkit interface
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
// End of FAssetEditorToolkit interface
// Get the edited asset
UDismembermentGraphAsset* GetEditedAsset() const { return EditedAsset; }
// Get the graph editor widget
TSharedRef<SGraphEditor> GetGraphEditor() const { return GraphEditorWidget.ToSharedRef(); }
private:
// The asset being edited
UDismembermentGraphAsset* EditedAsset;
// The graph editor widget
TSharedPtr<SGraphEditor> GraphEditorWidget;
// Tab spawners
TSharedRef<SDockTab> SpawnTab_GraphCanvas(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Properties(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Palette(const FSpawnTabArgs& Args);
// Create graph editor widget
TSharedRef<SGraphEditor> CreateGraphEditorWidget();
// Graph editor commands
void CreateCommandList();
TSharedPtr<FUICommandList> GraphEditorCommands;
// Command handlers
void SelectAllNodes();
void DeleteSelectedNodes();
void CutSelectedNodes();
void CopySelectedNodes();
void PasteNodes();
void DuplicateSelectedNodes();
// Graph changed handler
void OnGraphChanged(const FEdGraphEditAction& Action);
// Node selection changed handler
void OnSelectedNodesChanged(const TSet<UObject*>& NewSelection);
// Compile the graph
void CompileGraph();
// Properties panel
TSharedPtr<class IDetailsView> PropertiesWidget;
// Node palette
TSharedPtr<class SDismembermentGraphPalette> PaletteWidget;
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "DismembermentGraphEditorFactory.generated.h"
/**
* Factory for creating dismemberment graph assets
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphEditorFactory : public UFactory
{
GENERATED_BODY()
public:
UDismembermentGraphEditorFactory();
// UFactory interface
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
virtual bool ShouldShowInNewMenu() const override;
// End of UFactory interface
};

View File

@@ -0,0 +1,43 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "DismembermentGraphNode.generated.h"
/**
* Base class for all dismemberment graph nodes
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNode : public UEdGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNode();
// Node title color
UPROPERTY(EditAnywhere, Category = "Appearance")
FLinearColor NodeTitleColor;
// Node category
UPROPERTY(EditAnywhere, Category = "Category")
FText NodeCategory;
// Node description
UPROPERTY(EditAnywhere, Category = "Description")
FText NodeDescription;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual FLinearColor GetNodeTitleColor() const override;
virtual FText GetTooltipText() const override;
virtual FText GetMenuCategory() const override;
// End of UEdGraphNode interface
// Compile this node into executable logic
virtual void CompileNode(class FDismembermentCompiler* Compiler);
// Execute this node
virtual void ExecuteNode(class FDismembermentExecutor* Executor);
};

View File

@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodeBloodEffect.generated.h"
/**
* Node for creating blood effects in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodeBloodEffect : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodeBloodEffect();
// Blood effect parameters
UPROPERTY(EditAnywhere, Category = "Blood Effect")
TObjectPtr<UNiagaraSystem> BloodEffect;
UPROPERTY(EditAnywhere, Category = "Blood Effect")
float BloodAmount;
UPROPERTY(EditAnywhere, Category = "Blood Effect")
float BloodPressure;
UPROPERTY(EditAnywhere, Category = "Blood Effect")
bool bCreateBloodPool;
UPROPERTY(EditAnywhere, Category = "Blood Effect", meta = (EditCondition = "bCreateBloodPool"))
float BloodPoolSize;
UPROPERTY(EditAnywhere, Category = "Blood Effect", meta = (EditCondition = "bCreateBloodPool"))
TObjectPtr<UMaterialInterface> BloodPoolMaterial;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodeBoneSelect.generated.h"
/**
* Node for selecting bones in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodeBoneSelect : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodeBoneSelect();
// Bone selection parameters
UPROPERTY(EditAnywhere, Category = "Bone Selection")
TArray<FName> BoneNames;
UPROPERTY(EditAnywhere, Category = "Bone Selection")
bool bUseRegex;
UPROPERTY(EditAnywhere, Category = "Bone Selection", meta = (EditCondition = "bUseRegex"))
FString BoneNamePattern;
UPROPERTY(EditAnywhere, Category = "Bone Selection")
bool bIncludeChildren;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodeCut.generated.h"
/**
* Node for performing a cut operation in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodeCut : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodeCut();
// Cut parameters
UPROPERTY(EditAnywhere, Category = "Cut Parameters")
float CutWidth;
UPROPERTY(EditAnywhere, Category = "Cut Parameters")
float CutDepth;
UPROPERTY(EditAnywhere, Category = "Cut Parameters")
bool bUseCustomMaterial;
UPROPERTY(EditAnywhere, Category = "Cut Parameters", meta = (EditCondition = "bUseCustomMaterial"))
TObjectPtr<UMaterialInterface> CustomCutMaterial;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodeOrgan.generated.h"
/**
* Node for organ simulation in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodeOrgan : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodeOrgan();
// Organ parameters
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
TObjectPtr<UStaticMesh> OrganMesh;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
TObjectPtr<UMaterialInterface> OrganMaterial;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
FName AttachBoneName;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
FVector RelativeLocation;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
FRotator RelativeRotation;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
FVector RelativeScale;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
bool bSimulatePhysics;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
float DamageMultiplier;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
bool bIsCriticalOrgan;
UPROPERTY(EditAnywhere, Category = "Organ Parameters")
float BloodAmount;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodePhysics.generated.h"
/**
* Node for physics simulation in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodePhysics : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodePhysics();
// Physics parameters
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
float Mass;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
float LinearDamping;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
float AngularDamping;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
bool bEnableGravity;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
bool bSimulatePhysics;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
bool bGenerateOverlapEvents;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
TObjectPtr<UPhysicalMaterial> PhysicalMaterial;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
float ImpulseForce;
UPROPERTY(EditAnywhere, Category = "Physics Parameters")
float ImpulseRadius;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include "CoreMinimal.h"
#include "DismembermentGraphNode.h"
#include "DismembermentGraphNodeWound.generated.h"
/**
* Node for wound effects in the dismemberment graph
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphNodeWound : public UDismembermentGraphNode
{
GENERATED_BODY()
public:
UDismembermentGraphNodeWound();
// Wound parameters
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
float WoundSize;
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
float WoundDepth;
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
TObjectPtr<UMaterialInterface> WoundMaterial;
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
TObjectPtr<UNiagaraSystem> WoundEffect;
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
bool bCreateDecal;
UPROPERTY(EditAnywhere, Category = "Wound Parameters", meta = (EditCondition = "bCreateDecal"))
TObjectPtr<UMaterialInterface> DecalMaterial;
UPROPERTY(EditAnywhere, Category = "Wound Parameters", meta = (EditCondition = "bCreateDecal"))
float DecalSize;
UPROPERTY(EditAnywhere, Category = "Wound Parameters", meta = (EditCondition = "bCreateDecal"))
float DecalLifetime;
UPROPERTY(EditAnywhere, Category = "Wound Parameters")
bool bAffectBoneHealth;
UPROPERTY(EditAnywhere, Category = "Wound Parameters", meta = (EditCondition = "bAffectBoneHealth"))
float BoneDamage;
// UEdGraphNode interface
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
// End of UEdGraphNode interface
// UDismembermentGraphNode interface
virtual void CompileNode(class FDismembermentCompiler* Compiler) override;
virtual void ExecuteNode(class FDismembermentExecutor* Executor) override;
// End of UDismembermentGraphNode interface
};

View File

@@ -0,0 +1,84 @@
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/STreeView.h"
class FDismembermentGraphEditor;
/**
* Node category structure for the palette
*/
struct FDismembermentGraphNodeCategory
{
// Category name
FText CategoryName;
// Child nodes
TArray<TSharedPtr<FDismembermentGraphNodeCategory>> Children;
// Node classes in this category
TArray<UClass*> NodeClasses;
// Constructor
FDismembermentGraphNodeCategory(const FText& InCategoryName)
: CategoryName(InCategoryName)
{
}
};
/**
* Node palette widget for the dismemberment graph editor
*/
class FLESHEDITOR_API SDismembermentGraphPalette : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SDismembermentGraphPalette) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, TSharedPtr<FDismembermentGraphEditor> InGraphEditor);
private:
// The graph editor that owns this palette
TWeakPtr<FDismembermentGraphEditor> GraphEditor;
// Root categories
TArray<TSharedPtr<FDismembermentGraphNodeCategory>> RootCategories;
// Tree view widget
TSharedPtr<STreeView<TSharedPtr<FDismembermentGraphNodeCategory>>> CategoriesTreeView;
// Search box widget
TSharedPtr<class SSearchBox> SearchBox;
// Search filter
TSharedPtr<class FDismembermentGraphNodeSearchFilter> SearchFilter;
// Initialize the palette
void InitializePalette();
// Create a category tree item
TSharedRef<ITableRow> OnGenerateCategoryRow(TSharedPtr<FDismembermentGraphNodeCategory> Category, const TSharedRef<STableViewBase>& OwnerTable);
// Get child categories
void OnGetCategoryChildren(TSharedPtr<FDismembermentGraphNodeCategory> Category, TArray<TSharedPtr<FDismembermentGraphNodeCategory>>& OutChildren);
// Category is expanded
void OnCategoryExpansionChanged(TSharedPtr<FDismembermentGraphNodeCategory> Category, bool bExpanded);
// Search text changed
void OnSearchTextChanged(const FText& InFilterText);
// Create a node from the palette
FReply OnCreateNode(UClass* NodeClass, const FVector2D& ScreenPosition, const FVector2D& GraphPosition);
// Handle drag detected
FReply OnDragDetected(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, UClass* NodeClass);
// Create default categories
void CreateDefaultCategories();
// Add a node class to the palette
void AddNodeClassToCategory(UClass* NodeClass, const FText& CategoryName);
};

View File

@@ -0,0 +1,168 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphSchema.h"
#include "DismembermentGraphSchema.generated.h"
class UDismembermentGraphNode;
/**
* Connection response
*/
USTRUCT()
struct FDismembermentGraphConnectionResponse
{
GENERATED_BODY()
// Response type
UPROPERTY()
int32 Response;
// Response text
UPROPERTY()
FText Message;
// Pin names that would be broken
UPROPERTY()
TArray<FText> BreakingPins;
// Constructor
FDismembermentGraphConnectionResponse()
: Response(0)
{
}
};
/**
* Connection response types
*/
struct FDismembermentGraphConnectionResponse_K2
{
enum Type
{
// No error
OK = 0,
// Generic error
ERROR_INCOMPATIBLE = 1,
// Disallowed pin connection
ERROR_DISALLOWED = 2,
// Self-connection not allowed
ERROR_SELF_CONNECTION = 3,
// Cycle not allowed
ERROR_CYCLE = 4
};
};
/**
* Pin type
*/
USTRUCT()
struct FDismembermentGraphPinType
{
GENERATED_BODY()
// Pin category
UPROPERTY()
FName PinCategory;
// Constructor
FDismembermentGraphPinType()
{
}
// Constructor with category
FDismembermentGraphPinType(const FName& InPinCategory)
: PinCategory(InPinCategory)
{
}
// Equality operator
bool operator==(const FDismembermentGraphPinType& Other) const
{
return PinCategory == Other.PinCategory;
}
// Inequality operator
bool operator!=(const FDismembermentGraphPinType& Other) const
{
return !(*this == Other);
}
};
/**
* Dismemberment graph schema
*/
UCLASS()
class FLESHEDITOR_API UDismembermentGraphSchema : public UEdGraphSchema
{
GENERATED_BODY()
public:
// Pin categories
static const FName PC_Exec;
static const FName PC_Bone;
static const FName PC_Cut;
static const FName PC_Blood;
static const FName PC_Physics;
static const FName PC_Organ;
static const FName PC_Wound;
// UEdGraphSchema interface
virtual void GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const override;
virtual void GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const override;
virtual const FPinConnectionResponse CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const override;
virtual bool TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const override;
virtual bool ShouldHidePinDefaultValue(UEdGraphPin* Pin) const override;
virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override;
virtual void BreakNodeLinks(UEdGraphNode& TargetNode) const override;
virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotification) const override;
virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override;
virtual void DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const override;
virtual void DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const override;
virtual void DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const override;
virtual void GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const override;
virtual void GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const override;
// End of UEdGraphSchema interface
/**
* Check if two pins can be connected
* @param PinA - First pin
* @param PinB - Second pin
* @param OutResponse - Connection response
* @return True if the pins can be connected
*/
bool CanConnectPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, FDismembermentGraphConnectionResponse& OutResponse) const;
/**
* Check if connecting two pins would create a cycle
* @param PinA - First pin
* @param PinB - Second pin
* @return True if connecting the pins would create a cycle
*/
bool WouldCreateCycle(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const;
/**
* Get the pin type from a pin
* @param Pin - The pin to get the type from
* @return The pin type
*/
static FDismembermentGraphPinType GetPinType(const UEdGraphPin* Pin);
/**
* Get the pin type color
* @param PinType - The pin type
* @return The pin color
*/
static FLinearColor GetPinTypeColor(const FDismembermentGraphPinType& PinType);
/**
* Create a new node
* @param NodeClass - Class of the node to create
* @param ParentGraph - Graph to create the node in
* @param NodePosX - X position of the node
* @param NodePosY - Y position of the node
* @param bSelectNewNode - Whether to select the new node
* @return The created node
*/
static UDismembermentGraphNode* CreateNode(TSubclassOf<UDismembermentGraphNode> NodeClass, UEdGraph* ParentGraph, float NodePosX, float NodePosY, bool bSelectNewNode = true);
};

View File

@@ -0,0 +1,167 @@
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DismembermentPreviewManager.generated.h"
// Add a log category declaration
DECLARE_LOG_CATEGORY_EXTERN(LogFLESHPreview, Log, All);
class UDismembermentGraphNode;
class USkeletalMeshComponent;
class AActor;
class UWorld;
class UNiagaraComponent;
class UDecalComponent;
class UStaticMeshComponent;
/**
* Preview manager for dismemberment effects
* Handles real-time preview of dismemberment nodes
*/
UCLASS()
class FLESHEDITOR_API UDismembermentPreviewManager : public UObject
{
GENERATED_BODY()
public:
// Constructor
UDismembermentPreviewManager();
/**
* Initialize the preview manager
* @param InWorld - The world to create the preview in
*/
void Initialize(TObjectPtr<UWorld> InWorld);
/**
* Clean up the preview manager
*/
void Cleanup();
/**
* Set the target actor for preview
* @param InActor - The actor to preview on
*/
void SetTargetActor(TObjectPtr<AActor> InActor);
/**
* Preview a node
* @param Node - The node to preview
* @return True if the preview was successful
*/
bool PreviewNode(TObjectPtr<UDismembermentGraphNode> Node);
/**
* Clear the current preview
*/
void ClearPreview();
/**
* Update the preview
* @param DeltaTime - Time since last update
*/
void Tick(float DeltaTime);
/**
* Get the target actor
* @return The target actor
*/
TObjectPtr<AActor> GetTargetActor() const { return TargetActor; }
/**
* Get the target skeletal mesh component
* @return The target skeletal mesh component
*/
TObjectPtr<USkeletalMeshComponent> GetTargetSkeletalMesh() const { return TargetSkeletalMesh; }
private:
// The world to create the preview in
UPROPERTY()
TObjectPtr<UWorld> World;
// The target actor
UPROPERTY()
TObjectPtr<AActor> TargetActor;
// The target skeletal mesh component
UPROPERTY()
TObjectPtr<USkeletalMeshComponent> TargetSkeletalMesh;
// The currently previewed node
UPROPERTY()
TObjectPtr<UDismembermentGraphNode> PreviewedNode;
// Preview components
UPROPERTY()
TArray<TObjectPtr<UNiagaraComponent>> PreviewNiagaraComponents;
UPROPERTY()
TArray<TObjectPtr<UDecalComponent>> PreviewDecalComponents;
UPROPERTY()
TArray<TObjectPtr<UStaticMeshComponent>> PreviewStaticMeshComponents;
// Preview cut plane mesh
UPROPERTY()
TObjectPtr<UStaticMeshComponent> PreviewCutPlaneMesh;
// Preview bone selections
UPROPERTY()
TArray<FName> PreviewBoneSelections;
// Preview cut locations
UPROPERTY()
TArray<FTransform> PreviewCutTransforms;
// Preview blood effect locations
UPROPERTY()
TArray<FTransform> PreviewBloodEffectTransforms;
// Preview organ locations
UPROPERTY()
TArray<FTransform> PreviewOrganTransforms;
// Preview wound locations
UPROPERTY()
TArray<FTransform> PreviewWoundTransforms;
// Find the target skeletal mesh component
bool FindTargetSkeletalMesh();
// Preview a cut node
bool PreviewCutNode(TObjectPtr<class UDismembermentGraphNodeCut> CutNode);
// Preview a bone select node
bool PreviewBoneSelectNode(TObjectPtr<class UDismembermentGraphNodeBoneSelect> BoneSelectNode);
// Preview a blood effect node
bool PreviewBloodEffectNode(TObjectPtr<class UDismembermentGraphNodeBloodEffect> BloodEffectNode);
// Preview a physics node
bool PreviewPhysicsNode(TObjectPtr<class UDismembermentGraphNodePhysics> PhysicsNode);
// Preview an organ node
bool PreviewOrganNode(TObjectPtr<class UDismembermentGraphNodeOrgan> OrganNode);
// Preview a wound node
bool PreviewWoundNode(TObjectPtr<class UDismembermentGraphNodeWound> WoundNode);
// Create a preview cut plane mesh
TObjectPtr<UStaticMeshComponent> CreatePreviewCutPlaneMesh(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material);
// Create a preview blood effect
TObjectPtr<UNiagaraComponent> CreatePreviewBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure);
// Create a preview blood pool
TObjectPtr<UDecalComponent> CreatePreviewBloodPool(const FVector& Location, float Size, UMaterialInterface* Material);
// Create a preview organ
TObjectPtr<UStaticMeshComponent> CreatePreviewOrgan(UStaticMesh* OrganMesh, UMaterialInterface* OrganMaterial, const FName& AttachBoneName, const FVector& RelativeLocation, const FRotator& RelativeRotation, const FVector& RelativeScale);
// Create a preview wound
TObjectPtr<UDecalComponent> CreatePreviewWound(const FVector& Location, float Size, UMaterialInterface* Material);
// Clear all preview components
void ClearPreviewComponents();
};

View File

@@ -0,0 +1,68 @@
#pragma once
#include "CoreMinimal.h"
#include "SGraphNode.h"
class UDismembermentGraphNode;
/**
* Visual representation of a dismemberment graph node
*/
class FLESHEDITOR_API SDismembermentGraphNode : public SGraphNode
{
public:
SLATE_BEGIN_ARGS(SDismembermentGraphNode) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, UEdGraphNode* InNode);
// SGraphNode interface
virtual void UpdateGraphNode() override;
virtual void CreatePinWidgets() override;
virtual void AddPin(const TSharedRef<SGraphPin>& PinToAdd) override;
virtual TSharedPtr<SToolTip> GetComplexTooltip() override;
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
// End of SGraphNode interface
protected:
// Get the dismemberment graph node
UDismembermentGraphNode* GetDismembermentGraphNode() const;
// Get the node title widget
TSharedRef<SWidget> GetNodeTitleWidget();
// Get the node body widget
TSharedRef<SWidget> GetNodeBodyWidget();
// Get the node preview widget
TSharedRef<SWidget> GetNodePreviewWidget();
// Node color
FSlateColor GetNodeColor() const;
// Node title color
FSlateColor GetNodeTitleColor() const;
// Node title text
FText GetNodeTitle() const;
// Node category text
FText GetNodeCategory() const;
// Node description text
FText GetNodeDescription() const;
// Is the node selected
bool IsNodeSelected() const;
// Is the node hovered
bool IsNodeHovered() const;
private:
// Is the node hovered
bool bIsHovered;
};

View File

@@ -0,0 +1,120 @@
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "EditorViewportClient.h"
#include "SEditorViewport.h"
class UDismembermentPreviewManager;
class USkeletalMesh;
class AActor;
class FPreviewScene;
class SDismembermentPreviewViewportClient;
/**
* Viewport for previewing dismemberment effects
*/
class FLESHEDITOR_API SDismembermentPreviewViewport : public SEditorViewport
{
public:
SLATE_BEGIN_ARGS(SDismembermentPreviewViewport)
{}
SLATE_END_ARGS()
/**
* Constructs the viewport widget
*/
void Construct(const FArguments& InArgs);
/**
* Destructor
*/
virtual ~SDismembermentPreviewViewport();
/**
* Set the preview manager
* @param InPreviewManager - The preview manager to use
*/
void SetPreviewManager(UDismembermentPreviewManager* InPreviewManager);
/**
* Set the preview skeletal mesh
* @param InSkeletalMesh - The skeletal mesh to preview
*/
void SetPreviewSkeletalMesh(USkeletalMesh* InSkeletalMesh);
/**
* Get the preview actor
* @return The preview actor
*/
AActor* GetPreviewActor() const;
/**
* Refresh the viewport
*/
void RefreshViewport();
protected:
// SEditorViewport interface
virtual TSharedRef<FEditorViewportClient> MakeEditorViewportClient() override;
virtual void OnFocusViewportToSelection() override;
virtual bool IsVisible() const override;
// End of SEditorViewport interface
private:
// The preview scene
TSharedPtr<FPreviewScene> PreviewScene;
// The viewport client
TSharedPtr<SDismembermentPreviewViewportClient> ViewportClient;
// The preview manager
TObjectPtr<UDismembermentPreviewManager> PreviewManager;
// The preview actor
TObjectPtr<AActor> PreviewActor;
// Create the preview actor
void CreatePreviewActor();
// Update the preview actor
void UpdatePreviewActor();
};
/**
* Viewport client for previewing dismemberment effects
*/
class SDismembermentPreviewViewportClient : public FEditorViewportClient
{
public:
/**
* Constructor
* @param InPreviewScene - The preview scene
* @param InViewportWidget - The viewport widget
*/
SDismembermentPreviewViewportClient(FPreviewScene* InPreviewScene, const TWeakPtr<SDismembermentPreviewViewport>& InViewportWidget);
/**
* Destructor
*/
virtual ~SDismembermentPreviewViewportClient();
// FEditorViewportClient interface
virtual void Tick(float DeltaSeconds) override;
virtual void Draw(const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
virtual void DrawCanvas(FViewport& InViewport, FSceneView& View, FCanvas& Canvas) override;
// End of FEditorViewportClient interface
/**
* Set the preview manager
* @param InPreviewManager - The preview manager to use
*/
void SetPreviewManager(UDismembermentPreviewManager* InPreviewManager);
private:
// The viewport widget
TWeakPtr<SDismembermentPreviewViewport> ViewportWidget;
// The preview manager
TObjectPtr<UDismembermentPreviewManager> PreviewManager;
};

View File

@@ -0,0 +1,107 @@
#pragma once
#include "CoreMinimal.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "Widgets/Docking/SDockTab.h"
class SDockTab;
class SGraphEditor;
class SPropertyTreeView;
class SAssetBrowser;
class SMatrixInputWidget;
/**
* FLESH Main Editor
* Provides the main editing functionality for the dismemberment system
*/
class FLESHEDITOR_API FFLESHEditor : public FAssetEditorToolkit
{
public:
FFLESHEditor();
virtual ~FFLESHEditor();
// Initialize the editor
void InitFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost);
// FAssetEditorToolkit interface
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabManager) override;
virtual FName GetToolkitFName() const override;
virtual FText GetBaseToolkitName() const override;
virtual FString GetWorldCentricTabPrefix() const override;
virtual FLinearColor GetWorldCentricTabColorScale() const override;
// End of FAssetEditorToolkit interface
// Open the editor
static void OpenEditor();
private:
// Tab spawners
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_AssetBrowser(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_MatrixEditor(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_GraphEditor(const FSpawnTabArgs& Args);
TSharedRef<SDockTab> SpawnTab_Toolbar(const FSpawnTabArgs& Args);
// Create viewport widget
TSharedRef<SWidget> CreateViewportWidget();
// Create details panel
TSharedRef<SWidget> CreateDetailsWidget();
// Create asset browser
TSharedRef<SWidget> CreateAssetBrowserWidget();
// Create matrix editor
TSharedRef<SWidget> CreateMatrixEditorWidget();
// Create graph editor
TSharedRef<SWidget> CreateGraphEditorWidget();
// Create toolbar
TSharedRef<SWidget> CreateToolbarWidget();
// Create command list
void CreateCommandList();
// Command handlers
void OnOpenDismembermentGraphEditor();
void OnOpenAnatomicalLayerEditor();
void OnOpenBooleanCutTool();
void OnOpenBloodSystemEditor();
void OnImportCharacterModel();
void OnImportOrganModel();
void OnImportSkeletonModel();
void OnImportPhysicsAsset();
void OnTestMatrix();
// Viewport widget
TSharedPtr<class SViewport> ViewportWidget;
// Details panel
TSharedPtr<class IDetailsView> DetailsWidget;
// Asset browser
TSharedPtr<SAssetBrowser> AssetBrowserWidget;
// Matrix editor
TSharedPtr<SMatrixInputWidget> MatrixEditorWidget;
// Graph editor
TSharedPtr<SGraphEditor> GraphEditorWidget;
// Toolbar
TSharedPtr<class SBorder> ToolbarWidget;
// Command list
TSharedPtr<FUICommandList> CommandList;
// Tab IDs
static const FName ViewportTabId;
static const FName DetailsTabId;
static const FName AssetBrowserTabId;
static const FName MatrixEditorTabId;
static const FName GraphEditorTabId;
static const FName ToolbarTabId;
};

View File

@@ -0,0 +1,41 @@
#pragma once
#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"
#include "FLESHEditorStyle.h"
/**
* FLESH Editor Commands
* Defines all commands for the FLESH editor
*/
class FFLESHEditorCommands : public TCommands<FFLESHEditorCommands>
{
public:
FFLESHEditorCommands()
: TCommands<FFLESHEditorCommands>(
TEXT("FLESHEditor"),
NSLOCTEXT("Contexts", "FLESHEditor", "FLESH Editor"),
NAME_None,
FFLESHEditorStyle::GetStyleSetName())
{
}
// TCommands interface
virtual void RegisterCommands() override;
// End of TCommands interface
// Open FLESH Editor command
TSharedPtr<FUICommandInfo> OpenFLESHEditor;
// Open Dismemberment Graph Editor command
TSharedPtr<FUICommandInfo> OpenDismembermentGraphEditor;
// Open Anatomical Layer Editor command
TSharedPtr<FUICommandInfo> OpenAnatomicalLayerEditor;
// Open Boolean Cut Tool command
TSharedPtr<FUICommandInfo> OpenBooleanCutTool;
// Open Blood System Editor command
TSharedPtr<FUICommandInfo> OpenBloodSystemEditor;
};

View File

@@ -0,0 +1,28 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
/**
* FLESH Editor Module - Editor tools for the Fully Locational Evisceration System for Humanoids
*/
class FFLESHEditorModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
/** Singleton getter */
static FFLESHEditorModule& Get()
{
return FModuleManager::LoadModuleChecked<FFLESHEditorModule>("FLESHEditor");
}
/** Open FLESH Editor */
void OpenFLESHEditor();
private:
/** Plugin command list */
TSharedPtr<class FUICommandList> PluginCommands;
};

View File

@@ -0,0 +1,34 @@
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
/**
* FLESH Editor Style
* Defines the visual style for the FLESH editor
*/
class FFLESHEditorStyle
{
public:
// Initialize the style
static void Initialize();
// Shutdown the style
static void Shutdown();
// Reload textures
static void ReloadTextures();
// Get the style set name
static FName GetStyleSetName();
// Get the instance
static const ISlateStyle& Get();
private:
// Create the style
static TSharedRef<FSlateStyleSet> Create();
// The instance
static TSharedPtr<FSlateStyleSet> StyleInstance;
};

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SGridPanel.h"
/**
* Matrix input widget for the FLESH editor
* Allows inputting transformation matrices for precise cutting operations
*/
class FLESHEDITOR_API SMatrixInputWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SMatrixInputWidget)
: _Matrix(FMatrix::Identity)
, _OnMatrixChanged()
{}
/** The initial matrix value */
SLATE_ARGUMENT(FMatrix, Matrix)
/** Called when the matrix value changes */
SLATE_EVENT(FOnMatrixChanged, OnMatrixChanged)
SLATE_END_ARGS()
/** Constructs this widget */
void Construct(const FArguments& InArgs);
/** Sets the matrix value */
void SetMatrix(const FMatrix& InMatrix);
/** Gets the current matrix value */
FMatrix GetMatrix() const;
/** Resets the matrix to identity */
void ResetToIdentity();
/** Creates a rotation matrix from Euler angles */
void SetFromEulerAngles(float Roll, float Pitch, float Yaw);
/** Creates a translation matrix */
void SetFromTranslation(const FVector& Translation);
/** Creates a combined transformation matrix */
void SetFromTransform(const FTransform& Transform);
private:
/** The current matrix value */
FMatrix Matrix;
/** Called when the matrix value changes */
FOnMatrixChanged OnMatrixChanged;
/** The grid panel containing the matrix elements */
TSharedPtr<SGridPanel> GridPanel;
/** Array of numeric entry boxes for matrix elements */
TArray<TSharedPtr<SNumericEntryBox<float>>> NumericEntryBoxes;
/** Creates a numeric entry box for a matrix element */
TSharedPtr<SNumericEntryBox<float>> CreateMatrixElementWidget(int32 Row, int32 Col);
/** Called when a matrix element changes */
void OnMatrixElementChanged(float NewValue, int32 Row, int32 Col);
/** Updates the UI from the matrix value */
void UpdateUI();
};
/** Delegate for matrix value changes */
DECLARE_DELEGATE_OneParam(FOnMatrixChanged, const FMatrix&);