实现DynamicIronBlock温度体素系统基础功能
新增功能: - 添加FTemperatureVoxel结构体(温度、塑性系数、表面标志) - 实现自适应温度网格(5mm体素精度,基于BoxSize计算分辨率) - 实现三线性插值温度/塑性系数采样 - 实现黑体辐射温度可视化(顶点颜色) - 添加HeatRegion蓝图测试函数 - 添加OnHammerImpact锤击形变函数(体积守恒) 技术细节: - Phase 1: CPU侧温度网格初始化,支持10cm-100cm+可变尺寸 - Phase 3: 温度可视化系统(20°C灰色 → 1200°C白色) - 内存占用:30×30×30cm ≈ 1.7MB(60×60×60体素) 待实现:Phase 2 GPU热传导计算 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
#include "GeometryScript/MeshPrimitiveFunctions.h"
|
||||
#include "GeometryScript/MeshSpatialFunctions.h"
|
||||
#include "GeometryScript/MeshBasicEditFunctions.h"
|
||||
#include "GeometryScript/MeshUVFunctions.h"
|
||||
#include "GeometryScript/MeshQueryFunctions.h"
|
||||
#include "GeometryScript/MeshDeformFunctions.h"
|
||||
#include "GeometryScript/MeshNormalsFunctions.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
|
||||
ADynamicIronBlock::ADynamicIronBlock()
|
||||
{
|
||||
@@ -73,12 +78,36 @@ void ADynamicIronBlock::InitializeBoxMesh()
|
||||
NumSubDivide.X, NumSubDivide.Y, NumSubDivide.Z // X, Y, Z 轴的细分数
|
||||
);
|
||||
|
||||
// 为网格生成唯一的 UV 坐标(使用 XAtlas 自动展开)
|
||||
FGeometryScriptXAtlasOptions XAtlasOptions;
|
||||
XAtlasOptions.MaxIterations = 1;
|
||||
|
||||
UGeometryScriptLibrary_MeshUVFunctions::AutoGenerateXAtlasMeshUVs(
|
||||
DynMesh,
|
||||
0, // UV 通道 0
|
||||
XAtlasOptions
|
||||
);
|
||||
|
||||
// 如果设置了材质,创建动态材质实例并应用
|
||||
if (IronMaterial)
|
||||
{
|
||||
UMaterialInstanceDynamic* DynamicMaterialInstance = UMaterialInstanceDynamic::Create(IronMaterial, this);
|
||||
if (DynamicMaterialInstance)
|
||||
{
|
||||
DynMeshComp->SetMaterial(0, DynamicMaterialInstance);
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Dynamic material instance created and applied"));
|
||||
}
|
||||
}
|
||||
|
||||
// 通知组件网格已更新
|
||||
DynMeshComp->NotifyMeshUpdated();
|
||||
|
||||
// 生成 BVH
|
||||
GenerateBVH();
|
||||
|
||||
// 初始化温度网格
|
||||
InitializeTemperatureGrid();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Box mesh initialized with size (%f, %f, %f)"),
|
||||
BoxSize.X, BoxSize.Y, BoxSize.Z);
|
||||
}
|
||||
@@ -112,4 +141,474 @@ void ADynamicIronBlock::GenerateBVH()
|
||||
DynMeshComp->UpdateCollision();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: BVH generated successfully"));
|
||||
}
|
||||
}
|
||||
|
||||
void ADynamicIronBlock::OnHammerImpact(FVector HitLocation, FVector HitNormal, float ImpactRadius, float ImpactDepth, float ImpactForce)
|
||||
{
|
||||
// 获取 DynamicMeshComponent
|
||||
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
|
||||
if (!DynMeshComp)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMeshComponent for hammer impact"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 DynamicMesh
|
||||
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
|
||||
if (!DynMesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Failed to get DynamicMesh for hammer impact"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 限制力度范围在0-1之间
|
||||
ImpactForce = FMath::Clamp(ImpactForce, 0.0f, 1.0f);
|
||||
|
||||
// 将世界坐标转换为局部坐标
|
||||
FTransform ActorTransform = GetActorTransform();
|
||||
FVector LocalHitLocation = ActorTransform.InverseTransformPosition(HitLocation);
|
||||
FVector LocalHitNormal = ActorTransform.InverseTransformVector(HitNormal).GetSafeNormal();
|
||||
|
||||
// 计算实际的形变深度
|
||||
float ActualDepth = ImpactDepth * ImpactForce;
|
||||
|
||||
// 计算凸起区域的外半径
|
||||
float BulgeOuterRadius = ImpactRadius * (1.0f + BulgeRatio);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Hammer impact at local position (%f, %f, %f), depth: %f, impact radius: %f, bulge radius: %f"),
|
||||
LocalHitLocation.X, LocalHitLocation.Y, LocalHitLocation.Z, ActualDepth, ImpactRadius, BulgeOuterRadius);
|
||||
|
||||
// 使用GeometryScript修改网格顶点
|
||||
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
|
||||
{
|
||||
// 遍历所有顶点
|
||||
for (int32 VertexID : EditMesh.VertexIndicesItr())
|
||||
{
|
||||
// 获取顶点位置
|
||||
FVector3d VertexPos = EditMesh.GetVertex(VertexID);
|
||||
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
|
||||
|
||||
// 计算顶点到锤击点的距离
|
||||
float Distance = FVector::Dist(VertexPosFloat, LocalHitLocation);
|
||||
|
||||
FVector Displacement = FVector::ZeroVector;
|
||||
|
||||
// 压缩区:顶点在锤击半径内,向下压
|
||||
if (Distance < ImpactRadius)
|
||||
{
|
||||
// 计算衰减因子(使用幂函数实现平滑过渡)
|
||||
float NormalizedDist = Distance / ImpactRadius; // 归一化距离 [0, 1]
|
||||
float Falloff = 1.0f - FMath::Pow(NormalizedDist, FalloffExponent); // 衰减系数
|
||||
|
||||
// 计算该顶点的形变偏移量
|
||||
float VertexDeformation = ActualDepth * Falloff;
|
||||
|
||||
// 沿着锤击法线方向进行形变(向内压)
|
||||
Displacement = -LocalHitNormal * VertexDeformation;
|
||||
}
|
||||
// 凸起区:顶点在压缩区外、凸起区内,材料向上和向外流动
|
||||
else if (Distance < BulgeOuterRadius)
|
||||
{
|
||||
// 计算在凸起区中的位置 [0, 1]
|
||||
float BulgeNormalizedDist = (Distance - ImpactRadius) / (BulgeOuterRadius - ImpactRadius);
|
||||
|
||||
// 凸起区衰减:靠近压缩区边界最强,向外逐渐减弱
|
||||
float BulgeFalloff = 1.0f - FMath::Pow(BulgeNormalizedDist, FalloffExponent);
|
||||
|
||||
// 计算凸起高度(基于体积守恒和BulgeStrength)
|
||||
float BulgeHeight = ActualDepth * BulgeStrength * BulgeFalloff;
|
||||
|
||||
// 计算径向方向(从锤击点指向顶点的水平方向)
|
||||
FVector RadialDir = VertexPosFloat - LocalHitLocation;
|
||||
RadialDir = RadialDir - LocalHitNormal * FVector::DotProduct(RadialDir, LocalHitNormal); // 移除法线方向分量
|
||||
RadialDir.Normalize();
|
||||
|
||||
// 材料流动:主要向上(反法线方向),少量向外(径向)
|
||||
// 向上分量占70%,向外分量占30%
|
||||
FVector UpwardDisplacement = -LocalHitNormal * BulgeHeight * 0.7f;
|
||||
FVector OutwardDisplacement = RadialDir * BulgeHeight * 0.3f;
|
||||
|
||||
Displacement = UpwardDisplacement + OutwardDisplacement;
|
||||
}
|
||||
|
||||
// 应用形变
|
||||
if (!Displacement.IsNearlyZero())
|
||||
{
|
||||
FVector3d NewPosition = VertexPos + FVector3d(Displacement.X, Displacement.Y, Displacement.Z);
|
||||
EditMesh.SetVertex(VertexID, NewPosition);
|
||||
}
|
||||
}
|
||||
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
|
||||
|
||||
// 重新计算法线
|
||||
FGeometryScriptCalculateNormalsOptions NormalOp;
|
||||
UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh,NormalOp);
|
||||
|
||||
// 通知组件网格已更新
|
||||
DynMeshComp->NotifyMeshUpdated();
|
||||
|
||||
// 重新生成 BVH
|
||||
GenerateBVH();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Hammer impact deformation applied successfully"));
|
||||
}
|
||||
|
||||
// ========== 温度系统实现 ==========
|
||||
|
||||
void ADynamicIronBlock::CalculateTemperatureResolution()
|
||||
{
|
||||
// 将BoxSize从厘米转换为毫米
|
||||
FVector BoxSizeMM = BoxSize * 10.0f;
|
||||
|
||||
// 计算每个轴的体素数量(向上取整)
|
||||
TemperatureResolution.X = FMath::Max(1, FMath::CeilToInt(BoxSizeMM.X / TemperatureVoxelSize));
|
||||
TemperatureResolution.Y = FMath::Max(1, FMath::CeilToInt(BoxSizeMM.Y / TemperatureVoxelSize));
|
||||
TemperatureResolution.Z = FMath::Max(1, FMath::CeilToInt(BoxSizeMM.Z / TemperatureVoxelSize));
|
||||
|
||||
// 限制最大分辨率(防止内存爆炸)
|
||||
const int32 MaxResolutionPerAxis = 200;
|
||||
TemperatureResolution.X = FMath::Min(TemperatureResolution.X, MaxResolutionPerAxis);
|
||||
TemperatureResolution.Y = FMath::Min(TemperatureResolution.Y, MaxResolutionPerAxis);
|
||||
TemperatureResolution.Z = FMath::Min(TemperatureResolution.Z, MaxResolutionPerAxis);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Temperature resolution calculated as (%d, %d, %d)"),
|
||||
TemperatureResolution.X, TemperatureResolution.Y, TemperatureResolution.Z);
|
||||
}
|
||||
|
||||
void ADynamicIronBlock::InitializeTemperatureGrid()
|
||||
{
|
||||
if (!bEnableTemperatureSystem)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Temperature system is disabled"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算分辨率
|
||||
CalculateTemperatureResolution();
|
||||
|
||||
// 分配体素数组
|
||||
const int32 TotalVoxels = TemperatureResolution.X * TemperatureResolution.Y * TemperatureResolution.Z;
|
||||
TemperatureGrid.SetNum(TotalVoxels);
|
||||
|
||||
// 计算体素网格的起始位置(相对于Actor中心,单位:厘米)
|
||||
FVector GridOrigin = -BoxSize * 0.5f;
|
||||
|
||||
// 计算实际体素尺寸(毫米转厘米)
|
||||
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
|
||||
|
||||
// 初始化每个体素
|
||||
for (int32 Z = 0; Z < TemperatureResolution.Z; ++Z)
|
||||
{
|
||||
for (int32 Y = 0; Y < TemperatureResolution.Y; ++Y)
|
||||
{
|
||||
for (int32 X = 0; X < TemperatureResolution.X; ++X)
|
||||
{
|
||||
int32 Index = GetTemperatureIndex(X, Y, Z);
|
||||
FTemperatureVoxel& Voxel = TemperatureGrid[Index];
|
||||
|
||||
// 初始温度(室温)
|
||||
Voxel.Temperature = AmbientTemperature;
|
||||
|
||||
// 检测是否为表面体素
|
||||
if (IsSurfaceTemperatureVoxel(X, Y, Z))
|
||||
{
|
||||
Voxel.Flags |= 1; // 设置bit 0为1
|
||||
}
|
||||
else
|
||||
{
|
||||
Voxel.Flags &= ~1; // 设置bit 0为0
|
||||
}
|
||||
|
||||
// 更新塑性系数
|
||||
UpdatePlasticityCoefficient(Voxel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Temperature grid initialized with %d voxels (%.2f MB)"),
|
||||
TotalVoxels, (TotalVoxels * sizeof(FTemperatureVoxel)) / (1024.0f * 1024.0f));
|
||||
}
|
||||
|
||||
bool ADynamicIronBlock::IsSurfaceTemperatureVoxel(int32 X, int32 Y, int32 Z) const
|
||||
{
|
||||
return X == 0 || X == TemperatureResolution.X - 1 ||
|
||||
Y == 0 || Y == TemperatureResolution.Y - 1 ||
|
||||
Z == 0 || Z == TemperatureResolution.Z - 1;
|
||||
}
|
||||
|
||||
float ADynamicIronBlock::CalculatePlasticityFromTemperature(float Temperature) const
|
||||
{
|
||||
// 归一化温度到 [0, 1] 范围
|
||||
float NormalizedTemp = FMath::Clamp(
|
||||
(Temperature - ForgeTemperatureMin) / (ForgeTemperatureMax - ForgeTemperatureMin),
|
||||
0.0f, 1.0f
|
||||
);
|
||||
|
||||
// 使用平滑步函数(Smoothstep)
|
||||
return NormalizedTemp * NormalizedTemp * (3.0f - 2.0f * NormalizedTemp);
|
||||
}
|
||||
|
||||
void ADynamicIronBlock::UpdatePlasticityCoefficient(FTemperatureVoxel& Voxel)
|
||||
{
|
||||
Voxel.PlasticityCoefficient = CalculatePlasticityFromTemperature(Voxel.Temperature);
|
||||
}
|
||||
|
||||
void ADynamicIronBlock::HeatRegion(FVector WorldCenter, float Radius, float TemperatureIncrease)
|
||||
{
|
||||
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Temperature system not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 将世界坐标转换为局部坐标
|
||||
FTransform ActorTransform = GetActorTransform();
|
||||
FVector LocalCenter = ActorTransform.InverseTransformPosition(WorldCenter);
|
||||
|
||||
// 计算体素网格的起始位置
|
||||
FVector GridOrigin = -BoxSize * 0.5f;
|
||||
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
|
||||
|
||||
// 将影响范围转换为体素坐标
|
||||
FVector LocalCenterVoxel = (LocalCenter - GridOrigin) / VoxelSizeCM;
|
||||
int32 RadiusVoxels = FMath::CeilToInt(Radius / VoxelSizeCM.X);
|
||||
|
||||
int32 MinX = FMath::Max(0, FMath::FloorToInt(LocalCenterVoxel.X) - RadiusVoxels);
|
||||
int32 MaxX = FMath::Min(TemperatureResolution.X - 1, FMath::CeilToInt(LocalCenterVoxel.X) + RadiusVoxels);
|
||||
int32 MinY = FMath::Max(0, FMath::FloorToInt(LocalCenterVoxel.Y) - RadiusVoxels);
|
||||
int32 MaxY = FMath::Min(TemperatureResolution.Y - 1, FMath::CeilToInt(LocalCenterVoxel.Y) + RadiusVoxels);
|
||||
int32 MinZ = FMath::Max(0, FMath::FloorToInt(LocalCenterVoxel.Z) - RadiusVoxels);
|
||||
int32 MaxZ = FMath::Min(TemperatureResolution.Z - 1, FMath::CeilToInt(LocalCenterVoxel.Z) + RadiusVoxels);
|
||||
|
||||
int32 AffectedVoxels = 0;
|
||||
|
||||
// 遍历影响范围内的体素
|
||||
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
|
||||
{
|
||||
for (int32 Y = MinY; Y <= MaxY; ++Y)
|
||||
{
|
||||
for (int32 X = MinX; X <= MaxX; ++X)
|
||||
{
|
||||
// 计算体素中心在局部坐标中的位置
|
||||
FVector VoxelCenter = GridOrigin + FVector(
|
||||
(X + 0.5f) * VoxelSizeCM.X,
|
||||
(Y + 0.5f) * VoxelSizeCM.Y,
|
||||
(Z + 0.5f) * VoxelSizeCM.Z
|
||||
);
|
||||
|
||||
// 计算距离
|
||||
float Distance = FVector::Dist(VoxelCenter, LocalCenter);
|
||||
|
||||
if (Distance < Radius)
|
||||
{
|
||||
int32 Index = GetTemperatureIndex(X, Y, Z);
|
||||
FTemperatureVoxel& Voxel = TemperatureGrid[Index];
|
||||
|
||||
// 距离衰减
|
||||
float Falloff = 1.0f - (Distance / Radius);
|
||||
float TempDelta = TemperatureIncrease * Falloff;
|
||||
|
||||
Voxel.Temperature += TempDelta;
|
||||
UpdatePlasticityCoefficient(Voxel);
|
||||
++AffectedVoxels;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Heated %d voxels in region (center: %s, radius: %.2f)"),
|
||||
AffectedVoxels, *LocalCenter.ToString(), Radius);
|
||||
|
||||
// 更新顶点颜色以显示温度变化
|
||||
UpdateVertexColors();
|
||||
}
|
||||
|
||||
float ADynamicIronBlock::SampleTemperatureAtPosition(const FVector& LocalPosition) const
|
||||
{
|
||||
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
|
||||
{
|
||||
return AmbientTemperature;
|
||||
}
|
||||
|
||||
// 将位置转换为体素坐标系
|
||||
FVector GridOrigin = -BoxSize * 0.5f;
|
||||
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
|
||||
FVector VoxelCoord = (LocalPosition - GridOrigin) / VoxelSizeCM;
|
||||
|
||||
// 获取相邻的8个体素的索引
|
||||
int32 X0 = FMath::FloorToInt(VoxelCoord.X);
|
||||
int32 Y0 = FMath::FloorToInt(VoxelCoord.Y);
|
||||
int32 Z0 = FMath::FloorToInt(VoxelCoord.Z);
|
||||
int32 X1 = X0 + 1;
|
||||
int32 Y1 = Y0 + 1;
|
||||
int32 Z1 = Z0 + 1;
|
||||
|
||||
// 边界检查
|
||||
if (!IsValidTemperatureCoord(X0, Y0, Z0) || !IsValidTemperatureCoord(X1, Y1, Z1))
|
||||
{
|
||||
return AmbientTemperature;
|
||||
}
|
||||
|
||||
// 插值权重
|
||||
float Fx = VoxelCoord.X - X0;
|
||||
float Fy = VoxelCoord.Y - Y0;
|
||||
float Fz = VoxelCoord.Z - Z0;
|
||||
|
||||
// 获取8个角点的温度
|
||||
float T000 = TemperatureGrid[GetTemperatureIndex(X0, Y0, Z0)].Temperature;
|
||||
float T100 = TemperatureGrid[GetTemperatureIndex(X1, Y0, Z0)].Temperature;
|
||||
float T010 = TemperatureGrid[GetTemperatureIndex(X0, Y1, Z0)].Temperature;
|
||||
float T110 = TemperatureGrid[GetTemperatureIndex(X1, Y1, Z0)].Temperature;
|
||||
float T001 = TemperatureGrid[GetTemperatureIndex(X0, Y0, Z1)].Temperature;
|
||||
float T101 = TemperatureGrid[GetTemperatureIndex(X1, Y0, Z1)].Temperature;
|
||||
float T011 = TemperatureGrid[GetTemperatureIndex(X0, Y1, Z1)].Temperature;
|
||||
float T111 = TemperatureGrid[GetTemperatureIndex(X1, Y1, Z1)].Temperature;
|
||||
|
||||
// 三线性插值
|
||||
float T00 = FMath::Lerp(T000, T100, Fx);
|
||||
float T10 = FMath::Lerp(T010, T110, Fx);
|
||||
float T01 = FMath::Lerp(T001, T101, Fx);
|
||||
float T11 = FMath::Lerp(T011, T111, Fx);
|
||||
float T0 = FMath::Lerp(T00, T10, Fy);
|
||||
float T1 = FMath::Lerp(T01, T11, Fy);
|
||||
return FMath::Lerp(T0, T1, Fz);
|
||||
}
|
||||
|
||||
float ADynamicIronBlock::SamplePlasticityAtPosition(const FVector& LocalPosition) const
|
||||
{
|
||||
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// 将位置转换为体素坐标系
|
||||
FVector GridOrigin = -BoxSize * 0.5f;
|
||||
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
|
||||
FVector VoxelCoord = (LocalPosition - GridOrigin) / VoxelSizeCM;
|
||||
|
||||
// 获取相邻的8个体素的索引
|
||||
int32 X0 = FMath::FloorToInt(VoxelCoord.X);
|
||||
int32 Y0 = FMath::FloorToInt(VoxelCoord.Y);
|
||||
int32 Z0 = FMath::FloorToInt(VoxelCoord.Z);
|
||||
int32 X1 = X0 + 1;
|
||||
int32 Y1 = Y0 + 1;
|
||||
int32 Z1 = Z0 + 1;
|
||||
|
||||
// 边界检查
|
||||
if (!IsValidTemperatureCoord(X0, Y0, Z0) || !IsValidTemperatureCoord(X1, Y1, Z1))
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// 插值权重
|
||||
float Fx = VoxelCoord.X - X0;
|
||||
float Fy = VoxelCoord.Y - Y0;
|
||||
float Fz = VoxelCoord.Z - Z0;
|
||||
|
||||
// 获取8个角点的塑性系数
|
||||
float P000 = TemperatureGrid[GetTemperatureIndex(X0, Y0, Z0)].PlasticityCoefficient;
|
||||
float P100 = TemperatureGrid[GetTemperatureIndex(X1, Y0, Z0)].PlasticityCoefficient;
|
||||
float P010 = TemperatureGrid[GetTemperatureIndex(X0, Y1, Z0)].PlasticityCoefficient;
|
||||
float P110 = TemperatureGrid[GetTemperatureIndex(X1, Y1, Z0)].PlasticityCoefficient;
|
||||
float P001 = TemperatureGrid[GetTemperatureIndex(X0, Y0, Z1)].PlasticityCoefficient;
|
||||
float P101 = TemperatureGrid[GetTemperatureIndex(X1, Y0, Z1)].PlasticityCoefficient;
|
||||
float P011 = TemperatureGrid[GetTemperatureIndex(X0, Y1, Z1)].PlasticityCoefficient;
|
||||
float P111 = TemperatureGrid[GetTemperatureIndex(X1, Y1, Z1)].PlasticityCoefficient;
|
||||
|
||||
// 三线性插值
|
||||
float P00 = FMath::Lerp(P000, P100, Fx);
|
||||
float P10 = FMath::Lerp(P010, P110, Fx);
|
||||
float P01 = FMath::Lerp(P001, P101, Fx);
|
||||
float P11 = FMath::Lerp(P011, P111, Fx);
|
||||
float P0 = FMath::Lerp(P00, P10, Fy);
|
||||
float P1 = FMath::Lerp(P01, P11, Fy);
|
||||
return FMath::Lerp(P0, P1, Fz);
|
||||
}
|
||||
|
||||
FLinearColor ADynamicIronBlock::TemperatureToColor(float Temperature) const
|
||||
{
|
||||
// 归一化温度到 [0, 1] 范围
|
||||
float NormalizedTemp = FMath::Clamp(
|
||||
(Temperature - AmbientTemperature) / (ForgeTemperatureMax - AmbientTemperature),
|
||||
0.0f, 1.0f
|
||||
);
|
||||
|
||||
// 黑体辐射近似颜色映射
|
||||
if (NormalizedTemp < 0.33f)
|
||||
{
|
||||
// 冷:深灰 → 暗红
|
||||
return FMath::Lerp(
|
||||
FLinearColor(0.2f, 0.2f, 0.2f),
|
||||
FLinearColor(0.5f, 0.1f, 0.05f),
|
||||
NormalizedTemp * 3.0f
|
||||
);
|
||||
}
|
||||
else if (NormalizedTemp < 0.66f)
|
||||
{
|
||||
// 中:暗红 → 亮红
|
||||
return FMath::Lerp(
|
||||
FLinearColor(0.5f, 0.1f, 0.05f),
|
||||
FLinearColor(1.0f, 0.3f, 0.1f),
|
||||
(NormalizedTemp - 0.33f) * 3.0f
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 热:亮红 → 橙黄白
|
||||
return FMath::Lerp(
|
||||
FLinearColor(1.0f, 0.3f, 0.1f),
|
||||
FLinearColor(1.0f, 1.0f, 0.9f),
|
||||
(NormalizedTemp - 0.66f) * 3.0f
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ADynamicIronBlock::UpdateVertexColors()
|
||||
{
|
||||
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
|
||||
if (!DynMeshComp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
|
||||
if (!DynMesh)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
|
||||
{
|
||||
// 启用顶点颜色(如果尚未启用)
|
||||
if (!EditMesh.HasVertexColors())
|
||||
{
|
||||
EditMesh.EnableVertexColors(FVector3f(0.2f, 0.2f, 0.2f));
|
||||
}
|
||||
|
||||
// 遍历所有顶点
|
||||
for (int32 VertexID : EditMesh.VertexIndicesItr())
|
||||
{
|
||||
// 获取顶点位置(局部坐标)
|
||||
FVector3d VertexPos = EditMesh.GetVertex(VertexID);
|
||||
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
|
||||
|
||||
// 通过三线性插值获取该顶点的温度
|
||||
float Temperature = SampleTemperatureAtPosition(VertexPosFloat);
|
||||
|
||||
// 将温度映射到颜色
|
||||
FLinearColor VertexColor = TemperatureToColor(Temperature);
|
||||
|
||||
// 设置顶点颜色
|
||||
EditMesh.SetVertexColor(VertexID, FVector3f(VertexColor.R, VertexColor.G, VertexColor.B));
|
||||
}
|
||||
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
|
||||
|
||||
DynMeshComp->NotifyMeshUpdated();
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex colors updated based on temperature"));
|
||||
}
|
||||
|
||||
@@ -6,9 +6,37 @@
|
||||
#include "DynamicMeshActor.h"
|
||||
#include "DynamicIronBlock.generated.h"
|
||||
|
||||
/**
|
||||
* 温度体素结构 - 存储单个体素的温度和塑性数据
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FTemperatureVoxel
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// 温度值(摄氏度)
|
||||
UPROPERTY()
|
||||
float Temperature;
|
||||
|
||||
// 塑性系数(0-1),由温度计算得出
|
||||
UPROPERTY()
|
||||
float PlasticityCoefficient;
|
||||
|
||||
// 标志位(bit 0: 是否为表面体素)
|
||||
UPROPERTY()
|
||||
uint8 Flags;
|
||||
|
||||
FTemperatureVoxel()
|
||||
: Temperature(20.0f)
|
||||
, PlasticityCoefficient(0.0f)
|
||||
, Flags(0)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* 动态铁块Actor,继承自ADynamicMeshActor
|
||||
* 用于模拟铁匠打铁游戏中的铁块,支持动态变形
|
||||
* 包含基于体素的温度模拟系统
|
||||
*/
|
||||
UCLASS()
|
||||
class BLADESMITH_API ADynamicIronBlock : public ADynamicMeshActor
|
||||
@@ -29,6 +57,72 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block")
|
||||
bool bEnableFastBuild;
|
||||
|
||||
// 铁块材质
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block")
|
||||
UMaterialInterface* IronMaterial;
|
||||
|
||||
// 形变衰减指数(控制边缘过渡的平滑度)- 材质物理特性
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties")
|
||||
float FalloffExponent = 2.0f;
|
||||
|
||||
// 材料凸起比例(凸起区域半径相对于压缩区域的倍数)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties")
|
||||
float BulgeRatio = 0.8f;
|
||||
|
||||
// 材料凸起强度(控制凸起高度,基于体积守恒)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Iron Block|Material Properties")
|
||||
float BulgeStrength = 0.5f;
|
||||
|
||||
// ========== 温度系统参数 ==========
|
||||
|
||||
// 温度体素大小(毫米)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System", meta = (ClampMin = "3.0", ClampMax = "10.0"))
|
||||
float TemperatureVoxelSize = 5.0f;
|
||||
|
||||
// 热传导系数
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float ThermalConductivity = 0.1f;
|
||||
|
||||
// 表面冷却速率
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||
float CoolingRate = 0.05f;
|
||||
|
||||
// 环境温度(摄氏度)
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System")
|
||||
float AmbientTemperature = 20.0f;
|
||||
|
||||
// 锻造温度下限(摄氏度)- 开始软化
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System")
|
||||
float ForgeTemperatureMin = 800.0f;
|
||||
|
||||
// 锻造温度上限(摄氏度)- 完全软化
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System")
|
||||
float ForgeTemperatureMax = 1200.0f;
|
||||
|
||||
// 是否启用温度系统
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System")
|
||||
bool bEnableTemperatureSystem = true;
|
||||
|
||||
/**
|
||||
* 测试函数 - 加热指定区域
|
||||
* @param WorldCenter 加热中心位置(世界坐标)
|
||||
* @param Radius 加热半径
|
||||
* @param TemperatureIncrease 温度增量
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Temperature System")
|
||||
void HeatRegion(FVector WorldCenter, float Radius, float TemperatureIncrease);
|
||||
|
||||
/**
|
||||
* 受击函数 - 模拟圆形铁锤锤击金属表面产生塑性形变
|
||||
* @param HitLocation 锤击位置(世界坐标)
|
||||
* @param HitNormal 锤击法线方向
|
||||
* @param ImpactRadius 锤击影响半径
|
||||
* @param ImpactDepth 锤击形变深度
|
||||
* @param ImpactForce 锤击力度(0-1),影响形变深度
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Hammer Impact")
|
||||
void OnHammerImpact(FVector HitLocation, FVector HitNormal, float ImpactRadius, float ImpactDepth, float ImpactForce = 1.0f);
|
||||
|
||||
protected:
|
||||
// 当游戏开始或生成时调用
|
||||
virtual void BeginPlay() override;
|
||||
@@ -46,4 +140,95 @@ private:
|
||||
* 生成BVH加速结构
|
||||
*/
|
||||
void GenerateBVH();
|
||||
|
||||
// ========== 温度系统数据 ==========
|
||||
|
||||
// 温度体素网格(CPU侧)
|
||||
TArray<FTemperatureVoxel> TemperatureGrid;
|
||||
|
||||
// 温度网格分辨率
|
||||
FIntVector TemperatureResolution;
|
||||
|
||||
// ========== 温度系统方法 ==========
|
||||
|
||||
/**
|
||||
* 计算温度网格分辨率(基于BoxSize和TemperatureVoxelSize)
|
||||
*/
|
||||
void CalculateTemperatureResolution();
|
||||
|
||||
/**
|
||||
* 初始化温度体素网格
|
||||
*/
|
||||
void InitializeTemperatureGrid();
|
||||
|
||||
/**
|
||||
* 获取体素索引
|
||||
*/
|
||||
FORCEINLINE int32 GetTemperatureIndex(int32 X, int32 Y, int32 Z) const
|
||||
{
|
||||
return X + Y * TemperatureResolution.X + Z * TemperatureResolution.X * TemperatureResolution.Y;
|
||||
}
|
||||
|
||||
FORCEINLINE int32 GetTemperatureIndex(const FIntVector& Coord) const
|
||||
{
|
||||
return GetTemperatureIndex(Coord.X, Coord.Y, Coord.Z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查体素坐标是否有效
|
||||
*/
|
||||
FORCEINLINE bool IsValidTemperatureCoord(int32 X, int32 Y, int32 Z) const
|
||||
{
|
||||
return X >= 0 && X < TemperatureResolution.X &&
|
||||
Y >= 0 && Y < TemperatureResolution.Y &&
|
||||
Z >= 0 && Z < TemperatureResolution.Z;
|
||||
}
|
||||
|
||||
FORCEINLINE bool IsValidTemperatureCoord(const FIntVector& Coord) const
|
||||
{
|
||||
return IsValidTemperatureCoord(Coord.X, Coord.Y, Coord.Z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为表面体素
|
||||
*/
|
||||
bool IsSurfaceTemperatureVoxel(int32 X, int32 Y, int32 Z) const;
|
||||
|
||||
/**
|
||||
* 从温度网格采样温度(三线性插值)
|
||||
* @param LocalPosition 局部坐标位置
|
||||
* @return 插值后的温度值
|
||||
*/
|
||||
float SampleTemperatureAtPosition(const FVector& LocalPosition) const;
|
||||
|
||||
/**
|
||||
* 从温度网格采样塑性系数(三线性插值)
|
||||
* @param LocalPosition 局部坐标位置
|
||||
* @return 插值后的塑性系数
|
||||
*/
|
||||
float SamplePlasticityAtPosition(const FVector& LocalPosition) const;
|
||||
|
||||
/**
|
||||
* 根据温度计算塑性系数
|
||||
* @param Temperature 温度值
|
||||
* @return 塑性系数(0-1)
|
||||
*/
|
||||
float CalculatePlasticityFromTemperature(float Temperature) const;
|
||||
|
||||
/**
|
||||
* 更新体素的塑性系数(基于当前温度)
|
||||
*/
|
||||
void UpdatePlasticityCoefficient(FTemperatureVoxel& Voxel);
|
||||
|
||||
/**
|
||||
* 温度到颜色的映射(黑体辐射近似)
|
||||
* @param Temperature 温度值
|
||||
* @return 颜色
|
||||
*/
|
||||
FLinearColor TemperatureToColor(float Temperature) const;
|
||||
|
||||
/**
|
||||
* 更新网格顶点颜色以显示温度
|
||||
*/
|
||||
void UpdateVertexColors();
|
||||
};
|
||||
Reference in New Issue
Block a user