using System; using CounterDrone.Core.Models; namespace CounterDrone.Core.Algorithms { /// 通用运动学 / 几何 / 大气扩散工具 public static class Kinematics { /// Pasquill 稳定度等级 public enum StabilityClass { A, B, C, D, E, F } /// 风向 → 单位速度矢量 (X, 0, Z),右手系 Y-up public static (float X, float Y, float Z) WindToVector(WindDirection dir, float speed) { var angle = dir switch { WindDirection.N => 0f, WindDirection.NE => 45f, WindDirection.E => 90f, WindDirection.SE => 135f, WindDirection.S => 180f, WindDirection.SW => 225f, WindDirection.W => 270f, WindDirection.NW => 315f, _ => 0f, }; var rad = angle * (float)Math.PI / 180f; return ((float)Math.Sin(rad) * speed, 0, (float)Math.Cos(rad) * speed); } /// 给定水平距离和初速,求解抛物线发射角(逆问题) /// 水平距离 (m),必须 > 0 /// 初速 (m/s),必须 > 0 /// 目标相对高度 targetY - startY (m) /// 发射角 (rad),取低弹道解 public static float CalculateLaunchAngle(float range, float muzzleVelocity, float heightDiff) { if (range <= 0) throw new ArgumentException($"range 必须 > 0,实际: {range}", nameof(range)); if (muzzleVelocity <= 0) throw new ArgumentException($"muzzleVelocity 必须 > 0,实际: {muzzleVelocity}", nameof(muzzleVelocity)); float v2 = muzzleVelocity * muzzleVelocity; float g = 9.81f; // 轨迹方程: heightDiff = R·tanθ - gR²/(2v²cos²θ) // 令 T = tanθ, a = gR²/(2v²): aT² - RT + (a + heightDiff) = 0 float a = 0.5f * g * range * range / v2; float discriminant = range * range - 4f * a * (heightDiff + a); if (discriminant < 0) throw new ArgumentException( $"当前参数无法命中目标: range={range}, heightDiff={heightDiff}, v₀={muzzleVelocity}"); // 低弹道解(取较小 tanθ) float tanTheta = (range - (float)Math.Sqrt(discriminant)) / (2f * a); return (float)Math.Atan(tanTheta); } /// 弹道飞行时间(秒)。水平距离 / 水平分速 public static float ParabolicTimeOfFlight(float range, float launchAngle, float muzzleVelocity) { if (range <= 0) throw new ArgumentException($"range 必须 > 0,实际: {range}", nameof(range)); if (muzzleVelocity <= 0) throw new ArgumentException($"muzzleVelocity 必须 > 0,实际: {muzzleVelocity}", nameof(muzzleVelocity)); float cosAngle = (float)Math.Cos(launchAngle); return range / (muzzleVelocity * cosAngle); } /// 抛物线飞行时间:给定水平距离和目标高度差,计算弹道时间 /// 等价于先调用 CalculateLaunchAngle 再调用 ParabolicTimeOfFlight public static float ParabolicShellTime(float horizontalDist, float heightDiff, float muzzleVelocity) { float angle = CalculateLaunchAngle(horizontalDist, muzzleVelocity, heightDiff); return ParabolicTimeOfFlight(horizontalDist, angle, muzzleVelocity); } /// 给定初速和发射角,计算到达指定相对高度的水平射程和飞行时间(正问题) /// 初速 (m/s),必须 > 0 /// 发射角 (rad),必须 ∈ (0, π/2) /// 目标相对高度 targetY - startY (m) /// (水平射程 m, 飞行时间 s),取下行段解 public static (float range, float timeOfFlight) ComputeParabolicRange( float muzzleVelocity, float launchAngle, float heightDiff) { if (muzzleVelocity <= 0) throw new ArgumentException($"muzzleVelocity 必须 > 0,实际: {muzzleVelocity}", nameof(muzzleVelocity)); if (launchAngle <= -Math.PI / 2 || launchAngle >= Math.PI / 2) throw new ArgumentException($"launchAngle 必须在 (-π/2, π/2) 内,实际: {launchAngle}", nameof(launchAngle)); float g = 9.81f; float cosA = (float)Math.Cos(launchAngle); float tanA = (float)Math.Tan(launchAngle); // 轨迹方程: heightDiff = R·tanθ - gR²/(2v²cos²θ) // 整理: (g/(2v²cos²θ))·R² - tanθ·R + heightDiff = 0 float a = g / (2f * muzzleVelocity * muzzleVelocity * cosA * cosA); float disc = tanA * tanA - 4f * a * heightDiff; if (disc < 0) throw new ArgumentException( $"当前参数无法达到目标高度: heightDiff={heightDiff}, v₀={muzzleVelocity}, θ={launchAngle * 180 / Math.PI:F1}°"); // 下行段解(较大根),+√disc 给出弹道下降分支与目标高度的交点 float range = (tanA + (float)Math.Sqrt(disc)) / (2f * a); float timeOfFlight = range / (muzzleVelocity * cosA); return (range, timeOfFlight); } /// 计算弹道顶点(最大高度和到达时间),相对发射点 /// 初速 (m/s),必须 > 0 /// 发射角 (rad),必须 ∈ [0, π/2) /// (顶点高度 m, 到达顶点时间 s),高度是相对发射点的增量 public static (float apexHeight, float apexTime) ParabolicApex( float muzzleVelocity, float launchAngle) { if (muzzleVelocity <= 0) throw new ArgumentException($"muzzleVelocity 必须 > 0,实际: {muzzleVelocity}", nameof(muzzleVelocity)); if (launchAngle <= -Math.PI / 2 || launchAngle >= Math.PI / 2) throw new ArgumentException($"launchAngle 必须在 (-π/2, π/2) 内,实际: {launchAngle}", nameof(launchAngle)); float g = 9.81f; float sinA = (float)Math.Sin(launchAngle); if (sinA <= 0) return (0, 0); // 水平或俯射,无上升顶点 float vy = muzzleVelocity * sinA; float apexTime = vy / g; float apexHeight = vy * vy / (2f * g); return (apexHeight, apexTime); } /// 给定初速、发射角和方位角,计算任意时刻的抛物线位置(正问题核心) /// 发射角 (rad),水平面以上为正 /// 方位角 (rad),0=N(+Z), π/2=E(+X) public static (float X, float Y, float Z) ParabolicPosition( float startX, float startY, float startZ, float launchAngle, float azimuth, float muzzleVelocity, float time) { var cosA = (float)Math.Cos(launchAngle); var sinA = (float)Math.Sin(launchAngle); var g = 9.81f; var horizontalDist = muzzleVelocity * cosA * time; var x = startX + horizontalDist * (float)Math.Sin(azimuth); var z = startZ + horizontalDist * (float)Math.Cos(azimuth); var y = startY + muzzleVelocity * sinA * time - 0.5f * g * time * time; return (x, y, z); } /// 空投弹药位置:继承载机速度 + 重力下落 /// 投放点坐标 /// 载机速度矢量 (m/s) /// 经过时间 (s) public static (float X, float Y, float Z) AirDropPosition( float startX, float startY, float startZ, float carrierVelX, float carrierVelY, float carrierVelZ, float time) { const float g = 9.81f; var x = startX + carrierVelX * time; var y = startY + carrierVelY * time - 0.5f * g * time * time; var z = startZ + carrierVelZ * time; return (x, y, z); } /// 空投降落时间:从释放高度下落到目标高度所需时间 /// 释放高度 (m) /// 目标高度 (m) /// 载机垂直速度 (m/s),正=向上,默认 0(平飞) public static float AirDropFallTime(float releaseAlt, float targetAlt, float carrierVelY = 0f) { const float g = 9.81f; var dy = releaseAlt - targetAlt; if (dy <= 0) return 0f; return ((float)Math.Sqrt(carrierVelY * carrierVelY + 2f * g * dy) - carrierVelY) / g; } /// 两点间方向的速度矢量:normalize(to - from) × speed public static (float X, float Y, float Z) DirectionVelocity( float fromX, float fromY, float fromZ, float toX, float toY, float toZ, float speed) { float dx = toX - fromX; float dy = toY - fromY; float dz = toZ - fromZ; float dist = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); if (dist < 0.01f) return (0, 0, 0); float s = speed / dist; return (dx * s, dy * s, dz * s); } /// 云团覆盖间隔:无人机穿越单个云团的时间 = 云团直径 / 无人机速度 /// 云团有效直径 m /// 目标速度 km/h /// 硬件最小间隔 s public static float CloudCoverInterval(float cloudDiameter, float targetSpeedKmh, float minInterval = 0.1f) { float speedMs = targetSpeedKmh / 3.6f; if (speedMs <= 0.1f) return minInterval; return Math.Max(minInterval, cloudDiameter / speedMs); } /// 点到矩形距离(简化判定) public static float Distance2D(float x1, float z1, float x2, float z2) { var dx = x2 - x1; var dz = z2 - z1; return (float)Math.Sqrt(dx * dx + dz * dz); } /// 匀速直线运动从一点到另一点的飞行时间(秒)。 /// 物理模型:无人机/平台沿直线匀速飞行,时间 = 距离 / 速度。 /// speedKmh 为 0 时抛异常(速度必须 > 0,由调用方保证)。 /// 速度 km/h(与 TypicalSpeed 单位一致) public static float TravelTime(float fromX, float fromZ, float toX, float toZ, float speedKmh) { if (speedKmh <= 0) throw new ArgumentException("速度必须 > 0", nameof(speedKmh)); float dist = Distance2D(fromX, fromZ, toX, toZ); return dist / (speedKmh / 3.6f); } /// 点到三维点距离 public static float Distance3D(float x1, float y1, float z1, float x2, float y2, float z2) { var dx = x2 - x1; var dy = y2 - y1; var dz = z2 - z1; return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz); } /// 根据天气+风速确定 Pasquill 稳定度 public static StabilityClass GetStabilityClass(WeatherType weather, float windSpeed) { return weather switch { WeatherType.Sunny when windSpeed <= 5 => StabilityClass.A, WeatherType.Sunny => StabilityClass.B, WeatherType.Overcast => StabilityClass.D, WeatherType.Rain => StabilityClass.D, WeatherType.Fog => StabilityClass.E, WeatherType.Night => StabilityClass.F, _ => StabilityClass.D, }; } /// Pasquill-Gifford 水平扩散系数 σy(m),x 为扩散距离(m) public static float SigmaY(StabilityClass cls, float x) { var coeff = cls switch { StabilityClass.A => 0.22f, StabilityClass.B => 0.16f, StabilityClass.C => 0.11f, StabilityClass.D => 0.08f, StabilityClass.E => 0.06f, StabilityClass.F => 0.04f, _ => 0.08f, }; return coeff * x / (float)Math.Sqrt(1f + 0.0001f * x); } /// Pasquill-Gifford 垂直扩散系数 σz(m),x 为扩散距离(m) public static float SigmaZ(StabilityClass cls, float x) { return cls switch { StabilityClass.A => 0.20f * x, StabilityClass.B => 0.12f * x, StabilityClass.C => 0.08f * x / (float)Math.Sqrt(1f + 0.0002f * x), StabilityClass.D => 0.06f * x / (float)Math.Sqrt(1f + 0.0015f * x), StabilityClass.E => 0.03f * x / (float)Math.Sqrt(1f + 0.0003f * x), StabilityClass.F => 0.016f * x / (float)Math.Sqrt(1f + 0.0003f * x), _ => 0.06f * x / (float)Math.Sqrt(1f + 0.0015f * x), }; } /// 高斯烟团中心浓度 C_max = Q / [(2π)^(3/2) σy² σz] public static float GaussianPeakConcentration(float sourceStrength, float sigmaY, float sigmaZ) { var denom = (float)Math.Pow(2f * (float)Math.PI, 1.5f) * sigmaY * sigmaY * sigmaZ; return denom > 0.001f ? sourceStrength / denom : 0f; } public static bool PointInPolygon(float px, float pz, ReadOnlySpan<(float X, float Z)> vertices) { if (vertices.Length < 3) return false; bool inside = false; int j = vertices.Length - 1; for (int i = 0; i < vertices.Length; i++) { var xi = vertices[i].X; var zi = vertices[i].Z; var xj = vertices[j].X; var zj = vertices[j].Z; if ((zi > pz) != (zj > pz) && px < (xj - xi) * (pz - zi) / (zj - zi) + xi) inside = !inside; j = i; } return inside; } } }