#include "fiko/dokum/hava_analiz.hpp" #include #include #include namespace fiko::dokum { namespace { constexpr f64 kDolmadi = 1e30; // dolmayan hücre varış zamanı (∞ vekili) struct Yon { i32 di, dj, dk; }; // Sıra: +x,-x,+y,-y,+z,-z (eksen*2 + işaret) — karşıt çiftler yan yana. constexpr Yon kYonlar[6] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}; } // namespace HavaAnalizSonuc havaAnaliz(const VoxelKafes& kafes, const DolumSonuc& dolum, const HavaAnalizAyar& ayar) { HavaAnalizSonuc s; const u32 N = kafes.hucreSayi(); if (!kafes.gecerliMi() || !dolum.hazir || dolum.varisZamani.size() != N || kafes.tip.size() != N) return s; const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); auto metal = [&](u32 idx) { return kafes.tip[idx] != HucreTip::Dis; }; auto varis = [&](u32 idx) { const f64 t = dolum.varisZamani[idx]; return t < 0 ? kDolmadi : t; }; auto koord = [&](u32 idx, u32& i, u32& j, u32& k) { i = idx % nx; j = (idx / nx) % ny; k = idx / (nx * ny); }; auto komsu = [&](u32 idx, const Yon& y) -> u32 { u32 i, j, k; koord(idx, i, j, k); const i64 ni = i64(i) + y.di, nj = i64(j) + y.dj, nk = i64(k) + y.dk; if (ni < 0 || nj < 0 || nk < 0 || ni >= i64(nx) || nj >= i64(ny) || nk >= i64(nz)) return static_cast(-1); return kafes.indeks(u32(ni), u32(nj), u32(nk)); }; // "Yukarı" = gravite tersi; grid kaçış adımı için baskın eksen yönü. Vec3d g = ayar.graviteYon; const f64 gUz = std::sqrt(g.x * g.x + g.y * g.y + g.z * g.z); const Vec3d yukari = gUz > 1e-12 ? Vec3d{-g.x / gUz, -g.y / gUz, -g.z / gUz} : Vec3d{0, 0, 1}; const f64 uc[3] = {yukari.x, yukari.y, yukari.z}; u32 yukariYon = 4; f64 enB = -1; for (u32 a = 0; a < 3; ++a) if (std::fabs(uc[a]) > enB) { enB = std::fabs(uc[a]); yukariYon = a * 2 + (uc[a] >= 0 ? 0 : 1); } auto yukseklik = [&](u32 idx) { u32 i, j, k; koord(idx, i, j, k); const Vec3d m = kafes.merkez(i, j, k); return m.x * yukari.x + m.y * yukari.y + m.z * yukari.z; }; // Üst-yüzey hücresi: yukarı komşusu grid dışı veya kalıp (Dis). auto ustYuzey = [&](u32 idx) { const u32 n = komsu(idx, kYonlar[yukariYon]); return n == static_cast(-1) || !metal(n); }; const f64 hMesh = nx > 1 ? (kafes.merkez(1, 0, 0).x - kafes.merkez(0, 0, 0).x) : 1.0; // "Açık" hücreler: kavitenin en üst katmanındaki üst-yüzey hücreleri // (yolluk/besleyici ağzı varsayımı — hava buradan atmosfere kaçar). f64 enUst = -1e300; for (u32 c = 0; c < N; ++c) if (metal(c) && ustYuzey(c)) enUst = std::max(enUst, yukseklik(c)); auto acik = [&](u32 idx) { return metal(idx) && ustYuzey(idx) && yukseklik(idx) >= enUst - 1.01 * hMesh; }; // ---- Toplam süre + son-hava eşiği -------------------------------------- f64 son = dolum.toplamSure; if (son <= 0) for (u32 c = 0; c < N; ++c) if (metal(c) && dolum.varisZamani[c] >= 0) son = std::max(son, dolum.varisZamani[c]); const f64 dilim = std::clamp(ayar.sonHavaDilim, 0.005, 0.5); const f64 tLim = son * (1.0 - dilim); const f64 epsT = 1e-12 + 1e-9 * std::max(1.0, son); // ---- Per-hücre skorlar: lastAir + kabarcikRisk (Campbell vekili) -------- s.lastAir.assign(N, 0.0f); s.kabarcikRisk.assign(N, 0.0f); const f64 h = kafes.h(); const f64 vKrit = std::max(ayar.kritikHizMs, 1e-9); for (u32 c = 0; c < N; ++c) { if (!metal(c)) continue; const f64 t = dolum.varisZamani[c]; if (t < 0) { s.lastAir[c] = 1.0f; // hiç dolmadı = hava orada kaldı continue; } if (t >= tLim) s.lastAir[c] = son > tLim ? f32(std::clamp((t - tLim) / (son - tLim), 0.0, 1.0)) : 1.0f; // Cephe hızı: en hızlı upstream komşudan v ≈ h/Δt (Campbell V_krit aşımı). f64 dtMin = kDolmadi; bool bulusma = false; for (u32 a = 0; a < 3; ++a) { u32 erken = 0; for (u32 y = a * 2; y < a * 2 + 2; ++y) { const u32 nb = komsu(c, kYonlar[y]); if (nb == static_cast(-1) || !metal(nb)) continue; const f64 tn = dolum.varisZamani[nb]; if (tn >= 0 && tn < t - epsT) { dtMin = std::min(dtMin, t - tn); ++erken; } } if (erken == 2) bulusma = true; // karşıt cepheler bu hücrede buluştu } f64 sV = 0; if (dtMin < kDolmadi) { const f64 v = h / dtMin; s.azamiCepheHiz = std::max(s.azamiCepheHiz, v); sV = std::clamp(v / vKrit - 1.0, 0.0, 1.0); } const f64 risk = std::clamp( ayar.hizAgirlik * sV + ayar.bulusmaAgirlik * (bulusma ? 1.0 : 0.0), 0.0, 1.0); s.kabarcikRisk[c] = f32(risk); if (risk > 0.5) ++s.riskliHucre; } // ---- Bağlı bileşenler + kaçış testi ------------------------------------- std::vector ziyaret(N, 0); auto bilesenler = [&](auto aday) { std::vector> sonuc; std::fill(ziyaret.begin(), ziyaret.end(), u8(0)); for (u32 c = 0; c < N; ++c) { if (ziyaret[c] || !metal(c) || !aday(c)) continue; std::vector bolge; std::deque kuyruk{c}; ziyaret[c] = 1; while (!kuyruk.empty()) { const u32 a = kuyruk.front(); kuyruk.pop_front(); bolge.push_back(a); for (const Yon& y : kYonlar) { const u32 nb = komsu(a, y); if (nb == static_cast(-1) || ziyaret[nb] || !metal(nb) || !aday(nb)) continue; ziyaret[nb] = 1; kuyruk.push_back(nb); } } sonuc.push_back(std::move(bolge)); } return sonuc; }; // Kaçış: hava cephenin önünde, monoton ARTAN varış yolundan (henüz dolmamış // hücreler) üstteki açık hücrelere ulaşabiliyor mu? std::vector kacisZiyaret(N, 0); auto kacisVar = [&](const std::vector& bolge) { std::fill(kacisZiyaret.begin(), kacisZiyaret.end(), u8(0)); std::deque kuyruk; for (u32 c : bolge) { kacisZiyaret[c] = 1; if (acik(c)) return true; kuyruk.push_back(c); } while (!kuyruk.empty()) { const u32 a = kuyruk.front(); kuyruk.pop_front(); for (const Yon& y : kYonlar) { const u32 nb = komsu(a, y); if (nb == static_cast(-1) || kacisZiyaret[nb] || !metal(nb)) continue; if (varis(nb) < varis(a) - epsT) continue; // erken dolan = duvar kacisZiyaret[nb] = 1; if (acik(nb)) return true; kuyruk.push_back(nb); } } return false; }; // Dolum hava geri-basıncı modeli çalıştı mı? (gerçek basınç geçmişi varsa // vekil skorların YANINDA gerçek cep basıncı + kritik aşım raporlanır.) const bool basincVar = dolum.cepBasinc.size() == N; const bool hapisVar = dolum.hapsoldu.size() == N; auto bolgeYap = [&](const std::vector& hucreler, bool dolmayan) { HavaCebi cep; Vec3d toplam{0, 0, 0}; f64 enYuksek = -1e300, maksT = -1; for (u32 idx : hucreler) { if (basincVar) cep.sonBasinc = std::max(cep.sonBasinc, f64(dolum.cepBasinc[idx])); if (hapisVar && dolum.hapsoldu[idx]) cep.kritikAsti = true; u32 i, j, k; koord(idx, i, j, k); const Vec3d m = kafes.merkez(i, j, k); toplam = toplam + m; const f64 yy = yukseklik(idx); if (yy > enYuksek) { enYuksek = yy; cep.ventHucre = idx; cep.ventNoktasi = Vec3f{f32(m.x), f32(m.y), f32(m.z)}; } if (!dolmayan) maksT = std::max(maksT, dolum.varisZamani[idx]); } const f64 n = f64(hucreler.size()); cep.merkez = Vec3f{f32(toplam.x / n), f32(toplam.y / n), f32(toplam.z / n)}; cep.hacim = n * kafes.hucreHacim(); cep.hucreSayi = u32(hucreler.size()); cep.maksVarisZamani = dolmayan ? -1 : maksT; cep.kapali = !kacisVar(hucreler); if (cep.kritikAsti) cep.kapali = true; // gerçek hapis vekil kaçış testini ezer return cep; }; for (const auto& b : bilesenler([&](u32 c) { return dolum.varisZamani[c] < 0; })) s.cepler.push_back(bolgeYap(b, true)); for (const auto& b : bilesenler([&](u32 c) { const f64 t = dolum.varisZamani[c]; return t >= 0 && t >= tLim; })) s.sonHavaBolgeleri.push_back(bolgeYap(b, false)); // ---- Vent önerileri (kapalı cep + son-hava tepeleri, tekilleştirilmiş) -- std::vector ventIdx; auto ventEkle = [&](const HavaCebi& c) { if (c.ventHucre == static_cast(-1)) return; if (std::find(ventIdx.begin(), ventIdx.end(), c.ventHucre) != ventIdx.end()) return; ventIdx.push_back(c.ventHucre); s.ventOnerileri.push_back(c.ventNoktasi); }; for (const auto& c : s.cepler) if (c.kapali) { ++s.kapaliCepSayi; ventEkle(c); } for (const auto& c : s.sonHavaBolgeleri) { if (c.kapali) ++s.kapaliCepSayi; ventEkle(c); } // Gerçek basınç geçmişi (dolum hava modeli) özet istatistiği. s.gercekBasinc = basincVar; if (basincVar) { s.maksCepBasinc = dolum.maksCepBasinc; for (const auto& c : s.cepler) s.maksCepBasinc = std::max(s.maksCepBasinc, c.sonBasinc); } s.hazir = true; return s; } } // namespace fiko::dokum