514 lines
23 KiB
C#
514 lines
23 KiB
C#
using System;
|
||
using System.Linq; // Add Linq for FirstOrDefault
|
||
using ThreatSource.Equipment;
|
||
using ThreatSource.Utils;
|
||
using AirTransmission;
|
||
using ThreatSource.Jammer; // 添加引用
|
||
using ThreatSource.Simulation; // Add Simulation namespace
|
||
|
||
namespace ThreatSource.Guidance
|
||
{
|
||
/// <summary>
|
||
/// 红外图像生成器,用于生成目标的红外图像
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 该类实现红外图像的生成过程:
|
||
/// - 计算目标在图像平面上的投影
|
||
/// - 根据目标特性生成红外强度
|
||
/// - 添加背景噪声
|
||
/// - 考虑大气透过率影响
|
||
/// </remarks>
|
||
public class InfraredImageGenerator
|
||
{
|
||
/// <summary>
|
||
/// 图像宽度,单位:像素
|
||
/// </summary>
|
||
private readonly int imageWidth;
|
||
|
||
/// <summary>
|
||
/// 图像高度,单位:像素
|
||
/// </summary>
|
||
private readonly int imageHeight;
|
||
|
||
/// <summary>
|
||
/// 视场角,单位:弧度
|
||
/// </summary>
|
||
private readonly double fieldOfView;
|
||
|
||
/// <summary>
|
||
/// 背景辐射强度,单位:W/sr
|
||
/// </summary>
|
||
private readonly double backgroundIntensity;
|
||
|
||
/// <summary>
|
||
/// 红外波长,单位:微米
|
||
/// </summary>
|
||
private readonly double wavelength;
|
||
|
||
/// <summary>
|
||
/// 随机数生成器
|
||
/// </summary>
|
||
private readonly Random random = new();
|
||
|
||
/// <summary>
|
||
/// 烟幕温度转换为强度的比例因子 (简化 T^4 关系)
|
||
/// </summary>
|
||
private const double SMOKE_TEMPERATURE_FACTOR = 1e-11;
|
||
|
||
/// <summary>
|
||
/// 目标温度(K)转换为强度(W/sr)的比例因子 (基于简化 T^4) - **需要调整**
|
||
/// </summary>
|
||
private const double TEMPERATURE_TO_INTENSITY_FACTOR = 1e-6; // Initial guess 1e-11, changed to 1e-7, now 1e-6
|
||
private const double KELVIN_OFFSET = 273.15;
|
||
|
||
// 视线坐标系
|
||
private Vector3D forward; // 视线方向
|
||
private Vector3D right; // 图像平面右方向
|
||
private Vector3D up; // 图像平面上方向
|
||
|
||
/// <summary>
|
||
/// 初始化红外图像生成器的新实例
|
||
/// </summary>
|
||
/// <param name="imageWidth">图像宽度</param>
|
||
/// <param name="imageHeight">图像高度</param>
|
||
/// <param name="fieldOfView">视场角</param>
|
||
/// <param name="backgroundIntensity">背景辐射强度,单位:W/sr,典型地表背景约0.01-0.1 W/sr</param>
|
||
/// <param name="wavelength">红外波长,单位:微米,默认为3.0(中波红外)</param>
|
||
public InfraredImageGenerator(
|
||
int imageWidth = 640,
|
||
int imageHeight = 512,
|
||
double fieldOfView = Math.PI / 18,
|
||
double backgroundIntensity = 0.01,
|
||
double wavelength = 3.0)
|
||
{
|
||
this.imageWidth = imageWidth;
|
||
this.imageHeight = imageHeight;
|
||
this.fieldOfView = fieldOfView;
|
||
this.backgroundIntensity = backgroundIntensity;
|
||
this.wavelength = wavelength;
|
||
|
||
// Initialize coordinate system with default values
|
||
forward = Vector3D.UnitZ;
|
||
right = Vector3D.UnitX;
|
||
up = Vector3D.UnitY;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新视线坐标系
|
||
/// </summary>
|
||
private void UpdateLineOfSightFrame(Vector3D missilePosition, Vector3D targetPosition)
|
||
{
|
||
// 计算视线方向
|
||
forward = (targetPosition - missilePosition).Normalize();
|
||
|
||
Console.WriteLine($"视线方向: {forward}");
|
||
|
||
// 选择合适的上方向
|
||
Vector3D worldUp = Math.Abs(Vector3D.DotProduct(forward, Vector3D.UnitZ)) > 0.99
|
||
? Vector3D.UnitY
|
||
: Vector3D.UnitZ;
|
||
|
||
// 计算右方向和上方向
|
||
right = Vector3D.CrossProduct(forward, worldUp).Normalize();
|
||
up = Vector3D.CrossProduct(right, forward);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将世界坐标投影到图像平面
|
||
/// </summary>
|
||
private (double x, double y) ProjectToImagePlane(Vector3D worldPoint, Vector3D origin)
|
||
{
|
||
Vector3D relativePos = worldPoint - origin;
|
||
|
||
// 计算在图像平面上的投影
|
||
double x = Vector3D.DotProduct(relativePos, right);
|
||
double y = Vector3D.DotProduct(relativePos, up);
|
||
double z = Vector3D.DotProduct(relativePos, forward);
|
||
|
||
// 转换为角度
|
||
double angleX = Math.Atan2(x, z);
|
||
double angleY = Math.Atan2(y, z);
|
||
|
||
Console.WriteLine($"投影角度: X={angleX:F6} 弧度, Y={angleY:F6} 弧度");
|
||
|
||
return (angleX, angleY);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将角度转换为像素坐标
|
||
/// </summary>
|
||
private (int x, int y) AngleToPixel(double angleX, double angleY)
|
||
{
|
||
double pixelSize = fieldOfView / imageWidth;
|
||
|
||
int pixelX = imageWidth/2 + (int)(angleX / pixelSize);
|
||
int pixelY = imageHeight/2 + (int)(angleY / pixelSize);
|
||
|
||
return (pixelX, pixelY);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算大气透过率
|
||
/// </summary>
|
||
/// <param name="distance">距离(米)</param>
|
||
/// <param name="weather">天气条件</param>
|
||
/// <param name="smokeAttenuation">烟幕衰减</param>
|
||
/// <returns>大气透过率(0-1之间)</returns>
|
||
private double CalculateAtmosphericTransmittance(double distance, Weather weather, double smokeAttenuation)
|
||
{
|
||
double transmittance;
|
||
|
||
// 使用AtmosphereDllWrapper计算红外波段的透过率
|
||
transmittance = AtmosphereDllWrapper.CalculateTransmittance(
|
||
distance,
|
||
RadiationType.Infrared,
|
||
wavelength,
|
||
weather);
|
||
|
||
// 考虑烟幕衰减
|
||
transmittance*= smokeAttenuation;
|
||
|
||
return transmittance;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用遮蔽型烟幕效果 (根据类型区分形状)
|
||
/// </summary>
|
||
/// <param name="smokeIntensityLayer">存储烟幕强度的图层</param>
|
||
/// <param name="missilePosition">导弹当前位置</param>
|
||
/// <param name="simulationManager">仿真管理器实例</param>
|
||
private void ApplyObscuringSmokeOverlay(double[,] smokeIntensityLayer, Vector3D missilePosition, ISimulationManager simulationManager)
|
||
{
|
||
const int minPixelSize = 3; // 最小像素尺寸 (直径或边长)
|
||
|
||
var smokeGrenades = simulationManager.GetEntitiesByType<SmokeGrenade>();
|
||
|
||
foreach (var smoke in smokeGrenades)
|
||
{
|
||
// 检查是否是遮蔽型烟幕 - 通过 config 访问
|
||
// 确保 config 和 RadiationTemperature 存在
|
||
if (smoke.IsActive ==false || smoke.config == null || !smoke.config.IsObscuring || smoke.config.RadiationTemperature <= 0) continue;
|
||
|
||
Vector3D toSmoke = smoke.Position - missilePosition;
|
||
double distance = toSmoke.Magnitude();
|
||
|
||
// 粗略检查烟幕是否在前方视野内
|
||
if (Vector3D.DotProduct(toSmoke.Normalize(), forward) < Math.Cos(fieldOfView / 2.0)) continue; // Use half FOV
|
||
|
||
// 计算烟幕强度 (基于 T^4 简化模型) - 通过 config 访问
|
||
// 注意: 这里没有考虑从烟幕到导弹的大气衰减,可以根据需要添加
|
||
double smokeIntensity = SMOKE_TEMPERATURE_FACTOR * Math.Pow(smoke.config.RadiationTemperature, 4);
|
||
|
||
// 投影烟幕中心
|
||
var (angleX, angleY) = ProjectToImagePlane(smoke.Position, missilePosition);
|
||
var (smokeCenterX, smokeCenterY) = AngleToPixel(angleX, angleY);
|
||
|
||
// 检查投影中心是否大致在图像内 (允许部分超出)
|
||
// if (smokeCenterX < -imageWidth/2 || smokeCenterX >= imageWidth*1.5 || smokeCenterY < -imageHeight/2 || smokeCenterY >= imageHeight*1.5) continue;
|
||
// More robust check needed? For now, let's proceed if center is roughly projectable.
|
||
|
||
// 根据烟幕类型计算尺寸并填充 - 通过 config 访问
|
||
switch (smoke.config.SmokeType)
|
||
{
|
||
|
||
case SmokeScreenType.Wall:
|
||
// 墙状烟幕,近似为水平矩形
|
||
double wallWidth = smoke.config.WallWidth; // 通过 config 访问
|
||
double wallHeight = smoke.config.WallHeight; // 通过 config 访问
|
||
int pixelWidth = (int)Math.Max(minPixelSize, (wallWidth / distance) * imageWidth / fieldOfView);
|
||
int pixelHeight = (int)Math.Max(minPixelSize, (wallHeight / distance) * imageHeight / fieldOfView); // Use imageHeight for vertical FOV estimate
|
||
|
||
int halfPixelWidth = pixelWidth / 2;
|
||
int halfPixelHeight = pixelHeight / 2;
|
||
|
||
// 确定绘制边界,确保在图像范围内
|
||
int minX_rect = Math.Max(0, smokeCenterX - halfPixelWidth);
|
||
int maxX_rect = Math.Min(imageWidth - 1, smokeCenterX + halfPixelWidth);
|
||
int minY_rect = Math.Max(0, smokeCenterY - halfPixelHeight);
|
||
int maxY_rect = Math.Min(imageHeight - 1, smokeCenterY + halfPixelHeight);
|
||
|
||
for (int y = minY_rect; y <= maxY_rect; y++)
|
||
{
|
||
for (int x = minX_rect; x <= maxX_rect; x++)
|
||
{
|
||
// 存储烟幕强度,处理重叠(取最大值)
|
||
smokeIntensityLayer[y, x] = Math.Max(smokeIntensityLayer[y, x], smokeIntensity);
|
||
}
|
||
}
|
||
Console.WriteLine($"记录遮蔽烟幕(Wall): 中心({smokeCenterX},{smokeCenterY}), 尺寸{pixelWidth}x{pixelHeight}px, 强度{smokeIntensity:F6}");
|
||
break;
|
||
|
||
case SmokeScreenType.Cloud:
|
||
// 云状烟幕,近似为圆形
|
||
double cloudDiameter = smoke.config.CloudDiameter; // 通过 config 访问
|
||
int pixelDiameter = (int)Math.Max(minPixelSize, (cloudDiameter / distance) * imageWidth / fieldOfView);
|
||
int pixelRadius = pixelDiameter / 2;
|
||
int radiusSquared = pixelRadius * pixelRadius;
|
||
|
||
// 确定绘制边界,确保在图像范围内
|
||
int minX_circle = Math.Max(0, smokeCenterX - pixelRadius);
|
||
int maxX_circle = Math.Min(imageWidth - 1, smokeCenterX + pixelRadius);
|
||
int minY_circle = Math.Max(0, smokeCenterY - pixelRadius);
|
||
int maxY_circle = Math.Min(imageHeight - 1, smokeCenterY + pixelRadius);
|
||
|
||
for (int y = minY_circle; y <= maxY_circle; y++)
|
||
{
|
||
for (int x = minX_circle; x <= maxX_circle; x++)
|
||
{
|
||
int dx = x - smokeCenterX;
|
||
int dy = y - smokeCenterY;
|
||
if (dx * dx + dy * dy <= radiusSquared)
|
||
{
|
||
// 存储烟幕强度,处理重叠(取最大值)
|
||
smokeIntensityLayer[y, x] = Math.Max(smokeIntensityLayer[y, x], smokeIntensity);
|
||
}
|
||
}
|
||
}
|
||
Console.WriteLine($"记录遮蔽烟幕(Cloud): 中心({smokeCenterX},{smokeCenterY}), 半径{pixelRadius}px, 强度{smokeIntensity:F6}");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成目标的红外图像
|
||
/// </summary>
|
||
/// <param name="target">目标对象</param>
|
||
/// <param name="missilePosition">导弹位置</param>
|
||
/// <param name="smokeAttenuation">衰减型烟幕透过率</param>
|
||
/// <param name="simulationManager">仿真管理器实例</param>
|
||
/// <returns>红外图像</returns>
|
||
public InfraredImage GenerateImage(BaseEquipment target, Vector3D missilePosition, double smokeAttenuation, ISimulationManager simulationManager)
|
||
{
|
||
// 更新视线坐标系
|
||
UpdateLineOfSightFrame(missilePosition, target.Position);
|
||
|
||
// 创建并初始化烟幕强度图层 (-1表示无烟幕)
|
||
double[,] smokeIntensityLayer = new double[imageHeight, imageWidth];
|
||
for (int y = 0; y < imageHeight; y++)
|
||
{
|
||
for (int x = 0; x < imageWidth; x++)
|
||
{
|
||
smokeIntensityLayer[y, x] = -1.0;
|
||
}
|
||
}
|
||
|
||
// 应用(记录)遮蔽型烟幕效果到图层
|
||
ApplyObscuringSmokeOverlay(smokeIntensityLayer, missilePosition, simulationManager);
|
||
|
||
// --- 新增:根据烟幕强度图层创建烟幕覆盖掩码 ---
|
||
bool[,] smokeCoverageMask = new bool[imageHeight, imageWidth];
|
||
for (int y = 0; y < imageHeight; y++)
|
||
{
|
||
for (int x = 0; x < imageWidth; x++)
|
||
{
|
||
smokeCoverageMask[y, x] = (smokeIntensityLayer[y, x] >= 0); // >= 0 表示被覆盖
|
||
}
|
||
}
|
||
// --- 结束新增 ---
|
||
|
||
// --- 新增:在此处创建 image 对象并传入掩码 ---
|
||
var image = new InfraredImage(imageWidth, imageHeight, fieldOfView / imageWidth, forward, smokeCoverageMask);
|
||
// --- 结束新增 ---
|
||
|
||
// 计算目标中心投影
|
||
var (angleX, angleY) = ProjectToImagePlane(target.Position, missilePosition);
|
||
var (centerX, centerY) = AngleToPixel(angleX, angleY);
|
||
|
||
// 计算目标尺寸
|
||
double distance = (target.Position - missilePosition).Magnitude();
|
||
double targetLengthAngle = Math.Atan2(target.Properties.Length, distance);
|
||
double targetWidthAngle = Math.Atan2(target.Properties.Width, distance);
|
||
|
||
int pixelLength = (int)(targetLengthAngle / (fieldOfView / imageWidth));
|
||
int pixelWidth = (int)(targetWidthAngle / (fieldOfView / imageWidth));
|
||
|
||
// 确保最小尺寸
|
||
pixelLength = Math.Max(1, pixelLength);
|
||
pixelWidth = Math.Max(1, pixelWidth);
|
||
|
||
Console.WriteLine($"生成目标 {target.Id} 的图像,尺寸: {pixelLength}x{pixelWidth} 像素,目标中心: ({centerX}, {centerY})");
|
||
|
||
// 计算大气和衰减型烟幕的总透过率
|
||
double transmittance = CalculateAtmosphericTransmittance(distance, simulationManager.CurrentWeather, smokeAttenuation);
|
||
Console.WriteLine($"大气及衰减烟幕透过率: {transmittance}");
|
||
|
||
// 生成目标图像强度,考虑遮蔽烟幕覆盖
|
||
GenerateTargetIntensity(image, smokeIntensityLayer, centerX, centerY, pixelLength, pixelWidth, target, distance, transmittance);
|
||
|
||
// 添加背景和噪声(这会影响到烟幕和目标区域)
|
||
AddBackgroundAndNoise(image, simulationManager.CurrentWeather);
|
||
|
||
return image;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成目标的红外强度分布,考虑遮蔽烟幕覆盖
|
||
/// </summary>
|
||
/// <param name="image">红外图像对象</param>
|
||
/// <param name="smokeIntensityLayer">遮蔽烟幕强度图层</param>
|
||
/// <param name="centerX">目标中心X像素坐标</param>
|
||
/// <param name="centerY">目标中心Y像素坐标</param>
|
||
/// <param name="pixelLength">目标像素长度</param>
|
||
/// <param name="pixelWidth">目标像素宽度</param>
|
||
/// <param name="target">目标设备对象</param>
|
||
/// <param name="distance">目标距离</param>
|
||
/// <param name="transmittance">大气和衰减烟幕透过率</param>
|
||
private static void GenerateTargetIntensity(
|
||
InfraredImage image,
|
||
double[,] smokeIntensityLayer, // 新增参数
|
||
int centerX,
|
||
int centerY,
|
||
int pixelLength,
|
||
int pixelWidth,
|
||
BaseEquipment target,
|
||
double distance,
|
||
double transmittance)
|
||
{
|
||
// --- 开始: 使用 ThermalPattern 重构 ---
|
||
|
||
// 获取目标当前的 3x3 热成像模式 (摄氏度)
|
||
double[,] thermalPattern = target.GetCurrentThermalPattern();
|
||
if (thermalPattern == null)
|
||
{
|
||
Console.WriteLine($"警告: 目标 {target.Id} 未提供 ThermalPattern,无法生成基于模式的强度。");
|
||
return;
|
||
}
|
||
|
||
int halfLength = pixelLength / 2;
|
||
int halfWidth = pixelWidth / 2;
|
||
int pixelsSet = 0;
|
||
double maxSetIntensity = 0;
|
||
|
||
// 确保映射的分母不为零
|
||
double patternPixelWidth = Math.Max(1.0, (double)pixelWidth / 3.0);
|
||
double patternPixelHeight = Math.Max(1.0, (double)pixelLength / 3.0); // Assuming Length maps to Cols (Height of pattern grid)
|
||
|
||
// 遍历目标在图像中的包围盒
|
||
for (int dy = -halfWidth; dy <= halfWidth; dy++)
|
||
{
|
||
int y = centerY + dy;
|
||
if (y < 0 || y < 0 || y >= image.Height) continue; // Corrected y boundary check
|
||
|
||
for (int dx = -halfLength; dx <= halfLength; dx++)
|
||
{
|
||
int x = centerX + dx;
|
||
if (x < 0 || x >= image.Width) continue;
|
||
|
||
double finalIntensity;
|
||
double smokeIntensity = smokeIntensityLayer[y, x];
|
||
|
||
if (smokeIntensity >= 0) // 检查此像素是否被遮蔽烟幕覆盖
|
||
{
|
||
finalIntensity = smokeIntensity;
|
||
}
|
||
else // 未被烟幕覆盖,基于 ThermalPattern 计算强度
|
||
{
|
||
// 1. 将像素坐标映射到 3x3 热成像模式的索引
|
||
// Map offset relative to top-left of bbox to [0, width/height-1] then divide
|
||
// Map dy from [-halfWidth, +halfWidth] to [0, pixelWidth-1]
|
||
// Map dx from [-halfLength, +halfLength] to [0, pixelLength-1]
|
||
int pixelYInBox = dy + halfWidth;
|
||
int pixelXInBox = dx + halfLength;
|
||
|
||
// Map to pattern indices [0, 2]
|
||
int patternRow = (int)Math.Floor(pixelYInBox / patternPixelWidth);
|
||
int patternCol = (int)Math.Floor(pixelXInBox / patternPixelHeight); // X maps to Col
|
||
|
||
// Clamp indices to [0, 2]
|
||
patternRow = Math.Max(0, Math.Min(2, patternRow));
|
||
patternCol = Math.Max(0, Math.Min(2, patternCol));
|
||
|
||
// 2. 获取对应模式单元的温度 (摄氏度)
|
||
double tempCelsius = thermalPattern[patternRow, patternCol];
|
||
|
||
// 3. 转换为开尔文
|
||
double tempKelvin = tempCelsius + KELVIN_OFFSET;
|
||
|
||
// 4. 使用简化的 T^4 定律计算基础强度 (W/sr)
|
||
// Ensure tempKelvin is positive before power calculation
|
||
double pixelBaseIntensity = tempKelvin > 0 ? TEMPERATURE_TO_INTENSITY_FACTOR * Math.Pow(tempKelvin, 4) : 0;
|
||
|
||
|
||
// 5. 应用大气透过率和距离衰减 (使用 distance^2)
|
||
// Avoid division by zero distance
|
||
finalIntensity = (distance > 1e-6) ? (pixelBaseIntensity * transmittance / (distance * distance)) : pixelBaseIntensity * transmittance;
|
||
|
||
|
||
}
|
||
|
||
// 设置最终计算出的强度到图像
|
||
// 确保强度非负
|
||
finalIntensity = Math.Max(0, finalIntensity);
|
||
image.SetIntensity(y, x, finalIntensity);
|
||
|
||
if (smokeIntensity < 0) // 只统计非烟幕覆盖的像素
|
||
{
|
||
pixelsSet++;
|
||
maxSetIntensity = Math.Max(maxSetIntensity, finalIntensity);
|
||
}
|
||
}
|
||
}
|
||
|
||
Console.WriteLine($"目标基于ThermalPattern强度分布: 像素设置: {pixelsSet}, 最大强度: {maxSetIntensity:F6} W/sr");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加背景辐射和噪声
|
||
/// </summary>
|
||
private void AddBackgroundAndNoise(InfraredImage image, Weather weather)
|
||
{
|
||
double maxIntensityBefore = double.MinValue;
|
||
double maxIntensityAfter = double.MinValue;
|
||
int pixelsModified = 0;
|
||
|
||
// 根据天气条件调整背景噪声水平
|
||
double noiseLevel = 0.2; // 默认噪声水平
|
||
double bgIntensity = backgroundIntensity;
|
||
|
||
|
||
// 雾、雨、雪等天气增加背景噪声
|
||
if (weather.Type == WeatherType.Fog)
|
||
{
|
||
noiseLevel = 0.5;
|
||
bgIntensity *= (1.0 + (1.0 - weather.Visibility / 10.0)); // 雾增加背景辐射
|
||
}
|
||
else if (weather.Type == WeatherType.Rain || weather.Type == WeatherType.Snow)
|
||
{
|
||
noiseLevel = 0.3;
|
||
// 降水增加噪声水平
|
||
if (weather.Precipitation > 0)
|
||
{
|
||
noiseLevel += 0.1 * Math.Min(1.0, weather.Precipitation / 10.0);
|
||
}
|
||
}
|
||
|
||
|
||
for (int y = 0; y < image.Height; y++)
|
||
{
|
||
for (int x = 0; x < image.Width; x++)
|
||
{
|
||
double currentIntensity = image.GetIntensity(y, x);
|
||
maxIntensityBefore = Math.Max(maxIntensityBefore, currentIntensity);
|
||
|
||
// 只在非目标区域添加背景
|
||
if (currentIntensity < bgIntensity)
|
||
{
|
||
currentIntensity = bgIntensity;
|
||
pixelsModified++;
|
||
}
|
||
|
||
// 添加高斯噪声 (降低噪声水平)
|
||
double noise = (random.NextDouble() - 0.5) * bgIntensity * noiseLevel;
|
||
currentIntensity += noise;
|
||
|
||
// 确保非负
|
||
currentIntensity = Math.Max(0, currentIntensity);
|
||
|
||
image.SetIntensity(y, x, currentIntensity);
|
||
maxIntensityAfter = Math.Max(maxIntensityAfter, currentIntensity);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |