// FarkPanel — web fiko-cad-diff panelinin BİREBİR native karşılığı (açık-gri // CATIA V5 teması + turkuaz DialogFiko kromu). Düzen/etiketler/renkler web // component.html sırasıyla eş; hesap native farkHesapla (GPU compute → CPU). #include "fark_panel.hpp" #include #include #include #include #include #include #include #include #include "fark_karsilastirma.hpp" #include "fark_stil.hpp" #include "fark_tur.hpp" #include "fiko/analiz/analiz_sistemi.hpp" #include "fiko/sahne/sahne.hpp" #include "imgui.h" namespace fiko { namespace { using namespace farkstil; // Pencere açık-kenar takibi: boyama yalnız YENİ açılışta zorlanır (toggle ezilmez). std::unordered_map g_panelAcikti; // TR binlik ayraçlı sayı ("65.012") — web Angular number pipe karşılığı. void binlikFmt(size_t n, char* b, size_t bs) { char ham[32]; std::snprintf(ham, sizeof(ham), "%zu", n); const size_t L = std::strlen(ham); size_t yi = 0; for (size_t i = 0; i < L && yi + 1 < bs; ++i) { b[yi++] = ham[i]; const size_t kalan = L - 1 - i; if (kalan > 0 && kalan % 3 == 0 && yi + 1 < bs) b[yi++] = '.'; } b[yi] = 0; } // Web formatSayi: ≥1M → "1.2M", ≥1000 → "1.2K". void kisaFmt(u32 n, char* b, size_t bs) { if (n >= 1000000) std::snprintf(b, bs, "%.1fM", n / 1e6); else if (n >= 1000) std::snprintf(b, bs, "%.1fK", n / 1e3); else std::snprintf(b, bs, "%u", n); } // Web formatMesafe: ≥1000mm→m, ≥10→1 hane, ≥1→2 hane, ≥0.01→3 hane, altı bilimsel. void mesafeFmt(f32 mm, char* b, size_t bs) { if (mm >= 1000.0f) std::snprintf(b, bs, "%.2f m", mm / 1000.0f); else if (mm >= 10.0f) std::snprintf(b, bs, "%.1f mm", mm); else if (mm >= 1.0f) std::snprintf(b, bs, "%.2f mm", mm); else if (mm >= 0.01f) std::snprintf(b, bs, "%.3f mm", mm); else std::snprintf(b, bs, "%.2e mm", mm); } // Model kutusu (web .model-kutu): rozet + "Eski/Yeni versiyon:" başlık; boşsa // ≡ (liste dropdown) + ⊕ (sahneden/ağaçtan seçim); doluysa ad + × temizle. // Seçim modunda mor pulse border. Dönüş: model değişti (çağıran hesaplar). bool modelKutu(Sahne& sahne, analiz::AnalizSistemi& an, analiz::AnalizNesnesi& a, bool eskiKutu, float genislik) { bool degisti = false; u32& govde = eskiKutu ? a.farkKarsiGovde : a.hedefGovde; const u32 haric = eskiKutu ? a.hedefGovde : a.farkKarsiGovde; const analiz::SecimKip kip = eskiKutu ? analiz::SecimKip::FarkKarsi : analiz::SecimKip::Hedef; const bool secimde = an.secimAktif() && an.secimKip() == kip; Dugum* d = govde ? sahne.bul(govde) : nullptr; ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 k0 = ImGui::GetCursorScreenPos(); const float kH = 52.0f; // Kutu zemini: boş #fafafa, dolu #e8f4fc + #2980b9; seçim modunda #9b59b6 pulse. ImU32 zemin = d ? IM_COL32(0xe8, 0xf4, 0xfc, 255) : IM_COL32(0xfa, 0xfa, 0xfa, 255); ImU32 cerceve = d ? IM_COL32(0x29, 0x80, 0xb9, 255) : IM_COL32(0xdd, 0xdd, 0xdd, 255); if (secimde) { const f32 nabiz = 0.5f + 0.5f * std::sin((f32)ImGui::GetTime() * 6.28f); cerceve = IM_COL32(0x9b, 0x59, 0xb6, (int)(140 + nabiz * 115)); } dl->AddRectFilled(k0, {k0.x + genislik, k0.y + kH}, zemin, 3.0f); dl->AddRect(k0, {k0.x + genislik, k0.y + kH}, cerceve, 3.0f, 0, secimde ? 2.0f : 1.0f); // Rozet (14×14) + başlık. dl->AddRectFilled({k0.x + 5, k0.y + 5}, {k0.x + 19, k0.y + 19}, eskiKutu ? kRozetA : kRozetB, 2.0f); const char* rozetHarf = eskiKutu ? "A" : "B"; dl->AddText({k0.x + 8, k0.y + 5}, IM_COL32_WHITE, rozetHarf); dl->AddText({k0.x + 23, k0.y + 5}, IM_COL32(0x66, 0x66, 0x66, 255), eskiKutu ? "Eski versiyon:" : "Yeni versiyon:"); ImGui::SetCursorScreenPos({k0.x + 5, k0.y + 24}); ImGui::PushID(eskiKutu ? "kutuE" : "kutuY"); if (d) { // Dolu: ad (ellipsis) + × temizle. ImGui::PushTextWrapPos(k0.x + genislik - 24); ImGui::TextUnformatted(d->ad.c_str()); ImGui::PopTextWrapPos(); ImGui::SetCursorScreenPos({k0.x + genislik - 20, k0.y + 24}); if (ImGui::SmallButton("×")) { govde = 0; degisti = true; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Modeli temizle"); } else { // Boş: ≡ liste + ⊕ sahneden seç (web ikonlarının karşılığı). const float bw = (genislik - 16.0f) * 0.5f; if (ImGui::Button("≡", {bw, 22})) ImGui::OpenPopup("modelListe"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Listeden seç (ürün ağacı gövdeleri)"); ImGui::SameLine(0, 6); if (secimde) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.61f, 0.35f, 0.71f, 1)); if (ImGui::Button("⊕", {bw, 22})) an.secimBaslat(a.kimlik(), kip, [] {}); if (secimde) ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Sahneden veya ürün ağacından seç"); if (ImGui::BeginPopup("modelListe")) { ImGui::TextDisabled("Gövde seç"); ImGui::Separator(); for (u32 g : sahne.seciliGovdeler()) { // ağaçta seçili olanlar ⭐ üstte Dugum* sd = sahne.bul(g); if (!sd || sd->indeksSayi == 0 || g == haric) continue; char sat[160]; std::snprintf(sat, sizeof(sat), "⭐ %s##s%u", sd->ad.c_str(), g); if (ImGui::Selectable(sat)) { govde = g; degisti = true; } } for (Dugum& sd : sahne.dugumler()) { if (sd.indeksSayi == 0 || sd.cpuKonum.empty() || sd.cpuIndeks.empty()) continue; if (sd.kimlik == haric) continue; char sat[160]; std::snprintf(sat, sizeof(sat), "%s##g%u", sd.ad.c_str(), sd.kimlik); if (ImGui::Selectable(sat)) { govde = sd.kimlik; degisti = true; } } ImGui::EndPopup(); } } ImGui::PopID(); ImGui::SetCursorScreenPos({k0.x, k0.y + kH + 2}); ImGui::Dummy({genislik, 0}); return degisti; } // CSV sapma raporu (web raporAktar birebir: TR yerel, UTF-8 BOM + ';', özet + // P50/P95/P99 + 20 kovalı histogram). Çalışma dizinine zaman damgalı yazar. bool csvRaporYaz(analiz::AnalizNesnesi& a, std::string& yol) { std::vector tum; tum.reserve(a.sonuc.deger.size() + a.farkSonucEski.deger.size()); tum.insert(tum.end(), a.sonuc.deger.begin(), a.sonuc.deger.end()); tum.insert(tum.end(), a.farkSonucEski.deger.begin(), a.farkSonucEski.deger.end()); if (tum.empty()) return false; std::sort(tum.begin(), tum.end()); const auto p = [&](f64 q) { return tum[(size_t)((f64)(tum.size() - 1) * q)]; }; const auto ts = std::chrono::system_clock::now().time_since_epoch().count(); char ad[96]; std::snprintf(ad, sizeof(ad), "fark_raporu_%lld.csv", (long long)(ts % 100000000)); std::FILE* f = nullptr; if (fopen_s(&f, ad, "wb") != 0 || !f) return false; std::fprintf(f, "\xEF\xBB\xBF"); // UTF-8 BOM (Excel TR) std::fprintf(f, "CAD Fark Sapma Raporu\n"); // İzlenebilir üstbilgi (kendini belgeleyen rapor): kullanılan eşik + yöntem/süre. std::fprintf(f, "Degisim esigi (cozulmus);%.4f mm\nGirdi hassasiyet;%.4f mm\n" "Yontem;%s\nSure;%.1f ms\n", a.farkToleransCozulmus, a.farkTolerans, a.farkGpu ? "GPU compute" : "CPU", a.farkSureMs); std::fprintf(f, "Eslesme;%%%.1f\nSilinen (A);%u\nEklenen (B);%u\n", 100.0f * (1.0f - (f32)(a.farkEklenen + a.farkSilinen) / std::max(tum.size(), 1)), a.farkSilinen, a.farkEklenen); std::fprintf(f, "P50;%.4f mm\nP95;%.4f mm\nP99;%.4f mm\nOrtalama;%.4f mm\nRMS;%.4f mm\n" "Std;%.4f mm\n3-sigma (%%99.7);%.4f mm\n", p(0.50), p(0.95), p(0.99), a.farkOrt, a.farkRms, a.farkStd, a.farkStd * 3.0f); std::fprintf(f, "\nHistogram (20 kova)\nKova baslangic (mm);Adet\n"); const f32 mx = std::max(tum.back(), 1e-6f); u32 kova[20] = {}; for (f32 v : tum) ++kova[std::min((int)(v / mx * 20.0f), 19)]; for (int i = 0; i < 20; ++i) std::fprintf(f, "%.4f;%u\n", mx / 20.0f * i, kova[i]); // Değişim bölgeleri (maks sapma azalan sıralı): tip sezgisi + konum → CATIA/GOM // rapor kalıbı (her bölge tek satır, mühendis kök-neden analizi yapabilsin). if (!a.farkBolgeler.empty()) { std::fprintf(f, "\nDegisim Bolgeleri (%zu)\n", a.farkBolgeler.size()); std::fprintf(f, "No;Yon;Tip;Maks sapma (mm);Vertex;Yaricap (mm);Merkez X;Merkez Y;Merkez Z\n"); int i = 0; for (const FarkBolge& b : a.farkBolgeler) std::fprintf(f, "%d;%s;%s;%.4f;%u;%.3f;%.3f;%.3f;%.3f\n", ++i, b.eklenen ? "Eklenen" : "Silinen", bolgeTipAdi(b), b.maksSapma, b.vertexSayi, b.yaricap, b.merkez.x, b.merkez.y, b.merkez.z); } std::fclose(f); yol = ad; return true; } bool jsonOzetYaz(analiz::AnalizNesnesi& a, std::string& yol) { const auto ts = std::chrono::system_clock::now().time_since_epoch().count(); char ad[96]; std::snprintf(ad, sizeof(ad), "fark_sonuc_%lld.json", (long long)(ts % 100000000)); std::FILE* f = nullptr; if (fopen_s(&f, ad, "wb") != 0 || !f) return false; std::fprintf(f, "{\n \"eslesmeYuzde\": %.2f,\n \"silinenVertex\": %u,\n" " \"eklenenVertex\": %u,\n \"maksSapma\": %.5f,\n \"maksSapmaP99\": %.5f,\n" " \"ortSapma\": %.5f,\n \"rmsSapma\": %.5f,\n \"stdSapma\": %.5f,\n" " \"uc3Sigma\": %.5f,\n" " \"degisimEsigi\": %.5f,\n \"sureMs\": %.1f,\n \"gpu\": %s,\n", 100.0f * (1.0f - (f32)(a.farkEklenen + a.farkSilinen) / std::max(a.sonuc.deger.size() + a.farkSonucEski.deger.size(), 1)), a.farkSilinen, a.farkEklenen, a.farkMaks, a.farkP99, a.farkOrt, a.farkRms, a.farkStd, a.farkStd * 3.0f, a.farkToleransCozulmus, a.farkSureMs, a.farkGpu ? "true" : "false"); // Değişim bölgeleri dizisi (CSV ile parite — makine-okur kök-neden verisi). std::fprintf(f, " \"bolgeler\": ["); for (size_t i = 0; i < a.farkBolgeler.size(); ++i) { const FarkBolge& b = a.farkBolgeler[i]; std::fprintf(f, "%s\n {\"yon\": \"%s\", \"tip\": \"%s\", \"maksSapma\": %.5f, " "\"vertex\": %u, \"yaricap\": %.4f, \"merkez\": [%.4f, %.4f, %.4f]}", i ? "," : "", b.eklenen ? "eklenen" : "silinen", bolgeTipAdi(b), b.maksSapma, b.vertexSayi, b.yaricap, b.merkez.x, b.merkez.y, b.merkez.z); } std::fprintf(f, "%s]\n}\n", a.farkBolgeler.empty() ? "" : "\n "); std::fclose(f); yol = ad; return true; } } // namespace void farkPanelCiz(Sahne& sahne, analiz::AnalizSistemi& an, analiz::AnalizNesnesi& a, bool& acik) { const int tema = acikTemaBasla(); char baslik[128]; std::snprintf(baslik, sizeof(baslik), "CAD Farklar - Model Karşılaştırma###fark_%u", a.kimlik()); // NOT: AlwaysAutoResize + genişliği "kalan alan" (0) olan kart child'ları // geri-besleme döngüsü yapar (pencere her kare daralır) → sabit ilk boyut + // kısıt; kullanıcı kenardan boyutlandırır (web paneli de sabit 300px). ImGui::SetNextWindowSize({330, 660}, ImGuiCond_FirstUseEver); ImGui::SetNextWindowSizeConstraints({316, 300}, {900, 2000}); if (ImGui::Begin(baslik, &acik, ImGuiWindowFlags_NoCollapse)) { turkuazGlowCiz(); aeroGridCiz(); // camsı ızgara zemin (web DialogFiko dokusu) const auto hesapla = [&] { an.hesapla(a.kimlik()); }; // Boyamayı yalnız pencere YENİ açıldığında zorla (kenar tetik) — araç // çubuğundaki "Orijinal Modelleri Göster" toggle'ı her kare ezilmesin. // İlk açılışta: model atanmamışsa sahnedeki İKİ FARKLI mesh gövdesini otomatik // ata (eski=sondan bir önceki, yeni=son — genelde "Part-eski" + "Part-yeni") // ve HEMEN hesapla → kullanıcı elle seçmeden renkler anında sahnede belirir. if (!g_panelAcikti[a.kimlik()]) { a.aktif = true; g_panelAcikti[a.kimlik()] = true; if (!a.hedefGovde || !a.farkKarsiGovde || a.hedefGovde == a.farkKarsiGovde) { std::vector govdeler; for (Dugum& d : sahne.dugumler()) if (d.indeksSayi > 0 && !d.cpuKonum.empty() && !d.cpuIndeks.empty() && d.tip != DugumTip::Duzlem && d.tip != DugumTip::Eksen && d.tip != DugumTip::EksenSistemi && d.tip != DugumTip::Nokta && d.tip != DugumTip::Cizgi && d.tip != DugumTip::Sketch) govdeler.push_back(d.kimlik); if (govdeler.size() >= 2) { a.farkKarsiGovde = govdeler[govdeler.size() - 2]; // eski (A) a.hedefGovde = govdeler.back(); // yeni (B) a.bayatAyarla(true); hesapla(); } } else if (a.bayat()) { hesapla(); // zaten iki gövde seçili ama hesap eskimiş → tazele } } static std::string sonAktarim; // son export dosya yolu (tooltip bildirimi) // ---- Başlık: ⇄ CAD Farklar | süre + tier rozeti ---- // Koyu camsı zeminde AÇIK turkuaz (kartlar içindeki metin koyu kalır). ImGui::TextColored(ImVec4(0.35f, 0.95f, 0.90f, 1.0f), "⇄ CAD Farklar"); if (a.sonuc.hazir) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - 92.0f); ImGui::TextDisabled("| %.0fms", a.farkSureMs); ImGui::SameLine(); ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 rp = ImGui::GetCursorScreenPos(); const char* tier = a.farkGpu ? "GPU" : "CPU"; const ImVec2 tb = ImGui::CalcTextSize(tier); dl->AddRectFilled({rp.x, rp.y + 1}, {rp.x + tb.x + 8, rp.y + tb.y + 3}, a.farkGpu ? kYesil : IM_COL32(0x95, 0xa5, 0xa6, 255), 2.0f); dl->AddText({rp.x + 4, rp.y + 2}, IM_COL32_WHITE, tier); ImGui::Dummy({tb.x + 10, tb.y + 4}); } ImGui::Separator(); // ---- Model Tanımı ---- { kartBaslat("modelTanimKart"); bolumBaslik("Model Tanımı"); const float kolW = (ImGui::GetContentRegionAvail().x - 38.0f) * 0.5f; bool degisti = false; ImGui::BeginGroup(); degisti |= modelKutu(sahne, an, a, true, kolW); ImGui::EndGroup(); ImGui::SameLine(0, 5); ImGui::BeginGroup(); ImGui::Dummy({0, 14}); if (ImGui::Button("⇄##takas", {28, 24})) { std::swap(a.hedefGovde, a.farkKarsiGovde); degisti = true; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Modelleri yer değiştir"); ImGui::EndGroup(); ImGui::SameLine(0, 5); ImGui::BeginGroup(); degisti |= modelKutu(sahne, an, a, false, kolW); ImGui::EndGroup(); if (degisti) { a.bayatAyarla(true); a.sonuc.temizle(); // Native üstünlüğü: iki model de atanınca beklemeden hesapla (web'de // Önizle manuel; burada fark ANINDA görünür — Önizle yine yeniler). if (a.hedefGovde && a.farkKarsiGovde) hesapla(); } kartBitir(); } // Seçim uyarı bandı (web mor bant). if (an.secimAktif()) { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.97f, 0.94f, 0.99f, 1)); if (ImGui::BeginChild("secimBant", {0, 0}, ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY)) { ImGui::TextColored(ImVec4(0.61f, 0.35f, 0.71f, 1), "Modeli sahneden veya ürün ağacından seçin"); ImGui::SameLine(ImGui::GetContentRegionAvail().x - 16.0f); if (ImGui::SmallButton("×##secIptal")) an.secimBitir(); } ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::Dummy({0, 2}); } // ---- Karşılaştırma kipi + yerel eksen ---- { kartBaslat("kipKart"); int kip = a.farkGorselKip ? 1 : 0; if (ImGui::RadioButton("Geometrik Karşılaştırma", &kip, 0)) a.farkGorselKip = false; if (ImGui::RadioButton("Görsel Karşılaştırma", &kip, 1)) a.farkGorselKip = true; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Native motor iki kipte de GPU hızlandırmalı tam-doğruluk\n" "BVH karşılaştırması kullanır (web'in aksine kayıp yok)."); bool ye = a.farkYerelEksen; if (ImGui::Checkbox("Yerel eksen sistemini kullan", &ye)) a.farkYerelEksen = ye; if (ImGui::IsItemHovered()) ImGui::SetTooltip("Modeller kendi merkezlerine hizalanır —\nkonum farkı yok sayılır."); kartBitir(); } // ---- Geometrik / Görsel ayar paneli ---- if (!a.farkGorselKip) { { kartBaslat("geoKart"); bolumBaslik("Geometrik Karşılaştırma"); ImGui::TextDisabled("Tolerans (yeşil):"); ImGui::SameLine(160); ImGui::SetNextItemWidth(70); float ht = a.farkTolerans; if (ImGui::DragFloat("##hesapTol", &ht, 0.05f, 0.01f, 100.0f, "%.2f")) a.farkTolerans = ht; ImGui::SameLine(); ImGui::TextDisabled("mm"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Bu değerin altı 'OK' → YEŞİL. Üstü sapma renkleri."); // Renk skalası ± = deviation'ın tam mavi/kırmızıya doyduğu değer (GOM legend). // KÜÇÜLT → radius/kapatma gibi ince değişiklikler görünür; BÜYÜT → büyük odak. ImGui::TextDisabled("Renk skalası ±:"); ImGui::SameLine(160); ImGui::SetNextItemWidth(110); float gt = a.farkToleransCozulmus > 0 ? a.farkToleransCozulmus : 1.0f; const float gtMaks = std::max(a.farkMaks, 1.0f); if (ImGui::SliderFloat("##gorTol", >, 0.1f, gtMaks, "%.2f mm", ImGuiSliderFlags_Logarithmic)) a.farkToleransCozulmus = gt; // CANLI (push-constant, rebuild yok) if (ImGui::IsItemHovered()) ImGui::SetTooltip("Renk skalası ucu. KÜÇÜLT → ince farklar (radius/kapatma)\n" "tam renk; BÜYÜT → büyük farklara (silindir) odak."); ImGui::TextDisabled("Tür:"); ImGui::SameLine(160); ImGui::SetNextItemWidth(130); const char* turler[] = {"Eklenen + Silinen", "Sadece Eklenen", "Sadece Silinen"}; ImGui::Combo("##tur", &a.farkTip, turler, 3); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Hangi değişim tipi renklensin:\n" "• Eklenen = yeni modelde fazla malzeme (mavi, +)\n" "• Silinen = eski modelde olup kaldırılmış (kırmızı, −)"); kartBitir(); } } else { { kartBaslat("gorselKart"); bolumBaslik("Görsel Karşılaştırma"); ImGui::TextDisabled("Karşılaştırma hassasiyeti:"); ImGui::SameLine(160); ImGui::SetNextItemWidth(90); ImGui::SliderFloat("##gorselTol", &a.farkGorselHassasiyet, 0.1f, 5.0f, "%.1fmm"); ImGui::TextDisabled("Tür:"); ImGui::SameLine(160); ImGui::SetNextItemWidth(130); const char* turler[] = {"Eklenen + Silinen", "Sadece Eklenen", "Sadece Silinen"}; ImGui::Combo("##turV", &a.farkTip, turler, 3); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Hangi değişim tipi renklensin (mavi=eklenen, kırmızı=silinen)."); ImGui::TextDisabled("Görünen gövde:"); // farkTip'ten AYRI: hangi model çizilsin ImGui::RadioButton("Her İkisi", &a.farkViewMode, 0); ImGui::SameLine(); ImGui::RadioButton("Sadece Eski", &a.farkViewMode, 1); ImGui::SameLine(); ImGui::RadioButton("Sadece Yeni", &a.farkViewMode, 2); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Sahnede hangi model görünsün (Tür'den bağımsız):\n" "• Her İkisi = eski + yeni birlikte (ghost)\n" "• Sadece Eski = referans model • Sadece Yeni = sonuç model"); kartBitir(); } } // ---- Görüntüleme Ayarları ---- { kartBaslat("goruntulemeKart"); bolumBaslik("Görüntüleme Ayarları"); bool kg = a.farkKenarGoster; if (ImGui::Checkbox("Kenarları Göster", &kg)) a.farkKenarGoster = kg; bool gh = a.farkGorunum == 0; if (ImGui::Checkbox("Gradient Heatmap", &gh)) a.farkGorunum = gh ? 0 : 1; ImGui::BeginDisabled(true); bool refine = true; ImGui::Checkbox("Sınır Üçgen Refine", &refine); ImGui::EndDisabled(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Native motor her zaman tam BVH doğruluğunda —\n" "web'in yaklaşık sınır iyileştirmesine gerek yok."); bool sf = a.farkSadeceFark; if (ImGui::Checkbox("Sadece Farkları Göster", &sf)) a.farkSadeceFark = sf; // Overlay: eski modeli ghost saydamlıkta üst üste. Dugum* eskiD = a.farkKarsiGovde ? sahne.bul(a.farkKarsiGovde) : nullptr; bool ov = a.farkGhostOnceki >= 0.0f; ImGui::BeginDisabled(!eskiD); if (ImGui::Checkbox("Overlay (Üst Üste)", &ov) && eskiD) { if (ov) { a.farkGhostOnceki = eskiD->saydamlik; eskiD->saydamlik = a.farkGhostOpaklik; } else { eskiD->saydamlik = a.farkGhostOnceki; a.farkGhostOnceki = -1.0f; } } ImGui::EndDisabled(); // Renk satırı: Aynı / Silinen / Eklenen (+ Kenar, showEdges'te) + ↶. const auto renk = [](const char* ad, const char* id, Vec3f& c) { ImGui::TextDisabled("%s", ad); ImGui::SameLine(); ImGui::ColorEdit3(id, &c.x, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel); }; renk("Aynı:", "##rA", a.farkRenkAyni); ImGui::SameLine(0, 10); renk("Silinen:", "##rS", a.farkRenkSilinen); ImGui::SameLine(0, 10); renk("Eklenen:", "##rE", a.farkRenkEklenen); if (a.farkKenarGoster) renk("Kenar:", "##rK", a.farkRenkKenar); ImGui::SameLine(ImGui::GetContentRegionAvail().x - 52.0f); // Renk-körü güvenli palet (Wong/Nature Methods CVD-safe): mavi↔turuncu ayrımı // her tip renk körlüğünde ayırt edilir (kırmızı-yeşilden farklı), nötr gri merkez. if (ImGui::SmallButton("◑##renkCvd")) { a.farkRenkAyni = {0.86f, 0.86f, 0.86f}; // nötr açık gri (tolerans içi) a.farkRenkSilinen = {0.835f, 0.369f, 0.0f}; // vermilion (eksik, −) a.farkRenkEklenen = {0.0f, 0.447f, 0.698f}; // mavi (fazla, +) a.farkRenkKenar = {0.15f, 0.15f, 0.15f}; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Renk-körü güvenli palet (mavi↔turuncu)"); ImGui::SameLine(); if (ImGui::SmallButton("↶##renkSifir")) { a.farkRenkAyni = {0.80f, 0.74f, 0.34f}; // altın (web estetiği; yeşil metroloji ops.) a.farkRenkSilinen = {0.86f, 0.22f, 0.34f}; a.farkRenkEklenen = {0.20f, 0.55f, 0.86f}; a.farkRenkKenar = {0.15f, 0.15f, 0.15f}; } if (ImGui::IsItemHovered()) ImGui::SetTooltip("Varsayılan palete dön (altın merkez)"); kartBitir(); } // ---- Fark Filtre Ayarları (Sadece Farkları Göster açıkken — web paritesi) ---- if (a.farkSadeceFark) { kartBaslat("filtreKart"); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kBaslikMetin), "Fark Filtre Ayarları"); ImGui::SameLine(ImGui::GetContentRegionAvail().x - 130.0f); const auto preset = [&](const char* ad, int filtre, int derinlik, f32 ghost) { if (ImGui::SmallButton(ad)) { a.farkYuzeyFiltre = filtre; a.farkSinirDerinlik = derinlik; a.farkGhostOpaklik = ghost; } }; preset("Hassas", 5, 4, 0.20f); ImGui::SameLine(); preset("Standart", 10, 3, 0.20f); ImGui::SameLine(); preset("Hızlı", 20, 1, 0.10f); ImGui::Separator(); ImGui::TextDisabled("Yüzey filtresi:"); ImGui::SameLine(120); ImGui::SetNextItemWidth(110); ImGui::SliderInt("##yuzFiltre", &a.farkYuzeyFiltre, 1, 30, "×%d"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Küçük sapmaları (tessellation gürültüsü) bastırır.\n" "Büyük = daha temiz, yalnız gerçek değişimler kalır."); ImGui::TextDisabled("Sınır hassasiyeti:"); ImGui::SameLine(120); ImGui::SetNextItemWidth(110); ImGui::SliderInt("##sinirHass", &a.farkSinirDerinlik, 0, 5, "%d"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Ghost ↔ solid geçiş keskinliği. Yüksek = keskin sınır,\n" "düşük = yumuşak geçiş."); ImGui::TextDisabled("Ghost opaklığı:"); ImGui::SameLine(120); ImGui::SetNextItemWidth(110); if (ImGui::SliderFloat("##ghostOp", &a.farkGhostOpaklik, 0.0f, 0.5f, "%%%.0f", ImGuiSliderFlags_None)) { if (Dugum* eskiD = a.farkKarsiGovde ? sahne.bul(a.farkKarsiGovde) : nullptr) if (a.farkGhostOnceki >= 0.0f) eskiD->saydamlik = a.farkGhostOpaklik; // canlı } kartBitir(); } // ---- Önizle / Kapat (web mavi gradyan buton) ---- ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.20f, 0.60f, 0.86f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.16f, 0.50f, 0.73f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.10f, 0.32f, 0.46f, 1)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 1)); ImGui::BeginDisabled(!a.hedefGovde || !a.farkKarsiGovde); if (ImGui::Button("⟳ Önizle", {-92, 24})) hesapla(); ImGui::EndDisabled(); ImGui::PopStyleColor(4); ImGui::SameLine(); if (ImGui::Button("Kapat", {-1, 24})) acik = false; // ---- Sonuç kartı ---- if (a.sonuc.hazir) { kartBaslat("sonucKart"); const size_t nY = a.sonuc.deger.size(); const size_t nE = a.farkSonucEski.deger.size(); const size_t nT = nY + nE; const f32 eslesme = nT ? 100.0f * (1.0f - (f32)(a.farkEklenen + a.farkSilinen) / (f32)nT) : 100.0f; // Web eşikleri: ≥90 yeşil, 50–90 turuncu, <50 kırmızı; 20px 700. const ImU32 er = eslesme >= 90.0f ? kYesil : eslesme >= 50.0f ? kTuruncu : kKirmizi; char eb[24]; std::snprintf(eb, sizeof(eb), "%.1f%%", eslesme); ImGui::SetCursorPosX( (ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(eb).x * 2.0f) * 0.5f); ImGui::SetWindowFontScale(2.0f); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(er), "%s", eb); ImGui::SetWindowFontScale(1.0f); ImGui::SetCursorPosX( (ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("EŞLEŞME").x) * 0.5f); ImGui::TextDisabled("EŞLEŞME"); // Signed DIVERGING legend (metroloji, GOM/PolyWorks): −P99 kırmızı(içe) ← // yeşil(tolerans) → +P99 mavi(dışa). Binary kipte: Aynı/Silinen/Eklenen kutuları. ImDrawList* dl = ImGui::GetWindowDrawList(); if (a.farkGorunum == 0) { const ImVec2 gp = ImGui::GetCursorScreenPos(); const float gw = ImGui::GetContentRegionAvail().x; const int seg = 64; const auto lerp3 = [](Vec3f x, Vec3f y, f32 t) -> Vec3f { return {x.x + (y.x - x.x) * t, x.y + (y.y - x.y) * t, x.z + (y.z - x.z) * t}; }; const Vec3f merkez = a.farkRenkAyni; // sapma=0 (shader ile aynı, panelden) for (int i = 0; i < seg; ++i) { const f32 s = (f32)i / (seg - 1) * 2.0f - 1.0f; // [-1, +1] const f32 t = std::sqrt(std::abs(s)); // shader ile aynı sqrt akış const Vec3f c = s >= 0 ? lerp3(merkez, a.farkRenkEklenen, t) : lerp3(merkez, a.farkRenkSilinen, t); dl->AddRectFilled({gp.x + gw * i / seg, gp.y}, {gp.x + gw * (i + 1) / seg, gp.y + 10}, IM_COL32((int)(c.x * 255), (int)(c.y * 255), (int)(c.z * 255), 255)); } // Tik işaretleri: 5 çeyrek konumda (−, −½, 0, +½, +) ince çizgi (metroloji legend). for (int k = 0; k <= 4; ++k) { const float tx = gp.x + gw * (float)k / 4.0f; const bool orta = (k == 2); dl->AddLine({tx, gp.y - (orta ? 3.0f : 1.0f)}, {tx, gp.y + 10 + (orta ? 3.0f : 1.0f)}, IM_COL32(30, 34, 40, orta ? 220 : 140), orta ? 1.4f : 1.0f); } dl->AddRect(gp, {gp.x + gw, gp.y + 10}, IM_COL32(80, 84, 92, 180)); // çerçeve ImGui::Dummy({gw, 12}); const f32 maxR = std::max(a.farkToleransCozulmus, 0.001f); // renk skalası ± char mxb[32], yariB[32]; mesafeFmt(maxR, mxb, sizeof(mxb)); mesafeFmt(maxR * 0.5f, yariB, sizeof(yariB)); // Alt etiket satırı: −P ... −P/2 ... 0 ... +P/2 ... +P (birim mesafeFmt'ten: mm/m). // Metin renkleri PALETTEN akar (varsayılan kırmızı/mavi, CVD'de turuncu/mavi) → // legend her zaman sahnedeki renklerle tutarlı; yarı-uçlar alfa ile mat. const Vec3f& cS = a.farkRenkSilinen; const Vec3f& cE = a.farkRenkEklenen; ImGui::TextColored(ImVec4(cS.x, cS.y, cS.z, 1), "-%s", mxb); // içe (−) ImGui::SameLine(gw * 0.25f - ImGui::CalcTextSize(yariB).x * 0.5f); ImGui::TextColored(ImVec4(cS.x, cS.y, cS.z, 0.7f), "-%s", yariB); ImGui::SameLine((gw - ImGui::CalcTextSize("0").x) * 0.5f); ImGui::TextDisabled("0"); ImGui::SameLine(gw * 0.75f - ImGui::CalcTextSize(yariB).x * 0.5f); ImGui::TextColored(ImVec4(cE.x, cE.y, cE.z, 0.7f), "+%s", yariB); ImGui::SameLine(gw - ImGui::CalcTextSize(mxb).x - 8); ImGui::TextColored(ImVec4(cE.x, cE.y, cE.z, 1), "+%s", mxb); // dışa (+) } else { const auto lejant = [&](Vec3f c, const char* ad) { const ImVec2 lp = ImGui::GetCursorScreenPos(); dl->AddRectFilled({lp.x, lp.y + 2}, {lp.x + 20, lp.y + 14}, IM_COL32((int)(c.x * 255), (int)(c.y * 255), (int)(c.z * 255), 255), 2.0f); ImGui::Dummy({22, 16}); ImGui::SameLine(); ImGui::TextDisabled("%s", ad); }; ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - 250.0f) * 0.5f); lejant(a.farkRenkAyni, "Aynı"); ImGui::SameLine(0, 16); lejant(a.farkRenkSilinen, "Silinen"); ImGui::SameLine(0, 16); lejant(a.farkRenkEklenen, "Eklenen"); } // İstatistik satırları. const auto satir = [&](const char* etiket, const char* deger, ImU32 renk) { ImGui::TextDisabled("%s", etiket); ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(deger).x); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(renk), "%s", deger); }; char v[48]; kisaFmt(a.farkSilinen, v, sizeof(v)); satir("Silinen (A):", v, kSilinenMetin); kisaFmt(a.farkEklenen, v, sizeof(v)); satir("Eklenen (B):", v, kEklenenMetin); if (a.farkP99 > 0.0f) { ImGui::Separator(); mesafeFmt(a.farkP99, v, sizeof(v)); satir("Maks. sapma (P99):", v, IM_COL32(0x33, 0x33, 0x33, 255)); mesafeFmt(a.farkOrt, v, sizeof(v)); satir("Ort. sapma:", v, IM_COL32(0x33, 0x33, 0x33, 255)); mesafeFmt(a.farkRms, v, sizeof(v)); satir("RMS sapma:", v, IM_COL32(0x33, 0x33, 0x33, 255)); // karekök-ortalama (yüzey metriği) mesafeFmt(a.farkStd, v, sizeof(v)); satir("Std. sapma:", v, IM_COL32(0x33, 0x33, 0x33, 255)); // ±3σ kapsam bandı (SPC/muayene: sapmaların ~%99.7'sinin kaldığı yarı-genişlik). mesafeFmt(a.farkStd * 3.0f, v, sizeof(v)); satir("±3σ (%99.7):", v, IM_COL32(0x33, 0x33, 0x33, 255)); // Sapma dağılım histogramı (GOM/PolyWorks) — motorda hesaplı, O(kova) çizim. if (!a.farkHistogram.empty()) { u32 tepe = 1; for (u32 c : a.farkHistogram) tepe = std::max(tepe, c); ImDrawList* hl = ImGui::GetWindowDrawList(); const ImVec2 hp = ImGui::GetCursorScreenPos(); const float hw = ImGui::GetContentRegionAvail().x, hh = 34.0f; const int nk = (int)a.farkHistogram.size(); hl->AddRectFilled(hp, {hp.x + hw, hp.y + hh}, IM_COL32(20, 24, 30, 90), 3.0f); const Vec3f& cE = a.farkRenkEklenen; const ImU32 cub = IM_COL32((int)(cE.x * 255), (int)(cE.y * 255), (int)(cE.z * 255), 220); for (int i = 0; i < nk; ++i) { const float yuk = hh * (float)a.farkHistogram[i] / (float)tepe; const float x0 = hp.x + hw * i / nk, x1 = hp.x + hw * (i + 1) / nk; hl->AddRectFilled({x0, hp.y + hh - yuk}, {x1 - 1.0f, hp.y + hh}, cub); } // Renk skalası ucu (farkToleransCozulmus) dikey işaret: dağılımın neresinde // skalanın doyduğu (bunun ötesi tam renk) — GOM legend-histogram korelasyonu. const float mks = std::max(a.farkMaks, 1e-6f); if (a.farkToleransCozulmus > 0.0f && a.farkToleransCozulmus < mks) { const float mx = hp.x + hw * std::min(a.farkToleransCozulmus / mks, 1.0f); hl->AddLine({mx, hp.y}, {mx, hp.y + hh}, IM_COL32(240, 190, 70, 230), 1.4f); } // Hover: fare hangi kovanın üstünde → aralık + adet tooltip (GOM interaktif histogram). if (ImGui::IsMouseHoveringRect(hp, {hp.x + hw, hp.y + hh})) { const int ki = std::clamp((int)((ImGui::GetMousePos().x - hp.x) / hw * nk), 0, nk - 1); const float ad = a.farkHistogramAdim > 0 ? a.farkHistogramAdim : mks / nk; ImGui::SetTooltip("%.3f – %.3f mm\n%u vertex", ki * ad, (ki + 1) * ad, a.farkHistogram[ki]); } ImGui::Dummy({hw, hh + 2}); ImGui::TextDisabled("Sapma dağılımı (0 → maks, %d kova) · ⟂ skala ucu", nk); } } // Vertex bilgisi (web: "A: 65.012 v" iki uçta). char av[32], bv[32]; binlikFmt(nE, av, sizeof(av)); binlikFmt(nY, bv, sizeof(bv)); ImGui::TextDisabled("A: %s v", av); char bvSat[48]; std::snprintf(bvSat, sizeof(bvSat), "B: %s v", bv); ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(bvSat).x); ImGui::TextDisabled("%s", bvSat); // Araç çubuğu: 7 ikon buton (web sırası + işlevleri). ImGui::Dummy({0, 2}); ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - 7 * 28.0f) * 0.5f); const auto arac = [&](const char* ikon, const char* tip, bool aktifRozet) { if (aktifRozet) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.91f, 0.96f, 0.99f, 1)); const bool bas = ImGui::Button(ikon, {24, 22}); if (aktifRozet) ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", tip); ImGui::SameLine(0, 4); return bas; }; if (arac("⬢", "Orijinal Modelleri Göster", !a.aktif)) a.aktif = !a.aktif; if (arac("▦", "Kenarları Aç/Kapat", a.farkKenarGoster)) a.farkKenarGoster = !a.farkKenarGoster; if (arac("⊕", "Farklara Odaklan", false)) { farkTurBaslat(a.kimlik(), (float)ImGui::GetTime()); farkTur().idx = -1; farkTur().otomatik = false; } if (arac("🎯", "En Kritik Bölgeye Git", false) && !a.farkBolgeler.empty()) { farkTurBaslat(a.kimlik(), (float)ImGui::GetTime()); farkTur().idx = 0; // bölgeler maks-sapma azalan sıralı → 0 = en kritik farkTur().otomatik = false; // orada dursun (otomatik ilerleme yok) } if (arac("▥", "Karşılaştırma Penceresi", false)) farkKarsilastirmaAc(a.kimlik()); if (arac("⬇", "Sonuçları Dışa Aktar (JSON)", false)) jsonOzetYaz(a, sonAktarim); if (arac("🗎", "Sapma Raporu (CSV)", false)) csvRaporYaz(a, sonAktarim); if (arac("🗑", "Temizle", false)) { if (Dugum* eskiD = a.farkKarsiGovde ? sahne.bul(a.farkKarsiGovde) : nullptr) if (a.farkGhostOnceki >= 0.0f) { eskiD->saydamlik = a.farkGhostOnceki; a.farkGhostOnceki = -1.0f; } a.hedefGovde = 0; a.farkKarsiGovde = 0; a.sonuc.temizle(); a.farkSonucEski.temizle(); a.farkBolgeler.clear(); } ImGui::NewLine(); if (!sonAktarim.empty()) ImGui::TextDisabled("Kaydedildi: %s", sonAktarim.c_str()); // Bölge tipi dağılımı (AABB-oran sezgisi): kaç oyuk/çıkıntı/kenar/yüzey. if (!a.farkBolgeler.empty()) { int say[3][2] = {{0, 0}, {0, 0}, {0, 0}}; // [tip][eklenen?] for (const FarkBolge& b : a.farkBolgeler) say[(int)b.tip][b.eklenen ? 1 : 0]++; char oz[160]; std::snprintf(oz, sizeof(oz), "%d oyuk/delik · %d çıkıntı · %d kenar · %d yüzey", say[0][0], say[0][1], say[1][0] + say[1][1], say[2][0] + say[2][1]); ImGui::TextDisabled("%s", oz); } // Native bonus: 🎬 sinematik tur (web'de yok — bölgelere kamera uçuşu). if (!a.farkBolgeler.empty()) { const bool turAktif = farkTur().analiz == a.kimlik(); if (ImGui::Button(turAktif ? "⏹ Turu Durdur" : "🎬 Sinematik Tur", {-1, 0})) { if (turAktif) farkTurDurdur(); else farkTurBaslat(a.kimlik(), (float)ImGui::GetTime()); } } kartBitir(); } if (!a.sonuc.hazir) { if (ImGui::SmallButton("↻ Listeyi Yenile")) {} // web paritesi (liste her kare taze) } } ImGui::End(); acikTemaBitir(tema); // Kapanış: SAHNE BOYAMASI KALIR (a.aktif=true bırakılır — kullanıcı fark rengini // panel kapalıyken de görsün; kapatmak için ürün ağacından analizi sil veya ⬢ // "Orijinal Modelleri Göster"). Yalnız ghost saydamlığı + tur geri alınır. if (!acik) { g_panelAcikti[a.kimlik()] = false; // yeniden açılışta kenar-tetik çalışsın if (a.farkGhostOnceki >= 0.0f) { if (Dugum* eskiD = a.farkKarsiGovde ? sahne.bul(a.farkKarsiGovde) : nullptr) eskiD->saydamlik = a.farkGhostOnceki; a.farkGhostOnceki = -1.0f; } if (farkTur().analiz == a.kimlik()) farkTurDurdur(); } } } // namespace fiko