/** * The solved field as the views need it, and how a complex value becomes a * color: the pressure at phase t, Re(u exp(-i omega t)) = Re u cos wt + Im u sin wt, * on a symmetric diverging scale, or the level 20 log10(|u| / ref) in dB. */ import type { Slice } from '../problem.ts'; export type Mode = 'pressure' | 'level'; export interface SliceField { slice: Slice; /** interleaved complex, NaN outside the room */ u: Float64Array; } export interface Field { k: number; beta: number; x0: [number, number, number]; a: number; p: number; ngrid: number; grid: Float64Array; slices: SliceField[]; /** wall field at the nodes, interleaved complex */ uWall: Float64Array; /** color limit for the pressure: 99th percentile of |u| more than half a wavelength from the source */ umax: number; /** reference for the level: the median of |u| on the same points */ ref: number; } export const DB_MIN = -30; export const DB_MAX = 15; /** Color limits from the slices, as plot.py computes them. */ export function colorLimits(slices: SliceField[], x0: ArrayLike, k: number): { umax: number; ref: number } { const vals: number[] = []; const rmin = Math.PI / k; for (const { slice, u } of slices) { const n = slice.mask.length; for (let q = 0; q < n; q++) { if (!slice.mask[q]) continue; const r = Math.hypot(slice.pts[q] - x0[0], slice.pts[n + q] - x0[1], slice.pts[2 * n + q] - x0[2]); if (r <= rmin) continue; const v = Math.hypot(u[2 * q], u[2 * q + 1]); if (Number.isFinite(v)) vals.push(v); } } if (!vals.length) return { umax: 1, ref: 1 }; vals.sort((x, y) => x - y); return { umax: vals[Math.floor(0.99 * (vals.length - 1))], ref: vals[Math.floor(0.5 * (vals.length - 1))] }; } /** Position in [0, 1] on the colormap of a complex value, or NaN. */ export function colorParam(re: number, im: number, mode: Mode, c: number, s: number, umax: number, ref: number): number { if (mode === 'pressure') return 0.5 + (0.5 * (re * c + im * s)) / umax; const db = 20 * Math.log10(Math.hypot(re, im) / ref); return (db - DB_MIN) / (DB_MAX - DB_MIN); }