#include "malzeme_penceresi.hpp" #include "malzeme_islem.hpp" #include "malzeme_nesnesi.hpp" #include "urun_agaci_paneli.hpp" // agacKirlet — atama sonrası rozet tazeleme #include #include #include #include #include #include "belge_ciz.hpp" // BoruHatlari (doku dizisi köprüsü) #include "dosya_islemleri.hpp" // resimDosyaSec #include "fiko/core/gunluk.hpp" #include "fiko/malzeme/malzeme.hpp" #include "fiko/platform/pencere.hpp" #include "fiko/cekirdek/dunya.hpp" #include "fiko/sahne/sahne.hpp" #include "fiko/ui/bilesen.hpp" #include "fiko/ui/bilesen_ek.hpp" // toastGoster #include "fiko/ui/form.hpp" #include "fiko/ui/pencere_cerceve.hpp" #include "imgui.h" #include "imgui_impl_vulkan.h" // ImGui_ImplVulkan_AddTexture (doku küçük resmi) #include "uygulama_baglam.hpp" namespace fiko { using namespace fiko::malzeme; namespace { bool g_acik = false; bool g_pin = false; u32 g_secili = 0; // düzenlenen malzeme kimliği char g_filtre[64] = {}; int g_sinifFiltre = -1; // -1 = tümü bool g_yuzeAta = false; // atama hedefi: yüz (kapalı = gövde) const ImVec4 SOLUK{0.55f, 0.62f, 0.66f, 1.0f}; const ImVec4 UYARI{0.95f, 0.62f, 0.18f, 1.0f}; // Malzemeyi temsil eden 2B önizleme küresi. Gerçek render geçişi AÇMAZ (ayrı // hedef + pipeline maliyeti); analitik Lambert + Blinn parlaklık + Fresnel ile // malzemenin "hissini" verir — kullanıcı slider'ı çevirirken anında geri bildirim. void onizlemeKuresi(const MalzemeTanim& m, float cap) { ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 p0 = ImGui::GetCursorScreenPos(); const float r = cap * 0.5f; const ImVec2 mrk{p0.x + r, p0.y + r}; ImGui::Dummy({cap, cap}); const int halka = 14, dilim = 24; const float isikX = -0.45f, isikY = -0.55f, isikZ = 0.70f; const float puruz = std::clamp(m.puruzluluk, 0.02f, 1.0f); const float parlakUs = 2.0f / (puruz * puruz + 1e-3f); for (int i = 0; i < halka; ++i) { const float r0 = r * (float)i / halka, r1 = r * (float)(i + 1) / halka; for (int j = 0; j < dilim; ++j) { const float a0 = 6.2831853f * (float)j / dilim, a1 = 6.2831853f * (float)(j + 1) / dilim; const float rc = (r0 + r1) * 0.5f, ac = (a0 + a1) * 0.5f; // Küre normali (ekran uzayında yarıçaptan) const float nx = std::cos(ac) * rc / r, ny = std::sin(ac) * rc / r; const float nz = std::sqrt(std::max(0.0f, 1.0f - nx * nx - ny * ny)); const float nl = std::max(0.0f, nx * isikX + ny * isikY + nz * isikZ); const float vh = std::max(0.0f, nz); const float fres = std::pow(1.0f - vh, 5.0f); const float spek = std::pow(std::max(0.0f, nz * 0.5f + nl * 0.5f), parlakUs) * (0.15f + 0.85f * (1.0f - puruz)); const float metal = std::clamp(m.metaliklik, 0.0f, 1.0f); const float difuz = (0.18f + 0.82f * nl) * (1.0f - metal * 0.85f); auto kanal = [&](float taban, float emis) { float v = taban * difuz + spek * (metal ? taban : 1.0f) * (0.25f + 0.75f * metal); v += fres * (0.15f + m.kaplamaBerrak * 0.5f); return std::clamp(v + emis * m.emisyonSiddet, 0.0f, 1.0f); }; const ImU32 renk = IM_COL32( (int)(kanal(m.tabanRenk.x, m.emisyonRenk.x) * 255), (int)(kanal(m.tabanRenk.y, m.emisyonRenk.y) * 255), (int)(kanal(m.tabanRenk.z, m.emisyonRenk.z) * 255), (int)(std::clamp(m.opaklik + m.gecirgenlik * 0.35f, 0.15f, 1.0f) * 255)); const ImVec2 k0{mrk.x + std::cos(a0) * r0, mrk.y + std::sin(a0) * r0}; const ImVec2 k1{mrk.x + std::cos(a1) * r0, mrk.y + std::sin(a1) * r0}; const ImVec2 k2{mrk.x + std::cos(a1) * r1, mrk.y + std::sin(a1) * r1}; const ImVec2 k3{mrk.x + std::cos(a0) * r1, mrk.y + std::sin(a0) * r1}; dl->AddQuadFilled(k0, k1, k2, k3, renk); } } dl->AddCircle(mrk, r, IM_COL32(255, 255, 255, 60), 48, 1.2f); // Zemin gölgesi — küre "duruyor" hissi dl->AddEllipseFilled({mrk.x, mrk.y + r * 0.98f}, {r * 0.75f, r * 0.13f}, IM_COL32(0, 0, 0, 55)); } ImU32 renkU32(const Vec3f& c, float alfa = 1.0f) { return IM_COL32((int)(std::clamp(c.x, 0.0f, 1.0f) * 255), (int)(std::clamp(c.y, 0.0f, 1.0f) * 255), (int)(std::clamp(c.z, 0.0f, 1.0f) * 255), (int)(alfa * 255)); } bool renkAlani(const char* etiket, Vec3f& c) { float v[3] = {c.x, c.y, c.z}; ui::alan(etiket); if (ImGui::ColorEdit3((std::string("##") + etiket).c_str(), v, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_Float)) { c = {v[0], v[1], v[2]}; return true; } return false; } bool kaydirici(const char* etiket, f32& v, float mn, float mx, const char* bicim = "%.3f") { ui::alan(etiket); return ui::sliderYazilabilir((std::string("##") + etiket).c_str(), &v, mn, mx, bicim); } // Seçili sahne öğelerini (gövde/yüz) atama hedefi olarak çözer. struct AtamaHedef { u32 govde = 0; u32 yuz = 0; // 0 = tüm gövde }; // Bir Varlik ALTINDAKİ tüm mesh'li düğümleri toplar (PartBody / klasör / grup // seçimi → içindeki gövdelerin hepsi). Ürün ağacında PartBody bir GRUP'tur, Dugum // değildir → doğrudan atama görünmez kalırdı; kullanıcı "atayamıyorum" der (07-20). void altAgacGovdeleri(Sahne& sahne, Varlik kok, std::vector& cikti) { Dunya& d = sahne.dunya(); for (const Dugum& dg : sahne.dugumler()) { if (!dg.yuzSayi_ && dg.indeksSayi == 0) continue; // datum/tel → malzeme anlamsız // Zincirde kök var mı (derinlik sınırlı: ürün ağacı sığdır, döngü koruması var) Varlik v = dg.kimlik; for (int adim = 0; adim < 64 && v != VARLIK_YOK; ++adim) { if (v == kok) { cikti.push_back(dg.kimlik); break; } const HiyerarsiBilesen* h = d.bul(v); v = h ? h->ust : VARLIK_YOK; } } } std::vector hedefleriCoz(Sahne& sahne, bool yuzeAta) { std::vector h; const SecimKip kip = sahne.kip(); for (const SecimOge& o : sahne.secililer()) { if (!o.govde) continue; if (sahne.bul(o.govde)) { // gerçek gövde düğümü const bool yuzSecimi = yuzeAta && kip == SecimKip::Yuz && o.alt != 0; h.push_back({o.govde, yuzSecimi ? o.alt : 0u}); continue; } // Düğüm değil → GRUP/PartBody: altındaki tüm gövdelere uygula. std::vector alt; altAgacGovdeleri(sahne, o.govde, alt); for (u32 g : alt) h.push_back({g, 0u}); } // Aynı gövde birden çok yoldan gelebilir (grup + kendisi) — tekille. std::sort(h.begin(), h.end(), [](const AtamaHedef& a, const AtamaHedef& b) { return a.govde != b.govde ? a.govde < b.govde : a.yuz < b.yuz; }); h.erase(std::unique(h.begin(), h.end(), [](const AtamaHedef& a, const AtamaHedef& b) { return a.govde == b.govde && a.yuz == b.yuz; }), h.end()); return h; } void malzemeListesi(UygulamaBaglam& b) { MalzemeKutuphanesi& kut = b.malzemeKut; // İlk açılışta seçili malzeme YOKSA ilkini seç: "ata" butonunun ölü görünmesinin // en sık nedeni buydu (kullanıcı listeden seçmeyi beklemiyor). if (!g_secili && !kut.hepsi().empty()) g_secili = kut.hepsi()[0].kimlik; ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##filtre", "🔍 Ara (çelik, alüminyum, cam…)", g_filtre, sizeof(g_filtre)); // Sınıf sekmesi — kompakt combo (10 sınıf butona sığmaz) ui::alan("Sınıf"); const char* sinifAdlari[] = {"Tümü", "Metal", "Plastik", "Ahşap", "Cam", "Kauçuk", "Seramik", "Boya", "Kumaş", "Sıvı", "Diğer"}; int secim = g_sinifFiltre + 1; if (ImGui::Combo("##sinif", &secim, sinifAdlari, IM_ARRAYSIZE(sinifAdlari))) g_sinifFiltre = secim - 1; const std::vector liste = kut.ara(g_filtre, g_sinifFiltre >= 0, static_cast(std::max(g_sinifFiltre, 0))); ImGui::BeginChild("##mliste", {0, 190}, true); for (u32 kimlik : liste) { const MalzemeTanim* m = kut.bul(kimlik); if (!m) continue; ImGui::PushID((int)kimlik); const ImVec2 p0 = ImGui::GetCursorScreenPos(); const bool sec = ImGui::Selectable("##sat", g_secili == kimlik, 0, {0, 22}); ImDrawList* dl = ImGui::GetWindowDrawList(); // Renk noktası + ad + sınıf rozeti (kart hissi) dl->AddCircleFilled({p0.x + 11, p0.y + 11}, 7.0f, renkU32(m->tabanRenk)); dl->AddCircle({p0.x + 11, p0.y + 11}, 7.0f, IM_COL32(255, 255, 255, 90), 16, 1.0f); if (m->metaliklik > 0.5f) // metal işareti: iç parlak yay dl->AddCircleFilled({p0.x + 8.5f, p0.y + 8.0f}, 2.0f, IM_COL32(255, 255, 255, 150)); char etiket[128]; std::snprintf(etiket, sizeof(etiket), "%s%s", m->ad.c_str(), m->hazir ? "" : " ✎"); dl->AddText({p0.x + 25, p0.y + 3}, IM_COL32(232, 240, 244, 255), etiket); const char* sa = sinifAdi(m->sinif); const ImVec2 sb = ImGui::CalcTextSize(sa); const float sagX = p0.x + ImGui::GetContentRegionAvail().x - sb.x - 8; dl->AddText({sagX, p0.y + 4}, IM_COL32(120, 190, 205, 200), sa); if (sec) g_secili = kimlik; ImGui::PopID(); } if (liste.empty()) ImGui::TextColored(SOLUK, "Eşleşen malzeme yok"); ImGui::EndChild(); // Kütüphane işlemleri if (ImGui::Button("+ Yeni", {0, 0})) { MalzemeTanim y; y.ad = "Yeni Malzeme"; y.sinif = MalzemeSinif::Plastik; y.metaliklik = 0.0f; g_secili = kut.ekle(std::move(y)); } ImGui::SameLine(); if (ImGui::Button("⧉ Kopyala") && g_secili) { const u32 k = kut.kopyala(g_secili); if (k) g_secili = k; } ImGui::SameLine(); const MalzemeTanim* sm = kut.bul(g_secili); ImGui::BeginDisabled(!sm || sm->hazir); if (ImGui::Button("🗑 Sil") && sm) { b.malzemeAta.malzemeyiKaldir(g_secili); kut.sil(g_secili); g_secili = 0; } ImGui::EndDisabled(); if (sm && sm->hazir) { ImGui::SameLine(); ImGui::TextColored(SOLUK, "(hazır — kopyalayıp düzenle)"); } // Atama BURADA da dursun: kullanıcı sekme değiştirmeden, listeden seçip tek tıkla // uygulayabilsin (07-20 geri bildirimi: "malzeme atayamıyorum"). Buton ASLA sessizce // ölü kalmaz — engel varsa nedenini yazar. const auto hedefler = hedefleriCoz(b.sahne, g_yuzeAta); const bool hazir = !hedefler.empty() && g_secili != 0; ImGui::BeginDisabled(!hazir); char btn[96]; std::snprintf(btn, sizeof(btn), "✔ Seçime Ata (%d hedef)", (int)hedefler.size()); if (ImGui::Button(btn, {-1, 26})) malzemeSeciliyeAta(b, g_secili); ImGui::EndDisabled(); if (!hazir) ImGui::TextColored(UYARI, "%s", g_secili == 0 ? "↑ Önce listeden bir malzeme seç" : "Sahnede/ağaçta gövde, PartBody ya da yüz seç"); } // Doku küçük resmi: GPU görüntüsü için ImGui descriptor'ı LAZY kurulur ve görünüm // tanıtıcısına göre önbelleklenir (her kare AddTexture = descriptor sızıntısı). ImTextureID dokuOnizleme(DokuDizisi& dizi, u32 slot) { const VkImageView gorunum = dizi.gorunum(slot); if (!gorunum) return 0; static std::unordered_map onbellek; auto it = onbellek.find(gorunum); if (it == onbellek.end()) it = onbellek.emplace(gorunum, ImGui_ImplVulkan_AddTexture( dizi.ornekleyici(), gorunum, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)) .first; return (ImTextureID)it->second; } // Tek doku kanalı satırı: dosya seç / temizle / şiddet / ters. bool dokuKanaliSatiri(UygulamaBaglam& b, BoruHatlari& hatlar, MalzemeTanim& m, DokuKanal kanal) { const u32 k = static_cast(kanal); DokuKatman& d = m.dokular[k]; bool degisti = false; ImGui::PushID((int)k); ImGui::TextColored(d.acik() ? ImVec4{0.75f, 0.92f, 0.95f, 1} : SOLUK, "%s", kanalAdi(kanal)); ImGui::SameLine(130.0f); const std::string kisa = d.yol.empty() ? std::string("— yok —") : d.yol.substr(d.yol.find_last_of("/\\") + 1); if (ImGui::Button((kisa + "##sec").c_str(), {ImGui::GetContentRegionAvail().x - 58, 0})) { const std::string yol = resimDosyaSec((HWND)b.pencere.dogalTutamac()); if (!yol.empty()) { d.yol = yol; degisti = true; } } if (!d.yol.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", d.yol.c_str()); ImGui::SameLine(); ImGui::BeginDisabled(!d.acik()); if (ImGui::Button("✕", {22, 0})) { d.yol.clear(); degisti = true; } ImGui::EndDisabled(); if (d.acik()) { ImGui::Indent(12.0f); if (const ImTextureID kres = dokuOnizleme(hatlar.malzemeGpu.dokular(), d.gpuSlot)) { ImGui::Image(kres, {36, 36}); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::Image(kres, {192, 192}); // büyük önizleme ImGui::EndTooltip(); } ImGui::SameLine(); } ImGui::SetNextItemWidth(-90); if (ui::sliderYazilabilir("##sid", &d.siddet, 0.0f, 1.0f, "şiddet %.2f")) degisti = true; ImGui::SameLine(); if (ImGui::Checkbox("Ters", &d.ters)) degisti = true; if (ImGui::IsItemHovered()) ImGui::SetTooltip("1−x: parlaklık (glossiness) haritasını pürüzlülük olarak kullan"); ImGui::Unindent(12.0f); } ImGui::PopID(); return degisti; } // Düzenleme undo'su: kare başında anlık görüntü al, kullanıcı bir widget'ı BIRAKINCA // (aktiflik biter) tek işlem olarak geçmişe it — slider sürüklerken 60 kayıt olmaz // (ozellikler_paneli kalıbı). MalzemeTanim g_duzenOnce; u32 g_duzenKimlik = 0; bool g_duzenAktifti = false; void editor(UygulamaBaglam& b, BoruHatlari& hatlar, MalzemeTanim& m, bool& dokuDegisti) { bool d = false; if (!g_duzenAktifti || g_duzenKimlik != m.kimlik) { g_duzenOnce = m; g_duzenKimlik = m.kimlik; } // ── Önizleme + kimlik ── onizlemeKuresi(m, 78.0f); ImGui::SameLine(); ImGui::BeginGroup(); { char ad[96]; std::snprintf(ad, sizeof(ad), "%s", m.ad.c_str()); ImGui::SetNextItemWidth(-1); ImGui::BeginDisabled(m.hazir); if (ImGui::InputText("##ad", ad, sizeof(ad))) { m.ad = ad; d = true; } char acik[160]; std::snprintf(acik, sizeof(acik), "%s", m.aciklama.c_str()); ImGui::SetNextItemWidth(-1); if (ImGui::InputTextWithHint("##acik", "açıklama / not", acik, sizeof(acik))) { m.aciklama = acik; d = true; } ImGui::EndDisabled(); ImGui::TextColored(SOLUK, "%s · %.0f kg/m³", sinifAdi(m.sinif), m.yogunluk); } ImGui::EndGroup(); if (m.hazir) ui::bilgiSeridi(2, "Hazır malzeme salt-okunur. Düzenlemek için 'Kopyala'."); ImGui::BeginDisabled(m.hazir); if (ui::bolum("🎨", "Taban", "##btaban")) { d |= renkAlani("Taban Renk", m.tabanRenk); d |= kaydirici("Metaliklik", m.metaliklik, 0.0f, 1.0f, "%.2f"); d |= kaydirici("Pürüzlülük", m.puruzluluk, 0.015f, 1.0f, "%.3f"); d |= kaydirici("Yansıma", m.yansima, 0.0f, 1.0f, "%.2f"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Dielektrik yansıma gücü (metalde etkisiz)"); d |= kaydirici("Opaklık", m.opaklik, 0.0f, 1.0f, "%.2f"); d |= kaydirici("Örtme (AO)", m.ortmeSiddet, 0.0f, 1.0f, "%.2f"); } if (ui::bolum("💫", "Anizotropi", "##baniz")) { d |= kaydirici("Miktar", m.anizotropi, -1.0f, 1.0f, "%.2f"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Fırçalanmış metal / torna izi: parlaklığı bir eksende uzatır"); d |= kaydirici("Açı (°)", m.anizotropiAci, 0.0f, 180.0f, "%.0f"); } if (ui::bolum("✨", "Berrak Kaplama", "##bkap")) { d |= kaydirici("Miktar", m.kaplamaBerrak, 0.0f, 1.0f, "%.2f"); d |= kaydirici("Kaplama Pürüzü", m.kaplamaPuruz, 0.015f, 1.0f, "%.3f"); d |= renkAlani("Kaplama Rengi", m.kaplamaRenk); } if (ui::bolum("🧊", "Geçirgenlik", "##bgec")) { d |= kaydirici("Geçirgenlik", m.gecirgenlik, 0.0f, 1.0f, "%.2f"); d |= kaydirici("Kalınlık (mm)", m.kalinlik, 0.0f, 200.0f, "%.1f"); d |= renkAlani("Emilim Rengi", m.emilimRenk); d |= kaydirici("Kırılma İndisi", m.kirilmaIndisi, 1.0f, 3.0f, "%.3f"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Cam 1.52 · Su 1.33 · Elmas 2.42"); } if (ui::bolum("🧶", "Kumaş / Saçılma", "##btuy")) { d |= renkAlani("Tüy (Sheen)", m.parlakTuyRenk); d |= kaydirici("Tüy Pürüzü", m.parlakTuyPuruz, 0.015f, 1.0f, "%.3f"); d |= renkAlani("Saçılma Rengi", m.sacilmaRenk); d |= kaydirici("Saçılma", m.sacilmaSiddet, 0.0f, 1.0f, "%.2f"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Yüzey altı saçılma (deri, mum, plastik)"); } if (ui::bolum("💡", "Emisyon", "##bemis")) { d |= renkAlani("Emisyon Rengi", m.emisyonRenk); d |= kaydirici("Şiddet", m.emisyonSiddet, 0.0f, 10.0f, "%.2f"); } if (ui::bolum("🧱", "Doku Kanalları", "##bdoku")) { for (u32 k = 0; k < DOKU_KANAL_SAYI; ++k) if (dokuKanaliSatiri(b, hatlar, m, static_cast(k))) { d = true; dokuDegisti = true; } } if (ui::bolum("🗺", "Doku İzdüşümü", "##bizd")) { ui::alan("Kaplama"); const char* kaplamalar[] = {"Triplanar", "Düzlemsel", "Silindirik", "Küresel"}; int kt = (int)m.kaplama; if (ImGui::Combo("##kaplama", &kt, kaplamalar, 4)) { m.kaplama = (KaplamaTip)kt; d = true; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("UV yok (BREP) → doku konumdan izdüşürülür.\nTriplanar her yüzde tutarlıdır."); float olcek[3] = {m.dokuOlcek.x, m.dokuOlcek.y, m.dokuOlcek.z}; ui::alan("Ölçek (mm)"); if (ui::vektorGirdi3("##dolcek", olcek, 0.5f, "%.1f")) { m.dokuOlcek = {olcek[0], olcek[1], olcek[2]}; d = true; } float ofs[3] = {m.dokuOfset.x, m.dokuOfset.y, m.dokuOfset.z}; ui::alan("Ofset (mm)"); if (ui::vektorGirdi3("##dofset", ofs, 0.5f, "%.1f")) { m.dokuOfset = {ofs[0], ofs[1], ofs[2]}; d = true; } d |= kaydirici("Döndürme (°)", m.dokuDonus, 0.0f, 360.0f, "%.0f"); d |= kaydirici("Geçiş Keskinliği", m.triplanarKeskinlik, 1.0f, 32.0f, "%.1f"); if (ImGui::Checkbox("Dünya uzayında (parça dönünce doku sabit)", &m.dunyaUzayinda)) d = true; } if (ui::bolum("⚖", "Mühendislik", "##bmuh")) { ui::alan("Sınıf"); const char* sinifAdlari[] = {"Metal", "Plastik", "Ahşap", "Cam", "Kauçuk", "Seramik", "Boya", "Kumaş", "Sıvı", "Diğer"}; int si = (int)m.sinif; if (ImGui::Combo("##msinif", &si, sinifAdlari, IM_ARRAYSIZE(sinifAdlari))) { m.sinif = (MalzemeSinif)si; d = true; } ui::alan("Yoğunluk (kg/m³)"); float yog = (float)m.yogunluk; if (ImGui::DragFloat("##yog", &yog, 10.0f, 1.0f, 25000.0f, "%.0f")) { m.yogunluk = yog; d = true; } char alasim[64]; std::snprintf(alasim, sizeof(alasim), "%s", m.dokumAlasimAdi.c_str()); ui::alan("Döküm alaşımı"); if (ImGui::InputTextWithHint("##alasim", "ör. AlSi7Mg", alasim, sizeof(alasim))) { m.dokumAlasimAdi = alasim; d = true; } } ImGui::EndDisabled(); if (d) { m.gecerliKil(); b.malzemeKut.degisti(); } // Widget bırakıldı mı (aktiflik bu kare bitti) → tek undo kaydı. const bool aktif = ImGui::IsAnyItemActive(); if (g_duzenAktifti && !aktif && g_duzenKimlik == m.kimlik) { const bool fark = g_duzenOnce.tabanRenk.x != m.tabanRenk.x || g_duzenOnce.tabanRenk.y != m.tabanRenk.y || g_duzenOnce.tabanRenk.z != m.tabanRenk.z || g_duzenOnce.metaliklik != m.metaliklik || g_duzenOnce.puruzluluk != m.puruzluluk || g_duzenOnce.opaklik != m.opaklik || g_duzenOnce.gecirgenlik != m.gecirgenlik || g_duzenOnce.kaplamaBerrak != m.kaplamaBerrak || g_duzenOnce.anizotropi != m.anizotropi || g_duzenOnce.emisyonSiddet != m.emisyonSiddet || g_duzenOnce.yogunluk != m.yogunluk || g_duzenOnce.dokuOlcek.x != m.dokuOlcek.x || g_duzenOnce.ad != m.ad; if (fark) b.cekirdek.uygula(std::make_unique(b.malzemeKut, g_duzenOnce, m)); g_duzenOnce = m; } g_duzenAktifti = aktif; } void atamaBolumu(UygulamaBaglam& b) { Sahne& sahne = b.sahne; const auto hedefler = hedefleriCoz(sahne, g_yuzeAta); ui::baslikSeridi("🎯", "Atama"); ImGui::Checkbox("Yüze ata (kapalı = tüm gövde)", &g_yuzeAta); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Yüz kipinde seçili yüze uygular. PartBody/grup seçiliyse " "atama altındaki TÜM gövdelere gider."); if (hedefler.empty()) { ui::bilgiSeridi(2, "Hedef yok: 3B sahnede ya da ürün ağacında gövde / PartBody / yüz seç."); } else { char bilgi[128]; std::snprintf(bilgi, sizeof(bilgi), "%d hedef seçili (%s)", (int)hedefler.size(), hedefler[0].yuz ? "yüz" : "gövde"); ui::bilgiSeridi(0, bilgi); } ImGui::BeginDisabled(hedefler.empty() || !g_secili); if (ImGui::Button("✔ Seçime Ata", {-1, 26})) malzemeSeciliyeAta(b, g_secili); ImGui::EndDisabled(); ImGui::BeginDisabled(hedefler.empty()); if (ImGui::Button("✖ Atamayı Kaldır", {-1, 0})) malzemeSeciliyeAta(b, 0); ImGui::EndDisabled(); // Mevcut atamalar (seçili gövdeler için) if (!hedefler.empty()) { ImGui::Spacing(); for (const AtamaHedef& h : hedefler) { const u32 mk = b.malzemeAta.coz(h.govde, h.yuz); const MalzemeTanim* m = b.malzemeKut.bul(mk); const Dugum* dn = sahne.bul(h.govde); char sol[80]; std::snprintf(sol, sizeof(sol), "%s%s", dn ? dn->ad.c_str() : "?", h.yuz ? " · yüz" : ""); ui::istatistikSatiri(sol, m ? m->ad.c_str() : "—"); // Kütle: gövde hacmi × yoğunluk (mühendislik geri bildirimi) if (m && dn && dn->hacim > 0 && !h.yuz) { char kg[48]; std::snprintf(kg, sizeof(kg), "%.3f kg", m->kutleKg(dn->hacim)); ui::istatistikSatiri(" Kütle", kg); } } } } } // namespace u32 malzemeAktifKimlik() { return g_secili; } void malzemePencereAc() { g_acik = true; } bool malzemePencereAcikMi() { return g_acik; } bool malzemeSeciliyeAta(UygulamaBaglam& b, u32 malzemeKimlik) { const auto hedefler = hedefleriCoz(b.sahne, g_yuzeAta); if (hedefler.empty()) return false; const malzeme::MalzemeAtama once = b.malzemeAta; // undo anlık görüntüsü (küçük) for (const AtamaHedef& h : hedefler) { if (h.yuz) b.malzemeAta.yuzeAta(h.govde, h.yuz, malzemeKimlik); else b.malzemeAta.govdeyeAta(h.govde, malzemeKimlik); } // Ctrl+Z'ye gir: uygula() idempotent (zaten uygulanmış durumu yeniden yazar). b.cekirdek.uygula(std::make_unique( b.malzemeAta, once, b.malzemeAta, malzemeKimlik ? "Malzeme Ata" : "Malzeme Kaldır")); // Ürün ağacındaki malzeme feature düğümlerini atamaya eşitle (ekle/güncelle/sil). malzemeDugumleriSenkron(b.cekirdek, b.sahne, b.malzemeKut, b.malzemeAta); agacKirlet(); const MalzemeTanim* m = b.malzemeKut.bul(malzemeKimlik); char mesaj[128]; std::snprintf(mesaj, sizeof(mesaj), "%s → %d hedef", m ? m->ad.c_str() : "Malzeme kaldırıldı", (int)hedefler.size()); ui::toastGoster(mesaj); return true; } void malzemePenceresiCiz(UygulamaBaglam& b, BoruHatlari& hatlar) { if (!g_acik) return; bool dokuDegisti = false; { ui::Pencere p{"Malzeme###malzeme", &g_acik, {.boyut = {380, 0}, .minBoyut = {330, 220}, .pinDurum = &g_pin, .skin = ui::PencereSkin::WebCam}}; if (p) { malzemeListesi(b); ImGui::Spacing(); MalzemeTanim* m = b.malzemeKut.bul(g_secili); if (!m) { ui::bilgiSeridi(0, "Listeden bir malzeme seç ya da '+ Yeni' ile oluştur."); } else { if (ui::sekmeBaslat("##msek")) { if (ui::sekme("Özellikler", "🎛")) { editor(b, hatlar, *m, dokuDegisti); ui::sekmeBitir(); } if (ui::sekme("Atama", "🎯")) { atamaBolumu(b); ui::sekmeBitir(); } ui::sekmeKapat(); } } } } // Doku dosyası değiştiyse GPU'ya yükle — dosya I/O olduğu için YALNIZ kullanıcı // eylemiyle (sıcak yolda değil). if (dokuDegisti) hatlar.malzemeGpu.dokulariBagla(b.malzemeKut); } } // namespace fiko