#include "fiko/analiz/analiz_sistemi.hpp" #include #include #include #include #include "fiko/analiz/analiz_hesap.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/gorev.hpp" #include "fiko/geometri/brep.hpp" #include "fiko/render/compute_baglam.hpp" #include "fiko/render/vulkan_baglam.hpp" #include "fiko/core/gunluk.hpp" #include "fiko/sahne/fark_hesap.hpp" #include "fiko/sahne/isin_bvh.hpp" #include "fiko/sahne/sahne.hpp" namespace fiko::analiz { namespace { // Sahne'nin OZEL (mutable isinBvh) alani — diger modullerden erisilemez. Bu yuzden // BVH burada on-demand kurulur. DIKKAT: isinBvhKur shared_ptr DONDURUR; gecici // nesne cagidan cikinca sayac sifirlanir ve BVH YOK OLUR. Bu yuzden shared_ptr // static olarak tutulur (kalinlik analizi ana disiplinden bir kez cagrilir). // cpuKonum/cpuIndeks'i degistiren her yol onceden bvhIsIptal() cagirir // (CLAUDE.md kuralı) — yeniden kurulum ayni tamponlar uzerinden yapilir. const IsinBvh* sahneIsinBvh(const Dugum& d) { if (d.cpuKonum.empty() || d.cpuIndeks.empty()) return nullptr; static std::shared_ptr tutulan; tutulan = isinBvhKur(d.cpuKonum, d.cpuIndeks); return tutulan.get(); } } // namespace namespace { Vec3f birimNormal(Vec3f v) { return v.uzunlukKare() > 1e-8f ? v.birim() : Vec3f{0, 0, 1}; } // ---- Kalinlik CPU ornekleme (saf; senkron fallback + async worker ORTAK) ---- // Ayar degerleri (yuzMin/izgara/inceEsik) parametre: worker global ayara dokunmaz. struct KalinlikCpuSonuc { std::vector alan; // [ordinal] = kalinlik; -1 notr f64 mn = 1e6, mx = 0; u32 ince = 0, ornekli = 0, maksOrd = 0; }; KalinlikCpuSonuc kalinlikCpuHesap(const geometri::BrepGovde& g, bool yuzMin, u32 izgara, f32 inceEsik) { KalinlikCpuSonuc s; for (u32 ord = 1;; ++ord) { geometri::YuzBilgi yb; if (!g.yuzBilgi(ord, yb)) break; s.maksOrd = ord; } s.alan.assign(s.maksOrd + 1, -1.0f); if (s.maksOrd == 0) return s; // TEK gecis, govde basina TEK BRepIntCurveSurface_Inter Load (govdeKalinlikOrnekle). // Onceki yol her YUZ icin Load ediyordu: 66 yuzde 66 kurulum — hesabin buyuk // payi. Simdi kurulum 1, isin sayisi ayni. std::vector np; std::vector nk; std::vector nord; np.reserve(s.maksOrd * 16u); nk.reserve(s.maksOrd * 16u); nord.reserve(s.maksOrd * 16u); if (!geometri::govdeKalinlikOrnekle(g, izgara, 1e5, np, nk, nord)) return s; // Yuz basina topla (nord sirali → tek gecis, yeniden tahsis yok). // Ortalama; geometri::kalinlikRapor ile AYNI filtreyi uygular: govde kosesine // yakin orneklerde ic-yonlu isin komsu YAN duvara capraz vurur (kabuk kenar seridi) // -> bu ornekler gecerlidir ama o noktanin yerel cidarini temsil etmez; ham // ortalama kirilma degeri uretir (olculmus: min=2mm dogru, ort=7.5mm). Yalniz // gosterge olan ortalamayi minimuma yakin (<=1.5x) kume aliriz; min/ornek sayisi // zaten TUM ornekleri yansitir. u32 i = 0; while (i < nord.size()) { const u32 ord = nord[i]; u32 j = i; f64 top = 0, yMin = 1e30; while (j < nord.size() && nord[j] == ord) { top += nk[j]; yMin = std::min(yMin, nk[j]); ++j; } f64 ov; if (yuzMin) { ov = yMin; } else { const f64 esik = yMin * 1.5; f64 fTop = 0; u32 fSay = 0; for (size_t q = i; q < j; ++q) if (nk[q] <= esik) { fTop += nk[q]; ++fSay; } ov = fSay ? fTop / (f64)fSay : top / (f64)(j - i); } if (ord <= s.maksOrd) { s.alan[ord] = (f32)ov; s.mn = std::min(s.mn, ov); s.mx = std::max(s.mx, ov); if (ov < inceEsik) ++s.ince; ++s.ornekli; } i = j; } return s; } // ---- CPU renk rampasi (renkRampa.glsl ikizi — kesit kapagi vertex renkleri) ---- f32 kirp01(f32 t) { return t < 0.f ? 0.f : (t > 1.f ? 1.f : t); } Vec3f cpuJet(f32 t) { t = kirp01(t); return {kirp01(1.5f - std::abs(4.f * t - 3.f)), kirp01(1.5f - std::abs(4.f * t - 2.f)), kirp01(1.5f - std::abs(4.f * t - 1.f))}; } Vec3f cpuTurbo(f32 t) { t = kirp01(t); const f32 t2 = t * t, t3 = t2 * t, t4 = t2 * t2, t5 = t4 * t, t6 = t4 * t2; const auto k = [&](f32 c0, f32 c1, f32 c2, f32 c3, f32 c4, f32 c5, f32 c6) { return kirp01(c0 + c1 * t + c2 * t2 + c3 * t3 + c4 * t4 + c5 * t5 + c6 * t6); }; return {k(0.1140f, 6.7164f, -66.094f, 228.766f, -334.83f, 218.766f, -52.889f), k(0.0628f, 3.1822f, -4.9279f, 25.005f, -69.315f, 67.523f, -21.549f), k(0.2248f, 7.5715f, -10.089f, -91.541f, 288.586f, -305.20f, 110.514f)}; } f32 puruzsuzAdim(f32 a, f32 b, f32 x) { const f32 t = kirp01((x - a) / (b - a + 1e-12f)); return t * t * (3.f - 2.f * t); } Vec3f karisim(Vec3f a, Vec3f b, f32 t) { return {a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; } Vec3f cpuSigned(f32 s, f32 bant) { s = s < -1.f ? -1.f : (s > 1.f ? 1.f : s); const Vec3f kirmizi{0.90f, 0.20f, 0.18f}, sari{0.96f, 0.82f, 0.24f}, yesil{0.30f, 0.74f, 0.42f}; bant = std::max(bant, 0.02f); return s < 0.f ? karisim(kirmizi, sari, puruzsuzAdim(-1.f, -bant, s)) : karisim(sari, yesil, puruzsuzAdim(bant, 1.f, s)); } Vec3f cpuTolerans(f32 t) { const f32 d = std::abs(kirp01(t) - 0.5f) * 2.f; const Vec3f yesil{0.24f, 0.74f, 0.36f}, sari{0.96f, 0.84f, 0.26f}, kirm{0.90f, 0.22f, 0.20f}; return d < 0.5f ? karisim(yesil, sari, d * 2.f) : karisim(sari, kirm, (d - 0.5f) * 2.f); } Vec3f cpuRampaRenk(int tip, f32 t) { if (tip == 1) return cpuTurbo(t); if (tip == 3) return cpuTolerans(t); return cpuJet(t); } f32 cpuAlanNorm(f32 deger, f32 mn, f32 mx) { const f32 d = mx - mn; return d > 1e-6f ? kirp01((deger - mn) / d) : 0.f; } // Nokta → polyline seti en kisa mesafe (hepsi ayni duzlemde; kapak yerel cidar // yaklastirmasi: cidar ≈ 2 × kontur mesafesi). f64 konturMesafe(const Vec3d& p, const std::vector>& konturlar) { f64 enKisaK = 1e30; for (const auto& poly : konturlar) for (size_t i = 0; i + 1 < poly.size(); ++i) { const Vec3d a = poly[i], b = poly[i + 1]; const Vec3d ab{b.x - a.x, b.y - a.y, b.z - a.z}; const Vec3d ap{p.x - a.x, p.y - a.y, p.z - a.z}; const f64 payda = ab.x * ab.x + ab.y * ab.y + ab.z * ab.z; f64 t = payda > 1e-18 ? (ap.x * ab.x + ap.y * ab.y + ap.z * ab.z) / payda : 0.0; t = t < 0 ? 0 : (t > 1 ? 1 : t); const Vec3d f{p.x - (a.x + ab.x * t), p.y - (a.y + ab.y * t), p.z - (a.z + ab.z * t)}; const f64 kk = f.x * f.x + f.y * f.y + f.z * f.z; if (kk < enKisaK) enKisaK = kk; } return enKisaK < 1e29 ? std::sqrt(enKisaK) : -1.0; } // Kaba tekduze izgara: yerel yuzey vertex'lerine en yakin komsu sorgusu // (kapak → en yakin yuzey degeri orneklemesi; kaba ama okunur). struct VertexIzgara { const std::vector* noktalar = nullptr; std::unordered_map> hucre; f32 boyut = 1.f; Vec3f taban{}; static u64 anahtar(i64 x, i64 y, i64 z) { return (u64)(x & 0x1FFFFF) | ((u64)(y & 0x1FFFFF) << 21) | ((u64)(z & 0x1FFFFF) << 42); } void kur(const std::vector& np, f32 hucreBoyut, Vec3f min) { noktalar = &np; boyut = std::max(hucreBoyut, 1e-4f); taban = min; hucre.clear(); hucre.reserve(np.size() / 4 + 1); for (u32 i = 0; i < (u32)np.size(); ++i) { const Vec3f& p = np[i]; hucre[anahtar((i64)((p.x - taban.x) / boyut), (i64)((p.y - taban.y) / boyut), (i64)((p.z - taban.z) / boyut))].push_back(i); } } // En yakin vertex indeksi; bulunamazsa (bos izgara) -1. i64 enYakin(const Vec3f& p) const { if (!noktalar || noktalar->empty()) return -1; const i64 cx = (i64)((p.x - taban.x) / boyut), cy = (i64)((p.y - taban.y) / boyut), cz = (i64)((p.z - taban.z) / boyut); i64 enIyi = -1; f32 enKisa = 1e30f; for (int yari = 1; yari <= 4 && enIyi < 0; yari += 1) { for (i64 dx = -yari; dx <= yari; ++dx) for (i64 dy = -yari; dy <= yari; ++dy) for (i64 dz = -yari; dz <= yari; ++dz) { const auto it = hucre.find(anahtar(cx + dx, cy + dy, cz + dz)); if (it == hucre.end()) continue; for (u32 i : it->second) { const Vec3f f = (*noktalar)[i] - p; const f32 kk = f.uzunlukKare(); if (kk < enKisa) { enKisa = kk; enIyi = (i64)i; } } } } return enIyi; } }; // Draft/Undercut icin varsayilan renk bantlari (SW Plastics kalibi). void draftAraliklar(AnalizNesnesi& a, bool undercut) { a.araliklar.clear(); if (undercut) { a.araliklar.push_back({-90.f, -0.001f, {0.90f, 0.20f, 0.20f}, "Undercut (ters)"}); a.araliklar.push_back({-0.001f, 90.f, {0.35f, 0.75f, 0.40f}, "Çekilebilir"}); } else { a.araliklar.push_back({-90.f, -0.3f, {0.90f, 0.20f, 0.20f}, "Ters Açı"}); a.araliklar.push_back({-0.3f, 0.3f, {0.95f, 0.80f, 0.25f}, "Dik Duvar"}); a.araliklar.push_back({0.3f, 90.f, {0.35f, 0.75f, 0.40f}, "İyi Açı"}); } } // GPU per-vertex et kalınlığı (wall.comp ray) → per-yüz ortalamaya indirgeme. // Başarısızlıkta false döner (çağıran CPU analitik yoluna düşer). Eğri yüzler // dahil TÜM yüzler örneklenir (CPU yolu silindir/torus'u nötr bırakıyordu). // hesapla() main-thread'de koşar → grafik kuyruğuyla yarış yok (ComputeBaglam). bool gpuKalinlikPerYuz(VulkanBaglam& vb, const Dugum& d, u32 maksOrd, std::vector& alan, f64& mn, f64& mx, std::vector* perVertexCikti) { const u32 n = (u32)d.cpuKonum.size(); if (n == 0 || d.cpuIndeks.empty() || d.cpuNormal.size() != n || d.cpuAlt.size() != n) return false; if (vb.aygitKayipMi()) return false; // BVH ZORUNLU: wall.comp artik brute-force DEGIL, BVH uzerinden geziyor (O(log n)). // BVH yoksa tum isin kacirilir → duvar yok (-1) donerdi, yani ESKI O(V×T) yolu // sessizce "hepsi kaci" gibi gorunurdu. BVH hazir degilse CPU yoluna dus. const IsinBvh* bvh = sahneIsinBvh(d); if (!bvh || bvh->dallar.empty() || bvh->ucgen.empty()) return false; struct V4 { f32 x, y, z, w; }; std::vector vpos(n), vnrm(n); for (u32 i = 0; i < n; ++i) { vpos[i] = {d.cpuKonum[i].x, d.cpuKonum[i].y, d.cpuKonum[i].z, 0}; vnrm[i] = {d.cpuNormal[i].x, d.cpuNormal[i].y, d.cpuNormal[i].z, 0}; } // Ucgenler BVH YAPRAK SIRASINA duzlestirilir (shader dogrudan diziye indeksler, // ayri bir dolaylama tabani yok) — isin_alani.cpp ile ayni duzen. const u32 tc = (u32)bvh->ucgen.size(); std::vector tris((size_t)tc * 3ull); for (u32 i = 0; i < tc; ++i) { const u32 taban = bvh->ucgen[i] * 3u; if (taban + 2 >= d.cpuIndeks.size()) return false; for (u32 k = 0; k < 3; ++k) { const Vec3f& p = d.cpuKonum[d.cpuIndeks[taban + k]]; tris[(size_t)i * 3 + k] = {p.x, p.y, p.z, 0}; } } // BVH dallari (32B, std430: min.xyz + a, max.xyz + bYaprak). struct GpuDal { f32 minX, minY, minZ; u32 a; f32 maksX, maksY, maksZ; u32 bYaprak; }; constexpr u32 kYaprakBit = 0x80000000u; std::vector dallar(bvh->dallar.size()); for (size_t i = 0; i < bvh->dallar.size(); ++i) { const IsinBvh::Dal& b = bvh->dallar[i]; GpuDal g{}; if (b.kutu.gecerliMi()) { g.minX = b.kutu.asgari.x; g.minY = b.kutu.asgari.y; g.minZ = b.kutu.asgari.z; g.maksX = b.kutu.azami.x; g.maksY = b.kutu.azami.y; g.maksZ = b.kutu.azami.z; } else { // bozuk kutu: hicbir isin kesismesin g.minX = g.minY = g.minZ = 1e30f; g.maksX = g.maksY = g.maksZ = -1e30f; } g.a = b.a; g.bYaprak = b.b | (b.yaprak ? kYaprakBit : 0u); dallar[i] = g; } const f32 enBuyuk = d.yerelSinir.gecerliMi() ? d.yerelSinir.kosegen() * 1.5f : 1e5f; struct Itme { u32 vertexSayi, dalSayi; f32 enBuyuk, eps; }; const Itme itme{n, (u32)dallar.size(), enBuyuk, enBuyuk * 1e-5f}; SsboTampon sPos(vb, n * 16ull), sNrm(vb, n * 16ull); SsboTampon sDal(vb, dallar.size() * 32ull); SsboTampon sTri(vb, (size_t)tc * 48ull), sOut(vb, n * 4ull); if (!sPos.gecerli() || !sNrm.gecerli() || !sDal.gecerli() || !sTri.gecerli() || !sOut.gecerli()) return false; sPos.yaz(vpos.data(), n * 16ull); sNrm.yaz(vnrm.data(), n * 16ull); sDal.yaz(dallar.data(), dallar.size() * 32ull); sTri.yaz(tris.data(), (size_t)tc * 48ull); ComputeBoruHatti boru(vb, "shader/wall.comp.spv", 5, sizeof(Itme)); if (!boru.gecerli()) return false; boru.bagla({sPos.tampon(), sNrm.tampon(), sDal.tampon(), sTri.tampon(), sOut.tampon()}); ComputeBaglam comp(vb); const u32 grup = (n + 63u) / 64u; if (!comp.calis([&](VkCommandBuffer kb) { boru.dispatchKaydet(kb, &itme, sizeof(itme), grup, 1, 1); })) return false; std::vector kal(n); sOut.oku(kal.data(), n * 4ull); // Per-vertex → per-yüz indirgeme (mevcut PerYuz render akışı değişmeden kalır). // MIN = en-kötü-durum cidar (kalinlikYuzMin kapalı → eski ortalama davranışı). // Ortalama da CPU yoluyla AYNI 1.5x filtresinden geçer: govde kosesine yakin // vertexler ic-yonlu isini komsu YAN duvara capraz vurur (kabuk kenar seridi) — // bu vertexler gecerli ama yuzun yerel cidarini temsil etmez; ham ortalama // kirilma degeri verir (CPU'da olculdu: min=2mm, ort=7.5mm). Iki yolun rapor // sayilari ayni govdede ayni kalmali. const bool yuzMin = kalinlikYuzMin(); std::vector top(maksOrd + 1, 0.0); std::vector enAz(maksOrd + 1, 1e30); std::vector say(maksOrd + 1, 0); std::vector> yuzDeger(maksOrd + 1); for (u32 i = 0; i < n; ++i) { const f32 k = kal[i]; if (k <= 0.0f || k >= enBuyuk * 0.99f) continue; // -1 = duvar yok / kaçak const u32 ord = d.cpuAlt[i]; if (ord == 0 || ord > maksOrd) continue; top[ord] += k; enAz[ord] = std::min(enAz[ord], (f64)k); ++say[ord]; yuzDeger[ord].push_back(k); } bool var = false; for (u32 o = 1; o <= maksOrd; ++o) if (say[o]) { f64 ov; if (yuzMin) { ov = enAz[o]; } else { const f64 esik = enAz[o] * 1.5; f64 fTop = 0; u32 fSay = 0; for (const f32 v : yuzDeger[o]) if ((f64)v <= esik) { fTop += v; ++fSay; } ov = fSay ? fTop / (f64)fSay : top[o] / say[o]; } alan[o] = (f32)ov; mn = std::min(mn, ov); mx = std::max(mx, ov); var = true; } // PER-VERTEX alan (ısı haritası): wall.comp zaten vertex başına ölçüyordu, // per-yüz ortalama/min ALIP ATIYORDUK → duvar tek renk görünüyordu. İstenirse // ham vektörü de veriyoruz; geçersiz (-1/kaçak) vertex kendi yüzünün indirgenmiş // değeriyle doldurulur (delik/leke olmasın, rampa süreklidir). if (var && perVertexCikti) { perVertexCikti->assign(n, -1.0f); for (u32 i = 0; i < n; ++i) { const f32 k = kal[i]; const u32 ord = d.cpuAlt[i]; const bool gecerli = k > 0.0f && k < enBuyuk * 0.99f; if (gecerli) (*perVertexCikti)[i] = k; else if (ord >= 1 && ord <= maksOrd && say[ord]) (*perVertexCikti)[i] = alan[ord]; if (gecerli) { mn = std::min(mn, (f64)k); mx = std::max(mx, (f64)k); } } } return var; } } // namespace AnalizSistemi::AnalizSistemi(Cekirdek& cekirdek, Sahne& sahne) : cekirdek_(cekirdek), sahne_(sahne) { // RecomputeMotor bagi: girdi geometri/donusum degisince ilgili analiz bayatlar. cekirdek_.olaylar().abone([this](const BrepDegisti& o) { bayatla(o.varlik); }); cekirdek_.olaylar().abone( [this](const DonusumDegisti& o) { bayatla(o.varlik); }); } AnalizSistemi::~AnalizSistemi() = default; void AnalizSistemi::pencereAc(u32 kimlik) { if (std::find(acikPencereler.begin(), acikPencereler.end(), kimlik) == acikPencereler.end()) acikPencereler.push_back(kimlik); } void AnalizSistemi::pencereKapat(u32 kimlik) { std::erase(acikPencereler, kimlik); } Varlik AnalizSistemi::grupAl(Varlik& saklama, const std::string& ad) { if (saklama != 0 && cekirdek_.nesne(saklama)) return saklama; saklama = sahne_.grupEkle(ad, VARLIK_YOK); return saklama; } u32 AnalizSistemi::ekle(AnalizTip tip, u32 hedefGovde) { const Varlik grup = grupAl(analizGrup_, "Analizler"); const Varlik v = cekirdek_.dunya().olustur(); auto nesne = std::make_unique(v, tip); char ad[64]; std::snprintf(ad, sizeof(ad), "%s %u", analizTipAdi(tip), (unsigned)(analizler_.size() + 1)); nesne->adAyarla(ad); nesne->hedefGovde = hedefGovde; if (tip == AnalizTip::Draft) draftAraliklar(*nesne, false); else if (tip == AnalizTip::Undercut) draftAraliklar(*nesne, true); else if (tip == AnalizTip::KeskinKenar) { nesne->keskinEsikDeg = keskinVarsayilanEsikDeg; // Ozellik panelinden "Aktif" degisince kenar dugumleri tazelensin (bilinen // sinir kapandi; kare-basi maliyet yok — yalniz yazim aninda kosar). nesne->aktifDegisti = [this, v](bool) { keskinGorselGuncelle(v); }; } cekirdek_.nesneEkle(std::move(nesne)); cekirdek_.dunya().ekle(v, AdBilesen{ad}); cekirdek_.dunya().ekle(v, HiyerarsiBilesen{grup}); analizler_.push_back(v); aktifAnaliz = v; cekirdek_.olaylar().yayinla(AnalizEklendi{v, (u32)tip}, ad); hesapla(v); return v; } bool AnalizSistemi::sil(u32 kimlik) { auto it = std::find(analizler_.begin(), analizler_.end(), kimlik); if (it == analizler_.end()) return false; analizler_.erase(it); pencereKapat(kimlik); if (aktifAnaliz == kimlik) aktifAnaliz = 0; if (auto kd = keskinDugumler_.find(kimlik); kd != keskinDugumler_.end()) { for (u32 dg : kd->second) if (dg) sahne_.sil(dg); keskinDugumler_.erase(kd); } // Kosan async is: iptal + nesil ilerlet (bitmis-ama-pompalanmamis sonuc da coper). if (auto ai = asyncIs_.find(kimlik); ai != asyncIs_.end()) { cekirdek_.gorevler().iptalEt(ai->second); asyncIs_.erase(ai); } ++asyncNesil_[kimlik]; cekirdek_.dunya().yokEt(kimlik); cekirdek_.olaylar().yayinla(AnalizSilindi{kimlik}); return true; } AnalizNesnesi* AnalizSistemi::bul(u32 kimlik) { return dynamic_cast(cekirdek_.nesne(kimlik)); } void AnalizSistemi::bayatla(u32 govde) { for (u32 k : analizler_) if (AnalizNesnesi* a = bul(k); a && (a->hedefGovde == govde || a->hedefGovde == 0)) { // Kosan async isin sonucu artik bayat: nesil ilerlet (bitince cope atar). if (auto ai = asyncIs_.find(k); ai != asyncIs_.end()) { cekirdek_.gorevler().iptalEt(ai->second); asyncIs_.erase(ai); ++asyncNesil_[k]; } if (!a->bayat()) { a->bayatAyarla(true); cekirdek_.olaylar().yayinla(AnalizBayatladi{k}); } } for (u32 k : kesitler_) if (KesitDuzlemi* kd = kesitBul(k)) { // Kendi kapak/profil dugumumuzun eklenmesi/degisimi tetiklemesin (dongu). if (kd->kapakDugum == govde || kd->profilDugum == govde) continue; kesitEgriGuncelle(*kd); kesitKapakGuncelle(*kd); } } // --- Bir govdenin hedef Dugum'unu bul (0 = ilk gorunur brep'li govde) --- static Dugum* hedefDugumBul(Sahne& sahne, u32 hedefGovde) { if (hedefGovde) return sahne.bul(hedefGovde); for (Dugum& d : sahne.dugumler()) if (d.brep && d.brep->gecerliMi() && d.indeksSayi > 0 && sahne.agacGorunur(d.kimlik)) return &d; return nullptr; } // CAD Fark: hedef (YENI model) vs farkKarsiGovde (ESKI/referans) — çift-yön // Hausdorff (farkHesapla; GPU compute → CPU fallback). PerVertex alan = yeni model // vertexlerinin eski yüzeye mesafesi → mod-3 rampa ısı-haritası. MESH-bazlı. bool AnalizSistemi::farkHesaplaDal(AnalizNesnesi* a, Dugum* d, u32 kimlik) { a->rapor.clear(); a->sonuc.temizle(); if (!d || d->cpuKonum.empty() || d->cpuIndeks.empty()) { a->ozet = "Hedef gövde yok"; return false; } a->hedefGovde = d->kimlik; Dugum* eskiD = a->farkKarsiGovde ? sahne_.bul(a->farkKarsiGovde) : nullptr; if (!eskiD || eskiD->kimlik == d->kimlik || eskiD->cpuKonum.empty() || eskiD->cpuIndeks.empty()) { a->ozet = "Karşılaştırma modeli seçilmedi (eski/referans gövde gerekli)"; gunluk::bilgi("[fark] hesap ATLANDI: yeni={} eski={} (eski yok/aynı/boş) — " "iki FARKLI gövde seçilmeli", d->kimlik, a->farkKarsiGovde); return false; } FarkGirdi eski, yeni; eski.konum = eskiD->cpuKonum.data(); eski.konumSayi = eskiD->cpuKonum.size(); eski.indeks = eskiD->cpuIndeks.data(); eski.indeksSayi = eskiD->cpuIndeks.size(); eski.donusum = eskiD->donusum; yeni.konum = d->cpuKonum.data(); yeni.konumSayi = d->cpuKonum.size(); yeni.indeks = d->cpuIndeks.data(); yeni.indeksSayi = d->cpuIndeks.size(); yeni.donusum = d->donusum; FarkSecenek sec; sec.simetrik = a->farkSimetrik; // Web paritesi: geometrik kipte computationAccuracy, gorsel kipte visualAccuracy // (native'de iki kip de ayni tam-dogruluk BVH motorunu kullanir — yalniz tolerans degisir). sec.degisimTolerans = a->farkGorselKip ? a->farkGorselHassasiyet : a->farkTolerans; sec.yerelEksen = a->farkYerelEksen; FarkSonuc fs = farkHesapla(eski, yeni, sec, &sahne_.vulkan()); if (!fs.gecerli) { a->ozet = "Fark hesaplanamadı (geçersiz mesh)"; return false; } a->sonuc.yerlesim = SonucAlan::Yerlesim::PerVertex; a->sonuc.deger = std::move(fs.sapmaYeni); a->sonuc.min = 0.f; a->sonuc.max = std::max(fs.maksSapma, 1e-6f); a->sonuc.birim = "mm"; a->sonuc.kritikSayi = fs.eklenenVertex + fs.silinenVertex; a->sonuc.hazir = true; a->farkMaks = fs.maksSapma; a->farkOrt = fs.ortSapma; a->farkRms = fs.rmsSapma; a->farkMaksNokta = fs.maksNokta; a->farkMaksKarsi = fs.maksKarsi; a->farkEklenen = fs.eklenenVertex; a->farkSilinen = fs.silinenVertex; a->farkGpu = fs.gpuKullanildi; a->farkBolgeler = std::move(fs.bolgeler); a->farkSureMs = (f32)fs.sureMs; a->farkP99 = fs.p99Sapma; a->farkStd = fs.stdSapma; a->farkHistogram = std::move(fs.histogram); // panel mini-grafiği (hesapta bir kez) a->farkHistogramAdim = fs.histogramAdim; // Renk skalası ± başlangıç = yüzdelik sapma (varsayılan P95 — AyarSistemi // fark.skala_yuzdelik; aykırıları yok say, dolgun/dengeli skala, GOM legend). Kullanıcı // "Renk skalası ±" slider'ı ile ayrıca küçültüp radius/kapatma gibi inceleri görür. a->farkToleransCozulmus = std::max(fs.skalaSapma > 0 ? fs.skalaSapma : fs.maksSapma, 1e-3f); // Eski govdenin alani (ayrik kipte silinen-bolge boyamasi icin). a->farkSonucEski.temizle(); if (!fs.sapmaEski.empty()) { a->farkSonucEski.yerlesim = SonucAlan::Yerlesim::PerVertex; a->farkSonucEski.deger = std::move(fs.sapmaEski); a->farkSonucEski.min = 0.f; a->farkSonucEski.max = a->sonuc.max; a->farkSonucEski.birim = "mm"; a->farkSonucEski.hazir = true; } char oz[192]; std::snprintf(oz, sizeof(oz), "Maks %.3f Ort %.3f RMS %.3f mm Eklenen: %u Silinen: %u [%s %.0f ms]", fs.maksSapma, fs.ortSapma, fs.rmsSapma, fs.eklenenVertex, fs.silinenVertex, fs.gpuKullanildi ? "GPU" : "CPU", fs.sureMs); a->ozet = oz; gunluk::bilgi("[fark] hesap OK: yeni={}({} v) eski={}({} v) maks={:.4f} ort={:.4f} " "tol={:.4f} eklenen={} silinen={} [{} {:.0f}ms] aktif={} → sahne boyanmalı", d->kimlik, d->cpuKonum.size(), eskiD->kimlik, eskiD->cpuKonum.size(), fs.maksSapma, fs.ortSapma, fs.tolerans, fs.eklenenVertex, fs.silinenVertex, fs.gpuKullanildi ? "GPU" : "CPU", fs.sureMs, a->aktif); a->rapor.push_back({"Maks sapma", fs.maksSapma, fs.maksSapma > fs.tolerans}); a->rapor.push_back({"Ortalama sapma", fs.ortSapma, false}); a->rapor.push_back({"RMS sapma", fs.rmsSapma, false}); a->rapor.push_back({"Eklenen bölge (vertex)", (f64)fs.eklenenVertex, fs.eklenenVertex > 0}); a->rapor.push_back({"Silinen bölge (vertex)", (f64)fs.silinenVertex, fs.silinenVertex > 0}); a->bayatAyarla(false); cekirdek_.olaylar().yayinla(AnalizHesaplandi{kimlik}); return true; } bool AnalizSistemi::hesapla(u32 kimlik) { AnalizNesnesi* a = bul(kimlik); if (!a) return false; Dugum* d = hedefDugumBul(sahne_, a->hedefGovde); if (a->tip() == AnalizTip::Fark) { // CAD Fark MESH-bazlı — brep gerekmez (STL/salt-mesh gövde de karşılaştırılır). // Bu yüzden aşağıdaki brep zorunluluğuna girmeden erken işlenir. return farkHesaplaDal(a, d, kimlik); } if (!d || !d->brep || !d->brep->gecerliMi()) { a->sonuc.temizle(); a->ozet = "Hedef katı gövde yok"; if (a->tip() == AnalizTip::KeskinKenar) { a->keskinKenarlar.clear(); keskinGorselGuncelle(kimlik); } return false; } a->hedefGovde = d->kimlik; // 0 idiyse cozulen govdeyi sabitle // Async yol (bayrak arkasinda, varsayilan kapali): rapor worker'da, eski // rapor/gorsel sonuc gelene kadar yerinde kalir (bosluk yerine "Hesaplanıyor…"). if (hesapAsync && (a->tip() == AnalizTip::Sureklilik || a->tip() == AnalizTip::KeskinKenar) && asyncBaslat(kimlik, *a, *d)) return true; const geometri::BrepGovde& brep = *d->brep; a->rapor.clear(); a->sonuc.temizle(); // Yuz basi skaler → per-vertex alan: her vertex ait oldugu yuz ordinaliyle // etiketli (cpuKonum sirasi = vertex sirasi; alt kimlik yuz ordinali). // Bu blok cpuNormal/cpuKonum yerine ucgenle vAlt'i kullanir → shader PerYuz. const size_t vSayi = d->cpuKonum.size(); auto yuzAlanKur = [&](std::vector& yuzDeger, u32 yuzMaks) { // PerVertex tampon: her vertex, cpuIndeks olmadan → yuz ordinali cpuNormal'a // gomulu degil; bu yuzden PerYuz modunu kullaniyoruz (shader vAlt → yuz idx). a->sonuc.yerlesim = SonucAlan::Yerlesim::PerYuz; a->sonuc.deger = std::move(yuzDeger); (void)yuzMaks; }; if (a->tip() == AnalizTip::Draft || a->tip() == AnalizTip::Undercut) { a->sonuc.min = -90.f; a->sonuc.max = 90.f; a->sonuc.birim = "°"; // UNDERCUT EXACT PER-YÜZ GÖRÜNÜRLÜK (BREP ışın — tessellation YOK). // `sonuc`tan AYRI alanda → Kesin mod draft raporu bozulmaz. Hızlı mod // erken-çıkışından ÖNCE: GÖRSELİN KENDİSİ buna bağlı (rapora değil). // Boş kalırsa (kip=Gölge / kapalı / OCCT hatası) shader gölge yoluna düşer. if (a->tip() == AnalizTip::Undercut) { a->gorunurluk.temizle(); if (undercutGorunurluk() && undercutKip() != UndercutKip::Golge) { const Vec3d cy{a->cekimYonu.x, a->cekimYonu.y, a->cekimYonu.z}; // Async: exact rapor+golge worker'da (546 ms olculdu); dal senkron devam // eder, shader gorunurluk.hazir olana dek golge/analitik yola duser. if (!(hesapAsync && undercutAsyncBaslat(kimlik, *a, *d))) { const auto uy = geometri::undercutRapor(brep, d->donusum, cy, (f64)undercutOrnekAdim(), undercutIzgaraMaks()); if (!uy.empty()) { u32 maksOrd = 0; for (const auto& u : uy) maksOrd = std::max(maksOrd, u.ordinal); // deger[ordinal] = UndercutDurum kodu (shader alanDuz ile FLAT okur). std::vector alan(maksOrd + 1, 0.0f); // 0 = bilinmiyor u32 kapali = 0, kismi = 0; for (const auto& u : uy) { alan[u.ordinal] = (f32)(u8)u.durum; if (u.durum == geometri::UndercutDurum::Kapali) ++kapali; else if (u.durum == geometri::UndercutDurum::Kismi) ++kismi; } // KISMI yüzlerin gölgeli alt-bölgesi = EXACT overlay geometrisi. // Yalnız Kısmi'ler (hepsine boolean koşmak çok pahalı olurdu). std::vector gk, gn; std::vector gi; if (kismi > 0 && undercutKip() == UndercutKip::Hibrit) { std::vector kismiOrd; kismiOrd.reserve(kismi); for (const auto& u : uy) if (u.durum == geometri::UndercutDurum::Kismi) kismiOrd.push_back(u.ordinal); geometri::undercutGolgeMesh(brep, d->donusum, cy, kismiOrd, gk, gn, gi); if (gi.empty()) gunluk::bilgi("[analiz] undercut gölge overlay ÜRETİLEMEDİ ({} kısmi yüz) — " "kısmi yüzler gölge haritasına düşecek", kismiOrd.size()); } undercutSonucUygula(a, std::move(alan), kapali, kismi, std::move(gk), std::move(gn), std::move(gi), (u32)uy.size()); } } } } // HIZLI MOD (donma fix): görsel shader'da cekimYonu'ndan CANLI boyanır → ağır // CPU draftRapor'u (OCCT yüz-döngüsü) TAMAMEN atla. Aktifleşince ANINDA görünür. // Sayısal per-yüz rapor Kesin modda üretilir (kullanıcı ⚙'den seçer). if (a->hesapMod == HesapMod::Hizli) { a->sonuc.hazir = false; // alan yok; render analitik yoldan (aktif) çizer a->sonuc.kritikSayi = 0; a->rapor.clear(); a->ozet = "Canlı GPU (hızlı) — sayısal rapor için ⚙ → Kesin mod"; return true; } const bool undercut = a->tip() == AnalizTip::Undercut; const f64 tersEsik = undercut ? 0.0 : -0.3; f64 minDraft = 90.0; const Vec3d cy{a->cekimYonu.x, a->cekimYonu.y, a->cekimYonu.z}; const auto yuzler = draftRapor(brep, d->donusum, cy, tersEsik, minDraft); // Yuz ordinal → draft aci alani. u32 maksOrd = 0; for (auto& y : yuzler) maksOrd = std::max(maksOrd, y.ordinal); std::vector alan(maksOrd + 1, 0.0f); u32 kritik = 0; for (auto& y : yuzler) { alan[y.ordinal] = (f32)y.draftDeg; if (y.tersMi) ++kritik; } a->sonuc.min = -90.f; a->sonuc.max = 90.f; a->sonuc.birim = "°"; a->sonuc.kritikSayi = kritik; a->sonuc.hazir = true; yuzAlanKur(alan, maksOrd); char oz[128]; std::snprintf(oz, sizeof(oz), "Yüz: %zu MinDraft: %.1f° Ters: %u", yuzler.size(), minDraft, kritik); a->ozet = oz; // Rapor: en kritik ilk (ters yuzler). for (auto& y : yuzler) if (y.tersMi) { char rb[64]; std::snprintf(rb, sizeof(rb), "Yüz #%u", y.ordinal); a->rapor.push_back({rb, y.draftDeg, true, y.ordinal}); if (a->rapor.size() >= 12) break; } } else if (a->tip() == AnalizTip::Radius) { u32 kritik = 0; const auto yuzler = radiusRapor(brep, a->radiusEsik, kritik); u32 maksOrd = 0; for (auto& y : yuzler) maksOrd = std::max(maksOrd, y.ordinal); std::vector alan(maksOrd + 1, -1.0f); // -1 = nötr (duzlem) f64 minR = 1e9; for (auto& y : yuzler) { alan[y.ordinal] = (f32)y.yaricap; if (y.yaricap > 0) minR = std::min(minR, y.yaricap); if (y.kritik) { char rb[64]; std::snprintf(rb, sizeof(rb), "Yüz #%u R%.2f", y.ordinal, y.yaricap); if (a->rapor.size() < 12) a->rapor.push_back({rb, y.yaricap, true, y.ordinal}); } } a->sonuc.min = 0.f; a->sonuc.max = a->radiusMaks; a->sonuc.birim = "mm"; a->sonuc.kritikSayi = kritik; a->sonuc.hazir = true; yuzAlanKur(alan, maksOrd); char oz[128]; std::snprintf(oz, sizeof(oz), "Min R: %.2f mm Eşik<%.1f: %u yüz", minR > 1e8 ? 0.0 : minR, a->radiusEsik, kritik); a->ozet = oz; // İtem-bazlı sınıf raporu (teşkilat): her banda düşen yüz + tolerans geçti/kaldı. if (!a->araliklar.empty()) { std::string s = std::string(oz) + "\nSınıflar: "; for (const auto& b : a->araliklar) { u32 adet = 0, disi = 0; for (const auto& y : yuzler) { if (y.yaricap < 0) continue; const f32 r = (f32)y.yaricap; if (r < b.min || r > b.max) continue; ++adet; if (b.tolerans > 0 && std::fabs(r - b.nominal) > b.tolerans) ++disi; } char seg[96]; if (disi) std::snprintf(seg, sizeof(seg), "%s=%u (%u tol✗) ", b.isim.c_str(), adet, disi); else std::snprintf(seg, sizeof(seg), "%s=%u ", b.isim.c_str(), adet); s += seg; } a->ozet = s; } } else if (a->tip() == AnalizTip::Kalinlik) { // Et kalınlığı: yüz başı IC-normal ray → karşı duvar mesafesi (PerYüz alan → // GPU rampa PURUZSUZ boyar). Düzlem/küre yüzler analitik normal; silindir/torus // ilk sürümde nötr (-1). Büyük parça için wall.comp (ray compute) yolu ayrı. f64 mn = 1e6, mx = 0; u32 maksOrd = 0; for (u32 ord = 1;; ++ord) { geometri::YuzBilgi yb; if (!brep.yuzBilgi(ord, yb)) break; maksOrd = ord; } std::vector alan(maksOrd + 1, -1.0f); // -1 = nötr (ray yok / eğri yüz) u32 ince = 0, ornekli = 0; // ÖNCE GPU (wall.comp per-vertex ray → per-yüz ortalama): eğri yüzler dahil // tüm yüzler örneklenir. Başarısızlıkta (aygıt kaybı/spv yok) CPU analitik yol. std::vector perVertex; const bool gpuTamam = gpuKalinlikPerYuz(sahne_.vulkan(), *d, maksOrd, alan, mn, mx, kalinlikPerVertex() ? &perVertex : nullptr); if (gpuTamam) { for (u32 ord = 1; ord <= maksOrd; ++ord) if (alan[ord] >= 0.0f) { if (alan[ord] < a->kalinlikInce) ++ince; ++ornekli; } } else { // GPU yok → agir CPU orneklemesi (657 ms olculdu, tarak). Bayrak aciksa // worker'a: kuyruklandiysa sonuc bitince'de uygulanir, burada cikilir. if (hesapAsync && kalinlikAsyncBaslat(kimlik, *a, *d)) return true; // CPU: yüz UV ızgarası (köşe ıskası + ölçülmeyen yüz içi sorunu çözülür; // eğri yüzler de örneklenir). İndirgeme: en-kötü-durum MIN (kalinlikYuzMin // kapalı → eski ortalama). Saf hesap kalinlikCpuHesap'ta (async ile ortak). KalinlikCpuSonuc cs = kalinlikCpuHesap(brep, kalinlikYuzMin(), kalinlikIzgara(), a->kalinlikInce); alan = std::move(cs.alan); mn = cs.mn; mx = cs.mx; ince = cs.ince; ornekli = cs.ornekli; } kalinlikSonucUygula(a, d, std::move(alan), maksOrd, std::move(perVertex), mn, mx, ince, ornekli); } else if (a->tip() == AnalizTip::Kurvatur) { // Kurvatur: yuz basi analitik egrilik → PerYuz skaler alan (mod-3 jet rampa). const KurvaturOlcu olcu = a->kurvaturMod == AnalizNesnesi::KurvaturMod::Gauss ? KurvaturOlcu::Gauss : a->kurvaturMod == AnalizNesnesi::KurvaturMod::Maks ? KurvaturOlcu::MaksMutlak : KurvaturOlcu::Ortalama; f64 kmn = 0, kmx = 0; const auto yuzler = kurvaturRapor(brep, olcu, kmn, kmx); u32 maksOrd = 0; for (auto& y : yuzler) maksOrd = std::max(maksOrd, y.ordinal); std::vector alan(maksOrd + 1, 0.0f); for (auto& y : yuzler) alan[y.ordinal] = (f32)y.deger; // Rampa ucu: kullanici kurvaturMaks (mutlak) VEYA veri araligi (buyugu). const f32 ucu = std::max(a->kurvaturMaks, (f32)std::max(std::abs(kmn), std::abs(kmx))); a->sonuc.min = olcu == KurvaturOlcu::Gauss ? -ucu * ucu : -ucu; // Gauss ~ 1/R² a->sonuc.max = olcu == KurvaturOlcu::Gauss ? ucu * ucu : ucu; if (olcu == KurvaturOlcu::MaksMutlak) { a->sonuc.min = 0.f; a->sonuc.max = ucu; } a->sonuc.birim = olcu == KurvaturOlcu::Gauss ? "1/mm²" : "1/mm"; a->sonuc.kritikSayi = 0; a->sonuc.hazir = true; yuzAlanKur(alan, maksOrd); char oz[128]; std::snprintf(oz, sizeof(oz), "Egrilik [%.4f, %.4f] %s Yüz: %zu", kmn, kmx, a->sonuc.birim.c_str(), yuzler.size()); a->ozet = oz; } else if (a->tip() == AnalizTip::Sureklilik) { // Sureklilik: komsu-yuz ortak kenar G0/G1/G2 → sayisal rapor (shader alan yok). sureklilikSonucUygula(a, sureklilikRapor(brep, a->g1EsikDeg)); } else if (a->tip() == AnalizTip::KeskinKenar) { // Dihedral esigi ustundeki kenarlar; gorsel = sahnede renkli kenar dugumleri // (disbukey kirmizi / icbukey mavi) — yuzey boyama yok. keskinSonucUygula(kimlik, a, keskinKenarRapor(brep, (f64)a->keskinEsikDeg)); } // Zebra/Reflect: saf CANLI-GPU (shader) → CPU hesap yok; render koprusu // dogrudan aktif+hedef alir (main.cpp pipeline varyanti — kontrat). (void)vSayi; a->bayatAyarla(false); cekirdek_.olaylar().yayinla(AnalizHesaplandi{kimlik}); return true; } // ---- Sonuc uygulama (senkron dal + async bitince ORTAK — davranis tek kaynak) ---- void AnalizSistemi::sureklilikSonucUygula(AnalizNesnesi* a, std::vector kenarlar) { a->rapor.clear(); u32 g0 = 0, g1 = 0, g2 = 0; for (auto& k : kenarlar) { if (k.derece == 0) ++g0; else if (k.derece == 1) ++g1; else ++g2; } // Rapor: en kotu (kirilma) kenarlar once. std::sort(kenarlar.begin(), kenarlar.end(), [](const SureklilikKenar& x, const SureklilikKenar& y) { return x.g1AciDeg > y.g1AciDeg; }); for (auto& k : kenarlar) { if (a->rapor.size() >= 12) break; char rb[80]; std::snprintf(rb, sizeof(rb), "Kenar #%u (Yüz %u-%u) G%u", k.kenarOrdinal, k.yuzA, k.yuzB, k.derece); a->rapor.push_back({rb, k.g1AciDeg, k.derece == 0}); } a->sonuc.temizle(); // gorsel alan yok (yalniz rapor) a->sonuc.birim = "°"; a->sonuc.kritikSayi = g0; a->sonuc.hazir = true; char oz[128]; std::snprintf(oz, sizeof(oz), "İç kenar: %zu G0(kırık): %u G1: %u G2: %u", kenarlar.size(), g0, g1, g2); a->ozet = oz; } void AnalizSistemi::keskinSonucUygula(u32 kimlik, AnalizNesnesi* a, std::vector kenarlar) { a->rapor.clear(); a->keskinKenarlar = std::move(kenarlar); std::sort(a->keskinKenarlar.begin(), a->keskinKenarlar.end(), [](const KeskinKenarBilgi& x, const KeskinKenarBilgi& y) { return x.aciDeg > y.aciDeg; }); u32 disbukey = 0; for (const auto& k : a->keskinKenarlar) if (k.disbukey) ++disbukey; const u32 icbukey = (u32)a->keskinKenarlar.size() - disbukey; for (const auto& k : a->keskinKenarlar) { if (a->rapor.size() >= 12) break; char rb[96]; std::snprintf(rb, sizeof(rb), "Kenar #%u (Yüz %u-%u) %s", k.kenarOrdinal, k.yuzA, k.yuzB, k.disbukey ? "dışbükey" : "içbükey"); // ordinal = yuzA → rapor satirina tik kamerayi o yuze ucurur (mevcut kopru). a->rapor.push_back({rb, k.aciDeg, true, k.yuzA}); } a->sonuc.temizle(); // skaler alan yok (gorsel kenar dugumlerinden gelir) a->sonuc.birim = "°"; a->sonuc.kritikSayi = (u32)a->keskinKenarlar.size(); a->sonuc.hazir = true; char oz[160]; std::snprintf(oz, sizeof(oz), "%zu keskin kenar (%u dışbükey, %u içbükey), eşik %.1f°", a->keskinKenarlar.size(), disbukey, icbukey, a->keskinEsikDeg); a->ozet = oz; keskinGorselGuncelle(kimlik); } // ---- Kalinlik sonuc uygulama (senkron GPU/CPU + async bitince ORTAK) ------- // NOT: eski kod PerYuz yolunda alan'i sonuc.deger'e TASIDIKTAN sonra bant/rapor // bloklari alan[o] okuyordu (moved-from vektore indeks = UB; GPU per-vertex yolu // hep basarili oldugundan gorulmemisti). Sira duzeltildi: alan EN SON tasinir. void AnalizSistemi::kalinlikSonucUygula(AnalizNesnesi* a, const Dugum* d, std::vector alan, u32 maksOrd, std::vector perVertex, f64 mn, f64 mx, u32 ince, u32 ornekli) { // DÜNYA ÖLÇEĞİ: hesap BREP YEREL uzayında koşar (konum/yönelim kalınlığı // değiştirmez — ölçüldü), ama düğüm ölçek taşıyorsa yerel mm ≠ dünya mm. // Düzgün (uniform) ölçekte tek çarpan yeterli; düzgün DEĞİLSE kalınlık yöne // bağlı olur → tek çarpan yanlış olurdu, ölçeklemiyoruz ve özette uyarıyoruz. const Vec3d ol = d->donusum.olcek; const f64 olMin = std::min({ol.x, ol.y, ol.z}), olMaks = std::max({ol.x, ol.y, ol.z}); const bool duzgunOlcek = (olMaks - olMin) <= 1e-9 * std::max(1.0, olMaks); const char* olcekUyari = ""; if (duzgunOlcek) { if (std::abs(ol.x - 1.0) > 1e-9) { const f64 s = ol.x; for (u32 o = 1; o <= maksOrd; ++o) if (alan[o] >= 0.0f) alan[o] = (f32)(alan[o] * s); mn *= s; mx *= s; } } else { olcekUyari = " ⚠ düzgün olmayan ölçek (yerel mm)"; } // Rampa/lejant sınırı = ÖLÇÜLEN aralık (0 taban kontrastı öldürüyordu). a->sonuc.min = mn > 1e5 ? 0.f : (f32)mn; a->sonuc.max = mx > 0 ? (f32)mx : a->kalinlikMaks; if (a->sonuc.max <= a->sonuc.min) a->sonuc.max = a->sonuc.min + 1.f; // dejenere rampa a->sonuc.birim = "mm"; a->sonuc.kritikSayi = ince; a->sonuc.hazir = true; const bool pv = !perVertex.empty() && perVertex.size() == d->cpuKonum.size(); char oz[160]; std::snprintf(oz, sizeof(oz), "Kalınlık min %.2f max %.2f mm İnce(<%.1f): %u Yüz: %u [%s]%s", mn > 1e5 ? 0.0 : mn, mx, a->kalinlikInce, ince, ornekli, pv ? "nokta ısı haritası" : "yüz başına", olcekUyari); a->ozet = oz; // Kullanıcı kalınlık sınıfları: bant başına yüz adedi + nominal±tolerans dışı // sayısı (Radius sınıflarıyla AYNI kalıp). 3B'de ayrık renk, burada sayısal. if (!a->araliklar.empty()) { std::string s2 = a->ozet + " "; for (const AnalizAralik& b : a->araliklar) { u32 adet = 0, disi = 0; for (u32 o = 1; o <= maksOrd; ++o) { const f32 v = alan[o]; if (v < 0 || v < b.min || v > b.max) continue; ++adet; if (b.tolerans > 0 && std::fabs(v - b.nominal) > b.tolerans) ++disi; } char seg[96]; if (disi) std::snprintf(seg, sizeof(seg), "%s=%u (%u tol✗) ", b.isim.c_str(), adet, disi); else std::snprintf(seg, sizeof(seg), "%s=%u ", b.isim.c_str(), adet); s2 += seg; } a->ozet = s2; } // Yüz NUMARALI döküm: en ince 6 + en kalın 3 (ordinal ile yüz sahnede bulunur). // BUGDUUZTMESI: eski dongu `for (i = size; i-- > 0 && i+3 >= size;)` idi → // (a) YUZ SAYISI < 9 iken en ince 6 ile en kalın 3 listeleri CAKISIYORDU // (4 yuz → 4 satır "İnce" + 4 satır "Kalın" = ayni yüz iki kez raporlandı), // (b) i + 3 >= size koşulu i azaltıldıkça KÖTÜLEŞİyor → son birkaç yüzü atlayıp // ortadakileri (gercek en kalinlar DEGIL) yaziyordu. // Dogrusu: sirali zaten kucukten buyuge; en kalin = son 3, en ince = ilk 6, ve // ikisi CakisMIYOR (kalan 0 ise kalin bolum bos gecer). { std::vector> sirali; sirali.reserve(maksOrd); for (u32 o = 1; o <= maksOrd; ++o) if (alan[o] >= 0.0f) sirali.push_back({alan[o], o}); std::sort(sirali.begin(), sirali.end()); char rb[64]; const size_t inceAdet = std::min(6, sirali.size()); for (size_t i = 0; i < inceAdet; ++i) { std::snprintf(rb, sizeof(rb), "İnce Yüz #%u", sirali[i].second); a->rapor.push_back({rb, sirali[i].first, sirali[i].first < a->kalinlikInce, sirali[i].second}); } // En kalin 3 = son 3; en ince 6 ile cakismasin diye kalan kadarini al. const size_t kalan = sirali.size() - inceAdet; for (size_t j = 0; j < std::min(3, kalan); ++j) { const auto& e = sirali[sirali.size() - 1 - j]; std::snprintf(rb, sizeof(rb), "Kalın Yüz #%u", e.second); a->rapor.push_back({rb, e.first, false, e.second}); } } // PER-VERTEX (ısı haritası) varsa onu ver; yoksa PerYüz alan. Alan tasimasi // EN SONDA — ustteki bant/rapor bloklari alan'i okur (UB duzeltmesi). if (pv) { if (duzgunOlcek && std::abs(ol.x - 1.0) > 1e-9) for (f32& v : perVertex) if (v >= 0.0f) v = (f32)(v * ol.x); a->sonuc.yerlesim = SonucAlan::Yerlesim::PerVertex; a->sonuc.deger = std::move(perVertex); } else { a->sonuc.yerlesim = SonucAlan::Yerlesim::PerYuz; a->sonuc.deger = std::move(alan); // shader mod-3 boyar (yüz başına tek renk) } } // Kalinlik CPU async: GPU yolu basarisiz oldugunda cagrilir (GPU zaten hizli). // Worker derin kopyada kalinlikCpuHesap kosar; ayar degerleri kuyruklama aninda // yakalanir (worker global ayara dokunmaz). Tazelik nesli asyncBaslat ile ortak. bool AnalizSistemi::kalinlikAsyncBaslat(u32 kimlik, AnalizNesnesi& a, const Dugum& d) { auto kopya = std::make_shared(geometri::govdeDerinKopya(*d.brep)); if (!kopya->gecerliMi()) return false; const bool yuzMin = kalinlikYuzMin(); const u32 izgara = kalinlikIzgara(); const f32 inceEsik = a.kalinlikInce; auto sonuc = std::make_shared(); asyncKuyrukla( kimlik, "Et Kalınlığı hesabı", [kopya, yuzMin, izgara, inceEsik, sonuc](GorevBaglam& gb) { if (gb.iptalIstendi()) return; *sonuc = kalinlikCpuHesap(*kopya, yuzMin, izgara, inceEsik); }, [this, sonuc](AnalizNesnesi* a) { Dugum* d = hedefDugumBul(sahne_, a->hedefGovde); if (!d || !d->brep) return; // hedef kaybolmus a->rapor.clear(); kalinlikSonucUygula(a, d, std::move(sonuc->alan), sonuc->maksOrd, {}, sonuc->mn, sonuc->mx, sonuc->ince, sonuc->ornekli); a->bayatAyarla(false); cekirdek_.olaylar().yayinla(AnalizHesaplandi{a->kimlik()}); }); a.ozet = "Hesaplanıyor…"; return true; } // ---- Undercut exact sonuc uygulama (senkron + async bitince ORTAK) --------- void AnalizSistemi::undercutSonucUygula(AnalizNesnesi* a, std::vector alan, u32 kapali, u32 kismi, std::vector golgeKonum, std::vector golgeNormal, std::vector golgeIndeks, u32 yuzSayi) { if (alan.empty()) return; // rapor bos → gorunurluk temiz kalir (shader golge yolu) a->gorunurluk.yerlesim = SonucAlan::Yerlesim::PerYuz; a->gorunurluk.deger = std::move(alan); a->gorunurluk.min = 0.f; a->gorunurluk.max = 3.f; a->gorunurluk.kritikSayi = kapali + kismi; a->gorunurluk.hazir = true; a->golgeKonum = std::move(golgeKonum); a->golgeNormal = std::move(golgeNormal); a->golgeIndeks = std::move(golgeIndeks); if (!a->golgeIndeks.empty()) gunluk::bilgi("[analiz] undercut EXACT gölge overlay: {} kısmi yüz → {} üçgen", kismi, a->golgeIndeks.size() / 3); ++a->golgeSurum; gunluk::bilgi("[analiz] undercut EXACT: {} yüz — {} kapalı, {} kısmi (adım {:.3f}mm, tavan {}²)", yuzSayi, kapali, kismi, undercutOrnekAdim(), undercutIzgaraMaks()); } // Ortak async kuyruklama (4 kopyaya bolunmus nesil-guard kalibinin tek kaynagi). void AnalizSistemi::asyncKuyrukla(u32 kimlik, const char* baslik, std::function is, std::function uygula) { if (auto it = asyncIs_.find(kimlik); it != asyncIs_.end()) cekirdek_.gorevler().iptalEt(it->second); // cifte hesap: eskisi cope const u64 nesil = ++asyncNesil_[kimlik]; const u64 g = cekirdek_.gorevler().kuyrukla( baslik, std::move(is), [this, kimlik, nesil, uygula = std::move(uygula)] { if (asyncNesil_[kimlik] != nesil) return; // bayat sonuc (bayatla/sil/yeni hesap) asyncIs_.erase(kimlik); if (AnalizNesnesi* a = bul(kimlik)) uygula(a); }); asyncIs_[kimlik] = g; } // Undercut exact async: rapor + (Hibrit'te) golge mesh worker'da, derin kopyayla. // hesapla() dali worker'i beklemez — draft/ozet senkron tamamlanir, exact alan // bitince'de gelir + AnalizHesaplandi yayinlanir (render tazelenir). bool AnalizSistemi::undercutAsyncBaslat(u32 kimlik, AnalizNesnesi& a, const Dugum& d) { auto kopya = std::make_shared(geometri::govdeDerinKopya(*d.brep)); if (!kopya->gecerliMi()) return false; const Vec3d cy{a.cekimYonu.x, a.cekimYonu.y, a.cekimYonu.z}; const Donusumd donusum = d.donusum; const f64 adim = (f64)undercutOrnekAdim(); const u32 izgara = undercutIzgaraMaks(); const bool hibrit = undercutKip() == UndercutKip::Hibrit; struct UndercutAsyncSonuc { std::vector alan; u32 kapali = 0, kismi = 0, yuzSayi = 0; std::vector gk, gn; std::vector gi; }; auto sonuc = std::make_shared(); asyncKuyrukla( kimlik, "Undercut exact hesabı", [kopya, cy, donusum, adim, izgara, hibrit, sonuc](GorevBaglam& gb) { if (gb.iptalIstendi()) return; const auto uy = geometri::undercutRapor(*kopya, donusum, cy, adim, izgara); if (uy.empty()) return; sonuc->yuzSayi = (u32)uy.size(); u32 maksOrd = 0; for (const auto& u : uy) maksOrd = std::max(maksOrd, u.ordinal); sonuc->alan.assign(maksOrd + 1, 0.0f); for (const auto& u : uy) { sonuc->alan[u.ordinal] = (f32)(u8)u.durum; if (u.durum == geometri::UndercutDurum::Kapali) ++sonuc->kapali; else if (u.durum == geometri::UndercutDurum::Kismi) ++sonuc->kismi; } if (gb.iptalIstendi()) return; if (sonuc->kismi > 0 && hibrit) { std::vector kismiOrd; kismiOrd.reserve(sonuc->kismi); for (const auto& u : uy) if (u.durum == geometri::UndercutDurum::Kismi) kismiOrd.push_back(u.ordinal); geometri::undercutGolgeMesh(*kopya, donusum, cy, kismiOrd, sonuc->gk, sonuc->gn, sonuc->gi); } }, [this, sonuc](AnalizNesnesi* a) { undercutSonucUygula(a, std::move(sonuc->alan), sonuc->kapali, sonuc->kismi, std::move(sonuc->gk), std::move(sonuc->gn), std::move(sonuc->gi), sonuc->yuzSayi); // bayat'a DOKUNMA: dalin senkron kismi (draft/ozet) kendi bayrağını yonetti. cekirdek_.olaylar().yayinla(AnalizHesaplandi{a->kimlik()}); }); return true; } // ---- Async hesap (analiz.hesap_async acikken KeskinKenar/Sureklilik) ------- // Worker DERIN KOPYA okur (paylasilan Geom'a iki thread dokunmaz — STEP import // kurali); kopya basarisizsa false doner, cagiran senkron yolda devam eder. // Tazelik nesil sayaciyla: iptalEt bitmis isin bitince'sini ENGELLEMEZ (gorev.cpp // yalniz bayrak atar), bu yuzden bitince nesil esitligini kontrol eder. bool AnalizSistemi::asyncBaslat(u32 kimlik, AnalizNesnesi& a, const Dugum& d) { auto kopya = std::make_shared(geometri::govdeDerinKopya(*d.brep)); if (!kopya->gecerliMi()) return false; const AnalizTip tip = a.tip(); const f64 esik = tip == AnalizTip::KeskinKenar ? (f64)a.keskinEsikDeg : (f64)a.g1EsikDeg; auto sonucK = std::make_shared>(); auto sonucS = std::make_shared>(); asyncKuyrukla( kimlik, tip == AnalizTip::KeskinKenar ? "Keskin Kenar hesabı" : "Süreklilik hesabı", [kopya, tip, esik, sonucK, sonucS](GorevBaglam& gb) { if (gb.iptalIstendi()) return; if (tip == AnalizTip::KeskinKenar) *sonucK = keskinKenarRapor(*kopya, esik); else *sonucS = sureklilikRapor(*kopya, esik); }, [this, tip, sonucK, sonucS](AnalizNesnesi* a) { const u32 kimlik = a->kimlik(); if (tip == AnalizTip::KeskinKenar) keskinSonucUygula(kimlik, a, std::move(*sonucK)); else sureklilikSonucUygula(a, std::move(*sonucS)); a->bayatAyarla(false); cekirdek_.olaylar().yayinla(AnalizHesaplandi{kimlik}); }); a.ozet = "Hesaplanıyor…"; return true; } // ---- Keskin Kenar gorsel katmani (kesitEgriGuncelle kalibi) ---------------- // Kenar poligonlari BREP'ten (kenarNoktalari) alinir, GOVDE DUNYA donusumu // uygulanir; disbukey/icbukey iki ayri kenar dugumune yazilir (renk dugum // basina tek). Yalniz hesap/panel aninda kosar — kare-basi maliyet yok. void AnalizSistemi::keskinGorselGuncelle(u32 kimlik) { const auto hepsiniSil = [&] { if (auto it = keskinDugumler_.find(kimlik); it != keskinDugumler_.end()) { for (u32 dg : it->second) if (dg) sahne_.sil(dg); keskinDugumler_.erase(it); } }; AnalizNesnesi* a = bul(kimlik); if (!a || a->tip() != AnalizTip::KeskinKenar || !a->aktif || a->keskinKenarlar.empty()) { hepsiniSil(); return; } Dugum* d = hedefDugumBul(sahne_, a->hedefGovde); if (!d || !d->brep || !d->brep->gecerliMi()) { hepsiniSil(); return; } std::vector kenarlar[2]; // [0] disbukey, [1] icbukey Aabbf sinir[2]; bool sinirVar[2] = {false, false}; for (const auto& kk : a->keskinKenarlar) { const int s = kk.disbukey ? 0 : 1; const std::vector poly = d->brep->kenarNoktalari(kk.kenarOrdinal); for (size_t i = 0; i + 1 < poly.size(); ++i) { const Vec3d pa = d->donusum.noktaUygula(poly[i]); const Vec3d pb = d->donusum.noktaUygula(poly[i + 1]); const Vec3f af{(f32)pa.x, (f32)pa.y, (f32)pa.z}; const Vec3f bf{(f32)pb.x, (f32)pb.y, (f32)pb.z}; kenarlar[s].push_back({{af.x, af.y, af.z}, 0}); kenarlar[s].push_back({{bf.x, bf.y, bf.z}, 0}); if (!sinirVar[s]) { sinir[s].asgari = sinir[s].azami = af; sinirVar[s] = true; } sinir[s].genislet(af); sinir[s].genislet(bf); } } const Vec3f renkler[2] = {{0.85f, 0.15f, 0.15f}, {0.15f, 0.4f, 0.85f}}; const char* adlar[2] = {"Keskin Kenar (dışbükey)", "Keskin Kenar (içbükey)"}; auto& dugumler = keskinDugumler_[kimlik]; const Varlik grup = grupAl(analizGrup_, "Analizler"); for (int s = 0; s < 2; ++s) { if (kenarlar[s].empty() || !sinirVar[s]) { if (dugumler[s]) { sahne_.sil(dugumler[s]); dugumler[s] = 0; } continue; } if (dugumler[s] && sahne_.bul(dugumler[s])) sahne_.kenarNesneGuncelle(dugumler[s], kenarlar[s], sinir[s]); else dugumler[s] = sahne_.kenarNesneEkle(adlar[s], grup, renkler[s], kenarlar[s], sinir[s]); // Belirginlik: kalın çizgi + opsiyonel X-ray (ayardan; her güncellemede // yeniden uygulanır ki ayar değişimi sonraki hesapta etkisini göstersin). sahne_.kenarNesneKalin(dugumler[s], keskinKalin); sahne_.kenarNesneUstte(dugumler[s], keskinUstte); } } // ---- Kesit ---------------------------------------------------------------- u32 AnalizSistemi::kesitEkle(u32 hedefGovde) { const Varlik grup = grupAl(kesitGrup_, "Kesitler"); const Varlik v = cekirdek_.dunya().olustur(); auto kd = std::make_unique(v); char ad[48]; std::snprintf(ad, sizeof(ad), "Kesit %u", (unsigned)(kesitler_.size() + 1)); kd->adAyarla(ad); // Yeni kesit KULLANILMAYAN bir eksende dogar (Z → Y → X → Z…). Hepsi ayni // varsayilanla (Z, mesafe 0) dogunca ikinci kesit birincinin TAM ustune oturuyor, // ekranda hicbir sey degismiyor → "eklenmedi" hissi (kullanici bildirdi). Ilk // bos eksen secilir; hepsi doluysa Z'ye doner (eski davranis). { const Vec3f eksenler[3] = {{0, 0, 1}, {0, 1, 0}, {1, 0, 0}}; bool dolu[3] = {false, false, false}; for (u32 k : kesitler_) if (const KesitDuzlemi* v = dynamic_cast(cekirdek_.nesne(k))) for (int i = 0; i < 3; ++i) if (std::abs(std::abs(v->normal.nokta(eksenler[i])) - 1.0f) < 1e-3f) dolu[i] = true; for (int i = 0; i < 3; ++i) if (!dolu[i]) { kd->normal = eksenler[i]; break; } } // Taban = hedef govde merkezi (dunya). if (Dugum* d = hedefDugumBul(sahne_, hedefGovde)) { const Vec3f m = d->dunyaMerkez(); kd->taban = m; } KesitDuzlemi* ref = kd.get(); cekirdek_.nesneEkle(std::move(kd)); cekirdek_.dunya().ekle(v, AdBilesen{ad}); cekirdek_.dunya().ekle(v, HiyerarsiBilesen{grup}); kesitler_.push_back(v); cekirdek_.olaylar().yayinla(KesitEklendi{v}, ad); kesitEgriGuncelle(*ref); kesitKapakGuncelle(*ref); return v; } bool AnalizSistemi::kesitSil(u32 kimlik) { auto it = std::find(kesitler_.begin(), kesitler_.end(), kimlik); if (it == kesitler_.end()) return false; if (KesitDuzlemi* kd = kesitBul(kimlik)) { if (kd->profilDugum) sahne_.sil(kd->profilDugum); if (kd->kapakDugum) sahne_.sil(kd->kapakDugum); } kesitler_.erase(it); cekirdek_.dunya().yokEt(kimlik); return true; } KesitDuzlemi* AnalizSistemi::kesitBul(u32 kimlik) { return dynamic_cast(cekirdek_.nesne(kimlik)); } void AnalizSistemi::kesitGuncelle(u32 kimlik, bool kapakDahil) { if (KesitDuzlemi* kd = kesitBul(kimlik)) { kesitEgriGuncelle(*kd); if (kapakDahil) { kesitKapakGuncelle(*kd); } else if (kd->kapakDugum) { // Surukleme kipi: bayat kapak eski duzlemde asili gorunur → sil; birakis // tam kesitGuncelle ile yeniden kurar. (GPU stencil kapak render'da canli.) sahne_.sil(kd->kapakDugum); kd->kapakDugum = 0; } cekirdek_.olaylar().yayinla(KesitDegisti{kimlik}); } } void AnalizSistemi::kesitEgriGuncelle(KesitDuzlemi& k) { if (!k.profilGoster) { if (k.profilDugum) { sahne_.sil(k.profilDugum); k.profilDugum = 0; } return; } // TUM gorunur brep'li govdeleri kes (dunya kesit egrisi). std::vector kenarlar; Aabbf sinir; bool sinirVar = false; const Vec3f nk = k.nokta(); for (Dugum& d : sahne_.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || !sahne_.agacGorunur(d.kimlik)) continue; if (d.indeksSayi == 0) continue; // Duzlem denklemini GOVDE-YEREL uzaya tasi (brepKesit yerel calisir). const Donusumd ters = d.donusum.ters(); const Vec3d ptY = ters.noktaUygula({nk.x, nk.y, nk.z}); const Vec3d nY = ters.yonelim.dondur({k.normal.x, k.normal.y, k.normal.z}); const auto egriler = geometri::brepKesit(*d.brep, ptY, nY, 0.05); for (const auto& poly : egriler) { for (size_t i = 0; i + 1 < poly.size(); ++i) { const Vec3d a = d.donusum.noktaUygula(poly[i]); const Vec3d b = d.donusum.noktaUygula(poly[i + 1]); const Vec3f af{(f32)a.x, (f32)a.y, (f32)a.z}; const Vec3f bf{(f32)b.x, (f32)b.y, (f32)b.z}; kenarlar.push_back({{af.x, af.y, af.z}, 0}); kenarlar.push_back({{bf.x, bf.y, bf.z}, 0}); if (!sinirVar) { sinir.asgari = sinir.azami = af; sinirVar = true; } sinir.genislet(af); sinir.genislet(bf); } } } if (kenarlar.empty()) { if (k.profilDugum) { sahne_.sil(k.profilDugum); k.profilDugum = 0; } return; } if (!sinirVar) return; // guard: gecersiz bbox (NaN fit onleme) const Vec3f profilRenk{1.0f, 0.25f, 0.30f}; if (k.profilDugum && sahne_.bul(k.profilDugum)) { sahne_.kenarNesneGuncelle(k.profilDugum, kenarlar, sinir); } else { const Varlik grup = grupAl(kesitGrup_, "Kesitler"); k.profilDugum = sahne_.kenarNesneEkle("Kesit Profili", grup, profilRenk, kenarlar, sinir); } sahne_.kenarNesneKalin(k.profilDugum, kesitProfilKalin); // ayar degisimi sonraki guncellemede } // ---- Kesit kapagi (capping — kesilen ic DOLU + veri-renkli) ---------------- // Her aktif kesit icin: gorunur brep govdelerin duzlem-ici kapak yuzu OCCT // Common ile cikarilir, ucgenler tek mesh dugumune toplanir. Vertex renkleri: // Kalinlik analizi → kesit konturuna 2×mesafe (YAKLASIK yerel cidar, MAGMA tarzi) // Diger PerYuz alanlar (draft/undercut/radius/kurvatur) → en yakin yuzey degeri // Analiz yok → malzeme rengi (duz dolu kapak) // Saydamlik render'da push-constant alfa ile uygulanir (kapakAlfa()). void AnalizSistemi::kesitKapakGuncelle(KesitDuzlemi& k) { const auto kapakSil = [&] { if (k.kapakDugum) { sahne_.sil(k.kapakDugum); k.kapakDugum = 0; } }; if (!k.aktif || kapakKip == KapakKip::Yok) { kapakSil(); return; } // GPU stencil kapağı: DÜZ (analiz-renksiz) kapak → OCCT boolean+remesh ATLA, // render GPU'da anlık çizsin. Yalnız analiz-renkli gövde varsa OCCT'ye devam. if (gpuKapak) { bool analizRenk = false; for (Dugum& d : sahne_.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || !sahne_.agacGorunur(d.kimlik)) continue; if (d.indeksSayi == 0) continue; const RenderBilgi rb = renderBilgi(d.kimlik); const bool kalinlikKip = rb.var && rb.tip == AnalizTip::Kalinlik; const bool alanKip = rb.var && !kalinlikKip && rb.alan && rb.alan->yerlesim == SonucAlan::Yerlesim::PerYuz && (rb.tip == AnalizTip::Draft || rb.tip == AnalizTip::Undercut || rb.tip == AnalizTip::Radius || rb.tip == AnalizTip::Kurvatur); if (kalinlikKip || alanKip) { analizRenk = true; break; } } if (!analizRenk) { kapakSil(); return; } } const Vec3f nk = k.nokta(); // Kapak disa normali: atilan tarafa bakar (kamera kesigi o taraftan gorur). Vec3f klipN = k.normal; if (k.ters) klipN = {-klipN.x, -klipN.y, -klipN.z}; const Vec3f kapakNormal{-klipN.x, -klipN.y, -klipN.z}; MeshVeri veri; bool sinirVar = false; for (Dugum& d : sahne_.dugumler()) { if (!d.brep || !d.brep->gecerliMi() || !sahne_.agacGorunur(d.kimlik)) continue; if (d.indeksSayi == 0) continue; const Donusumd tersD = d.donusum.ters(); const Vec3d ptY = tersD.noktaUygula({nk.x, nk.y, nk.z}); const Vec3d nY = tersD.yonelim.dondur({k.normal.x, k.normal.y, k.normal.z}); const auto ucgenler = geometri::brepKesitKapak(*d.brep, ptY, nY); if (ucgenler.empty()) continue; // ---- Renk ornekleyici (govde-basina) ---- const RenderBilgi rb = renderBilgi(d.kimlik); const bool kalinlikKip = rb.var && rb.tip == AnalizTip::Kalinlik; const bool alanKip = rb.var && !kalinlikKip && rb.alan && rb.alan->yerlesim == SonucAlan::Yerlesim::PerYuz && (rb.tip == AnalizTip::Draft || rb.tip == AnalizTip::Undercut || rb.tip == AnalizTip::Radius || rb.tip == AnalizTip::Kurvatur); std::vector> konturlar; if (kalinlikKip) konturlar = geometri::brepKesit(*d.brep, ptY, nY, 0.1); VertexIzgara izgara; if (alanKip && !d.cpuKonum.empty()) { const f32 hucre = std::max(d.yerelSinir.kosegen() / 32.0f, 1e-3f); izgara.kur(d.cpuKonum, hucre, d.yerelSinir.asgari); } const f32 kalMaks = rb.var && rb.alan && rb.alan->max > 0 ? rb.alan->max : std::max(rb.esik * 4.f, 1.f); const auto renkSec = [&](const Vec3d& yerel) -> Vec3f { if (kalinlikKip && !konturlar.empty()) { const f64 m = konturMesafe(yerel, konturlar); if (m < 0) return d.renk; const f32 cidar = (f32)(m * 2.0); // yaklasik yerel cidar Vec3f c = cpuRampaRenk(rb.rampaTip, cpuAlanNorm(cidar, 0.f, kalMaks)); if (rb.esik > 0 && cidar < rb.esik) c = karisim(c, {0.95f, 0.12f, 0.12f}, 0.6f); return c; } if (alanKip) { const i64 i = izgara.enYakin({(f32)yerel.x, (f32)yerel.y, (f32)yerel.z}); if (i >= 0 && (size_t)i < d.cpuAlt.size()) { const u32 ord = d.cpuAlt[(size_t)i]; if (ord < rb.alan->deger.size()) { const f32 deger = rb.alan->deger[ord]; if (rb.tip == AnalizTip::Draft || rb.tip == AnalizTip::Undercut) return cpuSigned(deger / 90.f, 0.02f); if (deger < 0.f) return {0.62f, 0.64f, 0.68f}; // notr (duzlem/orneksiz) // Radius item-bazli bant: yuzun yaricapina dusen bandin rengi (kapakta tutarli). if (rb.tip == AnalizTip::Radius && rb.bantlar && !rb.bantlar->empty()) { for (const auto& b : *rb.bantlar) if (deger >= b.min && deger <= b.max) return b.renk; return {0.62f, 0.64f, 0.68f}; // bant disi → notr } return cpuRampaRenk(rb.rampaTip, cpuAlanNorm(deger, rb.alan->min, rb.alan->max)); } } return {0.62f, 0.64f, 0.68f}; } return d.renk; // analiz yok → malzeme renginde dolu kapak }; const u32 taban = (u32)veri.vertexler.size(); veri.vertexler.reserve(taban + ucgenler.size()); for (size_t i = 0; i < ucgenler.size(); ++i) { const Vec3d w = d.donusum.noktaUygula(ucgenler[i]); const Vec3f wf{(f32)w.x, (f32)w.y, (f32)w.z}; const Vec3f renk = renkSec(ucgenler[i]); Vertex v{}; v.konum[0] = wf.x; v.konum[1] = wf.y; v.konum[2] = wf.z; v.normal[0] = kapakNormal.x; v.normal[1] = kapakNormal.y; v.normal[2] = kapakNormal.z; v.renk[0] = renk.x; v.renk[1] = renk.y; v.renk[2] = renk.z; v.altKimlik = 0; veri.vertexler.push_back(v); veri.indeksler.push_back(taban + (u32)i); if (!sinirVar) { veri.sinir.asgari = veri.sinir.azami = wf; sinirVar = true; } veri.sinir.genislet(wf); } } if (veri.vertexler.empty() || !sinirVar) { kapakSil(); return; } veri.yuzSayi = 1; if (k.kapakDugum && sahne_.bul(k.kapakDugum)) { if (!sahne_.hazirMeshGuncelle(k.kapakDugum, veri)) kapakSil(); // kapasite → sil+ekle } if (!k.kapakDugum || !sahne_.bul(k.kapakDugum)) { const Varlik grup = grupAl(kesitGrup_, "Kesitler"); k.kapakDugum = sahne_.hazirMeshEkle("Kesit Kapağı", grup, {0.75f, 0.75f, 0.78f}, std::move(veri)); if (Dugum* kd = sahne_.bul(k.kapakDugum)) kd->dinamikMesh = true; // mega-parti disi } } u32 AnalizSistemi::kesitKapakSahibi(u32 dugumKimlik) const { if (!dugumKimlik) return 0; for (u32 kk : kesitler_) { const KesitDuzlemi* kd = dynamic_cast(cekirdek_.nesne(kk)); if (kd && kd->kapakDugum == dugumKimlik) return kk; } return 0; } u32 AnalizSistemi::kesitProfilSahibi(u32 dugumKimlik) const { if (!dugumKimlik) return 0; for (u32 kk : kesitler_) { const KesitDuzlemi* kd = dynamic_cast(cekirdek_.nesne(kk)); if (kd && kd->profilDugum == dugumKimlik) return kk; } return 0; } // ---- Sahneden-secim ------------------------------------------------------- void AnalizSistemi::secimBaslat(u32 analizKimlik, SecimKip kip, std::function gc) { if (secKip_ == kip && secAnaliz_ == analizKimlik) { secimBitir(); return; } secKip_ = kip; secAnaliz_ = analizKimlik; secKesit_ = 0; secGc_ = std::move(gc); hud_ = kip == SecimKip::CekimYonu ? "Çekim yönü: bir YÜZ tıkla (normal = yön)" : kip == SecimKip::Hedef ? "Hedef gövde: bir gövde tıkla" : kip == SecimKip::FarkKarsi ? "Karşılaştırma (eski/referans) modeli: bir gövde tıkla" : "Kesit düzlemi: bir yüz tıkla"; } void AnalizSistemi::kesitSecimBaslat(u32 kesitKimlik, std::function gc) { if (secKip_ == SecimKip::KesitDuzlem && secKesit_ == kesitKimlik) { secimBitir(); return; } secKip_ = SecimKip::KesitDuzlem; secKesit_ = kesitKimlik; secAnaliz_ = 0; secGc_ = std::move(gc); hud_ = "Kesit düzlemi normali: bir yüz tıkla"; } void AnalizSistemi::secimBitir() { secKip_ = SecimKip::Yok; secAnaliz_ = 0; secKesit_ = 0; hud_.clear(); if (secGc_) { auto g = secGc_; secGc_ = nullptr; g(); } } int AnalizSistemi::beklenenVurguTur() const { if (secKip_ == SecimKip::Hedef || secKip_ == SecimKip::FarkKarsi) return 0; // gövde if (secKip_ == SecimKip::Yok) return -1; return 1; // yüz (cekim yonu / kesit normali) } const char* AnalizSistemi::hudMetin() const { return hud_.c_str(); } void AnalizSistemi::tik(u32 govde, u32 alt, int vurguTur, const std::function& isinVur) { if (secKip_ == SecimKip::Yok) return; if (secKip_ == SecimKip::Hedef) { if (!govde) return; if (AnalizNesnesi* a = bul(secAnaliz_)) { a->hedefGovde = govde; a->bayatAyarla(true); } cekirdek_.olaylar().yayinla(AnalizSecimYapildi{secAnaliz_, govde, 0}); const u32 an = secAnaliz_; secimBitir(); hesapla(an); return; } if (secKip_ == SecimKip::FarkKarsi) { if (!govde) return; if (AnalizNesnesi* a = bul(secAnaliz_)) { a->farkKarsiGovde = govde; a->bayatAyarla(true); } cekirdek_.olaylar().yayinla(AnalizSecimYapildi{secAnaliz_, govde, 0}); const u32 an = secAnaliz_; secimBitir(); hesapla(an); return; } // CekimYonu / KesitDuzlem: yüz normali gerekir. Vec3f nokta{}, normal{}; if (vurguTur == 1 && govde && isinVur(nokta, normal) && normal.uzunlukKare() > 1e-8f) { const Vec3f n = birimNormal(normal); if (secKip_ == SecimKip::CekimYonu) { if (AnalizNesnesi* a = bul(secAnaliz_)) { a->cekimYonu = n; a->bayatAyarla(true); } cekirdek_.olaylar().yayinla(AnalizSecimYapildi{secAnaliz_, govde, alt}); const u32 an = secAnaliz_; secimBitir(); hesapla(an); } else if (secKip_ == SecimKip::KesitDuzlem) { if (KesitDuzlemi* kd = kesitBul(secKesit_)) { kd->normal = n; kd->taban = nokta; kd->mesafe = 0; } const u32 kk = secKesit_; cekirdek_.olaylar().yayinla(AnalizSecimYapildi{0, govde, alt}); secimBitir(); kesitGuncelle(kk); } } } // ---- Render koprusu ------------------------------------------------------- // AnalizNesnesi → RenderBilgi doldur (aktif+hazir+uygun ise var=true). Ortak // mantık; renderBilgi() ve tumRenderBilgi() paylaşır. static bool renderBilgiDoldur(AnalizNesnesi* a, u32 k, AnalizSistemi::RenderBilgi& r) { if (!a || !a->aktif) return false; const AnalizTip t = a->tip(); // Analitik/CANLI-GPU (alan yok da olur): Draft/Undercut/Zebra/Reflect. const bool analitik = t == AnalizTip::Draft || t == AnalizTip::Undercut || t == AnalizTip::Zebra || t == AnalizTip::Reflect; // Sureklilik/KeskinKenar: sayisal rapor (+ keskin kenar dugumleri) → yuzey boyama YOK. if (t == AnalizTip::Sureklilik || t == AnalizTip::KeskinKenar) return false; // KRİTİK (donma fix): analitik tipler shader'da cekimYonu'ndan CANLI boyanır → // CPU alanı/hazır GEREKMEZ → aktifleşince ANINDA görsel (draftRapor beklenmez). // Skaler-alanli (Radius/Kalinlik/Kurvatur): alan zorunlu → hazır bekle. if (!analitik && (!a->sonuc.hazir || a->sonuc.yerlesim == SonucAlan::Yerlesim::Yok)) return false; r.var = true; r.tip = t; r.analizKimlik = k; r.alan = &a->sonuc; r.cekimYonu = a->cekimYonu; // Undercut: görseli PER-YÜZ görünürlük alanı sürer (sonuc = sayısal rapor olarak // kalır). Alan yoksa (kip=Gölge / kapalı / OCCT hatası) shader gölge/analitik yola // düşer — `alan` sonuc'ta kalır ama mod 1 skaler okumaz, zararsız. if (t == AnalizTip::Undercut && a->gorunurluk.hazir && a->gorunurluk.yerlesim == SonucAlan::Yerlesim::PerYuz) { r.alan = &a->gorunurluk; r.undercutYuzAlani = true; if (!a->golgeIndeks.empty()) { r.golgeKonum = &a->golgeKonum; r.golgeNormal = &a->golgeNormal; r.golgeIndeks = &a->golgeIndeks; r.golgeSurum = a->golgeSurum; } } // esik/maks: Radius min-R; Kalinlik ince duvar; Kurvatur alan min/maks (rampa). if (t == AnalizTip::Radius) { r.esik = a->radiusEsik; r.maks = a->radiusMaks; } // Kalinlik: rampa sınırları ÖLÇÜLEN min/max (sonuc.min/max). Eskiden shader // sabit [kalinlikInce, 90] alıyordu ama panel lejantı sonuc.min/max yazıyordu → // LEJANT İLE 3B RENK BİRBİRİNİ TUTMUYORDU (ör. lejant 0–70 derken shader 2–90). else if (t == AnalizTip::Kalinlik) { r.esik = a->sonuc.min; r.maks = a->sonuc.max; } else if (t == AnalizTip::Kurvatur) { r.esik = a->sonuc.min; r.maks = a->sonuc.max; } else if (t == AnalizTip::Fark) { r.esik = a->sonuc.min; r.maks = a->sonuc.max; } else { r.esik = 0.f; r.maks = 90.f; } r.tersEsik = t == AnalizTip::Undercut ? 0.f : -0.3f; r.zebraSerit = a->zebraSerit; r.zebraAci = a->zebraAci; r.rampaTip = a->rampaTip; r.bantlar = &a->araliklar; // Radius item-bazli bantlar → mod 7 discrete boyama if (t == AnalizTip::Fark) { // YENİ gövde = eklenen/yüzey sapması. "Sadece Eski" veya "Sadece Silinen" → gizle. if (a->farkViewMode == 1 || a->farkTip == 2) return false; r.farkGovdesi = true; r.farkKenarGoster = a->farkKenarGoster; r.farkRenkKenar = a->farkRenkKenar; // Fark HER kipte paketli-renk (mod 7) yoluyla boyanir: binary (ayrik) veya // web gradyani (SAME→turuncu→degisim). Rampa CPU'da pisirilir, tampon imzasi // tolerans+renkleri icerir → slider canli. r.farkGradyan = a->farkGorunum == 0; // pürüzsüz signed diverging (mod 8, metroloji) r.farkAyrik = a->farkGorunum == 1; // ayrık binary (mod 7, flat crisp) // Yeşil band = hesaplama toleransı (bunun altı "OK"). Binary'de bu eşik değişimi belirler. r.farkTolerans = a->farkGorunum == 1 ? a->farkToleransCozulmus : std::max(a->farkTolerans, 1e-4f); // Renkler TAMAMEN PANELDEN (farkRenkAyni/Eklenen/Silinen seçicileri) — merkez/ // tolerans-içi/ghost rengi de "Aynı" seçicisinden (kullanıcı isteği: hep formdan). r.farkRenkAyni = a->farkRenkAyni; // Signed diverging uçları (metroloji): + (dışa) = EKLENEN, − (içe) = SILINEN. r.farkRenkArti = a->farkRenkEklenen; r.farkRenkEksi = a->farkRenkSilinen; // Renk skalası ± ucu = "Görüntüleme hassasiyeti" slider'ı (GOM legend range ayarı): // KÜÇÜLT → küçük değişiklikler (radius/kapatma) tam renk; BÜYÜT → büyük (silindir) // gradient, küçükler yeşile yakın. Deviation bu değerde tam mavi/kırmızıya doyar. r.farkMaksAralik = std::max(a->farkToleransCozulmus, std::max(a->farkTolerans, 1e-4f) * 1.5f); // Yüzey filtresi (Fark Filtre Ayarları): bu mesafenin altındaki sapma bastırılır // (tessellation gürültüsü/küçük dalgalanma → merkez rengi). "Sadece farklar"da aktif. r.farkGurultuTaban = a->farkSadeceFark ? std::max(a->farkTolerans, 1e-4f) * (a->farkYuzeyFiltre / 10.0f) : 0.0f; // "Sadece Farkları Göster" → GHOST kip: değişmeyen bölge saydam, farklar solid. r.farkGhostKip = a->farkSadeceFark; r.farkGhostOp = std::clamp(a->farkGhostOpaklik, 0.0f, 1.0f); // Sınır hassasiyeti (subdivisionDepth 0-5): ghost↔solid geçiş keskinliği (yüksek = keskin). r.farkSinirKeskin = 0.30f / (1.0f + (f32)a->farkSinirDerinlik); // 0→0.30 (yumuşak), 5→0.05 (keskin) // Ghost kip (Sadece Farkları): YENİ gövde YALNIZ eklenen (pozitif, dışa/mavi) gösterir; // içe (negatif) = eski gövdenin silinen'iyle çakışır → o eski gövdede kırmızı. Çift YOK. r.farkTekYon = a->farkSadeceFark ? 1 : 0; // Binary (mod 7) için: tolerans-üstü düz renk (tip filtresi çözülmüş). r.farkRenkDegisim = a->farkTip == 2 ? r.farkRenkAyni : a->farkRenkEklenen; } return true; } // ESKİ (referans) gövde = SİLİNEN malzeme gösterimi. Eski gövdenin yeni yüzeye // sapması → silinen bölgeler (eski malzeme yenide yok) KIRMIZI. DAİMA GHOST // (transparan, derinlik yazmaz) → yeni gövdeyle üst üste z-fight YOK, silinen // hacimler yeni modelin üstünde kırmızı belirir. Tür=Sadece Eklenen → gizli. static bool farkEskiBilgiDoldur(AnalizNesnesi* a, u32 k, u32 govde, AnalizSistemi::RenderBilgi& r) { if (!a || !a->aktif || a->tip() != AnalizTip::Fark || !a->sonuc.hazir) return false; if (a->farkKarsiGovde != govde || !a->farkSonucEski.hazir) return false; if (a->farkTip == 1 || a->farkViewMode == 2) return false; // Sadece Eklenen/Yeni → gizle r.var = true; r.tip = AnalizTip::Fark; r.analizKimlik = k; r.alan = &a->farkSonucEski; r.farkGovdesi = true; r.farkKenarGoster = a->farkKenarGoster; r.farkRenkKenar = a->farkRenkKenar; r.farkGradyan = a->farkGorunum == 0; r.farkAyrik = a->farkGorunum == 1; r.farkTolerans = std::max(a->farkTolerans, 1e-4f); r.farkMaksAralik = std::max(a->farkToleransCozulmus, r.farkTolerans * 1.5f); // Eski gövdede HER iki işaret de "silinen" (eski malzeme yenide yok) → SILINEN rengi. r.farkRenkArti = a->farkRenkSilinen; r.farkRenkEksi = a->farkRenkSilinen; r.farkRenkAyni = a->farkRenkAyni; // eşleşen/ghost merkez = panelden "Aynı" (formdan) r.farkRenkDegisim = a->farkRenkSilinen; // DAİMA ghost: eşleşen bölge saydam, silinen solid → yeni gövde üstüne temiz overlay. r.farkGhostKip = true; const f32 gop = std::clamp(a->farkGhostOpaklik > 0 ? a->farkGhostOpaklik : 0.15f, 0.0f, 0.9f); // Heatmap kipinde (Sadece Farkları KAPALI) yeni gövde tam gradient odaktır → eski // gövde yalnızca FAINT REFERANS (ghost op yarıya) olsun; ghost kipte panel değeri tam. r.farkGhostOp = a->farkSadeceFark ? gop : gop * 0.5f; r.farkSinirKeskin = 0.30f / (1.0f + (f32)a->farkSinirDerinlik); r.farkGurultuTaban = std::max(a->farkTolerans, 1e-4f) * (a->farkYuzeyFiltre / 10.0f); // Eski gövde YALNIZ pozitif (eski malzeme yenin dışında = SİLİNEN, kırmızı); negatif // (eski içeride = yeninin eklediği) ghost → yeni gövdedeki mavi ile çakışma YOK. r.farkTekYon = 1; return true; } AnalizSistemi::RenderBilgi AnalizSistemi::renderBilgi(u32 govde) const { RenderBilgi r; // Bu govdeyi hedefleyen ilk AKTIF, hazir analiz. for (u32 k : analizler_) { AnalizNesnesi* a = dynamic_cast(cekirdek_.nesne(k)); if (!a) continue; if (a->hedefGovde == govde && renderBilgiDoldur(a, k, r)) return r; if (farkEskiBilgiDoldur(a, k, govde, r)) return r; // eski gövde = silinen (kırmızı ghost) } return r; } std::unordered_map AnalizSistemi::tumRenderBilgi() const { std::unordered_map harita; // Her analiz için: YENİ gövde (eklenen) + ESKİ gövde (silinen ghost) — ikisi de. for (u32 k : analizler_) { AnalizNesnesi* a = dynamic_cast(cekirdek_.nesne(k)); if (!a) continue; if (!harita.count(a->hedefGovde)) { RenderBilgi r; if (renderBilgiDoldur(a, k, r)) harita.emplace(a->hedefGovde, r); } if (a->tip() == AnalizTip::Fark && a->farkKarsiGovde && !harita.count(a->farkKarsiGovde)) { RenderBilgi r; if (farkEskiBilgiDoldur(a, k, a->farkKarsiGovde, r)) harita.emplace(a->farkKarsiGovde, r); } } return harita; } bool AnalizSistemi::farkGizliGovde(u32 kimlik) const { // Eski (referans) gövde artık analiz pipeline'ında SİLİNEN ghost olarak çizilir // (farkEskiBilgiDoldur → transparan, derinlik yazmaz → z-fight yok). Ayrıca gizleme // GEREKMEZ. YALNIZ "Sadece Eklenen/Yeni" kipinde eski hiç boyanmaz VE model // pipeline'ında da görünmemeli → o durumda gizle (aksi halde renksiz opak çift). for (u32 k : analizler_) { AnalizNesnesi* a = dynamic_cast(cekirdek_.nesne(k)); if (!a || !a->aktif || a->tip() != AnalizTip::Fark || !a->sonuc.hazir) continue; if (a->farkKarsiGovde == kimlik && (a->farkTip == 1 || a->farkViewMode == 2)) return true; // Sadece Eklenen/Yeni → eski gövde tamamen gizli } return false; } bool AnalizSistemi::farkGizliAktifMi() const { for (u32 k : analizler_) { AnalizNesnesi* a = dynamic_cast(cekirdek_.nesne(k)); if (!a || !a->aktif || a->tip() != AnalizTip::Fark || !a->sonuc.hazir) continue; if (a->farkTip == 1 || a->farkViewMode == 2) return true; // en az bir gizleyen fark } return false; } std::vector AnalizSistemi::aktifKesitler() const { std::vector r; for (u32 k : kesitler_) { KesitDuzlemi* kd = dynamic_cast(cekirdek_.nesne(k)); if (!kd || !kd->aktif) continue; if (r.size() >= 4) break; // shader push-constant siniri: 4 duzlem Vec3f n = kd->normal; if (kd->ters) n = {-n.x, -n.y, -n.z}; const Vec3f p = kd->nokta(); // dot(worldPos, n) + d < 0 → atilir. d = -dot(p, n). r.push_back({n, -(p.x * n.x + p.y * n.y + p.z * n.z), k}); } return r; } // ---- KesitDuzlemi ozellikleri --------------------------------------------- OzellikSeti KesitDuzlemi::ozellikler(Dunya&) { OzellikSeti o; o.push_back(Ozellik::bayrak("Aktif", [this] { return aktif; }, [this](bool v) { aktif = v; })); o.push_back(Ozellik::bayrak("Ters", [this] { return ters; }, [this](bool v) { ters = v; })); o.push_back(Ozellik::sayi("Mesafe", [this] { return (f64)mesafe; }, [this](f64 v) { mesafe = (f32)v; })); return o; } } // namespace fiko::analiz