import type { NPoint } from './types/effects'; /** Верхняя граница спрайтов на один штрих тумана/огня/дождя. */ export const FIELD_STAMP_MAX_SPRITES = 40; /** * Минимальное расстояние между центрами спрайтов (норм. 0..1). * Огонь крупнее пятна радиуса — реже ставим штампы. */ export function fieldStampMinSpacingN(type: 'fog' | 'fire' | 'rain', radiusN: number): number { const factor = type === 'fire' ? 0.75 : type === 'rain' ? 0.55 : 0.5; return Math.max(0.006, radiusN * factor); } /** * Оставляет концы штриха и точки не ближе `minSpacingN`. * Если точек всё ещё больше `maxPoints` — равномерный stride. */ export function decimateFieldStampPoints( points: readonly NPoint[], minSpacingN: number, maxPoints: number = FIELD_STAMP_MAX_SPRITES, ): NPoint[] { if (points.length <= 1) return points.slice(); const first = points[0]; const last = points[points.length - 1]; if (!first || !last) { return points.filter((p): p is NPoint => Boolean(p)); } const cap = Math.max(2, Math.floor(maxPoints)); const minDistSq = Math.max(0, minSpacingN) * Math.max(0, minSpacingN); const kept: NPoint[] = [first]; for (let i = 1; i < points.length - 1; i += 1) { const p = points[i]; const prev = kept[kept.length - 1]; if (!p || !prev) continue; const dx = p.x - prev.x; const dy = p.y - prev.y; if (dx * dx + dy * dy >= minDistSq) kept.push(p); } const tail = kept[kept.length - 1]; if (!tail) { kept.push(last); } else { const dx = last.x - tail.x; const dy = last.y - tail.y; if (dx * dx + dy * dy > 1e-12) kept.push(last); } if (kept.length <= cap) return kept; const reduced: NPoint[] = []; const lastIdx = kept.length - 1; const stride = Math.max(1, Math.ceil(lastIdx / (cap - 1))); for (let i = 0; i < kept.length; i += stride) { const p = kept[i]; if (p) reduced.push(p); } const reducedTail = reduced[reduced.length - 1]; if (reducedTail !== last) reduced.push(last); if (reduced.length > cap) { const head = reduced[0]; if (!head) return kept.slice(0, cap); return [head, ...reduced.slice(reduced.length - (cap - 1))]; } return reduced; }