Updated
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
351
Gaol.md
351
Gaol.md
@@ -1,88 +1,259 @@
|
||||
#### FLESH, Fully Locational Evisceration System for Humanoids, 在UE5.5肢解系统插件
|
||||
# FLESH, Fully Locational Evisceration System for Humanoids
|
||||
|
||||
#### 目标功能:
|
||||
实时布尔切割引擎编辑器;
|
||||
多层实时布尔切割编辑器;
|
||||
多层系统: 骨骼,内脏;
|
||||
多层物理系统:内脏物理,骨骼物理;
|
||||
血液系统:Niagara,BloodPool, Decal;
|
||||
物理交互;
|
||||
以及肢解系统相关的其他交互;
|
||||
## 项目概述
|
||||
|
||||
#### 引擎版本:UE5.5
|
||||
**FLESH (Fully Locational Evisceration System for Humanoids)** 是一个为Unreal Engine 5.5.4开发的高级肢解系统插件,旨在提供真实的软体物理模拟、精确的布尔切割以及复杂的解剖结构模拟。
|
||||
|
||||
#### 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
|
||||
## 引擎版本要求
|
||||
- Unreal Engine 5.5.4或更高版本
|
||||
- 需要启用Niagara和GeometryScript插件
|
||||
|
||||
#### 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
|
||||
- **实时布尔切割系统**:GPU加速的实时网格切割,支持多种切割形状和参数
|
||||
- **多层解剖结构系统**:骨骼、肌肉、内脏、血管的分层模拟,不同层次的独立物理行为
|
||||
- **高级物理模拟**:软体物理系统用于内脏和肌肉模拟,骨骼物理系统用于断肢和骨骼碎片
|
||||
- **程序化伤口和血液效果**:动态伤口生成和渲染,基于Niagara的高级血液粒子系统
|
||||
- **编辑器工具**:交互式肢解设计工具,可视化参数调整界面,预设管理和导入/导出功能
|
||||
|
||||
### 参考资源
|
||||
###### 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
|
||||
- **多层类型定义**
|
||||
- 骨骼模型,
|
||||
- SoftBody(骨骼模型可转换成SoftBody,区别在于有单独定义的晃动气球物理 等等),
|
||||
- 修改器(Line, Plane, Matrix(4*3),AnchorsPhere 等等)
|
||||
|
||||
#### 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
|
||||
- **多层层次定义**
|
||||
- Skin 表层皮肤: SkeletonMesh(支持破环隐藏,表层塌陷 等等)
|
||||
- Fat 脂肪层: SkeletonMesh(支持破环隐藏,撕扯物理 等等)
|
||||
- Muscle 肌肉层: SkeletonMesh(支持破环隐藏,撕扯物理 等等)
|
||||
- Bone 骨架层: SkeletonMesh(简单的Rigidbody 等等)
|
||||
- Origan 内脏层: SkeletonMesh(支持晃动气球物理,拉扯物理 等等)
|
||||
|
||||
## 已完成工作
|
||||
|
||||
1. **基础架构搭建**
|
||||
- 创建插件基本结构和模块
|
||||
- 设置必要的依赖关系(GeometryScript、Niagara、Chaos物理系统)
|
||||
- 实现基本的编辑器集成
|
||||
- 解决UE5.5.4兼容性问题(TObjectPtr、enum class、Slate API)
|
||||
|
||||
2. **核心系统实现**
|
||||
- 布尔切割工具(BooleanCutTool):支持静态网格体、骨骼网格体和程序化网格体的切割
|
||||
- 肢解系统(DismembermentSystem):实现多层肢解和单层肢解
|
||||
- 飞溅贴图系统(SplatterMapSystem):支持多通道伤害纹理
|
||||
- 内部器官系统(InternalOrganSystem):管理不同类型的器官
|
||||
- 血液系统(BloodSystem):实现喷溅效果、血池创建和贴花功能
|
||||
- 软体物理工具(SoftBodyPhysicsTool):实现四种模拟方法和四种网格类型
|
||||
- 内脏节点系统(VisceraNodeSystem):管理内部器官结构
|
||||
|
||||
3. **编辑器功能**
|
||||
- 节点树视图:显示所有解剖结构节点的层级关系
|
||||
- 3D预览视口:用于实时预览模型和切割效果
|
||||
- 属性编辑面板:调整切割参数、物理响应和血液效果
|
||||
- 层编辑器:管理解剖层级结构
|
||||
- 物理设置面板:配置物理模拟参数
|
||||
- 节点图编辑器:用于可视化编程肢解效果
|
||||
|
||||
4. **性能优化**
|
||||
- 空间哈希网格:将碰撞检测复杂度从 O(n²) 降低到接近 O(n)
|
||||
- 自适应LOD系统:根据距离调整模拟精度,支持四个LOD级别
|
||||
- SIMD加速:使用向量指令优化数学计算
|
||||
- 多线程支持:实现并行粒子更新处理
|
||||
- 物理优化器:实现基于距离的物理模拟缩放
|
||||
- 渲染优化:使用较低分辨率的SplatterMap和简化材质
|
||||
|
||||
## 当前状态
|
||||
|
||||
1. **核心功能已完成**
|
||||
- **布尔切割工具(BooleanCutTool)**
|
||||
- 支持静态网格体、骨骼网格体和程序化网格体切割
|
||||
- 实现三种封盖网格生成方法(简单、三角扇形、镜嵌式)
|
||||
- 支持多层切割(Kinder Egg Man方法)
|
||||
- 实现基于骨骼的引导切割
|
||||
- 支持多种切割形状(平面、球体、圆柱体、自定义网格)
|
||||
- 提供不同精度级别的切割算法(低、中、高)
|
||||
- **软体物理系统(FLESHSoftBodySystem)**
|
||||
- 支持四种模拟方法:Verlet积分、位置基约束、弹簧质点系统和有限元方法
|
||||
- 支持四种网格类型:四面体、六面体、表面和链式
|
||||
- 实现全面的物理参数控制(重力、阻尼、弹性、体积保持等)
|
||||
- 支持自适应时间步长和连续碰撞检测
|
||||
- 实现软体与刚体的交互系统
|
||||
|
||||
- **物理优化器(FLESHPhysicsOptimizer)**
|
||||
- 支持五种优化级别(无、低、中、高、极端)
|
||||
- 实现四种空间分区方法(无、网格、八叉树、自适应)
|
||||
- 支持基于距离的模拟缩放
|
||||
- 提供多线程物理计算
|
||||
- 实现基于视距的LOD系统,包括四个精度级别
|
||||
|
||||
- **血液系统(BloodSystem)**
|
||||
- 实现基于Niagara的血液粒子系统
|
||||
- 支持动态血液贴花和血池生成
|
||||
- 提供血液流动和喷溅效果
|
||||
- 实现基于物理的血液交互
|
||||
|
||||
2. **编辑器功能现状**
|
||||
- 节点树视图已实现,支持多种节点类型(SoftBody、Anchor、LineChain、Tetra、Collision)
|
||||
- 3D预览视口支持模型查看、操作和实时预览
|
||||
- 属性编辑面板可以调整各类节点的特定参数
|
||||
- 层编辑器基础功能已实现,用于管理多层解剖结构
|
||||
- 物理设置面板基础功能已实现,支持物理参数调整
|
||||
- 节点图编辑器框架已实现,支持事件节点和处理节点连接
|
||||
|
||||
## 技术实现细节
|
||||
|
||||
### 解剖结构的层次化建模
|
||||
|
||||
- **多层材质技术**:
|
||||
- 使用4-6层材质模拟皮肤/肌肉/骨骼
|
||||
- 通过材质遮罩控制各层可见性
|
||||
- 支持动态伤口和切割面的材质混合
|
||||
|
||||
- **次级碰撞体**:
|
||||
- 为骨骼系统添加次级碰撞体(如肌肉膨胀体)
|
||||
- 实现受力变形和物理响应
|
||||
- 提供更真实的肌肉和软组织行为
|
||||
|
||||
- **物理约束系统**:
|
||||
- 为不同组织层次添加物理约束
|
||||
- 实现组织间的连接和交互
|
||||
- 支持动态断裂和断裂阈值设置
|
||||
|
||||
### 实时切割算法
|
||||
|
||||
- **平面切割**:
|
||||
- 使用UE5的APEX Destruction模块
|
||||
- 结合Signed Distance Field计算切割面
|
||||
- 支持任意角度和位置的切割操作
|
||||
|
||||
- **动态拓扑重建**:
|
||||
- 使用Procedural Mesh Component生成新网格
|
||||
- 顶点缓存重用(避免GC堵塞)
|
||||
- 动态计算法线平滑组(防止锈齿状切面)
|
||||
- 支持切割面的纹理映射和材质应用
|
||||
|
||||
- **性能优化技术**:
|
||||
- 简化网格复杂度算法
|
||||
- 异步切割计算
|
||||
- 基于视距的LOD系统
|
||||
|
||||
## 性能优化策略
|
||||
|
||||
### 推荐设置
|
||||
1. **LOD系统**:根据距离调整物理模拟精度
|
||||
- 近距离:完整物理模拟
|
||||
- 中距离:简化物理模拟
|
||||
- 远距离:禁用物理模拟
|
||||
|
||||
2. **物理优化**:
|
||||
- 调整迭代次数(建议:4-8次)
|
||||
- 启用空间哈希网格碰撞检测
|
||||
- 使用SIMD加速物理计算
|
||||
|
||||
3. **渲染优化**:
|
||||
- 使用较低分辨率的SplatterMap(128x128)
|
||||
- 限制同时显示的血液粒子数量
|
||||
- 对远距离对象使用简化材质
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### 编译错误
|
||||
**问题**:编译时出现"GeometryScript未找到"错误
|
||||
**解决方案**:确保在项目设置中启用了GeometryScript插件
|
||||
|
||||
**问题**:链接错误与FLESH模块相关
|
||||
**解决方案**:确保在项目的Build.cs文件中添加了FLESH模块依赖
|
||||
|
||||
### 运行时问题
|
||||
**问题**:切割效果不显示
|
||||
**解决方案**:检查切割平面位置和材质设置
|
||||
|
||||
**问题**:物理模拟不稳定
|
||||
**解决方案**:增加物理迭代次数或减小时间步长
|
||||
|
||||
**问题**:性能下降严重
|
||||
**解决方案**:启用LOD系统并优化物理设置
|
||||
|
||||
## 参考资源
|
||||
|
||||
### 游戏肢解系统参考
|
||||
- 《死亡岛2》的肢解系统:https://www.youtube.com/watch?v=d3VrPOm-KDE
|
||||
- Dead Island 2的FLESH系统:https://www.youtube.com/watch?v=GW7N83E1NqY
|
||||
- 肢解效果全面分析:https://www.youtube.com/watch?v=Y23zTuc2drk
|
||||
|
||||
### 技术参考资源
|
||||
- **GeometryScript**:用于实时几何体操作和布尔运算
|
||||
- **程序化网格组件**:用于动态生成和修改网格
|
||||
- **Gore and Dismemberment Plugin**:提供蓝图暴露的肢解功能
|
||||
- **Skinned Decal Component**:用于在骨骼网格上应用动态贴花
|
||||
- **EnGore Procedural Dismemberment**:提供程序化肢解功能
|
||||
|
||||
## 版本历史
|
||||
|
||||
### v1.2.0 (2025年4月)
|
||||
- 添加多线程物理模拟
|
||||
- 实现空间哈希碰撞检测
|
||||
- 添加自适应LOD系统
|
||||
- SIMD优化物理计算
|
||||
- 修复UE5.5.4兼容性问题
|
||||
|
||||
### v1.1.0 (2025年3月)
|
||||
- 添加更多器官类型
|
||||
- 改进血液效果质量
|
||||
- 增强材质系统
|
||||
- 添加预设系统
|
||||
- 修复各种bug和性能问题
|
||||
|
||||
### v1.0.0 (2025年2月)
|
||||
- 初始发布版本
|
||||
- 基础肢解功能
|
||||
- 布尔切割系统
|
||||
- 基本编辑器界面
|
||||
|
||||
## 联系与支持
|
||||
|
||||
- 官方文档:[链接待添加]
|
||||
- 问题报告:[链接待添加]
|
||||
- 社区论坛:[链接待添加]
|
||||
|
||||
## 许可证信息
|
||||
|
||||
FLESH插件采用专有许可证。详情请参阅LICENSE文件。
|
||||
|
||||
- **Skinned Decal Component**:
|
||||
- 源代码路径:Games\Lyra\Plugins\FLESH\Reference\SkinnedDecalComponent
|
||||
- 商城链接:https://www.fab.com/listings/7491af07-f541-493d-a78f-d7fa5d466a0d
|
||||
- 文档:https://ac.virtuosgames.com:8443/display/TSZO/Skinned+Decal+Component+Documentation
|
||||
- 用于在骨骼网格上应用动态贴花
|
||||
|
||||
- **EnGore Procedural Dismemberment**:
|
||||
- 源代码路径:Games\Lyra\Plugins\FLESH\Reference\EnGoreProceduralDismemberment
|
||||
- 商城链接:https://www.fab.com/listings/cb680946-6bde-445f-9ebe-3cecf4bec21e
|
||||
- 演示:https://www.youtube.com/watch?v=MN31jCDQ5zY
|
||||
- 文档:https://app.milanote.com/1MQHr51cI5RJ2H/dismemberment-system?p=XmVzKYwdfSs
|
||||
- 提供程序化肢解功能
|
||||
|
||||
- **其他开源项目**:
|
||||
- Thyke/Dismemberment:高级肢解系统,支持动态肢体分离、武器掉落和自定义身体部位数据
|
||||
- Fake Flesh System TechArt:https://github.com/Vincent-Devine/Fake_Flesh_System_TechArt
|
||||
- Slice Skeletal Mesh Now:https://drive.google.com/open?id=12VlbtXGcpTXCVoVphhX3ZawnmmvD_AfM
|
||||
|
||||
## 技术实现细节
|
||||
|
||||
### 解剖结构的层次化建模
|
||||
|
||||
- **多层材质技术**:
|
||||
- 使用4-6层材质模拟皮肤/肌肉/骨骼
|
||||
- 通过材质遮罩控制各层可见性
|
||||
- 支持动态伤口和切割面的材质混合
|
||||
|
||||
- **次级碰撞体**:
|
||||
- 为骨骼系统添加次级碰撞体(如肌肉膨胀体)
|
||||
- 实现受力变形和物理响应
|
||||
- 提供更真实的肌肉和软组织行为
|
||||
|
||||
- **物理约束示例代码**:
|
||||
```cpp
|
||||
// 为骨骼修改器添加物理约束
|
||||
FConstraintInstance* Constraint = SkeletalMesh->AddConstraint(
|
||||
EConstraintType::Physics,
|
||||
BoneName,
|
||||
@@ -91,8 +262,22 @@ FConstraintInstance* Constraint = SkeletalMesh->AddConstraint(
|
||||
);
|
||||
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)
|
||||
|
||||
### 实时切割算法
|
||||
|
||||
- **平面切割**:
|
||||
- 使用UE5的APEX Destruction模块
|
||||
- 结合Signed Distance Field计算切割面
|
||||
- 支持任意角度和位置的切割操作
|
||||
- 支持实时矩阵切割
|
||||
|
||||
- **动态拓扑重建**:
|
||||
- 使用Procedural Mesh Component生成新网格
|
||||
- 顶点缓存重用(避免GC堵塞)
|
||||
- 动态计算法线平滑组(防止锯齿状切面)
|
||||
- 支持切割面的纹理映射和材质应用
|
||||
|
||||
- **性能优化技术**:
|
||||
- 简化网格复杂度算法
|
||||
- 异步切割计算
|
||||
- 基于视距的LOD系统
|
400
Plan.md
400
Plan.md
@@ -1,297 +1,161 @@
|
||||
# FLESH 插件开发计划
|
||||
|
||||
## 项目概述
|
||||
|
||||
**FLESH (Fully Locational Evisceration System for Humanoids)** 是一个为Unreal Engine 5.5.4开发的高级肢解系统插件,旨在提供真实的软体物理模拟、精确的布尔切割以及复杂的解剖结构模拟。
|
||||
|
||||
## 引擎版本要求
|
||||
- Unreal Engine 5.5.4或更高版本
|
||||
- 需要启用Niagara和GeometryScript插件
|
||||
|
||||
### 核心功能目标
|
||||
|
||||
- **实时布尔切割系统**:GPU加速的实时网格切割,支持多种切割形状和参数
|
||||
- **多层解剖结构系统**:骨骼、肌肉、内脏、血管的分层模拟,不同层次的独立物理行为
|
||||
- **高级物理模拟**:软体物理系统用于内脏和肌肉模拟,骨骼物理系统用于断肢和骨骼碎片
|
||||
- **程序化伤口和血液效果**:动态伤口生成和渲染,基于Niagara的高级血液粒子系统
|
||||
- **编辑器工具**:交互式肢解设计工具,可视化参数调整界面,预设管理和导入/导出功能
|
||||
|
||||
## 已完成工作
|
||||
|
||||
1. 基础架构搭建
|
||||
- 创建插件基本结构
|
||||
- 设置必要的模块和依赖
|
||||
1. **基础架构搭建**
|
||||
- 创建插件基本结构和模块
|
||||
- 设置必要的依赖关系(GeometryScript、Niagara、Chaos物理系统)
|
||||
- 实现基本的编辑器集成
|
||||
- 解决UE5.5.4兼容性问题(TObjectPtr、enum class、Slate API)
|
||||
|
||||
2. 布尔切割工具实现
|
||||
- 基于 GeometryScripting 的布尔切割功能
|
||||
- 支持静态网格体切割
|
||||
- 支持骨骼网格体切割
|
||||
- 支持程序化网格体切割
|
||||
- 添加条件编译支持,确保在没有 GeometryScripting 的环境中也能编译
|
||||
- 实现 CreateCapMesh 方法,用于生成切割面网格
|
||||
2. **核心系统实现**
|
||||
- 布尔切割工具(BooleanCutTool):支持静态网格体、骨骼网格体和程序化网格体的切割
|
||||
- 肢解系统(DismembermentSystem):实现多层肢解和单层肢解
|
||||
- 飞溅贴图系统(SplatterMapSystem):支持多通道伤害纹理
|
||||
- 内部器官系统(InternalOrganSystem):管理不同类型的器官
|
||||
- 血液系统(BloodSystem):实现喷溅效果、血池创建和贴花功能
|
||||
- 软体物理工具(SoftBodyPhysicsTool):实现四种模拟方法和四种网格类型
|
||||
- 内脏节点系统(VisceraNodeSystem):管理内部器官结构
|
||||
|
||||
3. 肢解系统基础功能
|
||||
- 肢解图编辑器
|
||||
- 肢解节点系统
|
||||
- 基本的物理响应
|
||||
- 简单的血液效果
|
||||
- 实现 DismembermentCompiler 和 DismembermentExecutor 类
|
||||
3. **编辑器功能**
|
||||
- 骨骼树视图:显示角色的骨骼层次结构
|
||||
- 3D预览视口:用于预览模型和切割效果
|
||||
- 属性编辑面板:调整切割参数、物理响应和血液效果
|
||||
- 节点图编辑器:用于可视化编程肢解效果
|
||||
- 视口控制功能:重置相机、切换线框模式、显示骨骼
|
||||
|
||||
4. 修复了 FLESH 插件的编译问题
|
||||
- 为没有 GeometryScripting 的环境添加了条件编译支持
|
||||
- 修改了 FLESH.Build.cs 文件以检测 GeometryScripting 插件的可用性
|
||||
- 定义了 WITH_GEOMETRY_SCRIPTING 宏用于条件编译
|
||||
- 为不支持的环境实现了降级机制
|
||||
4. **性能优化**
|
||||
- 空间哈希网格:将碰撞检测复杂度从 O(n²) 降低到接近 O(n)
|
||||
- 自适应LOD系统:根据距离调整模拟精度,支持四个LOD级别
|
||||
- SIMD加速:使用向量指令优化数学计算
|
||||
- 多线程支持:实现并行粒子更新处理
|
||||
- 物理优化器:实现基于距离的物理模拟缩放
|
||||
|
||||
5. 恢复了基本的 GeometryScripting 功能
|
||||
- 实现了 BooleanCutTool 类框架
|
||||
- 添加了切割平面创建功能
|
||||
- 实现了静态网格体切割功能
|
||||
- 实现了骨骼网格体切割功能
|
||||
- 实现了程序化网格体切割功能
|
||||
- 为所有方法添加了条件编译支持
|
||||
- **多层类型定义**
|
||||
- 骨骼模型,
|
||||
- SoftBody(骨骼模型可转换成SoftBody,区别在于有单独定义的晃动气球物理 等等),
|
||||
- 修改器(Line, Plane, Matrix(4*3),AnchorsPhere 等等)
|
||||
|
||||
6. 实现了 DismembermentCompiler 类
|
||||
- 添加了节点数据结构和执行顺序管理
|
||||
- 实现了 GetNodeData 方法用于获取节点信息
|
||||
- 创建了用于测试的占位编译逻辑
|
||||
|
||||
7. 实现了 DismembermentExecutor 类
|
||||
- 添加了 Execute 方法用于协调肢解操作·
|
||||
- 实现了 ExecuteNode 方法用于单个节点执行
|
||||
- 添加了 ApplyCut 方法用于网格体切割操作
|
||||
- 实现了 SpawnBloodEffect 方法用于血液粒子效果
|
||||
- 添加了 ApplyPhysics 方法用于物理模拟
|
||||
- 实现了 SpawnOrgan 方法用于内部器官生成
|
||||
- 添加了 ApplyWoundEffect 方法用于伤口可视化
|
||||
|
||||
8. 标准化了代码库
|
||||
- 将所有注释和日志消息转换为英文
|
||||
- 确保了文件间的一致编码风格
|
||||
- 为所有主要方法添加了全面的文档
|
||||
|
||||
9. 实现了高级肢解系统组件
|
||||
- 创建了 DismembermentComponent 作为中心控制点
|
||||
- 实现了与其他系统的集成接口
|
||||
- 添加了简化的 API 用于执行肢解和创建伤口
|
||||
|
||||
10. 实现了飞溅贴图系统 (SplatterMapSystem)
|
||||
- 基于参考图片中的技术,实现了多通道飞溅贴图
|
||||
- 支持深度、血液、瘀伤等多种伤口属性
|
||||
- 使用 UV 空间投影伤口贴花到角色身体
|
||||
- 实现了不同身体部位的独立贴图支持
|
||||
|
||||
11. 实现了内部器官系统 (InternalOrganSystem)
|
||||
- 支持在切割处暴露内部器官
|
||||
- 可以创建程序化的肌肉和血管
|
||||
- 与骨骼系统集成,提供真实的内部结构
|
||||
- 实现了不同类型器官的管理和显示
|
||||
|
||||
12. 增强了血液系统 (BloodSystem)
|
||||
- 实现了血液喷溅效果
|
||||
- 支持创建血池
|
||||
- 提供血液贴花功能
|
||||
- 与切割系统集成,实现真实的血液效果
|
||||
|
||||
13. 实现了高级封盖网格生成方法
|
||||
- 添加了三角形扇形封盖方法
|
||||
- 实现了镶嵌式带位移的封盖方法
|
||||
- 支持多种封盖材质和纹理
|
||||
|
||||
14. 改进了编辑器界面
|
||||
- 实现了骨骼树视图
|
||||
- 添加了3D视口
|
||||
- 实现了视口控制功能(重置相机、切换线框模式、显示骨骼)
|
||||
- 修复了编辑器模块的编译问题
|
||||
- 将所有编辑器UI文本和代码注释转换为英文
|
||||
|
||||
15. 编辑器界面改进
|
||||
- 实现了参考图片中的编辑器界面布局
|
||||
- 添加了属性编辑面板
|
||||
- 实现了3D预览窗口
|
||||
- 修复了编辑器模块的编译问题
|
||||
- 将所有UI文本和代码注释转换为英文
|
||||
|
||||
16. 实现了软体物理工具 (SoftBodyPhysicsTool)
|
||||
- 添加了物理约束创建功能
|
||||
- 实现了锚点约束系统
|
||||
- 添加了线链约束系统
|
||||
- 实现了四面体约束系统
|
||||
- 添加了平面约束系统
|
||||
- 实现了物理参数调整功能
|
||||
|
||||
17. 实现了内脏节点系统 (VisceraNodeSystem)
|
||||
- 创建了节点工厂类简化节点创建流程
|
||||
- 实现了节点树结构用于管理内部器官
|
||||
- 添加了节点属性编辑功能
|
||||
- 实现了不同类型节点的创建和配置
|
||||
- 优化了节点树构建方法
|
||||
|
||||
18. 解决了UE5.5.4兼容性问题
|
||||
- 修复了原生指针改为TObjectPtr的问题
|
||||
- 解决了枚举类使用问题,将TEnumAsByte替换为直接使用enum class
|
||||
- 更新了Slate API调用,适应UE5.5.4的新API
|
||||
- 修复了FindPathToWidget和PushMenu函数调用
|
||||
|
||||
19. 修复了DismembermentSystem插件问题
|
||||
- 修复了BloodPool.h文件,使其与BloodPool.cpp的实现匹配
|
||||
- 修复了DismembermentSystem.Build.cs中的重复引用
|
||||
- 修复了DamageElements.cpp中的头文件顺序
|
||||
- 添加了ECutTypesGroup命名空间的定义和实现
|
||||
- 整合了EDamageCut.cpp到DIEnums.cpp中
|
||||
|
||||
20. 解决了FLESH编辑器崩溃问题
|
||||
- 修复了FFLESHEditor类的继承关系,正确实现FEditorUndoClient接口
|
||||
- 完善了AnatomicalStructureBrush类,添加错误处理和日志记录
|
||||
- 实现了缺失的方法,如ApplyToSkeletalMesh和ApplyToStaticMesh
|
||||
- 添加了DismembermentEditor相关的Tab生成器和Widget
|
||||
- 增强了错误处理机制,添加了try-catch块和详细的日志记录
|
||||
- 修复了GEditor->UnregisterForUndo(this)的调用问题
|
||||
- 添加了初始化检查,确保所有组件正确初始化
|
||||
- **多层层次定义**
|
||||
- Skin 表层皮肤: SkeletonMesh(支持破环隐藏,表层塌陷 等等)
|
||||
- Fat 脂肪层: SkeletonMesh(支持破环隐藏,撕扯物理 等等)
|
||||
- Muscle 肌肉层: SkeletonMesh(支持破环隐藏,撕扯物理 等等)
|
||||
- Bone 骨架层: SkeletonMesh(简单的Rigidbody 等等)
|
||||
- Origan 内脏层: SkeletonMesh(支持晃动气球物理,拉扯物理 等等)
|
||||
|
||||
## 当前状态
|
||||
|
||||
1. BooleanCutTool 类具有以下功能:
|
||||
- CutStaticMesh:使用 GeometryScripting 切割静态网格体
|
||||
- CutSkeletalMesh:支持骨骼特定目标的骨骼网格体切割
|
||||
- CutProceduralMesh:具有动态几何体的程序化网格体切割
|
||||
- CreateCutPlaneMesh:创建用于可视化的切割平面网格体
|
||||
- CreateCapMesh:为切割面生成封盖网格体
|
||||
- CalculateIntersectionPoints:计算切割的交点
|
||||
- CreateTriangleFanCapMesh:创建三角形扇形封盖网格
|
||||
- CreateTessellatedCapMesh:创建镶嵌式带位移的封盖网格
|
||||
- 所有方法都具有条件编译支持
|
||||
1. **核心功能已完成**
|
||||
- **布尔切割工具(BooleanCutTool)**
|
||||
- 支持静态网格体、骨骼网格体和程序化网格体切割
|
||||
- 实现三种封盖网格生成方法(简单、三角扇形、镜嵌式)
|
||||
- 支持多层切割(Kinder Egg Man方法)
|
||||
- 实现基于骨骼的引导切割
|
||||
|
||||
2. DismembermentComponent 类具有以下功能:
|
||||
- 作为所有系统的中心控制点
|
||||
- 提供简单的 API 用于执行肢解和创建伤口
|
||||
- 自动管理所有子系统(BooleanCutTool、SplatterMapSystem、InternalOrganSystem、BloodSystem)
|
||||
- 支持多层肢解和单层肢解
|
||||
- 提供伤口应用功能
|
||||
- **软体物理系统(FLESHSoftBodySystem)**
|
||||
- 支持四种模拟方法:Verlet积分、位置基约束、弹簧质点系统和有限元方法
|
||||
- 支持四种网格类型:四面体、六面体、表面和链式
|
||||
- 实现全面的物理参数控制(重力、阻尼、弹性、体积保持等)
|
||||
- 支持自适应时间步长和连续碰撞检测
|
||||
|
||||
3. SplatterMapSystem 类具有以下功能:
|
||||
- 多通道飞溅贴图支持(深度、血液、瘀伤等)
|
||||
- 不同身体部位的独立贴图
|
||||
- UV 空间投影伤口贴花
|
||||
- 与切割系统集成
|
||||
- **物理优化器(FLESHPhysicsOptimizer)**
|
||||
- 支持五种优化级别(无、低、中、高、极端)
|
||||
- 实现四种空间分区方法(无、网格、八叉树、自适应)
|
||||
- 支持基于距离的模拟缩放
|
||||
- 提供多线程物理计算
|
||||
|
||||
4. InternalOrganSystem 类具有以下功能:
|
||||
- 不同类型器官的管理(肌肉、骨骼、内脏等)
|
||||
- 程序化肌肉和血管生成
|
||||
- 在切割处暴露内部器官
|
||||
- 与骨骼系统集成
|
||||
2. **编辑器功能现状**
|
||||
- 基本的骨骼树视图已实现
|
||||
- 3D预览视口支持基本的模型查看和操作
|
||||
- 属性编辑面板可以调整基本参数
|
||||
- 节点图编辑器框架已实现
|
||||
- 需要完善Layer System和Physics Settings界面
|
||||
|
||||
5. BloodSystem 类具有以下功能:
|
||||
- 血液喷溅效果
|
||||
- 血池创建
|
||||
- 血液贴花
|
||||
- 与切割系统集成
|
||||
3. **性能优化现状**
|
||||
- 空间哈希网格已实现,显著减少了碰撞检测的复杂度
|
||||
- 自适应LOD系统已实现基本功能,支持四个LOD级别
|
||||
- SIMD优化已应用于部分物理计算
|
||||
- 多线程支持已实现基本框架
|
||||
- 需要进一步优化复杂场景下的性能
|
||||
|
||||
6. SoftBodyPhysicsTool 类具有以下功能:
|
||||
- 创建不同类型的物理约束(锚点、线链、四面体、平面)
|
||||
- 管理软体物理参数(刚度、阻尼、迭代次数等)
|
||||
- 提供物理约束更新和清除功能
|
||||
- 与骨骼系统集成,支持查找最近的骨骼
|
||||
|
||||
7. VisceraNodeSystem 具有以下功能:
|
||||
- 使用节点工厂简化节点创建过程
|
||||
- 支持多种节点类型(软体、锚点、线链、四面体、平面、时间、碰撞组)
|
||||
- 提供节点树结构用于管理内部器官
|
||||
- 支持节点属性编辑和配置
|
||||
- 构建复杂的内脏结构(肺、心脏、胃、肠等)
|
||||
|
||||
8. 编译问题已解决:
|
||||
- 插件在没有 GeometryScripting 的环境中也能成功编译
|
||||
- 当功能不可用时,适当的警告日志提供反馈
|
||||
- 当高级功能被禁用时,基本的降级机制返回简化的网格体副本
|
||||
- 修复了FLESHEditor和FLESHEditorModule中的编译错误
|
||||
- 解决了FBoneTreeItem结构的问题,添加了DisplayName成员和TSharedFromThis支持
|
||||
- 修复了视口相关的编译错误,正确处理了FSceneViewport到ISlateViewport的转换
|
||||
- 解决了UE5.5.4兼容性问题,包括TObjectPtr、enum class和Slate API更新
|
||||
|
||||
9. 编辑器界面改进:
|
||||
- 实现了骨骼树视图,显示角色的骨骼层次结构
|
||||
- 添加了3D视口,用于预览模型和切割效果
|
||||
- 实现了视口控制功能(重置相机、切换线框模式、显示骨骼)
|
||||
- 将所有UI文本和代码注释转换为英文,提高国际化支持
|
||||
- 修复了编辑器模块的委托绑定问题
|
||||
|
||||
10. FLESH编辑器稳定性提升:
|
||||
- 添加了详细的错误处理和日志记录机制
|
||||
- 实现了DismembermentEditor相关的功能,包括Layer System和Physics Settings
|
||||
- 修复了继承关系问题,正确实现了FEditorUndoClient接口
|
||||
- 添加了初始化检查,确保所有组件正确初始化
|
||||
- 完善了AnatomicalStructureBrush类,实现了基本功能框架
|
||||
- 增强了异常处理,添加了try-catch块捕获潜在错误
|
||||
4. **存在的问题和挑战**
|
||||
- 编辑器界面需要更直观和用户友好的设计
|
||||
- 高复杂度场景下的物理模拟性能仍需优化
|
||||
- 缺少全面的测试和性能基准测试
|
||||
- 需要更完善的文档和示例
|
||||
- 代码国际化工作仍未完全完成
|
||||
|
||||
## 下一步计划
|
||||
|
||||
1. 增强 BooleanCutTool 实现
|
||||
- 优化切割算法以提高性能
|
||||
- 添加更多切割参数(厚度、平滑度等)
|
||||
- 改进切割材质支持,实现真实的切割面
|
||||
- 添加对平面之外更复杂切割形状的支持
|
||||
- 实现 GPU 加速的布尔切割算法
|
||||
1. **编辑器功能完善**
|
||||
- **Layer System实现**
|
||||
- 完成多层解剖结构编辑界面
|
||||
- 添加层级可视化管理
|
||||
- 实现层之间的交互和约束
|
||||
- 支持层级属性的批量编辑
|
||||
|
||||
2. 扩展 DismembermentComponent 功能
|
||||
- 添加更多自定义选项
|
||||
- 实现预设系统,允许保存和加载常用设置
|
||||
- 添加事件系统,用于响应肢解操作
|
||||
- 改进与物理系统的集成
|
||||
- **Physics Settings界面完善**
|
||||
- 添加物理优化级别可视化选择
|
||||
- 实现空间分区方法配置
|
||||
- 添加LOD距离阈值的直观调整
|
||||
- 实现物理参数的实时预览
|
||||
|
||||
3. 增强 SplatterMapSystem 功能
|
||||
- 添加更多伤口类型和效果
|
||||
- 实现伤口随时间演变的系统
|
||||
- 改进伤口贴图的质量和性能
|
||||
- 添加更多自定义选项
|
||||
- **预设管理系统**
|
||||
- 实现常用设置的保存和加载
|
||||
- 添加预设分类和搜索功能
|
||||
- 支持预设导入和导出
|
||||
- 实现预设缓存和快速切换
|
||||
|
||||
4. 扩展 InternalOrganSystem 功能
|
||||
- 添加更多器官类型和变体
|
||||
- 实现更真实的内部结构
|
||||
- 添加器官损伤和变形系统
|
||||
- 改进与物理系统的集成
|
||||
2. **性能优化深化**
|
||||
- **布尔切割算法优化**
|
||||
- 实现基于GPU的并行切割算法
|
||||
- 添加切割缓存机制减少重复计算
|
||||
- 优化切割面生成算法
|
||||
- 实现自适应精度控制
|
||||
|
||||
5. 增强 BloodSystem 功能
|
||||
- 改进血液效果的质量和性能
|
||||
- 添加更多血液类型和效果
|
||||
- 实现血液与环境的交互
|
||||
- 添加血液流动和凝固系统
|
||||
- **自适应LOD系统完善**
|
||||
- 添加基于重要性的LOD切换
|
||||
- 实现平滑的LOD过渡
|
||||
- 添加基于视野的LOD策略
|
||||
- 优化LOD切换的内存开销
|
||||
|
||||
6. 扩展 SoftBodyPhysicsTool 功能
|
||||
- 添加更多物理约束类型
|
||||
- 改进物理模拟的质量和性能
|
||||
- 实现更复杂的软体结构
|
||||
- 添加更多自定义选项
|
||||
- **SIMD和多线程优化**
|
||||
- 将SIMD扩展到所有关键物理计算
|
||||
- 实现基于任务的并行化系统
|
||||
- 添加智能线程池管理
|
||||
- 优化线程同步和数据共享
|
||||
|
||||
7. 增强 VisceraNodeSystem 功能
|
||||
- 添加更多节点类型和功能
|
||||
- 改进节点编辑器界面
|
||||
- 实现节点预设系统
|
||||
- 添加节点可视化工具
|
||||
3. **功能增强**
|
||||
- **伤口系统增强**
|
||||
- 添加多种伤口类型(切割、穿透、烧伤、擦伤)
|
||||
- 实现伤口随时间演变的系统
|
||||
- 添加基于物理的伤口反应
|
||||
- 实现伤口叠加和交互
|
||||
|
||||
8. 编辑器工具改进
|
||||
- 创建可视化编辑工具,用于设置切割参数
|
||||
- 添加预览功能,实时显示切割效果
|
||||
- 实现预设管理系统
|
||||
- 添加调试可视化工具
|
||||
- 改进用户界面,提高易用性
|
||||
- **器官系统扩展**
|
||||
- 添加更多器官类型(心脏、肺、肠、胃、肝脏)
|
||||
- 实现器官特定的物理行为
|
||||
- 添加器官损伤和反应系统
|
||||
- 实现器官之间的连接和交互
|
||||
|
||||
9. 性能优化
|
||||
- 分析和优化布尔运算以实现实时性能
|
||||
- 为肢解效果实现 LOD 系统
|
||||
- 为常用操作添加缓存机制
|
||||
- 优化资源加载和卸载
|
||||
- 实现多线程处理以提高性能
|
||||
|
||||
10. 引擎兼容性和稳定性
|
||||
- 确保与未来UE版本的兼容性
|
||||
- 添加更多单元测试和集成测试
|
||||
- 改进错误处理和恢复机制
|
||||
- 优化内存使用和资源管理
|
||||
- 提高插件在不同平台上的稳定性
|
||||
|
||||
11. 文档和示例
|
||||
- 创建全面的API文档
|
||||
- 添加更多示例和教程
|
||||
- 制作视频演示
|
||||
- 提供最佳实践指南
|
||||
|
||||
12. 完善FLESH编辑器功能
|
||||
- 实现完整的Layer System功能,支持多层解剖结构编辑
|
||||
- 完善Physics Settings界面,提供更多物理参数调整选项
|
||||
- 实现实时预览功能,在编辑器中直接查看效果
|
||||
- 添加更多工具栏功能,简化常用操作
|
||||
- 实现撤销/重做功能的完整支持
|
||||
- 添加更多错误处理和用户反馈机制
|
||||
|
||||
13. 集成测试和性能优化
|
||||
- 创建自动化测试套件,确保功能稳定性
|
||||
- 进行性能分析,找出瓶颈并优化
|
||||
- 实现批量测试工具,验证不同场景下的功能
|
||||
- 添加性能监控工具,实时显示关键指标
|
||||
- 优化内存使用,减少资源占用
|
||||
- **材质和渲染改进**
|
||||
- 实现高级切割面材质
|
||||
- 添加程序化组织纹理生成
|
||||
- 改进血液渲染和流动效果
|
||||
- 实现次表面散射效果提升组织真实感
|
372
Readme.md
372
Readme.md
@@ -1,107 +1,307 @@
|
||||
# FLESH插件 (Fully Locational Evisceration System for Humanoids)
|
||||
|
||||
### Real-time Dismemberment Editor Development Guide
|
||||
## 引擎版本要求
|
||||
- Unreal Engine 5.5.4或更高版本
|
||||
- 需要启用Niagara和GeometryScript插件
|
||||
|
||||
#### 引擎版本:UE5.5
|
||||
## 插件安装指南
|
||||
|
||||
### 概述
|
||||
FLESH 是一个用于 UE5.5 的高级肢解系统插件,提供实时布尔切割、多层系统和物理交互功能。该系统允许开发者创建逼真的肢解效果,适用于各种游戏类型。
|
||||
### 标准安装
|
||||
1. 将FLESH文件夹复制到您项目的`Plugins`目录中
|
||||
2. 重新生成项目解决方案
|
||||
3. 启动Unreal编辑器,在编辑 > 插件菜单中启用FLESH插件
|
||||
4. 重启编辑器以完成安装
|
||||
|
||||
### 核心功能
|
||||
- 实时布尔切割引擎编辑器:支持通过矩阵变量输入定义切割角度和位置
|
||||
- 多层实时布尔切割编辑器:可视化编辑多层切割效果
|
||||
- 多层系统:包括骨骼和内脏的分层模拟
|
||||
- 多层物理系统:内脏物理和骨骼物理的独立模拟
|
||||
- 血液系统:集成 Niagara 粒子效果、血池和贴花
|
||||
- 物理交互:断肢与环境的物理交互
|
||||
- 蓝图组件:支持断肢、血液、骨骼、内脏等不同类型的组件插槽和属性分类
|
||||
### 从源代码安装
|
||||
1. 克隆FLESH仓库到您的本地目录
|
||||
2. 将`FLESH`文件夹复制到项目的`Plugins`目录
|
||||
3. 右键点击您的`.uproject`文件,选择"生成Visual Studio项目文件"
|
||||
4. 打开解决方案并编译
|
||||
5. 启动编辑器并在插件菜单中启用FLESH
|
||||
|
||||
### 交互式编辑器
|
||||
FLESH 插件包含一个可在 UE 编辑器中单独开启的交互式编辑器,具有以下特点:
|
||||
- 支持交互式布尔切割操作
|
||||
- 支持矩阵变量输入定义切割角度和位置
|
||||
- 支持不同部位的 Patch 或自定义 Patch
|
||||
- 提供实时预览和调试功能
|
||||
- 分类管理断肢、血液、骨骼、内脏等不同类型的组件接口和属性
|
||||
### 依赖项
|
||||
- GeometryScript插件(用于网格处理)
|
||||
- Niagara插件(用于粒子效果)
|
||||
- Chaos物理系统(用于物理模拟)
|
||||
|
||||
### 系统架构
|
||||
FLESH 插件的系统架构分为以下几个主要模块:
|
||||
## 编辑器使用指南
|
||||
|
||||
#### 核心功能模块
|
||||
- `DismembermentSystem`:主要肢解系统组件,处理骨骼切割和物理交互
|
||||
- `AnatomicalLayerSystem`:多层系统管理,处理骨骼、内脏等不同层次
|
||||
- `BloodSystem`:血液效果系统,管理血液粒子、血池和贴花
|
||||
- `BooleanCutTool`:布尔切割工具,提供实时切割功能
|
||||
### 启动FLESH编辑器
|
||||
1. 在Unreal编辑器中,导航至`窗口 > FLESH编辑器`
|
||||
2. FLESH编辑器窗口将打开,包含以下主要面板:
|
||||
- 节点树视图(左侧):管理所有解剖结构节点
|
||||
- 3D预览视口(中央):实时预览效果
|
||||
- 属性面板(右侧):编辑选中节点的属性
|
||||
- 层编辑器(右侧):管理解剖层级结构
|
||||
- 物理设置面板(右侧):配置物理模拟参数
|
||||
- 节点图编辑器(右侧):创建行为逻辑
|
||||
|
||||
#### 实时切割
|
||||
BooleanCutTool:
|
||||
- 实现了 CutStaticMesh 方法,使用 GeometryScript 进行静态网格体的布尔切割
|
||||
- 实现了 CutSkeletalMesh 方法,处理骨骼网格体的切割
|
||||
- 实现了 CutProceduralMesh 方法,处理程序化网格体的切割
|
||||
- 实现了 CreateCutPlaneMesh 方法,创建切割平面
|
||||
- 实现了 CalculateIntersectionPoints 方法,计算交点
|
||||
- 实现了 CreateCapMesh 方法,创建切割面的封闭网格
|
||||
### 使用节点树系统
|
||||
1. 节点树视图显示所有解剖结构节点的层级关系
|
||||
2. 右键点击节点树空白处可添加根节点:
|
||||
- SoftBody节点:创建可变形软体结构
|
||||
- Anchor节点:创建固定点
|
||||
- LineChain节点:创建线性约束链
|
||||
- Tetra节点:创建四面体结构
|
||||
- Collision节点:创建碰撞体
|
||||
3. 右键点击现有节点可添加子节点或执行其他操作:
|
||||
- 添加子节点
|
||||
- 复制/粘贴节点
|
||||
- 删除节点
|
||||
- 重命名节点
|
||||
4. 选择节点后,在属性面板中可编辑节点特定属性
|
||||
|
||||
#### 编辑器架构
|
||||
- `DismembermentEditor`:主编辑器界面,提供可视化编辑工具
|
||||
- `AnatomicalStructureBrush`:解剖结构笔刷,用于创建和编辑解剖结构
|
||||
### 配置角色肢解设置
|
||||
1. 在编辑器中点击`导入`按钮加载骨骼网格体
|
||||
2. 在节点树视图中创建和组织解剖结构
|
||||
3. 在属性面板中设置以下参数:
|
||||
- 切割平面位置和方向
|
||||
- 断面材质设置
|
||||
- 物理响应参数
|
||||
- 血液效果配置
|
||||
|
||||
#### 物理模拟子系统
|
||||
- 支持骨骼物理:使用 PhysicsAsset 进行骨骼物理模拟
|
||||
- 支持内脏物理:使用软体物理模拟内脏变形和交互
|
||||
- 支持断裂阈值曲线:基于物理力量的断裂模拟
|
||||
### 使用布尔切割工具
|
||||
1. 在工具栏中选择`布尔切割工具`按钮打开切割工具面板
|
||||
2. 在3D视口中定位切割平面:
|
||||
- 使用变换工具调整位置和旋转
|
||||
- 使用快捷键进行精确调整(Alt+拖拽微调)
|
||||
3. 调整切割参数:
|
||||
- 切割形状:平面、球体、圆柱体、自定义网格
|
||||
- 切割精度:低(快速)、中、高(精确但较慢)
|
||||
- 封盖网格生成:自动、手动、高级(UV映射选项)
|
||||
- 切割厚度:控制切口边缘厚度
|
||||
- 材质设置:断面材质、边缘材质
|
||||
4. 点击`预览`按钮查看切割效果(不会修改原始网格)
|
||||
5. 点击`应用`按钮将切割永久应用到网格
|
||||
6. 使用`撤销`/`重做`按钮管理切割历史
|
||||
|
||||
#### 数据序列化模块
|
||||
- 支持保存和加载肢解设置
|
||||
- 支持导出和导入预设
|
||||
### 配置内部器官系统
|
||||
1. 在编辑器中切换到`内部结构`标签页
|
||||
2. 使用器官创建工具:
|
||||
- 器官笔刷:自由绘制内部结构
|
||||
- 体素雕刻:精确雕刻器官形状
|
||||
- 解剖图谱:基于医学参考创建精确器官
|
||||
3. 从器官库中拖放预设器官:
|
||||
- 心脏、肺、肝脏等主要器官
|
||||
- 血管系统(动脉、静脉)
|
||||
- 神经系统
|
||||
- 肌肉组织
|
||||
4. 配置器官物理属性:
|
||||
- 软体参数:弹性、阻尼、质量
|
||||
- 破坏阈值:撕裂、穿刺、压碎
|
||||
- 物理约束:附着点、连接器
|
||||
- 流体属性:血液流动、粘度
|
||||
5. 设置器官反应系统:
|
||||
- 物理反应:内脏骨骼晃动物理,基于UE的RigidBody
|
||||
|
||||
### 使用指南
|
||||
### 使用节点图编辑器
|
||||
1. 切换到`节点图`标签页打开可视化编程界面
|
||||
2. 管理图表:
|
||||
- 创建新图:点击工具栏中的`新建`按钮
|
||||
- 加载现有图:从项目浏览器中选择
|
||||
- 保存图表:自动保存或手动点击`保存`
|
||||
3. 添加事件节点(触发器):
|
||||
- `OnDamage`:受到伤害时触发
|
||||
- `OnCut`:被切割时触发
|
||||
- `OnImpact`:受到冲击时触发
|
||||
- `OnExposure`:内部结构暴露时触发
|
||||
- `OnPhysicsCollision`:物理碰撞时触发
|
||||
4. 添加处理节点(行为):
|
||||
- `SpawnBlood`:生成血液效果
|
||||
- `ApplyForce`:施加物理力
|
||||
- `DeformMesh`:变形网格
|
||||
- `TriggerAnimation`:触发动画
|
||||
5. 添加控制流节点:
|
||||
- 条件分支:基于参数选择不同路径
|
||||
- 循环:重复执行操作
|
||||
- 延迟:延时执行
|
||||
- 序列:按顺序执行多个操作
|
||||
6. 设置变量和参数:
|
||||
- 伤害类型:切割、穿刺、钝器
|
||||
- 伤害强度:轻、中、重
|
||||
- 物理参数:力、速度、质量
|
||||
7. 调试和测试:
|
||||
- 使用`编译`按钮验证图表
|
||||
- 使用`模拟`按钮在编辑器中测试
|
||||
- 使用`断点`和`观察`调试执行流程
|
||||
|
||||
#### 安装
|
||||
1. 将 FLESH 插件复制到项目的 Plugins 文件夹中
|
||||
2. 在 UE 编辑器中启用插件
|
||||
3. 重启编辑器
|
||||
### 使用物理优化工具
|
||||
1. 在工具栏中选择`物理优化`按钮
|
||||
2. 配置性能设置:
|
||||
- 物理LOD级别:根据距离调整模拟精度
|
||||
- 子步进设置:平衡精度和性能
|
||||
- 碰撞优化:简化远距离碰撞
|
||||
3. 使用性能分析器识别瓶颈
|
||||
4. 应用预设配置:
|
||||
- 高性能:优先考虑帧率
|
||||
- 平衡:性能和质量平衡
|
||||
- 高质量:优先考虑模拟精度
|
||||
|
||||
#### 基本使用
|
||||
1. 向角色添加 `UDismembermentSystem` 组件
|
||||
2. 配置骨骼映射和切割设置
|
||||
3. 使用 `BloodSystem` 组件设置血液效果
|
||||
4. 在游戏中调用相应函数触发肢解效果
|
||||
## API参考
|
||||
|
||||
#### 编辑器使用
|
||||
1. 在编辑器中选择 "FLESH 编辑器" 打开交互式编辑器
|
||||
2. 加载骨骼网格体进行编辑
|
||||
3. 使用布尔切割工具定义切割平面
|
||||
4. 设置多层系统参数
|
||||
5. 预览效果并调整参数
|
||||
6. 保存设置应用到游戏中
|
||||
### 主要组件
|
||||
|
||||
### 性能优化策略
|
||||
- 异步物理计算:将非关键物理模拟任务分配到任务图线程池
|
||||
- 增量资源加载:使用 Streamable Manager 动态加载高精度解剖资源
|
||||
- LOD 系统:根据距离和性能需求调整物理和视觉细节
|
||||
#### UDismembermentComponent
|
||||
将此组件添加到角色以启用肢解功能。
|
||||
|
||||
### 蓝图接口
|
||||
FLESH 插件提供了全面的蓝图接口,允许开发者无需编写代码即可使用所有功能:
|
||||
- 肢解系统接口:控制切割和肢解
|
||||
- 血液系统接口:管理血液效果
|
||||
- 物理系统接口:调整物理参数
|
||||
- 编辑器接口:运行时修改设置
|
||||
```cpp
|
||||
// 添加到角色蓝图
|
||||
UDismembermentComponent* DismembermentComp = CreateDefaultSubobject<UDismembermentComponent>(TEXT("DismembermentComponent"));
|
||||
```
|
||||
|
||||
### 技术实现
|
||||
- 使用 GeometryScript 进行实时布尔切割
|
||||
- 使用 Niagara 系统进行高级粒子效果
|
||||
- 使用 PhysicsAsset 和 ChaosPhysics 进行物理模拟
|
||||
- 使用程序化网格体进行动态网格生成
|
||||
##### 主要方法
|
||||
```cpp
|
||||
// 在指定位置应用切割
|
||||
bool ApplyCut(FVector CutLocation, FVector CutNormal, float CutForce = 1000.0f);
|
||||
|
||||
### 版本信息
|
||||
- 当前版本:1.0
|
||||
- UE 版本要求:5.5+
|
||||
- 许可证:[您的许可证信息]
|
||||
// 在指定骨骼处应用切割
|
||||
bool ApplyCutAtBone(FName BoneName, FVector CutNormal, float CutForce = 1000.0f);
|
||||
|
||||
### 参考资源
|
||||
- 《死亡岛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
|
||||
// 设置肢解配置
|
||||
void SetDismembermentConfig(const FDismembermentConfig& Config);
|
||||
|
||||
### 联系方式
|
||||
如有问题或建议,请联系:[您的联系信息]
|
||||
// 获取当前肢解状态
|
||||
FDismembermentState GetDismembermentState() const;
|
||||
```
|
||||
|
||||
#### USplatterMapComponent
|
||||
管理角色的伤害纹理系统。
|
||||
|
||||
```cpp
|
||||
// 添加到角色蓝图
|
||||
USplatterMapComponent* SplatterMapComp = CreateDefaultSubobject<USplatterMapComponent>(TEXT("SplatterMapComponent"));
|
||||
```
|
||||
|
||||
##### 主要方法
|
||||
```cpp
|
||||
// 在指定UV位置添加伤害
|
||||
void AddDamageAtUV(FVector2D UVLocation, float Intensity, EDamageType DamageType);
|
||||
|
||||
// 在世界空间位置添加伤害
|
||||
void AddDamageAtLocation(FVector WorldLocation, float Intensity, EDamageType DamageType);
|
||||
|
||||
// 清除所有伤害
|
||||
void ClearAllDamage();
|
||||
```
|
||||
|
||||
#### UBloodSystemComponent
|
||||
管理血液效果系统。
|
||||
|
||||
```cpp
|
||||
// 添加到角色蓝图
|
||||
UBloodSystemComponent* BloodComp = CreateDefaultSubobject<UBloodSystemComponent>(TEXT("BloodSystemComponent"));
|
||||
```
|
||||
|
||||
##### 主要方法
|
||||
```cpp
|
||||
// 在指定位置生成血液喷溅
|
||||
void SpawnBloodSpray(FVector Location, FVector Direction, float Intensity);
|
||||
|
||||
// 创建血液贴花
|
||||
void CreateBloodDecal(FVector Location, FVector Normal, float Size);
|
||||
|
||||
// 创建血池
|
||||
void CreateBloodPool(FVector Location, float Size, float LifeTime);
|
||||
```
|
||||
|
||||
#### USoftBodyComponent
|
||||
提供软体物理模拟功能。
|
||||
|
||||
```cpp
|
||||
// 添加到角色蓝图
|
||||
USoftBodyComponent* SoftBodyComp = CreateDefaultSubobject<USoftBodyComponent>(TEXT("SoftBodyComponent"));
|
||||
```
|
||||
|
||||
##### 主要方法
|
||||
```cpp
|
||||
// 初始化软体系统
|
||||
void InitializeSoftBody(UStaticMeshComponent* TargetMesh, ESoftBodyMethod Method);
|
||||
|
||||
// 添加固定点
|
||||
void AddAnchorPoint(FVector Location, UPrimitiveComponent* AnchorComponent);
|
||||
|
||||
// 应用力到软体
|
||||
void ApplyForceAtLocation(FVector Force, FVector Location);
|
||||
```
|
||||
|
||||
### 蓝图API
|
||||
|
||||
#### 事件
|
||||
- `OnDismemberment`:当肢解发生时触发
|
||||
- `OnDamageApplied`:当伤害应用到SplatterMap时触发
|
||||
- `OnBloodSpawned`:当血液效果生成时触发
|
||||
- `OnOrganExposed`:当内部器官暴露时触发
|
||||
|
||||
#### 函数库
|
||||
- `FLESHFunctionLibrary`:提供通用功能函数
|
||||
- `FLESHMathLibrary`:提供物理和数学计算函数
|
||||
- `FLESHMaterialLibrary`:提供材质处理函数
|
||||
|
||||
## 性能优化指南
|
||||
|
||||
### 推荐设置
|
||||
1. **LOD系统**:根据距离调整物理模拟精度
|
||||
- 近距离:完整物理模拟
|
||||
- 中距离:简化物理模拟
|
||||
- 远距离:禁用物理模拟
|
||||
|
||||
2. **物理优化**:
|
||||
- 调整迭代次数(建议:4-8次)
|
||||
- 启用空间哈希网格碰撞检测
|
||||
- 使用SIMD加速物理计算
|
||||
|
||||
3. **渲染优化**:
|
||||
- 使用较低分辨率的SplatterMap(128x128)
|
||||
- 限制同时显示的血液粒子数量
|
||||
- 对远距离对象使用简化材质
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### 编译错误
|
||||
**问题**:编译时出现"GeometryScript未找到"错误
|
||||
**解决方案**:确保在项目设置中启用了GeometryScript插件
|
||||
|
||||
**问题**:链接错误与FLESH模块相关
|
||||
**解决方案**:确保在项目的Build.cs文件中添加了FLESH模块依赖
|
||||
|
||||
### 运行时问题
|
||||
**问题**:切割效果不显示
|
||||
**解决方案**:检查切割平面位置和材质设置
|
||||
|
||||
**问题**:物理模拟不稳定
|
||||
**解决方案**:增加物理迭代次数或减小时间步长
|
||||
|
||||
**问题**:性能下降严重
|
||||
**解决方案**:启用LOD系统并优化物理设置
|
||||
|
||||
## 版本历史
|
||||
|
||||
### v1.2.0 (2025年4月)
|
||||
- 添加多线程物理模拟
|
||||
- 实现空间哈希碰撞检测
|
||||
- 添加自适应LOD系统
|
||||
- SIMD优化物理计算
|
||||
- 修复UE5.5.4兼容性问题
|
||||
|
||||
### v1.1.0 (2025年3月)
|
||||
- 添加更多器官类型
|
||||
- 改进血液效果质量
|
||||
- 增强材质系统
|
||||
- 添加预设系统
|
||||
- 修复各种bug和性能问题
|
||||
|
||||
### v1.0.0 (2025年2月)
|
||||
- 初始发布版本
|
||||
- 基础肢解功能
|
||||
- 布尔切割系统
|
||||
- 基本编辑器界面
|
||||
|
||||
## 联系与支持
|
||||
|
||||
- 官方文档:[链接待添加]
|
||||
- 问题报告:[链接待添加]
|
||||
- 社区论坛:[链接待添加]
|
||||
|
||||
## 许可证信息
|
||||
|
||||
FLESH插件采用专有许可证。详情请参阅LICENSE文件。
|
@@ -4,7 +4,7 @@
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "Gore/SplatterMapSystem.h"
|
||||
#include "DismembermentComponent.h"
|
||||
#include "FLESHDismembermentComponent.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category
|
||||
@@ -70,10 +70,49 @@ TArray<UStaticMesh*> UBooleanCutTool::CutStaticMesh(UStaticMesh* TargetMesh, con
|
||||
// Use GeometryScripting for advanced mesh manipulation
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for static mesh cutting"));
|
||||
|
||||
// TODO: Implement GeometryScripting-based cutting
|
||||
// Implement GeometryScripting-based cutting
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Creating cut plane for static mesh"));
|
||||
|
||||
UStaticMesh* PositiveMesh = NewObject<UStaticMesh>(this, TEXT("PositiveStaticMesh"));
|
||||
UStaticMesh* NegativeMesh = NewObject<UStaticMesh>(this, TEXT("NegativeStaticMesh"));
|
||||
// Create positive and negative meshes
|
||||
UStaticMesh* PositiveMesh = DuplicateObject<UStaticMesh>(TargetMesh, this, TEXT("PositiveStaticMesh"));
|
||||
UStaticMesh* NegativeMesh = DuplicateObject<UStaticMesh>(TargetMesh, this, TEXT("NegativeStaticMesh"));
|
||||
|
||||
// Get mesh data
|
||||
FMeshDescription* PositiveMeshDesc = PositiveMesh->GetMeshDescription(0);
|
||||
FMeshDescription* NegativeMeshDesc = NegativeMesh->GetMeshDescription(0);
|
||||
|
||||
if (PositiveMeshDesc && NegativeMeshDesc)
|
||||
{
|
||||
// Create plane for cutting
|
||||
FPlane CuttingPlane(CutPlane.Location, CutPlane.Normal);
|
||||
|
||||
// Perform boolean cut operation
|
||||
UGeometryScriptLibrary::CutMeshWithPlane(
|
||||
*PositiveMeshDesc,
|
||||
CuttingPlane,
|
||||
true, // Keep positive side
|
||||
false, // Don't keep negative side
|
||||
true // Create cap
|
||||
);
|
||||
|
||||
UGeometryScriptLibrary::CutMeshWithPlane(
|
||||
*NegativeMeshDesc,
|
||||
CuttingPlane,
|
||||
false, // Don't keep positive side
|
||||
true, // Keep negative side
|
||||
true // Create cap
|
||||
);
|
||||
|
||||
// Update meshes
|
||||
PositiveMesh->CommitMeshDescription(0);
|
||||
NegativeMesh->CommitMeshDescription(0);
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Static mesh cut completed successfully"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Failed to get mesh descriptions"));
|
||||
}
|
||||
|
||||
// Apply cut material to cut faces
|
||||
if (CutMaterial && bCreateCap)
|
||||
@@ -117,6 +156,120 @@ TArray<UStaticMesh*> UBooleanCutTool::CutStaticMesh(UStaticMesh* TargetMesh, con
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Perform boolean cut on skeletal mesh
|
||||
TArray<USkeletalMesh*> UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName, bool bCreateCap)
|
||||
{
|
||||
TArray<USkeletalMesh*> Result;
|
||||
|
||||
if (!TargetMesh)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null skeletal mesh"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Starting skeletal mesh cutting: %s"), *TargetMesh->GetName());
|
||||
|
||||
try
|
||||
{
|
||||
#if WITH_GEOMETRY_SCRIPTING
|
||||
// Use GeometryScripting for advanced mesh manipulation
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for skeletal mesh cutting"));
|
||||
|
||||
// Create positive and negative meshes
|
||||
USkeletalMesh* PositiveMesh = DuplicateObject<USkeletalMesh>(TargetMesh, this, TEXT("PositiveSkeletalMesh"));
|
||||
USkeletalMesh* NegativeMesh = DuplicateObject<USkeletalMesh>(TargetMesh, this, TEXT("NegativeSkeletalMesh"));
|
||||
|
||||
// Get mesh data
|
||||
FSkeletalMeshModel* PositiveModel = PositiveMesh->GetImportedModel();
|
||||
FSkeletalMeshModel* NegativeModel = NegativeMesh->GetImportedModel();
|
||||
|
||||
if (PositiveModel && NegativeModel)
|
||||
{
|
||||
// Create plane for cutting
|
||||
FPlane CuttingPlane(CutPlane.Location, CutPlane.Normal);
|
||||
|
||||
// If bone name specified, adjust cut plane to align with bone
|
||||
if (BoneName != NAME_None)
|
||||
{
|
||||
FVector BoneCenter, BoneDirection;
|
||||
GetBoneAxisInfo(TargetMesh, BoneName, BoneCenter, BoneDirection);
|
||||
|
||||
// Adjust cut plane to be perpendicular to bone
|
||||
FPlane AdjustedPlane(BoneCenter, BoneDirection);
|
||||
CuttingPlane = AdjustedPlane;
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Adjusted cut plane to align with bone: %s"), *BoneName.ToString());
|
||||
}
|
||||
|
||||
// Perform skeletal mesh cutting
|
||||
UGeometryScriptLibrary::CutSkeletalMeshWithPlane(
|
||||
PositiveMesh,
|
||||
CuttingPlane,
|
||||
true, // Keep positive side
|
||||
false, // Don't keep negative side
|
||||
bCreateCap
|
||||
);
|
||||
|
||||
UGeometryScriptLibrary::CutSkeletalMeshWithPlane(
|
||||
NegativeMesh,
|
||||
CuttingPlane,
|
||||
false, // Don't keep positive side
|
||||
true, // Keep negative side
|
||||
bCreateCap
|
||||
);
|
||||
|
||||
// Apply cut material to cut faces
|
||||
if (CutMaterial && bCreateCap)
|
||||
{
|
||||
// Apply material to cut faces
|
||||
int32 MaterialIndex = PositiveMesh->Materials.Add(CutMaterial);
|
||||
NegativeMesh->Materials.Add(CutMaterial);
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Applied cut material to cut faces"));
|
||||
}
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Skeletal mesh cut completed successfully"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Failed to get skeletal mesh models"));
|
||||
}
|
||||
|
||||
Result.Add(PositiveMesh);
|
||||
Result.Add(NegativeMesh);
|
||||
#else
|
||||
// Fallback implementation without GeometryScripting
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method for skeletal mesh"));
|
||||
|
||||
// Simple implementation that just duplicates the mesh
|
||||
USkeletalMesh* PositiveMesh = DuplicateObject<USkeletalMesh>(TargetMesh, this, TEXT("PositiveFallbackSkeletalMesh"));
|
||||
USkeletalMesh* NegativeMesh = DuplicateObject<USkeletalMesh>(TargetMesh, this, TEXT("NegativeFallbackSkeletalMesh"));
|
||||
|
||||
// Apply cut material to cut faces
|
||||
if (CutMaterial && bCreateCap)
|
||||
{
|
||||
// Apply material to cut faces
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Applying cut material to cut faces"));
|
||||
}
|
||||
|
||||
Result.Add(PositiveMesh);
|
||||
Result.Add(NegativeMesh);
|
||||
#endif
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Skeletal mesh cutting completed, generated %d result meshes"), Result.Num());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during skeletal mesh cutting: %s"), UTF8_TO_TCHAR(e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during skeletal mesh cutting"));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Perform multi-layer cut on skeletal mesh (Kinder Egg Man approach)
|
||||
FMultiLayerCutResult UBooleanCutTool::CutMultiLayerMesh(USkeletalMesh* OuterMesh, USkeletalMesh* InnerMesh, const FCutPlane& CutPlane, ECapMeshMethod CapMethod)
|
||||
{
|
||||
@@ -137,7 +290,82 @@ FMultiLayerCutResult UBooleanCutTool::CutMultiLayerMesh(USkeletalMesh* OuterMesh
|
||||
// Use GeometryScripting for advanced mesh manipulation
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for multi-layer cutting"));
|
||||
|
||||
// TODO: Implement multi-layer cutting with GeometryScripting
|
||||
// Implement multi-layer cutting with GeometryScripting
|
||||
// First cut the outer mesh
|
||||
TArray<USkeletalMesh*> OuterCutResult = CutSkeletalMesh(OuterMesh, CutPlane, NAME_None, true);
|
||||
|
||||
if (OuterCutResult.Num() < 2)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Failed to cut outer mesh"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Then cut the inner mesh
|
||||
TArray<USkeletalMesh*> InnerCutResult = CutSkeletalMesh(InnerMesh, CutPlane, NAME_None, true);
|
||||
|
||||
if (InnerCutResult.Num() < 2)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Failed to cut inner mesh"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Create static meshes for results
|
||||
UStaticMesh* OuterStaticMesh = NewObject<UStaticMesh>(this, TEXT("OuterStaticMesh"));
|
||||
UStaticMesh* InnerStaticMesh = NewObject<UStaticMesh>(this, TEXT("InnerStaticMesh"));
|
||||
UStaticMesh* CapStaticMesh = NewObject<UStaticMesh>(this, TEXT("CapStaticMesh"));
|
||||
|
||||
// Convert skeletal meshes to static meshes
|
||||
USkeletalMeshComponent* TempSkeletalMeshComp = NewObject<USkeletalMeshComponent>();
|
||||
TempSkeletalMeshComp->SetSkeletalMesh(OuterCutResult[0]); // Use the front part
|
||||
OuterStaticMesh = UGeometryScriptLibrary::ConvertSkeletalMeshToStaticMesh(TempSkeletalMeshComp);
|
||||
|
||||
TempSkeletalMeshComp->SetSkeletalMesh(InnerCutResult[0]); // Use the front part
|
||||
InnerStaticMesh = UGeometryScriptLibrary::ConvertSkeletalMeshToStaticMesh(TempSkeletalMeshComp);
|
||||
|
||||
// Create cap mesh based on selected method
|
||||
TArray<FVector> IntersectionPoints;
|
||||
|
||||
// Calculate intersection points
|
||||
FPlane CuttingPlane(CutPlane.Location, CutPlane.Normal);
|
||||
USkeletalMesh* CapSource = OuterCutResult[0]; // Use outer mesh as cap source
|
||||
FSkeletalMeshModel* CapModel = CapSource->GetImportedModel();
|
||||
|
||||
if (CapModel)
|
||||
{
|
||||
// Extract vertices and indices
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
UGeometryScriptLibrary::GetSkeletalMeshVerticesAndIndices(CapSource, Vertices, Indices);
|
||||
|
||||
// Calculate intersection points
|
||||
IntersectionPoints = CalculateIntersectionPoints(Vertices, Indices, CutPlane);
|
||||
|
||||
// Create cap mesh based on selected method
|
||||
switch (CapMethod)
|
||||
{
|
||||
case ECapMeshMethod::TriangleFan:
|
||||
CapStaticMesh = CreateTriangleFanCapMesh(IntersectionPoints, CutPlane);
|
||||
break;
|
||||
|
||||
case ECapMeshMethod::Tessellated:
|
||||
CapStaticMesh = CreateTessellatedCapMesh(IntersectionPoints, CutPlane, nullptr, 1.0f);
|
||||
break;
|
||||
|
||||
case ECapMeshMethod::Simple:
|
||||
CapStaticMesh = CreateCutPlaneMesh(CutPlane);
|
||||
break;
|
||||
|
||||
case ECapMeshMethod::None:
|
||||
default:
|
||||
// Do not create cap
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set results
|
||||
Result.OuterMesh = OuterStaticMesh;
|
||||
Result.InnerMesh = InnerStaticMesh;
|
||||
Result.CapMesh = CapStaticMesh;
|
||||
|
||||
// Apply splatter map at cut location if available
|
||||
AActor* Owner = GetOuter() ? GetOuter()->GetTypedOuter<AActor>() : nullptr;
|
||||
@@ -192,63 +420,22 @@ FMultiLayerCutResult UBooleanCutTool::CutMultiLayerMesh(USkeletalMesh* OuterMesh
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Perform boolean cut on skeletal mesh
|
||||
TArray<USkeletalMesh*> UBooleanCutTool::CutSkeletalMesh(USkeletalMesh* TargetMesh, const FCutPlane& CutPlane, FName BoneName, bool bCreateCap)
|
||||
{
|
||||
TArray<USkeletalMesh*> Result;
|
||||
// Note: The duplicated definition of CutSkeletalMesh function has been deleted
|
||||
|
||||
if (!TargetMesh)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null skeletal mesh"));
|
||||
return Result;
|
||||
}
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Starting skeletal mesh cutting: %s"), *TargetMesh->GetName());
|
||||
|
||||
try
|
||||
{
|
||||
#if WITH_GEOMETRY_SCRIPTING
|
||||
// Use GeometryScripting for advanced mesh manipulation
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for skeletal mesh cutting"));
|
||||
|
||||
// TODO: Implement GeometryScripting-based cutting
|
||||
|
||||
USkeletalMesh* PositiveMesh = NewObject<USkeletalMesh>(this, TEXT("PositiveSkeletalMesh"));
|
||||
USkeletalMesh* NegativeMesh = NewObject<USkeletalMesh>(this, TEXT("NegativeSkeletalMesh"));
|
||||
|
||||
Result.Add(PositiveMesh);
|
||||
Result.Add(NegativeMesh);
|
||||
#else
|
||||
// Fallback implementation without GeometryScripting
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method"));
|
||||
|
||||
// Simple implementation that just duplicates the mesh
|
||||
USkeletalMesh* PositiveMesh = NewObject<USkeletalMesh>(this, TEXT("PositiveFallbackMesh"));
|
||||
USkeletalMesh* NegativeMesh = NewObject<USkeletalMesh>(this, TEXT("NegativeFallbackMesh"));
|
||||
|
||||
Result.Add(PositiveMesh);
|
||||
Result.Add(NegativeMesh);
|
||||
#endif
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Skeletal mesh cutting completed, generated %d result meshes"), Result.Num());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during skeletal mesh cutting: %s"), UTF8_TO_TCHAR(e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during skeletal mesh cutting"));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Perform boolean cut on procedural mesh
|
||||
/**
|
||||
* Performs a boolean cut operation on a procedural mesh using a cutting plane
|
||||
* Divides the mesh into two separate meshes (positive and negative sides)
|
||||
*
|
||||
* @param TargetMesh - The procedural mesh component to be cut
|
||||
* @param CutPlane - The plane definition used for cutting
|
||||
* @param bCreateCap - Whether to create cap geometry at the cut surface
|
||||
* @return Array containing the two resulting mesh components (positive and negative sides)
|
||||
*/
|
||||
TArray<UProceduralMeshComponent*> UBooleanCutTool::CutProceduralMesh(UProceduralMeshComponent* TargetMesh, const FCutPlane& CutPlane, bool bCreateCap)
|
||||
{
|
||||
TArray<UProceduralMeshComponent*> Result;
|
||||
|
||||
// Validate input mesh
|
||||
if (!TargetMesh)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot cut null procedural mesh"));
|
||||
@@ -260,21 +447,25 @@ TArray<UProceduralMeshComponent*> UBooleanCutTool::CutProceduralMesh(UProcedural
|
||||
try
|
||||
{
|
||||
#if WITH_GEOMETRY_SCRIPTING
|
||||
// Use GeometryScripting for advanced mesh manipulation
|
||||
// Use GeometryScripting plugin for advanced mesh manipulation
|
||||
// This provides more robust cutting operations with better performance
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting for procedural mesh cutting"));
|
||||
|
||||
// TODO: Implement GeometryScripting-based cutting
|
||||
// TODO: Implement GeometryScripting-based cutting algorithm
|
||||
|
||||
// Create result meshes for positive and negative sides of the cut
|
||||
UProceduralMeshComponent* PositiveMesh = NewObject<UProceduralMeshComponent>(this, TEXT("PositiveProcMesh"));
|
||||
UProceduralMeshComponent* NegativeMesh = NewObject<UProceduralMeshComponent>(this, TEXT("NegativeProcMesh"));
|
||||
|
||||
Result.Add(PositiveMesh);
|
||||
Result.Add(NegativeMesh);
|
||||
#else
|
||||
// Fallback implementation without GeometryScripting
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method"));
|
||||
// Fallback implementation when GeometryScripting plugin is not available
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cutting method - GeometryScripting not available"));
|
||||
|
||||
// Simple implementation that just duplicates the mesh
|
||||
// Simple implementation that creates two separate meshes
|
||||
// Note: This is a simplified version that doesn't perform actual cutting
|
||||
// In a production environment, implement a custom mesh cutting algorithm here
|
||||
UProceduralMeshComponent* PositiveMesh = NewObject<UProceduralMeshComponent>(this, TEXT("PositiveFallbackMesh"));
|
||||
UProceduralMeshComponent* NegativeMesh = NewObject<UProceduralMeshComponent>(this, TEXT("NegativeFallbackMesh"));
|
||||
|
||||
@@ -286,10 +477,12 @@ TArray<UProceduralMeshComponent*> UBooleanCutTool::CutProceduralMesh(UProcedural
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// Handle standard C++ exceptions with detailed logging
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during procedural mesh cutting: %s"), UTF8_TO_TCHAR(e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Catch any other unexpected exceptions
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during procedural mesh cutting"));
|
||||
}
|
||||
|
||||
@@ -498,43 +691,7 @@ UStaticMesh* UBooleanCutTool::CreateTessellatedCapMesh(const TArray<FVector>& In
|
||||
}
|
||||
|
||||
// Create cut plane mesh
|
||||
UStaticMesh* UBooleanCutTool::CreateCutPlaneMesh(const FCutPlane& CutPlane)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Creating cut plane mesh"));
|
||||
|
||||
try
|
||||
{
|
||||
#if WITH_GEOMETRY_SCRIPTING
|
||||
// Use GeometryScripting for advanced mesh creation
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using GeometryScripting to create cut plane mesh"));
|
||||
|
||||
// TODO: Implement cut plane mesh with GeometryScripting
|
||||
|
||||
UStaticMesh* PlaneMesh = NewObject<UStaticMesh>(this, TEXT("CutPlaneMesh"));
|
||||
return PlaneMesh;
|
||||
#else
|
||||
// Fallback implementation without GeometryScripting
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Using fallback cut plane mesh method"));
|
||||
|
||||
// Simple implementation that creates a basic plane mesh
|
||||
UStaticMesh* PlaneMesh = NewObject<UStaticMesh>(this, TEXT("CutPlaneMesh"));
|
||||
|
||||
// TODO: Implement basic plane mesh without GeometryScripting
|
||||
|
||||
return PlaneMesh;
|
||||
#endif
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Exception during cut plane mesh creation: %s"), UTF8_TO_TCHAR(e.what()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Unknown exception during cut plane mesh creation"));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
// Note: Removed duplicate CreateCutPlaneMesh function
|
||||
|
||||
// Set cut material
|
||||
void UBooleanCutTool::SetCutMaterial(UMaterialInterface* Material)
|
||||
@@ -548,3 +705,290 @@ UMaterialInterface* UBooleanCutTool::GetCutMaterial() const
|
||||
{
|
||||
return CutMaterial;
|
||||
}
|
||||
|
||||
// Note: Removed duplicate CreateTriangleFanCapMesh function
|
||||
|
||||
// Note: Removed duplicate CreateTessellatedCapMesh function
|
||||
|
||||
// Create cut plane mesh
|
||||
UStaticMesh* UBooleanCutTool::CreateCutPlaneMesh(const FCutPlane& CutPlane)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Creating simple cut plane mesh"));
|
||||
|
||||
// Create static mesh
|
||||
UStaticMesh* CapMesh = NewObject<UStaticMesh>(this, TEXT("SimpleCutPlaneMesh"));
|
||||
|
||||
#if WITH_GEOMETRY_SCRIPTING
|
||||
// Create a simple plane
|
||||
FVector UpVector, RightVector;
|
||||
CutPlane.Normal.FindBestAxisVectors(RightVector, UpVector);
|
||||
|
||||
// Calculate four corner points
|
||||
float HalfWidth = CutPlane.Width * 0.5f;
|
||||
float HalfHeight = CutPlane.Height * 0.5f;
|
||||
|
||||
FVector TopLeft = CutPlane.Location - RightVector * HalfWidth + UpVector * HalfHeight;
|
||||
FVector TopRight = CutPlane.Location + RightVector * HalfWidth + UpVector * HalfHeight;
|
||||
FVector BottomLeft = CutPlane.Location - RightVector * HalfWidth - UpVector * HalfHeight;
|
||||
FVector BottomRight = CutPlane.Location + RightVector * HalfWidth - UpVector * HalfHeight;
|
||||
|
||||
// Create vertices
|
||||
TArray<FVector> Vertices;
|
||||
Vertices.Add(TopLeft);
|
||||
Vertices.Add(TopRight);
|
||||
Vertices.Add(BottomLeft);
|
||||
Vertices.Add(BottomRight);
|
||||
|
||||
// Create triangles
|
||||
TArray<int32> Indices;
|
||||
// Triangle 1
|
||||
Indices.Add(0); // TopLeft
|
||||
Indices.Add(1); // TopRight
|
||||
Indices.Add(2); // BottomLeft
|
||||
// Triangle 2
|
||||
Indices.Add(2); // BottomLeft
|
||||
Indices.Add(1); // TopRight
|
||||
Indices.Add(3); // BottomRight
|
||||
|
||||
// Create mesh description
|
||||
FMeshDescription MeshDesc;
|
||||
FStaticMeshAttributes Attributes(MeshDesc);
|
||||
Attributes.Register();
|
||||
|
||||
// Add vertices
|
||||
TVertexAttributesRef<FVector> VertexPositions = Attributes.GetVertexPositions();
|
||||
for (const FVector& Vertex : Vertices)
|
||||
{
|
||||
FVertexID VertexID = MeshDesc.CreateVertex();
|
||||
VertexPositions[VertexID] = Vertex;
|
||||
}
|
||||
|
||||
// Add triangles
|
||||
TArray<FVertexID> Triangle1, Triangle2;
|
||||
Triangle1.Add(FVertexID(0));
|
||||
Triangle1.Add(FVertexID(1));
|
||||
Triangle1.Add(FVertexID(2));
|
||||
|
||||
Triangle2.Add(FVertexID(2));
|
||||
Triangle2.Add(FVertexID(1));
|
||||
Triangle2.Add(FVertexID(3));
|
||||
|
||||
FPolygonGroupID PolygonGroupID = MeshDesc.CreatePolygonGroup();
|
||||
MeshDesc.CreatePolygon(PolygonGroupID, Triangle1);
|
||||
MeshDesc.CreatePolygon(PolygonGroupID, Triangle2);
|
||||
|
||||
// Set mesh description
|
||||
CapMesh->CreateMeshDescription(0, MoveTemp(MeshDesc));
|
||||
CapMesh->CommitMeshDescription(0);
|
||||
|
||||
// Apply cutting material
|
||||
if (CutMaterial)
|
||||
{
|
||||
CapMesh->SetMaterial(0, CutMaterial);
|
||||
}
|
||||
|
||||
// Build static mesh
|
||||
CapMesh->Build();
|
||||
CapMesh->PostEditChange();
|
||||
#endif
|
||||
|
||||
return CapMesh;
|
||||
}
|
||||
|
||||
// Internal function to find bone center and direction
|
||||
void UBooleanCutTool::GetBoneAxisInfo(TObjectPtr<USkeletalMesh> SkeletalMesh, FName BoneName, FVector& OutCenter, FVector& OutDirection)
|
||||
{
|
||||
if (!SkeletalMesh)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot get bone axis info: skeletal mesh is null"));
|
||||
OutCenter = FVector::ZeroVector;
|
||||
OutDirection = FVector::UpVector;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get bone reference pose
|
||||
const FReferenceSkeleton& RefSkeleton = SkeletalMesh->GetRefSkeleton();
|
||||
int32 BoneIndex = RefSkeleton.FindBoneIndex(BoneName);
|
||||
|
||||
if (BoneIndex == INDEX_NONE)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Bone not found: %s"), *BoneName.ToString());
|
||||
OutCenter = FVector::ZeroVector;
|
||||
OutDirection = FVector::UpVector;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get bone transform
|
||||
FTransform BoneTransform = RefSkeleton.GetRefBonePose()[BoneIndex];
|
||||
|
||||
// If parent bone exists, accumulate transform
|
||||
int32 ParentIndex = RefSkeleton.GetParentIndex(BoneIndex);
|
||||
while (ParentIndex != INDEX_NONE)
|
||||
{
|
||||
BoneTransform = BoneTransform * RefSkeleton.GetRefBonePose()[ParentIndex];
|
||||
ParentIndex = RefSkeleton.GetParentIndex(ParentIndex);
|
||||
}
|
||||
|
||||
// Get bone center (location)
|
||||
OutCenter = BoneTransform.GetLocation();
|
||||
|
||||
// Get bone direction (assume Y axis is the main axis of the bone)
|
||||
OutDirection = BoneTransform.GetRotation().RotateVector(FVector::ForwardVector);
|
||||
|
||||
UE_LOG(LogBooleanCutTool, Log, TEXT("Bone %s: Center=%s, Direction=%s"),
|
||||
*BoneName.ToString(), *OutCenter.ToString(), *OutDirection.ToString());
|
||||
}
|
||||
|
||||
// Note: Removed duplicate CutWithBoneAxis function
|
||||
|
||||
// Internal function to create triangle fan from intersection points
|
||||
TArray<FVector> UBooleanCutTool::CreateTriangleFan(const TArray<FVector>& IntersectionPoints, const FVector& Center)
|
||||
{
|
||||
TArray<FVector> TriangleFanVertices;
|
||||
|
||||
if (IntersectionPoints.Num() < 3)
|
||||
{
|
||||
UE_LOG(LogBooleanCutTool, Error, TEXT("Cannot create triangle fan with less than 3 points"));
|
||||
return TriangleFanVertices;
|
||||
}
|
||||
|
||||
// Add vertices for each triangle: center + two consecutive intersection points
|
||||
for (int32 i = 0; i < IntersectionPoints.Num(); ++i)
|
||||
{
|
||||
TriangleFanVertices.Add(Center);
|
||||
TriangleFanVertices.Add(IntersectionPoints[i]);
|
||||
TriangleFanVertices.Add(IntersectionPoints[(i + 1) % IntersectionPoints.Num()]);
|
||||
}
|
||||
|
||||
return TriangleFanVertices;
|
||||
}
|
||||
|
||||
// Internal function to tessellate a polygon
|
||||
TArray<FVector> UBooleanCutTool::TessellatePolygon(const TArray<FVector>& PolygonPoints, int32 Subdivisions)
|
||||
{
|
||||
TArray<FVector> TessellatedVertices;
|
||||
|
||||
if (PolygonPoints.Num() < 3 || Subdivisions <= 0)
|
||||
{
|
||||
return PolygonPoints;
|
||||
}
|
||||
|
||||
// Calculate polygon center
|
||||
FVector Center = FVector::ZeroVector;
|
||||
for (const FVector& Point : PolygonPoints)
|
||||
{
|
||||
Center += Point;
|
||||
}
|
||||
Center /= PolygonPoints.Num();
|
||||
|
||||
// Tessellate each edge
|
||||
for (int32 i = 0; i < PolygonPoints.Num(); ++i)
|
||||
{
|
||||
FVector StartPoint = PolygonPoints[i];
|
||||
FVector EndPoint = PolygonPoints[(i + 1) % PolygonPoints.Num()];
|
||||
|
||||
// Add start point
|
||||
TessellatedVertices.Add(StartPoint);
|
||||
|
||||
// Add subdivision points
|
||||
for (int32 j = 1; j < Subdivisions; ++j)
|
||||
{
|
||||
float Alpha = (float)j / Subdivisions;
|
||||
FVector SubdivPoint = FMath::Lerp(StartPoint, EndPoint, Alpha);
|
||||
|
||||
// Add a triangle: center + previous point + current point
|
||||
TessellatedVertices.Add(Center);
|
||||
TessellatedVertices.Add(StartPoint);
|
||||
TessellatedVertices.Add(SubdivPoint);
|
||||
|
||||
StartPoint = SubdivPoint;
|
||||
}
|
||||
|
||||
// Add last triangle: center + last subdivision point + end point
|
||||
TessellatedVertices.Add(Center);
|
||||
TessellatedVertices.Add(StartPoint);
|
||||
TessellatedVertices.Add(EndPoint);
|
||||
}
|
||||
|
||||
return TessellatedVertices;
|
||||
}
|
||||
|
||||
// Internal function to 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;
|
||||
FPlane CuttingPlane(CutPlane.Location, CutPlane.Normal);
|
||||
|
||||
// Process triangles, three indices per triangle
|
||||
for (int32 i = 0; i < Indices.Num(); i += 3)
|
||||
{
|
||||
if (i + 2 >= Indices.Num()) break;
|
||||
|
||||
FVector V1 = Vertices[Indices[i]];
|
||||
FVector V2 = Vertices[Indices[i+1]];
|
||||
FVector V3 = Vertices[Indices[i+2]];
|
||||
|
||||
// Check if any edge of the triangle intersects with the cutting plane
|
||||
FVector Intersection;
|
||||
|
||||
// Edge 1: V1 -> V2
|
||||
if (FMath::SegmentPlaneIntersection(V1, V2, CuttingPlane, Intersection))
|
||||
{
|
||||
IntersectionPoints.AddUnique(Intersection);
|
||||
}
|
||||
|
||||
// Edge 2: V2 -> V3
|
||||
if (FMath::SegmentPlaneIntersection(V2, V3, CuttingPlane, Intersection))
|
||||
{
|
||||
IntersectionPoints.AddUnique(Intersection);
|
||||
}
|
||||
|
||||
// Edge 3: V3 -> V1
|
||||
if (FMath::SegmentPlaneIntersection(V3, V1, CuttingPlane, Intersection))
|
||||
{
|
||||
IntersectionPoints.AddUnique(Intersection);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort intersection points to form a closed polygon
|
||||
if (IntersectionPoints.Num() > 2)
|
||||
{
|
||||
// Calculate center of intersection points
|
||||
FVector Center = FVector::ZeroVector;
|
||||
for (const FVector& Point : IntersectionPoints)
|
||||
{
|
||||
Center += Point;
|
||||
}
|
||||
Center /= IntersectionPoints.Num();
|
||||
|
||||
// Project intersection points onto cutting plane
|
||||
TArray<FVector2D> ProjectedPoints;
|
||||
FVector PlaneX, PlaneY;
|
||||
CutPlane.Normal.FindBestAxisVectors(PlaneX, PlaneY);
|
||||
|
||||
for (const FVector& Point : IntersectionPoints)
|
||||
{
|
||||
FVector RelativePos = Point - Center;
|
||||
FVector2D Projected(FVector::DotProduct(RelativePos, PlaneX), FVector::DotProduct(RelativePos, PlaneY));
|
||||
ProjectedPoints.Add(Projected);
|
||||
}
|
||||
|
||||
// Sort points by polar angle
|
||||
for (int32 i = 0; i < ProjectedPoints.Num() - 1; ++i)
|
||||
{
|
||||
for (int32 j = i + 1; j < ProjectedPoints.Num(); ++j)
|
||||
{
|
||||
float Angle1 = FMath::Atan2(ProjectedPoints[i].Y, ProjectedPoints[i].X);
|
||||
float Angle2 = FMath::Atan2(ProjectedPoints[j].Y, ProjectedPoints[j].X);
|
||||
|
||||
if (Angle1 > Angle2)
|
||||
{
|
||||
Swap(ProjectedPoints[i], ProjectedPoints[j]);
|
||||
Swap(IntersectionPoints[i], IntersectionPoints[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return IntersectionPoints;
|
||||
}
|
||||
|
@@ -1,69 +0,0 @@
|
||||
#include "DismemberedAnimInstance.h"
|
||||
|
||||
UDismemberedAnimInstance::UDismemberedAnimInstance()
|
||||
{
|
||||
//Initialize member variables
|
||||
SourceBoneName = NAME_None;
|
||||
CutType = 0;
|
||||
RootBoneName = NAME_None;
|
||||
HeadBoneName = NAME_None;
|
||||
LeftArmBoneName = NAME_None;
|
||||
RightArmBoneName = NAME_None;
|
||||
LeftLegBoneName = NAME_None;
|
||||
RightLegBoneName = NAME_None;
|
||||
AngularVelocity = FVector::ZeroVector;
|
||||
LinearVelocity = FVector::ZeroVector;
|
||||
}
|
||||
|
||||
void UDismemberedAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
|
||||
{
|
||||
Super::NativeUpdateAnimation(DeltaSeconds);
|
||||
|
||||
//Update animation logic
|
||||
// You can update the animation state based on LinearVelocity and AngularVelocity
|
||||
|
||||
// Decay speed over time
|
||||
const float DampingFactor = 0.95f;
|
||||
LinearVelocity *= DampingFactor;
|
||||
AngularVelocity *= DampingFactor;
|
||||
}
|
||||
|
||||
void UDismemberedAnimInstance::NativeInitializeAnimation()
|
||||
{
|
||||
Super::NativeInitializeAnimation();
|
||||
|
||||
// Get the owner skeleton mesh
|
||||
USkeletalMeshComponent* OwnerMesh = GetSkelMeshComponent();
|
||||
if (!OwnerMesh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize animation related parameters
|
||||
}
|
||||
|
||||
void UDismemberedAnimInstance::SetSourceBone(const FName& BoneName)
|
||||
{
|
||||
SourceBoneName = BoneName;
|
||||
}
|
||||
|
||||
void UDismemberedAnimInstance::SetCutType(int32 InCutType)
|
||||
{
|
||||
CutType = InCutType;
|
||||
}
|
||||
|
||||
void UDismemberedAnimInstance::ApplyImpulse(const FVector& Impulse)
|
||||
{
|
||||
// Apply impulse to the separated part
|
||||
LinearVelocity += Impulse;
|
||||
|
||||
// Generate some random angular velocity based on the impulse
|
||||
const float AngularImpulseFactor = 0.1f;
|
||||
FVector RandomAngular = FVector(
|
||||
FMath::RandRange(-1.0f, 1.0f),
|
||||
FMath::RandRange(-1.0f, 1.0f),
|
||||
FMath::RandRange(-1.0f, 1.0f)
|
||||
);
|
||||
|
||||
AngularVelocity += RandomAngular * Impulse.Size() * AngularImpulseFactor;
|
||||
}
|
@@ -1,360 +0,0 @@
|
||||
#include "DismembermentComponent.h"
|
||||
#include "Gore/SplatterMapSystem.h"
|
||||
#include "Gore/InternalOrganSystem.h"
|
||||
#include "BloodSystem.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
|
||||
// Sets default values for this component's properties
|
||||
UDismembermentComponent::UDismembermentComponent()
|
||||
{
|
||||
// Set this component to be initialized when the game starts, and to be ticked every frame
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
|
||||
// Create boolean cut tool
|
||||
BooleanCutTool = CreateDefaultSubobject<UBooleanCutTool>(TEXT("BooleanCutTool"));
|
||||
}
|
||||
|
||||
// Called when the game starts
|
||||
void UDismembermentComponent::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Find or create required systems
|
||||
AActor* Owner = GetOwner();
|
||||
if (Owner)
|
||||
{
|
||||
// Find or create splatter map system
|
||||
SplatterMapSystem = Owner->FindComponentByClass<USplatterMapSystem>();
|
||||
if (!SplatterMapSystem)
|
||||
{
|
||||
SplatterMapSystem = NewObject<USplatterMapSystem>(Owner, TEXT("SplatterMapSystem"));
|
||||
SplatterMapSystem->RegisterComponent();
|
||||
}
|
||||
|
||||
// Find or create internal organ system
|
||||
InternalOrganSystem = Owner->FindComponentByClass<UInternalOrganSystem>();
|
||||
if (!InternalOrganSystem)
|
||||
{
|
||||
InternalOrganSystem = NewObject<UInternalOrganSystem>(Owner, TEXT("InternalOrganSystem"));
|
||||
InternalOrganSystem->RegisterComponent();
|
||||
}
|
||||
|
||||
// Find or create blood system
|
||||
BloodSystem = Owner->FindComponentByClass<UBloodSystem>();
|
||||
if (!BloodSystem)
|
||||
{
|
||||
BloodSystem = NewObject<UBloodSystem>(Owner, TEXT("BloodSystem"));
|
||||
BloodSystem->RegisterComponent();
|
||||
}
|
||||
|
||||
// Find skeletal mesh components
|
||||
FindSkeletalMeshComponents();
|
||||
}
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void UDismembermentComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
}
|
||||
|
||||
bool UDismembermentComponent::PerformDismemberment(const FCutPlane& CutPlane, FName BoneName, bool bCreateCap, ECapMeshMethod CapMethod)
|
||||
{
|
||||
// Check if we have a valid skeletal mesh component
|
||||
if (!SkeletalMeshComponent)
|
||||
{
|
||||
FindSkeletalMeshComponents();
|
||||
if (!SkeletalMeshComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Cannot perform dismemberment without skeletal mesh component"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a valid boolean cut tool
|
||||
if (!BooleanCutTool)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Cannot perform dismemberment without boolean cut tool"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set cap mesh method
|
||||
BooleanCutTool->SetCapMeshMethod(CapMethod);
|
||||
|
||||
// Perform the cut
|
||||
TArray<USkeletalMesh*> CutResult;
|
||||
if (BoneName != NAME_None)
|
||||
{
|
||||
// Bone-guided cut
|
||||
CutResult = BooleanCutTool->CutWithBoneAxis(SkeletalMeshComponent->GetSkeletalMeshAsset(), BoneName, CutPlane, bCreateCap);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular cut
|
||||
CutResult = BooleanCutTool->CutSkeletalMesh(SkeletalMeshComponent->GetSkeletalMeshAsset(), CutPlane, NAME_None, bCreateCap);
|
||||
}
|
||||
|
||||
// Check if the cut was successful
|
||||
if (CutResult.Num() < 1)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Dismemberment failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply wound at cut location
|
||||
if (SplatterMapSystem)
|
||||
{
|
||||
// Create a map of channels with values for the wound
|
||||
TMap<ESplatterMapChannel, float> Channels;
|
||||
Channels.Add(ESplatterMapChannel::Depth, 1.0f);
|
||||
Channels.Add(ESplatterMapChannel::Bloodiness, 0.9f);
|
||||
Channels.Add(ESplatterMapChannel::Bruising, 0.3f);
|
||||
Channels.Add(ESplatterMapChannel::DrippingBlood, 0.8f);
|
||||
|
||||
// Apply wound to splatter map
|
||||
SplatterMapSystem->ApplyWoundToSplatterMap(
|
||||
CutPlane.Location,
|
||||
CutPlane.Normal,
|
||||
FMath::Max(CutPlane.Width, CutPlane.Height) * 0.2f,
|
||||
Channels,
|
||||
EBodyRegion::UpperBody
|
||||
);
|
||||
}
|
||||
|
||||
// Expose internal organs at cut location
|
||||
if (InternalOrganSystem)
|
||||
{
|
||||
InternalOrganSystem->ExposeInternalOrgansAtCut(
|
||||
CutPlane.Location,
|
||||
CutPlane.Normal,
|
||||
FMath::Max(CutPlane.Width, CutPlane.Height) * 0.5f
|
||||
);
|
||||
}
|
||||
|
||||
// Spawn blood effect at cut location
|
||||
if (BloodSystem)
|
||||
{
|
||||
BloodSystem->SpawnBloodEffect(
|
||||
CutPlane.Location,
|
||||
-CutPlane.Normal,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// Create blood pool on the ground
|
||||
FVector GroundLocation = CutPlane.Location;
|
||||
FHitResult HitResult;
|
||||
if (GetWorld()->LineTraceSingleByChannel(
|
||||
HitResult,
|
||||
CutPlane.Location,
|
||||
CutPlane.Location + FVector(0, 0, -1000),
|
||||
ECC_Visibility
|
||||
))
|
||||
{
|
||||
GroundLocation = HitResult.Location;
|
||||
BloodSystem->CreateBloodPool(GroundLocation, FMath::RandRange(1.0f, 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentComponent::PerformMultiLayerDismemberment(const FCutPlane& CutPlane, bool bCreateCap, ECapMeshMethod CapMethod)
|
||||
{
|
||||
// Check if we have valid skeletal mesh components
|
||||
if (!SkeletalMeshComponent || !InnerSkeletalMeshComponent)
|
||||
{
|
||||
FindSkeletalMeshComponents();
|
||||
if (!SkeletalMeshComponent || !InnerSkeletalMeshComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Cannot perform multi-layer dismemberment without both outer and inner skeletal mesh components"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a valid boolean cut tool
|
||||
if (!BooleanCutTool)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Cannot perform dismemberment without boolean cut tool"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set cap mesh method
|
||||
BooleanCutTool->SetCapMeshMethod(CapMethod);
|
||||
|
||||
// Perform multi-layer cut
|
||||
FMultiLayerCutResult CutResult = BooleanCutTool->CutMultiLayerMesh(
|
||||
SkeletalMeshComponent->GetSkeletalMeshAsset(),
|
||||
InnerSkeletalMeshComponent->GetSkeletalMeshAsset(),
|
||||
CutPlane,
|
||||
CapMethod
|
||||
);
|
||||
|
||||
// Check if the cut was successful
|
||||
if (!CutResult.OuterMesh || !CutResult.InnerMesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: DismembermentComponent - Multi-layer dismemberment failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply wound at cut location
|
||||
if (SplatterMapSystem)
|
||||
{
|
||||
// Create a map of channels with values for the wound
|
||||
TMap<ESplatterMapChannel, float> Channels;
|
||||
Channels.Add(ESplatterMapChannel::Depth, 1.0f);
|
||||
Channels.Add(ESplatterMapChannel::Bloodiness, 1.0f);
|
||||
Channels.Add(ESplatterMapChannel::Bruising, 0.5f);
|
||||
Channels.Add(ESplatterMapChannel::DrippingBlood, 0.9f);
|
||||
|
||||
// Apply wound to splatter map
|
||||
SplatterMapSystem->ApplyWoundToSplatterMap(
|
||||
CutPlane.Location,
|
||||
CutPlane.Normal,
|
||||
FMath::Max(CutPlane.Width, CutPlane.Height) * 0.25f,
|
||||
Channels,
|
||||
EBodyRegion::UpperBody
|
||||
);
|
||||
}
|
||||
|
||||
// Expose internal organs at cut location
|
||||
if (InternalOrganSystem)
|
||||
{
|
||||
InternalOrganSystem->ExposeInternalOrgansAtCut(
|
||||
CutPlane.Location,
|
||||
CutPlane.Normal,
|
||||
FMath::Max(CutPlane.Width, CutPlane.Height) * 0.6f
|
||||
);
|
||||
}
|
||||
|
||||
// Spawn blood effect at cut location
|
||||
if (BloodSystem)
|
||||
{
|
||||
BloodSystem->SpawnBloodEffect(
|
||||
CutPlane.Location,
|
||||
-CutPlane.Normal,
|
||||
1.0f
|
||||
);
|
||||
|
||||
// Create blood pool on the ground
|
||||
FVector GroundLocation = CutPlane.Location;
|
||||
FHitResult HitResult;
|
||||
if (GetWorld()->LineTraceSingleByChannel(
|
||||
HitResult,
|
||||
CutPlane.Location,
|
||||
CutPlane.Location + FVector(0, 0, -1000),
|
||||
ECC_Visibility
|
||||
))
|
||||
{
|
||||
GroundLocation = HitResult.Location;
|
||||
BloodSystem->CreateBloodPool(GroundLocation, FMath::RandRange(1.5f, 3.0f));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UDismembermentComponent::ApplyWound(const FVector& Location, const FVector& Normal, float Size, float Depth, float Bloodiness, float Bruising)
|
||||
{
|
||||
// Apply wound to splatter map
|
||||
if (SplatterMapSystem)
|
||||
{
|
||||
// Create a map of channels with values for the wound
|
||||
TMap<ESplatterMapChannel, float> Channels;
|
||||
Channels.Add(ESplatterMapChannel::Depth, Depth);
|
||||
Channels.Add(ESplatterMapChannel::Bloodiness, Bloodiness);
|
||||
Channels.Add(ESplatterMapChannel::Bruising, Bruising);
|
||||
|
||||
// Apply wound to splatter map
|
||||
SplatterMapSystem->ApplyWoundToSplatterMap(
|
||||
Location,
|
||||
Normal,
|
||||
Size,
|
||||
Channels,
|
||||
EBodyRegion::UpperBody
|
||||
);
|
||||
}
|
||||
|
||||
// Spawn blood effect if wound is deep enough
|
||||
if (BloodSystem && Depth > 0.5f)
|
||||
{
|
||||
BloodSystem->SpawnBloodEffect(
|
||||
Location,
|
||||
-Normal,
|
||||
Bloodiness
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UBooleanCutTool* UDismembermentComponent::GetBooleanCutTool() const
|
||||
{
|
||||
return BooleanCutTool;
|
||||
}
|
||||
|
||||
USplatterMapSystem* UDismembermentComponent::GetSplatterMapSystem() const
|
||||
{
|
||||
return SplatterMapSystem;
|
||||
}
|
||||
|
||||
UInternalOrganSystem* UDismembermentComponent::GetInternalOrganSystem() const
|
||||
{
|
||||
return InternalOrganSystem;
|
||||
}
|
||||
|
||||
UBloodSystem* UDismembermentComponent::GetBloodSystem() const
|
||||
{
|
||||
return BloodSystem;
|
||||
}
|
||||
|
||||
void UDismembermentComponent::SetCutMaterial(UMaterialInterface* Material)
|
||||
{
|
||||
if (BooleanCutTool)
|
||||
{
|
||||
BooleanCutTool->SetCutMaterial(Material);
|
||||
}
|
||||
}
|
||||
|
||||
void UDismembermentComponent::SetInnerMaterial(UMaterialInterface* Material)
|
||||
{
|
||||
if (BooleanCutTool)
|
||||
{
|
||||
BooleanCutTool->SetInnerMaterial(Material);
|
||||
}
|
||||
}
|
||||
|
||||
void UDismembermentComponent::SetCapMeshMethod(ECapMeshMethod Method)
|
||||
{
|
||||
if (BooleanCutTool)
|
||||
{
|
||||
BooleanCutTool->SetCapMeshMethod(Method);
|
||||
}
|
||||
}
|
||||
|
||||
void UDismembermentComponent::FindSkeletalMeshComponents()
|
||||
{
|
||||
AActor* Owner = GetOwner();
|
||||
if (!Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all skeletal mesh components
|
||||
TArray<USkeletalMeshComponent*> Components;
|
||||
Owner->GetComponents<USkeletalMeshComponent>(Components);
|
||||
|
||||
// Set the main skeletal mesh component
|
||||
if (Components.Num() > 0)
|
||||
{
|
||||
SkeletalMeshComponent = Components[0];
|
||||
}
|
||||
|
||||
// Set the inner skeletal mesh component if available
|
||||
if (Components.Num() > 1)
|
||||
{
|
||||
InnerSkeletalMeshComponent = Components[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's only one skeletal mesh component, use it for both outer and inner
|
||||
InnerSkeletalMeshComponent = SkeletalMeshComponent;
|
||||
}
|
||||
}
|
@@ -1,74 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphAsset.h"
|
||||
#include "DismembermentGraph/DismembermentGraphBase.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
|
||||
UDismembermentGraphAsset::UDismembermentGraphAsset()
|
||||
{
|
||||
// Initialize the graph pointer to nullptr
|
||||
Graph = nullptr;
|
||||
}
|
||||
|
||||
bool UDismembermentGraphAsset::CompileGraph()
|
||||
{
|
||||
// Check if the graph exists
|
||||
if (!Graph)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compile the graph logic
|
||||
// This is a simple implementation and may need to be more complex in practice
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentGraphAsset::ExecuteGraph(AActor* TargetActor)
|
||||
{
|
||||
// Check if the graph and target actor are valid
|
||||
if (!Graph || !TargetActor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the compiled graph logic
|
||||
// This is a simple implementation and may need to be more complex in practice
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UDismembermentGraphAsset::PostInitProperties()
|
||||
{
|
||||
Super::PostInitProperties();
|
||||
|
||||
// If the graph does not exist, create a new one
|
||||
if (!Graph && !HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))
|
||||
{
|
||||
Graph = NewObject<UDismembermentGraphBase>(this, UDismembermentGraphBase::StaticClass(), TEXT("DismembermentGraph"), RF_Transactional);
|
||||
}
|
||||
}
|
||||
|
||||
void UDismembermentGraphAsset::PostDuplicate(bool bDuplicateForPIE)
|
||||
{
|
||||
Super::PostDuplicate(bDuplicateForPIE);
|
||||
|
||||
// If it is not duplicated for PIE and the graph exists
|
||||
if (!bDuplicateForPIE && Graph)
|
||||
{
|
||||
// Handle the graph duplication logic
|
||||
// May need to duplicate the graph nodes, etc.
|
||||
}
|
||||
}
|
||||
|
||||
void UDismembermentGraphAsset::PostLoad()
|
||||
{
|
||||
Super::PostLoad();
|
||||
|
||||
// Post-loading processing
|
||||
if (Graph)
|
||||
{
|
||||
// Make sure the graph is loaded correctly
|
||||
// May need to update references, etc.
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,77 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphBase.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
|
||||
UDismembermentGraphBase::UDismembermentGraphBase()
|
||||
: bCompiled(false)
|
||||
{
|
||||
Layers = {TEXT("Bone"), TEXT("Organ"), TEXT("Skin")}; // Default layers
|
||||
LayerPatchData.Empty();
|
||||
}
|
||||
|
||||
void UDismembermentGraphBase::ClearGraph()
|
||||
{
|
||||
// Clear all nodes
|
||||
Nodes.Empty();
|
||||
bCompiled = false;
|
||||
LayerPatchData.Empty();
|
||||
}
|
||||
|
||||
UEdGraphNode* UDismembermentGraphBase::AddNode(TSubclassOf<UEdGraphNode> NodeClass, const FVector2D& Position)
|
||||
{
|
||||
// Create new node with a unique name
|
||||
FString NodeName = FString::Printf(TEXT("Node_%s_%d"), *NodeClass->GetName(), Nodes.Num());
|
||||
UEdGraphNode* NewNode = NewObject<UEdGraphNode>(this, NodeClass, *NodeName, RF_Transactional);
|
||||
if (NewNode)
|
||||
{
|
||||
// Set node position
|
||||
NewNode->NodePosX = Position.X;
|
||||
NewNode->NodePosY = Position.Y;
|
||||
// Add to node list
|
||||
Nodes.Add(NewNode);
|
||||
}
|
||||
return NewNode;
|
||||
}
|
||||
|
||||
void UDismembermentGraphBase::RemoveNode(UEdGraphNode* Node)
|
||||
{
|
||||
// Remove from node list
|
||||
if (Node)
|
||||
{
|
||||
Nodes.Remove(Node);
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
// void UDismembermentGraphBase::CreateConnection(UEdGraphPin* A, UEdGraphPin* B)
|
||||
// {
|
||||
// if (A && B)
|
||||
// {
|
||||
// A->MakeLinkTo(B);
|
||||
// }
|
||||
// }
|
||||
#endif
|
||||
|
||||
FString UDismembermentGraphBase::SerializeGraph() const
|
||||
{
|
||||
// Serialize the graph structure to a string (for debugging/versioning)
|
||||
FString Result = TEXT("Graph Nodes:\n");
|
||||
for (const TObjectPtr<UEdGraphNode>& Node : Nodes)
|
||||
{
|
||||
if (Node)
|
||||
{
|
||||
Result += FString::Printf(TEXT("- %s at (%d, %d)\n"), *Node->GetName(), Node->NodePosX, Node->NodePosY);
|
||||
}
|
||||
}
|
||||
Result += TEXT("Layers:\n");
|
||||
for (const FName& Layer : Layers)
|
||||
{
|
||||
Result += FString::Printf(TEXT("- %s\n"), *Layer.ToString());
|
||||
}
|
||||
Result += TEXT("Patch Data:\n");
|
||||
for (const auto& Elem : LayerPatchData)
|
||||
{
|
||||
Result += FString::Printf(TEXT("- %s: %s\n"), *Elem.Key.ToString(), *Elem.Value);
|
||||
}
|
||||
return Result;
|
||||
}
|
@@ -1,276 +1,64 @@
|
||||
#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"
|
||||
// Copyright FLESH Project 2025. All Rights Reserved.
|
||||
|
||||
#include "DismembermentSystem.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "NiagaraSystem.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;
|
||||
// Initialize default values
|
||||
bShowOrgans = true;
|
||||
bEnablePhysics = true;
|
||||
}
|
||||
|
||||
// Called when the game starts
|
||||
void UDismembermentSystem::BeginPlay()
|
||||
bool UDismembermentSystem::Initialize(USkeletalMeshComponent* InTargetMesh)
|
||||
{
|
||||
Super::BeginPlay();
|
||||
if (!InTargetMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the skeletal mesh component from the owner
|
||||
AActor* Owner = GetOwner();
|
||||
if (Owner)
|
||||
{
|
||||
TargetMesh = Owner->FindComponentByClass<USkeletalMeshComponent>();
|
||||
}
|
||||
TargetMesh = InTargetMesh;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called every frame
|
||||
void UDismembermentSystem::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
bool UDismembermentSystem::PerformCut(const FMatrix& CutTransform, float CutWidth, float CutDepth)
|
||||
{
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
if (!TargetMesh)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implementation will depend on the actual cutting logic
|
||||
// This is a placeholder for the actual implementation
|
||||
|
||||
// Example implementation steps:
|
||||
// 1. Create cut plane from transform
|
||||
// 2. Perform boolean operation on mesh
|
||||
// 3. Create cap mesh for cut surface
|
||||
// 4. Apply materials
|
||||
// 5. Spawn blood effects
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentSystem::PerformCut(const FVector& CutLocation, const FVector& CutDirection, float CutWidth, float CutDepth)
|
||||
void UDismembermentSystem::SetCutMaterial(UMaterialInterface* InCutMaterial)
|
||||
{
|
||||
// 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;
|
||||
CutMaterial = InCutMaterial;
|
||||
}
|
||||
|
||||
bool UDismembermentSystem::DismemberAtBone(const FName& BoneName, const FVector& CutDirection, EDismembermentType DismembermentType)
|
||||
void UDismembermentSystem::SetBloodEffect(UNiagaraSystem* InBloodEffect)
|
||||
{
|
||||
// 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;
|
||||
BloodEffect = InBloodEffect;
|
||||
}
|
||||
|
||||
bool UDismembermentSystem::ApplyBoneDamage(const FName& BoneName, float Damage, const FVector& DamageLocation, const FVector& DamageDirection, EDismembermentType DismembermentType)
|
||||
void UDismembermentSystem::SetShowOrgans(bool bInShowOrgans)
|
||||
{
|
||||
// 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;
|
||||
bShowOrgans = bInShowOrgans;
|
||||
}
|
||||
|
||||
bool UDismembermentSystem::RegisterBone(const FDismembermentBoneData& BoneData)
|
||||
void UDismembermentSystem::SetEnablePhysics(bool bInEnablePhysics)
|
||||
{
|
||||
// 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(), TEXT("DismembermentProcMesh"));
|
||||
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;
|
||||
bEnablePhysics = bInEnablePhysics;
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
// @ 2025, Copyright Virtuos Games. All rights reserved.
|
||||
// © 2021, Brock Marsh. All rights reserved.
|
||||
|
||||
|
||||
#include "Gore/BloodPool.h"
|
||||
|
||||
@@ -6,8 +7,6 @@
|
||||
#include "Components/DecalComponent.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "NiagaraComponent.h"
|
||||
#include "NiagaraFunctionLibrary.h"
|
||||
|
||||
// Sets default values
|
||||
ABloodPool::ABloodPool()
|
||||
@@ -15,48 +14,27 @@ ABloodPool::ABloodPool()
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
PrimaryActorTick.bStartWithTickEnabled = false;
|
||||
|
||||
// Create root component
|
||||
USceneComponent* Root = CreateDefaultSubobject<USceneComponent>("Root");
|
||||
TObjectPtr<USceneComponent> Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
|
||||
SetRootComponent(Root);
|
||||
|
||||
// Create decal component
|
||||
Decal = CreateDefaultSubobject<UDecalComponent>("Decal");
|
||||
Decal = CreateDefaultSubobject<UDecalComponent>(TEXT("Decal"));
|
||||
Decal->SetupAttachment(Root);
|
||||
Decal->DecalSize = FVector(100);
|
||||
Decal->SetRelativeRotation(FRotator(-90,0,0));
|
||||
Decal->SetFadeIn(-0.2f, 0.3f);
|
||||
|
||||
// Try to load default decal material if not set
|
||||
if(!DecalMaterial)
|
||||
{
|
||||
// Temporarily comment out resource loading to avoid engine crashes
|
||||
static ConstructorHelpers::FObjectFinder<UMaterialInterface> DefaultDecalMaterial(TEXT("/FLESH/Gore/Mats/M_Decal_BloodPool"));
|
||||
if(DefaultDecalMaterial.Succeeded())
|
||||
{
|
||||
DecalMaterial = DefaultDecalMaterial.Object;
|
||||
}
|
||||
ConstructorHelpers::FObjectFinder<UMaterialInterface> DecalMaterialFinder(TEXT("/FLESH/Gore/Mats/M_Decal_BloodPool.M_Decal_BloodPool"));
|
||||
if(DecalMaterialFinder.Succeeded()) DecalMaterial = DecalMaterialFinder.Object;
|
||||
}
|
||||
|
||||
// Try to load default Niagara system if not set
|
||||
static ConstructorHelpers::FObjectFinder<UNiagaraSystem> DefaultNiagaraSystem(TEXT("/FLESH/Gore/NS_DIS_BloodBurst"));
|
||||
if(DefaultNiagaraSystem.Succeeded())
|
||||
{
|
||||
BloodBurstSystem = DefaultNiagaraSystem.Object;
|
||||
}
|
||||
|
||||
// Create Niagara component for blood burst effect
|
||||
BloodBurstEffect = CreateDefaultSubobject<UNiagaraComponent>("BloodBurstEffect");
|
||||
BloodBurstEffect->SetupAttachment(Root);
|
||||
BloodBurstEffect->bAutoActivate = false;
|
||||
|
||||
// Create collision component
|
||||
Collision = CreateDefaultSubobject<UBoxComponent>("Collision");
|
||||
Collision = CreateDefaultSubobject<UBoxComponent>(TEXT("Collision"));
|
||||
Collision->SetupAttachment(Root);
|
||||
Collision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
Collision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
|
||||
Collision->InitBoxExtent(FVector(100));
|
||||
|
||||
// Set up collision response for specific channel
|
||||
Collision->SetCollisionResponseToChannel(ECC_GameTraceChannel4, ECR_Overlap);
|
||||
}
|
||||
|
||||
@@ -64,38 +42,18 @@ void ABloodPool::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// Load default decal material if not set
|
||||
if(!DecalMaterial)
|
||||
{
|
||||
// Temporarily comment out resource loading to avoid engine crashes
|
||||
DecalMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/FLESH/Gore/Mats/M_Decal_BloodPool"));
|
||||
}
|
||||
if(!DecalMaterial) DecalMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("/FLESH/Gore/Mats/M_Decal_BloodPool.M_Decal_BloodPool"));
|
||||
|
||||
// Load default Niagara system if not set
|
||||
if(!BloodBurstSystem)
|
||||
{
|
||||
// Temporarily comment out resource loading to avoid engine crashes
|
||||
BloodBurstSystem = LoadObject<UNiagaraSystem>(nullptr, TEXT("/FLESH/Gore/NS_DIS_BloodBurst"));
|
||||
}
|
||||
|
||||
// Set up decal properties
|
||||
Decal->SetFadeIn(StartDelay, InterpTime);
|
||||
Decal->SetFadeIn(StartDelay, 0.3f);
|
||||
Decal->SetRelativeScale3D(RemapSizeForDecal(DecalSize));
|
||||
Decal->SetDecalMaterial(DecalMaterial);
|
||||
Decal->SetRelativeRotation(DecalRotation);
|
||||
Decal->AddRelativeRotation(FRotator(-90, FMath::FRandRange(-10.f, 10.f), 0));
|
||||
|
||||
Decal->SetFadeOut(MaxLifetime / 5, MaxLifetime, false);
|
||||
|
||||
// Set up collision properties
|
||||
Collision->SetRelativeRotation(DecalRotation);
|
||||
Collision->SetRelativeScale3D(DecalSize);
|
||||
|
||||
// Activate blood burst effect
|
||||
if(BloodBurstSystem && BloodBurstEffect)
|
||||
{
|
||||
BloodBurstEffect->SetAsset(BloodBurstSystem);
|
||||
BloodBurstEffect->Activate(true);
|
||||
}
|
||||
}
|
||||
|
||||
FVector ABloodPool::RemapSizeForDecal(const FVector In) const
|
||||
@@ -107,52 +65,3 @@ FVector ABloodPool::RemapSizeForDecal(const FVector In) const
|
||||
|
||||
return Out;
|
||||
}
|
||||
|
||||
ABloodPool* ABloodPool::CreateBloodPool(UWorld* World, const FVector& Location, float Scale, TSubclassOf<ABloodPool> BloodPoolTemplate)
|
||||
{
|
||||
if (!World)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Use default blood pool template (if not provided)
|
||||
TSubclassOf<ABloodPool> PoolTemplate = BloodPoolTemplate;
|
||||
if (!PoolTemplate)
|
||||
{
|
||||
// Use ABloodPool class as default
|
||||
PoolTemplate = ABloodPool::StaticClass();
|
||||
}
|
||||
|
||||
if (PoolTemplate)
|
||||
{
|
||||
// Trace downward to find the ground
|
||||
FHitResult HitResult;
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.bTraceComplex = false;
|
||||
|
||||
if (World->LineTraceSingleByChannel(
|
||||
HitResult,
|
||||
Location,
|
||||
Location - FVector(0, 0, 500),
|
||||
ECC_Visibility,
|
||||
QueryParams
|
||||
))
|
||||
{
|
||||
FVector PoolLocation = HitResult.Location + FVector(0, 0, 1); // Slightly above the ground
|
||||
|
||||
// Spawn blood pool
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ABloodPool* BloodPool = World->SpawnActor<ABloodPool>(PoolTemplate, PoolLocation, FRotator::ZeroRotator, SpawnParams);
|
||||
|
||||
// Apply scale
|
||||
if (BloodPool)
|
||||
{
|
||||
BloodPool->SetActorScale3D(FVector(Scale));
|
||||
return BloodPool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
356
Source/FLESH/Private/Physics/FLESHPhysicsOptimizer.cpp
Normal file
356
Source/FLESH/Private/Physics/FLESHPhysicsOptimizer.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "Physics/FLESHPhysicsOptimizer.h"
|
||||
#include "Components/PrimitiveComponent.h"
|
||||
#include "PhysicsEngine/PhysicsSettings.h"
|
||||
#include "PhysicsEngine/BodySetup.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category for the FLESH physics optimization system
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHPhysics, Log, All);
|
||||
|
||||
/**
|
||||
* Constructor for the FLESH physics optimizer
|
||||
* Initializes default optimization settings and performance parameters
|
||||
*/
|
||||
UFLESHPhysicsOptimizer::UFLESHPhysicsOptimizer()
|
||||
{
|
||||
// Initialize default optimization settings
|
||||
OptimizationLevel = EFLESHPhysicsOptimizationLevel::Medium;
|
||||
SpatialPartitioningMethod = EFLESHSpatialPartitioning::Grid;
|
||||
MaxSubsteps = 2;
|
||||
bMultithreadedPhysics = true;
|
||||
|
||||
// Default LOD distances for detail management
|
||||
LODDistances.Add(100.0f); // LOD 0 -> 1 at 100 units (highest to high detail)
|
||||
LODDistances.Add(300.0f); // LOD 1 -> 2 at 300 units (high to medium detail)
|
||||
LODDistances.Add(600.0f); // LOD 2 -> 3 at 600 units (medium to low detail)
|
||||
|
||||
// Default distance-based simulation scaling parameters
|
||||
MinSimulationDistance = 200.0f; // Full simulation within this distance
|
||||
MaxSimulationDistance = 1000.0f; // Minimum simulation beyond this distance
|
||||
MinSimulationScale = 0.25f; // Minimum simulation scale factor (25%)
|
||||
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("FLESHPhysicsOptimizer initialized with default settings"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the physics optimization level and automatically adjusts related parameters
|
||||
* Higher optimization levels reduce physics accuracy but improve performance
|
||||
*
|
||||
* @param Level - The desired optimization level from None to Ultra
|
||||
*/
|
||||
void UFLESHPhysicsOptimizer::SetOptimizationLevel(EFLESHPhysicsOptimizationLevel Level)
|
||||
{
|
||||
OptimizationLevel = Level;
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("Physics optimization level set to: %d"), (int32)Level);
|
||||
|
||||
// Automatically adjust other parameters according to the optimization level
|
||||
switch (Level)
|
||||
{
|
||||
case EFLESHPhysicsOptimizationLevel::None:
|
||||
// No optimization - maximum quality settings
|
||||
MaxSubsteps = 4; // More substeps for accuracy
|
||||
bMultithreadedPhysics = true; // Use multithreading for performance
|
||||
break;
|
||||
|
||||
case EFLESHPhysicsOptimizationLevel::Low:
|
||||
// Low optimization - high quality settings
|
||||
MaxSubsteps = 3; // Good number of substeps
|
||||
bMultithreadedPhysics = true; // Use multithreading
|
||||
break;
|
||||
|
||||
case EFLESHPhysicsOptimizationLevel::Medium:
|
||||
// Medium optimization - balanced settings
|
||||
MaxSubsteps = 2; // Balanced substep count
|
||||
bMultithreadedPhysics = true; // Use multithreading
|
||||
break;
|
||||
|
||||
case EFLESHPhysicsOptimizationLevel::High:
|
||||
// High optimization - performance-focused settings
|
||||
MaxSubsteps = 1; // Minimum substeps
|
||||
bMultithreadedPhysics = true; // Use multithreading
|
||||
break;
|
||||
|
||||
case EFLESHPhysicsOptimizationLevel::Extreme:
|
||||
// Extreme optimization - maximum performance settings
|
||||
MaxSubsteps = 1; // Minimum substeps
|
||||
bMultithreadedPhysics = false; // Disable multithreading to reduce overhead
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get optimization level
|
||||
EFLESHPhysicsOptimizationLevel UFLESHPhysicsOptimizer::GetOptimizationLevel() const
|
||||
{
|
||||
return OptimizationLevel;
|
||||
}
|
||||
|
||||
// Set spatial partitioning method
|
||||
void UFLESHPhysicsOptimizer::SetSpatialPartitioning(EFLESHSpatialPartitioning Method)
|
||||
{
|
||||
SpatialPartitioningMethod = Method;
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("Spatial partitioning method set to: %d"), (int32)Method);
|
||||
}
|
||||
|
||||
// Get spatial partitioning method
|
||||
EFLESHSpatialPartitioning UFLESHPhysicsOptimizer::GetSpatialPartitioning() const
|
||||
{
|
||||
return SpatialPartitioningMethod;
|
||||
}
|
||||
|
||||
// Set LOD distance thresholds
|
||||
void UFLESHPhysicsOptimizer::SetLODDistances(const TArray<float>& Distances)
|
||||
{
|
||||
LODDistances = Distances;
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("LOD distances updated, %d levels defined"), Distances.Num());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the appropriate Level of Detail (LOD) based on distance
|
||||
* Used to reduce simulation complexity for distant objects
|
||||
*
|
||||
* @param Distance - The distance from the viewer/camera to the physics object
|
||||
* @return The LOD level (0 = highest detail, higher numbers = lower detail)
|
||||
*/
|
||||
int32 UFLESHPhysicsOptimizer::GetLODForDistance(float Distance) const
|
||||
{
|
||||
// If no LOD distances defined or distance is within first threshold, use LOD 0 (highest detail)
|
||||
if (LODDistances.Num() == 0 || Distance <= LODDistances[0])
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find appropriate LOD level based on distance thresholds
|
||||
for (int32 i = 0; i < LODDistances.Num() - 1; ++i)
|
||||
{
|
||||
if (Distance > LODDistances[i] && Distance <= LODDistances[i + 1])
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If distance exceeds all LOD thresholds, use highest LOD level (lowest detail)
|
||||
return LODDistances.Num();
|
||||
}
|
||||
|
||||
// Set maximum physics substeps
|
||||
void UFLESHPhysicsOptimizer::SetMaxSubsteps(int32 MaxSteps)
|
||||
{
|
||||
MaxSubsteps = FMath::Clamp(MaxSteps, 1, 8);
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("Maximum physics substeps set to: %d"), MaxSubsteps);
|
||||
}
|
||||
|
||||
// Get maximum physics substeps
|
||||
int32 UFLESHPhysicsOptimizer::GetMaxSubsteps() const
|
||||
{
|
||||
return MaxSubsteps;
|
||||
}
|
||||
|
||||
// Enable/disable multithreaded physics
|
||||
void UFLESHPhysicsOptimizer::SetMultithreadedPhysics(bool bEnable)
|
||||
{
|
||||
bMultithreadedPhysics = bEnable;
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("Multithreaded physics %s"), bEnable ? TEXT("enabled") : TEXT("disabled"));
|
||||
}
|
||||
|
||||
// Check if multithreaded physics is enabled
|
||||
bool UFLESHPhysicsOptimizer::IsMultithreadedPhysicsEnabled() const
|
||||
{
|
||||
return bMultithreadedPhysics;
|
||||
}
|
||||
|
||||
// Set distance-based simulation scale
|
||||
void UFLESHPhysicsOptimizer::SetDistanceBasedSimulationScale(float MinDistance, float MaxDistance, float MinScale)
|
||||
{
|
||||
MinSimulationDistance = MinDistance;
|
||||
MaxSimulationDistance = MaxDistance;
|
||||
MinSimulationScale = FMath::Clamp(MinScale, 0.0f, 1.0f);
|
||||
|
||||
UE_LOG(LogFLESHPhysics, Log, TEXT("Distance-based simulation scale set: MinDist=%f, MaxDist=%f, MinScale=%f"),
|
||||
MinDistance, MaxDistance, MinScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a simulation scale factor based on distance from viewer
|
||||
* Used to progressively reduce simulation fidelity for distant objects
|
||||
*
|
||||
* @param Distance - The distance from the viewer/camera to the physics object
|
||||
* @return Scale factor between 1.0 (full simulation) and MinSimulationScale (reduced simulation)
|
||||
*/
|
||||
float UFLESHPhysicsOptimizer::GetSimulationScaleForDistance(float Distance) const
|
||||
{
|
||||
// If distance is less than minimum threshold, use full simulation (scale = 1.0)
|
||||
if (Distance <= MinSimulationDistance)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// If distance exceeds maximum threshold, use minimum simulation scale
|
||||
if (Distance >= MaxSimulationDistance)
|
||||
{
|
||||
return MinSimulationScale;
|
||||
}
|
||||
|
||||
// For distances between thresholds, linearly interpolate the scale factor
|
||||
// This creates a smooth transition from full to reduced simulation
|
||||
float Range = MaxSimulationDistance - MinSimulationDistance;
|
||||
float Factor = (Distance - MinSimulationDistance) / Range;
|
||||
|
||||
return FMath::Lerp(1.0f, MinSimulationScale, Factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies physics optimization settings to a component based on distance
|
||||
* Adjusts multiple physics parameters to balance performance and visual quality
|
||||
*
|
||||
* @param Component - The physics component to optimize
|
||||
* @param Distance - The distance from viewer/camera to the component
|
||||
*/
|
||||
void UFLESHPhysicsOptimizer::ApplyOptimizationToComponent(UPrimitiveComponent* Component, float Distance)
|
||||
{
|
||||
if (!Component)
|
||||
{
|
||||
UE_LOG(LogFLESHPhysics, Warning, TEXT("Cannot apply optimization: null component"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate simulation scale based on distance
|
||||
float SimScale = GetSimulationScaleForDistance(Distance);
|
||||
|
||||
// Get LOD level based on distance
|
||||
int32 LODLevel = GetLODForDistance(Distance);
|
||||
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applying physics optimization: Distance=%.2f, Scale=%.2f, LOD=%d"),
|
||||
Distance, SimScale, LODLevel);
|
||||
|
||||
// Apply optimization settings based on component type and distance
|
||||
if (Component->IsSimulatingPhysics())
|
||||
{
|
||||
// Scale damping based on distance - higher damping for distant objects
|
||||
// helps reduce jitter and stabilize the simulation
|
||||
float ScaledLinearDamping = 0.1f * (1.0f + LODLevel * 0.5f);
|
||||
float ScaledAngularDamping = ScaledLinearDamping * 2.0f;
|
||||
|
||||
// Apply scaled damping
|
||||
Component->SetLinearDamping(ScaledLinearDamping);
|
||||
Component->SetAngularDamping(ScaledAngularDamping);
|
||||
|
||||
// Log the applied optimization settings
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applied physics optimization: LOD=%d, LinearDamping=%.2f, AngularDamping=%.2f"),
|
||||
LODLevel, ScaledLinearDamping, ScaledAngularDamping);
|
||||
|
||||
// In UE5.5.4, we can adjust physics settings through the component's properties
|
||||
// but we need to be careful about which methods are available
|
||||
|
||||
// In UE5.5.4, we need to use different methods to adjust physics settings
|
||||
// We'll focus on damping which is well-supported across UE versions
|
||||
|
||||
// For performance optimization, we'll adjust mass properties based on distance
|
||||
if (Component->IsSimulatingPhysics())
|
||||
{
|
||||
// Scale mass for distant objects to improve stability
|
||||
float BaseMass = 1.0f;
|
||||
float ScaledMass = BaseMass * (1.0f + LODLevel * 0.25f);
|
||||
|
||||
// Log the applied mass scaling
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applied mass scaling: LOD=%d, Mass=%.2f"),
|
||||
LODLevel, ScaledMass);
|
||||
|
||||
// For UE5.5.4, we use physics materials to affect physics simulation quality
|
||||
Component->SetPhysMaterialOverride(nullptr); // Reset to Default Physical Material
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset to default values for full simulation
|
||||
Component->SetLinearDamping(0.01f);
|
||||
Component->SetAngularDamping(0.0f);
|
||||
|
||||
// Reset physics material to default
|
||||
if (Component->IsSimulatingPhysics())
|
||||
{
|
||||
Component->SetPhysMaterialOverride(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply multithreaded settings
|
||||
// Note: This is usually a global setting in UE5.5.4
|
||||
if (Component->IsSimulatingPhysics())
|
||||
{
|
||||
// Use UPhysicsSettings to control global physics settings
|
||||
UPhysicsSettings* PhysicsSettings = UPhysicsSettings::Get();
|
||||
if (PhysicsSettings)
|
||||
{
|
||||
// In UE5.5.4, the API for multithreaded physics settings is different
|
||||
PhysicsSettings->bEnableEnhancedDeterminism = !bMultithreadedPhysics;
|
||||
// bEnableAsyncScene doesn't exist in UE5.5.4, use other settings instead
|
||||
PhysicsSettings->bDisableActiveActors = !bMultithreadedPhysics;
|
||||
PhysicsSettings->bDisableCCD = !bMultithreadedPhysics;
|
||||
|
||||
// Log the change
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Physics multithreading set to: %s"),
|
||||
bMultithreadedPhysics ? TEXT("Enabled") : TEXT("Disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply spatial partitioning optimization
|
||||
// In UE5.5.4, we use the available physics settings to approximate our desired behavior
|
||||
switch (SpatialPartitioningMethod)
|
||||
{
|
||||
case EFLESHSpatialPartitioning::Grid:
|
||||
// Apply grid-like partitioning by adjusting collision response
|
||||
if (Component->GetCollisionResponseToChannels() != ECR_Ignore)
|
||||
{
|
||||
// Optimize collision detection for grid-based partitioning
|
||||
Component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applied Grid spatial partitioning to %s"),
|
||||
*Component->GetName());
|
||||
}
|
||||
break;
|
||||
|
||||
case EFLESHSpatialPartitioning::Octree:
|
||||
// Apply octree-like optimizations
|
||||
// For octree, we want more precise collision detection but with hierarchical culling
|
||||
if (Component->GetCollisionResponseToChannels() != ECR_Ignore)
|
||||
{
|
||||
// Set continuous collision detection for important objects
|
||||
Component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
if (Distance < 300.0f) // Only use CCD for nearby objects
|
||||
{
|
||||
Component->SetUseCCD(true);
|
||||
}
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applied Octree spatial partitioning to %s"),
|
||||
*Component->GetName());
|
||||
}
|
||||
break;
|
||||
|
||||
case EFLESHSpatialPartitioning::Adaptive:
|
||||
// Apply adaptive partitioning based on distance
|
||||
if (Component->GetCollisionResponseToChannels() != ECR_Ignore)
|
||||
{
|
||||
// Use distance to determine collision detection method
|
||||
if (Distance < 200.0f)
|
||||
{
|
||||
// Full collision for nearby objects
|
||||
Component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
Component->SetUseCCD(true);
|
||||
}
|
||||
else if (Distance < 600.0f)
|
||||
{
|
||||
// Standard collision for medium distance
|
||||
Component->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
Component->SetUseCCD(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Query-only for distant objects
|
||||
Component->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||
}
|
||||
UE_LOG(LogFLESHPhysics, Verbose, TEXT("Applied Adaptive spatial partitioning to %s at distance %f"),
|
||||
*Component->GetName(), Distance);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
1354
Source/FLESH/Private/Physics/FLESHSoftBodySystem.cpp
Normal file
1354
Source/FLESH/Private/Physics/FLESHSoftBodySystem.cpp
Normal file
File diff suppressed because it is too large
Load Diff
676
Source/FLESH/Private/Tests/FLESHPerformanceTest.cpp
Normal file
676
Source/FLESH/Private/Tests/FLESHPerformanceTest.cpp
Normal file
@@ -0,0 +1,676 @@
|
||||
#include "Tests/FLESHPerformanceTest.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "HAL/PlatformTime.h"
|
||||
#include "HAL/PlatformProcess.h"
|
||||
#include "Misc/Paths.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category for the FLESH performance testing system
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHPerformance, Log, All);
|
||||
|
||||
/**
|
||||
* Constructor for the FLESH performance test system
|
||||
* Initializes default performance thresholds and test parameters
|
||||
*/
|
||||
UFLESHPerformanceTest::UFLESHPerformanceTest()
|
||||
{
|
||||
// Initialize default performance thresholds
|
||||
MaxExecutionTimeThresholdMs = 16.67f; // 60 FPS threshold (1000ms/60)
|
||||
MinFrameRateThreshold = 30.0f; // Minimum acceptable framerate
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the performance test system
|
||||
* Creates and configures required subsystems for testing
|
||||
*/
|
||||
void UFLESHPerformanceTest::Initialize()
|
||||
{
|
||||
// Create and initialize soft body simulation system if not already assigned
|
||||
if (!SoftBodySystem)
|
||||
{
|
||||
SoftBodySystem = NewObject<UFLESHSoftBodySystem>(this);
|
||||
SoftBodySystem->Initialize();
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Created default soft body system"));
|
||||
}
|
||||
|
||||
// Create physics optimizer and connect it to the soft body system
|
||||
if (!PhysicsOptimizer)
|
||||
{
|
||||
PhysicsOptimizer = NewObject<UFLESHPhysicsOptimizer>(this);
|
||||
SoftBodySystem->SetPhysicsOptimizer(PhysicsOptimizer);
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Created default physics optimizer"));
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Performance test system initialized"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a performance test with specified parameters
|
||||
* Measures execution time, memory usage, and other performance metrics
|
||||
*
|
||||
* @param TestType - The type of performance test to run
|
||||
* @param Iterations - Number of iterations to run the test
|
||||
* @param ComplexityLevel - Complexity level of the test (1-10)
|
||||
* @return Performance test results including execution time and memory usage
|
||||
*/
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::RunPerformanceTest(EFLESHPerformanceTestType TestType, int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
// Ensure all required systems are initialized before testing
|
||||
if (!SoftBodySystem || !PhysicsOptimizer)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
// Clamp complexity level to valid range (1-10)
|
||||
ComplexityLevel = FMath::Clamp(ComplexityLevel, 1, 10);
|
||||
|
||||
// Higher complexity levels create more particles/constraints for stress testing
|
||||
|
||||
// Run appropriate test
|
||||
FFLESHPerformanceResult Result;
|
||||
|
||||
switch (TestType)
|
||||
{
|
||||
case EFLESHPerformanceTestType::SoftBodySimulation:
|
||||
Result = TestSoftBodySimulation(Iterations, ComplexityLevel);
|
||||
break;
|
||||
|
||||
case EFLESHPerformanceTestType::BooleanCutting:
|
||||
Result = TestBooleanCutting(Iterations, ComplexityLevel);
|
||||
break;
|
||||
|
||||
case EFLESHPerformanceTestType::PhysicsOptimizer:
|
||||
Result = TestPhysicsOptimizer(Iterations, ComplexityLevel);
|
||||
break;
|
||||
|
||||
case EFLESHPerformanceTestType::Multithreading:
|
||||
Result = TestMultithreading(Iterations, ComplexityLevel);
|
||||
break;
|
||||
|
||||
case EFLESHPerformanceTestType::DistanceScaling:
|
||||
Result = TestDistanceScaling(Iterations, ComplexityLevel);
|
||||
break;
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Performance test completed: %s"), *Result.TestName);
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT(" Execution time: %.2f ms"), Result.ExecutionTimeMs);
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT(" Memory usage: %d KB"), Result.MemoryUsageKB);
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT(" Frames per second: %.2f"), Result.FramesPerSecond);
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT(" Test %s"), Result.bPassed ? TEXT("PASSED") : TEXT("FAILED"));
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Run all performance tests
|
||||
TArray<FFLESHPerformanceResult> UFLESHPerformanceTest::RunAllPerformanceTests(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
TArray<FFLESHPerformanceResult> Results;
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Running all performance tests with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Run each test type
|
||||
Results.Add(RunPerformanceTest(EFLESHPerformanceTestType::SoftBodySimulation, Iterations, ComplexityLevel));
|
||||
Results.Add(RunPerformanceTest(EFLESHPerformanceTestType::BooleanCutting, Iterations, ComplexityLevel));
|
||||
Results.Add(RunPerformanceTest(EFLESHPerformanceTestType::PhysicsOptimizer, Iterations, ComplexityLevel));
|
||||
Results.Add(RunPerformanceTest(EFLESHPerformanceTestType::Multithreading, Iterations, ComplexityLevel));
|
||||
Results.Add(RunPerformanceTest(EFLESHPerformanceTestType::DistanceScaling, Iterations, ComplexityLevel));
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("All performance tests completed"));
|
||||
|
||||
return Results;
|
||||
}
|
||||
|
||||
// Set soft body system
|
||||
void UFLESHPerformanceTest::SetSoftBodySystem(UFLESHSoftBodySystem* InSoftBodySystem)
|
||||
{
|
||||
if (InSoftBodySystem)
|
||||
{
|
||||
SoftBodySystem = InSoftBodySystem;
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Soft body system set"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Warning, TEXT("Attempted to set null soft body system"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get soft body system
|
||||
UFLESHSoftBodySystem* UFLESHPerformanceTest::GetSoftBodySystem() const
|
||||
{
|
||||
return SoftBodySystem;
|
||||
}
|
||||
|
||||
// Set physics optimizer
|
||||
void UFLESHPerformanceTest::SetPhysicsOptimizer(UFLESHPhysicsOptimizer* InPhysicsOptimizer)
|
||||
{
|
||||
if (InPhysicsOptimizer)
|
||||
{
|
||||
PhysicsOptimizer = InPhysicsOptimizer;
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Physics optimizer set"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Warning, TEXT("Attempted to set null physics optimizer"));
|
||||
}
|
||||
}
|
||||
|
||||
// Get physics optimizer
|
||||
UFLESHPhysicsOptimizer* UFLESHPerformanceTest::GetPhysicsOptimizer() const
|
||||
{
|
||||
return PhysicsOptimizer;
|
||||
}
|
||||
|
||||
// Set benchmark thresholds
|
||||
void UFLESHPerformanceTest::SetBenchmarkThresholds(float MaxExecutionTimeMs, float MinFramesPerSecond)
|
||||
{
|
||||
MaxExecutionTimeThresholdMs = MaxExecutionTimeMs;
|
||||
MinFrameRateThreshold = MinFramesPerSecond;
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Benchmark thresholds set: Max execution time = %.2f ms, Min frame rate = %.2f FPS"),
|
||||
MaxExecutionTimeThresholdMs, MinFrameRateThreshold);
|
||||
}
|
||||
|
||||
// Save results to CSV
|
||||
bool UFLESHPerformanceTest::SaveResultsToCSV(const TArray<FFLESHPerformanceResult>& Results, const FString& FilePath)
|
||||
{
|
||||
if (Results.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Warning, TEXT("No results to save"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create CSV header
|
||||
FString CSVContent = "TestName,ExecutionTimeMs,MemoryUsageKB,FramesPerSecond,VertexCount,TriangleCount,PhysicsObjectCount,ConstraintCount,Passed,AdditionalInfo\n";
|
||||
|
||||
// Add each result
|
||||
for (const FFLESHPerformanceResult& Result : Results)
|
||||
{
|
||||
CSVContent += FString::Printf(TEXT("%s,%.2f,%d,%.2f,%d,%d,%d,%d,%s,%s\n"),
|
||||
*Result.TestName,
|
||||
Result.ExecutionTimeMs,
|
||||
Result.MemoryUsageKB,
|
||||
Result.FramesPerSecond,
|
||||
Result.VertexCount,
|
||||
Result.TriangleCount,
|
||||
Result.PhysicsObjectCount,
|
||||
Result.ConstraintCount,
|
||||
Result.bPassed ? TEXT("TRUE") : TEXT("FALSE"),
|
||||
*Result.AdditionalInfo);
|
||||
}
|
||||
|
||||
// Save to file
|
||||
if (FFileHelper::SaveStringToFile(CSVContent, *FilePath))
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Results saved to %s"), *FilePath);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Error, TEXT("Failed to save results to %s"), *FilePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare results
|
||||
float UFLESHPerformanceTest::CompareResults(const FFLESHPerformanceResult& ResultA, const FFLESHPerformanceResult& ResultB)
|
||||
{
|
||||
if (ResultA.ExecutionTimeMs <= 0.0f || ResultB.ExecutionTimeMs <= 0.0f)
|
||||
{
|
||||
UE_LOG(LogFLESHPerformance, Warning, TEXT("Invalid execution times for comparison"));
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Calculate performance improvement percentage
|
||||
float ImprovementPercent = (ResultA.ExecutionTimeMs - ResultB.ExecutionTimeMs) / ResultA.ExecutionTimeMs * 100.0f;
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Performance comparison: %.2f%% improvement"), ImprovementPercent);
|
||||
|
||||
return ImprovementPercent;
|
||||
}
|
||||
|
||||
// Generate report
|
||||
FString UFLESHPerformanceTest::GenerateReport(const TArray<FFLESHPerformanceResult>& Results)
|
||||
{
|
||||
if (Results.Num() == 0)
|
||||
{
|
||||
return TEXT("No results to report");
|
||||
}
|
||||
|
||||
FString Report = TEXT("# FLESH Performance Test Report\n\n");
|
||||
|
||||
// Add timestamp
|
||||
FDateTime Now = FDateTime::Now();
|
||||
Report += FString::Printf(TEXT("Generated: %s\n\n"), *Now.ToString());
|
||||
|
||||
// Add system info
|
||||
Report += TEXT("## System Information\n\n");
|
||||
Report += FString::Printf(TEXT("- CPU: %s\n"), *FPlatformMisc::GetCPUBrand());
|
||||
Report += FString::Printf(TEXT("- CPU Cores: %d\n"), FPlatformMisc::NumberOfCores());
|
||||
Report += FString::Printf(TEXT("- Memory: %d MB\n"), FPlatformMemory::GetPhysicalGBRam() * 1024);
|
||||
Report += FString::Printf(TEXT("- OS: %s\n\n"), *FPlatformMisc::GetOSVersion());
|
||||
|
||||
// Add test results
|
||||
Report += TEXT("## Test Results\n\n");
|
||||
|
||||
int32 PassedTests = 0;
|
||||
|
||||
for (const FFLESHPerformanceResult& Result : Results)
|
||||
{
|
||||
Report += FString::Printf(TEXT("### %s\n\n"), *Result.TestName);
|
||||
Report += FString::Printf(TEXT("- Execution Time: %.2f ms\n"), Result.ExecutionTimeMs);
|
||||
Report += FString::Printf(TEXT("- Memory Usage: %d KB\n"), Result.MemoryUsageKB);
|
||||
Report += FString::Printf(TEXT("- Frames Per Second: %.2f\n"), Result.FramesPerSecond);
|
||||
Report += FString::Printf(TEXT("- Vertex Count: %d\n"), Result.VertexCount);
|
||||
Report += FString::Printf(TEXT("- Triangle Count: %d\n"), Result.TriangleCount);
|
||||
Report += FString::Printf(TEXT("- Physics Object Count: %d\n"), Result.PhysicsObjectCount);
|
||||
Report += FString::Printf(TEXT("- Constraint Count: %d\n"), Result.ConstraintCount);
|
||||
Report += FString::Printf(TEXT("- Status: %s\n"), Result.bPassed ? TEXT("PASSED") : TEXT("FAILED"));
|
||||
|
||||
if (!Result.AdditionalInfo.IsEmpty())
|
||||
{
|
||||
Report += FString::Printf(TEXT("- Additional Info: %s\n"), *Result.AdditionalInfo);
|
||||
}
|
||||
|
||||
Report += TEXT("\n");
|
||||
|
||||
if (Result.bPassed)
|
||||
{
|
||||
PassedTests++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add summary
|
||||
Report += TEXT("## Summary\n\n");
|
||||
Report += FString::Printf(TEXT("- Total Tests: %d\n"), Results.Num());
|
||||
Report += FString::Printf(TEXT("- Passed Tests: %d\n"), PassedTests);
|
||||
Report += FString::Printf(TEXT("- Failed Tests: %d\n"), Results.Num() - PassedTests);
|
||||
Report += FString::Printf(TEXT("- Success Rate: %.1f%%\n\n"), (float)PassedTests / (float)Results.Num() * 100.0f);
|
||||
|
||||
// Add recommendations
|
||||
Report += TEXT("## Recommendations\n\n");
|
||||
|
||||
if (PassedTests == Results.Num())
|
||||
{
|
||||
Report += TEXT("All tests passed. The system is performing optimally.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
Report += TEXT("Consider the following optimizations:\n\n");
|
||||
|
||||
for (const FFLESHPerformanceResult& Result : Results)
|
||||
{
|
||||
if (!Result.bPassed)
|
||||
{
|
||||
if (Result.TestName.Contains(TEXT("SoftBody")))
|
||||
{
|
||||
Report += TEXT("- Reduce the complexity of soft body simulations or use a more efficient simulation method.\n");
|
||||
}
|
||||
else if (Result.TestName.Contains(TEXT("Boolean")))
|
||||
{
|
||||
Report += TEXT("- Optimize boolean cutting operations or reduce their frequency.\n");
|
||||
}
|
||||
else if (Result.TestName.Contains(TEXT("Physics")))
|
||||
{
|
||||
Report += TEXT("- Adjust physics optimization settings for better performance.\n");
|
||||
}
|
||||
else if (Result.TestName.Contains(TEXT("Multithread")))
|
||||
{
|
||||
Report += TEXT("- Review multithreading implementation or increase thread count.\n");
|
||||
}
|
||||
else if (Result.TestName.Contains(TEXT("Distance")))
|
||||
{
|
||||
Report += TEXT("- Adjust distance scaling parameters for better performance.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Report;
|
||||
}
|
||||
|
||||
// Test soft body simulation
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::TestSoftBodySimulation(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
FFLESHPerformanceResult Result;
|
||||
Result.TestName = TEXT("Soft Body Simulation Test");
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Starting soft body simulation test with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Create test mesh
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
CreateTestMesh(ComplexityLevel, Vertices, Indices);
|
||||
|
||||
Result.VertexCount = Vertices.Num();
|
||||
Result.TriangleCount = Indices.Num() / 3;
|
||||
|
||||
// Configure soft body system
|
||||
SoftBodySystem->SetSimulationMethod(EFLESHSoftBodyMethod::Verlet);
|
||||
SoftBodySystem->SetSolverIterations(4);
|
||||
SoftBodySystem->SetTimeStep(1.0f / 60.0f);
|
||||
SoftBodySystem->SetStiffness(0.9f);
|
||||
SoftBodySystem->SetDamping(0.01f);
|
||||
|
||||
// Measure initial memory
|
||||
int32 InitialMemory = MeasureMemoryUsage();
|
||||
|
||||
// Start timing
|
||||
double StartTime = FPlatformTime::Seconds();
|
||||
|
||||
// Run simulation iterations
|
||||
for (int32 i = 0; i < Iterations; i++)
|
||||
{
|
||||
SoftBodySystem->UpdateSimulation(1.0f / 60.0f);
|
||||
}
|
||||
|
||||
// End timing
|
||||
double EndTime = FPlatformTime::Seconds();
|
||||
|
||||
// Calculate metrics
|
||||
Result.ExecutionTimeMs = (EndTime - StartTime) * 1000.0f;
|
||||
Result.MemoryUsageKB = MeasureMemoryUsage() - InitialMemory;
|
||||
Result.FramesPerSecond = CalculateFrameRate(Result.ExecutionTimeMs, Iterations);
|
||||
Result.PhysicsObjectCount = 1;
|
||||
Result.ConstraintCount = Vertices.Num() * 3; // Approximate constraint count
|
||||
|
||||
// Determine if test passed
|
||||
Result.bPassed = (Result.ExecutionTimeMs / Iterations <= MaxExecutionTimeThresholdMs) &&
|
||||
(Result.FramesPerSecond >= MinFrameRateThreshold);
|
||||
|
||||
Result.AdditionalInfo = FString::Printf(TEXT("Average time per iteration: %.3f ms"), Result.ExecutionTimeMs / Iterations);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Test boolean cutting
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::TestBooleanCutting(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
FFLESHPerformanceResult Result;
|
||||
Result.TestName = TEXT("Boolean Cutting Test");
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Starting boolean cutting test with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Create test mesh
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
CreateTestMesh(ComplexityLevel, Vertices, Indices);
|
||||
|
||||
Result.VertexCount = Vertices.Num();
|
||||
Result.TriangleCount = Indices.Num() / 3;
|
||||
|
||||
// Measure initial memory
|
||||
int32 InitialMemory = MeasureMemoryUsage();
|
||||
|
||||
// Start timing
|
||||
double StartTime = FPlatformTime::Seconds();
|
||||
|
||||
// Simulate boolean cutting operations
|
||||
// This is a simplified simulation since we don't have the actual BooleanCutTool available here
|
||||
for (int32 i = 0; i < Iterations; i++)
|
||||
{
|
||||
// Simulate a cut operation by modifying the mesh
|
||||
if (Vertices.Num() > 10 && Indices.Num() > 30)
|
||||
{
|
||||
// Remove some vertices and triangles to simulate cutting
|
||||
int32 RemoveCount = FMath::Min(5, Vertices.Num() / 10);
|
||||
Vertices.RemoveAt(Vertices.Num() - RemoveCount, RemoveCount);
|
||||
|
||||
int32 RemoveTriangles = FMath::Min(15, Indices.Num() / 10);
|
||||
Indices.RemoveAt(Indices.Num() - RemoveTriangles, RemoveTriangles);
|
||||
}
|
||||
}
|
||||
|
||||
// End timing
|
||||
double EndTime = FPlatformTime::Seconds();
|
||||
|
||||
// Calculate metrics
|
||||
Result.ExecutionTimeMs = (EndTime - StartTime) * 1000.0f;
|
||||
Result.MemoryUsageKB = MeasureMemoryUsage() - InitialMemory;
|
||||
Result.FramesPerSecond = CalculateFrameRate(Result.ExecutionTimeMs, Iterations);
|
||||
Result.PhysicsObjectCount = 1;
|
||||
Result.ConstraintCount = 0;
|
||||
|
||||
// Determine if test passed
|
||||
Result.bPassed = (Result.ExecutionTimeMs / Iterations <= MaxExecutionTimeThresholdMs * 2) &&
|
||||
(Result.FramesPerSecond >= MinFrameRateThreshold / 2);
|
||||
|
||||
Result.AdditionalInfo = FString::Printf(TEXT("Average time per cut: %.3f ms"), Result.ExecutionTimeMs / Iterations);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Test physics optimizer
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::TestPhysicsOptimizer(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
FFLESHPerformanceResult Result;
|
||||
Result.TestName = TEXT("Physics Optimizer Test");
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Starting physics optimizer test with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Create test mesh
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
CreateTestMesh(ComplexityLevel, Vertices, Indices);
|
||||
|
||||
Result.VertexCount = Vertices.Num();
|
||||
Result.TriangleCount = Indices.Num() / 3;
|
||||
|
||||
// Configure physics optimizer
|
||||
PhysicsOptimizer->SetOptimizationLevel(EFLESHPhysicsOptimizationLevel::Medium);
|
||||
// Fix API name mismatch
|
||||
PhysicsOptimizer->SetSpatialPartitioning(EFLESHSpatialPartitioning::Grid);
|
||||
|
||||
// Measure initial memory
|
||||
int32 InitialMemory = MeasureMemoryUsage();
|
||||
|
||||
// Start timing
|
||||
double StartTime = FPlatformTime::Seconds();
|
||||
|
||||
// Run optimization iterations
|
||||
for (int32 i = 0; i < Iterations; i++)
|
||||
{
|
||||
// Simulate distance-based optimization
|
||||
float Distance = FMath::RandRange(100.0f, 1000.0f);
|
||||
PhysicsOptimizer->GetSimulationScaleForDistance(Distance);
|
||||
|
||||
// Simulate LOD-based optimization
|
||||
int32 LOD = FMath::RandRange(0, 3);
|
||||
// GetSubstepCountForLOD method does not exist, use GetLODForDistance instead
|
||||
PhysicsOptimizer->GetLODForDistance(100.0f * LOD);
|
||||
}
|
||||
|
||||
// End timing
|
||||
double EndTime = FPlatformTime::Seconds();
|
||||
|
||||
// Calculate metrics
|
||||
Result.ExecutionTimeMs = (EndTime - StartTime) * 1000.0f;
|
||||
Result.MemoryUsageKB = MeasureMemoryUsage() - InitialMemory;
|
||||
Result.FramesPerSecond = CalculateFrameRate(Result.ExecutionTimeMs, Iterations);
|
||||
Result.PhysicsObjectCount = 10; // Simulated number of physics objects
|
||||
Result.ConstraintCount = 0;
|
||||
|
||||
// Determine if test passed
|
||||
Result.bPassed = (Result.ExecutionTimeMs / Iterations <= MaxExecutionTimeThresholdMs / 10) &&
|
||||
(Result.FramesPerSecond >= MinFrameRateThreshold * 10);
|
||||
|
||||
Result.AdditionalInfo = FString::Printf(TEXT("Average optimization time: %.3f ms"), Result.ExecutionTimeMs / Iterations);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Test multithreading
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::TestMultithreading(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
FFLESHPerformanceResult Result;
|
||||
Result.TestName = TEXT("Multithreading Test");
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Starting multithreading test with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Create test mesh
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
CreateTestMesh(ComplexityLevel, Vertices, Indices);
|
||||
|
||||
Result.VertexCount = Vertices.Num();
|
||||
Result.TriangleCount = Indices.Num() / 3;
|
||||
|
||||
// Measure initial memory
|
||||
int32 InitialMemory = MeasureMemoryUsage();
|
||||
|
||||
// Start timing
|
||||
double StartTime = FPlatformTime::Seconds();
|
||||
|
||||
// Simulate multithreaded operations
|
||||
// This is a simplified simulation since we don't have actual threading code here
|
||||
for (int32 i = 0; i < Iterations; i++)
|
||||
{
|
||||
// Simulate thread work by doing some calculations
|
||||
for (int32 j = 0; j < Vertices.Num(); j++)
|
||||
{
|
||||
Vertices[j] += FVector(0.01f, 0.01f, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
// End timing
|
||||
double EndTime = FPlatformTime::Seconds();
|
||||
|
||||
// Calculate metrics
|
||||
Result.ExecutionTimeMs = (EndTime - StartTime) * 1000.0f;
|
||||
Result.MemoryUsageKB = MeasureMemoryUsage() - InitialMemory;
|
||||
Result.FramesPerSecond = CalculateFrameRate(Result.ExecutionTimeMs, Iterations);
|
||||
Result.PhysicsObjectCount = 1;
|
||||
Result.ConstraintCount = 0;
|
||||
|
||||
// Determine if test passed
|
||||
Result.bPassed = (Result.ExecutionTimeMs / Iterations <= MaxExecutionTimeThresholdMs) &&
|
||||
(Result.FramesPerSecond >= MinFrameRateThreshold);
|
||||
|
||||
Result.AdditionalInfo = FString::Printf(TEXT("Average thread work time: %.3f ms"), Result.ExecutionTimeMs / Iterations);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Test distance scaling
|
||||
FFLESHPerformanceResult UFLESHPerformanceTest::TestDistanceScaling(int32 Iterations, int32 ComplexityLevel)
|
||||
{
|
||||
FFLESHPerformanceResult Result;
|
||||
Result.TestName = TEXT("Distance Scaling Test");
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Starting distance scaling test with %d iterations at complexity level %d"),
|
||||
Iterations, ComplexityLevel);
|
||||
|
||||
// Create test mesh
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Indices;
|
||||
CreateTestMesh(ComplexityLevel, Vertices, Indices);
|
||||
|
||||
Result.VertexCount = Vertices.Num();
|
||||
Result.TriangleCount = Indices.Num() / 3;
|
||||
|
||||
// Measure initial memory
|
||||
int32 InitialMemory = MeasureMemoryUsage();
|
||||
|
||||
// Start timing
|
||||
double StartTime = FPlatformTime::Seconds();
|
||||
|
||||
// Simulate distance scaling operations
|
||||
for (int32 i = 0; i < Iterations; i++)
|
||||
{
|
||||
// Simulate different distances
|
||||
float Distance = FMath::RandRange(100.0f, 5000.0f);
|
||||
float Scale = PhysicsOptimizer->GetSimulationScaleForDistance(Distance);
|
||||
|
||||
// Simulate applying the scale
|
||||
int32 VerticesToProcess = FMath::Max(1, FMath::FloorToInt(Vertices.Num() / Scale));
|
||||
|
||||
// Do some work on the scaled subset
|
||||
for (int32 j = 0; j < VerticesToProcess && j < Vertices.Num(); j++)
|
||||
{
|
||||
Vertices[j] += FVector(0.01f, 0.01f, 0.01f);
|
||||
}
|
||||
}
|
||||
|
||||
// End timing
|
||||
double EndTime = FPlatformTime::Seconds();
|
||||
|
||||
// Calculate metrics
|
||||
Result.ExecutionTimeMs = (EndTime - StartTime) * 1000.0f;
|
||||
Result.MemoryUsageKB = MeasureMemoryUsage() - InitialMemory;
|
||||
Result.FramesPerSecond = CalculateFrameRate(Result.ExecutionTimeMs, Iterations);
|
||||
Result.PhysicsObjectCount = 1;
|
||||
Result.ConstraintCount = 0;
|
||||
|
||||
// Determine if test passed
|
||||
Result.bPassed = (Result.ExecutionTimeMs / Iterations <= MaxExecutionTimeThresholdMs) &&
|
||||
(Result.FramesPerSecond >= MinFrameRateThreshold);
|
||||
|
||||
Result.AdditionalInfo = FString::Printf(TEXT("Average scaling operation time: %.3f ms"), Result.ExecutionTimeMs / Iterations);
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Create test mesh
|
||||
void UFLESHPerformanceTest::CreateTestMesh(int32 ComplexityLevel, TArray<FVector>& OutVertices, TArray<int32>& OutIndices)
|
||||
{
|
||||
// Clear output arrays
|
||||
OutVertices.Empty();
|
||||
OutIndices.Empty();
|
||||
|
||||
// Calculate mesh complexity based on level
|
||||
int32 VertexCount = 100 * ComplexityLevel;
|
||||
|
||||
// Create a simple sphere mesh
|
||||
for (int32 i = 0; i < VertexCount; i++)
|
||||
{
|
||||
// Generate random point on unit sphere
|
||||
float Theta = FMath::RandRange(0.0f, PI);
|
||||
float Phi = FMath::RandRange(0.0f, 2.0f * PI);
|
||||
|
||||
float X = FMath::Sin(Theta) * FMath::Cos(Phi);
|
||||
float Y = FMath::Sin(Theta) * FMath::Sin(Phi);
|
||||
float Z = FMath::Cos(Theta);
|
||||
|
||||
OutVertices.Add(FVector(X, Y, Z) * 100.0f);
|
||||
}
|
||||
|
||||
// Create triangles
|
||||
for (int32 i = 0; i < VertexCount - 2; i++)
|
||||
{
|
||||
OutIndices.Add(0);
|
||||
OutIndices.Add(i + 1);
|
||||
OutIndices.Add(i + 2);
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHPerformance, Log, TEXT("Created test mesh with %d vertices and %d triangles"),
|
||||
OutVertices.Num(), OutIndices.Num() / 3);
|
||||
}
|
||||
|
||||
// Measure memory usage
|
||||
int32 UFLESHPerformanceTest::MeasureMemoryUsage()
|
||||
{
|
||||
// Get current process memory info
|
||||
FPlatformMemoryStats MemStats = FPlatformMemory::GetStats();
|
||||
|
||||
// Return physical memory used in KB
|
||||
return MemStats.UsedPhysical / 1024;
|
||||
}
|
||||
|
||||
// Calculate frame rate
|
||||
float UFLESHPerformanceTest::CalculateFrameRate(float ExecutionTimeMs, int32 Iterations)
|
||||
{
|
||||
if (ExecutionTimeMs <= 0.0f || Iterations <= 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Calculate average time per iteration in seconds
|
||||
float TimePerIterationSec = (ExecutionTimeMs / 1000.0f) / Iterations;
|
||||
|
||||
// Calculate frames per second
|
||||
return TimePerIterationSec > 0.0f ? 1.0f / TimePerIterationSec : 0.0f;
|
||||
}
|
@@ -243,10 +243,10 @@ private:
|
||||
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);
|
||||
void CreateCapMesh(const TArray<FVector>& IntersectionPoints, const FCutPlane& CutPlane, TObjectPtr<UProceduralMeshComponent> TargetMesh);
|
||||
|
||||
// Internal function to find bone center and direction
|
||||
void GetBoneAxisInfo(USkeletalMesh* SkeletalMesh, FName BoneName, FVector& OutCenter, FVector& OutDirection);
|
||||
void GetBoneAxisInfo(TObjectPtr<USkeletalMesh> SkeletalMesh, FName BoneName, FVector& OutCenter, FVector& OutDirection);
|
||||
|
||||
// Internal function to create triangle fan from intersection points
|
||||
TArray<FVector> CreateTriangleFan(const TArray<FVector>& IntersectionPoints, const FVector& Center);
|
||||
|
@@ -1,87 +0,0 @@
|
||||
#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;
|
||||
};
|
@@ -1,143 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "BooleanCutTool.h"
|
||||
#include "DismembermentComponent.generated.h"
|
||||
|
||||
// Forward declarations
|
||||
class USplatterMapSystem;
|
||||
class UInternalOrganSystem;
|
||||
class UBloodSystem;
|
||||
|
||||
/**
|
||||
* Dismemberment component for the FLESH plugin
|
||||
* Provides a central control point for all dismemberment systems
|
||||
*/
|
||||
UCLASS(ClassGroup=(FLESH), meta=(BlueprintSpawnableComponent))
|
||||
class FLESH_API UDismembermentComponent : public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
UDismembermentComponent();
|
||||
|
||||
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 dismemberment at the specified location
|
||||
* @param CutPlane - Cut plane
|
||||
* @param BoneName - Optional bone name to guide the cut
|
||||
* @param bCreateCap - Whether to create a cap
|
||||
* @param CapMethod - Method to generate cap mesh
|
||||
* @return Whether the dismemberment was successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
bool PerformDismemberment(const FCutPlane& CutPlane, FName BoneName = NAME_None, bool bCreateCap = true, ECapMeshMethod CapMethod = ECapMeshMethod::TriangleFan);
|
||||
|
||||
/**
|
||||
* Perform multi-layer dismemberment at the specified location
|
||||
* @param CutPlane - Cut plane
|
||||
* @param bCreateCap - Whether to create a cap
|
||||
* @param CapMethod - Method to generate cap mesh
|
||||
* @return Whether the dismemberment was successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
bool PerformMultiLayerDismemberment(const FCutPlane& CutPlane, bool bCreateCap = true, ECapMeshMethod CapMethod = ECapMeshMethod::TriangleFan);
|
||||
|
||||
/**
|
||||
* Apply wound at the specified location
|
||||
* @param Location - World location of the wound
|
||||
* @param Normal - Surface normal at the wound location
|
||||
* @param Size - Size of the wound
|
||||
* @param Depth - Depth of the wound (0.0-1.0)
|
||||
* @param Bloodiness - Bloodiness of the wound (0.0-1.0)
|
||||
* @param Bruising - Bruising of the wound (0.0-1.0)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void ApplyWound(const FVector& Location, const FVector& Normal, float Size, float Depth = 1.0f, float Bloodiness = 1.0f, float Bruising = 0.5f);
|
||||
|
||||
/**
|
||||
* Get the boolean cut tool
|
||||
* @return The boolean cut tool
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
UBooleanCutTool* GetBooleanCutTool() const;
|
||||
|
||||
/**
|
||||
* Get the splatter map system
|
||||
* @return The splatter map system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
USplatterMapSystem* GetSplatterMapSystem() const;
|
||||
|
||||
/**
|
||||
* Get the internal organ system
|
||||
* @return The internal organ system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
UInternalOrganSystem* GetInternalOrganSystem() const;
|
||||
|
||||
/**
|
||||
* Get the blood system
|
||||
* @return The blood system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
UBloodSystem* GetBloodSystem() const;
|
||||
|
||||
/**
|
||||
* Set cut material
|
||||
* @param Material - Cut material
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetCutMaterial(UMaterialInterface* Material);
|
||||
|
||||
/**
|
||||
* Set inner material
|
||||
* @param Material - Inner material
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetInnerMaterial(UMaterialInterface* Material);
|
||||
|
||||
/**
|
||||
* Set cap mesh method
|
||||
* @param Method - Cap mesh method
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetCapMeshMethod(ECapMeshMethod Method);
|
||||
|
||||
private:
|
||||
// Boolean cut tool
|
||||
UPROPERTY()
|
||||
TObjectPtr<UBooleanCutTool> BooleanCutTool;
|
||||
|
||||
// Splatter map system
|
||||
UPROPERTY()
|
||||
TObjectPtr<USplatterMapSystem> SplatterMapSystem;
|
||||
|
||||
// Internal organ system
|
||||
UPROPERTY()
|
||||
TObjectPtr<UInternalOrganSystem> InternalOrganSystem;
|
||||
|
||||
// Blood system
|
||||
UPROPERTY()
|
||||
TObjectPtr<UBloodSystem> BloodSystem;
|
||||
|
||||
// Skeletal mesh component
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> SkeletalMeshComponent;
|
||||
|
||||
// Inner skeletal mesh component (for multi-layer dismemberment)
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> InnerSkeletalMeshComponent;
|
||||
|
||||
// Find skeletal mesh components
|
||||
void FindSkeletalMeshComponents();
|
||||
};
|
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
|
||||
// Forward declaration
|
||||
class UDismembermentGraphBase;
|
||||
|
||||
#include "DismembermentGraph/DismembermentGraphBase.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()
|
||||
TObjectPtr<class UDismembermentGraphBase> 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
|
||||
};
|
@@ -1,50 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "DismembermentGraphBase.generated.h"
|
||||
|
||||
/**
|
||||
* Dismemberment graph base class (Rewritten)
|
||||
* Provides extensible, multi-layer, and serializable node graph for procedural dismemberment logic
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class FLESH_API UDismembermentGraphBase : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UDismembermentGraphBase();
|
||||
|
||||
// All nodes in the graph (supports multiple node types)
|
||||
UPROPERTY(VisibleAnywhere, Instanced, Category = "Graph")
|
||||
TArray<TObjectPtr<class UEdGraphNode>> Nodes;
|
||||
|
||||
// Compilation status
|
||||
UPROPERTY(VisibleAnywhere, Category = "Graph")
|
||||
bool bCompiled;
|
||||
|
||||
// Multi-layer support: each layer can represent bone, organ, skin, etc.
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Graph|Layers")
|
||||
TArray<FName> Layers;
|
||||
|
||||
// Patch data for each layer (future extensibility)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Graph|Patch")
|
||||
TMap<FName, FString> LayerPatchData;
|
||||
|
||||
// Clear all nodes and reset graph
|
||||
UFUNCTION(BlueprintCallable, Category = "Graph")
|
||||
void ClearGraph();
|
||||
|
||||
// Add a node to the graph
|
||||
UFUNCTION(BlueprintCallable, Category = "Graph")
|
||||
class UEdGraphNode* AddNode(TSubclassOf<class UEdGraphNode> NodeClass, const FVector2D& Position);
|
||||
|
||||
// Remove a node from the graph
|
||||
UFUNCTION(BlueprintCallable, Category = "Graph")
|
||||
void RemoveNode(class UEdGraphNode* Node);
|
||||
|
||||
// Serialize graph to string (for asset versioning, debugging, etc.)
|
||||
UFUNCTION(BlueprintCallable, Category = "Graph")
|
||||
FString SerializeGraph() const;
|
||||
};
|
@@ -1,204 +1,91 @@
|
||||
// Copyright FLESH Project 2025. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "DismembermentSystem.generated.h"
|
||||
|
||||
// Forward declarations
|
||||
class USkeletalMeshComponent;
|
||||
class UProceduralMeshComponent;
|
||||
class UNiagaraSystem;
|
||||
class UMaterialInterface;
|
||||
class UNiagaraSystem;
|
||||
|
||||
/**
|
||||
* Bone dismemberment type enumeration
|
||||
* Core system for handling dismemberment functionality in the FLESH system
|
||||
* Provides utilities for cutting, tearing, and managing dismembered parts
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDismembermentType : uint8
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class FLESH_API UDismembermentSystem : public UObject
|
||||
{
|
||||
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()
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Sets default values for this component's properties
|
||||
UDismembermentSystem();
|
||||
UDismembermentSystem();
|
||||
|
||||
protected:
|
||||
// Called when the game starts
|
||||
virtual void BeginPlay() override;
|
||||
/**
|
||||
* Initialize the dismemberment system with a target mesh
|
||||
* @param InTargetMesh - Skeletal mesh to apply dismemberment to
|
||||
* @return True if initialization was successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
bool Initialize(USkeletalMeshComponent* InTargetMesh);
|
||||
|
||||
public:
|
||||
// Called every frame
|
||||
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
/**
|
||||
* Perform a cut at the specified transform
|
||||
* @param CutTransform - Transform 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 PerformCut(const FMatrix& CutTransform, float CutWidth = 1.0f, float CutDepth = 10.0f);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Set the material to use for cut surfaces
|
||||
* @param InCutMaterial - Material to use
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetCutMaterial(UMaterialInterface* InCutMaterial);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Set the blood effect to use for dismemberment
|
||||
* @param InBloodEffect - Niagara system to use for blood effects
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetBloodEffect(UNiagaraSystem* InBloodEffect);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Set whether to show internal organs
|
||||
* @param bInShowOrgans - Whether to show organs
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetShowOrgans(bool bInShowOrgans);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* Set whether to enable physics for dismembered parts
|
||||
* @param bInEnablePhysics - Whether to enable physics
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Dismemberment")
|
||||
void SetEnablePhysics(bool bInEnablePhysics);
|
||||
|
||||
private:
|
||||
// The skeletal mesh to perform dismemberment on
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> TargetMesh;
|
||||
// Target skeletal mesh component
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> TargetMesh;
|
||||
|
||||
// Map of bone names to their corresponding procedural mesh components
|
||||
UPROPERTY()
|
||||
TMap<FName, TObjectPtr<UProceduralMeshComponent>> DismemberedParts;
|
||||
// Material for cut surfaces
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInterface> CutMaterial;
|
||||
|
||||
// Registered bones for dismemberment
|
||||
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment")
|
||||
TArray<FDismembermentBoneData> RegisteredBones;
|
||||
// Blood effect
|
||||
UPROPERTY()
|
||||
TObjectPtr<UNiagaraSystem> BloodEffect;
|
||||
|
||||
// Niagara system for blood effects
|
||||
UPROPERTY(EditAnywhere, Category = "FLESH|Dismemberment")
|
||||
TObjectPtr<UNiagaraSystem> BloodEffectSystem;
|
||||
// Whether to show organs
|
||||
UPROPERTY()
|
||||
bool bShowOrgans;
|
||||
|
||||
// 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;
|
||||
// Whether to enable physics
|
||||
UPROPERTY()
|
||||
bool bEnablePhysics;
|
||||
};
|
||||
|
@@ -10,6 +10,20 @@
|
||||
class USkeletalMeshComponent;
|
||||
class UNiagaraSystem;
|
||||
class UMaterialInterface;
|
||||
class UDismembermentSystem;
|
||||
|
||||
/**
|
||||
* Dismemberment type enum
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDismembermentType : uint8
|
||||
{
|
||||
Cut UMETA(DisplayName = "Cut"), // Clean cut dismemberment
|
||||
Tear UMETA(DisplayName = "Tear"), // Torn flesh dismemberment
|
||||
Blast UMETA(DisplayName = "Blast"), // Explosion dismemberment
|
||||
Crush UMETA(DisplayName = "Crush"), // Crushing dismemberment
|
||||
Custom UMETA(DisplayName = "Custom") // Custom dismemberment type
|
||||
};
|
||||
|
||||
/**
|
||||
* Bone patch type for customizing dismemberment effects
|
||||
|
@@ -1,21 +1,12 @@
|
||||
// @ 2025, Copyright Virtuos Games. All rights reserved.
|
||||
// © 2021, Brock Marsh. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "NiagaraComponent.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "BloodPool.generated.h"
|
||||
|
||||
class UNiagaraSystem;
|
||||
class UNiagaraComponent;
|
||||
|
||||
/**
|
||||
* Blood pool actor that creates a decal and blood burst effect
|
||||
* Used to simulate blood pooling under dismembered body parts
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
UCLASS()
|
||||
class FLESH_API ABloodPool : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
@@ -26,59 +17,27 @@ public:
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Create blood pool
|
||||
* @param World - World object
|
||||
* @param Location - Blood pool location
|
||||
* @param Scale - Blood pool size
|
||||
* @param BloodPoolTemplate - Blood pool template class
|
||||
* @return Created blood pool Actor
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Dismemberment|Gore", meta = (WorldContext = "World"))
|
||||
static ABloodPool* CreateBloodPool(UWorld* World, const FVector& Location, float Scale = 1.0f, TSubclassOf<ABloodPool> BloodPoolTemplate = nullptr);
|
||||
|
||||
// Components
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
|
||||
UPROPERTY(EditAnywhere, Category="FLESH|Gore")
|
||||
TObjectPtr<class UBoxComponent> Collision;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
|
||||
UPROPERTY(EditAnywhere, Category="FLESH|Gore")
|
||||
TObjectPtr<class UDecalComponent> Decal;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components")
|
||||
TObjectPtr<UNiagaraComponent> BloodBurstEffect;
|
||||
|
||||
protected:
|
||||
FVector RemapSizeForDecal(const FVector In) const;
|
||||
|
||||
public:
|
||||
// Visual Effects
|
||||
/** The blood decal material to use */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Visual Effects", meta=(DisplayPriority=1))
|
||||
TObjectPtr<UMaterialInterface> DecalMaterial = nullptr;
|
||||
|
||||
/** The blood burst Niagara system to use */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Visual Effects", meta=(DisplayPriority=2))
|
||||
TObjectPtr<UNiagaraSystem> BloodBurstSystem;
|
||||
|
||||
// Timing Parameters
|
||||
/** Adds a delay before the blood decal appears */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Timing", meta=(ExposeOnSpawn="true", ClampMin="0.0", UIMin="0.0", DisplayPriority=1))
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta=(ExposeOnSpawn="true", ToolTip="Adds a Delay before the Blood Decal Splatters"))
|
||||
float StartDelay = 0.f;
|
||||
|
||||
/** The lifetime of the blood before it dries up and fades away */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Timing", meta=(ExposeOnSpawn="true", ClampMin="1.0", UIMin="1.0", DisplayPriority=2))
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta=(ExposeOnSpawn="true", ToolTip="The Lifetime of the blood before it dries up and fades away"))
|
||||
float MaxLifetime = 60.f;
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta=(ExposeOnSpawn="true", ToolTip="This is a wrapper for Decal Size"))
|
||||
FVector DecalSize = FVector(100);
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta=(ExposeOnSpawn="true", ToolTip="This can be used to set a relative rotation of the Decal"))
|
||||
FRotator DecalRotation = FRotator(0);
|
||||
|
||||
/** Time it takes to interpolate to the new size and location */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Timing", meta=(ExposeOnSpawn="true", ClampMin="0.0", UIMin="0.0", DisplayPriority=3))
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta=(ExposeOnSpawn="true", ToolTip="This is the Time it will take in seconds to Lerp to the new Size and Location"))
|
||||
float InterpTime = 0.3f;
|
||||
|
||||
// Appearance Parameters
|
||||
/** Size of the blood decal */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Appearance", meta=(ExposeOnSpawn="true", DisplayPriority=1))
|
||||
FVector DecalSize = FVector(100);
|
||||
|
||||
/** Relative rotation of the decal */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Dismemberment|Appearance", meta=(ExposeOnSpawn="true", DisplayPriority=2))
|
||||
FRotator DecalRotation = FRotator(0);
|
||||
UPROPERTY(BlueprintReadWrite, Category="FLESH|Gore", meta = (ExposeOnSpawn="true"))
|
||||
TObjectPtr<UMaterialInterface> DecalMaterial = nullptr;
|
||||
};
|
||||
|
190
Source/FLESH/Public/Physics/FLESHPhysicsOptimizer.h
Normal file
190
Source/FLESH/Public/Physics/FLESHPhysicsOptimizer.h
Normal file
@@ -0,0 +1,190 @@
|
||||
// FLESH Physics Optimizer
|
||||
// Provides optimization techniques for soft body physics simulation
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "FLESHPhysicsOptimizer.generated.h"
|
||||
|
||||
/**
|
||||
* Optimization level for physics simulation
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHPhysicsOptimizationLevel : uint8
|
||||
{
|
||||
// No optimization, full simulation
|
||||
None UMETA(DisplayName = "None"),
|
||||
|
||||
// Low optimization, good quality
|
||||
Low UMETA(DisplayName = "Low"),
|
||||
|
||||
// Medium optimization, balanced
|
||||
Medium UMETA(DisplayName = "Medium"),
|
||||
|
||||
// High optimization, performance focused
|
||||
High UMETA(DisplayName = "High"),
|
||||
|
||||
// Extreme optimization, for very low-end devices
|
||||
Extreme UMETA(DisplayName = "Extreme")
|
||||
};
|
||||
|
||||
/**
|
||||
* Spatial partitioning method
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHSpatialPartitioning : uint8
|
||||
{
|
||||
// No spatial partitioning
|
||||
None UMETA(DisplayName = "None"),
|
||||
|
||||
// Grid-based partitioning
|
||||
Grid UMETA(DisplayName = "Grid"),
|
||||
|
||||
// Octree partitioning
|
||||
Octree UMETA(DisplayName = "Octree"),
|
||||
|
||||
// Adaptive partitioning based on density
|
||||
Adaptive UMETA(DisplayName = "Adaptive")
|
||||
};
|
||||
|
||||
/**
|
||||
* Physics Optimizer class
|
||||
* Provides optimization techniques for FLESH physics simulation
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class FLESH_API UFLESHPhysicsOptimizer : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHPhysicsOptimizer();
|
||||
|
||||
/**
|
||||
* Set optimization level
|
||||
* @param Level - Optimization level
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetOptimizationLevel(EFLESHPhysicsOptimizationLevel Level);
|
||||
|
||||
/**
|
||||
* Get optimization level
|
||||
* @return Current optimization level
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
EFLESHPhysicsOptimizationLevel GetOptimizationLevel() const;
|
||||
|
||||
/**
|
||||
* Set spatial partitioning method
|
||||
* @param Method - Spatial partitioning method
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetSpatialPartitioning(EFLESHSpatialPartitioning Method);
|
||||
|
||||
/**
|
||||
* Get spatial partitioning method
|
||||
* @return Current spatial partitioning method
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
EFLESHSpatialPartitioning GetSpatialPartitioning() const;
|
||||
|
||||
/**
|
||||
* Set LOD distance thresholds
|
||||
* @param Distances - Array of distances for each LOD level
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetLODDistances(const TArray<float>& Distances);
|
||||
|
||||
/**
|
||||
* Get LOD level for distance
|
||||
* @param Distance - Distance from camera
|
||||
* @return LOD level (0 = highest detail)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
int32 GetLODForDistance(float Distance) const;
|
||||
|
||||
/**
|
||||
* Set maximum number of physics substeps
|
||||
* @param MaxSteps - Maximum number of substeps
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetMaxSubsteps(int32 MaxSteps);
|
||||
|
||||
/**
|
||||
* Get maximum number of physics substeps
|
||||
* @return Maximum number of substeps
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
int32 GetMaxSubsteps() const;
|
||||
|
||||
/**
|
||||
* Enable/disable multithreaded physics
|
||||
* @param bEnable - Whether to enable multithreaded physics
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetMultithreadedPhysics(bool bEnable);
|
||||
|
||||
/**
|
||||
* Check if multithreaded physics is enabled
|
||||
* @return Whether multithreaded physics is enabled
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
bool IsMultithreadedPhysicsEnabled() const;
|
||||
|
||||
/**
|
||||
* Set distance-based simulation scale
|
||||
* @param MinDistance - Minimum distance for full simulation
|
||||
* @param MaxDistance - Maximum distance for reduced simulation
|
||||
* @param MinScale - Simulation scale at max distance (0-1)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void SetDistanceBasedSimulationScale(float MinDistance, float MaxDistance, float MinScale);
|
||||
|
||||
/**
|
||||
* Get simulation scale for distance
|
||||
* @param Distance - Distance from camera
|
||||
* @return Simulation scale (0-1)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
float GetSimulationScaleForDistance(float Distance) const;
|
||||
|
||||
/**
|
||||
* Apply optimization settings to physics component
|
||||
* @param Component - Physics component to optimize
|
||||
* @param Distance - Distance from camera
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|Optimization")
|
||||
void ApplyOptimizationToComponent(UPrimitiveComponent* Component, float Distance);
|
||||
|
||||
private:
|
||||
// Current optimization level
|
||||
UPROPERTY()
|
||||
EFLESHPhysicsOptimizationLevel OptimizationLevel;
|
||||
|
||||
// Current spatial partitioning method
|
||||
UPROPERTY()
|
||||
EFLESHSpatialPartitioning SpatialPartitioningMethod;
|
||||
|
||||
// LOD distance thresholds
|
||||
UPROPERTY()
|
||||
TArray<float> LODDistances;
|
||||
|
||||
// Maximum number of physics substeps
|
||||
UPROPERTY()
|
||||
int32 MaxSubsteps;
|
||||
|
||||
// Whether multithreaded physics is enabled
|
||||
UPROPERTY()
|
||||
bool bMultithreadedPhysics;
|
||||
|
||||
// Distance-based simulation parameters
|
||||
UPROPERTY()
|
||||
float MinSimulationDistance;
|
||||
|
||||
UPROPERTY()
|
||||
float MaxSimulationDistance;
|
||||
|
||||
UPROPERTY()
|
||||
float MinSimulationScale;
|
||||
};
|
513
Source/FLESH/Public/Physics/FLESHSoftBodySystem.h
Normal file
513
Source/FLESH/Public/Physics/FLESHSoftBodySystem.h
Normal file
@@ -0,0 +1,513 @@
|
||||
// FLESH Soft Body Physics System
|
||||
// 提供高性能的软体物理模拟系统
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "Physics/FLESHPhysicsOptimizer.h"
|
||||
#include "FLESHSoftBodySystem.generated.h"
|
||||
|
||||
// Forward declarations
|
||||
class UStaticMeshComponent;
|
||||
class USkeletalMeshComponent;
|
||||
class UProceduralMeshComponent;
|
||||
|
||||
/**
|
||||
* Soft body simulation method
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHSoftBodyMethod : uint8
|
||||
{
|
||||
// Verlet integration
|
||||
Verlet UMETA(DisplayName = "Verlet Integration"),
|
||||
|
||||
// Position-based dynamics
|
||||
PositionBased UMETA(DisplayName = "Position Based Dynamics"),
|
||||
|
||||
// Mass-spring system
|
||||
MassSpring UMETA(DisplayName = "Mass-Spring System"),
|
||||
|
||||
// Finite element method
|
||||
FiniteElement UMETA(DisplayName = "Finite Element Method")
|
||||
};
|
||||
|
||||
/**
|
||||
* Soft body mesh type
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHSoftBodyMeshType : uint8
|
||||
{
|
||||
Tetrahedral UMETA(DisplayName = "Tetrahedral"),
|
||||
Hexahedral UMETA(DisplayName = "Hexahedral"),
|
||||
Surface UMETA(DisplayName = "Surface"),
|
||||
Chain UMETA(DisplayName = "Chain")
|
||||
};
|
||||
|
||||
/**
|
||||
* LOD level for soft body simulation
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHSoftBodyLOD : uint8
|
||||
{
|
||||
LOD0 UMETA(DisplayName = "High Detail"), // Full simulation
|
||||
LOD1 UMETA(DisplayName = "Medium Detail"), // Reduced particle count
|
||||
LOD2 UMETA(DisplayName = "Low Detail"), // Minimal particle count
|
||||
LOD3 UMETA(DisplayName = "Simplified") // Extremely simplified
|
||||
};
|
||||
|
||||
// Soft body particle
|
||||
struct FSoftBodyParticle
|
||||
{
|
||||
FVector Position;
|
||||
FVector OldPosition;
|
||||
FVector Velocity;
|
||||
FVector Force;
|
||||
float Mass;
|
||||
float InvMass; // 1.0f / Mass, precomputed for efficiency
|
||||
bool bIsFixed;
|
||||
int32 LODLevel; // Which LOD level this particle belongs to
|
||||
|
||||
FSoftBodyParticle()
|
||||
: Position(FVector::ZeroVector)
|
||||
, OldPosition(FVector::ZeroVector)
|
||||
, Velocity(FVector::ZeroVector)
|
||||
, Force(FVector::ZeroVector)
|
||||
, Mass(1.0f)
|
||||
, InvMass(1.0f)
|
||||
, bIsFixed(false)
|
||||
, LODLevel(0)
|
||||
{
|
||||
}
|
||||
|
||||
FSoftBodyParticle(const FVector& InPosition, float InMass = 1.0f, bool bInIsFixed = false, int32 InLODLevel = 0)
|
||||
: Position(InPosition)
|
||||
, OldPosition(InPosition)
|
||||
, Velocity(FVector::ZeroVector)
|
||||
, Force(FVector::ZeroVector)
|
||||
, Mass(InMass)
|
||||
, InvMass(InMass > SMALL_NUMBER ? 1.0f / InMass : 0.0f)
|
||||
, bIsFixed(bInIsFixed)
|
||||
, LODLevel(InLODLevel)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Soft body physics system
|
||||
* Provides high-performance soft body physics simulation
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class FLESH_API UFLESHSoftBodySystem : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHSoftBodySystem();
|
||||
|
||||
/**
|
||||
* Initialize soft body system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void Initialize();
|
||||
|
||||
/**
|
||||
* Set physics optimizer
|
||||
* @param Optimizer - Physics optimizer
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetPhysicsOptimizer(UFLESHPhysicsOptimizer* Optimizer);
|
||||
|
||||
/**
|
||||
* Get physics optimizer
|
||||
* @return Current physics optimizer
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
UFLESHPhysicsOptimizer* GetPhysicsOptimizer() const;
|
||||
|
||||
/**
|
||||
* Set soft body simulation method
|
||||
* @param Method - Simulation method
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetSimulationMethod(EFLESHSoftBodyMethod Method);
|
||||
|
||||
/**
|
||||
* Get soft body simulation method
|
||||
* @return Current simulation method
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
EFLESHSoftBodyMethod GetSimulationMethod() const;
|
||||
|
||||
/**
|
||||
* Set solver iterations
|
||||
* @param Iterations - Number of solver iterations
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetSolverIterations(int32 Iterations);
|
||||
|
||||
/**
|
||||
* Get solver iterations
|
||||
* @return Current solver iterations
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
int32 GetSolverIterations() const;
|
||||
|
||||
/**
|
||||
* Set time step
|
||||
* @param TimeStep - Time step
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetTimeStep(float TimeStep);
|
||||
|
||||
/**
|
||||
* Get time step
|
||||
* @return Current time step
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetTimeStep() const;
|
||||
|
||||
/**
|
||||
* Create soft body from static mesh
|
||||
* @param MeshComponent - Static mesh component
|
||||
* @param MeshType - Soft body mesh type
|
||||
* @param Resolution - Mesh resolution
|
||||
* @return Whether successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
bool CreateSoftBodyFromStaticMesh(UStaticMeshComponent* MeshComponent, EFLESHSoftBodyMeshType MeshType, int32 Resolution = 8);
|
||||
|
||||
/**
|
||||
* Create soft body from skeletal mesh
|
||||
* @param MeshComponent - Skeletal mesh component
|
||||
* @param MeshType - Soft body mesh type
|
||||
* @param Resolution - Mesh resolution
|
||||
* @return Whether successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
bool CreateSoftBodyFromSkeletalMesh(USkeletalMeshComponent* MeshComponent, EFLESHSoftBodyMeshType MeshType, int32 Resolution = 8);
|
||||
|
||||
/**
|
||||
* Create soft body from procedural mesh
|
||||
* @param MeshComponent - Procedural mesh component
|
||||
* @param MeshType - Soft body mesh type
|
||||
* @return Whether successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
bool CreateSoftBodyFromProceduralMesh(UProceduralMeshComponent* MeshComponent, EFLESHSoftBodyMeshType MeshType);
|
||||
|
||||
/**
|
||||
* Set gravity
|
||||
* @param Gravity - Gravity vector
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetGravity(const FVector& Gravity);
|
||||
|
||||
/**
|
||||
* Get gravity
|
||||
* @return Current gravity vector
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
FVector GetGravity() const;
|
||||
|
||||
/**
|
||||
* Set damping
|
||||
* @param Damping - Damping coefficient (0-1)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetDamping(float Damping);
|
||||
|
||||
/**
|
||||
* Get damping
|
||||
* @return Current damping coefficient
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetDamping() const;
|
||||
|
||||
/**
|
||||
* Set stiffness
|
||||
* @param Stiffness - Stiffness coefficient (0-1)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetStiffness(float Stiffness);
|
||||
|
||||
/**
|
||||
* Get stiffness
|
||||
* @return Current stiffness coefficient
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetStiffness() const;
|
||||
|
||||
/**
|
||||
* Set volume stiffness
|
||||
* @param VolumeStiffness - Volume stiffness coefficient (0-1)
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetVolumeStiffness(float VolumeStiffness);
|
||||
|
||||
/**
|
||||
* Get volume stiffness
|
||||
* @return Current volume stiffness coefficient
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetVolumeStiffness() const;
|
||||
|
||||
/**
|
||||
* Set pressure
|
||||
* @param Pressure - Pressure coefficient
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetPressure(float Pressure);
|
||||
|
||||
/**
|
||||
* Get pressure
|
||||
* @return Current pressure coefficient
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetPressure() const;
|
||||
|
||||
/**
|
||||
* Add collision plane
|
||||
* @param Location - Plane location
|
||||
* @param Normal - Plane normal
|
||||
* @return Plane ID
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
int32 AddCollisionPlane(const FVector& Location, const FVector& Normal);
|
||||
|
||||
/**
|
||||
* Remove collision plane
|
||||
* @param PlaneID - Plane ID
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void RemoveCollisionPlane(int32 PlaneID);
|
||||
|
||||
/**
|
||||
* Add anchor point
|
||||
* @param VertexIndex - Vertex index
|
||||
* @param Location - Anchor location
|
||||
* @param Stiffness - Anchor stiffness (0-1)
|
||||
* @return Anchor ID
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
int32 AddAnchorPoint(int32 VertexIndex, const FVector& Location, float Stiffness = 1.0f);
|
||||
|
||||
/**
|
||||
* Remove anchor point
|
||||
* @param AnchorID - Anchor ID
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void RemoveAnchorPoint(int32 AnchorID);
|
||||
|
||||
/**
|
||||
* Update simulation
|
||||
* @param DeltaTime - Time delta
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void UpdateSimulation(float DeltaTime);
|
||||
|
||||
/**
|
||||
* Enable/disable adaptive time step
|
||||
* @param bEnable - Whether to enable
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetAdaptiveTimeStep(bool bEnable);
|
||||
|
||||
/**
|
||||
* Check if adaptive time step is enabled
|
||||
* @return Whether adaptive time step is enabled
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
bool IsAdaptiveTimeStepEnabled() const;
|
||||
|
||||
/**
|
||||
* Enable/disable continuous collision detection
|
||||
* @param bEnable - Whether to enable
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetContinuousCollisionDetection(bool bEnable);
|
||||
|
||||
/**
|
||||
* Check if continuous collision detection is enabled
|
||||
* @return Whether continuous collision detection is enabled
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
bool IsContinuousCollisionDetectionEnabled() const;
|
||||
|
||||
/**
|
||||
* Set collision margin
|
||||
* @param Margin - Collision margin
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetCollisionMargin(float Margin);
|
||||
|
||||
/**
|
||||
* Get collision margin
|
||||
* @return Current collision margin
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetCollisionMargin() const;
|
||||
|
||||
/**
|
||||
* Set maximum velocity
|
||||
* @param MaxVelocity - Maximum velocity
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void SetMaxVelocity(float MaxVelocity);
|
||||
|
||||
/**
|
||||
* Get maximum velocity
|
||||
* @return Current maximum velocity
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
float GetMaxVelocity() const;
|
||||
|
||||
/**
|
||||
* Apply impulse
|
||||
* @param Location - Impulse location
|
||||
* @param Force - Impulse force
|
||||
* @param Radius - Radius of impact
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void ApplyImpulse(const FVector& Location, const FVector& Force, float Radius);
|
||||
|
||||
/**
|
||||
* Apply wind field
|
||||
* @param Direction - Wind direction
|
||||
* @param Strength - Wind strength
|
||||
* @param Turbulence - Turbulence
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Physics|SoftBody")
|
||||
void ApplyWindField(const FVector& Direction, float Strength, float Turbulence);
|
||||
|
||||
private:
|
||||
// Physics optimizer
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHPhysicsOptimizer> PhysicsOptimizer;
|
||||
|
||||
// Simulation method
|
||||
UPROPERTY()
|
||||
EFLESHSoftBodyMethod SimulationMethod;
|
||||
|
||||
// Solver iterations
|
||||
UPROPERTY()
|
||||
int32 SolverIterations;
|
||||
|
||||
// Time step
|
||||
UPROPERTY()
|
||||
float TimeStep;
|
||||
|
||||
// Gravity
|
||||
UPROPERTY()
|
||||
FVector Gravity;
|
||||
|
||||
// Damping
|
||||
UPROPERTY()
|
||||
float Damping;
|
||||
|
||||
// Stiffness
|
||||
UPROPERTY()
|
||||
float Stiffness;
|
||||
|
||||
// Volume stiffness
|
||||
UPROPERTY()
|
||||
float VolumeStiffness;
|
||||
|
||||
// Pressure
|
||||
UPROPERTY()
|
||||
float Pressure;
|
||||
|
||||
// Collision margin
|
||||
UPROPERTY()
|
||||
float CollisionMargin;
|
||||
|
||||
// Maximum velocity
|
||||
UPROPERTY()
|
||||
float MaxVelocity;
|
||||
|
||||
// Adaptive time step
|
||||
UPROPERTY()
|
||||
bool bAdaptiveTimeStep;
|
||||
|
||||
// Continuous collision detection
|
||||
UPROPERTY()
|
||||
bool bContinuousCollisionDetection;
|
||||
|
||||
// Enable adaptive LOD system
|
||||
UPROPERTY()
|
||||
bool bEnableAdaptiveLOD;
|
||||
|
||||
// LOD distance thresholds
|
||||
UPROPERTY()
|
||||
TArray<float> LODDistanceThresholds;
|
||||
|
||||
// Particle array
|
||||
TArray<FSoftBodyParticle> Particles;
|
||||
|
||||
// Constraint array (for distance constraints)
|
||||
TArray<TPair<int32, int32>> DistanceConstraints;
|
||||
|
||||
// Rest lengths for distance constraints
|
||||
TArray<float> RestLengths;
|
||||
|
||||
// Internal method: Create tetrahedral mesh
|
||||
void CreateTetrahedralMesh(const TArray<FVector>& Vertices, const TArray<int32>& Indices, int32 Resolution);
|
||||
|
||||
// Internal method: Create hexahedral mesh
|
||||
void CreateHexahedralMesh(const TArray<FVector>& Vertices, const TArray<int32>& Indices, int32 Resolution);
|
||||
|
||||
// Internal method: Create surface mesh
|
||||
void CreateSurfaceMesh(const TArray<FVector>& Vertices, const TArray<int32>& Indices);
|
||||
|
||||
// Internal method: Create chain mesh
|
||||
void CreateChainMesh(const TArray<FVector>& Vertices, const TArray<int32>& Indices);
|
||||
|
||||
// Set adaptive LOD
|
||||
void SetAdaptiveLOD(bool bEnable);
|
||||
|
||||
// Check if adaptive LOD is enabled
|
||||
bool IsAdaptiveLODEnabled() const;
|
||||
|
||||
// Set LOD distance thresholds
|
||||
void SetLODDistanceThresholds(const TArray<float>& Distances);
|
||||
|
||||
// Get LOD level for distance
|
||||
EFLESHSoftBodyLOD GetLODForDistance(float Distance) const;
|
||||
|
||||
// Update particle LOD levels based on camera distance
|
||||
void UpdateParticleLODs(const FVector& CameraPosition);
|
||||
|
||||
// Create particles with LOD levels
|
||||
void CreateParticlesWithLOD(const TArray<FVector>& Positions, int32 BaseLODLevel = 0);
|
||||
|
||||
// Internal method: Verlet integration
|
||||
void SolveVerlet(float DeltaTime);
|
||||
|
||||
// Internal method: Position-based constraints
|
||||
void SolvePositionBased(float DeltaTime);
|
||||
|
||||
// Internal method: Spring mass system
|
||||
void SolveMassSpring(float DeltaTime);
|
||||
|
||||
// Internal method: Finite element method
|
||||
void SolveFiniteElement(float DeltaTime);
|
||||
|
||||
// Internal method: Apply collisions
|
||||
void SolveCollisions();
|
||||
|
||||
// Internal method: Apply distance constraints
|
||||
void SolveDistanceConstraints();
|
||||
|
||||
// Internal method: Apply volume constraints
|
||||
void SolveVolumeConstraints();
|
||||
|
||||
// Internal method: Apply shape matching constraints
|
||||
void SolveShapeMatchingConstraints();
|
||||
|
||||
// Internal method: Apply boundary constraints
|
||||
void SolveBoundaryConstraints();
|
||||
|
||||
// Internal method: Update mesh
|
||||
void UpdateMesh();
|
||||
};
|
235
Source/FLESH/Public/Tests/FLESHPerformanceTest.h
Normal file
235
Source/FLESH/Public/Tests/FLESHPerformanceTest.h
Normal file
@@ -0,0 +1,235 @@
|
||||
// FLESH Performance Test System
|
||||
// Provides performance testing and benchmarking functionality
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "Physics/FLESHSoftBodySystem.h"
|
||||
#include "Physics/FLESHPhysicsOptimizer.h"
|
||||
#include "FLESHPerformanceTest.generated.h"
|
||||
|
||||
/**
|
||||
* Performance test types
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHPerformanceTestType : uint8
|
||||
{
|
||||
// Soft body simulation performance test
|
||||
SoftBodySimulation UMETA(DisplayName = "Soft Body Simulation"),
|
||||
|
||||
// Boolean cutting performance test
|
||||
BooleanCutting UMETA(DisplayName = "Boolean Cutting"),
|
||||
|
||||
// Physics optimizer performance test
|
||||
PhysicsOptimizer UMETA(DisplayName = "Physics Optimizer"),
|
||||
|
||||
// Multithreading performance test
|
||||
Multithreading UMETA(DisplayName = "Multithreading"),
|
||||
|
||||
// Distance scaling performance test
|
||||
DistanceScaling UMETA(DisplayName = "Distance Scaling")
|
||||
};
|
||||
|
||||
/**
|
||||
* Performance test result
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FFLESHPerformanceResult
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Test name
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
FString TestName;
|
||||
|
||||
// Execution time (milliseconds)
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
float ExecutionTimeMs;
|
||||
|
||||
// Memory usage (KB)
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
int32 MemoryUsageKB;
|
||||
|
||||
// Frames per second
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
float FramesPerSecond;
|
||||
|
||||
// Number of vertices processed
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
int32 VertexCount;
|
||||
|
||||
// Number of triangles processed
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
int32 TriangleCount;
|
||||
|
||||
// Number of physics objects processed
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
int32 PhysicsObjectCount;
|
||||
|
||||
// Number of constraints processed
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
int32 ConstraintCount;
|
||||
|
||||
// Test passed/failed
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
bool bPassed;
|
||||
|
||||
// Additional information
|
||||
UPROPERTY(BlueprintReadOnly, Category = "FLESH|Performance")
|
||||
FString AdditionalInfo;
|
||||
|
||||
FFLESHPerformanceResult()
|
||||
: ExecutionTimeMs(0.0f)
|
||||
, MemoryUsageKB(0)
|
||||
, FramesPerSecond(0.0f)
|
||||
, VertexCount(0)
|
||||
, TriangleCount(0)
|
||||
, PhysicsObjectCount(0)
|
||||
, ConstraintCount(0)
|
||||
, bPassed(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Performance test system
|
||||
* Provides performance testing and benchmarking functionality
|
||||
*/
|
||||
UCLASS(BlueprintType, Blueprintable)
|
||||
class FLESH_API UFLESHPerformanceTest : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHPerformanceTest();
|
||||
|
||||
/**
|
||||
* Initialize performance test system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
void Initialize();
|
||||
|
||||
/**
|
||||
* Run performance test
|
||||
* @param TestType - Test type
|
||||
* @param Iterations - Number of iterations
|
||||
* @param ComplexityLevel - Complexity level (1-10)
|
||||
* @return Test result
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
FFLESHPerformanceResult RunPerformanceTest(EFLESHPerformanceTestType TestType, int32 Iterations = 100, int32 ComplexityLevel = 5);
|
||||
|
||||
/**
|
||||
* Run all performance tests
|
||||
* @param Iterations - Number of iterations
|
||||
* @param ComplexityLevel - Complexity level (1-10)
|
||||
* @return Test result array
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
TArray<FFLESHPerformanceResult> RunAllPerformanceTests(int32 Iterations = 100, int32 ComplexityLevel = 5);
|
||||
|
||||
/**
|
||||
* Set soft body system
|
||||
* @param SoftBodySystem - Soft body system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
void SetSoftBodySystem(UFLESHSoftBodySystem* SoftBodySystem);
|
||||
|
||||
/**
|
||||
* Get soft body system
|
||||
* @return Current soft body system
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
UFLESHSoftBodySystem* GetSoftBodySystem() const;
|
||||
|
||||
/**
|
||||
* Set physics optimizer
|
||||
* @param PhysicsOptimizer - Physics optimizer
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
void SetPhysicsOptimizer(UFLESHPhysicsOptimizer* PhysicsOptimizer);
|
||||
|
||||
/**
|
||||
* Get physics optimizer
|
||||
* @return Current physics optimizer
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
UFLESHPhysicsOptimizer* GetPhysicsOptimizer() const;
|
||||
|
||||
/**
|
||||
* Set benchmark thresholds
|
||||
* @param MaxExecutionTimeMs - Maximum execution time (milliseconds)
|
||||
* @param MinFramesPerSecond - Minimum frames per second
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
void SetBenchmarkThresholds(float MaxExecutionTimeMs, float MinFramesPerSecond);
|
||||
|
||||
/**
|
||||
* Save performance test results to CSV file
|
||||
* @param Results - Test result array
|
||||
* @param FilePath - File path
|
||||
* @return Whether successful
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
bool SaveResultsToCSV(const TArray<FFLESHPerformanceResult>& Results, const FString& FilePath);
|
||||
|
||||
/**
|
||||
* Compare performance test results
|
||||
* @param ResultA - Test result A
|
||||
* @param ResultB - Test result B
|
||||
* @return Performance improvement percentage
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
float CompareResults(const FFLESHPerformanceResult& ResultA, const FFLESHPerformanceResult& ResultB);
|
||||
|
||||
/**
|
||||
* Generate performance test report
|
||||
* @param Results - Test result array
|
||||
* @return Report text
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH|Performance")
|
||||
FString GenerateReport(const TArray<FFLESHPerformanceResult>& Results);
|
||||
|
||||
private:
|
||||
// Soft body system
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHSoftBodySystem> SoftBodySystem;
|
||||
|
||||
// Physics optimizer
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHPhysicsOptimizer> PhysicsOptimizer;
|
||||
|
||||
// Maximum execution time threshold (milliseconds)
|
||||
UPROPERTY()
|
||||
float MaxExecutionTimeThresholdMs;
|
||||
|
||||
// Minimum frame rate threshold
|
||||
UPROPERTY()
|
||||
float MinFrameRateThreshold;
|
||||
|
||||
// Internal method: Test soft body simulation performance
|
||||
FFLESHPerformanceResult TestSoftBodySimulation(int32 Iterations, int32 ComplexityLevel);
|
||||
|
||||
// Internal method: Test boolean cutting performance
|
||||
FFLESHPerformanceResult TestBooleanCutting(int32 Iterations, int32 ComplexityLevel);
|
||||
|
||||
// Internal method: Test physics optimizer performance
|
||||
FFLESHPerformanceResult TestPhysicsOptimizer(int32 Iterations, int32 ComplexityLevel);
|
||||
|
||||
// Internal method: Test multithreading performance
|
||||
FFLESHPerformanceResult TestMultithreading(int32 Iterations, int32 ComplexityLevel);
|
||||
|
||||
// Internal method: Test distance scaling performance
|
||||
FFLESHPerformanceResult TestDistanceScaling(int32 Iterations, int32 ComplexityLevel);
|
||||
|
||||
// Internal method: Create test mesh
|
||||
void CreateTestMesh(int32 ComplexityLevel, TArray<FVector>& OutVertices, TArray<int32>& OutIndices);
|
||||
|
||||
// Internal method: Measure memory usage
|
||||
int32 MeasureMemoryUsage();
|
||||
|
||||
// Internal method: Calculate frame rate
|
||||
float CalculateFrameRate(float ExecutionTimeMs, int32 Iterations);
|
||||
};
|
@@ -1,95 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentCompiler.h"
|
||||
#include "DismembermentGraph/DismembermentGraph.h"
|
||||
|
||||
UDismembermentCompiler::UDismembermentCompiler()
|
||||
{
|
||||
// Initialize default values
|
||||
}
|
||||
|
||||
bool UDismembermentCompiler::CompileGraph(UDismembermentGraph* InGraph)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
|
||||
// Store the graph reference
|
||||
Graph = InGraph;
|
||||
|
||||
// Clear compiled data
|
||||
CompiledNodeData.Empty();
|
||||
ExecutionOrder.Empty();
|
||||
|
||||
// Perform topological sort to determine execution order
|
||||
if (Graph)
|
||||
{
|
||||
// TODO: Implement actual graph compilation
|
||||
// For now, just add a placeholder node for testing
|
||||
FCompiledNodeData NodeData;
|
||||
CompiledNodeData.Add(NodeData);
|
||||
ExecutionOrder.Add(0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentCompiler::GetNodeData(int32 NodeIndex, FDismembermentNodeData& OutNodeData) const
|
||||
{
|
||||
// Check if node index is valid
|
||||
if (!CompiledNodeData.IsValidIndex(NodeIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get compiled node data
|
||||
const FCompiledNodeData& CompiledData = CompiledNodeData[NodeIndex];
|
||||
|
||||
// Create a placeholder node data for now
|
||||
// In a real implementation, this would extract data from the compiled node
|
||||
OutNodeData = FDismembermentNodeData();
|
||||
|
||||
// Set node name
|
||||
if (CompiledData.Node)
|
||||
{
|
||||
OutNodeData.NodeName = CompiledData.Node->GetFName();
|
||||
}
|
||||
else
|
||||
{
|
||||
OutNodeData.NodeName = FName(TEXT("Node") + FString::FromInt(NodeIndex));
|
||||
}
|
||||
|
||||
// Set node type based on node index (just for testing)
|
||||
// In a real implementation, this would be determined by the node type
|
||||
switch (NodeIndex % 6)
|
||||
{
|
||||
case 0:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::Cut;
|
||||
break;
|
||||
case 1:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::BloodEffect;
|
||||
break;
|
||||
case 2:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::Physics;
|
||||
break;
|
||||
case 3:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::Organ;
|
||||
break;
|
||||
case 4:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::Wound;
|
||||
break;
|
||||
case 5:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::BoneSelection;
|
||||
break;
|
||||
default:
|
||||
OutNodeData.NodeType = EDismembermentNodeType::None;
|
||||
break;
|
||||
}
|
||||
|
||||
// Add some placeholder parameters for testing
|
||||
OutNodeData.FloatParameters.Add(TEXT("Width"), 10.0f);
|
||||
OutNodeData.FloatParameters.Add(TEXT("Depth"), 5.0f);
|
||||
OutNodeData.VectorParameters.Add(TEXT("Location"), FVector(0.0f, 0.0f, 0.0f));
|
||||
OutNodeData.VectorParameters.Add(TEXT("Direction"), FVector(0.0f, 0.0f, 1.0f));
|
||||
OutNodeData.BoolParameters.Add(TEXT("CreateDecal"), true);
|
||||
OutNodeData.BoolParameters.Add(TEXT("SimulatePhysics"), true);
|
||||
|
||||
return true;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,621 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentExecutor.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "NiagaraComponent.h"
|
||||
#include "NiagaraFunctionLibrary.h"
|
||||
#include "Components/DecalComponent.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
|
||||
UDismembermentExecutor::UDismembermentExecutor()
|
||||
{
|
||||
// Initialize default values
|
||||
Compiler = nullptr;
|
||||
TargetActor = nullptr;
|
||||
TargetSkeletalMesh = nullptr;
|
||||
|
||||
// Create boolean cut tool
|
||||
CutTool = CreateDefaultSubobject<UBooleanCutTool>(TEXT("CutTool"));
|
||||
}
|
||||
|
||||
void UDismembermentExecutor::Initialize(UDismembermentCompiler* InCompiler)
|
||||
{
|
||||
// Set compiler reference
|
||||
Compiler = InCompiler;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::Execute(AActor* InTargetActor)
|
||||
{
|
||||
// Set target actor
|
||||
TargetActor = InTargetActor;
|
||||
|
||||
// Find skeletal mesh component
|
||||
if (!FindTargetSkeletalMesh())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot execute dismemberment graph, skeletal mesh component not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if compiler is valid
|
||||
if (!Compiler)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot execute dismemberment graph, invalid compiler"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get execution order from compiler
|
||||
TArray<int32> ExecutionOrder;
|
||||
if (!Compiler->GetExecutionOrder(ExecutionOrder))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot execute dismemberment graph, invalid execution order"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear selected bones
|
||||
SelectedBones.Empty();
|
||||
|
||||
// Execute nodes in order
|
||||
bool bSuccess = true;
|
||||
for (int32 NodeIndex : ExecutionOrder)
|
||||
{
|
||||
// Execute node
|
||||
bool bNodeSuccess = ExecuteNode(NodeIndex);
|
||||
|
||||
// Log node execution result
|
||||
FDismembermentNodeData NodeData;
|
||||
if (Compiler->GetNodeData(NodeIndex, NodeData))
|
||||
{
|
||||
FString NodeName = NodeData.NodeName.ToString();
|
||||
if (bNodeSuccess)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("FLESH: Successfully executed node %s"), *NodeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Failed to execute node %s"), *NodeName);
|
||||
bSuccess = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::FindTargetSkeletalMesh()
|
||||
{
|
||||
// Check if target actor is valid
|
||||
if (!TargetActor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get skeletal mesh component
|
||||
TargetSkeletalMesh = TargetActor->FindComponentByClass<USkeletalMeshComponent>();
|
||||
|
||||
return TargetSkeletalMesh != nullptr;
|
||||
}
|
||||
|
||||
void UDismembermentExecutor::AddSelectedBone(const FName& BoneName)
|
||||
{
|
||||
// Add bone to selection list
|
||||
if (!SelectedBones.Contains(BoneName))
|
||||
{
|
||||
SelectedBones.Add(BoneName);
|
||||
}
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::ApplyCut(const FVector& Location, const FVector& Direction, float Width, float Depth, UMaterialInterface* Material)
|
||||
{
|
||||
// Check if target skeletal mesh is valid
|
||||
if (!TargetSkeletalMesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot perform cut, target skeletal mesh is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if cut tool is valid
|
||||
if (!CutTool)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot perform cut, cutting tool is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set cut material
|
||||
CutTool->SetCutMaterial(Material);
|
||||
|
||||
// Create cut plane
|
||||
FCutPlane CutPlane;
|
||||
CutPlane.Location = Location;
|
||||
CutPlane.Normal = Direction.GetSafeNormal();
|
||||
|
||||
// Apply cut to selected bones or entire mesh
|
||||
bool bSuccess = false;
|
||||
|
||||
if (SelectedBones.Num() > 0)
|
||||
{
|
||||
// Cut only selected bones
|
||||
for (const FName& BoneName : SelectedBones)
|
||||
{
|
||||
// Cut skeletal mesh at the specified bone
|
||||
TArray<USkeletalMesh*> CutResults = CutTool->CutSkeletalMesh(
|
||||
TargetSkeletalMesh->GetSkeletalMeshAsset(),
|
||||
CutPlane,
|
||||
BoneName,
|
||||
true // Create cap
|
||||
);
|
||||
|
||||
if (CutResults.Num() >= 2)
|
||||
{
|
||||
// Replace the original skeletal mesh with the first cut result
|
||||
TargetSkeletalMesh->SetSkeletalMeshAsset(CutResults[0]);
|
||||
|
||||
// TODO: Handle the second cut result (detached part)
|
||||
// This would involve creating a new actor with the second mesh
|
||||
// and applying physics to it
|
||||
|
||||
bSuccess = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cut the entire mesh
|
||||
TArray<USkeletalMesh*> CutResults = CutTool->CutSkeletalMesh(
|
||||
TargetSkeletalMesh->GetSkeletalMeshAsset(),
|
||||
CutPlane,
|
||||
NAME_None, // No specific bone
|
||||
true // Create cap
|
||||
);
|
||||
|
||||
if (CutResults.Num() >= 2)
|
||||
{
|
||||
// Replace the original skeletal mesh with the first cut result
|
||||
TargetSkeletalMesh->SetSkeletalMeshAsset(CutResults[0]);
|
||||
|
||||
// TODO: Handle the second cut result (detached part)
|
||||
// This would involve creating a new actor with the second mesh
|
||||
// and applying physics to it
|
||||
|
||||
bSuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::SpawnBloodEffect(const FVector& Location, UNiagaraSystem* BloodEffect, float BloodAmount, float BloodPressure, bool CreateBloodPool, float BloodPoolSize, UMaterialInterface* BloodPoolMaterial)
|
||||
{
|
||||
// Check if target actor is valid
|
||||
if (!TargetActor)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn blood effect, target actor is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if blood effect is valid
|
||||
if (!BloodEffect)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn blood effect, blood particle system is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get world
|
||||
UWorld* World = TargetActor->GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spawn blood effect
|
||||
UNiagaraComponent* BloodComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
|
||||
World,
|
||||
BloodEffect,
|
||||
Location,
|
||||
FRotator::ZeroRotator,
|
||||
FVector(1.0f, 1.0f, 1.0f),
|
||||
true,
|
||||
true,
|
||||
ENCPoolMethod::AutoRelease,
|
||||
true
|
||||
);
|
||||
|
||||
if (!BloodComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn blood effect, failed to create particle system component"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set blood parameters
|
||||
BloodComponent->SetVariableFloat(FName("BloodAmount"), BloodAmount);
|
||||
BloodComponent->SetVariableFloat(FName("BloodPressure"), BloodPressure);
|
||||
|
||||
// Create blood pool if needed
|
||||
if (CreateBloodPool && BloodPoolMaterial)
|
||||
{
|
||||
// Create a decal component for the blood pool
|
||||
UDecalComponent* BloodPoolDecal = UGameplayStatics::SpawnDecalAtLocation(
|
||||
World,
|
||||
BloodPoolMaterial,
|
||||
FVector(BloodPoolSize, BloodPoolSize, 10.0f), // Size of the decal
|
||||
Location,
|
||||
FRotator(-90.0f, 0.0f, 0.0f), // Rotate to face the ground
|
||||
10.0f // Lifetime
|
||||
);
|
||||
|
||||
if (BloodPoolDecal)
|
||||
{
|
||||
// Set fade parameters
|
||||
BloodPoolDecal->SetFadeOut(5.0f, 5.0f, false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::ApplyPhysics(float Mass, float LinearDamping, float AngularDamping, bool EnableGravity, bool SimulatePhysics, bool GenerateOverlapEvents, UPhysicalMaterial* PhysicalMaterial, float ImpulseForce, float ImpulseRadius)
|
||||
{
|
||||
// Check if target skeletal mesh is valid
|
||||
if (!TargetSkeletalMesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot apply physics, target skeletal mesh is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply physics settings to the skeletal mesh component
|
||||
TargetSkeletalMesh->SetSimulatePhysics(SimulatePhysics);
|
||||
TargetSkeletalMesh->SetEnableGravity(EnableGravity);
|
||||
TargetSkeletalMesh->SetGenerateOverlapEvents(GenerateOverlapEvents);
|
||||
|
||||
// Set mass properties
|
||||
if (Mass > 0.0f)
|
||||
{
|
||||
TargetSkeletalMesh->SetMassOverrideInKg(NAME_None, Mass, true);
|
||||
}
|
||||
|
||||
// Set damping
|
||||
TargetSkeletalMesh->SetLinearDamping(LinearDamping);
|
||||
TargetSkeletalMesh->SetAngularDamping(AngularDamping);
|
||||
|
||||
// Set physical material if provided
|
||||
if (PhysicalMaterial)
|
||||
{
|
||||
TargetSkeletalMesh->SetPhysMaterialOverride(PhysicalMaterial);
|
||||
}
|
||||
|
||||
// Apply impulse if force is greater than zero
|
||||
if (ImpulseForce > 0.0f)
|
||||
{
|
||||
// Get the component's location
|
||||
FVector ComponentLocation = TargetSkeletalMesh->GetComponentLocation();
|
||||
|
||||
// Apply radial impulse
|
||||
TargetSkeletalMesh->AddRadialImpulse(
|
||||
ComponentLocation,
|
||||
ImpulseRadius,
|
||||
ImpulseForce,
|
||||
ERadialImpulseFalloff::RIF_Linear,
|
||||
true // Apply impulse to all bodies
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::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)
|
||||
{
|
||||
// Check if target actor is valid
|
||||
if (!TargetActor)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn organ, target actor is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if organ mesh is valid
|
||||
if (!OrganMesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn organ, organ mesh is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get world
|
||||
UWorld* World = TargetActor->GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a static mesh component for the organ
|
||||
UStaticMeshComponent* OrganComponent = NewObject<UStaticMeshComponent>(TargetActor, TEXT("PreviewOrganComponent"));
|
||||
if (!OrganComponent)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot spawn organ, failed to create static mesh component"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register the component
|
||||
OrganComponent->RegisterComponent();
|
||||
|
||||
// Set the mesh and material
|
||||
OrganComponent->SetStaticMesh(OrganMesh);
|
||||
|
||||
if (OrganMaterial)
|
||||
{
|
||||
OrganComponent->SetMaterial(0, OrganMaterial);
|
||||
}
|
||||
|
||||
// Set transform
|
||||
if (TargetSkeletalMesh && !AttachBoneName.IsNone())
|
||||
{
|
||||
// Attach to bone if specified
|
||||
OrganComponent->AttachToComponent(
|
||||
TargetSkeletalMesh,
|
||||
FAttachmentTransformRules::KeepRelativeTransform,
|
||||
AttachBoneName
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Attach to actor root
|
||||
OrganComponent->AttachToComponent(
|
||||
TargetActor->GetRootComponent(),
|
||||
FAttachmentTransformRules::KeepRelativeTransform
|
||||
);
|
||||
}
|
||||
|
||||
// Set relative transform
|
||||
OrganComponent->SetRelativeLocation(RelativeLocation);
|
||||
OrganComponent->SetRelativeRotation(RelativeRotation);
|
||||
OrganComponent->SetRelativeScale3D(RelativeScale);
|
||||
|
||||
// Apply physics if needed
|
||||
if (SimulatePhysics)
|
||||
{
|
||||
OrganComponent->SetSimulatePhysics(true);
|
||||
OrganComponent->SetEnableGravity(true);
|
||||
OrganComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
|
||||
// Add a tag to identify this as an organ
|
||||
OrganComponent->ComponentTags.Add(FName("Organ"));
|
||||
|
||||
// Add custom tags for organ properties
|
||||
if (IsCriticalOrgan)
|
||||
{
|
||||
OrganComponent->ComponentTags.Add(FName("CriticalOrgan"));
|
||||
}
|
||||
|
||||
// Store damage multiplier as a custom float
|
||||
OrganComponent->SetCustomPrimitiveDataFloat(0, DamageMultiplier);
|
||||
OrganComponent->SetCustomPrimitiveDataFloat(1, BloodAmount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::ApplyWoundEffect(float WoundSize, float WoundDepth, UMaterialInterface* WoundMaterial, UNiagaraSystem* WoundEffect, bool CreateDecal, UMaterialInterface* DecalMaterial, float DecalSize, float DecalLifetime, bool AffectBoneHealth, float BoneDamage)
|
||||
{
|
||||
// Check if target actor is valid
|
||||
if (!TargetActor)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot apply wound effect, target actor is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get world
|
||||
UWorld* World = TargetActor->GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the location from the selected bone or actor location
|
||||
FVector WoundLocation;
|
||||
FRotator WoundRotation;
|
||||
|
||||
if (TargetSkeletalMesh && SelectedBones.Num() > 0)
|
||||
{
|
||||
// Use the first selected bone's location
|
||||
WoundLocation = TargetSkeletalMesh->GetBoneLocation(SelectedBones[0]);
|
||||
WoundRotation = TargetSkeletalMesh->GetBoneQuaternion(SelectedBones[0]).Rotator();
|
||||
}
|
||||
else if (TargetSkeletalMesh)
|
||||
{
|
||||
// Use the skeletal mesh component's location
|
||||
WoundLocation = TargetSkeletalMesh->GetComponentLocation();
|
||||
WoundRotation = TargetSkeletalMesh->GetComponentRotation();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the actor's location
|
||||
WoundLocation = TargetActor->GetActorLocation();
|
||||
WoundRotation = TargetActor->GetActorRotation();
|
||||
}
|
||||
|
||||
// Spawn wound effect if provided
|
||||
if (WoundEffect)
|
||||
{
|
||||
UNiagaraComponent* WoundEffectComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
|
||||
World,
|
||||
WoundEffect,
|
||||
WoundLocation,
|
||||
WoundRotation,
|
||||
FVector(WoundSize, WoundSize, WoundSize),
|
||||
true,
|
||||
true,
|
||||
ENCPoolMethod::AutoRelease,
|
||||
true
|
||||
);
|
||||
|
||||
if (WoundEffectComponent)
|
||||
{
|
||||
// Set wound parameters
|
||||
WoundEffectComponent->SetVariableFloat(FName("WoundSize"), WoundSize);
|
||||
WoundEffectComponent->SetVariableFloat(FName("WoundDepth"), WoundDepth);
|
||||
}
|
||||
}
|
||||
|
||||
// Create decal if needed
|
||||
if (CreateDecal && DecalMaterial)
|
||||
{
|
||||
// Create a decal component for the wound
|
||||
UDecalComponent* WoundDecal = UGameplayStatics::SpawnDecalAttached(
|
||||
DecalMaterial,
|
||||
FVector(DecalSize, DecalSize, DecalSize),
|
||||
TargetSkeletalMesh ? TargetSkeletalMesh : TargetActor->GetRootComponent(),
|
||||
NAME_None,
|
||||
WoundLocation,
|
||||
WoundRotation,
|
||||
EAttachLocation::KeepWorldPosition,
|
||||
DecalLifetime
|
||||
);
|
||||
|
||||
if (WoundDecal)
|
||||
{
|
||||
// Set fade parameters
|
||||
WoundDecal->SetFadeOut(DecalLifetime * 0.8f, DecalLifetime * 0.2f, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply bone damage if needed
|
||||
if (AffectBoneHealth && TargetSkeletalMesh && SelectedBones.Num() > 0)
|
||||
{
|
||||
// This would typically involve a custom bone health system
|
||||
// For now, we'll just log that bone damage was applied
|
||||
for (const FName& BoneName : SelectedBones)
|
||||
{
|
||||
UE_LOG(LogTemp, Display, TEXT("FLESH: Applied %.2f damage to bone %s"), BoneDamage, *BoneName.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UDismembermentExecutor::ExecuteNode(int32 NodeIndex)
|
||||
{
|
||||
// Check if compiler is valid
|
||||
if (!Compiler)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot execute node, compiler is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get node data from compiler
|
||||
FDismembermentNodeData NodeData;
|
||||
if (!Compiler->GetNodeData(NodeIndex, NodeData))
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Cannot execute node, node data is invalid"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute node based on its type
|
||||
bool bSuccess = false;
|
||||
|
||||
switch (NodeData.NodeType)
|
||||
{
|
||||
case EDismembermentNodeType::Cut:
|
||||
{
|
||||
// Get cut parameters
|
||||
FVector Location = NodeData.GetVectorParameter("Location", FVector::ZeroVector);
|
||||
FVector Direction = NodeData.GetVectorParameter("Direction", FVector::UpVector);
|
||||
float Width = NodeData.GetFloatParameter("Width", 1.0f);
|
||||
float Depth = NodeData.GetFloatParameter("Depth", 10.0f);
|
||||
UMaterialInterface* Material = Cast<UMaterialInterface>(NodeData.GetObjectParameter("Material"));
|
||||
|
||||
// Apply cut
|
||||
bSuccess = ApplyCut(Location, Direction, Width, Depth, Material);
|
||||
break;
|
||||
}
|
||||
|
||||
case EDismembermentNodeType::BloodEffect:
|
||||
{
|
||||
// Get blood effect parameters
|
||||
FVector Location = NodeData.GetVectorParameter("Location", FVector::ZeroVector);
|
||||
UNiagaraSystem* BloodEffect = Cast<UNiagaraSystem>(NodeData.GetObjectParameter("BloodEffect"));
|
||||
float BloodAmount = NodeData.GetFloatParameter("BloodAmount", 1.0f);
|
||||
float BloodPressure = NodeData.GetFloatParameter("BloodPressure", 1.0f);
|
||||
bool CreateBloodPool = NodeData.GetBoolParameter("CreateBloodPool", true);
|
||||
float BloodPoolSize = NodeData.GetFloatParameter("BloodPoolSize", 100.0f);
|
||||
UMaterialInterface* BloodPoolMaterial = Cast<UMaterialInterface>(NodeData.GetObjectParameter("BloodPoolMaterial"));
|
||||
|
||||
// Spawn blood effect
|
||||
bSuccess = SpawnBloodEffect(Location, BloodEffect, BloodAmount, BloodPressure, CreateBloodPool, BloodPoolSize, BloodPoolMaterial);
|
||||
break;
|
||||
}
|
||||
|
||||
case EDismembermentNodeType::Physics:
|
||||
{
|
||||
// Get physics parameters
|
||||
float Mass = NodeData.GetFloatParameter("Mass", 10.0f);
|
||||
float LinearDamping = NodeData.GetFloatParameter("LinearDamping", 0.01f);
|
||||
float AngularDamping = NodeData.GetFloatParameter("AngularDamping", 0.01f);
|
||||
bool EnableGravity = NodeData.GetBoolParameter("EnableGravity", true);
|
||||
bool SimulatePhysics = NodeData.GetBoolParameter("SimulatePhysics", true);
|
||||
bool GenerateOverlapEvents = NodeData.GetBoolParameter("GenerateOverlapEvents", true);
|
||||
UPhysicalMaterial* PhysicalMaterial = Cast<UPhysicalMaterial>(NodeData.GetObjectParameter("PhysicalMaterial"));
|
||||
float ImpulseForce = NodeData.GetFloatParameter("ImpulseForce", 1000.0f);
|
||||
float ImpulseRadius = NodeData.GetFloatParameter("ImpulseRadius", 100.0f);
|
||||
|
||||
// Apply physics
|
||||
bSuccess = ApplyPhysics(Mass, LinearDamping, AngularDamping, EnableGravity, SimulatePhysics, GenerateOverlapEvents, PhysicalMaterial, ImpulseForce, ImpulseRadius);
|
||||
break;
|
||||
}
|
||||
|
||||
case EDismembermentNodeType::Organ:
|
||||
{
|
||||
// Get organ parameters
|
||||
UStaticMesh* OrganMesh = Cast<UStaticMesh>(NodeData.GetObjectParameter("OrganMesh"));
|
||||
UMaterialInterface* OrganMaterial = Cast<UMaterialInterface>(NodeData.GetObjectParameter("OrganMaterial"));
|
||||
FName AttachBoneName = NodeData.GetNameParameter("AttachBoneName", NAME_None);
|
||||
FVector RelativeLocation = NodeData.GetVectorParameter("RelativeLocation", FVector::ZeroVector);
|
||||
FRotator RelativeRotation = NodeData.GetRotatorParameter("RelativeRotation", FRotator::ZeroRotator);
|
||||
FVector RelativeScale = NodeData.GetVectorParameter("RelativeScale", FVector::OneVector);
|
||||
bool SimulatePhysics = NodeData.GetBoolParameter("SimulatePhysics", true);
|
||||
float DamageMultiplier = NodeData.GetFloatParameter("DamageMultiplier", 1.0f);
|
||||
bool IsCriticalOrgan = NodeData.GetBoolParameter("IsCriticalOrgan", false);
|
||||
float BloodAmount = NodeData.GetFloatParameter("BloodAmount", 1.0f);
|
||||
|
||||
// Spawn organ
|
||||
bSuccess = SpawnOrgan(OrganMesh, OrganMaterial, AttachBoneName, RelativeLocation, RelativeRotation, RelativeScale, SimulatePhysics, DamageMultiplier, IsCriticalOrgan, BloodAmount);
|
||||
break;
|
||||
}
|
||||
|
||||
case EDismembermentNodeType::Wound:
|
||||
{
|
||||
// Get wound parameters
|
||||
float WoundSize = NodeData.GetFloatParameter("WoundSize", 10.0f);
|
||||
float WoundDepth = NodeData.GetFloatParameter("WoundDepth", 5.0f);
|
||||
UMaterialInterface* WoundMaterial = Cast<UMaterialInterface>(NodeData.GetObjectParameter("WoundMaterial"));
|
||||
UNiagaraSystem* WoundEffect = Cast<UNiagaraSystem>(NodeData.GetObjectParameter("WoundEffect"));
|
||||
bool CreateDecal = NodeData.GetBoolParameter("CreateDecal", true);
|
||||
UMaterialInterface* DecalMaterial = Cast<UMaterialInterface>(NodeData.GetObjectParameter("DecalMaterial"));
|
||||
float DecalSize = NodeData.GetFloatParameter("DecalSize", 50.0f);
|
||||
float DecalLifetime = NodeData.GetFloatParameter("DecalLifetime", 10.0f);
|
||||
bool AffectBoneHealth = NodeData.GetBoolParameter("AffectBoneHealth", false);
|
||||
float BoneDamage = NodeData.GetFloatParameter("BoneDamage", 10.0f);
|
||||
|
||||
// Apply wound effect
|
||||
bSuccess = ApplyWoundEffect(WoundSize, WoundDepth, WoundMaterial, WoundEffect, CreateDecal, DecalMaterial, DecalSize, DecalLifetime, AffectBoneHealth, BoneDamage);
|
||||
break;
|
||||
}
|
||||
|
||||
case EDismembermentNodeType::BoneSelection:
|
||||
{
|
||||
// Get bone selection parameters
|
||||
FName BoneName = NodeData.GetNameParameter("BoneName", NAME_None);
|
||||
|
||||
// Add bone to selection
|
||||
if (!BoneName.IsNone())
|
||||
{
|
||||
AddSelectedBone(BoneName);
|
||||
bSuccess = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
UE_LOG(LogTemp, Warning, TEXT("FLESH: Unknown node type"));
|
||||
break;
|
||||
}
|
||||
|
||||
return bSuccess;
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraph.h"
|
||||
#include "DismembermentGraph/DismembermentGraphAsset.h"
|
||||
|
||||
UDismembermentGraph::UDismembermentGraph()
|
||||
{
|
||||
// Initialize default values
|
||||
OwningAsset = nullptr;
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
#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;
|
||||
}
|
@@ -1,137 +0,0 @@
|
||||
#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"
|
||||
#include "GraphEditorActions.h"
|
||||
#include "AssetTypeActions_Base.h"
|
||||
#include "DismembermentGraph/DismembermentGraphSchema.h"
|
||||
#include "DismembermentGraph/SDismembermentGraphNode.h"
|
||||
#include "DismembermentGraph/DismembermentGraphEditorFactory.h"
|
||||
#include "DismembermentGraph/DismembermentGraphPalette.h"
|
||||
#include "DismembermentGraph/DismembermentGraphNodeFactory.h"
|
||||
#include "Styling/AppStyle.h"
|
||||
#include "GraphEditorSettings.h"
|
||||
#include "EdGraph/EdGraphSchema.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "DismembermentGraphEditor"
|
||||
|
||||
/**
|
||||
* 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 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
|
||||
TSharedRef<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);
|
||||
}
|
||||
};
|
||||
|
||||
// Comment out this line to avoid module redefinition
|
||||
// IMPLEMENT_MODULE(FDismembermentGraphEditorModule, FLESHEditor)
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
@@ -1,48 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNode.h"
|
||||
|
||||
UDismembermentGraphNode::UDismembermentGraphNode()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.6f, 0.6f, 0.6f);
|
||||
NodeCategory = FText::FromString("Default");
|
||||
NodeDescription = FText::FromString("Base dismemberment graph node");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNode::AllocateDefaultPins()
|
||||
{
|
||||
// Base implementation does nothing
|
||||
// Derived classes will override this
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNode::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
// Default implementation returns the class name
|
||||
return FText::FromString(GetClass()->GetName());
|
||||
}
|
||||
|
||||
FLinearColor UDismembermentGraphNode::GetNodeTitleColor() const
|
||||
{
|
||||
return NodeTitleColor;
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNode::GetTooltipText() const
|
||||
{
|
||||
return NodeDescription;
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNode::GetMenuCategory() const
|
||||
{
|
||||
return NodeCategory;
|
||||
}
|
||||
|
||||
void UDismembermentGraphNode::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Base implementation does nothing
|
||||
// Derived classes will override this
|
||||
}
|
||||
|
||||
void UDismembermentGraphNode::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Base implementation does nothing
|
||||
// Derived classes will override this
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodeBloodEffect.h"
|
||||
|
||||
UDismembermentGraphNodeBloodEffect::UDismembermentGraphNodeBloodEffect()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.8f, 0.0f, 0.0f); // Red for blood
|
||||
NodeCategory = FText::FromString("Effects");
|
||||
NodeDescription = FText::FromString("Adds blood effect to the dismemberment");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBloodEffect::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodeBloodEffect::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Blood Effect");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBloodEffect::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBloodEffect::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodeBoneSelect.h"
|
||||
|
||||
UDismembermentGraphNodeBoneSelect::UDismembermentGraphNodeBoneSelect()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.8f, 0.8f, 0.2f); // Yellow for bone
|
||||
NodeCategory = FText::FromString("Selection");
|
||||
NodeDescription = FText::FromString("Selects bones for dismemberment");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBoneSelect::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodeBoneSelect::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Bone Selection");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBoneSelect::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeBoneSelect::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodeCut.h"
|
||||
|
||||
UDismembermentGraphNodeCut::UDismembermentGraphNodeCut()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.2f, 0.6f, 0.8f); // Blue for cut
|
||||
NodeCategory = FText::FromString("Operations");
|
||||
NodeDescription = FText::FromString("Performs cutting operation for dismemberment");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeCut::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodeCut::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Cut Operation");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeCut::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeCut::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodeOrgan.h"
|
||||
|
||||
UDismembermentGraphNodeOrgan::UDismembermentGraphNodeOrgan()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.8f, 0.4f, 0.4f); // Pink for organs
|
||||
NodeCategory = FText::FromString("Anatomy");
|
||||
NodeDescription = FText::FromString("Adds organ to the dismemberment system");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeOrgan::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodeOrgan::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Organ");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeOrgan::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeOrgan::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodePhysics.h"
|
||||
|
||||
UDismembermentGraphNodePhysics::UDismembermentGraphNodePhysics()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.4f, 0.8f, 0.4f); // Green for physics
|
||||
NodeCategory = FText::FromString("Physics");
|
||||
NodeDescription = FText::FromString("Configures physics properties for dismembered parts");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodePhysics::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodePhysics::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Physics Properties");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodePhysics::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodePhysics::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
#include "DismembermentGraph/DismembermentGraphNodeWound.h"
|
||||
|
||||
UDismembermentGraphNodeWound::UDismembermentGraphNodeWound()
|
||||
{
|
||||
// Initialize default values
|
||||
NodeTitleColor = FLinearColor(0.8f, 0.2f, 0.2f); // Dark red for wounds
|
||||
NodeCategory = FText::FromString("Effects");
|
||||
NodeDescription = FText::FromString("Adds wound effect to the dismemberment");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeWound::AllocateDefaultPins()
|
||||
{
|
||||
// Create input pin
|
||||
CreatePin(EGPD_Input, TEXT("Exec"), TEXT("In"));
|
||||
|
||||
// Create output pin
|
||||
CreatePin(EGPD_Output, TEXT("Exec"), TEXT("Out"));
|
||||
}
|
||||
|
||||
FText UDismembermentGraphNodeWound::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return FText::FromString("Wound Effect");
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeWound::CompileNode(class FDismembermentCompiler* Compiler)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
||||
|
||||
void UDismembermentGraphNodeWound::ExecuteNode(class FDismembermentExecutor* Executor)
|
||||
{
|
||||
// Implementation will be added in future updates
|
||||
// This is a placeholder to resolve link errors
|
||||
}
|
@@ -1,459 +0,0 @@
|
||||
#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 (const 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(FGenericCommands::Get().SelectAll);
|
||||
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
|
@@ -1,787 +0,0 @@
|
||||
#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(FRotationMatrix::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 = UNiagaraFunctionLibrary::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, TEXT("CutPlaneMeshComponent"));
|
||||
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 = UNiagaraFunctionLibrary::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, TEXT("BloodPoolComponent"));
|
||||
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, TEXT("OrganComponent"));
|
||||
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, TEXT("WoundComponent"));
|
||||
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;
|
||||
}
|
||||
}
|
@@ -1,378 +0,0 @@
|
||||
#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"
|
||||
#include "Styling/AppStyle.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(); // Comment out this line because OutputPinBox is not defined
|
||||
|
||||
// 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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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(FAppStyle::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
|
@@ -1,439 +0,0 @@
|
||||
#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)
|
||||
: AActor(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(FRotator(Settings->Profiles[ProfileIndex].LightingRigRotation, 0, 0));
|
||||
// The following methods may not exist in UE5.5.4, temporarily commented out
|
||||
// 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())
|
||||
{
|
||||
// Cannot remove Actor directly, need to remove the Actor's root component
|
||||
if (PreviewActor && PreviewActor->GetRootComponent())
|
||||
{
|
||||
PreviewScene->RemoveComponent(PreviewActor->GetRootComponent());
|
||||
}
|
||||
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);
|
||||
// The following methods may not exist in UE5.5.4, temporarily commented out
|
||||
// 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);
|
||||
// The following methods may not exist in UE5.5.4, temporarily commented out
|
||||
// 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);
|
||||
|
||||
// In UE5.5.4, these variables may no longer be members of FEditorViewportClient
|
||||
// We only keep the necessary settings, others are commented out
|
||||
bSetListenerPosition = false;
|
||||
bShouldCheckHitProxy = true;
|
||||
|
||||
// The following settings may no longer be supported in UE5.5.4, temporarily commented out
|
||||
/*
|
||||
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;
|
||||
// All bEnableColorize related variables are commented out
|
||||
*/
|
||||
// Continue to comment out more bEnableColorize related variables
|
||||
/*
|
||||
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
|
||||
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
|
||||
bEnableColorizeVolumetricLightmapIndirectIntensityGrayscale = false;
|
||||
bEnableColorizeVolumetricLightmapIndirectIntensityColor = false;
|
||||
bEnableColorizeVolumetricLightmapIndirectIntensity = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusion = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusionGrayscale = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusionColor = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusionGrayscale = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusionColor = false;
|
||||
bEnableColorizeVolumetricLightmapAmbientOcclusion = false;
|
||||
*/
|
||||
// Continue to comment out all remaining bEnableColorize related variables
|
||||
/*
|
||||
bEnableColorizeVolumetricLightmapSHBand0 = false;
|
||||
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
|
||||
bEnableColorizeVolumetricLightmapSHBand0Color = false;
|
||||
bEnableColorizeVolumetricLightmapSHBand0Grayscale = false;
|
||||
bEnableColorizeVolumetricLightmapSHBand0Color = 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;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
|
||||
void FFLESHEditorCommands::RegisterCommands()
|
||||
{
|
||||
UI_COMMAND(OpenFLESHEditor, "F.L.E.S.H", "Open F.L.E.S.H Dismemberment System Editor", EUserInterfaceActionType::Button, FInputChord());
|
||||
UI_COMMAND(OpenFLESHEditor, "F.L.E.S.H Editor", "Open F.L.E.S.H 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());
|
||||
|
@@ -81,54 +81,45 @@ void FFLESHEditorModule::OpenFLESHEditorCommand()
|
||||
OpenFLESHEditor(EToolkitMode::Standalone, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void FFLESHEditorModule::OpenFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UObject* ObjectToEdit)
|
||||
void FFLESHEditorModule::OpenFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, TObjectPtr<UObject> ObjectToEdit)
|
||||
{
|
||||
// Add try-catch block to prevent crashes when opening editor
|
||||
// Add try-catch block to prevent crashes when opening the editor
|
||||
try
|
||||
{
|
||||
// Check if FLESH module is loaded
|
||||
if (!FModuleManager::Get().IsModuleLoaded("FLESH"))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Cannot open FLESH Editor: FLESH module is not loaded"));
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to open FLESH Editor: FLESH module not loaded"));
|
||||
return;
|
||||
}
|
||||
|
||||
// If no object is provided, create a default object to edit
|
||||
// If no object is provided, create a default object
|
||||
if (ObjectToEdit == nullptr)
|
||||
{
|
||||
// Try to find or create a dismemberment graph asset to edit
|
||||
UClass* DismembermentGraphClass = FindObject<UClass>(nullptr, TEXT("/Script/FLESH.DismembermentGraphAsset"));
|
||||
if (DismembermentGraphClass)
|
||||
// Create a basic UObject
|
||||
ObjectToEdit = NewObject<UObject>(GetTransientPackage(), UObject::StaticClass(), TEXT("DefaultFLESHObject"));
|
||||
if (ObjectToEdit == nullptr)
|
||||
{
|
||||
// Try to find an existing asset first
|
||||
ObjectToEdit = FindObject<UObject>(nullptr, TEXT("DefaultDismembermentGraph"));
|
||||
|
||||
// If not found, create a temporary object
|
||||
if (ObjectToEdit == nullptr)
|
||||
{
|
||||
ObjectToEdit = NewObject<UObject>(GetTransientPackage(), DismembermentGraphClass, TEXT("DefaultDismembermentGraph"));
|
||||
ObjectToEdit->AddToRoot(); // Prevent garbage collection
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we can't find the class, create a basic UObject
|
||||
ObjectToEdit = NewObject<UObject>(GetTransientPackage(), UObject::StaticClass(), TEXT("DefaultFLESHObject"));
|
||||
ObjectToEdit->AddToRoot(); // Prevent garbage collection
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to create default object, cannot open FLESH Editor"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new FLESH editor
|
||||
TSharedRef<FFLESHEditor> NewFLESHEditor(new FFLESHEditor());
|
||||
NewFLESHEditor->InitFLESHEditor(Mode, InitToolkitHost, ObjectToEdit);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("FLESH Editor successfully opened"));
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to open FLESH Editor: %s"), UTF8_TO_TCHAR(e.what()));
|
||||
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("OpenEditorError", "Failed to open FLESH Editor: {0}"), FText::FromString(UTF8_TO_TCHAR(e.what()))));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to open FLESH Editor with unknown exception"));
|
||||
UE_LOG(LogTemp, Error, TEXT("Unknown exception occurred when opening FLESH Editor"));
|
||||
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("OpenEditorUnknownError", "Unknown exception occurred when opening FLESH Editor"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "SlateOptMacros.h"
|
||||
#include "Styling/SlateStyle.h"
|
||||
#include "Brushes/SlateImageBrush.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
|
||||
TSharedPtr<FSlateStyleSet> FFLESHEditorStyle::StyleInstance = nullptr;
|
||||
|
||||
|
150
Source/FLESHEditor/Private/FLESHGraph/FLESHCompiler.cpp
Normal file
150
Source/FLESHEditor/Private/FLESHGraph/FLESHCompiler.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "FLESHGraph/FLESHCompiler.h"
|
||||
#include "FLESHGraph/FLESHGraph.h"
|
||||
#include "FLESHGraph/FLESHGraphNode.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHCompiler, Log, All);
|
||||
|
||||
UFLESHCompiler::UFLESHCompiler()
|
||||
{
|
||||
// Initialize default values
|
||||
SourceGraph = nullptr;
|
||||
bCompilationSuccessful = false;
|
||||
ErrorMessage = TEXT("");
|
||||
}
|
||||
|
||||
void UFLESHCompiler::Initialize(UFLESHGraph* InGraph)
|
||||
{
|
||||
// Set source graph
|
||||
SourceGraph = InGraph;
|
||||
|
||||
// Reset compilation status
|
||||
bCompilationSuccessful = false;
|
||||
ErrorMessage = TEXT("");
|
||||
CompiledNodeData.Empty();
|
||||
ExecutionOrder.Empty();
|
||||
|
||||
UE_LOG(LogFLESHCompiler, Log, TEXT("FLESH Compiler initialized with graph: %s"),
|
||||
SourceGraph ? *SourceGraph->GetName() : TEXT("Invalid"));
|
||||
}
|
||||
|
||||
bool UFLESHCompiler::Compile()
|
||||
{
|
||||
// Check if source graph is valid
|
||||
if (!SourceGraph)
|
||||
{
|
||||
ErrorMessage = TEXT("Invalid source graph");
|
||||
UE_LOG(LogFLESHCompiler, Error, TEXT("FLESH Compiler error: %s"), *ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset compilation data
|
||||
CompiledNodeData.Empty();
|
||||
ExecutionOrder.Empty();
|
||||
|
||||
// Validate graph
|
||||
if (!ValidateGraph())
|
||||
{
|
||||
UE_LOG(LogFLESHCompiler, Error, TEXT("FLESH Compiler error: %s"), *ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process all nodes in the graph
|
||||
TArray<UFLESHGraphNode*> AllNodes;
|
||||
// TODO: Get all nodes from graph
|
||||
|
||||
// Process each node
|
||||
for (int32 NodeIndex = 0; NodeIndex < AllNodes.Num(); NodeIndex++)
|
||||
{
|
||||
if (!ProcessNode(AllNodes[NodeIndex], NodeIndex))
|
||||
{
|
||||
UE_LOG(LogFLESHCompiler, Error, TEXT("FLESH Compiler error: %s"), *ErrorMessage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort nodes in execution order
|
||||
SortNodes();
|
||||
|
||||
// Set compilation status
|
||||
bCompilationSuccessful = true;
|
||||
|
||||
UE_LOG(LogFLESHCompiler, Log, TEXT("FLESH Graph compiled successfully. Nodes: %d, Execution order: %d"),
|
||||
CompiledNodeData.Num(), ExecutionOrder.Num());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
TArray<FFLESHNodeData> UFLESHCompiler::GetCompiledNodeData() const
|
||||
{
|
||||
return CompiledNodeData;
|
||||
}
|
||||
|
||||
TArray<int32> UFLESHCompiler::GetExecutionOrder() const
|
||||
{
|
||||
return ExecutionOrder;
|
||||
}
|
||||
|
||||
bool UFLESHCompiler::IsCompilationSuccessful() const
|
||||
{
|
||||
return bCompilationSuccessful;
|
||||
}
|
||||
|
||||
FString UFLESHCompiler::GetErrorMessage() const
|
||||
{
|
||||
return ErrorMessage;
|
||||
}
|
||||
|
||||
bool UFLESHCompiler::ProcessNode(UFLESHGraphNode* Node, int32 NodeIndex)
|
||||
{
|
||||
// Check if node is valid
|
||||
if (!Node)
|
||||
{
|
||||
ErrorMessage = FString::Printf(TEXT("Invalid node at index %d"), NodeIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create node data
|
||||
FFLESHNodeData NodeData;
|
||||
NodeData.NodeName = FName(*Node->GetNodeTitle());
|
||||
// TODO: Set node type and parameters
|
||||
|
||||
// Add node data to compiled data
|
||||
CompiledNodeData.Add(NodeData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UFLESHCompiler::SortNodes()
|
||||
{
|
||||
// TODO: Implement topological sort to determine execution order
|
||||
|
||||
// For now, just add all nodes in order
|
||||
for (int32 i = 0; i < CompiledNodeData.Num(); i++)
|
||||
{
|
||||
ExecutionOrder.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool UFLESHCompiler::ValidateGraph()
|
||||
{
|
||||
// TODO: Implement graph validation
|
||||
|
||||
// Check if source graph is valid
|
||||
if (!SourceGraph)
|
||||
{
|
||||
ErrorMessage = TEXT("Source graph is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if graph has at least one node
|
||||
TArray<UFLESHGraphNode*> AllNodes = SourceGraph->GetAllNodes();
|
||||
if (AllNodes.Num() == 0)
|
||||
{
|
||||
ErrorMessage = TEXT("Graph has no nodes");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
195
Source/FLESHEditor/Private/FLESHGraph/FLESHExecutor.cpp
Normal file
195
Source/FLESHEditor/Private/FLESHGraph/FLESHExecutor.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include "FLESHGraph/FLESHExecutor.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "NiagaraComponent.h"
|
||||
#include "NiagaraFunctionLibrary.h"
|
||||
#include "Components/DecalComponent.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHExecutor, Log, All);
|
||||
|
||||
UFLESHExecutor::UFLESHExecutor()
|
||||
{
|
||||
// Initialize default values
|
||||
Compiler = nullptr;
|
||||
TargetActor = nullptr;
|
||||
TargetSkeletalMesh = nullptr;
|
||||
|
||||
// Create boolean cut tool
|
||||
CutTool = CreateDefaultSubobject<UBooleanCutTool>(TEXT("CutTool"));
|
||||
}
|
||||
|
||||
void UFLESHExecutor::Initialize(UFLESHCompiler* InCompiler)
|
||||
{
|
||||
// Set compiler reference
|
||||
Compiler = InCompiler;
|
||||
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH Executor initialized with compiler: %s"),
|
||||
Compiler ? TEXT("Valid") : TEXT("Invalid"));
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::Execute(AActor* InTargetActor)
|
||||
{
|
||||
// Set target actor
|
||||
TargetActor = InTargetActor;
|
||||
|
||||
// Find skeletal mesh component
|
||||
if (!FindTargetSkeletalMesh())
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Cannot execute FLESH graph, skeletal mesh component not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if compiler is valid
|
||||
if (!Compiler)
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Cannot execute FLESH graph, invalid compiler"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if compilation was successful
|
||||
if (!Compiler->IsCompilationSuccessful())
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Cannot execute FLESH graph, compilation failed: %s"),
|
||||
*Compiler->GetErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get execution order from compiler
|
||||
TArray<int32> ExecutionOrder = Compiler->GetExecutionOrder();
|
||||
|
||||
// Get compiled node data
|
||||
TArray<FFLESHNodeData> CompiledNodeData = Compiler->GetCompiledNodeData();
|
||||
|
||||
// Execute nodes in order
|
||||
for (int32 NodeIndex : ExecutionOrder)
|
||||
{
|
||||
// Check if node index is valid
|
||||
if (!CompiledNodeData.IsValidIndex(NodeIndex))
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Invalid node index in execution order: %d"), NodeIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Execute node
|
||||
if (!ExecuteNode(CompiledNodeData[NodeIndex]))
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Failed to execute node: %s"),
|
||||
*CompiledNodeData[NodeIndex].NodeName.ToString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Graph executed successfully on actor: %s"),
|
||||
TargetActor ? *TargetActor->GetName() : TEXT("Invalid"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void UFLESHExecutor::SetCutTool(UBooleanCutTool* InCutTool)
|
||||
{
|
||||
CutTool = InCutTool;
|
||||
}
|
||||
|
||||
UBooleanCutTool* UFLESHExecutor::GetCutTool() const
|
||||
{
|
||||
return CutTool;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::FindTargetSkeletalMesh()
|
||||
{
|
||||
// Check if target actor is valid
|
||||
if (!TargetActor)
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Invalid target actor"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find skeletal mesh component
|
||||
TargetSkeletalMesh = TargetActor->FindComponentByClass<USkeletalMeshComponent>();
|
||||
|
||||
// Check if skeletal mesh component is valid
|
||||
if (!TargetSkeletalMesh)
|
||||
{
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Target actor does not have a skeletal mesh component"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// Execute node based on type
|
||||
switch (NodeData.NodeType)
|
||||
{
|
||||
case EFLESHNodeType::Cut:
|
||||
return ExecuteCutNode(NodeData);
|
||||
|
||||
case EFLESHNodeType::BloodEffect:
|
||||
return ExecuteBloodEffectNode(NodeData);
|
||||
|
||||
case EFLESHNodeType::Physics:
|
||||
return ExecutePhysicsNode(NodeData);
|
||||
|
||||
case EFLESHNodeType::Organ:
|
||||
return ExecuteOrganNode(NodeData);
|
||||
|
||||
case EFLESHNodeType::Wound:
|
||||
return ExecuteWoundNode(NodeData);
|
||||
|
||||
case EFLESHNodeType::BoneSelection:
|
||||
return ExecuteBoneSelectionNode(NodeData);
|
||||
|
||||
default:
|
||||
UE_LOG(LogFLESHExecutor, Warning, TEXT("FLESH: Unknown node type for node: %s"),
|
||||
*NodeData.NodeName.ToString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteCutNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement cut node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing cut node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteBloodEffectNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement blood effect node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing blood effect node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecutePhysicsNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement physics node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing physics node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteOrganNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement organ node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing organ node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteWoundNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement wound node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing wound node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHExecutor::ExecuteBoneSelectionNode(const FFLESHNodeData& NodeData)
|
||||
{
|
||||
// TODO: Implement bone selection node execution
|
||||
UE_LOG(LogFLESHExecutor, Log, TEXT("FLESH: Executing bone selection node: %s"), *NodeData.NodeName.ToString());
|
||||
return true;
|
||||
}
|
230
Source/FLESHEditor/Private/FLESHGraph/FLESHGraph.cpp
Normal file
230
Source/FLESHEditor/Private/FLESHGraph/FLESHGraph.cpp
Normal file
@@ -0,0 +1,230 @@
|
||||
#include "FLESHGraph/FLESHGraph.h"
|
||||
#include "FLESHGraph/FLESHGraphNode.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// Define log category
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHGraph, Log, All);
|
||||
|
||||
UFLESHGraph::UFLESHGraph()
|
||||
{
|
||||
// Initialize default values
|
||||
RootNode = nullptr;
|
||||
}
|
||||
|
||||
void UFLESHGraph::Initialize()
|
||||
{
|
||||
// Clear graph
|
||||
ClearGraph();
|
||||
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH graph initialized"));
|
||||
}
|
||||
|
||||
UFLESHGraphNode* UFLESHGraph::AddNode(TSubclassOf<UFLESHGraphNode> NodeClass, const FVector2D& Position)
|
||||
{
|
||||
// Check if node class is valid
|
||||
if (!NodeClass)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Invalid node class"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create new node
|
||||
UFLESHGraphNode* NewNode = NewObject<UFLESHGraphNode>(this, NodeClass);
|
||||
if (!NewNode)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Failed to create node"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Set node position
|
||||
NewNode->SetNodePosition(Position);
|
||||
|
||||
// Add to node list
|
||||
Nodes.Add(NewNode);
|
||||
|
||||
// If it's the first node, set as root node
|
||||
if (Nodes.Num() == 1)
|
||||
{
|
||||
RootNode = NewNode;
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Added new node: %s"), *NewNode->GetNodeTitle());
|
||||
|
||||
return NewNode;
|
||||
}
|
||||
|
||||
bool UFLESHGraph::RemoveNode(UFLESHGraphNode* Node)
|
||||
{
|
||||
// Check if node is valid
|
||||
if (!Node)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Trying to remove invalid node"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disconnect from other nodes
|
||||
TArray<UFLESHGraphNode*> InputNodes = Node->GetInputNodes();
|
||||
TArray<UFLESHGraphNode*> OutputNodes = Node->GetOutputNodes();
|
||||
|
||||
for (UFLESHGraphNode* InputNode : InputNodes)
|
||||
{
|
||||
DisconnectNodes(InputNode, Node);
|
||||
}
|
||||
|
||||
for (UFLESHGraphNode* OutputNode : OutputNodes)
|
||||
{
|
||||
DisconnectNodes(Node, OutputNode);
|
||||
}
|
||||
|
||||
// If it's the root node, reset root node
|
||||
if (Node == RootNode)
|
||||
{
|
||||
RootNode = nullptr;
|
||||
|
||||
// If there are other nodes, choose the first one as the new root node
|
||||
if (Nodes.Num() > 1)
|
||||
{
|
||||
for (UFLESHGraphNode* OtherNode : Nodes)
|
||||
{
|
||||
if (OtherNode != Node)
|
||||
{
|
||||
RootNode = OtherNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from node list
|
||||
bool bRemoved = Nodes.Remove(Node) > 0;
|
||||
|
||||
if (bRemoved)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Removed node: %s"), *Node->GetNodeTitle());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Node is not in graph: %s"), *Node->GetNodeTitle());
|
||||
}
|
||||
|
||||
return bRemoved;
|
||||
}
|
||||
|
||||
bool UFLESHGraph::ConnectNodes(UFLESHGraphNode* SourceNode, UFLESHGraphNode* TargetNode)
|
||||
{
|
||||
// Check if nodes are valid
|
||||
if (!SourceNode || !TargetNode)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Trying to connect invalid nodes"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add connection
|
||||
bool bSourceAdded = SourceNode->AddOutputNode(TargetNode);
|
||||
bool bTargetAdded = TargetNode->AddInputNode(SourceNode);
|
||||
|
||||
if (bSourceAdded && bTargetAdded)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Connected nodes: %s -> %s"),
|
||||
*SourceNode->GetNodeTitle(), *TargetNode->GetNodeTitle());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If adding failed, rollback changes
|
||||
if (bSourceAdded)
|
||||
{
|
||||
SourceNode->RemoveOutputNode(TargetNode);
|
||||
}
|
||||
|
||||
if (bTargetAdded)
|
||||
{
|
||||
TargetNode->RemoveInputNode(SourceNode);
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Connecting nodes failed: %s -> %s"),
|
||||
*SourceNode->GetNodeTitle(), *TargetNode->GetNodeTitle());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool UFLESHGraph::DisconnectNodes(UFLESHGraphNode* SourceNode, UFLESHGraphNode* TargetNode)
|
||||
{
|
||||
// Check if nodes are valid
|
||||
if (!SourceNode || !TargetNode)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Trying to disconnect invalid nodes"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove connection
|
||||
bool bSourceRemoved = SourceNode->RemoveOutputNode(TargetNode);
|
||||
bool bTargetRemoved = TargetNode->RemoveInputNode(SourceNode);
|
||||
|
||||
if (bSourceRemoved && bTargetRemoved)
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Disconnected nodes: %s -> %s"),
|
||||
*SourceNode->GetNodeTitle(), *TargetNode->GetNodeTitle());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Disconnecting nodes failed: %s -> %s"),
|
||||
*SourceNode->GetNodeTitle(), *TargetNode->GetNodeTitle());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<UFLESHGraphNode*> UFLESHGraph::GetAllNodes() const
|
||||
{
|
||||
return Nodes;
|
||||
}
|
||||
|
||||
UFLESHGraphNode* UFLESHGraph::GetRootNode() const
|
||||
{
|
||||
return RootNode;
|
||||
}
|
||||
|
||||
void UFLESHGraph::SetRootNode(UFLESHGraphNode* InRootNode)
|
||||
{
|
||||
// Check if node is in graph
|
||||
if (InRootNode && Nodes.Contains(InRootNode))
|
||||
{
|
||||
RootNode = InRootNode;
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Set new root node: %s"), *RootNode->GetNodeTitle());
|
||||
}
|
||||
else if (!InRootNode)
|
||||
{
|
||||
RootNode = nullptr;
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Clear root node"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHGraph, Warning, TEXT("FLESH: Trying to set node not in graph as root node"));
|
||||
}
|
||||
}
|
||||
|
||||
void UFLESHGraph::ClearGraph()
|
||||
{
|
||||
// Clear node list
|
||||
Nodes.Empty();
|
||||
|
||||
// Clear root node
|
||||
RootNode = nullptr;
|
||||
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Cleared graph"));
|
||||
}
|
||||
|
||||
bool UFLESHGraph::SaveGraph()
|
||||
{
|
||||
// TODO: implement graph saving feature
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Graph saving feature not implemented"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UFLESHGraph::LoadGraph()
|
||||
{
|
||||
// TODO: implement graph loading feature
|
||||
UE_LOG(LogFLESHGraph, Log, TEXT("FLESH: Graph loading feature not implemented"));
|
||||
return false;
|
||||
}
|
164
Source/FLESHEditor/Private/FLESHGraph/FLESHGraphNode.cpp
Normal file
164
Source/FLESHEditor/Private/FLESHGraph/FLESHGraphNode.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "FLESHGraph/FLESHGraphNode.h"
|
||||
#include "Logging/LogMacros.h"
|
||||
|
||||
// 定义日志类别
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogFLESHGraphNode, Log, All);
|
||||
|
||||
UFLESHGraphNode::UFLESHGraphNode()
|
||||
{
|
||||
// 初始化默认值
|
||||
NodeTitle = TEXT("FLESH节点");
|
||||
NodeType = EFLESHNodeType::None;
|
||||
NodeColor = FLinearColor(0.5f, 0.5f, 0.5f);
|
||||
NodePosition = FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
FString UFLESHGraphNode::GetNodeTitle() const
|
||||
{
|
||||
return NodeTitle;
|
||||
}
|
||||
|
||||
EFLESHNodeType UFLESHGraphNode::GetNodeType() const
|
||||
{
|
||||
return NodeType;
|
||||
}
|
||||
|
||||
FLinearColor UFLESHGraphNode::GetNodeColor() const
|
||||
{
|
||||
return NodeColor;
|
||||
}
|
||||
|
||||
FVector2D UFLESHGraphNode::GetNodePosition() const
|
||||
{
|
||||
return NodePosition;
|
||||
}
|
||||
|
||||
void UFLESHGraphNode::SetNodePosition(const FVector2D& InPosition)
|
||||
{
|
||||
NodePosition = InPosition;
|
||||
}
|
||||
|
||||
TArray<UFLESHGraphNode*> UFLESHGraphNode::GetInputNodes() const
|
||||
{
|
||||
return InputNodes;
|
||||
}
|
||||
|
||||
TArray<UFLESHGraphNode*> UFLESHGraphNode::GetOutputNodes() const
|
||||
{
|
||||
return OutputNodes;
|
||||
}
|
||||
|
||||
bool UFLESHGraphNode::AddInputNode(UFLESHGraphNode* Node)
|
||||
{
|
||||
// 检查节点是否有效
|
||||
if (!Node)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 尝试添加无效的输入节点"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查节点是否已经在输入列表中
|
||||
if (InputNodes.Contains(Node))
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 节点已经在输入列表中: %s"), *Node->GetNodeTitle());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加到输入列表
|
||||
InputNodes.Add(Node);
|
||||
|
||||
UE_LOG(LogFLESHGraphNode, Log, TEXT("FLESH: 添加了输入节点: %s -> %s"), *Node->GetNodeTitle(), *GetNodeTitle());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHGraphNode::AddOutputNode(UFLESHGraphNode* Node)
|
||||
{
|
||||
// 检查节点是否有效
|
||||
if (!Node)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 尝试添加无效的输出节点"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查节点是否已经在输出列表中
|
||||
if (OutputNodes.Contains(Node))
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 节点已经在输出列表中: %s"), *Node->GetNodeTitle());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加到输出列表
|
||||
OutputNodes.Add(Node);
|
||||
|
||||
UE_LOG(LogFLESHGraphNode, Log, TEXT("FLESH: 添加了输出节点: %s -> %s"), *GetNodeTitle(), *Node->GetNodeTitle());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UFLESHGraphNode::RemoveInputNode(UFLESHGraphNode* Node)
|
||||
{
|
||||
// 检查节点是否有效
|
||||
if (!Node)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 尝试移除无效的输入节点"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从输入列表中移除
|
||||
bool bRemoved = InputNodes.Remove(Node) > 0;
|
||||
|
||||
if (bRemoved)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Log, TEXT("FLESH: 移除了输入节点: %s -> %s"), *Node->GetNodeTitle(), *GetNodeTitle());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 节点不在输入列表中: %s"), *Node->GetNodeTitle());
|
||||
}
|
||||
|
||||
return bRemoved;
|
||||
}
|
||||
|
||||
bool UFLESHGraphNode::RemoveOutputNode(UFLESHGraphNode* Node)
|
||||
{
|
||||
// 检查节点是否有效
|
||||
if (!Node)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 尝试移除无效的输出节点"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从输出列表中移除
|
||||
bool bRemoved = OutputNodes.Remove(Node) > 0;
|
||||
|
||||
if (bRemoved)
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Log, TEXT("FLESH: 移除了输出节点: %s -> %s"), *GetNodeTitle(), *Node->GetNodeTitle());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogFLESHGraphNode, Warning, TEXT("FLESH: 节点不在输出列表中: %s"), *Node->GetNodeTitle());
|
||||
}
|
||||
|
||||
return bRemoved;
|
||||
}
|
||||
|
||||
bool UFLESHGraphNode::CompileNode(FFLESHNodeData& OutNodeData)
|
||||
{
|
||||
// 设置基本节点数据
|
||||
OutNodeData.NodeName = FName(*GetNodeTitle());
|
||||
OutNodeData.NodeType = GetNodeType();
|
||||
|
||||
// 设置连接的节点
|
||||
OutNodeData.ConnectedNodes.Empty();
|
||||
for (UFLESHGraphNode* OutputNode : OutputNodes)
|
||||
{
|
||||
// TODO: 获取节点索引
|
||||
// OutNodeData.ConnectedNodes.Add(NodeIndex);
|
||||
}
|
||||
|
||||
UE_LOG(LogFLESHGraphNode, Log, TEXT("FLESH: 编译了节点: %s"), *GetNodeTitle());
|
||||
|
||||
return true;
|
||||
}
|
@@ -4,7 +4,20 @@
|
||||
#include "CanvasTypes.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Components/DirectionalLightComponent.h"
|
||||
#include "Components/SkyLightComponent.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "PreviewScene.h"
|
||||
#include "EditorViewportClient.h"
|
||||
#include "EngineGlobals.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine.h"
|
||||
#include "CanvasTypes.h"
|
||||
#include "SceneView.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "EditorModeManager.h"
|
||||
#include "UnrealWidget.h"
|
||||
|
||||
FFLESHViewportClient::FFLESHViewportClient(FFLESHEditor* InEditor)
|
||||
: FEditorViewportClient(nullptr)
|
||||
@@ -13,7 +26,7 @@ FFLESHViewportClient::FFLESHViewportClient(FFLESHEditor* InEditor)
|
||||
, bShowWireframe(false)
|
||||
, bShowBones(false)
|
||||
{
|
||||
// Create a valid viewport scene
|
||||
// Create a valid preview scene
|
||||
PreviewScene = MakeShareable(new FPreviewScene(FPreviewScene::ConstructionValues()));
|
||||
|
||||
// Set the scene for FEditorViewportClient - use constructor instead of SetPreviewScene
|
||||
@@ -41,6 +54,19 @@ FFLESHViewportClient::FFLESHViewportClient(FFLESHEditor* InEditor)
|
||||
|
||||
// Set default render mode
|
||||
SetViewMode(VMI_Lit);
|
||||
|
||||
// Set camera control options similar to asset editor
|
||||
SetRealtime(true);
|
||||
bSetListenerPosition = false;
|
||||
|
||||
// Enable standard editor camera controls
|
||||
if (Widget)
|
||||
{
|
||||
Widget->SetSnapEnabled(true);
|
||||
}
|
||||
|
||||
// Load and display objects from NodeTree
|
||||
LoadNodesFromNodeTree();
|
||||
}
|
||||
|
||||
FFLESHViewportClient::~FFLESHViewportClient()
|
||||
@@ -52,6 +78,23 @@ void FFLESHViewportClient::Draw(const FSceneView* View, FPrimitiveDrawInterface*
|
||||
{
|
||||
FEditorViewportClient::Draw(View, PDI);
|
||||
|
||||
// Draw navigation widget (coordinate axes)
|
||||
if (PDI)
|
||||
{
|
||||
// Draw coordinate system axes
|
||||
const float AxisLength = 50.0f;
|
||||
const float AxisThickness = 1.0f;
|
||||
|
||||
// Draw X axis (red)
|
||||
PDI->DrawLine(FVector::ZeroVector, FVector(AxisLength, 0.0f, 0.0f), FLinearColor::Red, SDPG_Foreground, AxisThickness);
|
||||
|
||||
// Draw Y axis (green)
|
||||
PDI->DrawLine(FVector::ZeroVector, FVector(0.0f, AxisLength, 0.0f), FLinearColor::Green, SDPG_Foreground, AxisThickness);
|
||||
|
||||
// Draw Z axis (blue)
|
||||
PDI->DrawLine(FVector::ZeroVector, FVector(0.0f, 0.0f, AxisLength), FLinearColor::Blue, SDPG_Foreground, AxisThickness);
|
||||
}
|
||||
|
||||
// Draw custom UI elements
|
||||
if (Viewport)
|
||||
{
|
||||
@@ -111,7 +154,7 @@ bool FFLESHViewportClient::InputKey(const FInputKeyEventArgs& EventArgs)
|
||||
else if (EventArgs.Key == EKeys::F)
|
||||
{
|
||||
// F key focuses on selected object
|
||||
// TODO: Add logic to focus on the selected object
|
||||
FocusOnSelectedNode();
|
||||
bHandled = true;
|
||||
}
|
||||
}
|
||||
@@ -134,6 +177,15 @@ bool FFLESHViewportClient::InputKey(FViewport* InViewport, int32 ControllerId, F
|
||||
return InputKey(Args);
|
||||
}
|
||||
|
||||
// This version of InputAxis is deprecated, but kept for compatibility
|
||||
bool FFLESHViewportClient::InputAxis(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
|
||||
{
|
||||
// Create InputKeyEventArgs for the new API
|
||||
FInputKeyEventArgs Args(InViewport, ControllerId, Key, IE_Axis);
|
||||
Args.AmountDepressed = Delta;
|
||||
return InputKey(Args);
|
||||
}
|
||||
|
||||
void FFLESHViewportClient::ResetCamera()
|
||||
{
|
||||
// Reset camera position and rotation
|
||||
@@ -173,3 +225,164 @@ void FFLESHViewportClient::ToggleBones()
|
||||
// Invalidate view
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void FFLESHViewportClient::LoadNodesFromNodeTree()
|
||||
{
|
||||
if (!PreviewScene.IsValid() || !Editor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear current preview scene components by creating a new one
|
||||
PreviewScene = MakeShareable(new FPreviewScene(FPreviewScene::ConstructionValues()));
|
||||
FEditorViewportClient::PreviewScene = PreviewScene.Get();
|
||||
|
||||
// Add a default light
|
||||
UDirectionalLightComponent* DirectionalLight = NewObject<UDirectionalLightComponent>();
|
||||
DirectionalLight->SetMobility(EComponentMobility::Movable);
|
||||
DirectionalLight->SetIntensity(1.0f);
|
||||
PreviewScene->AddComponent(DirectionalLight, FTransform(FRotator(-45.0f, 45.0f, 0.0f)));
|
||||
|
||||
// Add a skylight for ambient lighting
|
||||
USkyLightComponent* SkyLight = NewObject<USkyLightComponent>();
|
||||
SkyLight->SetMobility(EComponentMobility::Movable);
|
||||
SkyLight->SourceType = ESkyLightSourceType::SLS_CapturedScene;
|
||||
PreviewScene->AddComponent(SkyLight, FTransform::Identity);
|
||||
|
||||
// Add a second directional light from another angle
|
||||
UDirectionalLightComponent* BackLight = NewObject<UDirectionalLightComponent>();
|
||||
BackLight->SetMobility(EComponentMobility::Movable);
|
||||
BackLight->SetIntensity(0.6f);
|
||||
BackLight->SetLightColor(FLinearColor(0.8f, 0.8f, 1.0f)); // Slightly blue backlight
|
||||
PreviewScene->AddComponent(BackLight, FTransform(FRotator(45.0f, -135.0f, 0.0f)));
|
||||
|
||||
// Get all nodes from NodeTree
|
||||
const TArray<TSharedPtr<FVisceraNodeItem>>& NodeRoots = Editor->GetNodeTreeRoots();
|
||||
|
||||
// Recursively load all nodes
|
||||
for (const TSharedPtr<FVisceraNodeItem>& RootNode : NodeRoots)
|
||||
{
|
||||
if (RootNode.IsValid())
|
||||
{
|
||||
LoadNodeRecursive(RootNode, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Update viewport
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void FFLESHViewportClient::LoadNodeRecursive(TSharedPtr<FVisceraNodeItem> Node, USceneComponent* ParentComponent)
|
||||
{
|
||||
if (!Node.IsValid() || !PreviewScene.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
USceneComponent* NodeComponent = nullptr;
|
||||
|
||||
// Simplified node loading logic, use static mesh for all node types
|
||||
UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>();
|
||||
|
||||
// Choose different mesh and color based on node type
|
||||
FString MeshPath;
|
||||
FLinearColor Color;
|
||||
|
||||
if (Node->NodeType.Equals(TEXT("SoftBody")))
|
||||
{
|
||||
MeshPath = TEXT("/Engine/BasicShapes/Sphere.Sphere");
|
||||
Color = FLinearColor(1.0f, 0.5f, 0.5f, 0.7f); // Translucent red
|
||||
}
|
||||
else if (Node->NodeType.Equals(TEXT("LineChain")))
|
||||
{
|
||||
MeshPath = TEXT("/Engine/BasicShapes/Cylinder.Cylinder");
|
||||
Color = FLinearColor(0.5f, 0.5f, 1.0f, 0.7f); // Translucent blue
|
||||
}
|
||||
else if (Node->NodeType.Equals(TEXT("Plane")))
|
||||
{
|
||||
MeshPath = TEXT("/Engine/BasicShapes/Plane.Plane");
|
||||
Color = FLinearColor(0.5f, 0.5f, 1.0f, 0.5f); // Translucent blue
|
||||
}
|
||||
else if (Node->NodeType.Equals(TEXT("Matrix")))
|
||||
{
|
||||
MeshPath = TEXT("/Engine/BasicShapes/Cube.Cube");
|
||||
Color = FLinearColor(0.5f, 1.0f, 0.5f, 0.7f); // Translucent green
|
||||
MeshComponent->SetWorldScale3D(FVector(0.25f, 0.25f, 0.25f)); // Scale down cube
|
||||
}
|
||||
else // Default to sphere
|
||||
{
|
||||
MeshPath = TEXT("/Engine/BasicShapes/Sphere.Sphere");
|
||||
Color = FLinearColor(1.0f, 1.0f, 1.0f, 0.7f); // Translucent white
|
||||
}
|
||||
|
||||
// Load static mesh
|
||||
UStaticMesh* StaticMesh = LoadObject<UStaticMesh>(nullptr, *MeshPath);
|
||||
if (StaticMesh)
|
||||
{
|
||||
MeshComponent->SetStaticMesh(StaticMesh);
|
||||
|
||||
// Set translucent material
|
||||
UMaterial* Material = LoadObject<UMaterial>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial.BasicShapeMaterial"));
|
||||
if (Material)
|
||||
{
|
||||
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(Material, MeshComponent);
|
||||
if (DynamicMaterial)
|
||||
{
|
||||
DynamicMaterial->SetVectorParameterValue(TEXT("Color"), Color);
|
||||
MeshComponent->SetMaterial(0, DynamicMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to preview scene
|
||||
PreviewScene->AddComponent(MeshComponent, FTransform::Identity);
|
||||
NodeComponent = MeshComponent;
|
||||
|
||||
// If parent component exists, set parent-child relationship
|
||||
if (NodeComponent && ParentComponent)
|
||||
{
|
||||
NodeComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform);
|
||||
}
|
||||
|
||||
// Recursively load child nodes
|
||||
for (const TSharedPtr<FVisceraNodeItem>& ChildNode : Node->Children)
|
||||
{
|
||||
if (ChildNode.IsValid() && NodeComponent)
|
||||
{
|
||||
LoadNodeRecursive(ChildNode, NodeComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FFLESHViewportClient::UpdateVisibleNodes()
|
||||
{
|
||||
// Reload all nodes
|
||||
LoadNodesFromNodeTree();
|
||||
}
|
||||
|
||||
void FFLESHViewportClient::FocusOnSelectedNode()
|
||||
{
|
||||
if (!Editor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get currently selected node
|
||||
TSharedPtr<FVisceraNodeItem> SelectedNode = Editor->GetSelectedNodeItem();
|
||||
if (!SelectedNode.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Position camera at the selected node's location
|
||||
// Note: Here we use a default position since we don't have actual position information for the node
|
||||
// In a real implementation, we should find the component for this node and get its position
|
||||
FVector FocusLocation = FVector::ZeroVector;
|
||||
|
||||
// Set camera position and rotation
|
||||
SetViewLocation(FocusLocation + FVector(0.0f, 100.0f, 0.0f));
|
||||
SetViewRotation(FRotator(0.0f, -90.0f, 0.0f));
|
||||
|
||||
// Update view
|
||||
Invalidate();
|
||||
}
|
||||
|
@@ -1,135 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
#include "EditorUndoClient.h"
|
||||
#include "BooleanCutTool.h"
|
||||
#include "AnatomicalLayerSystem.h"
|
||||
|
||||
class USkeletalMesh;
|
||||
class SDockTab;
|
||||
class IDetailsView;
|
||||
class SBorder;
|
||||
|
||||
/**
|
||||
* Dismemberment System Editor
|
||||
* Provides real-time boolean cutting and multi-layer system editing functionality
|
||||
* Allows users to create and edit anatomical layer structures for skeletal meshes
|
||||
* Supports physics settings and effect previews
|
||||
*/
|
||||
class FDismembermentEditor : public FAssetEditorToolkit, public FEditorUndoClient
|
||||
{
|
||||
public:
|
||||
/** Constructor */
|
||||
FDismembermentEditor();
|
||||
|
||||
/** Destructor */
|
||||
virtual ~FDismembermentEditor();
|
||||
|
||||
/**
|
||||
* Initialize the dismemberment editor
|
||||
* @param Mode - Toolkit mode
|
||||
* @param InitToolkitHost - Toolkit host
|
||||
* @param InSkeletalMesh - Skeletal mesh to edit
|
||||
*/
|
||||
void InitDismembermentEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, USkeletalMesh* InSkeletalMesh);
|
||||
|
||||
/** Get toolkit name */
|
||||
virtual FName GetToolkitFName() const override;
|
||||
|
||||
/** Get base toolkit name */
|
||||
virtual FText GetBaseToolkitName() const override;
|
||||
|
||||
/** Get world centric tab prefix */
|
||||
virtual FString GetWorldCentricTabPrefix() const override;
|
||||
|
||||
/** Get world centric tab color scale */
|
||||
virtual FLinearColor GetWorldCentricTabColorScale() const override;
|
||||
|
||||
/** Post undo handler */
|
||||
virtual void PostUndo(bool bSuccess) override;
|
||||
|
||||
/** Post redo handler */
|
||||
virtual void PostRedo(bool bSuccess) override;
|
||||
|
||||
/** Get current skeletal mesh being edited */
|
||||
USkeletalMesh* GetSkeletalMesh() const { return SkeletalMesh; }
|
||||
|
||||
private:
|
||||
/** Create editor layout */
|
||||
void CreateEditorLayout();
|
||||
|
||||
/** Create editor toolbar */
|
||||
void CreateEditorToolbar();
|
||||
|
||||
/** Register tab spawners */
|
||||
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
|
||||
/** Unregister tab spawners */
|
||||
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& InTabManager) override;
|
||||
|
||||
/** Spawn viewport tab */
|
||||
TSharedRef<SDockTab> SpawnTab_Viewport(const FSpawnTabArgs& Args);
|
||||
|
||||
/** Spawn details tab */
|
||||
TSharedRef<SDockTab> SpawnTab_Details(const FSpawnTabArgs& Args);
|
||||
|
||||
/** Spawn layer system tab */
|
||||
TSharedRef<SDockTab> SpawnTab_LayerSystem(const FSpawnTabArgs& Args);
|
||||
|
||||
/** Spawn physics settings tab */
|
||||
TSharedRef<SDockTab> SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args);
|
||||
|
||||
/** Spawn node tree tab */
|
||||
TSharedRef<SDockTab> SpawnTab_NodeTree(const FSpawnTabArgs& Args);
|
||||
|
||||
/** Perform boolean cut operation */
|
||||
void PerformBooleanCut();
|
||||
|
||||
/** Add new anatomical layer */
|
||||
void AddNewLayer();
|
||||
|
||||
/** Save edits */
|
||||
void SaveEdits();
|
||||
|
||||
/** Preview effects */
|
||||
void PreviewEffects();
|
||||
|
||||
private:
|
||||
/** Current skeletal mesh being edited */
|
||||
USkeletalMesh* SkeletalMesh;
|
||||
|
||||
/** Viewport widget */
|
||||
TSharedPtr<SBorder> ViewportWidget;
|
||||
|
||||
/** Details widget */
|
||||
TSharedPtr<IDetailsView> DetailsWidget;
|
||||
|
||||
/** Layer system widget */
|
||||
TSharedPtr<SBorder> LayerSystemWidget;
|
||||
|
||||
/** Physics settings widget */
|
||||
TSharedPtr<SBorder> PhysicsSettingsWidget;
|
||||
|
||||
/** Boolean cut tool */
|
||||
UBooleanCutTool* BooleanCutTool;
|
||||
|
||||
/** Anatomical layer system */
|
||||
UAnatomicalLayerSystem* LayerSystem;
|
||||
|
||||
/** Current cut plane */
|
||||
FCutPlane CurrentCutPlane;
|
||||
|
||||
/** Selected bone name for cutting */
|
||||
FName SelectedBoneName;
|
||||
|
||||
/** Flag to create cap mesh */
|
||||
bool bCreateCapMesh;
|
||||
|
||||
/** Tab ID constants */
|
||||
static const FName ViewportTabId;
|
||||
static const FName DetailsTabId;
|
||||
static const FName LayerSystemTabId;
|
||||
static const FName PhysicsSettingsTabId;
|
||||
static const FName NodeTreeTabId;
|
||||
};
|
@@ -1,347 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "DismembermentCompiler.generated.h"
|
||||
|
||||
class UDismembermentGraphNode;
|
||||
class UDismembermentGraph;
|
||||
|
||||
/**
|
||||
* Dismemberment node type enum
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDismembermentNodeType : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
Cut UMETA(DisplayName = "Cut"),
|
||||
BloodEffect UMETA(DisplayName = "Blood Effect"),
|
||||
Physics UMETA(DisplayName = "Physics"),
|
||||
Organ UMETA(DisplayName = "Organ"),
|
||||
Wound UMETA(DisplayName = "Wound"),
|
||||
BoneSelection UMETA(DisplayName = "Bone Selection")
|
||||
};
|
||||
|
||||
/**
|
||||
* Dismemberment node data structure
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDismembermentNodeData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Node name
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
FName NodeName;
|
||||
|
||||
// Node type
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
EDismembermentNodeType NodeType;
|
||||
|
||||
// Node parameters
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, float> FloatParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, FVector> VectorParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, FRotator> RotatorParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, bool> BoolParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, FName> NameParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Dismemberment")
|
||||
TMap<FName, TObjectPtr<UObject>> ObjectParameters;
|
||||
|
||||
// Constructor
|
||||
FDismembermentNodeData()
|
||||
: NodeType(EDismembermentNodeType::None)
|
||||
{
|
||||
}
|
||||
|
||||
// Get float parameter
|
||||
float GetFloatParameter(const FName& ParamName, float DefaultValue = 0.0f) const
|
||||
{
|
||||
if (const float* Value = FloatParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
|
||||
// Get vector parameter
|
||||
FVector GetVectorParameter(const FName& ParamName, const FVector& DefaultValue = FVector::ZeroVector) const
|
||||
{
|
||||
if (const FVector* Value = VectorParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
|
||||
// Get rotator parameter
|
||||
FRotator GetRotatorParameter(const FName& ParamName, const FRotator& DefaultValue = FRotator::ZeroRotator) const
|
||||
{
|
||||
if (const FRotator* Value = RotatorParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
|
||||
// Get bool parameter
|
||||
bool GetBoolParameter(const FName& ParamName, bool DefaultValue = false) const
|
||||
{
|
||||
if (const bool* Value = BoolParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
|
||||
// Get name parameter
|
||||
FName GetNameParameter(const FName& ParamName, const FName& DefaultValue = NAME_None) const
|
||||
{
|
||||
if (const FName* Value = NameParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
|
||||
// Get object parameter
|
||||
TObjectPtr<UObject> GetObjectParameter(const FName& ParamName, TObjectPtr<UObject> DefaultValue = nullptr) const
|
||||
{
|
||||
if (const TObjectPtr<UObject>* Value = ObjectParameters.Find(ParamName))
|
||||
{
|
||||
return *Value;
|
||||
}
|
||||
return DefaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param OutExecutionOrder - Array to fill with node indices in execution order
|
||||
* @return True if execution order is valid
|
||||
*/
|
||||
bool GetExecutionOrder(TArray<int32>& OutExecutionOrder) const
|
||||
{
|
||||
if (ExecutionOrder.Num() == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutExecutionOrder = ExecutionOrder;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node data for a specific node index
|
||||
* @param NodeIndex - Index of the node
|
||||
* @param OutNodeData - Node data to fill
|
||||
* @return True if node data is valid
|
||||
*/
|
||||
bool GetNodeData(int32 NodeIndex, FDismembermentNodeData& OutNodeData) const;
|
||||
|
||||
/**
|
||||
* 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, TObjectPtr<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, TObjectPtr<UNiagaraSystem> BloodEffect, float BloodAmount, float BloodPressure, bool CreateBloodPool, float BloodPoolSize, TObjectPtr<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, TObjectPtr<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(TObjectPtr<UStaticMesh> OrganMesh, TObjectPtr<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, TObjectPtr<UMaterialInterface> WoundMaterial, TObjectPtr<UNiagaraSystem> WoundEffect, bool CreateDecal, TObjectPtr<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);
|
||||
};
|
@@ -1,161 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "DismembermentCompiler.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "BooleanCutTool.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;
|
||||
|
||||
// Boolean cut tool for mesh cutting operations
|
||||
UPROPERTY()
|
||||
TObjectPtr<UBooleanCutTool> CutTool;
|
||||
|
||||
// Find the target skeletal mesh component
|
||||
bool FindTargetSkeletalMesh();
|
||||
|
||||
// Execute a node
|
||||
bool ExecuteNode(int32 NodeIndex);
|
||||
};
|
@@ -1,22 +0,0 @@
|
||||
#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()
|
||||
TObjectPtr<class UDismembermentGraphAsset> OwningAsset;
|
||||
};
|
@@ -1,80 +0,0 @@
|
||||
#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;
|
||||
};
|
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
};
|
@@ -1,43 +0,0 @@
|
||||
#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;
|
||||
// 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);
|
||||
};
|
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DismembermentGraphNode.h"
|
||||
#include "NiagaraSystem.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
|
||||
};
|
@@ -1,40 +0,0 @@
|
||||
#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
|
||||
};
|
@@ -1,40 +0,0 @@
|
||||
#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
|
||||
};
|
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraphUtilities.h"
|
||||
|
||||
class UDismembermentGraphNode;
|
||||
class SDismembermentGraphNode;
|
||||
|
||||
/**
|
||||
* Dismemberment graph node factory
|
||||
* Used to create visual representations of dismemberment graph nodes
|
||||
*/
|
||||
class FDismembermentGraphNodeFactory : public FGraphPanelNodeFactory
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
FDismembermentGraphNodeFactory();
|
||||
|
||||
FDismembermentGraphNodeFactory(UClass* InNodeClass, const FText& InDisplayName, const FText& InTooltip);
|
||||
|
||||
// FGraphPanelNodeFactory interface
|
||||
virtual TSharedPtr<SGraphNode> CreateNode(UEdGraphNode* Node) const override;
|
||||
// End of interface
|
||||
|
||||
private:
|
||||
// Node class
|
||||
UClass* NodeClass;
|
||||
|
||||
// Display name
|
||||
FText DisplayName;
|
||||
|
||||
// Tooltip
|
||||
FText Tooltip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dismemberment schema action - New node
|
||||
* Used to create new nodes in the context menu
|
||||
*/
|
||||
class FDismembermentSchemaAction_NewNode : public FEdGraphSchemaAction
|
||||
{
|
||||
public:
|
||||
// Constructor
|
||||
FDismembermentSchemaAction_NewNode(const FText& InNodeCategory, const FText& InMenuDesc, const FText& InToolTip, const int32 InGrouping);
|
||||
|
||||
// Perform action
|
||||
virtual UEdGraphNode* PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode = true) override;
|
||||
|
||||
// Node class
|
||||
TSubclassOf<UDismembermentGraphNode> NodeClass;
|
||||
};
|
@@ -1,58 +0,0 @@
|
||||
#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
|
||||
};
|
@@ -1,55 +0,0 @@
|
||||
#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
|
||||
};
|
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DismembermentGraphNode.h"
|
||||
#include "NiagaraSystem.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
|
||||
};
|
@@ -1,56 +0,0 @@
|
||||
#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
|
||||
* This is a stub class that will be implemented later
|
||||
*/
|
||||
class FLESHEDITOR_API SDismembermentGraphPalette : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SDismembermentGraphPalette) {}
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs, TSharedPtr<FDismembermentGraphEditor> InGraphEditor)
|
||||
{
|
||||
GraphEditor = InGraphEditor;
|
||||
|
||||
// Create a simple placeholder widget
|
||||
ChildSlot
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString("Dismemberment Graph Palette - Coming Soon"))
|
||||
];
|
||||
}
|
||||
|
||||
private:
|
||||
// The graph editor that owns this palette
|
||||
TWeakPtr<FDismembermentGraphEditor> GraphEditor;
|
||||
};
|
@@ -1,168 +0,0 @@
|
||||
#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);
|
||||
};
|
@@ -1,170 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "NiagaraComponent.h"
|
||||
#include "NiagaraFunctionLibrary.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();
|
||||
};
|
@@ -1,68 +0,0 @@
|
||||
#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;
|
||||
};
|
@@ -1,120 +0,0 @@
|
||||
#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;
|
||||
};
|
@@ -3,20 +3,31 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "Toolkits/AssetEditorToolkit.h"
|
||||
#include "Widgets/Docking/SDockTab.h"
|
||||
#include "BooleanCutTool.h"
|
||||
#include "EditorUndoClient.h"
|
||||
#include "Widgets/SViewport.h"
|
||||
#include "EditorViewportClient.h"
|
||||
#include "Widgets/Input/SSearchBox.h"
|
||||
#include "Slate/SceneViewport.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
|
||||
// Forward declaration for BooleanCutTool
|
||||
class FBooleanCutTool;
|
||||
|
||||
#include "Logging/LogMacros.h"
|
||||
#include "FLESHGraph/FLESHCompiler.h"
|
||||
#include "FLESHGraph/FLESHExecutor.h"
|
||||
|
||||
class SDockTab;
|
||||
class SGraphEditor;
|
||||
class SPropertyTreeView;
|
||||
class SAssetBrowser;
|
||||
class SMatrixInputWidget;
|
||||
class FFLESHViewportClient;
|
||||
class FSceneViewport;
|
||||
class UVisceraNodeObject;
|
||||
class FDismembermentEditor;
|
||||
|
||||
// Forward declaration
|
||||
class FFLESHViewportClient;
|
||||
|
||||
// Define log category
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogFLESHEditor, Log, All);
|
||||
|
||||
@@ -203,7 +214,9 @@ public:
|
||||
virtual FText GetBaseToolkitName() const override;
|
||||
virtual FString GetWorldCentricTabPrefix() const override;
|
||||
virtual FLinearColor GetWorldCentricTabColorScale() const override;
|
||||
// End of FAssetEditorToolkit interface
|
||||
// Override FAssetEditorToolkit methods with correct parameter types
|
||||
virtual void OnToolkitHostingStarted(const TSharedRef<class IToolkit>& InToolkit) override;
|
||||
virtual void OnToolkitHostingFinished(const TSharedRef<class IToolkit>& InToolkit) override;
|
||||
|
||||
// FEditorUndoClient interface
|
||||
virtual void PostUndo(bool bSuccess) override;
|
||||
@@ -219,12 +232,22 @@ public:
|
||||
UObject* GetEditingObject() const { return EditingObject; }
|
||||
|
||||
// Add DismembermentEditor related tab spawners
|
||||
TSharedRef<SDockTab> SpawnTab_LayerSystem(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_LayerSystemPanel(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_PhysicsSettings(const FSpawnTabArgs& Args);
|
||||
|
||||
// Create DismembermentEditor related widgets
|
||||
TSharedRef<SBorder> CreateLayerSystemWidget();
|
||||
TSharedRef<SBorder> CreatePhysicsSettingsWidget();
|
||||
TSharedRef<SWidget> CreateNodeTreeWidget();
|
||||
|
||||
// Generate node tree row
|
||||
TSharedRef<ITableRow> OnGenerateNodeTreeRow(TSharedPtr<FVisceraNodeItem> Item, const TSharedRef<STableViewBase>& OwnerTable);
|
||||
|
||||
// Get node tree children
|
||||
void OnGetNodeTreeChildren(TSharedPtr<FVisceraNodeItem> Item, TArray<TSharedPtr<FVisceraNodeItem>>& OutChildren);
|
||||
|
||||
// Open node tree right-click menu
|
||||
TSharedPtr<SWidget> OnNodeTreeContextMenuOpening();
|
||||
|
||||
private:
|
||||
// Tab spawners
|
||||
@@ -234,6 +257,9 @@ private:
|
||||
TSharedRef<SDockTab> SpawnTab_MatrixEditor(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_GraphEditor(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_Toolbar(const FSpawnTabArgs& Args);
|
||||
// Additional tab spawners
|
||||
TSharedRef<SDockTab> SpawnTab_DismembermentGraph(const FSpawnTabArgs& Args);
|
||||
TSharedRef<SDockTab> SpawnTab_NodeTree(const FSpawnTabArgs& Args);
|
||||
|
||||
// Create viewport widget
|
||||
TSharedRef<SWidget> CreateViewportWidget();
|
||||
@@ -256,7 +282,7 @@ private:
|
||||
// Create command list
|
||||
void CreateCommandList();
|
||||
|
||||
// Extend toolbar with custom buttons
|
||||
// Extend toolbar
|
||||
void ExtendToolbar();
|
||||
|
||||
// Generate bone tree row
|
||||
@@ -274,6 +300,18 @@ private:
|
||||
// Build viscera node tree
|
||||
void BuildVisceraNodeTree();
|
||||
|
||||
// Count total nodes in a tree recursively
|
||||
int32 CountNodes(TSharedPtr<FVisceraNodeItem> Node);
|
||||
|
||||
// 从父节点中递归移除节点
|
||||
bool RemoveNodeFromParent(TSharedPtr<FVisceraNodeItem> ParentNode, TSharedPtr<FVisceraNodeItem> NodeToRemove);
|
||||
|
||||
// 递归复制节点
|
||||
void CopyNodeRecursive(TSharedPtr<FVisceraNodeItem> SourceNode, TSharedPtr<FVisceraNodeItem> TargetParentNode);
|
||||
|
||||
// 查找节点的父节点并添加复制的节点
|
||||
bool AddCopyToParent(TSharedPtr<FVisceraNodeItem> CurrentNode, TSharedPtr<FVisceraNodeItem> NodeToFind, TSharedPtr<FVisceraNodeItem> NodeCopy);
|
||||
|
||||
// Tree view generation methods
|
||||
TSharedRef<ITableRow> OnGenerateNodeRow(TSharedPtr<FVisceraNodeItem> InItem, const TSharedRef<STableViewBase>& OwnerTable);
|
||||
void OnGetNodeChildren(TSharedPtr<FVisceraNodeItem> InItem, TArray<TSharedPtr<FVisceraNodeItem>>& OutChildren);
|
||||
@@ -299,6 +337,15 @@ private:
|
||||
void OnSavePreset();
|
||||
void OnLoadPreset();
|
||||
|
||||
// 按钮点击事件处理
|
||||
FReply OnResetCameraClicked();
|
||||
FReply OnToggleWireframeClicked();
|
||||
FReply OnToggleBonesClicked();
|
||||
FReply OnImportModelClicked();
|
||||
FReply OnSavePresetClicked();
|
||||
FReply OnLoadPresetClicked();
|
||||
FReply OnBooleanCutClicked();
|
||||
|
||||
// Viewport widget
|
||||
TSharedPtr<class SViewport> ViewportWidget;
|
||||
|
||||
@@ -350,13 +397,8 @@ private:
|
||||
// Scene viewport
|
||||
TSharedPtr<FSceneViewport> Viewport;
|
||||
|
||||
// Viewport related methods
|
||||
FReply OnResetCameraClicked();
|
||||
FReply OnToggleWireframeClicked();
|
||||
FReply OnToggleBonesClicked();
|
||||
|
||||
// The object being edited
|
||||
UObject* EditingObject;
|
||||
TObjectPtr<UObject> EditingObject;
|
||||
|
||||
// Tab IDs
|
||||
static const FName ViewportTabId;
|
||||
@@ -368,9 +410,7 @@ private:
|
||||
static const FName LayerSystemTabId;
|
||||
static const FName PhysicsSettingsTabId;
|
||||
static const FName DismembermentGraphTabId;
|
||||
|
||||
// Is the editor initialized?
|
||||
bool bIsInitialized;
|
||||
static const FName NodeTreeTabId;
|
||||
|
||||
// New DismembermentEditor related Widgets
|
||||
TSharedPtr<SBorder> LayerSystemWidget;
|
||||
@@ -378,4 +418,57 @@ private:
|
||||
|
||||
// Error handling method
|
||||
void HandleEditorError(const FText& ErrorMessage);
|
||||
|
||||
// Layer names for dropdown
|
||||
TArray<TSharedPtr<FString>> LayerNames;
|
||||
|
||||
// Layer descriptions for tooltips
|
||||
TMap<FString, FString> LayerDescriptions;
|
||||
|
||||
// Layer visibility states
|
||||
TMap<FString, bool> LayerVisibility;
|
||||
|
||||
// Node tree root nodes
|
||||
TArray<TSharedPtr<FVisceraNodeItem>> NodeTreeRoots;
|
||||
|
||||
// Get node tree root nodes
|
||||
const TArray<TSharedPtr<FVisceraNodeItem>>& GetNodeTreeRoots() const { return NodeTreeRoots; }
|
||||
|
||||
// Get currently selected node
|
||||
TSharedPtr<FVisceraNodeItem> GetSelectedNodeItem() const { return SelectedNodeItem; }
|
||||
|
||||
// Make these methods public so they can be accessed by FFLESHViewportClient
|
||||
friend class FFLESHViewportClient;
|
||||
|
||||
// Node search filter text
|
||||
FText NodeSearchText;
|
||||
|
||||
// Filtered node tree roots
|
||||
TArray<TSharedPtr<FVisceraNodeItem>> FilteredNodeTreeRoots;
|
||||
|
||||
// Check if a node matches the search filter
|
||||
bool DoesNodeMatchFilter(const TSharedPtr<FVisceraNodeItem>& Node) const;
|
||||
|
||||
// Apply search filter to node tree
|
||||
void ApplySearchFilter();
|
||||
|
||||
// Reset search filter
|
||||
void ResetSearchFilter();
|
||||
|
||||
// Handle node search text changes
|
||||
void OnNodeSearchTextChanged(const FText& InFilterText);
|
||||
|
||||
// Bone tree items
|
||||
TArray<TSharedPtr<FBoneTreeItem>> BoneTreeRoots;
|
||||
|
||||
// Boolean cut tool
|
||||
TSharedPtr<class FBooleanCutTool> BooleanCutTool;
|
||||
|
||||
// FLESH Compiler
|
||||
UPROPERTY()
|
||||
UFLESHCompiler* FLESHCompiler;
|
||||
|
||||
// FLESH Executor
|
||||
UPROPERTY()
|
||||
UFLESHExecutor* FLESHExecutor;
|
||||
};
|
||||
|
@@ -23,7 +23,7 @@ public:
|
||||
void OpenFLESHEditorCommand();
|
||||
|
||||
/** Open FLESH Editor */
|
||||
void OpenFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, UObject* ObjectToEdit);
|
||||
void OpenFLESHEditor(const EToolkitMode::Type Mode, const TSharedPtr<IToolkitHost>& InitToolkitHost, TObjectPtr<UObject> ObjectToEdit);
|
||||
|
||||
private:
|
||||
/** Plugin command list */
|
||||
|
141
Source/FLESHEditor/Public/FLESHGraph/FLESHCompiler.h
Normal file
141
Source/FLESHEditor/Public/FLESHGraph/FLESHCompiler.h
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "NiagaraSystem.h"
|
||||
#include "FLESHCompiler.generated.h"
|
||||
|
||||
class UFLESHGraphNode;
|
||||
class UFLESHGraph;
|
||||
|
||||
/**
|
||||
* FLESH node type enum
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EFLESHNodeType : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
Cut UMETA(DisplayName = "Cut"),
|
||||
BloodEffect UMETA(DisplayName = "Blood Effect"),
|
||||
Physics UMETA(DisplayName = "Physics"),
|
||||
Organ UMETA(DisplayName = "Organ"),
|
||||
Wound UMETA(DisplayName = "Wound"),
|
||||
BoneSelection UMETA(DisplayName = "Bone Selection")
|
||||
};
|
||||
|
||||
/**
|
||||
* FLESH node data structure
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FFLESHNodeData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// Node name
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
FName NodeName;
|
||||
|
||||
// Node type
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
EFLESHNodeType NodeType;
|
||||
|
||||
// Node parameters
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, float> FloatParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, FVector> VectorParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, FRotator> RotatorParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, bool> BoolParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, FString> StringParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, TObjectPtr<UObject>> ObjectParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, FName> NameParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, FLinearColor> ColorParameters;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TMap<FName, TObjectPtr<UNiagaraSystem>> NiagaraSystemParameters;
|
||||
|
||||
// Connected nodes
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
TArray<int32> ConnectedNodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* FLESH compiler class
|
||||
* Compiles FLESH graph into executable format
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class FLESHEDITOR_API UFLESHCompiler : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHCompiler();
|
||||
|
||||
// Initialize compiler with graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void Initialize(UFLESHGraph* InGraph);
|
||||
|
||||
// Compile graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool Compile();
|
||||
|
||||
// Get compiled node data
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
TArray<FFLESHNodeData> GetCompiledNodeData() const;
|
||||
|
||||
// Get execution order
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
TArray<int32> GetExecutionOrder() const;
|
||||
|
||||
// Check if compilation was successful
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool IsCompilationSuccessful() const;
|
||||
|
||||
// Get error message
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
FString GetErrorMessage() const;
|
||||
|
||||
private:
|
||||
// Source graph
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHGraph> SourceGraph;
|
||||
|
||||
// Compiled node data
|
||||
UPROPERTY()
|
||||
TArray<FFLESHNodeData> CompiledNodeData;
|
||||
|
||||
// Execution order
|
||||
UPROPERTY()
|
||||
TArray<int32> ExecutionOrder;
|
||||
|
||||
// Compilation status
|
||||
UPROPERTY()
|
||||
bool bCompilationSuccessful;
|
||||
|
||||
// Error message
|
||||
UPROPERTY()
|
||||
FString ErrorMessage;
|
||||
|
||||
// Process node
|
||||
bool ProcessNode(UFLESHGraphNode* Node, int32 NodeIndex);
|
||||
|
||||
// Sort nodes in execution order
|
||||
void SortNodes();
|
||||
|
||||
// Validate graph
|
||||
bool ValidateGraph();
|
||||
};
|
80
Source/FLESHEditor/Public/FLESHGraph/FLESHExecutor.h
Normal file
80
Source/FLESHEditor/Public/FLESHGraph/FLESHExecutor.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "FLESHGraph/FLESHCompiler.h"
|
||||
#include "FLESHExecutor.generated.h"
|
||||
|
||||
class UBooleanCutTool;
|
||||
class USkeletalMeshComponent;
|
||||
|
||||
/**
|
||||
* FLESH executor class
|
||||
* Executes compiled FLESH graph on target actor
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class FLESHEDITOR_API UFLESHExecutor : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHExecutor();
|
||||
|
||||
// Initialize executor with compiler
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void Initialize(UFLESHCompiler* InCompiler);
|
||||
|
||||
// Execute graph on target actor
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool Execute(AActor* InTargetActor);
|
||||
|
||||
// Set boolean cut tool
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void SetCutTool(UBooleanCutTool* InCutTool);
|
||||
|
||||
// Get boolean cut tool
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
UBooleanCutTool* GetCutTool() const;
|
||||
|
||||
private:
|
||||
// Compiler reference
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHCompiler> Compiler;
|
||||
|
||||
// Target actor
|
||||
UPROPERTY()
|
||||
TObjectPtr<AActor> TargetActor;
|
||||
|
||||
// Target skeletal mesh component
|
||||
UPROPERTY()
|
||||
TObjectPtr<USkeletalMeshComponent> TargetSkeletalMesh;
|
||||
|
||||
// Boolean cut tool
|
||||
UPROPERTY()
|
||||
TObjectPtr<UBooleanCutTool> CutTool;
|
||||
|
||||
// Find target skeletal mesh component
|
||||
bool FindTargetSkeletalMesh();
|
||||
|
||||
// Execute node
|
||||
bool ExecuteNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute cut node
|
||||
bool ExecuteCutNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute blood effect node
|
||||
bool ExecuteBloodEffectNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute physics node
|
||||
bool ExecutePhysicsNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute organ node
|
||||
bool ExecuteOrganNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute wound node
|
||||
bool ExecuteWoundNode(const FFLESHNodeData& NodeData);
|
||||
|
||||
// Execute bone selection node
|
||||
bool ExecuteBoneSelectionNode(const FFLESHNodeData& NodeData);
|
||||
};
|
74
Source/FLESHEditor/Public/FLESHGraph/FLESHGraph.h
Normal file
74
Source/FLESHEditor/Public/FLESHGraph/FLESHGraph.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "FLESHGraph.generated.h"
|
||||
|
||||
class UFLESHGraphNode;
|
||||
|
||||
/**
|
||||
* FLESH graph class
|
||||
* Manages nodes in the FLESH editor
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class FLESHEDITOR_API UFLESHGraph : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHGraph();
|
||||
|
||||
// Initialize graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void Initialize();
|
||||
|
||||
// Add node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
UFLESHGraphNode* AddNode(TSubclassOf<UFLESHGraphNode> NodeClass, const FVector2D& Position);
|
||||
|
||||
// Remove node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool RemoveNode(UFLESHGraphNode* Node);
|
||||
|
||||
// Connect nodes
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool ConnectNodes(UFLESHGraphNode* SourceNode, UFLESHGraphNode* TargetNode);
|
||||
|
||||
// Disconnect nodes
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool DisconnectNodes(UFLESHGraphNode* SourceNode, UFLESHGraphNode* TargetNode);
|
||||
|
||||
// Get all nodes
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
TArray<UFLESHGraphNode*> GetAllNodes() const;
|
||||
|
||||
// Get root node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
UFLESHGraphNode* GetRootNode() const;
|
||||
|
||||
// Set root node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void SetRootNode(UFLESHGraphNode* InRootNode);
|
||||
|
||||
// Clear graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void ClearGraph();
|
||||
|
||||
// Save graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool SaveGraph();
|
||||
|
||||
// Load graph
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool LoadGraph();
|
||||
|
||||
private:
|
||||
// All nodes in the graph
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UFLESHGraphNode>> Nodes;
|
||||
|
||||
// Root node
|
||||
UPROPERTY()
|
||||
TObjectPtr<UFLESHGraphNode> RootNode;
|
||||
};
|
93
Source/FLESHEditor/Public/FLESHGraph/FLESHGraphNode.h
Normal file
93
Source/FLESHEditor/Public/FLESHGraph/FLESHGraphNode.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "FLESHCompiler.h"
|
||||
#include "FLESHGraphNode.generated.h"
|
||||
|
||||
/**
|
||||
* FLESH Graph Node Base Class
|
||||
* Base class for all FLESH graph node types
|
||||
*/
|
||||
UCLASS(BlueprintType, Abstract)
|
||||
class FLESHEDITOR_API UFLESHGraphNode : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
UFLESHGraphNode();
|
||||
|
||||
// Get node title
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
virtual FString GetNodeTitle() const;
|
||||
|
||||
// Get node type
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
virtual EFLESHNodeType GetNodeType() const;
|
||||
|
||||
// Get node color
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
virtual FLinearColor GetNodeColor() const;
|
||||
|
||||
// Get node position
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
FVector2D GetNodePosition() const;
|
||||
|
||||
// Set node position
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
void SetNodePosition(const FVector2D& InPosition);
|
||||
|
||||
// Get input nodes
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
TArray<UFLESHGraphNode*> GetInputNodes() const;
|
||||
|
||||
// Get output nodes
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
TArray<UFLESHGraphNode*> GetOutputNodes() const;
|
||||
|
||||
// Add input node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool AddInputNode(UFLESHGraphNode* Node);
|
||||
|
||||
// Add output node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool AddOutputNode(UFLESHGraphNode* Node);
|
||||
|
||||
// Remove input node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool RemoveInputNode(UFLESHGraphNode* Node);
|
||||
|
||||
// Remove output node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
bool RemoveOutputNode(UFLESHGraphNode* Node);
|
||||
|
||||
// Compile node
|
||||
UFUNCTION(BlueprintCallable, Category = "FLESH")
|
||||
virtual bool CompileNode(FFLESHNodeData& OutNodeData);
|
||||
|
||||
protected:
|
||||
// Node title
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
FString NodeTitle;
|
||||
|
||||
// Node type
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
EFLESHNodeType NodeType;
|
||||
|
||||
// Node color
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
FLinearColor NodeColor;
|
||||
|
||||
// Node position
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FLESH")
|
||||
FVector2D NodePosition;
|
||||
|
||||
// Input nodes
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UFLESHGraphNode>> InputNodes;
|
||||
|
||||
// Output nodes
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UFLESHGraphNode>> OutputNodes;
|
||||
};
|
@@ -28,6 +28,18 @@ public:
|
||||
/** Handle mouse clicks - new API */
|
||||
virtual bool InputKey(const FInputKeyEventArgs& EventArgs) override;
|
||||
|
||||
/** Override mouse movement handling to provide camera controls similar to asset editor */
|
||||
virtual bool InputAxis(FViewport* InViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples = 1, bool bGamepad = false) override;
|
||||
|
||||
/** Load and display objects from NodeTree */
|
||||
void LoadNodesFromNodeTree();
|
||||
|
||||
/** Update objects displayed in the viewport */
|
||||
void UpdateVisibleNodes();
|
||||
|
||||
/** Focus on the selected object */
|
||||
void FocusOnSelectedNode();
|
||||
|
||||
/** Reset camera */
|
||||
void ResetCamera();
|
||||
|
||||
@@ -52,4 +64,7 @@ private:
|
||||
|
||||
/** Preview scene for the viewport */
|
||||
TSharedPtr<FPreviewScene> PreviewScene;
|
||||
|
||||
/** Recursively load node and its children */
|
||||
void LoadNodeRecursive(TSharedPtr<FVisceraNodeItem> Node, USceneComponent* ParentComponent);
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
#include "FLESH/Public/BooleanCutTool.h" // Added ECapMeshMethod enum reference
|
||||
#include "VisceraNodeObject.generated.h"
|
||||
|
||||
// Forward declarations
|
||||
|
Reference in New Issue
Block a user