#include "dokum_penceresi.hpp" #include "ayar_kayitlari.hpp" #include #include #include #include #include #include #include #include #include #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/dokum/dokum_kurulum.hpp" #include "fiko/dokum/dokum_simulasyonu.hpp" #include "fiko/dokum/katilasma_gpu.hpp" #include "fiko/dokum/sph_gpu.hpp" #include "fiko/dokum/hava_analiz.hpp" #include "fiko/dokum/sivi_yuzey.hpp" #include "fiko/analiz/analiz_hesap.hpp" // kalinlikRapor (cidardan çözünürlük önerisi) #include "fiko/dokum/voxel_gorsel.hpp" #include "fiko/dokum/voxelize.hpp" // kafesParcaKapsama (kafes/parça teşhisi) #include "fiko/geometri/brep.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/sahne_ref_secim.hpp" #include "fiko/ui/form.hpp" #include "fiko/ui/pencere_cerceve.hpp" #include "fiko/ui/tema.hpp" #include "fiko/ui/zaman_tarayici.hpp" #include "imgui.h" #include "pencere_util.hpp" namespace fiko { using dokum::Alasim; using dokum::DokumSimulasyonu; using dokum::DokumSurec; using dokum::Kalip; namespace { // Açık pencereler + pin durumu (analiz_penceresi kalıbı). std::unordered_map g_acik, g_pin; // Animasyon şerit durumu (pencere-başı; FikoUi animasyonSeridi bileşeni). std::unordered_map g_dolumSerit, g_cepheSerit; // Ölçülen en ince et kalınlığı [mm] (sim başına önbellek): 0 = hiç ölçülmedi, // <0 = ölçüldü ama sonuç alınamadı. Işın taraması pahalı → yalnız düğmeyle. std::unordered_map g_minCidar; // Kavite metal hacmi [m³] önbelleği (sim başına): O(N) hücre taraması her kare // YASAK (sıcak yol). Kafes kimliği (hücre sayısı + hücre boyu) değişince tazelenir; // dolum çözümü varsa toplamMetal zaten sayılmış → tarama hiç yapılmaz. struct MetalHacimOnbellek { u32 n = 0; f64 h = 0; f64 hacim = 0; f64 kapsama = -1; }; std::unordered_map g_metalHacim; // Giriş kaviteden uzak mı (1 = dışarıda) — kurulumDugumYenile'de hesaplanır // (O(N) enYakinMetalHucre taraması kare-başı yasak), panel yalnız okur. std::unordered_map> g_girisDisarda; // Dolmayan bölge raporu önbelleği (O(N) BFS kare-başı yasak; dolum sonucu // değişince tazelenir — anahtar dolan/toplam çifti). struct DolmayanOnbellek { u32 dolan = ~0u, toplam = ~0u; std::vector bolgeler; }; std::unordered_map g_dolmayanBolge; std::unordered_map g_dolumMod; // DolumGorunum (combo int'i) std::unordered_map g_dolumRenkKay; // DolumRenk (combo int'i) std::unordered_map g_cepheAc; // katılaşma cephesi göster std::unordered_map g_cepheMod; // 0 = izo-yüzey, 1 = yalnız sıvı std::unordered_map g_sonKurma; // izo-yüzey ~30 Hz throttle // Değer-eşiği tarama (soyma) + kesit süpürme animasyon şeritleri (pencere-başı). std::unordered_map g_esikSerit, g_kesitSerit; std::unordered_map g_esikAc; // Seçili türetilmiş katman adı (boş = özel VoxelAlan modu). Skaler çözüm // alanları elle listeden ÇIKTI — sonuc_katmanlari jenerik listesinden dolar. std::unordered_map g_katmanSecim; // Özel görselleştirme modları (post-process/önbellek isteyenler; jenerik // katmanlara taşınamayanlar). Skaler alanlar (Niyama/T/süre/fs) → katmanlardan. struct AlanGiris { dokum::VoxelAlan alan; const char* ad; const char* birim; }; const AlanGiris kAlanlar[] = { {dokum::VoxelAlan::Kapali, "Kapalı", ""}, {dokum::VoxelAlan::SonHava, "Son Hava", ""}, {dokum::VoxelAlan::KabarcikRisk, "Kabarcık Riski", ""}, {dokum::VoxelAlan::Porozite, "Porozite %", "%"}, }; // Porozite eşiği (bölge tespiti, % — pencere-başı; poroziteAl önbellek anahtarı). std::unordered_map g_porEsik; // Porozite % görünüm kipi: false = BALONCUK izo-yüzey (MAGMA, varsayılan), // true = küp ısı-haritası (seçenek). Combo-blob bayrağı: blob'u "Görselleştir" // seçimi kurdu → başka alana geçince otomatik kaldırılır (checkbox'ınki kalır). std::unordered_map g_porKup, g_porBlobCombo; float& porEsikAl(u32 kimlik) { auto it = g_porEsik.find(kimlik); if (it == g_porEsik.end()) it = g_porEsik.emplace(kimlik, 0.5f).first; return it->second; } // Oto eşik bu sim için uygulandı mı (bir kez; kullanıcı elle değiştirirse // üzerine yazılmaz — "Oto eşik" düğmesi kaydı silip yeniden türetir). std::unordered_set g_porOtoYapildi; // Hava analizi isteyen alanlar (dolum sonucu gerektirir). bool havaAlaniMi(dokum::VoxelAlan a) { return a == dokum::VoxelAlan::SonHava || a == dokum::VoxelAlan::KabarcikRisk; } const char* alanAd(dokum::VoxelAlan a) { for (auto& g : kAlanlar) if (g.alan == a) return g.ad; return "?"; } const char* alanBirim(dokum::VoxelAlan a) { for (auto& g : kAlanlar) if (g.alan == a) return g.birim; return ""; } // Ortak lejant (renk-bar + min/max) — renkRampa CPU ikizi ile boyanır. // kilitli=true → "aralık: global kilitli" notu (animasyonda skala sabit). // bandli → MAGMA kalıbı bandSayi kademeli ayrık kutular (aralıklı). // bosGoster → skaladan AYRI "Boş" girdisi (dolmamış/geçersiz hücre — MAGMA Empty). void voxelLejant(dokum::VoxelRampa rampa, f32 mn, f32 mx, const char* birim, bool kilitli = false, bool bandli = false, int bandSayi = 15, bool bosGoster = false) { const ImVec4 SOLUK = ui::tema().metinSoluk; if (bosGoster) { // MAGMA "Empty": geçersiz/dolmamış hücre rengi skaladan ayrı sabit kutu. ImDrawList* dl0 = ImGui::GetWindowDrawList(); const ImVec2 q = ImGui::GetCursorScreenPos(); dl0->AddRectFilled({q.x, q.y + 2}, {q.x + 14, q.y + 15}, IM_COL32(128, 118, 148, 255), 2.0f); ImGui::Dummy({16, 16}); ImGui::SameLine(); ImGui::TextColored(SOLUK, "Boş (dolmamış / örneklenmedi)"); } ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 p = ImGui::GetCursorScreenPos(); const float w = ImGui::GetContentRegionAvail().x, h = 16.0f; if (bandli && bandSayi >= 2) { // Kademeli: bandSayi ayrık kutu, 1 px aralık (band merkez rengi). const float bw = w / (float)bandSayi; for (int b = 0; b < bandSayi; ++b) { const float t = ((float)b + 0.5f) / (float)bandSayi; const Vec3f c = dokum::voxelRampaRenk(rampa, t); const ImU32 u = IM_COL32((int)(c.x * 255), (int)(c.y * 255), (int)(c.z * 255), 255); dl->AddRectFilled({p.x + bw * b, p.y}, {p.x + bw * (b + 1) - 1.0f, p.y + h}, u); } } else { const int seg = 48; for (int i = 0; i < seg; ++i) { const float t = (float)i / (seg - 1); const Vec3f c = dokum::voxelRampaRenk(rampa, t); const ImU32 u = IM_COL32((int)(c.x * 255), (int)(c.y * 255), (int)(c.z * 255), 255); dl->AddRectFilled({p.x + w * t, p.y}, {p.x + w * (t + 1.0f / seg), p.y + h}, u); } } ImGui::Dummy({w, h}); char mnB[48], mxB[48]; std::snprintf(mnB, sizeof(mnB), "%.3g %s", mn, birim); std::snprintf(mxB, sizeof(mxB), "%.3g %s", mx, birim); ImGui::TextUnformatted(mnB); if (kilitli) { const char* kilit = "🔒 aralık: global kilitli"; ImGui::SameLine(std::max((w - ImGui::CalcTextSize(kilit).x) * 0.5f, ImGui::CalcTextSize(mnB).x + 12.0f)); ImGui::TextColored(SOLUK, "%s", kilit); ImGui::SetItemTooltip("Renk skalası animasyon boyunca sabit — kare kare min/max oynamaz"); } ImGui::SameLine(std::max(w - ImGui::CalcTextSize(mxB).x, 0.0f)); ImGui::TextUnformatted(mxB); } // Rampa adı (combo + lejant etiketi). const char* rampaAd(dokum::VoxelRampa r) { switch (r) { case dokum::VoxelRampa::Jet: return "Jet"; case dokum::VoxelRampa::Turbo: return "Turbo"; default: return "İnferno"; } } // Hedef gövde Dugum'u bul (0 = ilk görünür brep'li gövde). Dugum* hedefDugumBul(Sahne& sahne, u32 hedefGovde) { if (hedefGovde) if (Dugum* d = sahne.bul(hedefGovde); d && d->brep) return d; for (Dugum& d : sahne.dugumler()) if (d.brep && d.brep->gecerliMi() && d.indeksSayi > 0 && sahne.agacGorunur(d.kimlik)) return &d; return nullptr; } // Parçanın HAM sınırı (mesh birimi) — çözünürlük↔h dönüşümünün girdisi. // Kafes kuruluysa oradan (kur()'a verilen sınırın ta kendisi); değilse düğümün // yerel AABB'sinden (bedava — corbaBrepten tessellation'ı GEREKMEZ). // ⚠️ Kafes sınırı kurulum geometrisiyle (besleyici/yolluk) genişletilmiştir → // iki kaynak birebir aynı değil; h tahmini için ikisi de yeterli. Aabbd parcaHamSinir(Sahne& sahne, const DokumSimulasyonu& s) { if (s.kafes.gecerliMi() && s.kafes.sinirHam().gecerliMi()) return s.kafes.sinirHam(); Aabbd r; const Dugum* d = hedefDugumBul(sahne, s.hedefGovde); if (!d) return r; const Aabbf& y = d->yerelSinir; if (!y.gecerliMi()) return r; r.genislet({(f64)y.asgari.x, (f64)y.asgari.y, (f64)y.asgari.z}); r.genislet({(f64)y.azami.x, (f64)y.azami.y, (f64)y.azami.z}); return r; } // Malzeme/kalıp DB dropdown'ları (DB genişledikçe buradan büyür). struct AlasimGiris { const char* ad; Alasim (*yap)(); }; const AlasimGiris kAlasimlar[] = { {"AlSi7Mg (Al döküm)", dokum::alasimAlSi7Mg}, {"GG25 (gri dökme demir)", dokum::alasimGG25}, {"Döküm çeliği", dokum::alasimCelik}, {"Bakır (saf)", dokum::alasimBakir}, {"Pirinç (CuZn30)", dokum::alasimPirinc}, {"Çinko (sıvı metal)", dokum::alasimCinko}, {"Magnezyum (AZ91D)", dokum::alasimMagnezyum}, }; struct KalipGiris { const char* ad; Kalip (*yap)(); }; const KalipGiris kKaliplar[] = { {"Silika kum", dokum::kalipSilikaKum}, {"Çelik kalıp (kokil)", dokum::kalipCelikKalip}, }; } // namespace // ============================================================================ // DokumSistemi // ============================================================================ DokumSistemi::DokumSistemi(Cekirdek& cekirdek, Sahne& sahne) : cekirdek_(cekirdek), sahne_(sahne) { // RecomputeMotor bağı: girdi geometri/dönüşüm değişince ilgili sim bayatlar. auto bayatla = [this](u32 govde) { for (u32 k : simler_) if (DokumSimulasyonu* s = bul(k); s && (s->hedefGovde == govde || s->hedefGovde == 0)) { s->bayat = true; s->dolumBayat = true; } }; cekirdek_.olaylar().abone([bayatla](const BrepDegisti& o) { bayatla(o.varlik); }); cekirdek_.olaylar().abone( [bayatla](const DonusumDegisti& o) { bayatla(o.varlik); }); // Çift yönlü senkron: kurulum önizleme düğümü SAHNEDEN silinirse (Delete/ // radyal Sil/ağaç) kurulum kaydı da düşer + sim bayatlar. cekirdek_.olaylar().abone( [this](const VarlikSilindi& o) { kurulumKaydiDus(o.varlik); }); } void DokumSistemi::kurulumKaydiDus(u32 dugumKimlik) { if (!dugumKimlik) return; for (u32 k : simler_) { DokumSimulasyonu* s = bul(k); if (!s) continue; auto dus = [&](auto& liste) -> bool { for (auto it = liste.begin(); it != liste.end(); ++it) if (it->dugumKimlik == dugumKimlik) { liste.erase(it); s->bayat = s->dolumBayat = true; return true; } return false; }; if (dus(s->kurulum.girisler) || dus(s->kurulum.sogutucular) || dus(s->kurulum.besleyiciler) || dus(s->kurulum.yolluklar)) return; } } u32 DokumSistemi::grupAl() { if (grup_ != 0 && cekirdek_.nesne(grup_)) return grup_; grup_ = sahne_.grupEkle("Döküm Simülasyonları", VARLIK_YOK); return grup_; } u32 DokumSistemi::ekle(u32 hedefGovde) { const u32 grup = grupAl(); const Varlik v = cekirdek_.dunya().olustur(); auto nesne = std::make_unique(v); char ad[64]; std::snprintf(ad, sizeof(ad), "Döküm Sim %u", (unsigned)(simler_.size() + 1)); nesne->adAyarla(ad); nesne->hedefGovde = hedefGovde; nesne->cozunurluk = dokumVarsayilanCozunurluk(); // merkezi ayardan dokumDereceVarsayilanUygula(nesne->kurulum.derece); cekirdek_.nesneEkle(std::move(nesne)); cekirdek_.dunya().ekle(v, AdBilesen{ad}); cekirdek_.dunya().ekle(v, HiyerarsiBilesen{grup}); simler_.push_back(v); // Derece/kum görseli sim EKLENİR EKLENMEZ kurulsun (panel etkileşimi // beklemesin) — profesyonel ilk deneyim; kokil kalıpta zaten çizilmez. kurulumDugumYenile(v); return v; } bool DokumSistemi::sil(u32 kimlik) { auto it = std::find(simler_.begin(), simler_.end(), kimlik); if (it == simler_.end()) return false; xrayAyarla(kimlik, false, XrayHepsi); // parça x-ray'i geri al (gövde kalıcı) voxelKaldir(kimlik); // varsa ısı-haritası düğümünü de temizle dolumKaldir(kimlik); // varsa dolum ısı-haritası düğümünü de temizle cepheKaldir(kimlik); // varsa katılaşma cephesi izo-yüzeyini temizle besleyiciHayaletKaldir(kimlik); // varsa besleyici hayalet önizlemesini temizle kurulumDugumleriKaldir(kimlik); // giriş/chill/besleyici/yolluk önizlemeleri ventKaldir(kimlik); // vent önerisi küreleri poroziteKaldir(kimlik); // baloncuk + bölge işareti + önbellek dolumAlan_.erase(kimlik); hava_.erase(kimlik); katman_.erase(kimlik); g_katmanSecim.erase(kimlik); g_dolumSerit.erase(kimlik); g_cepheSerit.erase(kimlik); g_dolumMod.erase(kimlik); g_dolumRenkKay.erase(kimlik); g_cepheAc.erase(kimlik); g_cepheMod.erase(kimlik); g_esikSerit.erase(kimlik); g_kesitSerit.erase(kimlik); g_esikAc.erase(kimlik); g_sonKurma.erase(kimlik); g_porEsik.erase(kimlik); g_porKup.erase(kimlik); g_porBlobCombo.erase(kimlik); g_porOtoYapildi.erase(kimlik); g_metalHacim.erase(kimlik); g_girisDisarda.erase(kimlik); g_dolmayanBolge.erase(kimlik); simler_.erase(it); if (koşanKimlik_ == kimlik) { cekirdek_.gorevler().iptalEt(koşanGorev_); koşanKimlik_ = 0; } cekirdek_.dunya().yokEt(kimlik); return true; } DokumSimulasyonu* DokumSistemi::bul(u32 kimlik) { return dynamic_cast(cekirdek_.nesne(kimlik)); } bool DokumSistemi::cozuluyorMu(u32 kimlik) const { return koşanKimlik_ == kimlik; } bool DokumSistemi::cozumBaslat(u32 kimlik) { if (koşanKimlik_ != 0) return false; // aynı anda tek çözüm (D1) DokumSimulasyonu* s = bul(kimlik); if (!s) return false; Dugum* d = hedefDugumBul(sahne_, s->hedefGovde); if (!d || !d->brep || !d->brep->gecerliMi()) return false; s->hedefGovde = d->kimlik; // 0 idiyse çözülen gövdeyi sabitle // ---- GPU FAST-PATH (ana iplik): Vulkan compute katilasmaGpuCoz ----------- // Vulkan gönderim iş-parçacığı-güvenli olmadığından GPU yolu worker'da DEĞİL, // ANA İPLİKTE senkron koşar. Büyük gridde (128³+) 5×+ hızlanma → blokaj kabul. // GPU koşarsa (basari=true) worker açılmaz; koşmazsa CPU worker'a düşülür. if (vulkan && gpuTercih && dokum::katilasmaGpuVarMi(*vulkan)) { cekirdek_.olaylar().yayinla(DokumBasladi{kimlik}, s->ad().c_str()); // Kafesi kur + kurulum uygula (hesaplaGovde'nin kafes yolu) → GPU çöz. if (s->hesaplaGovdeGpu(*vulkan, *d->brep)) { por_.erase(kimlik); katman_.erase(kimlik); s->bayat = !s->sonuc.hazir; if (s->sonuc.hazir) { hotSpotIsaretle(kimlik); const auto ks = g_katmanSecim.find(kimlik); if (ks != g_katmanSecim.end() && !ks->second.empty()) { katmanGorsellestir(kimlik, ks->second.c_str()); } else if (s->gorselAyar.alan != dokum::VoxelAlan::Kapali) { voxelGorsellestir(kimlik); } else { g_katmanSecim[kimlik] = "Sıcaklık (Son)"; katmanGorsellestir(kimlik, "Sıcaklık (Son)"); } } cekirdek_.olaylar().yayinla(DokumBitti{kimlik, s->sonuc.toplamSure}, s->ad().c_str()); return true; } // GPU başarısız (device-lost/geçersiz) → CPU worker'a düş (aşağıdaki yol). } // Worker'a brep'in shared_ptr kopyası (thread-safe ömür) + parametre snapshot'ı // geçilir; sonuç ayrı bir DokumSimulasyonu'nda hesaplanır (Sahne'ye dokunmaz). auto brep = d->brep; // shared_ptr kopya auto isSim = std::make_shared(kimlik); isSim->surec = s->surec; isSim->alasim = s->alasim; isSim->kalip = s->kalip; isSim->ayar = s->ayar; isSim->cozunurluk = s->cozunurluk; isSim->niyamaEsik = s->niyamaEsik; isSim->hedefGovde = s->hedefGovde; isSim->kurulum = s->kurulum; // besleyici/yolluk/chill/gravite worker'a taşınır koşanKimlik_ = kimlik; cekirdek_.olaylar().yayinla(DokumBasladi{kimlik}, s->ad().c_str()); koşanGorev_ = cekirdek_.gorevler().kuyrukla( "Döküm katılaşma çözümü", [isSim, brep](GorevBaglam& bg) { // SAF CPU — Sahne/GPU/olay YASAK. İlerleme → GorevBaglam köprüsü. isSim->hesaplaGovde(*brep, [&bg](f64 oran, const char* mesaj) { bg.ilerlet((f32)oran, mesaj ? mesaj : ""); return !bg.iptalIstendi(); }); if (!isSim->sonuc.hazir && !bg.iptalIstendi()) bg.hataAyarla("Çözüm başarısız (geçersiz mesh/kafes)"); }, [this, kimlik, isSim]() { // Main-thread finalize: sonucu gerçek sim nesnesine kopyala + olay. koşanKimlik_ = 0; DokumSimulasyonu* g = bul(kimlik); if (!g) return; g->kafes = std::move(isSim->kafes); g->sonuc = isSim->sonuc; g->bayat = !isSim->sonuc.hazir; por_.erase(kimlik); // porozite önbelleği yeni katılaşma sonucuyla bayat katman_.erase(kimlik); // türetilmiş katmanlar yeni sonuçla bayat if (isSim->sonuc.hazir) { hotSpotIsaretle(kimlik); // Görselleştirme açıksa yeni sonuçla ısı-haritasını tazele. const auto ks = g_katmanSecim.find(kimlik); if (ks != g_katmanSecim.end() && !ks->second.empty()) { katmanGorsellestir(kimlik, ks->second.c_str()); } else if (g->gorselAyar.alan != dokum::VoxelAlan::Kapali) { voxelGorsellestir(kimlik); } else { // MAGMA İLK DENEYİM: katılaşma bitti → varsayılan katman Sıcaklık. g_katmanSecim[kimlik] = "Sıcaklık (Son)"; katmanGorsellestir(kimlik, "Sıcaklık (Son)"); } } cekirdek_.olaylar().yayinla(DokumBitti{kimlik, g->sonuc.toplamSure}, g->ad().c_str()); }); return true; } bool DokumSistemi::dolumBaslat(u32 kimlik) { if (koşanKimlik_ != 0) return false; // aynı anda tek çözüm DokumSimulasyonu* s = bul(kimlik); if (!s) return false; Dugum* d = hedefDugumBul(sahne_, s->hedefGovde); if (!d || !d->brep || !d->brep->gecerliMi()) return false; s->hedefGovde = d->kimlik; // Partikül kaydı çözüm ANINDA kararlaşır (kare serisi sonradan zenginleşemez) // → GPU/CPU ayrımından ÖNCE, iki yol da aynı ayarı görsün. s->sphAyar.partikulKaydet = dokumPartikulKaydet(); s->sphAyar.partikulAltOrnek = dokumPartikulAltOrnek(); // DFSPH divergence çözücü: ÖLÇÜLDÜ (07-16) ince çözünürlükte hız alanını // patlatıyor (h=2mm: maks hız 98.0 → 7.3 m/s kapatınca, üstelik %30 hızlı). // Kütüphane varsayılanı eski davranış; uygulama ölçülen doğru yolu geçirir. s->sphAyar.divergenceCozucu = dokumSphDivergenceCozucu(); // Havuz tavanı da çözüm anında kararlaşır (emitter kapasitesi kuruluşta ayrılır). s->sphAyar.maksPartikul = dokumSphMaksPartikul(); // ---- GPU FAST-PATH (ana iplik): Gerçek SPH + Vulkan compute PBF ---------- // cozumBaslat katilasma kalıbı: Vulkan gönderimi ana iplikte senkron (worker // YASAK). GPU SPH ~saniyeler (CPU SPH dakikalar → worker'da kalır). Kafes // GPU katilasma ile kurulur (dolum zinciri kafes ister); GPU koşamazsa // aşağıdaki CPU worker yoluna düşülür. if (vulkan && gpuTercih && s->sphKullan && dokum::sphGpuVarMi(*vulkan) && dokum::katilasmaGpuVarMi(*vulkan)) { cekirdek_.olaylar().yayinla(DolumBasladi{kimlik}, s->ad().c_str()); if (s->hesaplaGovdeGpu(*vulkan, *d->brep) && s->dolumCozGpu(*vulkan)) { dolumSonrasi_(kimlik); return true; } // GPU başarısız → CPU worker'a düş (DolumBitti'yi worker finalize yayınlar). } auto brep = d->brep; // shared_ptr kopya (thread-safe ömür) auto isSim = std::make_shared(kimlik); isSim->surec = s->surec; isSim->alasim = s->alasim; isSim->kalip = s->kalip; isSim->ayar = s->ayar; isSim->cozunurluk = s->cozunurluk; isSim->niyamaEsik = s->niyamaEsik; isSim->hedefGovde = s->hedefGovde; isSim->dolumAyar = s->dolumAyar; isSim->girisHucre = s->girisHucre; isSim->kurulum = s->kurulum; // çoklu zamanlamalı girişler + gravite yönü isSim->sphKullan = s->sphKullan; // dolum fiziği + SPH ayarları worker'a taşınır isSim->sphAyar = s->sphAyar; koşanKimlik_ = kimlik; cekirdek_.olaylar().yayinla(DolumBasladi{kimlik}, s->ad().c_str()); koşanGorev_ = cekirdek_.gorevler().kuyrukla( s->sphKullan ? "Kalıp doldurma çözümü (SPH)" : "Kalıp doldurma çözümü", [isSim, brep](GorevBaglam& bg) { // SAF CPU — gövdeden kafes kur+sınıfla (hesaplaGovde kafesi kurar) + // dolumu çöz. Katılaşmayı da çözer ama dolum sonucu bize yeter. const geometri::BrepGovde& g = *brep; // Kafesi kur (dolumCoz kafes ister) — hesaplaGovde kafesi hazırlar. isSim->hesaplaGovde(g); isSim->dolumCoz([&bg](f64 oran, const char* mesaj) { bg.ilerlet((f32)oran, mesaj ? mesaj : ""); return !bg.iptalIstendi(); }); if (!isSim->dolum.hazir && !bg.iptalIstendi()) bg.hataAyarla("Dolum başarısız (geçersiz mesh/kafes)"); }, [this, kimlik, isSim]() { koşanKimlik_ = 0; DokumSimulasyonu* g = bul(kimlik); if (!g) return; g->kafes = std::move(isSim->kafes); g->dolum = std::move(isSim->dolum); g->sph = std::move(isSim->sph); // SPH kare serisi + hız istatistiği g->sonuc = isSim->sonuc; // yan-çözüm de taşınır (isteğe bağlı gösterim) g->dolumBayat = !g->dolum.hazir; dolumSonrasi_(kimlik); }); return true; } void DokumSistemi::dolumSonrasi_(u32 kimlik) { DokumSimulasyonu* g = bul(kimlik); if (!g) return; dolumAlan_.erase(kimlik); // pürüzsüz yüzey alan önbelleği bayatladı hava_.erase(kimlik); // hava analizi önbelleği bayatladı por_.erase(kimlik); // porozite gaz bileşeni hava analizine bağlı katman_.erase(kimlik); // dolum katmanları yeni sonuçla bayat ventKaldir(kimlik); // eski vent önerileri yeni doluma ait değil if (g->dolum.hazir) { if (havaAlaniMi(g->gorselAyar.alan)) { voxelGorsellestir(kimlik); } else { // MAGMA İLK DENEYİM: dolum bitti → otomatik pürüzsüz sıvı + parça // x-ray (İnferno + kademeli lejant varsayılanları ayarlardan gelir). // Isı-haritası küpleri sıvıyı örtmesin → perspektif dolum'a döner. g_katmanSecim[kimlik].clear(); voxelKaldir(kimlik); g_dolumSerit[kimlik].zaman = 1.0f; // scrubber "Tümü" ile hizalı const int mod = g_dolumMod.emplace(kimlik, (int)DolumGorunum::Sivi) .first->second; dolumGorsellestir(kimlik, -1.0, (DolumGorunum)mod, (DolumRenk)g_dolumRenkKay[kimlik]); } } cekirdek_.olaylar().yayinla(DolumBitti{kimlik, g->dolum.toplamSure}, g->ad().c_str()); } void DokumSistemi::dolumKaldir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->dolumDugum) return; if (sahne_.bul(s->dolumDugum)) sahne_.sil(s->dolumDugum); s->dolumDugum = 0; } void DokumSistemi::xrayAyarla(u32 kimlik, bool ac, u8 kaynak) { DokumSimulasyonu* s = bul(kimlik); if (!s) return; u8& istek = xrayIstek_[kimlik]; if (ac) istek |= kaynak; else istek &= (u8)~kaynak; const bool aktif = istek != 0; if (!aktif) xrayIstek_.erase(kimlik); Dugum* d = hedefDugumBul(sahne_, s->hedefGovde); if (!d) return; auto it = eskiSaydam_.find(kimlik); if (aktif) { if (it == eskiSaydam_.end()) eskiSaydam_.emplace(kimlik, d->saydamlik); d->saydamlik = 0.15f; // x-ray hissi (MAGMA kalıp hayaleti) } else if (it != eskiSaydam_.end()) { d->saydamlik = it->second; eskiSaydam_.erase(it); } } void DokumSistemi::dolumGorsellestir(u32 kimlik, f64 zamanKesim, DolumGorunum gorunum, DolumRenk renk) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->dolum.hazir || !s->kafes.gecerliMi()) return; s->gorselAyar.kirpParcaya = dokumVoxelKirp(); // "dokum.voxel_kirp" (canlı) const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; if (gorunum == DolumGorunum::Partikul) { // SPH partikül bulutu: hücre binning'i DEĞİL, partikülün kendi konumu. // Kare serisi yoksa (hızlı cephe kipi) veya partikül kaydı kapalıysa // çizilecek bir şey yok → sessizce sıvı yüzeyine düşmek yerine görseli // kaldır (panel zaten kipi pasifleştirir). const auto& kareler = s->sph.kareler; if (kareler.empty() || kareler.front().partikul.empty()) { dolumKaldir(kimlik); return; } const f64 anK = zamanKesim < 0 ? kareler.back().sure : zamanKesim; size_t ki = 0; while (ki + 1 < kareler.size() && kareler[ki].sure < anK) ++ki; dokum::PartikulGorselAyar pa{}; pa.rampa = s->gorselAyar.rampa; pa.bandli = s->gorselAyar.bandli; pa.bandSayi = s->gorselAyar.bandSayi; pa.yariCapOran = dokumPartikulYaricapOran(); // "dokum.sph_partikul_yaricap_oran" pa.altOrnek = dokumPartikulCizAltOrnek(); // "dokum.sph_partikul_ciz_alt_ornek" pa.hizRenk = renk != DolumRenk::Sicaklik; // Sıcaklık seçilmediyse hız (çalkantı) // GLOBAL LEJANT KİLİDİ: skala kare kare oynarsa çalkantı renk gürültüsüne // dönüşür → aralık TÜM karelerden bir kez türetilir. pa.renkSabit = true; f32 mn = 1e30f, mx = -1e30f; for (const auto& k : kareler) for (const auto& p : k.partikul) { const f32 v = pa.hizRenk ? p.hiz : p.sicaklik; mn = std::min(mn, v); mx = std::max(mx, v); } pa.renkSabitMin = mn; pa.renkSabitMax = mx > mn ? mx : mn + 1.0f; dokum::VoxelGorselSonuc pg = dokum::partikulMeshKur(s->kafes, kareler[ki].partikul, pa); if (!pg.gecerli) { dolumKaldir(kimlik); return; } s->dolumMin = pg.alanMin; s->dolumMax = pg.alanMax; if (s->dolumDugum && sahne_.hazirMeshGuncelle(s->dolumDugum, pg.mesh)) { if (Dugum* d = sahne_.bul(s->dolumDugum)) { d->ad = "Partikül Bulutu"; d->donusum = don; d->saydamlik = 1.0f; } } else { dolumKaldir(kimlik); const u32 dugum = sahne_.hazirMeshEkle("Partikül Bulutu", grupAl(), {0.95f, 0.45f, 0.10f}, std::move(pg.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don; d->saydamlik = 1.0f; d->dinamikMesh = true; // scrubber canlı düğümü — mega-partiye girmez } s->dolumDugum = dugum; } xrayAyarla(kimlik, true, XrayDolum); // partiküller parça İÇİNDE → x-ray şart return; } if (gorunum != DolumGorunum::Voxel) { // MAGMA tarzı pürüzsüz sıvı yüzeyi: Marching Cubes izo-yüzey (izo = an). // MalzemeIzi = Material Trace: parça x-ray + yüzey rengi seçilen alandan. // NOT: kare-arası zaman-interpolasyonu gerekmez — izo değeri sürekli f32, // MC her ara anda alanın kendisinden kesit alır (topoloji sabit kafes). auto alanIt = dolumAlan_.find(kimlik); if (alanIt == dolumAlan_.end()) alanIt = dolumAlan_.emplace(kimlik, dokum::dolumAlanKur(s->kafes, s->dolum)).first; const f64 an = zamanKesim < 0 ? s->dolum.toplamSure : zamanKesim; dokum::SiviYuzeyAyar ayar{}; ayar.rampa = s->gorselAyar.rampa; ayar.bandli = s->gorselAyar.bandli; // MAGMA kademeli lejantla eşleşsin ayar.bandSayi = s->gorselAyar.bandSayi; ayar.renkSabit = true; // GLOBAL LEJANT KİLİDİ: skala animasyonda sabit const std::vector* renkAlan; std::vector hizAlan; // Hiz modu için geçici (aşağıda doldurulur) if (renk == DolumRenk::VarisZamani) { // Material Trace: renk = malzemenin giriş anı (cephe = en yeni = parlak). renkAlan = &alanIt->second; ayar.renkSabitMin = 0.0f; ayar.renkSabitMax = (f32)std::max(s->dolum.toplamSure, 1e-9); } else { // Cephe sıcaklığı; varsayılan aralık [tSolidus−50, dokumSicaklik]. renkAlan = &s->dolum.varisT; ayar.renkSabitMin = (f32)(s->alasim.tSolidus - 50.0); ayar.renkSabitMax = (f32)std::max(s->ayar.dokumSicaklik, s->alasim.tSolidus - 49.0); } dokum::SiviYuzeySonuc sy; const auto& kareler = s->sph.kareler; if (!kareler.empty() && kareler.front().doluluk.size() == s->kafes.hucreSayi()) { // SPH kare serisi: izo-kesim yerine kare-indeksli doluluk alanı (u8→0..1; // çalkantı/sıçrama görünür — varış-zamanı monoton kesiti bunu düzleştirir). // BUGDUUZTMESI: eskiden kare yalnizca EN YAKIN kareye ATLANIYORDU // (ki = ilk sure >= anK). Kare araligi sim adimindan kaba oldugu icin // iki kare arasinda gorunur bir zaman farki kaliyor → hucre dolulugu // 0→0→0→255 gibi KADEMELI ATLAYARAK geliyor, aradaki akis gorunmuyordu // ("sivi zipliyor / akmiyor"). DOGRUSU: iki kareyi LERPOLASYONLA // (doluluk dolulukta lineer) → surekli, gercek akis. Ayni zamanda // kare arasi 0.5 esiginin altinda olan hucre komsulari da dolulugu // tasir (yuzey yumusatma etkisi) — MC zaten yapisal olarak yapar, // burada erken katmanin SERT adimini yumusatmis oluyoruz. const f64 anK = zamanKesim < 0 ? kareler.back().sure : an; size_t ki = 0; while (ki + 1 < kareler.size() && kareler[ki + 1].sure <= anK) ++ki; const size_t kj = (ki + 1 < kareler.size()) ? ki + 1 : ki; const f64 t0 = kareler[ki].sure; const f64 t1 = kareler[kj].sure; // Geriye gidiş (scrubber başa sararsa) → ilk kareyi kullan, oran 0. f64 oran = 0.0; if (kj != ki && t1 > t0) oran = std::clamp((anK - t0) / (t1 - t0), 0.0, 1.0); const auto& d0 = kareler[ki].doluluk; const auto& d1 = kareler[kj].doluluk; std::vector alan(d0.size()); if (ki == kj) { for (size_t c = 0; c < d0.size(); ++c) alan[c] = d0[c] / 255.0; } else { // Lineer enterpolasyon + komsu yumuşatma ağırlığı (yalnız iki kare // arasında; karelerin kendisi zaten 0..255 nicemlenmiş ham doluluk). for (size_t c = 0; c < d0.size(); ++c) alan[c] = (d0[c] + (f64(d1[c]) - d0[c]) * oran) / 255.0; } ayar.izoAltiIc = false; // doluluk > izo taraf sıvı // İzo = fizik "dolu" eşiği (doluEsik ~0.25). 0.5 sabiti ince akış/sığ // havuz hücrelerini (kısmi doluluk) görünmez kılıyordu → erken kareler // BOŞ görünüp "animasyon gelmiyor" algısı doğuruyordu. const f32 izo = std::clamp((f32)s->sphAyar.doluEsik, 0.05f, 0.9f); // AKIS HIZI RENK KAYNAGI: hız alanı yalnız SPH karelerinde var. Renk // seçimi Hiz ise (ve alan dolu) hücre-başı ort. hız kullanılır → jet // çalkantısı / mandala akış / durgun yüzey kontrastı görünür. Diğer renk // modlarında T/varisT alanı DEĞİŞMEZ. if (renk == DolumRenk::Hiz && kareler[ki].hizOrt.size() == d0.size()) { const auto& h0 = kareler[ki].hizOrt; const auto& h1 = kareler[kj].hizOrt; hizAlan.resize(d0.size()); for (size_t c = 0; c < d0.size(); ++c) hizAlan[c] = (f64)h0[c] + ((f64)h1[c] - h0[c]) * oran; f64 hMin = 1e30, hMax = 0.0; for (const f32 v : h0) { hMin = std::min(hMin, (f64)v); hMax = std::max(hMax, (f64)v); } for (const f32 v : h1) { hMin = std::min(hMin, (f64)v); hMax = std::max(hMax, (f64)v); } if (hMin > hMax) { hMin = 0; hMax = 1; } if (hMax - hMin < 1e-6) hMax = hMin + 1.0; // akış durmuşsa bölmeyi koru for (f64& v : hizAlan) v = (v - hMin) / (hMax - hMin); renkAlan = &hizAlan; ayar.renkSabit = false; // normalize edilmiş → sabit lejant kilidi gerekmez } sy = dokum::siviYuzeyKur(s->kafes, alan, izo, ayar, renkAlan); } else { sy = dokum::siviYuzeyKur(s->kafes, alanIt->second, (f32)an, ayar, renkAlan); } if (!sy.gecerli) { dolumKaldir(kimlik); return; } s->dolumMin = sy.renkMin; s->dolumMax = sy.renkMax; const bool iz = gorunum == DolumGorunum::MalzemeIzi; const char* dugumAd = iz ? "Malzeme İzi" : "Sıvı Yüzeyi"; // Scrubber sıcak yolu: mevcut düğümü YERİNDE güncelle (hazirMeshGuncelle // ping-pong tampon) — düğüm sil/ekle + olay/ağaç churn + tampon yeniden // yaratma yok; dinamikMesh düğümü mega-partiye de girmez (pişirme yok). if (s->dolumDugum && sahne_.hazirMeshGuncelle(s->dolumDugum, sy.mesh)) { if (Dugum* d = sahne_.bul(s->dolumDugum)) { d->ad = dugumAd; d->donusum = don; d->saydamlik = 1.0f; } } else { dolumKaldir(kimlik); const u32 dugum = sahne_.hazirMeshEkle(dugumAd, grupAl(), {0.95f, 0.45f, 0.10f}, std::move(sy.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don; // SIVI HER ZAMAN OPAK (MAGMA: kalıp saydam, sıvı opak). Yarı saydam + // derinlik-yazmasız çizim arka yüzleri karıştırıp "ince kağıt" algısı // veriyordu; opak sıvı derinlik-doğru katı hacim gibi görünür. d->saydamlik = 1.0f; d->dinamikMesh = true; // scrubber canlı düğümü — mega-partiye girmez } s->dolumDugum = dugum; } // Parça x-ray: sıvı parça İÇİNDE — parça opak kalırsa sıvı hiç görünmez. xrayAyarla(kimlik, true, XrayDolum); return; } // Dolum viz, ısı-haritası ayarını (rampa/oran/yalnız-sınır) paylaşır. xrayAyarla(kimlik, false, XrayDolum); // voxel kipinde parça x-ray'i gerekmez dokum::VoxelGorselSonuc vg = dokum::dolumMeshKur(s->kafes, s->dolum, s->gorselAyar, zamanKesim); if (!vg.gecerli) { dolumKaldir(kimlik); return; } s->dolumMin = vg.alanMin; s->dolumMax = vg.alanMax; // Scrubber sıcak yolu: yerinde güncelle (yukarıdaki pürüzsüz yolla aynı gerekçe). if (s->dolumDugum && sahne_.hazirMeshGuncelle(s->dolumDugum, vg.mesh)) { if (Dugum* d = sahne_.bul(s->dolumDugum)) { d->ad = "Dolum Varış-Zamanı"; d->donusum = don; d->saydamlik = 1.0f; } } else { dolumKaldir(kimlik); // eskiyi kaldır (yeniden kur) const Vec3f taban{0.6f, 0.6f, 0.6f}; const u32 dugum = sahne_.hazirMeshEkle("Dolum Varış-Zamanı", grupAl(), taban, std::move(vg.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don; d->dinamikMesh = true; // scrubber canlı düğümü — mega-partiye girmez } s->dolumDugum = dugum; } } void DokumSistemi::cepheKaldir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->cepheDugum) return; if (sahne_.bul(s->cepheDugum)) sahne_.sil(s->cepheDugum); s->cepheDugum = 0; xrayAyarla(kimlik, false, XrayCephe); } void DokumSistemi::cepheGorsellestir(u32 kimlik, f64 zamanKesim, bool yalnizSivi) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->sonuc.hazir || !s->kafes.gecerliMi()) return; if (s->sonuc.katilasmaSuresi.size() != s->kafes.hucreSayi()) return; s->gorselAyar.kirpParcaya = dokumVoxelKirp(); // "dokum.voxel_kirp" (canlı) const Dugum* hd0 = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don0 = hd0 ? hd0->donusum : Donusumd{}; if (yalnizSivi) { // MAGMA Fraction Liquid (video v7): katılaşan hücreler gizli, yalnız sıvı // (katılaşma anı > t) küpleri — izole adacıklar = çekinti/hot-spot adayı. // Renk = katılaşma anı, GLOBAL KİLİTLİ [0, toplamSure] (geç = sıcak renk). const dokum::SonucKatmanlari* kl = katmanlarAl(kimlik); const dokum::KatmanBilgi* kb = kl ? kl->bul("Katılaşma Süresi") : nullptr; if (!kb || kb->alan.size() != s->kafes.hucreSayi()) return; dokum::VoxelGorselAyar pa = s->gorselAyar; pa.otoMinMax = false; pa.elleMin = 0.0f; pa.elleMax = (f32)std::max(s->sonuc.toplamSure, 1e-9); dokum::VoxelGorselSonuc vg = dokum::alanMeshKur(s->kafes, kb->alan, pa, (f32)zamanKesim); if (!vg.gecerli) { cepheKaldir(kimlik); return; } if (s->cepheDugum && sahne_.hazirMeshGuncelle(s->cepheDugum, vg.mesh)) { if (Dugum* d = sahne_.bul(s->cepheDugum)) { d->ad = "Sıvı Bölgeler"; d->donusum = don0; d->saydamlik = 1.0f; } } else { cepheKaldir(kimlik); const u32 dugum = sahne_.hazirMeshEkle("Sıvı Bölgeler", grupAl(), {0.95f, 0.45f, 0.10f}, std::move(vg.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don0; d->saydamlik = 1.0f; d->dinamikMesh = true; // scrubber canlı düğümü — mega-partiye girmez } s->cepheDugum = dugum; } xrayAyarla(kimlik, true, XrayCephe); return; } // Katılaşma cephesi: alan = katılaşma anı, izo = t → katı/sıvı arayüzü. // izoAltiIc=true: süregorselAyar.rampa; ayar.bandli = s->gorselAyar.bandli; ayar.bandSayi = s->gorselAyar.bandSayi; ayar.renkSabit = true; ayar.renkSabitMin = (f32)(s->alasim.tSolidus - 50.0); ayar.renkSabitMax = (f32)std::max(s->ayar.dokumSicaklik, s->alasim.tSolidus - 49.0); dokum::SiviYuzeySonuc sy = dokum::siviYuzeyKur( s->kafes, s->sonuc.katilasmaSuresi, (f32)zamanKesim, ayar, &s->kafes.T); if (!sy.gecerli) { cepheKaldir(kimlik); return; } const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; // Cephe OPAK + parça x-ray (MAGMA): parça içindeki arayüz ancak böyle görünür; // yarı saydam derinlik-yazmasız çizim "ince kağıt" algısı veriyordu. // Scrubber sıcak yolu: yerinde güncelle (dolum yüzeyiyle aynı gerekçe). if (s->cepheDugum && sahne_.hazirMeshGuncelle(s->cepheDugum, sy.mesh)) { if (Dugum* d = sahne_.bul(s->cepheDugum)) { d->donusum = don; d->saydamlik = 1.0f; } } else { cepheKaldir(kimlik); const u32 dugum = sahne_.hazirMeshEkle("Katılaşma Cephesi", grupAl(), {0.95f, 0.45f, 0.10f}, std::move(sy.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don; d->saydamlik = 1.0f; d->dinamikMesh = true; // scrubber canlı düğümü — mega-partiye girmez } s->cepheDugum = dugum; } xrayAyarla(kimlik, true, XrayCephe); } void DokumSistemi::isaretKupKur(u32& dugum, const char* ad, Vec3f c, f32 r, Vec3f renk) { // Küçük tel-kafes küp işareti (dünya koordinatı; hot-spot/porozite ortak). const Vec3f kk[8] = {{c.x - r, c.y - r, c.z - r}, {c.x + r, c.y - r, c.z - r}, {c.x + r, c.y + r, c.z - r}, {c.x - r, c.y + r, c.z - r}, {c.x - r, c.y - r, c.z + r}, {c.x + r, c.y - r, c.z + r}, {c.x + r, c.y + r, c.z + r}, {c.x - r, c.y + r, c.z + r}}; const int e[12][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7}}; std::vector kenarlar; Aabbf sinir; bool ilk = true; for (auto& seg : e) { for (int u = 0; u < 2; ++u) { const Vec3f p = kk[seg[u]]; kenarlar.push_back({{p.x, p.y, p.z}, 0}); if (ilk) { sinir.asgari = sinir.azami = p; ilk = false; } sinir.genislet(p); } } if (dugum && sahne_.bul(dugum)) { sahne_.kenarNesneGuncelle(dugum, kenarlar, sinir); } else { dugum = sahne_.kenarNesneEkle(ad, grupAl(), renk, kenarlar, sinir); } } void DokumSistemi::hotSpotIsaretle(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->sonuc.hazir || !s->kafes.gecerliMi()) return; Dugum* d = hedefDugumBul(sahne_, s->hedefGovde); if (!d) return; // Hot-spot hücre merkezi (mesh birimi) → dünya. const u32 idx = s->sonuc.hotSpotIndeks; const u32 nx = s->kafes.nx(), ny = s->kafes.ny(); if (nx == 0 || ny == 0) return; const u32 i = idx % nx, j = (idx / nx) % ny, k = idx / (nx * ny); const Vec3d w = d->donusum.noktaUygula(s->kafes.merkez(i, j, k)); // Hücre boyutunun ~1.5 katı — besleyici konumu ipucu. const f32 r = (f32)(s->kafes.h() / 0.001) * 1.5f; // mesh birimine geri ölçekle isaretKupKur(s->hotSpotDugum, "Hot-Spot", {(f32)w.x, (f32)w.y, (f32)w.z}, r, {1.0f, 0.35f, 0.10f}); } // ============================================================================ // Porozite (katılaşma + opsiyonel hava post-process; önbellekli) // ============================================================================ const dokum::PoroziteSonuc* DokumSistemi::poroziteAl(u32 kimlik, f32 esik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->sonuc.hazir || !s->kafes.gecerliMi()) return nullptr; auto it = por_.find(kimlik); if (it == por_.end() || std::fabs(it->second.esik - esik) > 1e-6f) { dokum::PoroziteAyar pa; pa.esik = esik; const dokum::HavaAnalizSonuc* hv = s->dolum.hazir ? havaAl(kimlik) : nullptr; PorOnbellek pc{dokum::poroziteHesapla(s->kafes, s->sonuc, hv, s->alasim, pa), esik}; it = por_.insert_or_assign(kimlik, std::move(pc)).first; } return it->second.sonuc.hazir ? &it->second.sonuc : nullptr; } void DokumSistemi::poroziteBaloncukGoster(u32 kimlik, bool ac, f32 esik) { auto dugumSil = [&] { if (auto it = porDugum_.find(kimlik); it != porDugum_.end()) { if (sahne_.bul(it->second)) sahne_.sil(it->second); porDugum_.erase(it); } }; if (!ac) { dugumSil(); xrayAyarla(kimlik, false, XrayPorozite); return; } DokumSimulasyonu* s = bul(kimlik); // İLK kurulumda eşiği VERİDEN türet (dolu hücrelerin üst yüzdeliği). Sabit // %0.5 ÖLÇÜLDÜ: hücrelerin %58'i üstünde → izo-yüzey parçayı kaplıyor, // "her yer kırmızı" — MAGMA'daki yerel baloncuk okuması sıfırdı. Kullanıcı // eşiği elle değiştirirse bir daha dokunulmaz (g_porOtoYapildi). if (dokumPoroziteOtoEsik() && g_porOtoYapildi.insert(kimlik).second) { if (const dokum::PoroziteSonuc* p0 = poroziteAl(kimlik, esik)) { const f32 oto = (f32)dokum::poroziteOtoEsik(p0->porozite, dokumPoroziteYuzdelik()); porEsikAl(kimlik) = oto; esik = oto; } } const dokum::PoroziteSonuc* por = poroziteAl(kimlik, esik); if (!s || !por) return; // Blob izo-yüzey: porozite > eşik taraf "iç" (izoAltiIc=false) + parça x-ray. // MAGMA baloncuk okuması: her baloncuk BÖLGESİNİN TEPE değeriyle boyanır // (renk = şiddet). Yüzey her yerde ≈ eşik olduğundan alanın KENDİSİYLE // boyamak tüm baloncukları dip renge ezerdi (eski sabit-kırmızının sebebi); // bolgeId → maksPor alanı bunu çözer. Lejant kilidi 0-10% (küp kipiyle aynı). std::vector alan(por->porozite.begin(), por->porozite.end()); dokum::SiviYuzeyAyar ayar{}; ayar.izoAltiIc = false; ayar.sabitRenk = {0.95f, 0.30f, 0.30f}; // bolgeId boşsa eski tek renk std::vector renkAlan; if (s->kafes.hucreSayi() == por->bolgeId.size() && !por->bolgeler.empty()) { renkAlan.assign(por->bolgeId.size(), 0.0); for (size_t c = 0; c < por->bolgeId.size(); ++c) if (por->bolgeId[c] >= 0) renkAlan[c] = por->bolgeler[(size_t)por->bolgeId[c]].maksPor; // TEK genişletme geçişi: izo-yüzey bölge hücresi ile dış komşusu ARASINDA // durur; dış uç 0 kalırsa MC vertex interpolasyonu baloncuğu benekler // (senaryo PNG ile görüldü). Dış halkaya komşu bölge değerinin maksimumu // kopyalanır → baloncuk tek renk (= bölge şiddeti). { const i32 nx = (i32)s->kafes.nx(), ny = (i32)s->kafes.ny(), nz = (i32)s->kafes.nz(); std::vector genis = renkAlan; auto oku = [&](i32 i, i32 j, i32 k) -> f64 { if (i < 0 || j < 0 || k < 0 || i >= nx || j >= ny || k >= nz) return 0.0; return renkAlan[s->kafes.indeks((u32)i, (u32)j, (u32)k)]; }; for (i32 k = 0; k < nz; ++k) for (i32 j = 0; j < ny; ++j) for (i32 i = 0; i < nx; ++i) { const u32 idx = s->kafes.indeks((u32)i, (u32)j, (u32)k); if (renkAlan[idx] > 0.0) continue; const f64 m = std::max({oku(i - 1, j, k), oku(i + 1, j, k), oku(i, j - 1, k), oku(i, j + 1, k), oku(i, j, k - 1), oku(i, j, k + 1)}); if (m > 0.0) genis[idx] = m; } renkAlan.swap(genis); } // Normalizasyon VERİ TEPESİNE kilitli: en kötü baloncuk rampanın parlak // ucunda (Turbo kırmızı), hafifler maviye düşer — MAGMA "en kötü göze // batar" okuması. 0-10 sabit kilidi %3'lük tipik çekmeyi Inferno'nun // siyah-mor dibine eziyordu (senaryo PNG ile görüldü, okunmuyordu). ayar.rampa = dokum::VoxelRampa::Turbo; ayar.renkSabit = true; ayar.renkSabitMin = 0.0f; ayar.renkSabitMax = (f32)std::max(1.0, por->maksPor); } dokum::SiviYuzeySonuc sy = dokum::siviYuzeyKur( s->kafes, alan, esik, ayar, renkAlan.empty() ? nullptr : &renkAlan); dugumSil(); if (!sy.gecerli) return; const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const u32 dugum = sahne_.hazirMeshEkle("Porozite Baloncukları", grupAl(), {0.95f, 0.35f, 0.35f}, std::move(sy.mesh)); if (Dugum* d = sahne_.bul(dugum)) { d->donusum = don; d->dinamikMesh = true; // eşik sürüklemede yeniden kurulur — mega-partiye girmez } porDugum_[kimlik] = dugum; xrayAyarla(kimlik, true, XrayPorozite); } void DokumSistemi::dolmayanBolgeIsaretle(u32 kimlik, const dokum::DolmayanBolge& b) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->kafes.gecerliMi()) return; const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const Vec3d w = don.noktaUygula({b.merkez.x, b.merkez.y, b.merkez.z}); const f32 hMesh = (f32)s->kafes.hMesh(); const f32 r = std::max((f32)std::cbrt(b.hacimMm3 * 0.75 / 3.14159265) * 1.6f, hMesh); u32& dugum = porIsaret_[kimlik]; // tek işaret yuvası (porozite ile ortak) isaretKupKur(dugum, "Dolmayan Bölge", {(f32)w.x, (f32)w.y, (f32)w.z}, r, {1.0f, 0.45f, 0.15f}); if (kameraOdak) kameraOdak(w, (f64)r * 4.0); } void DokumSistemi::poroziteBolgeIsaretle(u32 kimlik, int bolge) { DokumSimulasyonu* s = bul(kimlik); const dokum::PoroziteSonuc* por = poroziteAl(kimlik, porEsikAl(kimlik)); if (!s || !por || bolge < 0 || bolge >= (int)por->bolgeler.size()) return; const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const auto& b = por->bolgeler[bolge]; const Vec3d w = don.noktaUygula({b.merkez.x, b.merkez.y, b.merkez.z}); // İşaret yarıçapı ≈ bölge hacminden eşdeğer küre (görsel oran; min hücre boyu). const f32 hMesh = (f32)(s->kafes.h() / 0.001); const f32 r = std::max((f32)std::cbrt(b.hacimMm3 * 0.75 / 3.14159265) * 1.6f, hMesh); u32& dugum = porIsaret_[kimlik]; isaretKupKur(dugum, "Porozite Bölgesi", {(f32)w.x, (f32)w.y, (f32)w.z}, r, {1.0f, 0.25f, 0.55f}); if (kameraOdak) kameraOdak(w, (f64)r * 4.0); // main köprüsü bağlıysa uçuş } void DokumSistemi::poroziteKaldir(u32 kimlik) { poroziteBaloncukGoster(kimlik, false); if (auto it = porIsaret_.find(kimlik); it != porIsaret_.end()) { if (sahne_.bul(it->second)) sahne_.sil(it->second); porIsaret_.erase(it); } por_.erase(kimlik); } void DokumSistemi::voxelKaldir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->voxelDugum) return; if (sahne_.bul(s->voxelDugum)) sahne_.sil(s->voxelDugum); s->voxelDugum = 0; xrayAyarla(kimlik, false, XrayVoxel); // porozite ısı-haritası x-ray'i bırak } void DokumSistemi::voxelGorsellestir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s) return; s->gorselAyar.kirpParcaya = dokumVoxelKirp(); // "dokum.voxel_kirp" (canlı) // Kapali → düğümü kaldır (viz kapat). if (s->gorselAyar.alan == dokum::VoxelAlan::Kapali) { voxelKaldir(kimlik); return; } if (!s->kafes.gecerliMi()) return; // SAF motor: kafes+sonuç+ayar → renk-rampalı küp mesh (Sahne'ye bağlı değil). dokum::VoxelGorselSonuc vg; if (s->gorselAyar.alan == dokum::VoxelAlan::Porozite) { const f32 esik = porEsikAl(kimlik); const dokum::PoroziteSonuc* por = poroziteAl(kimlik, esik); if (!por) { voxelKaldir(kimlik); return; } // Lejant GLOBAL KİLİT: 0-10% (endüstri kritik aralığı; üstü doygun renk). // 0-100 kilidi tipik %0.5-3 çekme porozitesini rampanın dibine (siyah) // eziyordu — görsel ayrım sıfırdı. dokum::VoxelGorselAyar pa = s->gorselAyar; pa.otoMinMax = false; pa.elleMin = 0.0f; pa.elleMax = 10.0f; // MAGMA tarzı: yalnız eşik üstü (gözenekli) hücreler çizilir + parça x-ray // (aşağıda) — %0 hücrelerle tüm parçayı siyah kütle olarak örtmek bilgi // taşımıyordu. Eşik altı temiz parça = hiç küp yok (doğru okuma). vg = dokum::alanMeshKur(s->kafes, por->porozite, pa, esik); } else if (havaAlaniMi(s->gorselAyar.alan)) { const dokum::HavaAnalizSonuc* h = havaAl(kimlik); if (!h) { voxelKaldir(kimlik); return; } vg = dokum::alanMeshKur( s->kafes, s->gorselAyar.alan == dokum::VoxelAlan::SonHava ? h->lastAir : h->kabarcikRisk, s->gorselAyar); } else { if (!s->sonuc.hazir) return; vg = dokum::voxelMeshKur(s->kafes, s->sonuc, s->gorselAyar); } if (!vg.gecerli) { voxelKaldir(kimlik); return; } voxelSonucYerlestir_(s, std::move(vg)); // Porozite: seyrek iç hücreler opak parça içinde görünsün → parça x-ray. xrayAyarla(kimlik, s->gorselAyar.alan == dokum::VoxelAlan::Porozite, XrayVoxel); } // Kurulan küp mesh'i sahneye yerleştir (voxel/katman ortak son adım). void DokumSistemi::voxelSonucYerlestir_(DokumSimulasyonu* s, dokum::VoxelGorselSonuc vg) { s->gorselMin = vg.alanMin; s->gorselMax = vg.alanMax; s->gorselHucre = vg.hucreSayi; // Mesh birimi (merkez() ile aynı) → düğüm hedef gövdenin dönüşümünü taşımalı. const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; voxelKaldir(s->kimlik()); // eskiyi kaldır (yeniden kur; tampon bir kez yüklenir) const Vec3f taban{0.6f, 0.6f, 0.6f}; // per-vertex renk baskın; taban etkisiz const u32 dugum = sahne_.hazirMeshEkle("Döküm Isı-Haritası", grupAl(), taban, std::move(vg.mesh)); if (Dugum* d = sahne_.bul(dugum)) d->donusum = don; s->voxelDugum = dugum; } // ============================================================================ // Türetilmiş sonuç katmanları (sonuc_katmanlari; çözüm başına bir hesap) // ============================================================================ const dokum::SonucKatmanlari* DokumSistemi::katmanlarAl(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->kafes.gecerliMi() || (!s->sonuc.hazir && !s->dolum.hazir)) return nullptr; auto it = katman_.find(kimlik); if (it == katman_.end()) it = katman_.emplace(kimlik, dokum::katmanlariHesapla( s->kafes, s->sonuc, s->dolum.hazir ? &s->dolum : nullptr, s->alasim)).first; return it->second.hazir ? &it->second : nullptr; } void DokumSistemi::katmanGorsellestir(u32 kimlik, const char* katmanAd, f32 esik) { DokumSimulasyonu* s = bul(kimlik); const dokum::SonucKatmanlari* kl = katmanlarAl(kimlik); const dokum::KatmanBilgi* kb = (kl && katmanAd) ? kl->bul(katmanAd) : nullptr; if (!s || !kb || kb->alan.size() != s->kafes.hucreSayi()) { voxelKaldir(kimlik); return; } s->gorselAyar.kirpParcaya = dokumVoxelKirp(); // "dokum.voxel_kirp" (canlı) // Lejant: oto → KatmanBilgi.min/maks (yüzdelik kırpılmış); elle → kullanıcı. dokum::VoxelGorselAyar pa = s->gorselAyar; if (pa.otoMinMax) { pa.elleMin = kb->min; pa.elleMax = kb->maks; } pa.otoMinMax = false; // Geçersiz hücre (kKatmanGecersiz = -1) eşik altında kalır → boyanmaz. // esik > -0.5: değer-eşiği taraması — model "soyulur", en kritik bölge kalır. dokum::VoxelGorselSonuc vg = dokum::alanMeshKur(s->kafes, kb->alan, pa, esik); if (!vg.gecerli) { voxelKaldir(kimlik); return; } voxelSonucYerlestir_(s, std::move(vg)); // lejant sabit (elle aralık) — taramada oynamaz } // ============================================================================ // Hava hapsi analizi (dolum post-process; önbellekli) + vent önerisi küreleri // ============================================================================ const dokum::HavaAnalizSonuc* DokumSistemi::havaAl(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->dolum.hazir || !s->kafes.gecerliMi()) return nullptr; auto it = hava_.find(kimlik); if (it == hava_.end()) { dokum::HavaAnalizAyar ha; if (s->kurulum.graviteYon.uzunlukKare() > 1e-8f) { const Vec3f g = s->kurulum.graviteYon.birim(); ha.graviteYon = {g.x, g.y, g.z}; } it = hava_.emplace(kimlik, dokum::havaAnaliz(s->kafes, s->dolum, ha)).first; } return it->second.hazir ? &it->second : nullptr; } void DokumSistemi::ventKaldir(u32 kimlik) { auto it = ventDugum_.find(kimlik); if (it == ventDugum_.end()) return; for (u32 d : it->second) if (sahne_.bul(d)) sahne_.sil(d); ventDugum_.erase(it); } void DokumSistemi::ventGoster(u32 kimlik) { ventKaldir(kimlik); DokumSimulasyonu* s = bul(kimlik); const dokum::HavaAnalizSonuc* h = havaAl(kimlik); if (!s || !h || h->ventOnerileri.empty()) return; const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; // Küre yarıçapı ≈ hücre boyutunun 1.2 katı (mesh birimi, kurulum öğeleri ölçeği). const Vec3d m0 = s->kafes.merkez(0, 0, 0), m1 = s->kafes.merkez(1, 0, 0); const f32 r = s->kafes.nx() > 1 ? (f32)std::fabs(m1.x - m0.x) * 1.2f : 2.0f; const Vec3f renk{0.55f, 0.85f, 1.0f}; auto& liste = ventDugum_[kimlik]; char ad[48]; int n = 0; for (const Vec3f& p : h->ventOnerileri) { std::snprintf(ad, sizeof(ad), "Vent Önerisi %d", ++n); const u32 d = sahne_.hazirMeshEkle(ad, grupAl(), renk, dokum::kurulumKureMesh(p, r, renk)); if (Dugum* dd = sahne_.bul(d)) { dd->donusum = don; dd->saydamlik = 0.45f; } liste.push_back(d); } } // ============================================================================ // Besleme / Methoding (Chvorinov besleyici öneri + hayalet önizleme) // ============================================================================ namespace { // Hot-spot hücre merkezi → mesh birimi konum (kafes yerelinde). Vec3d hotSpotMerkezi(const DokumSimulasyonu& s) { const u32 idx = s.sonuc.hotSpotIndeks; const u32 nx = s.kafes.nx(), ny = s.kafes.ny(); const u32 i = idx % nx, j = (idx / nx) % ny, k = idx / (nx * ny); return s.kafes.merkez(i, j, k); } } // namespace void DokumSistemi::besleyiciOner(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->sonuc.hazir || !s->kafes.gecerliMi()) return; // Parça modülü (V/A) + besleyici boyutlandır (saf motor). s->parcaModul = dokum::parcaModulu(s->kafes); const Vec3d hs = hotSpotMerkezi(*s); s->besleyiciOneri = dokum::besleyiciOner(s->parcaModul, hs, s->besleyiciCarpan, s->besleyiciHYuk); const auto& o = s->besleyiciOneri; if (!o.gecerli) { besleyiciHayaletKaldir(kimlik); return; } // Hayalet: taban hot-spot üstünde, +Z boyunca yükselen tel-kafes silindir. // Yaklaşık daire (16 dilim) alt+üst halka + dikey teller (yarı-saydam turuncu). const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const f64 r = o.capMm * 0.5; const Vec3d taban = hs; // taban merkezi (mesh birimi) const int dilim = 16; auto halka = [&](f64 z, Vec3d out[16]) { for (int i = 0; i < dilim; ++i) { const f64 a = (f64)i / dilim * 6.283185307179586; const Vec3d ml{taban.x + r * std::cos(a), taban.y + r * std::sin(a), taban.z + z}; out[i] = don.noktaUygula(ml); } }; Vec3d alt[16], ust[16]; halka(0.0, alt); halka(o.yukseklikMm, ust); std::vector kenarlar; Aabbf sinir; bool ilk = true; auto ekleSeg = [&](Vec3d a, Vec3d b) { const Vec3f pa{(f32)a.x, (f32)a.y, (f32)a.z}, pb{(f32)b.x, (f32)b.y, (f32)b.z}; kenarlar.push_back({{pa.x, pa.y, pa.z}, 0}); kenarlar.push_back({{pb.x, pb.y, pb.z}, 0}); if (ilk) { sinir.asgari = sinir.azami = pa; ilk = false; } sinir.genislet(pa); sinir.genislet(pb); }; for (int i = 0; i < dilim; ++i) { const int n = (i + 1) % dilim; ekleSeg(alt[i], alt[n]); // alt halka ekleSeg(ust[i], ust[n]); // üst halka if (i % 2 == 0) ekleSeg(alt[i], ust[i]); // dikey teller (seyrek) } const Vec3f renk{1.0f, 0.55f, 0.15f}; // sıcak turuncu (öneri hayaleti) if (s->besleyiciHayaletDugum && sahne_.bul(s->besleyiciHayaletDugum)) { sahne_.kenarNesneGuncelle(s->besleyiciHayaletDugum, kenarlar, sinir); } else { s->besleyiciHayaletDugum = sahne_.kenarNesneEkle("Besleyici (öneri)", grupAl(), renk, kenarlar, sinir); } } void DokumSistemi::besleyiciHayaletKaldir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->besleyiciHayaletDugum) return; if (sahne_.bul(s->besleyiciHayaletDugum)) sahne_.sil(s->besleyiciHayaletDugum); s->besleyiciHayaletDugum = 0; } void DokumSistemi::besleyiciKabul(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s || !s->besleyiciOneri.gecerli || !s->besleyiciHayaletDugum) return; // Hayalet düğümü kalıcı ağaç nesnesine dönüştür (ad değiştir, düğümü koru). if (Dugum* d = sahne_.bul(s->besleyiciHayaletDugum)) { char ad[64]; std::snprintf(ad, sizeof(ad), "Besleyici ⌀%.0f×%.0f mm", s->besleyiciOneri.capMm, s->besleyiciOneri.yukseklikMm); d->ad = ad; d->renk = {1.0f, 0.35f, 0.10f}; } s->besleyiciHayaletDugum = 0; // artık kalıcı; ayrı hayalet tutma } // ============================================================================ // Döküm kurulumu sahne önizlemesi — GERÇEK saydam düğümler (seçilebilir, // ağaçta görünür, Delete ile silinebilir; VarlikSilindi → kayıt düşer). // ============================================================================ void DokumSistemi::kurulumDugumleriKaldir(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s) return; auto kaldir = [&](u32& d) { const u32 eski = d; d = 0; // ÖNCE sıfırla: VarlikSilindi geri-çağrısı kaydı düşürmesin if (eski && sahne_.bul(eski)) sahne_.sil(eski); }; for (auto& g : s->kurulum.girisler) kaldir(g.dugumKimlik); for (auto& c : s->kurulum.sogutucular) kaldir(c.dugumKimlik); for (auto& b : s->kurulum.besleyiciler) kaldir(b.dugumKimlik); for (auto& y : s->kurulum.yolluklar) kaldir(y.dugumKimlik); auto& dr = s->kurulum.derece; kaldir(dr.dugumKumAlt); kaldir(dr.dugumKumUst); kaldir(dr.dugumCerceveAlt); kaldir(dr.dugumCerceveUst); } void DokumSistemi::kurulumDugumYenile(u32 kimlik) { DokumSimulasyonu* s = bul(kimlik); if (!s) return; const Dugum* hd = hedefDugumBul(sahne_, s->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const u32 grup = grupAl(); auto yenile = [&](u32& dugumKimlik, const char* ad, Vec3f renk, MeshVeri mesh) { const u32 eski = dugumKimlik; dugumKimlik = 0; // ÖNCE sıfırla (VarlikSilindi senkron koruması) if (eski && sahne_.bul(eski)) sahne_.sil(eski); if (mesh.vertexler.empty() || !mesh.sinir.gecerliMi()) return; const u32 d = sahne_.hazirMeshEkle(ad, grup, renk, std::move(mesh)); if (Dugum* dd = sahne_.bul(d)) { dd->donusum = don; dd->saydamlik = 0.45f; // saydam önizleme (gerçek düğüm — pick/Delete çalışır) } dugumKimlik = d; }; const Vec3f yukari = s->kurulum.graviteYon.uzunlukKare() > 1e-8f ? -s->kurulum.graviteYon.birim() : Vec3f{0, 0, 1}; char ad[64]; int n = 0; for (auto& g : s->kurulum.girisler) { ++n; std::snprintf(ad, sizeof(ad), "Giriş %d ⌀%.0f", n, g.capMm); const Vec3f yon = g.yon.uzunlukKare() > 1e-8f ? g.yon.birim() : -yukari; yenile(g.dugumKimlik, ad, {0.20f, 0.80f, 1.00f}, g.aktif ? dokum::kurulumKoniMesh(g.konum, yon, (f32)g.capMm, (f32)(g.capMm * 2.0), {0.20f, 0.80f, 1.00f}) : MeshVeri{}); } n = 0; for (auto& c : s->kurulum.sogutucular) { ++n; std::snprintf(ad, sizeof(ad), "Soğutucu %d r%.0f", n, c.yaricapMm); yenile(c.dugumKimlik, ad, {0.25f, 0.45f, 1.00f}, c.aktif ? dokum::kurulumKureMesh(c.konum, (f32)c.yaricapMm, {0.25f, 0.45f, 1.00f}) : MeshVeri{}); } n = 0; for (auto& b : s->kurulum.besleyiciler) { ++n; std::snprintf(ad, sizeof(ad), "Besleyici %d ⌀%.0f×%.0f", n, b.capMm, b.yukseklikMm); yenile(b.dugumKimlik, ad, {1.00f, 0.55f, 0.15f}, b.aktif ? dokum::kurulumSilindirMesh(b.konum, yukari, (f32)b.capMm, (f32)b.yukseklikMm, {1.00f, 0.55f, 0.15f}) : MeshVeri{}); } n = 0; for (auto& y : s->kurulum.yolluklar) { ++n; std::snprintf(ad, sizeof(ad), "Yolluk %d", n); const f32 r = (f32)std::sqrt(std::max(y.kesitMm2, 1.0) / 3.14159265358979); yenile(y.dugumKimlik, ad, {0.95f, 0.80f, 0.25f}, (y.aktif && y.guzergah.size() >= 2) ? dokum::kurulumTupMesh(y.guzergah, r, {0.95f, 0.80f, 0.25f}) : MeshVeri{}); } // Derece (kum döküm): alt/üst kum blokları + çelik çerçeveler. Yalnız kum // kalıpta (gecirgenlik>0; kokilde derece yok) ve göster açıkken. Fizik // ETKİLENMEZ — kum bölgesi kafeste zaten kalıp payı olarak var, bu önizleme. // DÜNYA-eksenli: derece yere oturur — parça sahnede dönük dursa da derece // ÇAPRAZ DURMAZ (yerel AABB 8 köşesi dünyaya taşınır, kutular dünya uzayında // kurulur, düğüm dönüşümü kimlik bırakılır). Mala z de DÜNYA z'sidir. { auto& dr = s->kurulum.derece; const Aabbd psYerel = parcaHamSinir(sahne_, *s); Aabbd ps; if (psYerel.gecerliMi()) for (int c = 0; c < 8; ++c) ps.genislet(don.noktaUygula({(c & 1) ? psYerel.azami.x : psYerel.asgari.x, (c & 2) ? psYerel.azami.y : psYerel.asgari.y, (c & 4) ? psYerel.azami.z : psYerel.asgari.z})); const bool kum = s->kalip.gecirgenlik > 1e-13; const bool ciz = dr.goster && kum && ps.gecerliMi(); if (dr.malaOto) dr.malaZMm = 0.5 * (ps.asgari.z + ps.azami.z); const f32 mala = (f32)dr.malaZMm; const f32 pay = (f32)std::max(dr.kumPayMm, 1.0); const f32 x0 = (f32)ps.asgari.x - pay, x1 = (f32)ps.azami.x + pay; const f32 y0 = (f32)ps.asgari.y - pay, y1 = (f32)ps.azami.y + pay; const f32 zAlt = (f32)(ps.asgari.z - std::max(dr.altPayMm, 0.0)); const f32 zUst = (f32)(ps.azami.z + std::max(dr.ustPayMm, 0.0)); const Vec3f kumRenk{0.78f, 0.62f, 0.38f}, celik{0.52f, 0.55f, 0.60f}; // Mala aralığı (±0.4mm): iki kum bloğu bitişik görünmesin, ayrım çizgisi okunur. const bool zOk = zAlt < mala - 1.0f && mala + 1.0f < zUst; yenile(dr.dugumKumAlt, "Alt Derece Kumu", kumRenk, (ciz && zOk) ? dokum::kurulumKutuMesh({x0, y0, zAlt}, {x1, y1, mala - 0.4f}, kumRenk) : MeshVeri{}); yenile(dr.dugumKumUst, "Üst Derece Kumu", kumRenk, (ciz && zOk) ? dokum::kurulumKutuMesh({x0, y0, mala + 0.4f}, {x1, y1, zUst}, kumRenk) : MeshVeri{}); const f32 duvar = (f32)dr.duvarMm; // Tek parça derece: TEK çerçeve tüm yüksekliği sarar (parça konur, // kumlanır, ters çevrilip kalanı kumlanır). Kum yine 2 blok — mala hattı // her iki yöntemde de var (model çıkarma düzlemi). yenile(dr.dugumCerceveAlt, dr.tekParca ? "Derece (tek parça)" : "Alt Derece", celik, (ciz && zOk) ? dokum::kurulumCerceveMesh({x0, y0, zAlt}, {x1, y1, dr.tekParca ? zUst : mala}, duvar, celik) : MeshVeri{}); yenile(dr.dugumCerceveUst, "Üst Derece", celik, (ciz && zOk && !dr.tekParca) ? dokum::kurulumCerceveMesh({x0, y0, mala}, {x1, y1, zUst}, duvar, celik) : MeshVeri{}); // yenile hedef gövde dönüşümünü uygular; derece mesh'i zaten DÜNYA // uzayında kuruldu → kimlik dönüşümüne çek (yoksa çapraz durur). for (u32 dk : {dr.dugumKumAlt, dr.dugumKumUst, dr.dugumCerceveAlt, dr.dugumCerceveUst}) if (Dugum* dd = dk ? sahne_.bul(dk) : nullptr) dd->donusum = Donusumd{}; // Kum blokları parçayı boğmasın: önizleme saydamlığından (0.45) daha // şeffaf çizilir (MAGMA'da kalıp hafif görünür); çerçeve 0.45 kalır. for (u32 dk : {dr.dugumKumAlt, dr.dugumKumUst}) if (Dugum* dd = dk ? sahne_.bul(dk) : nullptr) dd->saydamlik = (f32)dokumDereceKumSaydamlik(); } // Girişler kavitenin İÇİNDE mi? (yalnız yenilemede — O(N) tarama kare-başı // yasak). Kuma/dereceye tıklanmış giriş en yakın metal hücreden uzağa düşer; // SPH emitter'ı snap ETMEZ → partiküller kaviteye giremez, dolum yarım kalır. { auto& uzak = g_girisDisarda[s->kimlik()]; uzak.assign(s->kurulum.girisler.size(), 0); if (s->kafes.gecerliMi() && s->kafes.hMesh() > 0) { const f64 esik = dokumGirisUzakEsikH() * s->kafes.hMesh(); const u32 nx = s->kafes.nx(), ny = s->kafes.ny(); for (size_t i = 0; i < s->kurulum.girisler.size(); ++i) { const auto& g = s->kurulum.girisler[i]; if (!g.aktif) continue; const u32 idx = dokum::enYakinMetalHucre(s->kafes, g.konum); if (idx == (u32)-1) { uzak[i] = 1; continue; } const Vec3d m = s->kafes.merkez(idx % nx, (idx / nx) % ny, idx / (nx * ny)); const Vec3d f{m.x - g.konum.x, m.y - g.konum.y, m.z - g.konum.z}; if (f.uzunluk() > esik) uzak[i] = 1; } } } } // ============================================================================ // Panel (pinlenebilir pencere) // ============================================================================ namespace { // f64 alan için dar DragFloat (UI köprüsü). Dönüş: değişti mi. bool dragF64(const char* etiket, f64& v, f32 hiz, f32 mn, f32 mx, const char* fmt, f32 genislik = 70.0f) { float f = (float)v; ImGui::SetNextItemWidth(genislik); if (ImGui::DragFloat(etiket, &f, hiz, mn, mx, fmt)) { v = f; return true; } return false; } // ---- Kritik an önbelleği (şerit rozetleri; O(N) taramalar kare-başı değil) -- struct KritikAnlar { f64 dolumKey = -1, katKey = -1; // önbellek anahtarı (toplam süreler) f64 doluluk[4] = {-1, -1, -1, -1}; // %25/50/75/100 doluluk anları [s] f64 sonHava = -1; // ilk son-hava bölgesi kapanış anı [s] f64 katilasmaBas = -1; // katılaşma başlangıcı [s] f64 katYuzde[4] = {-1, -1, -1, -1}; // %25/50/75/100 katılaşma anları [s] std::vector katSirali; // sıralı katılaşma anları (kalan sıvı % HUD) }; std::unordered_map g_kritik; const KritikAnlar& kritikAl(DokumSistemi& ds, dokum::DokumSimulasyonu& s) { KritikAnlar& k = g_kritik[s.kimlik()]; static const f64 kOran[4] = {0.25, 0.5, 0.75, 1.0}; if (s.dolum.hazir && k.dolumKey != s.dolum.toplamSure) { k.dolumKey = s.dolum.toplamSure; const auto& df = s.dolum; for (int q = 0; q < 4; ++q) k.doluluk[q] = -1; if (!df.sira.empty() && df.varisZamani.size() > 0) for (int q = 0; q < 4; ++q) { const size_t i = std::min((size_t)(kOran[q] * (f64)df.sira.size()), df.sira.size() - 1); if (df.sira[i] < df.varisZamani.size()) k.doluluk[q] = df.varisZamani[df.sira[i]]; } k.sonHava = -1; if (const dokum::HavaAnalizSonuc* hv = ds.havaAl(s.kimlik())) for (const auto& b : hv->sonHavaBolgeleri) if (b.maksVarisZamani >= 0 && (k.sonHava < 0 || b.maksVarisZamani < k.sonHava)) k.sonHava = b.maksVarisZamani; } if (s.sonuc.hazir && k.katKey != s.sonuc.toplamSure) { k.katKey = s.sonuc.toplamSure; std::vector t; t.reserve(s.sonuc.katilasmaSuresi.size()); for (f64 v : s.sonuc.katilasmaSuresi) if (v > 0) t.push_back(v); k.katilasmaBas = -1; for (int q = 0; q < 4; ++q) k.katYuzde[q] = -1; k.katSirali.clear(); if (!t.empty()) { std::sort(t.begin(), t.end()); // tam sıralı: yüzdelikler + kalan sıvı % HUD k.katilasmaBas = t.front(); for (int q = 0; q < 4; ++q) { const size_t i = std::min((size_t)(kOran[q] * (f64)t.size()), t.size() - 1); k.katYuzde[q] = t[i]; } k.katSirali = std::move(t); } } return k; } // Rozet listesi kurucusu: string ömrü frame boyu (SSO taşınmasın diye reserve). struct RozetListe { std::vector roz; std::vector metin; void ayirt(size_t n) { roz.reserve(n); metin.reserve(n); } void ekle(f64 anS, f64 toplam, std::string m, ImVec4 renk = {0, 0, 0, 0}) { if (anS < 0 || toplam <= 0 || anS > toplam * 1.0001) return; metin.push_back(std::move(m)); roz.push_back({(float)std::clamp(anS / toplam, 0.0, 1.0), metin.back().c_str(), renk}); } }; // Ortak şerit: FikoUi animasyonSeridi + throttle'lı görsel tazeleme (TEK yer; // dolum + katılaşma cephesi aynı yardımcıyı kullanır — kopya kod yok). // Oynatma sırasında throttle YOK (akıcı MC); elle sürüklemede ~30 Hz. template void seritCizVeKur(const char* kimlik, ui::SeritDurum& sd, f64 toplamSure, const RozetListe& rl, double& sonKur, KurFn&& kur) { const ui::SeritOlay o = ui::animasyonSeridi(kimlik, sd, (float)toplamSure, rl.roz.data(), (int)rl.roz.size()); if (!o.degisti) return; const double simdi = ImGui::GetTime(); if (!o.kesin && !sd.oynat && simdi - sonKur < 0.033) return; sonKur = simdi; kur(sd.zaman * toplamSure, o.kesin); } // Viewport HUD satırı (sol-alt, MAGMA kalıbı): gölge + panel zemin. satir = 0 // en alt, her satır ~30 px yukarı (künye + animasyon HUD üst üste binmez). void hudSatir(const char* buf, float boy, int satir, ImU32 renk) { const ui::Tema& tm = ui::tema(); ImGuiViewport* vp = ImGui::GetMainViewport(); ImDrawList* dl = ImGui::GetForegroundDrawList(vp); ImFont* f = ImGui::GetFont(); const ImVec2 mb = f->CalcTextSizeA(boy, FLT_MAX, 0.0f, buf); const ImVec2 poz{vp->Pos.x + 18.0f, vp->Pos.y + vp->Size.y - mb.y - 18.0f - (float)satir * 30.0f}; dl->AddRectFilled({poz.x - 8, poz.y - 5}, {poz.x + mb.x + 8, poz.y + mb.y + 5}, ui::u32(tm.panel, 0.72f), 6.0f); dl->AddText(f, boy, {poz.x + 1, poz.y + 1}, IM_COL32(0, 0, 0, 160), buf); dl->AddText(f, boy, poz, renk, buf); } // MAGMA tarzı viewport HUD (sol-alt): oynatma sırasında an/doluluk/hız. void dolumHudCiz(const dokum::DokumSimulasyonu& s, const ui::SeritDurum& sd) { if (!sd.oynat) return; const auto& df = s.dolum; const f64 an = (f64)sd.zaman * df.toplamSure; // O anki doluluk %: varış ≤ an hücre sayısı (sira sıralı → ikili arama). u32 dolan = 0; if (!df.sira.empty()) { u32 lo = 0, hi = (u32)df.sira.size(); while (lo < hi) { const u32 mid = (lo + hi) / 2; if (df.varisZamani[df.sira[mid]] <= an) lo = mid + 1; else hi = mid; } dolan = lo; } const f32 doluluk = df.toplamMetal ? 100.0f * (f32)dolan / (f32)df.toplamMetal : 0.0f; char buf[96]; std::snprintf(buf, sizeof(buf), "t = %.2f s · %%%.0f doluluk · %g×", an, doluluk, ui::seritHizlar[std::clamp(sd.hizIndeks, 0, ui::seritHizSayi - 1)]); hudSatir(buf, 22.0f, 0, ui::u32(ui::tema().vurgu)); } // Katılaşma HUD'u (MAGMA Fraction Liquid — video v7): an + KALAN SIVI %. // katSirali: sıralı katılaşma anları (kritik-an önbelleğinden; ikili arama). void cepheHudCiz(const std::vector& katSirali, f64 toplamSure, const ui::SeritDurum& sd) { if (!sd.oynat || katSirali.empty() || toplamSure <= 0) return; const f64 an = (f64)sd.zaman * toplamSure; const size_t kati = std::upper_bound(katSirali.begin(), katSirali.end(), an) - katSirali.begin(); const f32 sivi = 100.0f * (f32)(katSirali.size() - kati) / (f32)katSirali.size(); char buf[96]; std::snprintf(buf, sizeof(buf), "t = %.2f s · %%%.1f sıvı · %g×", an, sivi, ui::seritHizlar[std::clamp(sd.hizIndeks, 0, ui::seritHizSayi - 1)]); hudSatir(buf, 22.0f, 0, ui::u32(ui::tema().vurgu)); } // Versiyon künyesi (MAGMA v01 kalıbı): sim sürüm no + ad + aktif katman/görünüm. // Herhangi bir döküm görseli açıkken sol-altta 1 satır (animasyon HUD'unun üstü). void kunyeHudCiz(DokumSistemi& ds, const dokum::DokumSimulasyonu& s, const char* gorunumAd, bool animHudVar) { int surum = 1; for (size_t i = 0; i < ds.hepsi().size(); ++i) if (ds.hepsi()[i] == s.kimlik()) { surum = (int)i + 1; break; } char buf[128]; std::snprintf(buf, sizeof(buf), "v%02d · %s · %s", surum, s.ad().c_str(), gorunumAd ? gorunumAd : ""); hudSatir(buf, 15.0f, animHudVar ? 1 : 0, ui::u32(ui::tema().metinSoluk)); } // Kavite metal hacmi [m³] — önbellekli (g_metalHacim; her-kare O(N) tarama // yasak; dolum çözülmüşse toplamMetal sayımı bedava). MetalHacimOnbellek& metalOnbellekAl(DokumSimulasyonu& s) { auto& mh = g_metalHacim[s.kimlik()]; if (mh.n != s.kafes.hucreSayi() || mh.h != s.kafes.hMesh()) { mh.n = s.kafes.hucreSayi(); mh.h = s.kafes.hMesh(); mh.hacim = dokum::kafesMetalHacimM3(s.kafes); mh.kapsama = dokum::kafesParcaKapsama(s.kafes); // -1 = bilinmiyor } return mh; } f64 metalHacimM3Onbellekli(DokumSimulasyonu& s) { if (!s.kafes.gecerliMi()) return 0.0; if (s.dolum.hazir && s.dolum.toplamMetal > 0) return (f64)s.dolum.toplamMetal * s.kafes.hucreHacim(); return metalOnbellekAl(s).hacim; } // KÜTLE DENETİM ŞERİDİ (Kurulum + Dolum sekmeleri): dolum çözücüleri KÜTLE // KORUNUMLU — girişlerin toplam kütlesi kavite kütlesinden azsa kalıp fizik // gereği DOLAMAZ. Fikri "hiç sıkıntı yok ama doldurmuyor" dedi: şerit yalnız // Kurulum sekmesindeydi, Dolum sekmesinden çalışan hiç görmüyordu. // Test: l2_dolum_giris_kutle_yetersiz_misrun. void kutleDenetimSeridi(DokumSistemi& ds, DokumSimulasyonu& s) { (void)ds; if (!s.kafes.gecerliMi()) return; f64 girisKg = 0; bool aktifKutle = false; for (const auto& g : s.kurulum.girisler) if (g.aktif && g.kutleKg > 0 && g.sureS > 0) { girisKg += g.kutleKg; aktifKutle = true; } const f64 kaviteKg = metalHacimM3Onbellekli(s) * s.alasim.yogunluk; if (aktifKutle && kaviteKg > 0 && girisKg < kaviteKg) { char kb[192]; std::snprintf(kb, sizeof(kb), "Girişler toplam %.2f kg < kavite %.2f kg — kalıp DOLAMAZ " "(dolum kütle korunumlu; eksik dolum/misrun).", girisKg, kaviteKg); ui::bilgiSeridi(3, kb); const f64 hedefKg = kaviteKg * dokumGirisKutlePay(); char ub[48]; std::snprintf(ub, sizeof(ub), "Uygula: %.2f kg##kutle", hedefKg); if (ImGui::SmallButton(ub)) { const f64 oran = hedefKg / girisKg; // orantılı dağıt (girişlerin dengesi korunur) for (auto& g : s.kurulum.girisler) if (g.aktif && g.kutleKg > 0 && g.sureS > 0) { g.kutleKg *= oran; g.sureS = dokum::dokumSuresiOner(s.alasim, g.kutleKg, s.parcaModul.kesitKalinlik); } s.dolumBayat = true; } ImGui::SetItemTooltip( "Aktif girişlerin kütlesini orantılı ölçekler (kavite × pay) ve " "süreyi √W formülünden yeniler.\nPay: Ayarlar → Döküm → Giriş " "kütlesi uygula payı"); } } // 🎯 sahneden konum/yön seç (SahneRefSecim serbest-nokta; cam_parca_op kalıbı). // yaz(sim, yerelNokta, yerelNormal, normalVar): dünya → hedef gövde yereline // çevrilmiş vuruş. coklu=true → mod açık kalır (yolluk güzergâhı; ESC bitirir). void kurulumKonumSec(DokumSistemi& ds, Sahne& sahne, DokumSimulasyonu& s, const char* etiket, const char* ipucu, std::function yaz, bool coklu = false) { ImGui::SameLine(); ImGui::PushID(etiket); if (!ds.refSecim) { ImGui::BeginDisabled(); ImGui::SmallButton("🎯"); ImGui::EndDisabled(); ImGui::SetItemTooltip("Sahneden seçim bağlı değil (main köprüsü eksik)"); ImGui::PopID(); return; } SahneRefSecim& rs = *ds.refSecim; const bool aktif = rs.aktifMi(s.kimlik(), etiket); if (aktif) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.08f, 0.42f, 0.55f, 1.0f}); if (ImGui::SmallButton("🎯")) { if (aktif) { rs.bitir(); } else { DokumSistemi* dsp = &ds; Sahne* sp = &sahne; const u32 kim = s.kimlik(); rs.baslat( kim, etiket, [dsp, sp, kim, yaz = std::move(yaz), coklu](const SahneRefSecim::Vurus& v) -> bool { DokumSimulasyonu* sim = dsp->bul(kim); if (!sim) return false; // Derece/kum görseline tıklamayı YUT: koca saydam kutu her ışını // yakalıyor, giriş kum yüzeyine oturuyordu (kavite dışı giriş → // SPH partikülleri kaviteye giremez → "bu kadarcık doluyor"). const auto& drf = sim->kurulum.derece; if (v.govde && (v.govde == drf.dugumKumAlt || v.govde == drf.dugumKumUst || v.govde == drf.dugumCerceveAlt || v.govde == drf.dugumCerceveUst)) return true; // mod açık kalsın, nokta yazılmasın const Dugum* hd = hedefDugumBul(*sp, sim->hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; const Vec3d yl = don.ters().noktaUygula(v.nokta); // dünya→yerel const Vec3d nl = don.yonelim.eslenik().dondur(v.normal); // yalnız dönme yaz(*sim, {(f32)yl.x, (f32)yl.y, (f32)yl.z}, {(f32)nl.x, (f32)nl.y, (f32)nl.z}, v.normalVar); dsp->kurulumDugumYenile(kim); return coklu; }, std::string(ipucu) + " · ESC=bitir", 0, true); } } if (aktif) ImGui::PopStyleColor(); ImGui::SetItemTooltip("%s", ipucu); ImGui::PopID(); } // ---- 🏭 Döküm Kurulumu bölümü: girişler + soğutucular + besleyiciler + // yolluklar + döküm yönü. Değişiklikler sahne önizleme düğümlerini yeniler. void kurulumBolumu(Sahne& sahne, DokumSistemi& ds, DokumSimulasyonu& s) { ImGui::Separator(); if (!ImGui::CollapsingHeader("🏭 Döküm Kurulumu")) return; const ImVec4 SOLUK = ui::tema().metinSoluk; const ImVec4 CYAN = ui::tema().vurgu; const ImVec4 SARI = ui::tema().uyari; const ImVec4 YESIL = ui::tema().basari; auto& kur = s.kurulum; bool yenile = false; // sahne önizleme düğümleri yeniden kurulsun // ---- Döküm yönü (gravite) ---- struct YonGiris { const char* ad; Vec3f v; }; static const YonGiris kYonler[] = { {"−Z (aşağı, standart)", {0, 0, -1}}, {"+Z (yukarı)", {0, 0, 1}}, {"−X", {-1, 0, 0}}, {"+X", {1, 0, 0}}, {"−Y", {0, -1, 0}}, {"+Y", {0, 1, 0}}, }; const char* yonAd = "özel"; for (auto& y : kYonler) if (kur.graviteYon.yaklasikEsit(y.v, 1e-3f)) { yonAd = y.ad; break; } ImGui::TextColored(SOLUK, "Döküm Yönü (gravite)"); ImGui::SetNextItemWidth(170); if (ImGui::BeginCombo("##dokumYon", yonAd)) { for (auto& y : kYonler) if (ImGui::Selectable(y.ad, kur.graviteYon.yaklasikEsit(y.v, 1e-3f))) { kur.graviteYon = y.v; s.dolumBayat = s.bayat = true; yenile = true; } ImGui::EndCombo(); } kurulumKonumSec(ds, sahne, s, "kur_yon", "Yüz normalinden döküm yönü seç", [](DokumSimulasyonu& sim, Vec3f, Vec3f n, bool nVar) { if (!nVar || n.uzunlukKare() < 1e-8f) return; sim.kurulum.graviteYon = -n.birim(); // yüzün içine akış sim.dolumBayat = sim.bayat = true; }); // ---- Girişler (çoklu zamanlamalı) ---- ImGui::Spacing(); ImGui::TextColored(SOLUK, "Girişler (%d)", (int)kur.girisler.size()); ImGui::SameLine(); if (ImGui::SmallButton("➕##giris")) { dokum::DokumGirisi g; g.yon = kur.graviteYon; if (s.kafes.gecerliMi()) { // varsayılan konum: kafes üstü orta const auto& sn = s.kafes.sinir(); g.konum = {(f32)(0.5 * (sn.asgari.x + sn.azami.x)), (f32)(0.5 * (sn.asgari.y + sn.azami.y)), (f32)sn.azami.z}; } g.sureS = dokum::dokumSuresiOner(s.alasim, g.kutleKg, s.parcaModul.kesitKalinlik); kur.girisler.push_back(g); s.dolumBayat = true; yenile = true; } for (size_t i = 0; i < kur.girisler.size(); ++i) { auto& g = kur.girisler[i]; ImGui::PushID((int)i); if (ImGui::Checkbox("##aktif", &g.aktif)) { s.dolumBayat = true; yenile = true; } ImGui::SameLine(); ImGui::TextColored(CYAN, "Giriş %d", (int)i + 1); char etGiris[32]; std::snprintf(etGiris, sizeof(etGiris), "kur_giris_%d", (int)i); kurulumKonumSec(ds, sahne, s, etGiris, "Giriş konumunu sahneden seç", [i](DokumSimulasyonu& sim, Vec3f p, Vec3f n, bool nVar) { if (i >= sim.kurulum.girisler.size()) return; auto& gg = sim.kurulum.girisler[i]; gg.konum = p; if (nVar && n.uzunlukKare() > 1e-8f) gg.yon = -n.birim(); sim.dolumBayat = true; }); ImGui::SameLine(); if (ImGui::SmallButton("🗑")) { const u32 eski = g.dugumKimlik; kur.girisler.erase(kur.girisler.begin() + i); if (eski && sahne.bul(eski)) sahne.sil(eski); s.dolumBayat = true; ImGui::PopID(); break; // liste değişti — bu kare için çık } ImGui::Indent(18.0f); if (dragF64("kg##m", g.kutleKg, 0.1f, 0.01f, 100000.0f, "%.2f kg")) s.dolumBayat = true; ImGui::SameLine(); if (dragF64("süre##s", g.sureS, 0.1f, 0.1f, 600.0f, "%.1f s")) s.dolumBayat = true; ImGui::SameLine(); if (ImGui::SmallButton("📐")) { // arastirma.md süre formülü önerisi g.sureS = dokum::dokumSuresiOner(s.alasim, g.kutleKg, s.parcaModul.kesitKalinlik); s.dolumBayat = true; } ImGui::SetItemTooltip("Süreyi formülden öner (√W — alaşıma göre)"); if (dragF64("gecikme##g", g.gecikmeS, 0.1f, 0.0f, 600.0f, "%.1f s")) s.dolumBayat = true; ImGui::SameLine(); if (dragF64("⌀##c", g.capMm, 0.5f, 2.0f, 200.0f, "%.0f mm")) { s.dolumBayat = true; yenile = true; } // Campbell kritik giriş hızı denetimi (entrainment/oksit katlanması). const f64 v = dokum::girisOrtalamaHiz(g, s.alasim.yogunluk); const f64 vKritik = dokum::kritikGirisHiz(s.alasim); if (v > vKritik) ImGui::TextColored(SARI, "⚠ Giriş hızı %.2f m/s > kritik %.1f (çapı büyüt/süreyi uzat)", v, vKritik); // Giriş kavitenin dışında mı (kuma/dereceye tıklanmış olabilir)? SPH // emitter'ı metal hücreye SNAP ETMEZ → dolum "bu kadarcık" kalır. // "Kaviteye taşı" = tek tık düzeltme (Fikri uyarıyı görüp yine de çözüyordu). if (const auto it = g_girisDisarda.find(s.kimlik()); it != g_girisDisarda.end() && i < it->second.size() && it->second[i]) { ImGui::TextColored(SARI, "⚠ Giriş kavitenin DIŞINDA — SPH partikülleri kaviteye giremez"); const bool metalVar = s.kafes.gecerliMi() && s.kafes.hucreHacim() > 0 && metalHacimM3Onbellekli(s) > 0.5 * s.kafes.hucreHacim(); if (metalVar) { ImGui::SameLine(); if (ImGui::SmallButton("Kaviteye taşı##tasi")) { if (const u32 idx = dokum::enYakinMetalHucre(s.kafes, g.konum); idx != (u32)-1) { const u32 knx = s.kafes.nx(), kny = s.kafes.ny(); const Vec3d m = s.kafes.merkez(idx % knx, (idx / knx) % kny, idx / (knx * kny)); g.konum = {(f32)m.x, (f32)m.y, (f32)m.z}; s.dolumBayat = true; yenile = true; } } ImGui::SetItemTooltip( "Girişi en yakın metal hücre merkezine taşır — emitter kaviteden akar"); } else { // Taşınacak hücre YOK — "taşı basıyorum, uyarı gitmiyor"un açıklaması. ImGui::TextColored(ui::tema().hata, " taşınamaz: kafeste metal hücre YOK (üstteki " "çözünürlük/kafes uyarısına bak)"); } } ImGui::Unindent(18.0f); ImGui::PopID(); } // KÜTLE DENETİMİ (ortak şerit — Dolum sekmesinde de görünür). kutleDenetimSeridi(ds, s); // Döküm sıcaklığı band denetimi (arastirma.md alaşım tabloları). { f64 mn = 0, mx = 0; dokum::dokumSicaklikBandi(s.alasim, mn, mx); if (s.ayar.dokumSicaklik < mn) ImGui::TextColored(SARI, "⚠ Döküm sıcaklığı düşük (önerilen %.0f–%.0f °C) — misrun riski", mn, mx); else if (s.ayar.dokumSicaklik > mx) ImGui::TextColored(SARI, "⚠ Döküm sıcaklığı yüksek (önerilen %.0f–%.0f °C) — erozyon/çekme", mn, mx); } // ---- Soğutucular (chill) ---- ImGui::Spacing(); ImGui::TextColored(SOLUK, "Soğutucular (%d)", (int)kur.sogutucular.size()); ImGui::SameLine(); if (ImGui::SmallButton("➕##chill")) { kur.sogutucular.push_back({}); s.bayat = true; yenile = true; } struct ChillMalzeme { const char* ad; f64 htc; }; static const ChillMalzeme kChiller[] = { {"Bakır (5000)", 5000.0}, {"Çelik (3000)", 3000.0}, {"Grafit (4000)", 4000.0}, {"Dökme demir (1500)", 1500.0}, }; for (size_t i = 0; i < kur.sogutucular.size(); ++i) { auto& c = kur.sogutucular[i]; ImGui::PushID(1000 + (int)i); if (ImGui::Checkbox("##aktif", &c.aktif)) { s.bayat = true; yenile = true; } ImGui::SameLine(); ImGui::TextColored(ImVec4{0.45f, 0.62f, 1.0f, 1.0f}, "Soğutucu %d", (int)i + 1); char etChill[32]; std::snprintf(etChill, sizeof(etChill), "kur_chill_%d", (int)i); kurulumKonumSec(ds, sahne, s, etChill, "Soğutucu konumunu sahneden seç", [i](DokumSimulasyonu& sim, Vec3f p, Vec3f, bool) { if (i >= sim.kurulum.sogutucular.size()) return; sim.kurulum.sogutucular[i].konum = p; sim.bayat = true; }); ImGui::SameLine(); if (ImGui::SmallButton("🗑")) { const u32 eski = c.dugumKimlik; kur.sogutucular.erase(kur.sogutucular.begin() + i); if (eski && sahne.bul(eski)) sahne.sil(eski); s.bayat = true; ImGui::PopID(); break; } ImGui::Indent(18.0f); ImGui::SetNextItemWidth(120); const char* mAd = "özel"; for (auto& m : kChiller) if (std::abs(c.htc - m.htc) < 1.0) { mAd = m.ad; break; } if (ImGui::BeginCombo("##malz", mAd)) { for (auto& m : kChiller) if (ImGui::Selectable(m.ad, std::abs(c.htc - m.htc) < 1.0)) { c.htc = m.htc; s.bayat = true; } ImGui::EndCombo(); } ImGui::SameLine(); if (dragF64("HTC##h", c.htc, 50.0f, 100.0f, 20000.0f, "%.0f W/m²K", 90.0f)) s.bayat = true; ImGui::SameLine(); if (dragF64("r##r", c.yaricapMm, 0.5f, 2.0f, 200.0f, "%.0f mm", 60.0f)) { s.bayat = true; yenile = true; } ImGui::Unindent(18.0f); ImGui::PopID(); } // ---- Besleyiciler (riser) ---- ImGui::Spacing(); ImGui::TextColored(SOLUK, "Besleyiciler (%d)", (int)kur.besleyiciler.size()); ImGui::SameLine(); if (ImGui::SmallButton("➕##besle")) { dokum::Besleyici b; if (s.kafes.gecerliMi()) { const auto& sn = s.kafes.sinir(); b.konum = {(f32)(0.5 * (sn.asgari.x + sn.azami.x)), (f32)(0.5 * (sn.asgari.y + sn.azami.y)), (f32)sn.azami.z}; } kur.besleyiciler.push_back(b); s.bayat = s.dolumBayat = true; yenile = true; } if (s.besleyiciOneri.gecerli) { ImGui::SameLine(); if (ImGui::SmallButton("➕ öneriden")) { // Chvorinov önerisini listeye al dokum::Besleyici b; b.konum = {(f32)s.besleyiciOneri.konum.x, (f32)s.besleyiciOneri.konum.y, (f32)s.besleyiciOneri.konum.z}; b.capMm = s.besleyiciOneri.capMm; b.yukseklikMm = s.besleyiciOneri.yukseklikMm; kur.besleyiciler.push_back(b); ds.besleyiciHayaletKaldir(s.kimlik()); s.bayat = s.dolumBayat = true; yenile = true; } ImGui::SetItemTooltip("Chvorinov önerisini (hayalet) kuruluma gerçek besleyici olarak ekle"); } for (size_t i = 0; i < kur.besleyiciler.size(); ++i) { auto& b = kur.besleyiciler[i]; ImGui::PushID(2000 + (int)i); if (ImGui::Checkbox("##aktif", &b.aktif)) { s.bayat = s.dolumBayat = true; yenile = true; } ImGui::SameLine(); ImGui::TextColored(ImVec4{1.0f, 0.62f, 0.25f, 1.0f}, "Besleyici %d", (int)i + 1); char etBesle[32]; std::snprintf(etBesle, sizeof(etBesle), "kur_besle_%d", (int)i); kurulumKonumSec(ds, sahne, s, etBesle, "Besleyici taban merkezini sahneden seç", [i](DokumSimulasyonu& sim, Vec3f p, Vec3f, bool) { if (i >= sim.kurulum.besleyiciler.size()) return; sim.kurulum.besleyiciler[i].konum = p; sim.bayat = sim.dolumBayat = true; }); ImGui::SameLine(); if (ImGui::SmallButton("🗑")) { const u32 eski = b.dugumKimlik; kur.besleyiciler.erase(kur.besleyiciler.begin() + i); if (eski && sahne.bul(eski)) sahne.sil(eski); s.bayat = s.dolumBayat = true; ImGui::PopID(); break; } ImGui::Indent(18.0f); if (dragF64("⌀##c", b.capMm, 0.5f, 5.0f, 400.0f, "%.0f mm")) { s.bayat = s.dolumBayat = true; yenile = true; } ImGui::SameLine(); if (dragF64("boy##y", b.yukseklikMm, 0.5f, 5.0f, 600.0f, "%.0f mm")) { s.bayat = s.dolumBayat = true; yenile = true; } ImGui::SameLine(); if (ImGui::Checkbox("açık", &b.acik)) s.bayat = true; ImGui::SetItemTooltip("Açık (atmosferik) / kör besleyici"); // Besleme hacmi İKİNCİ şartı: V_r·η ≥ β(V_p+V_r) — η açık %14 (arastirma.md). if (s.parcaModul.gecerli) { const f64 vR = dokum::silindirHacim(b.capMm, b.yukseklikMm); const f64 eta = b.acik ? 0.14 : 0.10; if (!dokum::besleyiciHacimYeterli(s.parcaModul.hacim, vR, eta, s.alasim.cekmeOrani)) ImGui::TextColored(SARI, "⚠ Besleme hacmi yetersiz (V·η < β·V_toplam) — büyüt"); } ImGui::Unindent(18.0f); ImGui::PopID(); } // ---- Yolluklar (gating) ---- ImGui::Spacing(); ImGui::TextColored(SOLUK, "Yolluklar (%d)", (int)kur.yolluklar.size()); ImGui::SameLine(); if (ImGui::SmallButton("➕##yolluk")) { kur.yolluklar.push_back({}); s.dolumBayat = true; } static const char* kYollukTipAd[] = {"Üstten", "Alttan", "Yandan"}; for (size_t i = 0; i < kur.yolluklar.size(); ++i) { auto& y = kur.yolluklar[i]; ImGui::PushID(3000 + (int)i); if (ImGui::Checkbox("##aktif", &y.aktif)) { s.dolumBayat = true; yenile = true; } ImGui::SameLine(); ImGui::TextColored(SARI, "Yolluk %d", (int)i + 1); char etYolluk[32]; std::snprintf(etYolluk, sizeof(etYolluk), "kur_yolluk_%d", (int)i); kurulumKonumSec(ds, sahne, s, etYolluk, "Güzergâh noktalarını sırayla tıkla", [i](DokumSimulasyonu& sim, Vec3f p, Vec3f, bool) { if (i >= sim.kurulum.yolluklar.size()) return; sim.kurulum.yolluklar[i].guzergah.push_back(p); sim.dolumBayat = true; }, /*coklu=*/true); ImGui::SameLine(); if (ImGui::SmallButton("🗑")) { const u32 eski = y.dugumKimlik; kur.yolluklar.erase(kur.yolluklar.begin() + i); if (eski && sahne.bul(eski)) sahne.sil(eski); s.dolumBayat = true; ImGui::PopID(); break; } ImGui::Indent(18.0f); ImGui::SetNextItemWidth(90); int tipIdx = (int)y.tip; if (ImGui::Combo("##tip", &tipIdx, kYollukTipAd, 3)) { y.tip = (dokum::YollukTip)tipIdx; s.dolumBayat = true; } ImGui::SameLine(); if (dragF64("kesit##k", y.kesitMm2, 5.0f, 10.0f, 5000.0f, "%.0f mm²", 80.0f)) { s.dolumBayat = true; yenile = true; } ImGui::SameLine(); ImGui::TextColored(SOLUK, "%d nokta", (int)y.guzergah.size()); if (!y.guzergah.empty()) { ImGui::SameLine(); if (ImGui::SmallButton("✖")) { y.guzergah.clear(); s.dolumBayat = true; yenile = true; } ImGui::SetItemTooltip("Güzergâh noktalarını temizle"); } ImGui::Unindent(18.0f); ImGui::PopID(); } // ---- Derece (kum döküm flask görseli) ---- { auto& dr = kur.derece; ImGui::Spacing(); ImGui::TextColored(SOLUK, "Derece (kum kalıp)"); ImGui::SameLine(); if (ImGui::Checkbox("##dereceGoster", &dr.goster)) yenile = true; ImGui::SetItemTooltip( "Alt/üst derece çerçevesi + kum bloklarını sahnede çiz (görsel — fizik " "etkilenmez; kum bölgesi kafeste zaten kalıp payı olarak var).\n" "Yalnız kum kalıpta çizilir (kokilde derece yok)."); if (s.kalip.gecirgenlik <= 1e-13) ImGui::TextColored(SOLUK, "Seçili kalıp kokil — derece çizilmez"); else if (dr.goster) { ImGui::Indent(18.0f); if (dragF64("yan kum##dp", dr.kumPayMm, 1.0f, 5.0f, 500.0f, "%.0f mm")) yenile = true; ImGui::SameLine(); if (dragF64("üst##du", dr.ustPayMm, 1.0f, 5.0f, 500.0f, "%.0f mm")) yenile = true; ImGui::SameLine(); if (dragF64("alt##da", dr.altPayMm, 1.0f, 5.0f, 500.0f, "%.0f mm")) yenile = true; if (dragF64("duvar##dd", dr.duvarMm, 0.5f, 2.0f, 80.0f, "%.0f mm")) yenile = true; ImGui::SameLine(); if (ImGui::Checkbox("Tek parça##dt", &dr.tekParca)) yenile = true; ImGui::SetItemTooltip( "Tek çerçeveli derece: parça konur, kumlanır, ters çevrilip kalanı " "kumlanır (çevirmeli kalıplama). Kapalı = klasik alt+üst derece."); if (ImGui::Checkbox("Mala oto##dm", &dr.malaOto)) yenile = true; ImGui::SetItemTooltip("Mala (ayrım) düzlemi: oto = parça ortası"); // 🎯 Mala düzlemini ÇİZİMDEN seç: tıklanan yüzey noktasının DÜNYA z'si // (derece dünya-dik durur). Sahne* yakalanır — Belge ömrü boyunca geçerli. kurulumKonumSec(ds, sahne, s, "kur_mala", "Mala düzlemini sahneden seç (parça yüzeyine tıkla)", [sp = &sahne](DokumSimulasyonu& sim, Vec3f p, Vec3f, bool) { const Dugum* hd = hedefDugumBul(*sp, sim.hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; sim.kurulum.derece.malaOto = false; sim.kurulum.derece.malaZMm = don.noktaUygula({p.x, p.y, p.z}).z; }); if (!dr.malaOto) { ImGui::SameLine(); if (dragF64("z##dz", dr.malaZMm, 1.0f, -10000.0f, 10000.0f, "%.0f mm")) yenile = true; } // Kum ihtiyacı raporu (dökümhane hazırlık): derece iç hacmi − parça. // Dünya AABB (derece dünya-dik durur) → boyutlar + litre + kg. { const Aabbd psY = parcaHamSinir(sahne, s); const Dugum* hd = hedefDugumBul(sahne, s.hedefGovde); const Donusumd don = hd ? hd->donusum : Donusumd{}; Aabbd ps; if (psY.gecerliMi()) for (int c = 0; c < 8; ++c) ps.genislet(don.noktaUygula({(c & 1) ? psY.azami.x : psY.asgari.x, (c & 2) ? psY.azami.y : psY.asgari.y, (c & 4) ? psY.azami.z : psY.asgari.z})); if (ps.gecerliMi()) { const f64 dx = (ps.azami.x - ps.asgari.x) + 2.0 * dr.kumPayMm; const f64 dy = (ps.azami.y - ps.asgari.y) + 2.0 * dr.kumPayMm; const f64 dzA = dr.malaOto ? 0.5 * (ps.azami.z - ps.asgari.z) + dr.altPayMm : (dr.malaZMm - ps.asgari.z) + dr.altPayMm; const f64 dzU = dr.malaOto ? 0.5 * (ps.azami.z - ps.asgari.z) + dr.ustPayMm : (ps.azami.z - dr.malaZMm) + dr.ustPayMm; f64 metalM3 = 0; // parça+besleyici hacmi (önbellekli; kum bundan düşülür) if (s.dolum.hazir && s.dolum.toplamMetal > 0 && s.kafes.gecerliMi()) metalM3 = (f64)s.dolum.toplamMetal * s.kafes.hucreHacim(); else if (auto mh = g_metalHacim.find(s.kimlik()); mh != g_metalHacim.end()) metalM3 = mh->second.hacim; const f64 icM3 = dx * dy * (dzA + dzU) * 1e-9; // mm³ → m³ const f64 kumM3 = std::max(icM3 - metalM3, 0.0); ImGui::TextColored(SOLUK, "Derece: %.0f × %.0f mm · alt %.0f + üst %.0f mm", dx, dy, dzA, dzU); ImGui::TextColored(SOLUK, "Kum ihtiyacı: ≈ %.1f L (%.0f kg)", kumM3 * 1000.0, kumM3 * dokumKumYogunluk()); ImGui::SetItemTooltip( "Derece iç hacmi − parça (metal hücre) hacmi.\n" "Kum yoğunluğu: Ayarlar → Döküm → Kum yoğunluğu " "(yeşil kum ≈ 1550 kg/m³)"); } } ImGui::Unindent(18.0f); } } // Aktif seçim HUD bilgisi (kuruluma ait mod açıksa). if (ds.refSecim && ds.refSecim->aktif() && ds.refSecim->sahip() == s.kimlik()) ImGui::TextColored(YESIL, "● %s", ds.refSecim->hudMetin()); if (yenile) ds.kurulumDugumYenile(s.kimlik()); } void dokumPenceresiCiz(Cekirdek& cekirdek, Sahne& sahne, DokumSistemi& ds, DokumSimulasyonu& s) { (void)cekirdek; const ImVec4 SOLUK = ui::tema().metinSoluk; const ImVec4 CYAN = ui::tema().vurgu; const ImVec4 SARI = ui::tema().uyari; const ImVec4 YESIL = ui::tema().basari; const ImVec4 KIRMIZI = ui::tema().hata; bool& ustte = g_pin[s.kimlik()]; char baslik[160]; std::snprintf(baslik, sizeof(baslik), "🔥 %s%s###dokum_%u", s.ad().c_str(), s.bayat ? " *" : "", s.kimlik()); bool acik = true; bool silindi = false; // Sil butonu: kapsam sonu güvenli çıkış (s yok olur) { ui::Pencere pen{baslik, &acik, {.boyut = {360, 0}, .pinDurum = &ustte}}; if (pen) { const bool cozuluyor = ds.cozuluyorMu(s.kimlik()); const auto& r = s.sonuc; // sonuç: Katılaşma + Görselleştir sekmelerinde ortak if (ui::sekmeBaslat("##dokumSek")) { if (ui::sekme("Kurulum", "🏭")) { // Hedef gövde. ImGui::TextColored(SOLUK, "Hedef Gövde"); const Dugum* hd = s.hedefGovde ? sahne.bul(s.hedefGovde) : nullptr; ImGui::Text("%s", hd ? hd->ad.c_str() : "(ilk görünür gövde)"); // Malzeme + kalıp DB dropdown. ImGui::Separator(); ImGui::TextColored(SOLUK, "Malzeme"); ui::alan("Alaşım"); if (ImGui::BeginCombo("##alasim", s.alasim.ad.c_str())) { for (auto& g : kAlasimlar) { const bool sec = s.alasim.ad == g.yap().ad; if (ImGui::Selectable(g.ad, sec)) { s.alasim = g.yap(); s.bayat = true; } } ImGui::EndCombo(); } ui::alan("Kalıp"); if (ImGui::BeginCombo("##kalip", s.kalip.ad.c_str())) { for (auto& g : kKaliplar) { const bool sec = s.kalip.ad == g.yap().ad; if (ImGui::Selectable(g.ad, sec)) { s.kalip = g.yap(); s.bayat = true; ds.kurulumDugumYenile(s.kimlik()); // derece görseli kum↔kokil'e bağlı } } ImGui::EndCombo(); } // Girdiler. ImGui::Separator(); ImGui::TextColored(SOLUK, "Parametreler"); float dok = (float)s.ayar.dokumSicaklik; ui::alan("Döküm Sıcaklığı"); if (ImGui::DragFloat("##dokumSic", &dok, 1.0f, 300.0f, 1800.0f, "%.0f °C")) { s.ayar.dokumSicaklik = dok; s.bayat = true; } int coz = (int)s.cozunurluk; ui::alan("Çözünürlük (hücre)"); if (ImGui::DragInt("##cozunurluk", &coz, 1.0f, 8, 100000)) { s.cozunurluk = coz < 4 ? 4 : (u32)coz; s.bayat = true; // üst sınır YOK (uyarılar hücre-sayısı bazlı) } // Hücre-sayısı bazlı bilgi + kademeli uyarı (MAGMA dili: hücre adediyle konuş). // Tahmin: kafes kuruluysa gerçek boyuttan oranla; değilse küp varsayımı // (en uzun eksen res × 1.8 — %40 kalıp payı iki yönde, voxel_kafes kur pay=0.4). { f64 tahmin; if (s.kafes.gecerliMi()) { const f64 enUzun = (f64)std::max({s.kafes.nx(), s.kafes.ny(), s.kafes.nz()}); const f64 o = (f64)s.cozunurluk * 1.8 / std::max(enUzun, 1.0); tahmin = (f64)s.kafes.hucreSayi() * o * o * o; } else { const f64 nT = (f64)s.cozunurluk * 1.8; tahmin = nT * nT * nT; } char hb[32]; if (tahmin >= 1e6) std::snprintf(hb, sizeof(hb), "%.1fM", tahmin / 1e6); else std::snprintf(hb, sizeof(hb), "%.0fK", tahmin / 1e3); if (tahmin > 8e6) { char ub[80]; std::snprintf(ub, sizeof(ub), "≈%s hücre — çözüm ÇOK uzun sürer + yüksek RAM", hb); ui::bilgiSeridi(3, ub); } else if (tahmin > 2e6) { char ub[80]; std::snprintf(ub, sizeof(ub), "≈%s hücre — çözüm dakikalar sürebilir", hb); ui::bilgiSeridi(2, ub); } else { ImGui::TextColored(SOLUK, "≈%s hücre (kalıp payı dahil)", hb); } } // ---- HÜCRE BOYU (h): doğruluk tarafı -------------------------------------- // Panel bugüne dek yalnız hücre SAYISINI (maliyet) uyarıyordu: çözünürlük // yükselince "çok uzun sürer" der, DÜŞÜNCE hiçbir şey demezdi → kullanıcı // sistematik olarak aşağı itiliyordu. Fikri res=40'ta kaldı → h=18mm → // "voxeller kocaman ve taşıyor" + "kalıbın sadece burasını dolduruyor". // h = (en uzun kenar × 1.8) / çözünürlük — pay=0.4 gizli çarpanı dahil. { const Aabbd ps = parcaHamSinir(sahne, s); if (ps.gecerliMi()) { const f64 h = dokum::VoxelKafes::hCozunurlukten(ps, s.cozunurluk, 0.4); f64& cidar = g_minCidar[s.kimlik()]; // 0 = ölçülmedi, <0 = ölçülemedi if (cidar > 0) { // Cidar ÖLÇÜLDÜ → h'yi ona göre yargıla (döküm kuralı: h ≤ cidar/2). const u32 oneri = dokum::VoxelKafes::cozunurlukHedefH(ps, cidar * 0.5, 0.4); if (h > cidar * 0.5) { char ub[176]; std::snprintf(ub, sizeof(ub), "h=%.2f mm ama tipik en ince cidar %.2f mm (p10) → cidar kafeste " "kaybolur/şişer. Çözünürlük ≥ %u olmalı (h ≤ %.2f mm).", h, cidar, oneri, cidar * 0.5); ui::bilgiSeridi(3, ub); } else { ImGui::TextColored(SOLUK, "h ≈ %.2f mm · en ince cidar %.2f mm (cidar başına %.1f hücre)", h, cidar, h > 0 ? cidar / h : 0.0); } } else { ImGui::TextColored(SOLUK, "Hücre boyu h ≈ %.2f mm", h); } ImGui::SameLine(); // Ölçüm ışın tabanlı (analiz::kalinlikRapor) → parça büyüdükçe pahalı. // Her karede DEĞİL, yalnız istendiğinde; sonuç sim başına önbellekte. if (ImGui::SmallButton(cidar > 0 ? "Yeniden ölç" : "Cidarı ölç")) { cidar = -1.0; if (Dugum* d = hedefDugumBul(sahne, s.hedefGovde); d && d->brep) { f64 mn = 1e30, mx = 0; const auto nk = analiz::kalinlikRapor(*d->brep, 1e5, mn, mx); // HAM MİNİMUM tek pah/delik kenarına kilitleniyordu (ölçüldü: 1.21mm // mikro özellik → "çözünürlük ≥ 2169" absürt önerisi → uyarı işlevsiz). // Pratik cidar = pozitif örneklerin ALT YÜZDELİĞİ (vars. p10; 0 = min). std::vector kal; kal.reserve(nk.size()); for (const auto& p : nk) if (p.kalinlik > 0 && p.kalinlik < 1e29) kal.push_back(p.kalinlik); if (!kal.empty()) { const f64 yz = std::clamp(dokumCidarYuzdelik(), 0.0, 0.5); const size_t n = (size_t)(yz * (kal.size() - 1)); std::nth_element(kal.begin(), kal.begin() + n, kal.end()); cidar = kal[n]; } else if (mn > 0 && mn < 1e29) { cidar = mn; } } } ImGui::SetItemTooltip( "Işın tabanlı et kalınlığı ölçer (analiz katmanı) → gereken çözünürlüğü " "buradan türetir. Büyük parçada birkaç saniye sürebilir."); if (cidar > 0) { ImGui::SameLine(); const u32 oneri = dokum::VoxelKafes::cozunurlukHedefH(ps, cidar * 0.5, 0.4); char bt[48]; std::snprintf(bt, sizeof(bt), "Uygula: %u", oneri); if (ImGui::SmallButton(bt) && oneri > 0) { s.cozunurluk = oneri; s.bayat = true; } ImGui::SetItemTooltip("Çözünürlüğü h ≤ cidar/2 olacak şekilde ayarla " "(maliyet hücre sayısıyla KÜPSEL büyür)"); } else if (cidar < 0) { ImGui::TextColored(SOLUK, "(cidar ölçülemedi)"); } } } // KAFES PARÇAYI GÖRÜYOR MU? (önbellekli metal sayısı + kapsama oranı). // 0 hücre = dolacak hiçbir şey yok; oran ≫ 1 = kafes parçayı ŞİŞİRİYOR // (ince plakalar tek koca hücreye yapışır → "%100 doldu" ama görsel/hız // TEMSİLİ; ölçüldü: 15.2 L parça 220×66mm hücrede 62.7 L = ×4.1). Eski // eşik yalnız <%50 tarafını yakalıyordu; şişme sessizdi. if (s.kafes.gecerliMi() && s.kafes.hucreHacim() > 0) { const u32 metalHucre = (u32)std::llround(metalHacimM3Onbellekli(s) / s.kafes.hucreHacim()); const f64 kapsama = metalOnbellekAl(s).kapsama; // -1 = bilinmiyor const f64 sisme = dokumKapsamaSismeEsik(); if (metalHucre == 0) ui::bilgiSeridi(3, "Kafes parçada HİÇ metal hücre bulamadı — parça kafeste YOK. " "Çözünürlüğü artır (yukarıdaki öneri) ve geometrinin KAPALI " "katı olduğunu denetle. Dolum/katılaşma bu yüzden boş kalır."); else if (kapsama > sisme && sisme > 0) { char sb[224]; std::snprintf(sb, sizeof(sb), "Kafes parçayı ŞİŞİRİYOR: %u hücre = parça hacminin %.1f katı " "(h=%.1f mm). İnce plakalar tek hücreye yapışık — doluluk/hız/" "görsel TEMSİLİ. Çözünürlüğü artır (Cidarı ölç → Uygula).", metalHucre, kapsama, s.kafes.hMesh()); ui::bilgiSeridi(2, sb); } else if (kapsama >= 0) ImGui::TextColored(SOLUK, "Kafes: %u metal hücre (parça hacminin %%%.0f'i)", metalHucre, kapsama * 100.0); else ImGui::TextColored(SOLUK, "Kafes: %u metal hücre", metalHucre); } float ny = (float)s.niyamaEsik; ui::alan("Niyama Eşiği"); if (ImGui::DragFloat("##niyamaEsik", &ny, 0.05f, 0.1f, 10.0f, "%.2f")) { s.niyamaEsik = ny; s.bayat = true; } // ---- 🏭 Döküm Kurulumu (girişler/chill/besleyici/yolluk/yön) ---- kurulumBolumu(sahne, ds, s); ui::sekmeBitir(); } if (ui::sekme("Dolum", "🌊")) { // ---- Kalıp Doldurma (D3 — yerçekimi-tahrikli cephe ilerlemesi) ---- ImGui::TextColored(SOLUK, "Kalıp Doldurma"); // Denetim şeritleri BURADA DA (Fikri "hiç sıkıntı yok ama doldurmuyor" — // uyarılar yalnız Kurulum sekmesindeydi, bu sekmeden çalışan görmüyordu). kutleDenetimSeridi(ds, s); int aktifGiris = 0; for (const auto& g : s.kurulum.girisler) if (g.aktif) ++aktifGiris; // Dolum fiziği: hızlı varış-zamanı cephesi / gerçek SPH (Navier-Stokes). static const char* kFizikAd[] = {"Hızlı cephe (sn)", "Gerçek SPH (dk)"}; int fizik = s.sphKullan ? 1 : 0; ui::alan("Dolum fiziği"); if (ImGui::Combo("##dolumFizik", &fizik, kFizikAd, 2)) { s.sphKullan = fizik == 1; s.dolumBayat = true; } ImGui::SetItemTooltip( "Hızlı cephe: varış-zamanı modeli (saniyeler sürer).\n" "Gerçek SPH: serbest-yüzey akışkan simülasyonu — sıçrama/çalkantı/gerçek hız " "(dakikalar; kurulumda aktif giriş şart)"); auto& da = s.dolumAyar; if (s.sphKullan) { auto& sa = s.sphAyar; if (dragF64("Partikül yarıçapı", sa.partikulYaricapMm, 0.1f, 0.0f, 10.0f, "%.1f mm", 90.0f)) s.dolumBayat = true; ImGui::SetItemTooltip("0 = otomatik (hücre boyutunun 1/4'ü)"); // İSTENEN ≠ ALINAN: yarıçap h/2'ye kırpılır (binning partikülü hücreden // büyük göremez). 10mm'e kadar yazılabiliyordu ama tavan üstü HİÇBİR ŞEY // yapmıyordu — sessiz. ÖLÇÜLDÜ: h=2.25mm'de 4mm istendi → 1.125mm alındı // (partikül sayısı birebir aynı). Artık söylüyoruz. if (s.kafes.gecerliMi()) { const f64 etkinMm = dokum::sphEtkinYaricapM(s.kafes, sa) * 1000.0; if (dokum::sphYaricapKirpildiMi(s.kafes, sa)) { char ub[160]; std::snprintf(ub, sizeof(ub), "Yarıçap %.2f mm'ye kırpıldı (tavan = hücre/2 = %.2f mm). " "Daha büyük partikül için çözünürlüğü DÜŞÜR.", etkinMm, s.kafes.h() * 500.0); ui::bilgiSeridi(2, ub); } else { ImGui::TextColored(SOLUK, "Etkin yarıçap %.2f mm (tavan %.2f mm = hücre/2)", etkinMm, s.kafes.h() * 500.0); } ImGui::SetItemTooltip( "SPH maliyeti ≈ çözünürlük^3.6 (ÖLÇÜLDÜ: res ×4 → süre ×145).\n" "Yarıçap hücre/2'yi aşamaz → ince kafes = çok partikül = yavaş SPH.\n" "Cidarı görmek için yüksek çözünürlük + SPH akışkanı AYNI ANDA " "isteniyorsa süre saatlere çıkabilir."); } int ks = (int)sa.kareSayi; ui::alan("Kare sayısı"); if (ImGui::DragInt("##kareSayi", &ks, 1.0f, 8, 240)) { sa.kareSayi = (u32)std::clamp(ks, 8, 240); s.dolumBayat = true; } ImGui::SetItemTooltip("Animasyon kare serisi (scrubber çalkantıyı bu karelerden oynatır)"); if (dragF64("Viskozite", sa.viskozite, 0.001f, 0.0f, 0.5f, "%.3f", 90.0f)) s.dolumBayat = true; // --- GERÇEK SIVI: yüzey gerilimi + serbest yüzey tespiti ----------------- // Yuzey gerilimi KAPALI iken SPH akisi "sise benzer havuz" gibi davranir: // sivi duz duzarlara yayilir, sise kaldi gibi toplanir, damla/jet/menisk // olusmaz. Becker2007 kohozyon kuvveti acikken sivi gercek yuzey // gerilimine sahip olur → kurelesme, damla kopmasi, sise cekme dogru cikar. // 0 = kapali (eski davranis), >0 = N/m (Becker2007 katsayi). if (dragF64("Yüzey gerilimi (N/m)", sa.yuzeyGerilim, 0.01f, 0.0f, 5.0f, "%.2f", 90.0f)) s.dolumBayat = true; ImGui::SetItemTooltip( "Serbest yuzey kohozyon kuvveti (Becker 2007).\n" "0 = kapali (sivi duz duzarlara yayilir, sise benzer). >0 = gercek\n" "yuzey gerilimi: sise kalkar, jet kirilir, damla olusur."); // Otomatik mod: sigma'yi secili ALASIMIN erimis haldeki degerinden al // (AlSi7Mg 0.80, GG25 1.40, celik 1.25, bakir 1.30, pirinc 1.00, // cinko 0.50, Mg 0.65 N/m). Malzeme degisince akis da degisir. if (ui::anahtarSatiri("Yüzey gerilimi: malzemeden otomatik", &sa.yuzeyGerilimOtomatik)) s.dolumBayat = true; ImGui::SetItemTooltip( "Acik: sigma secili alasimin erimis haldeki degerinden gelir\n" "(malzeme degisince akis fiziksel olarak da degisir).\n" "Kapali: ustteki elle deger kullanilir (deney icin)."); if (dragF64("xSPH çalkantı (Monaghan)", sa.xsphKatsayi, 0.005f, 0.0f, 0.3f, "%.3f", 90.0f)) s.dolumBayat = true; ImGui::SetItemTooltip( "Hiz alanini komsu ortalamasiyla yumusatir (Monaghan 1992).\n" "Basinç tabanlı SPH sayisal girdabi SÜPÜRÜR → akis yapiskan/\n" "donuk gorunur. Bu deger onu geri besler: en etkili tek parametre.\n" "0 = kapali, tipik 0.02-0.15. Cok yuksek → yapay yapiskan akis."); if (dragF64("Girdap sıkıştırma (Spalding)", sa.girdapKatsayi, 0.005f, 0.0f, 0.2f, "%.3f", 90.0f)) s.dolumBayat = true; ImGui::SetItemTooltip( "Girdap siddetini geri besler → dar bogaz cikisinda gercek\n" "turbulansli karisim. 0 = kapali, tipik 0.01-0.05.\n" "Cok yuksek → yapay girdap puskurmesi."); // Serbest yuzey tespit esigi: oksit/bifilm (Campbell) ve YUZEY KOMSUSU // sayimi bu esikle bulunur. 20 = guvenli (h=4r, aralik 2r → dolu ic bolgede // ~33 komsu). Cok dusuk → tum hucmeler "serbest yuzey" sayilir, oksit // isaretleri yanlis olur; cok yuksek → hicbir yuzey bulunamaz. int kk = (int)sa.yuzeyKomsuEsik; ui::alan("Yüzey komşu eşiği"); if (ImGui::DragInt("##yuzKomsu", &kk, 1.0f, 8, 60)) { sa.yuzeyKomsuEsik = (u32)std::clamp(kk, 8, 60); s.dolumBayat = true; } ImGui::SetItemTooltip( "Serbest yuzey tespiti: bu esigin altinda komsu sayisina sahip\n" "hucmeler yuzeydir (oksit/bifilm izlemesi bunu kullanir).\n" "Dolu ic bolgede tipik deger ~33. 8-15: yuzey genis, 30+: sadece\n" "gercek sinir sayilir."); if (ui::anahtarSatiri("Deterministik (tek iş parçacığı)", &sa.deterministik)) s.dolumBayat = true; ImGui::SetItemTooltip("Bit-düzeyi tekrarlanabilir sonuç — daha yavaş"); if (aktifGiris == 0) ui::bilgiSeridi(2, "SPH için kurulumda en az bir aktif giriş gerekir"); // HAVUZ TAVANI: gereken partikül maksPartikul'ü aşarsa emitter havuz // dolunca SUSAR → dökülen hacim kaviteyi karşılayamaz, doluluk sessizce // yarım kalır ("büyük kalıp SPH'de dolmuyor"un ikinci yarısı). Plan çözücü // ile TEK KAYNAK (sphEmitPlani) — test: sph_emit_plani_tavan_kirpma. if (s.kafes.gecerliMi() && aktifGiris > 0) { auto saPlan = sa; saPlan.maksPartikul = dokumSphMaksPartikul(); // çözümde geçecek gerçek tavan const auto plan = dokum::sphEmitPlani(s.kafes, s.kurulum, s.alasim, saPlan); if (plan.kirpildi) { char pb[224]; std::snprintf(pb, sizeof(pb), "Partikül havuzu tavanda: gereken ~%.0fk, tavan %uk — " "dökülen metalin ancak %%%.0f'i simüle edilir, kalıp " "DOLAMAZ. Yarıçapı/hücreyi büyüt veya tavanı yükselt.", plan.gerekliPartikul / 1000.0, saPlan.maksPartikul / 1000, 100.0 * (f64)plan.havuz / plan.gerekliPartikul); ui::bilgiSeridi(3, pb); ImGui::SetItemTooltip( "Tavan: Ayarlar → Döküm → SPH partikül havuzu tavanı.\n" "Maliyet ≈ çözünürlük^3.6 — tavanı büyütmeden önce partikül " "yarıçapını/hücre boyunu büyütmek çok daha ucuz."); } } } else { float gh = (float)da.girisHiz; ui::alan("Giriş Hızı"); if (ImGui::DragFloat("##girisHiz", &gh, 0.01f, 0.05f, 5.0f, "%.2f m/s")) { da.girisHiz = gh; s.dolumBayat = true; } float yc = (float)da.yukariCeza; ui::alan("Yukarı Akış Cezası"); if (ImGui::DragFloat("##yukariCeza", &yc, 0.05f, 1.0f, 8.0f, "%.2f ×")) { da.yukariCeza = yc; s.dolumBayat = true; } float sg = (float)da.soguma; ui::alan("Cephe Soğuması"); if (ImGui::DragFloat("##cepheSoguma", &sg, 1.0f, 0.0f, 300.0f, "%.0f °C/s")) { da.soguma = sg; s.dolumBayat = true; } } // İngate (giriş) seçimi: kurulum girişleri öncelikli; yoksa oto/elle hücre. if (aktifGiris > 0) { ImGui::TextColored(SOLUK, "Giriş: kurulumdan (%d zamanlamalı giriş)", aktifGiris); } else { if (s.girisHucre == (u32)-1) ImGui::TextColored(SOLUK, "Giriş: en üst metal hücre (oto)"); else ImGui::TextColored(SOLUK, "Giriş: hücre #%u", s.girisHucre); ImGui::SameLine(); if (ImGui::SmallButton("Oto giriş")) { s.girisHucre = (u32)-1; s.dolumBayat = true; } } if (cozuluyor) { ImGui::TextColored(CYAN, "Çözülüyor..."); } else if (ImGui::Button(s.dolumBayat ? "🌊 Kalıbı Doldur" : "🌊 Yeniden Doldur", {-1, 0})) { ds.dolumBaslat(s.kimlik()); } // Dolum sonucu + animasyon scrubber. if (s.dolum.hazir) { const auto& df = s.dolum; ImGui::Text("Dolum süresi: "); ImGui::SameLine(); ImGui::TextColored(CYAN, "%.4f s", df.toplamSure); const f32 doluluk = df.toplamMetal ? 100.0f * (f32)df.dolanHucre / (f32)df.toplamMetal : 0.0f; ImGui::Text("Doluluk: %.1f%% (%u/%u hücre)", doluluk, df.dolanHucre, df.toplamMetal); // NEDEN özeti: yarım dolumun sebebi artık EKRANDA (Fikri "hiç sıkıntı yok // ama doldurmuyor" — sebep hiçbir yerde yazmıyordu). Öncelik: SPH süre // tavanı → kütle → hava hapsi → erişim/eşik. if (df.hazir && df.dolanHucre < df.toplamMetal && s.kafes.gecerliMi()) { f64 girisKg = 0; for (const auto& g : s.kurulum.girisler) if (g.aktif && g.kutleKg > 0 && g.sureS > 0) girisKg += g.kutleKg; const f64 kaviteKg = (f64)df.toplamMetal * s.kafes.hucreHacim() * s.alasim.yogunluk; char neden[224]; if (s.sphKullan && !s.sph.kareler.empty() && s.sph.maksSureS > 0 && s.sph.kareler.back().sure >= 0.95 * s.sph.maksSureS) std::snprintf(neden, sizeof(neden), "Neden: SPH sim süresi doldu (tavan %.1f s) — dökme " "süresini uzat veya SPH süre sınırını büyüt", s.sph.maksSureS); else if (girisKg > 0 && girisKg + 1e-9 < kaviteKg) std::snprintf(neden, sizeof(neden), "Neden: giriş kütlesi %.1f kg < kavite %.1f kg — " "yukarıdaki Uygula düğmesi", girisKg, kaviteKg); else if (df.hapsolanHucre > 0) std::snprintf(neden, sizeof(neden), "Neden: %u hücre hava hapsi — cebe vent koy", df.hapsolanHucre); else std::snprintf(neden, sizeof(neden), "Neden: erişim/eşik — dolmayan bölgeleri 🎯 ile incele"); ui::bilgiSeridi(2, neden); } // ⚠️ GÜVENLİK AĞI: "%100 doldu" ancak kafes parçayı GÖRÜYORSA anlamlıdır. // Merkez-sınıflaması cidarı h'den ince bölgeyi tamamen kaçırır → geriye tek // kalın göbek kalır, dolum onu doldurup "%100 (12/12 hücre) ✓ risk yok" // der. Sonuç anlamsız ama YEŞİL görünüyordu (Fikri 07-16: "kalıbın sadece // burasını dolduruyor"). Oran bu yalanı yakalar; eşik AyarSistemi'nden. const f64 kapsama = dokum::kafesParcaKapsama(s.kafes); if (kapsama >= 0.0 && kapsama < (f64)dokumKapsamaEsik()) { char uy[224]; std::snprintf(uy, sizeof(uy), "Kafes parçanın yalnız %%%.1f'ini görüyor (hücre %.2f mm) — ince " "cidarlar kafeste YOK. Sonuç anlamsız: Çözünürlüğü artır.", kapsama * 100.0, s.kafes.hMesh()); ui::bilgiSeridi(3, uy); } // SPH gerçek hız istatistiği (Campbell denetimi — eşik alaşımdan). if (s.sph.hazir && s.sph.maksHiz > 0) { const f64 vKr = dokum::kritikGirisHiz(s.alasim); ImGui::Text("Azami akış hızı (SPH): "); ImGui::SameLine(); ImGui::TextColored(s.sph.maksHiz > vKr ? SARI : YESIL, "%.2f m/s", s.sph.maksHiz); ImGui::SetItemTooltip("%u partikül · %.2f kg · çözüm %.1f s", s.sph.partikulSayi, s.sph.partikulKutleKg, s.sph.cozumSuresiSn); if (s.sph.maksHiz > vKr) ImGui::TextColored(SARI, "⚠ Campbell kritik hız aşımı (eşik %.1f m/s) — oksit/hava riski", vKr); } if (df.misrun) ImGui::TextColored(KIRMIZI, "⚠ Eksik dolum (misrun): metal yetmedi veya erişemedi " "(Kurulum → Girişler kütlesini denetle)"); // Dolmayan bölge listesi (NEREsi dolmadı) — misrun/hapis teşhisi, tıkla-işaretle. if (df.dolanHucre < df.toplamMetal && s.kafes.gecerliMi()) { auto& db = g_dolmayanBolge[s.kimlik()]; if (db.dolan != df.dolanHucre || db.toplam != df.toplamMetal) { db.dolan = df.dolanHucre; db.toplam = df.toplamMetal; db.bolgeler = dokum::dolmayanBolgeler(s.kafes, df); } const int gosterN = (int)std::min(db.bolgeler.size(), 5); for (int bi = 0; bi < gosterN; ++bi) { const auto& b = db.bolgeler[bi]; ImGui::PushID(7000 + bi); ImGui::TextColored(b.havaHapsi ? SARI : KIRMIZI, " %s %.1f cm³ (%u hücre)", b.havaHapsi ? "🫧 hava hapsi — vent koy" : "⛔ dolmadı — kütle/erişim", b.hacimMm3 * 1e-3, b.hucreSayi); ImGui::SameLine(); if (ImGui::SmallButton("🎯##dolmayan")) ds.dolmayanBolgeIsaretle(s.kimlik(), b); ImGui::SetItemTooltip("Bölgeyi sahnede işaretle + kamerayı uçur"); ImGui::PopID(); } if ((int)db.bolgeler.size() > gosterN) ImGui::TextColored(SOLUK, " … +%d bölge daha", (int)db.bolgeler.size() - gosterN); } // Kusur bayrakları. if (df.coldShutSayi > 0) ImGui::TextColored(KIRMIZI, "⚠ Soğuk-birleşme riski: %u hücre", df.coldShutSayi); else ImGui::TextColored(YESIL, "✓ Soğuk-birleşme riski yok"); if (df.havaKapaniSayi > 0) ImGui::TextColored(SARI, "⚠ Hava kapanı riski: %u hücre", df.havaKapaniSayi); else ImGui::TextColored(YESIL, "✓ Hava kapanı riski yok"); // ---- Hava hapsi analizi (son hava / kapalı cep / entrainment) ---- if (const dokum::HavaAnalizSonuc* hv = ds.havaAl(s.kimlik())) { ImGui::TextColored(SOLUK, "Hava Analizi"); ImGui::Text("Kapalı cep: %u Riskli hücre: %u Azami cephe: %.2f m/s", hv->kapaliCepSayi, hv->riskliHucre, hv->azamiCepheHiz); if (hv->azamiCepheHiz > 0.5) ui::bilgiSeridi(2, "Campbell kritik hız aşımı — hava/oksit riski"); if (!hv->ventOnerileri.empty()) { bool vent = ds.ventVarMi(s.kimlik()); if (ui::anahtarSatiri("Havalandırma önerilerini göster", &vent)) { if (vent) ds.ventGoster(s.kimlik()); else ds.ventKaldir(s.kimlik()); } ImGui::SetItemTooltip("Önerilen vent noktaları (%d) sahnede saydam küre olarak", (int)hv->ventOnerileri.size()); } } // ---- Dolum animasyonu: zaman şeridi + kritik an rozetleri ---- ImGui::TextColored(SOLUK, "Dolum Animasyonu"); ui::SeritDurum& sd = g_dolumSerit[s.kimlik()]; // Varsayılan görünüm: Pürüzsüz sıvı yüzeyi (voxel küpler seçenek olarak kalır). auto modIt = g_dolumMod.find(s.kimlik()); if (modIt == g_dolumMod.end()) modIt = g_dolumMod.emplace(s.kimlik(), (int)DolumGorunum::Sivi).first; int& mod = modIt->second; // DolumGorunum int& renkKay = g_dolumRenkKay[s.kimlik()]; // DolumRenk auto dolumKur = [&](f64 an) { ds.dolumGorsellestir(s.kimlik(), an, (DolumGorunum)mod, (DolumRenk)renkKay); }; static const char* kModAd[] = {"Voxel küpler (hızlı)", "Pürüzsüz sıvı yüzeyi", "Malzeme İzi (x-ray)", "Partikül bulutu (çalkantı)"}; // Partikül bulutu yalnız SPH karesi partikül taşıyorsa anlamlı (hızlı cephe // kipinde kare serisi yok; kayıt kapalıysa konumlar saklanmamış). const bool partikulVar = !s.sph.kareler.empty() && !s.sph.kareler.front().partikul.empty(); const int modAdet = partikulVar ? 4 : 3; if (!partikulVar && mod == (int)DolumGorunum::Partikul) mod = (int)DolumGorunum::Sivi; ImGui::SetNextItemWidth(158); if (ImGui::Combo("##dolumMod", &mod, kModAd, modAdet)) { // Akıllı varsayılan: izde varış zamanı (Material Trace), partikülde hız // (çalkantının kendisi), yüzeyde sıcaklık. renkKay = mod == (int)DolumGorunum::MalzemeIzi ? (int)DolumRenk::VarisZamani : (int)DolumRenk::Sicaklik; if (mod == (int)DolumGorunum::Partikul) renkKay = (int)DolumRenk::VarisZamani; dolumKur(sd.zaman * df.toplamSure); // x-ray'i dolumGorsellestir mod'a göre kurar } ImGui::SetItemTooltip( partikulVar ? "Pürüzsüz modlarda MAGMA görünümü: parça x-ray saydam, dolan metal OPAK " "yüzey.\nPartikül bulutu: SPH partikülünün KENDİSİ — jet/sıçrama/çalkantı " "hücreye ezilmeden görünür." : "Pürüzsüz modlarda MAGMA görünümü: parça x-ray saydam, dolan metal OPAK " "yüzey.\n(Partikül bulutu için \"Gerçek SPH\" ile çöz — hızlı cephe kipi " "partikül üretmez.)"); if (mod != (int)DolumGorunum::Voxel) { ImGui::SameLine(); // Partikülde renk kaynağı hız/sıcaklık (varış zamanı partikülde tanımsız — // "Varış zamanı" yuvası hız anlamına gelir; çalkantıyı gösteren alan odur). // SIVI YUZEYI: ucuncu yuva AKIS HIZI [m/s] — doluluk alani tek degerli // oldugu icin hiz baska hicbir yolla gorunmez; bu yuva jet calkantisi / // mandala akis / durgun yuzey kontrastini ORTAYA CIKARIR (gercek sivi). static const char* kRenkAd[] = {"Sıcaklık °C", "Varış zamanı s", "Akış hızı m/s"}; static const char* kRenkAdP[] = {"Sıcaklık °C", "Hız m/s", "Hız m/s"}; ImGui::SetNextItemWidth(118); if (ImGui::Combo("##dolumRenk", &renkKay, mod == (int)DolumGorunum::Partikul ? kRenkAdP : kRenkAd, mod == (int)DolumGorunum::Partikul ? 2 : 3)) dolumKur(sd.zaman * df.toplamSure); ImGui::SetItemTooltip( "Yüzey renk kaynağı.\n" "Akış hızı: hücre-başı ortalama |v| (m/s) — yüzey hız≈0, " "mandala hızlı; SPH karelerinden enterpolasyonla gelir."); } // Kritik an rozetleri (önbellekli hesap; tık → o ana atla). const KritikAnlar& ka = kritikAl(ds, s); RozetListe rl; rl.ayirt(s.kurulum.girisler.size() + 8); char rm[96]; int gN = 0; for (const auto& g : s.kurulum.girisler) { ++gN; if (!g.aktif || g.gecikmeS <= 0) continue; std::snprintf(rm, sizeof(rm), "Giriş %d açılır — %.1f s", gN, g.gecikmeS); rl.ekle(g.gecikmeS, df.toplamSure, rm, CYAN); } static const char* kDolulukAd[4] = {"%25 doluluk", "%50 doluluk", "%75 doluluk", "%100 doluluk"}; for (int q = 0; q < 4; ++q) if (ka.doluluk[q] >= 0) { std::snprintf(rm, sizeof(rm), "%s — %.2f s", kDolulukAd[q], ka.doluluk[q]); rl.ekle(ka.doluluk[q], df.toplamSure, rm); } if (ka.sonHava >= 0) { std::snprintf(rm, sizeof(rm), "Son hava bölgesi — %.2f s", ka.sonHava); rl.ekle(ka.sonHava, df.toplamSure, rm, SARI); } if (ka.katilasmaBas >= 0 && ka.katilasmaBas <= df.toplamSure) { std::snprintf(rm, sizeof(rm), "Katılaşma başlar — %.2f s", ka.katilasmaBas); rl.ekle(ka.katilasmaBas, df.toplamSure, rm, KIRMIZI); } seritCizVeKur("##dolumSerit", sd, df.toplamSure, rl, g_sonKurma[s.kimlik()], [&](f64 an, bool) { dolumKur(an); }); dolumHudCiz(s, sd); if (ImGui::SmallButton("Tümü")) { sd.zaman = 1.0f; sd.oynat = false; dolumKur(-1.0); } ImGui::SetItemTooltip("Tam dolmuş son durumu göster"); ImGui::SameLine(); if (ImGui::SmallButton("Dolum görselini gizle")) { ds.dolumKaldir(s.kimlik()); ds.xrayAyarla(s.kimlik(), false); sd.oynat = false; } if (s.dolumDugum) { const bool sicaklik = renkKay == (int)DolumRenk::Sicaklik; // Voxel = varış zamanı [s]; partikülde sıcaklık-dışı alan HIZ [m/s]. const char* birim = "s"; if (mod == (int)DolumGorunum::Partikul) birim = sicaklik ? "°C" : "m/s"; else if (mod != (int)DolumGorunum::Voxel) birim = sicaklik ? "°C" : "s"; voxelLejant(s.gorselAyar.rampa, s.dolumMin, s.dolumMax, birim, /*kilitli=*/true, s.gorselAyar.bandli, s.gorselAyar.bandSayi); } } ui::sekmeBitir(); } if (ui::sekme("Katılaşma", "▶")) { // Çözüm butonu / ilerleme (katılaşma). ImGui::TextColored(SOLUK, "Katılaşma Çözümü"); if (cozuluyor) { ImGui::TextColored(CYAN, "Çözülüyor..."); for (const auto& g : cekirdek.gorevler().anlik()) { ImGui::ProgressBar(g.oran, {-1, 0}, g.mesaj.c_str()); break; } } else if (ImGui::Button(s.bayat ? "⟳ Çözümü Çalıştır" : "⚙ Yeniden Çöz", {-1, 0})) { ds.cozumBaslat(s.kimlik()); } // Sonuç. ImGui::Separator(); ImGui::TextColored(SOLUK, "Sonuç"); if (!r.hazir) { ImGui::TextColored(SOLUK, "henüz çözülmedi"); } else { ImGui::Text("Toplam katılaşma: "); ImGui::SameLine(); ImGui::TextColored(CYAN, "%.2f s", r.toplamSure); ImGui::Text("Min Niyama: "); ImGui::SameLine(); const bool risk = r.niyamaKritik > 0; ImGui::TextColored(risk ? KIRMIZI : YESIL, "%.3f (°C·s)^½/mm", r.niyamaMin); // Porozite risk bayrağı. if (risk) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4{0.32f, 0.10f, 0.10f, 1.0f}); ImGui::BeginChild("risk", {-1, 24}, false); ImGui::TextColored(KIRMIZI, "⚠ Porozite riski: %u hücre eşik altında", r.niyamaKritik); ImGui::EndChild(); ImGui::PopStyleColor(); } else { ImGui::TextColored(YESIL, "✓ Porozite riski düşük"); } // Hot-spot. ImGui::Text("Hot-spot süresi: %.2f s (en son katılaşan bölge)", r.hotSpotSure); if (ImGui::Button("🎯 Hot-spot'u 3B'de İşaretle", {-1, 0})) ds.hotSpotIsaretle(s.kimlik()); // ---- Besleme / Methoding (Chvorinov besleyici öneri) ---- ImGui::Separator(); ImGui::TextColored(SOLUK, "Besleme / Methoding"); // Parça modülü (henüz hesaplanmadıysa anlık hesapla — panel bilgisi). if (!s.parcaModul.gecerli && s.kafes.gecerliMi()) s.parcaModul = dokum::parcaModulu(s.kafes); if (s.parcaModul.gecerli) { ImGui::Text("Parça modülü M = "); ImGui::SameLine(); ImGui::TextColored(CYAN, "%.2f mm", s.parcaModul.modul); ImGui::TextColored(SOLUK, "V=%.0f mm³ A=%.0f mm² T≈%.1f mm", s.parcaModul.hacim, s.parcaModul.yuzeyAlan, s.parcaModul.kesitKalinlik); } else { ImGui::TextColored(SOLUK, "Parça modülü hesaplanamadı (kafes yok)"); } // Öneri parametreleri. float carp = (float)s.besleyiciCarpan; ui::alan("Modül çarpanı"); if (ImGui::DragFloat("##modulCarpan", &carp, 0.01f, 1.05f, 2.0f, "%.2f ×M")) { s.besleyiciCarpan = carp; } float hyk = (float)s.besleyiciHYuk; ui::alan("Yükseklik/Çap"); if (ImGui::DragFloat("##yukseklikCap", &hyk, 0.05f, 0.8f, 3.0f, "%.2f")) { s.besleyiciHYuk = hyk; } if (ImGui::Button("🧊 Besleyici Öner (hayalet)", {-1, 0})) ds.besleyiciOner(s.kimlik()); // Öneri sonucu. const auto& bo = s.besleyiciOneri; if (bo.gecerli) { ImGui::Text("Önerilen: "); ImGui::SameLine(); ImGui::TextColored(CYAN, "⌀%.0f × %.0f mm", bo.capMm, bo.yukseklikMm); ImGui::Text("Besleyici modülü M_r = "); ImGui::SameLine(); ImGui::TextColored(bo.modulYeterli ? YESIL : KIRMIZI, "%.2f mm", bo.modul); ImGui::TextColored(SOLUK, "(gerekli ≥ %.2f mm = %.2f×M)", bo.hedefModul, s.besleyiciCarpan); ImGui::TextColored(bo.modulYeterli ? YESIL : KIRMIZI, "%s", bo.modulYeterli ? "✓ Modül yeterli (çekme riski düşük)" : "⚠ Modül yetersiz — çapı büyüt"); ImGui::TextColored(SOLUK, "Besleme mesafesi (4.5T): %.0f mm", bo.beslemeMesafe); if (s.besleyiciHayaletDugum && ImGui::Button("✔ Besleyiciyi Kabul Et (ağaca ekle)", {-1, 0})) ds.besleyiciKabul(s.kimlik()); } } // Sonuç else bloğu (skaler + besleme) kapanışı ui::sekmeBitir(); } if (ui::sekme("Görselleştir", "📊")) { if (!r.hazir) { ImGui::TextColored(SOLUK, "Önce katılaşmayı çöz (Katılaşma sekmesi)."); } else { // ---- 3B renk-rampalı hacim ısı-haritası (MAGMA tarzı voxel görselleştirme) ---- ImGui::TextColored(SOLUK, "3B Hacim Görselleştirme"); auto& ga = s.gorselAyar; bool degisti = false; // yalnız değişince mesh yeniden kurulur (kare-başı değil) // Alan seçimi: özel modlar (hava/porozite) + JENERİK türetilmiş katmanlar // (sonuc_katmanlari listesinden otomatik dolar; ≈ = yaklaşık türetim). std::string& katmanAd = g_katmanSecim[s.kimlik()]; // boş = özel mod const dokum::SonucKatmanlari* katmanlar = ds.katmanlarAl(s.kimlik()); const dokum::KatmanBilgi* seciliKatman = (katmanlar && !katmanAd.empty()) ? katmanlar->bul(katmanAd) : nullptr; if (!katmanAd.empty() && !seciliKatman) katmanAd.clear(); // bayat seçim const char* onizleme = seciliKatman ? seciliKatman->ad.c_str() : alanAd(ga.alan); ui::alan("Görselleştir"); if (ImGui::BeginCombo("##gorselAlan", onizleme)) { for (auto& g : kAlanlar) { if (havaAlaniMi(g.alan) && !s.dolum.hazir) continue; if (ImGui::Selectable(g.ad, !seciliKatman && ga.alan == g.alan)) { ga.alan = g.alan; katmanAd.clear(); degisti = true; } } if (katmanlar && !katmanlar->katmanlar.empty()) { ImGui::Separator(); for (const auto& kb : katmanlar->katmanlar) { char et[96]; std::snprintf(et, sizeof(et), "%s%s", kb.ad.c_str(), kb.yaklasik ? " ≈" : ""); if (ImGui::Selectable(et, seciliKatman == &kb)) { katmanAd = kb.ad; degisti = true; } if (ImGui::IsItemHovered() && !kb.aciklama.empty()) ImGui::SetTooltip("%s%s", kb.aciklama.c_str(), kb.yaklasik ? "\n≈ yaklaşık (vekil türetim)" : ""); } } ImGui::EndCombo(); } seciliKatman = (katmanlar && !katmanAd.empty()) ? katmanlar->bul(katmanAd) : nullptr; const bool vizAcik = seciliKatman || ga.alan != dokum::VoxelAlan::Kapali; const bool porAlan = !seciliKatman && ga.alan == dokum::VoxelAlan::Porozite; // Porozite % görünüm kipi: varsayılan MAGMA baloncuk izo-yüzeyi; küp seçenek. if (porAlan) { static const char* kPorModAd[] = {"Baloncuk izo-yüzey", "Isı haritası (küp)"}; int pm = g_porKup[s.kimlik()] ? 1 : 0; ui::alan("Görünüm"); if (ImGui::Combo("##porMod", &pm, kPorModAd, 2)) { g_porKup[s.kimlik()] = pm == 1; degisti = true; } ImGui::SetItemTooltip("Baloncuk: porozite > eşik blob yüzeyi + parça x-ray (MAGMA).\n" "Isı haritası: hücre küpleri renk rampasıyla."); } const bool porBlob = porAlan && !g_porKup[s.kimlik()]; if (vizAcik && !porBlob) { // Rampa tipi (İnferno varsayılan: algısal düzgün "akkor" palet). if (ImGui::BeginCombo("Renk Rampası", rampaAd(ga.rampa))) { for (dokum::VoxelRampa rp : {dokum::VoxelRampa::Inferno, dokum::VoxelRampa::Turbo, dokum::VoxelRampa::Jet}) if (ImGui::Selectable(rampaAd(rp), ga.rampa == rp)) { ga.rampa = rp; degisti = true; } ImGui::EndCombo(); } ImGui::SetItemTooltip( "İnferno: algısal-düzgün, renk-körü dostu (önerilen). Turbo/Jet klasik."); // MAGMA kalıbı kademeli lejant: 15 ayrık renk bandı (sürekli ⇄ banded). if (ImGui::Checkbox("Kademeli renk (15 band)", &ga.bandli)) degisti = true; ImGui::SetItemTooltip("MAGMA tarzı ayrık renk bandları — değer okuma kolaylığı"); // Yalnız sınır (iç boşalt — hafif). if (ImGui::Checkbox("Yalnız sınır hücreler", &ga.yalnizSinir)) degisti = true; // Küp boyutu: 1.0 = aralıksız blok (yüz-ayıklamalı, MAGMA görünümü); <1.0 = // küpler küçülür, aralarında boşluk (kafes/nokta estetiği). ui::alan("Küp boyutu"); if (ImGui::SliderFloat("##kupBoyut", &ga.kupOlcek, 0.2f, 1.0f, "%.2f")) degisti = true; ImGui::SetItemTooltip("1.00 = aralıksız blok yüzey. <1.00 = küpler küçülür,\n" "aralarında boşluk kalır (kafes görünümü)."); // Min/max (oto veya elle). if (ImGui::Checkbox("Oto min/max", &ga.otoMinMax)) degisti = true; if (!ga.otoMinMax) { float mn = ga.elleMin, mx = ga.elleMax; if (ImGui::DragFloat("Min", &mn, 0.5f)) { ga.elleMin = mn; degisti = true; } if (ImGui::DragFloat("Max", &mx, 0.5f)) { ga.elleMax = mx; degisti = true; } } } // Aktif eşik (değer-eşiği taraması açıksa şerit konumundan) — refresh'lerde // korunur; kapalıyken -0.5 = tüm geçerli hücreler. bool& esikAc = g_esikAc[s.kimlik()]; ui::SeritDurum& esd = g_esikSerit[s.kimlik()]; auto esikDeger = [&]() -> f32 { if (!esikAc || !seciliKatman || esd.zaman <= 0.0f) return -0.5f; return seciliKatman->min + esd.zaman * (seciliKatman->maks - seciliKatman->min); }; auto vizKur = [&] { if (seciliKatman) ds.katmanGorsellestir(s.kimlik(), katmanAd.c_str(), esikDeger()); else ds.voxelGorsellestir(s.kimlik()); }; // ---- Kesit süpürme (voxel-dilim; MAGMA iç yapı turu — video v5/v6) ---- if (vizAcik && !porBlob) { static const char* kEksenAd[] = {"Kapalı", "X", "Y", "Z"}; int ke = ga.kesitEksen + 1; ui::alan("Kesit"); if (ImGui::Combo("##kesitEksen", &ke, kEksenAd, 4)) { ga.kesitEksen = ke - 1; if (ga.kesitEksen < 0) { ga.kesitOran = 1.0f; g_kesitSerit[s.kimlik()].oynat = false; } else if (g_kesitSerit[s.kimlik()].zaman <= 0.0f) { g_kesitSerit[s.kimlik()].zaman = 1.0f; // ilk açılış: tam gövde ga.kesitOran = 1.0f; } degisti = true; } ImGui::SetItemTooltip("Eksen boyunca animasyonlu düzlem süpürmesi (iç yapı turu)"); if (ga.kesitEksen >= 0) { ui::SeritDurum& kd = g_kesitSerit[s.kimlik()]; RozetListe bosR; seritCizVeKur("##kesitSerit", kd, 4.0f, bosR, g_sonKurma[s.kimlik()], [&](f64, bool) { ga.kesitOran = kd.zaman; vizKur(); }); } } // ---- Değer-eşiği taraması (MAGMA "soyma" — video v1 Feedmod kalıbı) ---- if (seciliKatman) { if (ImGui::Checkbox("Eşik taraması (soyma)", &esikAc)) { if (!esikAc) esd.oynat = false; vizKur(); } ImGui::SetItemTooltip( "Alt eşiği zamanla süpür: düşük değerli hücreler kademeli gizlenir —\n" "en kritik (en yüksek değerli) bölge en son kalır (MAGMA sunum kalıbı)"); if (esikAc) { RozetListe bosR; seritCizVeKur("##esikSerit", esd, 4.0f, bosR, g_sonKurma[s.kimlik()], [&](f64, bool) { vizKur(); }); ImGui::TextColored(SOLUK, "eşik: %.4g %s (üstü görünür)", esikDeger(), seciliKatman->birim.c_str()); } } else if (esikAc) { esikAc = false; // katman seçimi kalktı → tarama kapanır esd.oynat = false; } // Yalnız kullanıcı bir kontrolü DEĞİŞTİRİNCE yeniden kur (kare-başı değil; // boş sonuçta da tekrar denemez → kare-başı yeniden kurma yok). if (degisti) { bool& comboBlob = g_porBlobCombo[s.kimlik()]; if (porBlob) { ds.voxelKaldir(s.kimlik()); ds.poroziteBaloncukGoster(s.kimlik(), true, porEsikAl(s.kimlik())); comboBlob = true; } else { if (comboBlob) { // combo başka alana geçti → combo-blob'u kaldır ds.poroziteBaloncukGoster(s.kimlik(), false); comboBlob = false; } vizKur(); } } if (porBlob && ds.poroziteBaloncukVarMi(s.kimlik())) { ImGui::TextColored(SOLUK, "🫧 baloncuk izo-yüzey aktif (eşik %.2f%%)", porEsikAl(s.kimlik())); ImGui::SameLine(); if (ImGui::SmallButton("Oto eşik")) { g_porOtoYapildi.erase(s.kimlik()); // yeniden türet (elle kaydı sıfırla) ds.poroziteBaloncukGoster(s.kimlik(), true, porEsikAl(s.kimlik())); } ImGui::SetItemTooltip( "Eşiği veriden türet: dolu hücrelerin üst yüzdeliği (Ayarlar → Döküm). " "Sabit eşik parçayı kaplayabiliyordu (her yer kırmızı)."); } // Lejant (renk-bar + min/max) — viz aktifse (porozite skalası global 0-100). // Katman kipinde MAGMA "Boş" girdisi (geçersiz hücre skaladan ayrı). if (vizAcik && s.voxelDugum) { voxelLejant(ga.rampa, s.gorselMin, s.gorselMax, seciliKatman ? seciliKatman->birim.c_str() : alanBirim(ga.alan), !seciliKatman && ga.alan == dokum::VoxelAlan::Porozite, ga.bandli, ga.bandSayi, /*bosGoster=*/seciliKatman != nullptr); if (seciliKatman && seciliKatman->yaklasik) { ImGui::TextColored(SOLUK, "≈ yaklaşık türetim"); ImGui::SetItemTooltip("%s", seciliKatman->aciklama.c_str()); } ImGui::TextColored(SOLUK, "%u hücre boyandı", s.gorselHucre); // Saydamlık: voxel alanı yarı saydam → içerideki hot-spot/gövde görünür. if (Dugum* vd = sahne.bul(s.voxelDugum)) { f32 sy = vd->saydamlik; ImGui::SetNextItemWidth(-1); if (ImGui::SliderFloat("##voxSaydam", &sy, 0.1f, 1.0f, "Saydamlık %.2f")) vd->saydamlik = sy; } } // ---- Katılaşma cephesi (pürüzsüz izo-yüzey / yalnız-sıvı; ortak şerit) ---- bool& cephe = g_cepheAc[s.kimlik()]; ui::SeritDurum& cd = g_cepheSerit[s.kimlik()]; int& cepheMod = g_cepheMod[s.kimlik()]; // 0 = izo-yüzey, 1 = yalnız sıvı auto cepheKur = [&](f64 an) { // İzo=0 boş yüzey üretir → alt sınır %2 (cephe henüz doğarken de görünür). ds.cepheGorsellestir(s.kimlik(), std::max(an, 0.02 * r.toplamSure), cepheMod == 1); }; if (ImGui::Checkbox("Katılaşma cephesi (pürüzsüz)", &cephe)) { if (cephe) { if (cd.zaman <= 0.0f) cd.zaman = 0.5f; cepheKur(cd.zaman * r.toplamSure); } else { cd.oynat = false; ds.cepheKaldir(s.kimlik()); } } ImGui::SetItemTooltip("Katı/sıvı arayüzünü seçilen anda izo-yüzey olarak göster"); if (cephe) { // Görünüm kipi (MAGMA Fraction Liquid — video v7): katıyı gizle, yalnız // sıvı bölgeler (izole adacıklar = çekinti adayı) görünür kalsın. static const char* kCepheModAd[] = {"İzo-yüzey (cephe)", "Yalnız sıvı (adacıklar)"}; ImGui::SetNextItemWidth(170); if (ImGui::Combo("##cepheMod", &cepheMod, kCepheModAd, 2)) cepheKur(cd.zaman * r.toplamSure); ImGui::SetItemTooltip( "Yalnız sıvı: katılaşan hücreler gizli — sona doğru beliren izole sıvı\n" "adacıklar besleme yolundan kopmuş çekinti/porozite adaylarıdır"); const KritikAnlar& kc = kritikAl(ds, s); RozetListe rc; rc.ayirt(6); char cm[96]; if (kc.katilasmaBas >= 0) { std::snprintf(cm, sizeof(cm), "Katılaşma başlar — %.2f s", kc.katilasmaBas); rc.ekle(kc.katilasmaBas, r.toplamSure, cm, CYAN); } static const char* kKatAd[4] = {"%25 katılaşma", "%50 katılaşma", "%75 katılaşma", "Tam katılaşma"}; for (int q = 0; q < 4; ++q) if (kc.katYuzde[q] >= 0) { std::snprintf(cm, sizeof(cm), "%s — %.2f s", kKatAd[q], kc.katYuzde[q]); rc.ekle(kc.katYuzde[q], r.toplamSure, cm, q == 3 ? YESIL : ImVec4{0, 0, 0, 0}); } seritCizVeKur("##cepheSerit", cd, r.toplamSure, rc, g_sonKurma[s.kimlik()], [&](f64 an, bool) { cepheKur(an); }); cepheHudCiz(kc.katSirali, r.toplamSure, cd); // HUD: an + kalan sıvı % if (s.cepheDugum) { if (cepheMod == 1) // yalnız sıvı: renk = katılaşma anı [0, toplam] voxelLejant(s.gorselAyar.rampa, 0.0f, (f32)r.toplamSure, "s", /*kilitli=*/true, s.gorselAyar.bandli, s.gorselAyar.bandSayi); else voxelLejant(s.gorselAyar.rampa, (f32)(s.alasim.tSolidus - 50.0), (f32)s.ayar.dokumSicaklik, "°C", /*kilitli=*/true, s.gorselAyar.bandli, s.gorselAyar.bandSayi); } } // ---- 🫧 Porozite (kantitatif tahmin: çekme + gaz) ---- ImGui::Separator(); float& esik = porEsikAl(s.kimlik()); if (const dokum::PoroziteSonuc* por = ds.poroziteAl(s.kimlik(), esik)) { ImGui::TextColored(SOLUK, "Porozite"); ImGui::Text("Kalite: "); ImGui::SameLine(); ImGui::TextColored(por->poroziteOrani > 1.0 ? KIRMIZI : por->poroziteOrani > 0.3 ? SARI : YESIL, "%%%.2f porozite (%.0f mm³)", por->poroziteOrani, por->toplamPorHacimMm3); ImGui::SetItemTooltip("Toplam porozite hacmi / parça hacmi (maks hücre %%%.1f)", por->maksPor); bool balon = ds.poroziteBaloncukVarMi(s.kimlik()); if (ImGui::Checkbox("Baloncuk görünümü (x-ray)", &balon)) ds.poroziteBaloncukGoster(s.kimlik(), balon, esik); ImGui::SetItemTooltip("Porozite > eşik bölgeleri blob izo-yüzey, parça saydam"); ImGui::SameLine(); ImGui::SetNextItemWidth(90); if (ImGui::DragFloat("eşik##por", &esik, 0.05f, 0.1f, 10.0f, "%%%.1f")) { if (balon) ds.poroziteBaloncukGoster(s.kimlik(), true, esik); if (s.gorselAyar.alan == dokum::VoxelAlan::Porozite && g_katmanSecim[s.kimlik()].empty() && g_porKup[s.kimlik()]) ds.voxelGorsellestir(s.kimlik()); // yalnız küp kipinde ısı-haritası tazele } if (!por->bolgeler.empty()) { ImGui::TextColored(SOLUK, "Bölgeler (%d) — tıkla: odakla", (int)por->bolgeler.size()); const int gosterN = std::min((int)por->bolgeler.size(), 20); for (int b = 0; b < gosterN; ++b) { const auto& bg = por->bolgeler[b]; const char* tip = bg.tip == dokum::PoroziteTip::Cekme ? "Çekme ⬇" : bg.tip == dokum::PoroziteTip::Gaz ? "Gaz 🫧" : "Karışık ⬇🫧"; char sat[96]; std::snprintf(sat, sizeof(sat), "%d. %s %.1f mm³##porB%d", b + 1, tip, bg.hacimMm3, b); if (ImGui::Selectable(sat)) ds.poroziteBolgeIsaretle(s.kimlik(), b); ImGui::SetItemTooltip("Hacim %.2f mm³ · %u hücre\nOrtalama %%%.2f · Maks %%%.2f", bg.hacimMm3, bg.hucreSayi, bg.ortalamaPor, bg.maksPor); } if ((int)por->bolgeler.size() > gosterN) ImGui::TextColored(SOLUK, "… +%d bölge (eşiği yükselt)", (int)por->bolgeler.size() - gosterN); } else { ImGui::TextColored(YESIL, "✓ Eşik üstü porozite bölgesi yok"); } } } // Görselleştir else bloğu kapanışı ui::sekmeBitir(); } ui::sekmeKapat(); } // ---- Versiyon künyesi HUD (MAGMA v01 kalıbı) — herhangi bir görsel açıkken // sol-altta proje/sim adı + aktif katman (video sunumlarının kimlik satırı). ---- { const char* gorunum = nullptr; const auto ksIt = g_katmanSecim.find(s.kimlik()); if (s.voxelDugum) gorunum = (ksIt != g_katmanSecim.end() && !ksIt->second.empty()) ? ksIt->second.c_str() : alanAd(s.gorselAyar.alan); else if (s.dolumDugum) gorunum = "Dolum"; else if (s.cepheDugum) gorunum = g_cepheMod[s.kimlik()] == 1 ? "Yalnız Sıvı" : "Katılaşma Cephesi"; else if (ds.poroziteBaloncukVarMi(s.kimlik())) gorunum = "Porozite"; if (gorunum) { const bool animHud = (s.dolumDugum && g_dolumSerit[s.kimlik()].oynat) || (s.cepheDugum && g_cepheSerit[s.kimlik()].oynat); kunyeHudCiz(ds, s, gorunum, animHud); } } // Alt eylem. ImGui::Separator(); if (ImGui::Button("🗑 Sil", {-1, 0})) { const u32 kk = s.kimlik(); if (ds.refSecim && ds.refSecim->sahip() == kk) ds.refSecim->bitir(); ds.sil(kk); // DİKKAT: s bu noktadan sonra geçersiz g_acik[kk] = false; silindi = true; } } // if (pen) } // ui::Pencere yıkıcı → ImGui::End() if (silindi) return; if (!acik) { if (ds.refSecim && ds.refSecim->sahip() == s.kimlik()) ds.refSecim->bitir(); g_acik[s.kimlik()] = false; } } } // namespace void dokumPencereAc(u32 kimlik) { g_acik[kimlik] = true; } void dokumPencereleriCiz(Cekirdek& cekirdek, Sahne& sahne, DokumSistemi& ds) { for (auto it = g_acik.begin(); it != g_acik.end();) { if (!it->second) { it = g_acik.erase(it); continue; } DokumSimulasyonu* s = ds.bul(it->first); if (!s) { it = g_acik.erase(it); continue; } dokumPenceresiCiz(cekirdek, sahne, ds, *s); ++it; } } } // namespace fiko