#include "fiko/sahne/secim_sistemi.hpp" #include #include "fiko/cekirdek/olay.hpp" #include "fiko/render/referans_olcek.hpp" #include "fiko/sahne/sahne.hpp" namespace fiko { void SecimSistemi::vurguAyarla(u32 govde, u32 alt, u32 tur) { const SecimOge yeni{govde, alt}; const bool degisti = !(sahne_.vurgu == yeni) || vurguTur_ != tur; sahne_.vurgu = yeni; vurguTur_ = tur; if (degisti) olaylar_.yayinla(VurguDegisti{govde, alt, tur}); } // GPU kenar/nokta adayı öndeki CPU yüz vuruşunun ARKASINDA mı? (Datum'lar hariç — // onlar bilerek derinlik-testsiz seçilir.) Kenar pick'i derinlik-bias'la öne çekilir // ve yüz pass'ı artık GPU pick'e çizilmez → duvar arkası kenar/nokta seçilebiliyordu. // Çözüm: adayın ışına en yakın noktasının dünya-t'si, yüz vuruşundan belirgin // uzaktaysa (arkadaysa) bastır. Aday değişmedikçe sonuç önbellekten döner. bool SecimSistemi::kenarNoktaOrtulu(u32 tur, u32 govde, u32 alt) { if (!pickAyar().derinlikOrtme || !cpuYuzVar_ || !hoverIsinVar_) return false; if (govde == ortGovde_ && alt == ortAlt_ && tur == ortTur_ && cpuYuzT_ == ortYuzT_) return ortSonuc_; // aynı aday + aynı yüz vuruşu → segment taraması tekrarlanmaz ortGovde_ = govde; ortAlt_ = alt; ortTur_ = tur; ortYuzT_ = cpuYuzT_; ortSonuc_ = false; const Dugum* d = sahne_.bul(govde); if (!d) return false; // Datum/referans türleri örtme-muaf (görselde de hep üstteler). if (d->tip == DugumTip::Duzlem || d->tip == DugumTip::Eksen || d->tip == DugumTip::EksenSistemi || d->tip == DugumTip::Nokta || d->tip == DugumTip::Cizgi) return false; // Işını düğüm yerel uzayına taşı (cpuKenar/cpuNokta yerelde). const Donusumd ters = d->donusum.ters(); const Vec3d o0 = ters.noktaUygula({(f64)hoverOrij_.x, (f64)hoverOrij_.y, (f64)hoverOrij_.z}); const Vec3d o1 = ters.noktaUygula({(f64)(hoverOrij_.x + hoverYon_.x), (f64)(hoverOrij_.y + hoverYon_.y), (f64)(hoverOrij_.z + hoverYon_.z)}); const Vec3f lo{(f32)o0.x, (f32)o0.y, (f32)o0.z}; const Vec3f ld = Vec3f{(f32)(o1.x - o0.x), (f32)(o1.y - o0.y), (f32)(o1.z - o0.z)}.birim(); // Adayın ışına EN YAKIN noktasını bul (yerel) — imlecin gerçekten üzerinde // gezindiği nokta budur; onun derinliği yüz vuruşuyla karşılaştırılır. f32 enUzaklikK = 3.0e30f; Vec3f enYakinNokta{}; bool bulundu = false; const auto adayNokta = [&](const Vec3f& p) { const f32 t = (p - lo).nokta(ld); const Vec3f fark = p - (lo + ld * std::max(t, 0.0f)); const f32 uz = fark.uzunlukKare(); if (uz < enUzaklikK) { enUzaklikK = uz; enYakinNokta = p; bulundu = true; } }; if (tur == 2) { const size_t n = std::min(d->cpuKenar.size(), d->cpuKenarAlt.size()) & ~size_t{1}; for (size_t i = 0; i + 1 < n; i += 2) { if (d->cpuKenarAlt[i] != alt) continue; // Işın-segment en yakın yaklaşma: s∈[0,1] klemplenir, ardından ışına izdüşüm. const Vec3f a = d->cpuKenar[i], b = d->cpuKenar[i + 1]; const Vec3f u = b - a, w = lo - a; const f32 B = ld.nokta(u), C = u.nokta(u); const f32 payda = C - B * B; f32 s = payda > 1e-12f ? (u.nokta(w) - ld.nokta(w) * B) / payda : 0.0f; s = std::clamp(s, 0.0f, 1.0f); adayNokta(a + u * s); } } else { const size_t n = std::min(d->cpuNokta.size(), d->cpuNoktaAlt.size()); for (size_t i = 0; i < n; ++i) if (d->cpuNoktaAlt[i] == alt) adayNokta(d->cpuNokta[i]); } if (!bulundu) return false; // Dünya-t karşılaştırması: yüzeye OTURAN kenar/nokta (silüet/sınır) toleransta // kalır; gövde arkasındaki kenar belirgin uzaktır → bastır. const Vec3d dh = d->donusum.noktaUygula( {(f64)enYakinNokta.x, (f64)enYakinNokta.y, (f64)enYakinNokta.z}); const f32 t = (Vec3f{(f32)dh.x, (f32)dh.y, (f32)dh.z} - hoverOrij_).nokta(hoverYon_); ortSonuc_ = t > cpuYuzT_ + std::max(cpuYuzT_ * 0.01f, 1e-5f); return ortSonuc_; } void SecimSistemi::hoverCozumle(u32 kareIndeks, bool vpHover) { gpu_.sonucOku(kareIndeks); if (hoverBastir_) return; // alt-liste acik → vurgu programatik kontrol ediliyor if (!vpHover) return; const SecimVurusu vurus = gpu_.sonVurus(); SecimOge yeniVurgu{}; u32 yeniTur = 0; // Öncelik: GPU nokta/kenar (spesifik, ucuz pass) > CPU BVH yüz/gövde (pahalı GPU yüz-pass // yerine — rough mesh hızlanması) > GPU yüz/gövde fallback (CPU yüz kapalıyken eski davranış). // Kenar/nokta adayı öndeki yüzün ARKASINDAysa bastırılır (kenarNoktaOrtulu) → yüze düşer. // CPU kenar öndeki yüzün ARKASINDA mı? (Doğrudan derinlik karşılaştırması — segment // yeniden-taraması yok, cpuKenarT_ zaten pick'te hesaplandı.) Yüz yoksa örtme yok. const bool cpuKenarOrtulu = cpuYuzVar_ && cpuKenarT_ > cpuYuzT_ + std::max(cpuYuzT_ * 0.01f, 1e-5f); if (vurus.govde != 0 && vurus.tur == SecimGpu::TUR_NOKTA && fNokta && !kenarNoktaOrtulu(3, vurus.govde, vurus.alt)) { yeniVurgu = {vurus.govde, vurus.alt}; yeniTur = 3; } else if (cpuKenarVar_ && cpuKenarGovde_ != 0 && fKenar && !cpuKenarOrtulu) { yeniVurgu = {cpuKenarGovde_, cpuKenarAlt_}; yeniTur = 2; } else if (vurus.govde != 0 && vurus.tur == SecimGpu::TUR_KENAR && fKenar && !kenarNoktaOrtulu(2, vurus.govde, vurus.alt)) { yeniVurgu = {vurus.govde, vurus.alt}; yeniTur = 2; } else if (cpuYuzVar_ && cpuYuzGovde_ != 0 && fYuz) { yeniVurgu = {cpuYuzGovde_, cpuYuzAlt_}; yeniTur = 1; } else if (cpuYuzVar_ && cpuYuzGovde_ != 0 && fGovde) { yeniVurgu = {cpuYuzGovde_, 0}; yeniTur = 0; } else if (vurus.govde != 0 && vurus.tur == SecimGpu::TUR_YUZ && fYuz) { yeniVurgu = {vurus.govde, vurus.alt}; yeniTur = 1; } else if (vurus.govde != 0 && fGovde) { yeniVurgu = {vurus.govde, 0}; yeniTur = 0; } const bool degisti = !(sahne_.vurgu == yeniVurgu) || vurguTur_ != yeniTur; sahne_.vurgu = yeniVurgu; vurguTur_ = yeniTur; if (degisti) olaylar_.yayinla(VurguDegisti{yeniVurgu.govde, yeniVurgu.alt, yeniTur}); } void SecimSistemi::pickGecisiKaydet(VkCommandBuffer kb, const Mat4f& vp, i32 imlecX, i32 imlecY, f32 kameraMesafe, bool yuzPassAtla, bool kenarPassAtla) { gpu_.gecisBasla(kb, imlecX, imlecY); // Kare-başı frustum kültü: görünmeyen katı gövdeler pick geçişinde de atlanır // (render ile aynı görünürlük → seçim davranışı bozulmaz). Frustum frustum = Frustum::kur(vp); // İMLEÇ-BÖLGE KÜLTÜ (opsiyonel, agent B#3): pick yalnız imleç scissor'ına render eder ama // TÜM görünür düğümleri çizer (scissor dışı fragment atılsa da draw-call+vertex maliyeti). // vp'yi scissor NDC alt-dikdörtgenine daraltıp o frustum'la kült → imleç uzağındaki düğümler // TÜMÜYLE elenir (yoğun sahnede hover draw-call düşer). Cömert pad (≥%10 viewport) → bounding // küre testi konservatif kalır, imleç altındaki hiçbir nesne kaçmaz. Datum'lar kült-dışı (hep // seçilebilir). Deneysel → varsayılan KAPALI (AyarSistemi "secim.imlec_bolge_kult"). if (pickAyar().imlecBolgeKult) { const VkExtent2D vpb = gpu_.boyut(); if (vpb.width > 0 && vpb.height > 0) { const f32 W = (f32)vpb.width, H = (f32)vpb.height; const f32 R = (f32)std::clamp(pickAyar().yaricap, 0, 64) + std::max(W, H) * 0.10f; const f32 cx = (f32)imlecX, cy = (f32)imlecY; const auto ndcX = [&](f32 px) { return 2.0f * px / W - 1.0f; }; // Vulkan NDC const auto ndcY = [&](f32 py) { return 2.0f * py / H - 1.0f; }; const f32 nx0 = ndcX(cx - R), nx1 = ndcX(cx + R); const f32 ny0 = ndcY(cy - R), ny1 = ndcY(cy + R); const f32 sx = 2.0f / (nx1 - nx0), tx = -(nx1 + nx0) / (nx1 - nx0); const f32 sy = 2.0f / (ny1 - ny0), ty = -(ny1 + ny0) / (ny1 - ny0); Mat4f T = Mat4f::birim(); // clip'.x = sx·clip.x + tx·clip.w ; y benzer (e[col][row]) T.e[0][0] = sx; T.e[3][0] = tx; T.e[1][1] = sy; T.e[3][1] = ty; frustum = Frustum::kur(T * vp); } } // Ekran-sabit referans düğümü (düzlem/eksen) pick MVP'si render ile aynı ölçekte // olmalı → seçim görsel boyutla eşleşir. Diğer düğümler dünya-ölçekli. const auto pickMvp = [&](const Dugum& d) -> Mat4f { // Ekran-sabit olcek grubu (referans Cizgi HARIC: dunya-olcekli → pick de dunya). const bool datum = d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta; if (kameraMesafe > 0.0f && datum) { const Vec3f pivot{(f32)d.donusum.konum.x, (f32)d.donusum.konum.y, (f32)d.donusum.konum.z}; // Görsel=pick tutarlılığı: aynı tip çarpanı (eksen/düzlem) + düğüm katsayısı. const f32 tipOran = (d.tip == DugumTip::Duzlem) ? REFERANS_ORAN_DUZLEM : REFERANS_ORAN_EKSEN; return referansGorselMvp(vp, d.matris(), pivot, kameraMesafe, tipOran, d.gorselOran); } return vp * d.matris(); }; // Referans datum (düzlem/eksen/eksen-sistemi/nokta/çizgi) — kesit clip'inden MUAF // (render'da da kırpılmaz) + pick MVP'si ekran-sabit ölçekli olduğundan model // matrisi dünya konumunu vermez → onlara kesit gönderilmez. const auto datumMu = [](const Dugum& d) { return d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi; // referans nokta/cizgi de derinlik-testsiz pick }; // Aktif kesit düzlemleri → shader push-constant düzeni (vec4[4]). Kesit yoksa // sayı 0 → itmeYaz eski davranışı birebir yazar. f32 kesitDuz[16] = {}; u32 kesitSayi = 0; for (const auto& k : sahne_.kirpDuzlemleri()) { if (kesitSayi >= 4) break; kesitDuz[kesitSayi * 4 + 0] = k.normal.x; kesitDuz[kesitSayi * 4 + 1] = k.normal.y; kesitDuz[kesitSayi * 4 + 2] = k.normal.z; kesitDuz[kesitSayi * 4 + 3] = k.d; ++kesitSayi; } const auto pickItme = [&](const Dugum& d, u32 tur) { const u32 ks = datumMu(d) ? 0u : kesitSayi; gpu_.itmeYaz(kb, pickMvp(d), d.kimlik, tur, d.matris(), ks ? kesitDuz : nullptr, ks); }; // Filtreye gore acik turleri ayni geciste ciz; en spesifik/orten kazanir. // Yuz gecisi hem yuz hem govde secimi icin gerekir. yuzPassAtla → CPU BVH yüz pick // aktif (belge_ciz) → pahalı YÜZ vertex-pass'ı ÇİZME (rough mesh hızlanması). if ((fYuz || fGovde) && !yuzPassAtla) { gpu_.yuzBagla(kb); for (auto& d : sahne_.dugumler()) { if (!sahne_.agacGorunurHizli(d.kimlik) || !dugumFrustumGorunur(d, frustum)) continue; pickItme(d, SecimGpu::TUR_YUZ); // YÜZ pick KABA mesh ile (varsa): altKimlik=yüz ordinali kaba tessellation'da da doğru, // ~10× az üçgen → rough modelde hover/seçim rasterleme yükü düşer. Kaba yoksa ince. const bool kaba = d.kabaIndeksSayi > 0; const VkBuffer vt = (kaba ? d.kabaVertexTampon : d.vertexTampon).tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdBindIndexBuffer(kb, (kaba ? d.kabaIndeksTampon : d.indeksTampon).tampon(), 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(kb, kaba ? d.kabaIndeksSayi : d.indeksSayi, 1, 0, 0, 0); } } // Datum kenar/nokta pick geçişi — derinlik kapalı (render'daki refKenarHat ile aynı politika → // görsel "üstte" davranışıyla eşleşir). datumMu düğümler burada çizilir (model geçişi onları atlar). // Sıra ÖNEMLİ: datumArkada iken bu geçiş model kenar/noktasından ÖNCE çağrılır → aynı pikselde // model üste yazar (model kenarı kazanır, datum yalnız boşlukta seçilir → hover çalınmaz). const auto datumGecisi = [&] { if (fKenar) { gpu_.refKenarBagla(kb); for (auto& d : sahne_.dugumler()) { if (!sahne_.agacGorunurHizli(d.kimlik) || d.kenarVertexSayi == 0 || !datumMu(d)) continue; pickItme(d, SecimGpu::TUR_KENAR); const VkBuffer vt = d.kenarTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d.kenarVertexSayi, 1, 0, 0); } } if (fNokta) { gpu_.refNoktaBagla(kb); for (auto& d : sahne_.dugumler()) { if (!sahne_.agacGorunurHizli(d.kimlik) || d.noktaSayi == 0 || !datumMu(d)) continue; pickItme(d, SecimGpu::TUR_NOKTA); const VkBuffer vt = d.noktaTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d.noktaSayi, 1, 0, 0); } } }; if (datumArkada) datumGecisi(); // datum ÖNCE → model üste yazar (akıllı bastırma) // Referans datum: normal derinlikli MODEL geçişinde ATLA (datumMu skip); yukarıdaki/aşağıdaki // derinlik-testsiz geçişte çizilir → önlerinde katı olsa da seçilebilir (occlusion-bağımsız). if (fKenar && !kenarPassAtla) { // kenarPassAtla → MODEL kenarları CPU pick'te (datum ayrı pass'ta) gpu_.kenarBagla(kb); for (auto& d : sahne_.dugumler()) { if (!sahne_.agacGorunurHizli(d.kimlik) || d.kenarVertexSayi == 0 || datumMu(d) || !dugumFrustumGorunur(d, frustum)) continue; pickItme(d, SecimGpu::TUR_KENAR); const VkBuffer vt = d.kenarTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d.kenarVertexSayi, 1, 0, 0); } } if (fNokta) { gpu_.noktaBagla(kb); for (auto& d : sahne_.dugumler()) { if (!sahne_.agacGorunurHizli(d.kimlik) || d.noktaSayi == 0 || datumMu(d) || !dugumFrustumGorunur(d, frustum)) continue; pickItme(d, SecimGpu::TUR_NOKTA); const VkBuffer vt = d.noktaTampon.tampon(); const VkDeviceSize ofs = 0; vkCmdBindVertexBuffers(kb, 0, 1, &vt, &ofs); vkCmdDraw(kb, d.noktaSayi, 1, 0, 0); } } if (!datumArkada) datumGecisi(); // datum SONRA → üstte kazanır (klasik davranış, DM dışı) gpu_.gecisBitir(kb); } void SecimSistemi::tikla(bool coklu) { // Tiklanan elemanin turune gore aktif secim kipini ayarla (tek-tur secim // kumesi). Tur degisince kipAyarla mevcut secimi temizler. const SecimKip hedefKip = (vurguTur_ == 1) ? SecimKip::Yuz : (vurguTur_ == 2) ? SecimKip::Kenar : (vurguTur_ == 3) ? SecimKip::Nokta : SecimKip::Govde; // kipAyarla vurguyu temizler; sec icin once yakala (aksi halde ilk tik bosa gider). const SecimOge secilecek = sahne_.vurgu; if (sahne_.kip() != hedefKip) sahne_.kipAyarla(hedefKip); sahne_.sec(secilecek, coklu); } } // namespace fiko