Update
This commit is contained in:
59
FLESH.uplugin
Normal file
59
FLESH.uplugin
Normal 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
98
Gaol.md
Normal 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
|
||||
Docs:https://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
186
Readme.md
Normal 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 Script:https://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
|
||||
Docs:https://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
BIN
Resources/Icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
61
Source/FLESH/FLESH.Build.cs
Normal file
61
Source/FLESH/FLESH.Build.cs
Normal 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 ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
136
Source/FLESH/Private/AnatomicalLayerSystem.cpp
Normal file
136
Source/FLESH/Private/AnatomicalLayerSystem.cpp
Normal 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;
|
||||
}
|
||||
}
|
114
Source/FLESH/Private/BloodPool.cpp
Normal file
114
Source/FLESH/Private/BloodPool.cpp
Normal 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();
|
||||
}
|
166
Source/FLESH/Private/BloodSystem.cpp
Normal file
166
Source/FLESH/Private/BloodSystem.cpp
Normal 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;
|
||||
}
|
283
Source/FLESH/Private/BooleanCutTool.cpp
Normal file
283
Source/FLESH/Private/BooleanCutTool.cpp
Normal 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
|
||||
}
|
276
Source/FLESH/Private/DismembermentSystem.cpp
Normal file
276
Source/FLESH/Private/DismembermentSystem.cpp
Normal 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;
|
||||
}
|
20
Source/FLESH/Private/FLESHModule.cpp
Normal file
20
Source/FLESH/Private/FLESHModule.cpp
Normal 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)
|
159
Source/FLESH/Public/AnatomicalLayerSystem.h
Normal file
159
Source/FLESH/Public/AnatomicalLayerSystem.h
Normal 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;
|
||||
};
|
92
Source/FLESH/Public/BloodPool.h
Normal file
92
Source/FLESH/Public/BloodPool.h
Normal 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();
|
||||
};
|
98
Source/FLESH/Public/BloodSystem.h
Normal file
98
Source/FLESH/Public/BloodSystem.h
Normal 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;
|
||||
};
|
129
Source/FLESH/Public/BooleanCutTool.h
Normal file
129
Source/FLESH/Public/BooleanCutTool.h
Normal 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);
|
||||
};
|
87
Source/FLESH/Public/DismemberedAnimInstance.h
Normal file
87
Source/FLESH/Public/DismemberedAnimInstance.h
Normal 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;
|
||||
};
|
@@ -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
|
||||
};
|
204
Source/FLESH/Public/DismembermentSystem.h
Normal file
204
Source/FLESH/Public/DismembermentSystem.h
Normal 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;
|
||||
};
|
245
Source/FLESH/Public/FLESHDismembermentComponent.h
Normal file
245
Source/FLESH/Public/FLESHDismembermentComponent.h
Normal 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);
|
||||
};
|
21
Source/FLESH/Public/FLESHModule.h
Normal file
21
Source/FLESH/Public/FLESHModule.h
Normal 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");
|
||||
}
|
||||
};
|
67
Source/FLESHEditor/FLESHEditor.Build.cs
Normal file
67
Source/FLESHEditor/FLESHEditor.Build.cs
Normal 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 ...
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
367
Source/FLESHEditor/Private/DismembermentEditor.cpp
Normal file
367
Source/FLESHEditor/Private/DismembermentEditor.cpp
Normal 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
|
||||
}
|
61
Source/FLESHEditor/Private/DismembermentEditor.h
Normal file
61
Source/FLESHEditor/Private/DismembermentEditor.h
Normal 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;
|
||||
};
|
@@ -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;
|
||||
}
|
@@ -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
|
@@ -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
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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;
|
||||
}
|
546
Source/FLESHEditor/Private/FLESHEditor.cpp
Normal file
546
Source/FLESHEditor/Private/FLESHEditor.cpp
Normal 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
|
14
Source/FLESHEditor/Private/FLESHEditorCommands.cpp
Normal file
14
Source/FLESHEditor/Private/FLESHEditorCommands.cpp
Normal 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
|
74
Source/FLESHEditor/Private/FLESHEditorModule.cpp
Normal file
74
Source/FLESHEditor/Private/FLESHEditorModule.cpp
Normal 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)
|
92
Source/FLESHEditor/Private/FLESHEditorStyle.cpp
Normal file
92
Source/FLESHEditor/Private/FLESHEditorStyle.cpp
Normal 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
|
223
Source/FLESHEditor/Private/MatrixInputWidget.cpp
Normal file
223
Source/FLESHEditor/Private/MatrixInputWidget.cpp
Normal 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
|
153
Source/FLESHEditor/Public/AnatomicalStructureBrush.h
Normal file
153
Source/FLESHEditor/Public/AnatomicalStructureBrush.h
Normal 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);
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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;
|
||||
};
|
@@ -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;
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
};
|
@@ -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();
|
||||
};
|
@@ -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;
|
||||
};
|
@@ -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;
|
||||
};
|
107
Source/FLESHEditor/Public/FLESHEditor.h
Normal file
107
Source/FLESHEditor/Public/FLESHEditor.h
Normal 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;
|
||||
};
|
41
Source/FLESHEditor/Public/FLESHEditorCommands.h
Normal file
41
Source/FLESHEditor/Public/FLESHEditorCommands.h
Normal 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;
|
||||
};
|
28
Source/FLESHEditor/Public/FLESHEditorModule.h
Normal file
28
Source/FLESHEditor/Public/FLESHEditorModule.h
Normal 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;
|
||||
};
|
34
Source/FLESHEditor/Public/FLESHEditorStyle.h
Normal file
34
Source/FLESHEditor/Public/FLESHEditorStyle.h
Normal 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;
|
||||
};
|
72
Source/FLESHEditor/Public/MatrixInputWidget.h
Normal file
72
Source/FLESHEditor/Public/MatrixInputWidget.h
Normal 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&);
|
Reference in New Issue
Block a user