#include "fiko/dokum/dolum.hpp" #include #include #include namespace fiko::dokum { namespace { inline constexpr u32 kYok32 = static_cast(-1); inline bool metalMi(const VoxelKafes& kafes, u32 idx) { return kafes.tip[idx] == HucreTip::Ic || kafes.tip[idx] == HucreTip::Sinir; } // Dijkstra düğümü (min-öncelik: en erken varış önce). struct Dugum { f64 zaman; u32 idx; bool operator<(const Dugum& o) const { return zaman > o.zaman; } // min-heap }; // Debi penceresi segmenti (kümülatif V(t) parça-doğrusal). struct DebiSeg { f64 t0, t1; // [t0,t1] aralığı f64 q; // toplam debi [m³/s] f64 v0; // t0 anına kadar birikmiş hacim [m³] }; // Aktif kaynak pencerelerinden V(t) segment listesi kur (q>0 segmentler). std::vector debiSegmentleri(const std::vector& kaynaklar) { std::vector kirilim; for (const auto& k : kaynaklar) { if (k.debiM3s <= 0 || k.sureS <= 0) continue; kirilim.push_back(k.gecikmeS); kirilim.push_back(k.gecikmeS + k.sureS); } std::sort(kirilim.begin(), kirilim.end()); kirilim.erase(std::unique(kirilim.begin(), kirilim.end(), [](f64 a, f64 b) { return std::abs(a - b) < 1e-12; }), kirilim.end()); std::vector segler; f64 v = 0; for (size_t s = 0; s + 1 < kirilim.size(); ++s) { const f64 t0 = kirilim[s], t1 = kirilim[s + 1]; f64 q = 0; for (const auto& k : kaynaklar) if (k.debiM3s > 0 && k.sureS > 0 && k.gecikmeS <= t0 + 1e-12 && t1 <= k.gecikmeS + k.sureS + 1e-12) q += k.debiM3s; if (q > 0) { segler.push_back({t0, t1, q, v}); v += q * (t1 - t0); } } return segler; } // ============================================================================ // HAVA GERİ-BASINCI — kapalılaşan cepler ideal gaz (izotermal Boyle) + Darcy // kaçışı. Formüller: // • Boyle (izotermal ideal gaz): P·V = P0·V0 [sıkışma] // • Darcy (1856): Q = κ·A·ΔP / (μ·L) [kalıp kaçışı] // • Cep ODE (V sabit): dP/dt = −β·P·(P−P0), β = κA/(μLV) — kapalı-form // logistik çözüm: w = P/(P−P0) ölçeğinde w(t) = w0·e^{βP0·t}. // • Metalostatik itki: P_metal = P0 + ρ·g·h [cephe ezer] // ============================================================================ // dt süre Darcy kaçışından sonra cep basıncı (V sabit, kapalı-form). inline f64 kacisSonrasiBasinc(f64 P, f64 P0, f64 beta, f64 dt) { const f64 lam = beta * P0 * dt; if (lam <= 0.0 || P <= P0 * (1.0 + 1e-12)) return std::max(P, P0); if (lam > 60.0) return P0; // tam sönüm (e^60 → w→∞ → P→P0) const f64 u1 = P / P0; const f64 w = (u1 / (u1 - 1.0)) * std::exp(lam); return (w / (w - 1.0)) * P0; } // P1 → P2'ye Darcy kaçışıyla inme süresi [s]; inilemiyorsa (κ=0 veya P2≤P0) ∞. inline f64 kacisBeklemeSuresi(f64 P1, f64 P2, f64 P0, f64 beta) { if (P2 >= P1) return 0.0; if (beta * P0 <= 0.0 || P2 <= P0 * (1.0 + 1e-9)) return 1e300; const f64 u1 = P1 / P0, u2 = P2 / P0; return std::log((u2 / (u2 - 1.0)) / (u1 / (u1 - 1.0))) / (beta * P0); } // Dolum-anı hava cebi analizi: ters-zaman union-find ile mühürlenme tespiti // (hava_analiz kaçış-yolu mantığının dolum-içi hâli) + cep bazlı ileri // simülasyon (Boyle sıkışma, Darcy kaçış, metalostatik durma). r'yi günceller: // varisZamani gecikir/dolamaz, cepBasinc/hapsoldu alanları dolar. void havaGeriBasinc(const VoxelKafes& kafes, const Alasim& alasim, const Kalip& kalip, const DolumAyar& ayar, const std::vector& girisler, const std::vector& ventler, Vec3d gv, DolumSonuc& r) { const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); const u32 N = kafes.hucreSayi(); const f64 h = kafes.h(), vH = kafes.hucreHacim(); const f64 P0 = std::max(ayar.atmBasinc, 1.0); r.cepBasinc.assign(N, 0.0f); r.hapsoldu.assign(N, 0); const int di[6] = {-1, 1, 0, 0, 0, 0}; const int dj[6] = {0, 0, -1, 1, 0, 0}; const int dk[6] = {0, 0, 0, 0, -1, 1}; auto komsu = [&](u32 idx, int f) -> u32 { const int i = int(idx % nx) + di[f], j = int((idx / nx) % ny) + dj[f], kk = int(idx / (nx * ny)) + dk[f]; if (i < 0 || j < 0 || kk < 0 || i >= (int)nx || j >= (int)ny || kk >= (int)nz) return kYok32; return kafes.indeks(u32(i), u32(j), u32(kk)); }; // Fizik kot [m]: mesh merkez · (−ĝ) × birim ölçek (h fizik / h mesh). const Vec3d yukari{-gv.x, -gv.y, -gv.z}; f64 hMesh = 1.0; if (nx > 1) hMesh = kafes.merkez(1, 0, 0).x - kafes.merkez(0, 0, 0).x; else if (ny > 1) hMesh = kafes.merkez(0, 1, 0).y - kafes.merkez(0, 0, 0).y; else if (nz > 1) hMesh = kafes.merkez(0, 0, 1).z - kafes.merkez(0, 0, 0).z; const f64 olcek = std::abs(hMesh) > 1e-30 ? h / hMesh : 1.0; auto kot = [&](u32 idx) { return kafes.merkez(idx % nx, (idx / nx) % ny, idx / (nx * ny)).nokta(yukari) * olcek; }; // "Yukarı" baskın grid yüzü (üst-yüzey testi). int ustF = 5; { f64 enIyi = -1e9; for (int f = 0; f < 6; ++f) { const f64 a = di[f] * yukari.x + dj[f] * yukari.y + dk[f] * yukari.z; if (a > enIyi) { enIyi = a; ustF = f; } } } // Atmosfer hücreleri: vent + giriş ağzı (yolluk metal akarken de hava ters // akımla kaçar) + kavitenin en üst açık yüzeyi. Mühürlenme = bu kümeden kopma. std::vector atmH(N, 0); for (u32 v : ventler) if (v < N && metalMi(kafes, v)) atmH[v] = 1; for (u32 g : girisler) if (g < N && metalMi(kafes, g)) { atmH[g] = 1; for (int f = 0; f < 6; ++f) { const u32 nb = komsu(g, f); if (nb != kYok32 && metalMi(kafes, nb)) atmH[nb] = 1; } } f64 enUst = -1e300, kotUst = -1e300; for (u32 c = 0; c < N; ++c) { if (!metalMi(kafes, c)) continue; kotUst = std::max(kotUst, kot(c)); const u32 ust = komsu(c, ustF); if (ust == kYok32 || !metalMi(kafes, ust)) enUst = std::max(enUst, kot(c)); } for (u32 c = 0; c < N; ++c) { if (!metalMi(kafes, c)) continue; const u32 ust = komsu(c, ustF); if ((ust == kYok32 || !metalMi(kafes, ust)) && kot(c) >= enUst - 1.01 * h) atmH[c] = 1; } // ---- Ters-zaman union-find: dolanlar azalan varışla "hava"ya geri eklenir. // Bir ekleme atmosfersiz bileşeni atmosfere bağlıyorsa, İLERİ zamanda o // bileşen tam bu anda mühürlenmişti (kapalı cep) → üyeler + hacim kaydedilir. std::vector ebeveyn(N, kYok32); std::vector atmKok(N, 0); std::vector> uye(N); auto bul = [&](u32 x) { while (ebeveyn[x] != x) { ebeveyn[x] = ebeveyn[ebeveyn[x]]; x = ebeveyn[x]; } return x; }; auto aktifEt = [&](u32 c) { ebeveyn[c] = c; atmKok[c] = atmH[c]; uye[c].assign(1, c); }; auto birlestir = [&](u32 a, u32 b) { a = bul(a); b = bul(b); if (a == b) return; if (uye[a].size() < uye[b].size()) std::swap(a, b); ebeveyn[b] = a; atmKok[a] = u8(atmKok[a] | atmKok[b]); uye[a].insert(uye[a].end(), uye[b].begin(), uye[b].end()); uye[b].clear(); uye[b].shrink_to_fit(); }; struct Cep { std::vector hucreler; // mühür anındaki hava hücreleri (V0) f64 muhurZaman; // kapalılaşma anı [s] }; std::vector cepler; // 1) Hiç dolmayan metal hücreler baştan hava. for (u32 c = 0; c < N; ++c) if (metalMi(kafes, c) && r.varisZamani[c] < 0.0) aktifEt(c); for (u32 c = 0; c < N; ++c) { if (ebeveyn[c] == kYok32) continue; for (int f = 0; f < 6; ++f) { const u32 nb = komsu(c, f); if (nb != kYok32 && ebeveyn[nb] != kYok32) birlestir(c, nb); } } // 2) Dolan hücreler azalan varış sırasıyla. std::vector> dolanlar; dolanlar.reserve(N); for (u32 c = 0; c < N; ++c) if (metalMi(kafes, c) && r.varisZamani[c] >= 0.0) dolanlar.push_back({r.varisZamani[c], c}); std::sort(dolanlar.rbegin(), dolanlar.rend()); for (const auto& [t, c] : dolanlar) { u32 kokler[6]; int ns = 0; bool atmVar = atmH[c] != 0; for (int f = 0; f < 6; ++f) { const u32 nb = komsu(c, f); if (nb == kYok32 || ebeveyn[nb] == kYok32) continue; const u32 kk = bul(nb); bool var = false; for (int s = 0; s < ns; ++s) if (kokler[s] == kk) { var = true; break; } if (!var) kokler[ns++] = kk; atmVar = atmVar || atmKok[kk] != 0; } if (atmVar) for (int s = 0; s < ns; ++s) if (!atmKok[kokler[s]]) cepler.push_back({uye[kokler[s]], t}); aktifEt(c); for (int s = 0; s < ns; ++s) birlestir(c, kokler[s]); } // 3) Hâlâ atmosfersiz kökler = baştan kapalı bölgeler. Metal hiç girmediyse // (kopuk kavite) misrun'dur, hava cebi değil → yalnız dolanı olan alınır. for (u32 c = 0; c < N; ++c) { if (ebeveyn[c] == kYok32 || bul(c) != c || atmKok[c] || uye[c].empty()) continue; bool dolanVar = false; for (u32 x : uye[c]) if (r.varisZamani[x] >= 0.0) { dolanVar = true; break; } if (dolanVar) cepler.push_back({uye[c], 0.0}); } // ---- Cep bazlı ileri simülasyon: sıkışma + kaçış + metalostatik durma ---- const f64 mu = std::max(ayar.havaViskozite, 1e-9); const f64 L = std::max(ayar.kalipCidar, 1e-4); for (const auto& cep : cepler) { // Kalıba temas alanı (Darcy kaçış kesiti): Dis/grid-kenar yüzler. f64 A = 0; for (u32 c : cep.hucreler) for (int f = 0; f < 6; ++f) { const u32 nb = komsu(c, f); if (nb == kYok32 || !metalMi(kafes, nb)) A += h * h; } const f64 cEsc = std::max(kalip.gecirgenlik, 0.0) * A / (mu * L); // [m³/(Pa·s)] std::vector> sirali; std::vector havada; for (u32 c : cep.hucreler) r.varisZamani[c] >= 0.0 ? sirali.push_back({r.varisZamani[c], c}) : havada.push_back(c); std::sort(sirali.begin(), sirali.end()); f64 V = f64(cep.hucreler.size()) * vH; // V0 (mühür anı hacmi) f64 P = P0; f64 tcur = cep.muhurZaman, oncekiGeo = cep.muhurZaman; size_t durdu = sirali.size(); for (size_t m = 0; m < sirali.size(); ++m) { const f64 tg = sirali[m].first; const u32 c = sirali[m].second; const f64 dtGeo = std::max(tg - oncekiGeo, 0.0); oncekiGeo = tg; f64 dt = dtGeo * std::max(P / P0, 1.0); // geri-basınç cepheyi yavaşlatır P = kacisSonrasiBasinc(P, P0, cEsc / V, dt); // ilerleme boyunca kaçış const f64 Vy = std::max(V - vH, 0.05 * vH); // son hücre kalıntı hacmi f64 Py = P * V / Vy; // Boyle: P·V sabit (sıkışma) const f64 Pmetal = P0 + alasim.yogunluk * ayar.gravite * std::max(kotUst - kot(c), 0.0); // ρgh itkisi if (Py > Pmetal * (1.0 + 1e-9)) { // Basınç itkiyi aşıyor: kaçışla sönmesini bekle (kum) — κ=0'da imkânsız. const f64 bekle = kacisBeklemeSuresi(Py, Pmetal, P0, cEsc / Vy); if (!(bekle <= ayar.havaMaksBekleme)) { durdu = m; break; } // GERÇEK hapis dt += bekle; Py = Pmetal; // bekleme sonunda basınç dengeye indi } tcur += dt; const f64 gecikme = tcur - tg; if (gecikme > 1e-12) { r.varisZamani[c] = tcur; r.varisT[c] -= ayar.soguma * gecikme; // gecikme = ekstra soğuma } r.cepBasinc[c] = f32(Py); r.maksCepBasinc = std::max(r.maksCepBasinc, Py); V = Vy; P = Py; } if (durdu < sirali.size()) for (size_t m = durdu; m < sirali.size(); ++m) { const u32 c = sirali[m].second; r.varisZamani[c] = -1.0; // cephe durdu → hücre DOLMAZ (hava hapsi) r.varisT[c] = 0.0; r.girisId[c] = kGirisYok; havada.push_back(c); } for (u32 c : havada) { r.hapsoldu[c] = 1; r.cepBasinc[c] = f32(P); r.havaKapani[c] = 1; ++r.havaKapaniSayi; ++r.hapsolanHucre; } if (!havada.empty()) r.maksCepBasinc = std::max(r.maksCepBasinc, P); } } } // namespace u32 enUstMetalHucre(const VoxelKafes& kafes) { if (!kafes.gecerliMi()) return static_cast(-1); const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); // En büyük k (z↑) katmanından başlayıp ilk metal hücreyi bul (giriş = tepe). for (u32 kk = nz; kk-- > 0;) for (u32 j = 0; j < ny; ++j) for (u32 i = 0; i < nx; ++i) { const u32 idx = kafes.indeks(i, j, kk); if (metalMi(kafes, idx)) return idx; } return static_cast(-1); } u32 enUstMetalHucre(const VoxelKafes& kafes, Vec3d graviteYon) { if (!kafes.gecerliMi()) return static_cast(-1); const Vec3d g = graviteYon.uzunlukKare() > 1e-12 ? graviteYon.birim() : Vec3d{0, 0, -1}; const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); u32 enIyi = static_cast(-1); f64 enYuksek = -1e300; for (u32 kk = 0; kk < nz; ++kk) for (u32 j = 0; j < ny; ++j) for (u32 i = 0; i < nx; ++i) { const u32 idx = kafes.indeks(i, j, kk); if (!metalMi(kafes, idx)) continue; const f64 y = -kafes.merkez(i, j, kk).nokta(g); // graviteye karşı kot if (y > enYuksek) { enYuksek = y; enIyi = idx; } } return enIyi; } DolumSonuc dolumCoz(const VoxelKafes& kafes, const Alasim& alasim, const DolumAyar& ayar, const std::vector& girisIdx, IlerlemeCB ilerleme) { // Eski arayüz: debi sınırsız tek/çoklu kaynak, gravite −Z (davranış birebir). std::vector kaynaklar; for (u32 g : girisIdx) kaynaklar.push_back({g, 0.0, 0.0, 0.0}); return dolumCoz(kafes, alasim, ayar, kaynaklar, Vec3d{0, 0, -1}, std::move(ilerleme)); } // Ortak çözücü gövdesi: kalip=nullptr → hava geri-basıncı KAPALI (eski davranış). static DolumSonuc dolumCozImpl(const VoxelKafes& kafes, const Alasim& alasim, const Kalip* kalip, const DolumAyar& ayar, const std::vector& kaynaklar, Vec3d graviteYon, const std::vector& ventler, IlerlemeCB ilerleme) { DolumSonuc r; if (!kafes.gecerliMi()) return r; const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); const u32 N = kafes.hucreSayi(); const f64 h = kafes.h(); // hücre boyutu [m] (fizik ölçeği) if (!(h > 0) || !std::isfinite(h)) return r; const Vec3d gv = graviteYon.uzunlukKare() > 1e-12 ? graviteYon.birim() : Vec3d{0, 0, -1}; // Metal hücre kümesi + toplam. u32 metalSayi = 0; for (u32 i = 0; i < N; ++i) if (metalMi(kafes, i)) ++metalSayi; if (metalSayi == 0) return r; r.varisZamani.assign(N, -1.0); r.varisT.assign(N, 0.0); r.girisId.assign(N, kGirisYok); r.coldShut.assign(N, 0); r.havaKapani.assign(N, 0); // ---- Kenar maliyeti: gravite-ağırlıklı seyahat süresi [s] ---------------- // Yatay/aşağı akış serbest-düşme hızlı; yukarı akış hidrostatik dirençle // yavaş (yukariCeza). Taban hız = ingate hızı (girisHiz); gravite YÖNÜ // parametrik: adım·ĝ > 0 → yerçekimiyle (aşağı), < 0 → karşı (yukarı). const f64 v0 = std::max(ayar.girisHiz, 1e-4); const f64 dusHiz = std::sqrt(std::max(2.0 * ayar.gravite * h, 1e-9)); // 1 hücre serbest düşme const f64 yatayZ = h / v0; // yatay seyahat süresi const f64 asagiZ = h / std::max(v0 + dusHiz, 1e-4); // aşağı (hızlanır) const f64 yukariZ = (h / v0) * std::max(ayar.yukariCeza, 1.0); // yukarı (yavaşlar) auto kenarMaliyet = [&](int di, int dj, int dk) -> f64 { const f64 a = di * gv.x + dj * gv.y + dk * gv.z; // adım·ĝ (birim eksen adımı) if (a > 0.33) return asagiZ; // yerçekimiyle if (a < -0.33) return yukariZ; // yerçekimine karşı return yatayZ; }; // ---- Giriş kaynakları (verilmezse graviteye göre en üst metal hücre) ----- std::priority_queue pq; auto girisEkle = [&](u32 idx, f64 zaman, u16 kaynakNo) { if (idx >= N || !metalMi(kafes, idx)) return; if (r.varisZamani[idx] >= 0.0 && r.varisZamani[idx] <= zaman) return; r.varisZamani[idx] = zaman; r.varisT[idx] = ayar.dokumSicaklik; r.girisId[idx] = kaynakNo; // Material Trace: kaynak etiketi cepheyle yayılır pq.push({zaman, idx}); }; std::vector girisHucreler; // hava modeli: giriş ağzı = hava kaçış yolu if (kaynaklar.empty()) { const u32 g = enUstMetalHucre(kafes, gv); girisEkle(g, 0.0, 0); if (g < N) girisHucreler.push_back(g); } else { for (size_t s = 0; s < kaynaklar.size(); ++s) { girisEkle(kaynaklar[s].idx, std::max(kaynaklar[s].gecikmeS, 0.0), (u16)s); if (kaynaklar[s].idx < N) girisHucreler.push_back(kaynaklar[s].idx); } } if (pq.empty()) return r; // ---- Dijkstra cephe ilerlemesi (geometrik varış) ------------------------- const int di[6] = {-1, 1, 0, 0, 0, 0}; const int dj[6] = {0, 0, -1, 1, 0, 0}; const int dk[6] = {0, 0, 0, 0, -1, 1}; u32 islenen = 0; while (!pq.empty()) { const Dugum u = pq.top(); pq.pop(); if (u.zaman > r.varisZamani[u.idx] + 1e-15) continue; // eskimiş kayıt ++islenen; const u32 i = u.idx % nx, j = (u.idx / nx) % ny, kk = u.idx / (nx * ny); const f64 tSu = r.varisZamani[u.idx]; const f64 TSu = r.varisT[u.idx]; for (int f = 0; f < 6; ++f) { const int ni = (int)i + di[f], nj = (int)j + dj[f], nk = (int)kk + dk[f]; if (ni < 0 || nj < 0 || nk < 0 || ni >= (int)nx || nj >= (int)ny || nk >= (int)nz) continue; const u32 nidx = kafes.indeks(ni, nj, nk); if (!metalMi(kafes, nidx)) continue; // yalnız metal içinde akış const f64 yeni = tSu + kenarMaliyet(di[f], dj[f], dk[f]); if (r.varisZamani[nidx] < 0.0 || yeni < r.varisZamani[nidx] - 1e-15) { r.varisZamani[nidx] = yeni; // Cephe sıcaklığı: kaynak T − seyahat süresince soğuma. r.varisT[nidx] = TSu - ayar.soguma * (yeni - tSu); r.girisId[nidx] = r.girisId[u.idx]; // ilk-varış kaynağı etiketi pq.push({yeni, nidx}); } } if (ilerleme && (islenen % 256 == 0)) { if (!ilerleme(std::clamp((f64)islenen / (f64)metalSayi, 0.0, 1.0), "Kalıp dolduruluyor")) return r; // iptal → yarım sonuç (hazir=false) } } // ---- Debi kısıtı (zamanlamalı kaynaklar): n. hücre V(t)≥n·V_h anında ----- const f64 vHucre = kafes.hucreHacim(); // [m³] const std::vector segler = debiSegmentleri(kaynaklar); if (!segler.empty()) { // Geometrik varışa göre sıralı hücreler → debi tavanıyla yeniden zamanla. std::vector> geo; geo.reserve(metalSayi); for (u32 idx = 0; idx < N; ++idx) if (metalMi(kafes, idx) && r.varisZamani[idx] >= 0.0) geo.push_back({r.varisZamani[idx], idx}); std::sort(geo.begin(), geo.end()); const f64 vToplam = segler.back().v0 + segler.back().q * (segler.back().t1 - segler.back().t0); size_t si = 0; f64 sonZ = 0.0; u32 n = 0; for (auto& [gz, idx] : geo) { ++n; const f64 hedefV = (f64)n * vHucre; if (hedefV > vToplam + 1e-15) { r.varisZamani[idx] = -1.0; // teslim edilen hacim bitti → dolmadı r.varisT[idx] = 0.0; r.girisId[idx] = kGirisYok; continue; } while (si + 1 < segler.size() && segler[si].v0 + segler[si].q * (segler[si].t1 - segler[si].t0) < hedefV - 1e-15) ++si; const DebiSeg& s = segler[si]; const f64 debiZ = s.t0 + (hedefV - s.v0) / s.q; f64 t = std::max(gz, debiZ); t = std::max(t, sonZ); // monoton (dolum sırası korunur) sonZ = t; r.varisZamani[idx] = t; } } // ---- Hava geri-basıncı (yalnız Kalip'li aşırı yükleme; vekil değil) ------ if (kalip) havaGeriBasinc(kafes, alasim, *kalip, ayar, girisHucreler, ventler, gv, r); // ---- Sonuç toplama: sıra, hacim, misrun, soğuma sıcaklığı ---------------- r.toplamMetal = metalSayi; std::vector> sirali; sirali.reserve(metalSayi); for (u32 idx = 0; idx < N; ++idx) { if (!metalMi(kafes, idx)) continue; if (r.varisZamani[idx] < 0.0) { // Hava hapsi ayrı raporlanır (hapsoldu); misrun = metal erişemedi/yetmedi. if (r.hapsoldu.empty() || !r.hapsoldu[idx]) r.misrun = true; continue; } ++r.dolanHucre; r.dolanHacim += vHucre; r.toplamSure = std::max(r.toplamSure, r.varisZamani[idx]); sirali.push_back({r.varisZamani[idx], idx}); } std::sort(sirali.begin(), sirali.end()); r.sira.reserve(sirali.size()); for (auto& p : sirali) r.sira.push_back(p.second); // ---- Kusur önü: soğuk-birleşme (cold shut) + hava kapanı ----------------- // Cold shut: komşu iki metal hücre GEÇ dolmuş ve varış sıcaklıkları düşük // (likidus+pay altı) → cepheler soğukken buluşmuş, kaynaşamaz. // Hava kapanı: geç-dolan hücrenin ÜST komşusu daha ERKEN dolmuş (metal // tepeyi kapamış, alttaki cep havayı hapsetmiş). Üst = graviteye karşı komşu. const f64 likidusEsik = alasim.tLikidus + ayar.likidusPay; const f64 gecEsik = 0.6 * r.toplamSure; // dolumun son %40'ı "geç" const int u_di[6] = {-1, 1, 0, 0, 0, 0}; const int u_dj[6] = {0, 0, -1, 1, 0, 0}; const int u_dk[6] = {0, 0, 0, 0, -1, 1}; // Graviteye karşı en dik eksen komşusu (hava kapanı "üst" yönü). int ustF = 5; // varsayılan +z { f64 enIyi = -1e9; for (int f = 0; f < 6; ++f) { const f64 a = -(u_di[f] * gv.x + u_dj[f] * gv.y + u_dk[f] * gv.z); if (a > enIyi) { enIyi = a; ustF = f; } } } for (u32 idx = 0; idx < N; ++idx) { if (!metalMi(kafes, idx) || r.varisZamani[idx] < 0.0) continue; const u32 i = idx % nx, j = (idx / nx) % ny, kk = idx / (nx * ny); const f64 t = r.varisZamani[idx], T = r.varisT[idx]; // Cold shut: soğuk + geç dolan + komşusu farklı-yönden geç geldi (buluşma). if (T < likidusEsik && t > gecEsik) { for (int f = 0; f < 6; ++f) { const int ni = (int)i + u_di[f], nj = (int)j + u_dj[f], nk = (int)kk + u_dk[f]; if (ni < 0 || nj < 0 || nk < 0 || ni >= (int)nx || nj >= (int)ny || nk >= (int)nz) continue; const u32 nidx = kafes.indeks(ni, nj, nk); if (!metalMi(kafes, nidx) || r.varisZamani[nidx] < 0.0) continue; // İki cephe yakın zamanda buluştu + ikisi de soğuk → cold shut. if (std::abs(r.varisZamani[nidx] - t) < ayar.coldShutDT / std::max(ayar.soguma, 1e-6) && r.varisT[nidx] < likidusEsik) { r.coldShut[idx] = 1; ++r.coldShutSayi; break; } } } // Hava kapanı: üst komşu daha erken dolmuş (tepe kapandı, cep havayı hapsetti). { const int ni = (int)i + u_di[ustF], nj = (int)j + u_dj[ustF], nk = (int)kk + u_dk[ustF]; if (ni >= 0 && nj >= 0 && nk >= 0 && ni < (int)nx && nj < (int)ny && nk < (int)nz) { const u32 ust = kafes.indeks(ni, nj, nk); if (metalMi(kafes, ust) && r.varisZamani[ust] >= 0.0 && r.varisZamani[ust] + 1e-9 < t && t > gecEsik) { r.havaKapani[idx] = 1; ++r.havaKapaniSayi; } } } } r.hazir = true; return r; } DolumSonuc dolumCoz(const VoxelKafes& kafes, const Alasim& alasim, const DolumAyar& ayar, const std::vector& kaynaklar, Vec3d graviteYon, IlerlemeCB ilerleme) { return dolumCozImpl(kafes, alasim, nullptr, ayar, kaynaklar, graviteYon, {}, std::move(ilerleme)); } DolumSonuc dolumCoz(const VoxelKafes& kafes, const Alasim& alasim, const Kalip& kalip, const DolumAyar& ayar, const std::vector& kaynaklar, Vec3d graviteYon, const std::vector& ventHucreler, IlerlemeCB ilerleme) { return dolumCozImpl(kafes, alasim, &kalip, ayar, kaynaklar, graviteYon, ventHucreler, std::move(ilerleme)); } std::vector dolmayanBolgeler(const VoxelKafes& kafes, const DolumSonuc& dolum) { std::vector r; const u32 N = kafes.hucreSayi(); if (!dolum.hazir || dolum.varisZamani.size() != N || !kafes.gecerliMi()) return r; const i32 nx = (i32)kafes.nx(), ny = (i32)kafes.ny(), nz = (i32)kafes.nz(); auto dolmayan = [&](u32 c) { return metalMi(kafes, c) && dolum.varisZamani[c] < 0.0; }; const f64 hucreMm3 = kafes.hucreHacim() * 1e9; std::vector ziyaret(N, 0); std::vector kuyruk; for (u32 c = 0; c < N; ++c) { if (ziyaret[c] || !dolmayan(c)) continue; DolmayanBolge b; Vec3d toplam{0, 0, 0}; kuyruk.assign(1, c); ziyaret[c] = 1; while (!kuyruk.empty()) { const u32 a = kuyruk.back(); kuyruk.pop_back(); ++b.hucreSayi; const i32 i = (i32)(a % (u32)nx), j = (i32)((a / (u32)nx) % (u32)ny), k = (i32)(a / (u32)(nx * ny)); toplam = toplam + kafes.merkez((u32)i, (u32)j, (u32)k); if (!dolum.hapsoldu.empty() && dolum.hapsoldu[a]) b.havaHapsi = true; const i32 dd[6][3] = {{-1,0,0},{1,0,0},{0,-1,0},{0,1,0},{0,0,-1},{0,0,1}}; for (const auto& d6 : dd) { const i32 ni = i + d6[0], nj = j + d6[1], nk = k + d6[2]; if (ni < 0 || nj < 0 || nk < 0 || ni >= nx || nj >= ny || nk >= nz) continue; const u32 nb = kafes.indeks((u32)ni, (u32)nj, (u32)nk); if (ziyaret[nb] || !dolmayan(nb)) continue; ziyaret[nb] = 1; kuyruk.push_back(nb); } } b.hacimMm3 = b.hucreSayi * hucreMm3; const f64 n = (f64)b.hucreSayi; b.merkez = {(f32)(toplam.x / n), (f32)(toplam.y / n), (f32)(toplam.z / n)}; r.push_back(b); } std::sort(r.begin(), r.end(), [](const DolmayanBolge& a, const DolmayanBolge& b) { return a.hucreSayi > b.hucreSayi; }); return r; } } // namespace fiko::dokum