/** * Mudança de rugosidade do terreno — NBR 6123:2023, sec. 5.5 * * Implementa o perfil de velocidades (fatores S₂) na zona de transição * entre duas categorias de rugosidade. */ import type { TerrainCategory } from './wind-kernel'; import { Z0_BY_CATEGORY, ZG_BY_CATEGORY } from './nbr-tables/table-5'; export interface RoughnessTransitionInput { /** Categoria da edificação (terreno próximo) */ near: TerrainCategory; /** Categoria do terreno a barlavento (mais afastado) */ far: TerrainCategory; /** Distância horizontal x a partir da linha de mudança (m) */ distance: number; /** Altura z acima do terreno (m) */ height: number; } export interface RoughnessTransitionResult { z1: number; z2: number; /** S₂ equivalente na posição (x, z) */ s2: number; } /** Calcula A conforme sec. 5.5.1 ou 5.5.2 */ function coefficientA(z02: number, z01: number, case2to1: boolean): number { if (case2to1) { return 0.63 - 0.03 * Math.log(z02 / z01); } return 0.73 - 0.03 * Math.log(z02 / z01); } /** * Cálculo da altura z₁ (topo da camada de transição — perfil da * categoria mais próxima). Caso 5.5.1: z_{02} > z_{01}. */ function computeZ1(z02: number, z01: number, x: number, case2to1: boolean): number { const A = coefficientA(z02, z01, case2to1); return A * z02 * Math.pow(x / z02, 0.8); } /** Caso 5.5.2: z_{02} < z_{01} — calcula z₁ diferente */ function computeZ1Case5_5_2(z02: number, x: number): number { const A = 0.73 - 0.03 * Math.log(z02 / 0.07); return A * z02 * Math.pow(x / z02, 0.8); } /** S₂ da categoria para uma altura z (usa tabela de parâmetros) */ function s2FromCategory(cat: TerrainCategory, z: number, params: Map): number { const p = params.get(cat); if (!p) throw new Error(`Categoria inválida: ${cat}`); const zEff = Math.max(5, Math.min(z, ZG_BY_CATEGORY[cat])); return p.b * p.fr * Math.pow(zEff / 10, p.p); } /** * Calcula S₂ efetivo considerando mudança de rugosidade. * Requer tabela de parâmetros já construída pelo chamador (vide * `getDefaultParams` em wind-kernel). */ export function applyRoughnessChange( input: RoughnessTransitionInput, params: Map, ): RoughnessTransitionResult { const { near, far, distance, height } = input; const z01 = Z0_BY_CATEGORY[near]; const z02 = Z0_BY_CATEGORY[far]; const x = Math.max(0, distance); const case2to1 = z02 > z01; let z1: number; if (case2to1) { z1 = computeZ1(z02, z01, x, true); } else { z1 = computeZ1Case5_5_2(z02, x); } const s2Far = s2FromCategory(far, height, params); const s2Near = s2FromCategory(near, height, params); let s2: number; if (height >= z1) { s2 = s2Far; } else { const z2 = case2to1 ? 0.36 * z02 * Math.pow(x / z02, 0.8) : 0.36 * z02 * Math.pow(x / z02, 0.8); if (height <= z2) { s2 = s2Near; } else { const t = (height - z2) / (z1 - z2); s2 = s2Near + t * (s2Far - s2Near); } } return { z1, z2: 0.36 * Z0_BY_CATEGORY[far] * Math.pow(x / Z0_BY_CATEGORY[far], 0.8), s2 }; }