Files
usa/src/utils/defenseLine.ts
2026-03-05 15:53:10 +08:00

107 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 战场防御线:反弓曲线路径 + 锯齿齿形
*/
export type LngLat = [number, number]
/** 二次贝塞尔 B(t) = (1-t)²P0 + 2(1-t)t P1 + t² P2 */
function quadraticBezier(P0: LngLat, P1: LngLat, P2: LngLat, t: number): LngLat {
const u = 1 - t
return [
u * u * P0[0] + 2 * u * t * P1[0] + t * t * P2[0],
u * u * P0[1] + 2 * u * t * P1[1] + t * t * P2[1],
]
}
/**
* 生成反弓曲线路径:通过 path 各点,弧面向进攻方向(西侧)凸出
* @param path 关键点 [[lng, lat], ...](如大不里士→萨南达季→克尔曼沙赫)
* @param bulgeWest 向西凸出量(经度,正值表示弧顶在西侧/面向进攻方)
* @param samplesPerSegment 每段贝塞尔采样点数
*/
export function createCurvedDefensePath(
path: LngLat[],
bulgeWest: number = 0.35,
samplesPerSegment: number = 24
): LngLat[] {
if (path.length < 2) return path
const out: LngLat[] = []
for (let i = 0; i < path.length - 1; i++) {
const a = path[i]
const b = path[i + 1]
const midLng = (a[0] + b[0]) / 2
const midLat = (a[1] + b[1]) / 2
const control: LngLat = [midLng - bulgeWest, midLat]
const startK = i === 0 ? 0 : 1
for (let k = startK; k <= samplesPerSegment; k++) {
out.push(quadraticBezier(a, control, b, k / samplesPerSegment))
}
}
return out
}
/**
* 沿路径线性插值增加点(直线段)
*/
export function interpolatePath(path: LngLat[], stepsPerSegment: number = 4): LngLat[] {
if (path.length < 2) return path
const out: LngLat[] = []
for (let i = 0; i < path.length - 1; i++) {
const a = path[i]
const b = path[i + 1]
const startK = i === 0 ? 0 : 1
for (let k = startK; k <= stepsPerSegment; k++) {
const t = k / stepsPerSegment
out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t])
}
}
return out
}
/**
* 锯齿防御线:沿 path 每 toothWidth比例生成齿齿高 toothHeight朝向法向敌方侧
* @param path 路径点 [[lng, lat], ...](可先经 interpolatePath 插值)
* @param toothWidth 齿宽(与段长同单位,经纬度)
* @param toothHeight 齿高(法向伸出)
*/
export function createDefenseLine(
path: number[][],
toothWidth: number,
toothHeight: number
): number[][] {
const coords: number[][] = []
for (let i = 0; i < path.length - 1; i++) {
const start = path[i] as [number, number]
const end = path[i + 1] as [number, number]
const dx = end[0] - start[0]
const dy = end[1] - start[1]
const L = Math.sqrt(dx * dx + dy * dy)
if (L < 1e-9) continue
const ux = dx / L
const uy = dy / L
const nx = -uy
const ny = ux
const numTeeth = Math.max(1, Math.floor(L / toothWidth))
const actualWidth = L / numTeeth
for (let j = 0; j < numTeeth; j++) {
const t1 = j * actualWidth
const t2 = t1 + actualWidth * 0.5
const t3 = (j + 1) * actualWidth
coords.push([start[0] + ux * t1, start[1] + uy * t1])
coords.push([
start[0] + ux * t2 + nx * toothHeight,
start[1] + uy * t2 + ny * toothHeight,
])
coords.push([start[0] + ux * t3, start[1] + uy * t3])
}
}
coords.push(path[path.length - 1] as [number, number])
return coords
}