#pragma once // CATIA tarzı Selection Stack ("Other Selection"): Alt+sol tık → imleç ışınının // kestiği ÜST ÜSTE tüm aday nesneler (mesafeye sıralı, yakın→uzak). İmleç yanında // liste popup + 3B şeffaf daire overlay (aktif aday daire içinde vurgulanır). // Tekerlek/ok ile gez, tık/Enter ile seç, ESC ile iptal. Canlı highlight: gezilen // aday anında sahne-vurgusu olur (SecimSistemi::vurguAyarla), önceki kalkar. // // Modüler tasarım (kullanıcı isteği): SecimAday = aday, SecimYigini = durum makinesi // + overlay. Işın toplama Sahne::isinVurCoklu'da (BVH). Kenar/Nokta katmanı sonra // SecimFiltre ile eklenir (şu an Yüz + Gövde). #include #include #include #include #include #include #include #include "fiko/sahne/sahne.hpp" #include "fiko/sahne/secim_sistemi.hpp" #include "imgui.h" #include "secim_gorunum.hpp" #include "ui_ortak.hpp" namespace fiko { // Tek seçilebilir aday (bir ışın-katmanı). struct SecimAday { u32 govde = 0; u32 alt = 0; // yüz ordinali (0 = tüm gövde) u32 tur = 1; // 0 gövde, 1 yüz (ileride 2 kenar, 3 nokta) f32 mesafe = 0; // kameraya mesafe (mm) Vec3f nokta{}; // dünya vuruş noktası (3B daire konumu) std::string ad; // gövde adı const char* simge = "◼"; const char* tipAd = "Yüz"; }; class SecimYigini { public: bool acikMi() const { return acik_; } // Alt+tık: imleç altında topla (yüz+kenar+nokta+gövde), AKTİF FİLTREYE göre eleyerek // (fYuz/fKenar/fNokta/fGovde) — kullanıcı üst-çubuk filtresini görür, liste tutarlı. // refBeklenenTur >= 1 → feature ref seçimi: yalnız o tür aday (2=kenar, 1=yüz, 3=nokta). // Hiçbir filtre aday bırakmazsa gövde adayı gösterilir (kullanıcı boş liste görmesin). // Yüz ışın-BVH'den (tüm derinlik katmanları); kenar/nokta imleç altındaki TÜM görünür // gövdelerde ekran-uzayı yakınlıkla (silüet kenarı, tel gövde, sketch dahil; per-tık hızlı). void ac(Sahne& sahne, Vec3f orij, Vec3f yon, const Mat4f& vp, ImVec2 vpMin, ImVec2 vpBoyut, ImVec2 imlec, const SecimSistemi& sec, int refBeklenenTur = -1) { adaylar_.clear(); aktif_ = 0; acik_ = false; const f32 tol = secimGorunum().yiginTol; // px yakalama toleransı (Ayarlar) std::unordered_map govdeAday; // gövde-başı (en yakın) — sona eklenir // Aktif seçim filtresi (üst-çubuk toggle) + ref-mod tür kısıtı: tür izinli mi? auto turIzinli = [&](u32 tur) -> bool { if (refBeklenenTur >= 1) return (int)tur == refBeklenenTur; // ref modu: tek tür switch (tur) { case 1: return sec.fYuz; case 2: return sec.fKenar; case 3: return sec.fNokta; default: return sec.fGovde; // 0 gövde } }; auto ekle = [&](u32 gk, u32 alt, u32 tur, f32 mes, Vec3f w, const std::string& ad, const char* tipAd) { if (turIzinli(tur)) adaylar_.push_back({gk, alt, tur, mes, w, ad, turSimge(tur), tipAd}); auto it = govdeAday.find(gk); if (it == govdeAday.end() || mes < it->second.mesafe) govdeAday[gk] = {gk, 0u, 0u, mes, w, ad, turSimge(0u), "Gövde"}; }; // 1) Yüz adayları (ışın-BVH — tüm derinlik katmanları). for (const Sahne::IsinVurus& v : sahne.isinVurCoklu(orij, yon)) { Dugum* d = sahne.bul(v.govde); if (!d) continue; ekle(v.govde, v.alt, 1u, v.t, v.nokta, d->ad.empty() ? "Gövde" : d->ad, "Yüz"); } // 2) Kenar / Nokta — imleç altındaki TÜM görünür gövdeler (ekran-bbox küllemesi). for (Dugum& d : sahne.dugumler()) { if (!sahne.agacGorunur(d.kimlik)) continue; if (d.cpuKenar.empty() && d.cpuNokta.empty()) continue; const Mat4f m = d.matris(); if (!bboxImlecYakin(m, d.yerelSinir, vp, vpMin, vpBoyut, imlec, tol)) continue; const std::string ad = d.ad.empty() ? "Gövde" : d.ad; // Kenar: cpuKenar segment (2i,2i+1) → ekran; imleç-segment mesafesi < tol. std::unordered_set kenarGorulen; for (size_t i = 0; i + 1 < d.cpuKenar.size(); i += 2) { ImVec2 sa, sb; if (!projekteEkran(vp, vpMin, vpBoyut, dunya(m, d.cpuKenar[i]), sa)) continue; if (!projekteEkran(vp, vpMin, vpBoyut, dunya(m, d.cpuKenar[i + 1]), sb)) continue; if (nokSegMesafe(imlec, sa, sb) > tol) continue; const u32 ord = i < d.cpuKenarAlt.size() ? d.cpuKenarAlt[i] : 0; if (!kenarGorulen.insert(ord).second) continue; // kenar-başı tekil const Vec3f w = dunya(m, (d.cpuKenar[i] + d.cpuKenar[i + 1]) * 0.5f); ekle(d.kimlik, ord, 2u, (w - orij).nokta(yon), w, ad, "Kenar"); } // Nokta: cpuNokta köşe → ekran; imleç-nokta mesafesi < tol. for (size_t i = 0; i < d.cpuNokta.size(); ++i) { ImVec2 s; if (!projekteEkran(vp, vpMin, vpBoyut, dunya(m, d.cpuNokta[i]), s)) continue; const f32 dx = s.x - imlec.x, dy = s.y - imlec.y; if (dx * dx + dy * dy > tol * tol) continue; const u32 ord = i < d.cpuNoktaAlt.size() ? d.cpuNoktaAlt[i] : 0; const Vec3f w = dunya(m, d.cpuNokta[i]); ekle(d.kimlik, ord, 3u, (w - orij).nokta(yon), w, ad, "Nokta"); } } // 3) Gövde adayları (filtre izin veriyorsa solid/tel de seçilebilir). if (turIzinli(0u)) for (auto& [gk, a] : govdeAday) adaylar_.push_back(a); // Filtre hiçbir aday bırakmadıysa gövde adaylarını göster (boş liste olmasın). if (adaylar_.empty()) for (auto& [gk, a] : govdeAday) adaylar_.push_back(a); if (adaylar_.empty()) return; // Mesafeye sırala (yakın→uzak): yüz/kenar/nokta/gövde karışık, derinlik sırasıyla. std::sort(adaylar_.begin(), adaylar_.end(), [](const SecimAday& a, const SecimAday& b) { return a.mesafe < b.mesafe; }); acik_ = true; imlecKonum_ = imlec; } void kapat() { acik_ = false; adaylar_.clear(); aktif_ = 0; } // Tekerlek/ok ile gez (yön: +1 ileri, -1 geri; sarmalı). void gez(int yon) { if (adaylar_.empty()) return; const int n = (int)adaylar_.size(); aktif_ = ((aktif_ + yon) % n + n) % n; } const SecimAday* aktifAday() const { return (acik_ && aktif_ >= 0 && aktif_ < (int)adaylar_.size()) ? &adaylar_[aktif_] : nullptr; } // Peek lens: sabit daire merkezi (tık noktası, ekran-uzayı) + yarıçapı (Ayarlar). ImVec2 merkez() const { return imlecKonum_; } f32 yaricap() const { return secimGorunum().peekYari; } // İmleç-yanı liste + 3B şeffaf daire çiz. ekrana: dünya→ekran (main.cpp lambdası, // bool(const Vec3f&, ImVec2&)). Dönüş true → aktif aday SEÇİLDİ (caller commit eder). template bool ciz(ImDrawList* ciz, const EkranaFn& ekrana) { if (!acik_) return false; bool secildi = false; // Peek lens çerçevesi — SABİT imleç/tık noktasında (merkez değişmez). İçini // sahneye çizilen highlight gövde (peek pass, occluder'sız) doldurur; burada // yalnız halka çizilir. (void)ekrana — 3B nokta artık gerekmez. (void)ekrana; if (secimGorunum().peekAcik) { const ImVec2 s = imlecKonum_; const f32 R = secimGorunum().peekYari; ciz->AddCircle(s, R, IM_COL32(130, 210, 255, 240), 48, 2.4f); ciz->AddCircle(s, R, IM_COL32(255, 255, 255, 80), 48, 1.0f); ciz->AddCircle(s, R + 1.5f, IM_COL32(20, 30, 45, 150), 48, 1.5f); } // İmleç-yanı aday listesi (ekran-kenarına taşmaz — ImGui auto-clamp yapar). ImGui::SetNextWindowPos(ImVec2(imlecKonum_.x + 20.0f, imlecKonum_.y + 8.0f), ImGuiCond_Always); ImGui::SetNextWindowBgAlpha(0.94f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 9.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 8)); if (ImGui::Begin("Seçim Yığını##secimYigini", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoSavedSettings)) { ImGui::TextDisabled("Seçim yığını · %d aday", (int)adaylar_.size()); ImGui::SameLine(); ImGui::TextDisabled("(tekerlek: gez · tık: seç · Esc)"); ImGui::Separator(); for (int i = 0; i < (int)adaylar_.size(); ++i) { const SecimAday& a = adaylar_[i]; ImGui::PushID(i); char etiket[224]; std::snprintf(etiket, sizeof(etiket), "%s %s · %s · %.1f mm", a.simge, a.ad.c_str(), a.tipAd, (double)a.mesafe); if (ImGui::Selectable(etiket, i == aktif_)) { aktif_ = i; secildi = true; } if (ImGui::IsItemHovered()) aktif_ = i; // üzerine gelince canlı önizleme ImGui::PopID(); } } ImGui::End(); ImGui::PopStyleVar(2); return secildi; } private: static const char* turSimge(u32 tur) { // topoloji ikonu: nokta/kenar/yüz/gövde return tur == 3 ? "•" : tur == 2 ? "▬" : tur == 1 ? "◧" : "◼"; } static Vec3f dunya(const Mat4f& m, const Vec3f& p) { const Vec4f w = m * Vec4f{p.x, p.y, p.z, 1.0f}; return {w.x, w.y, w.z}; } static bool projekteEkran(const Mat4f& vp, ImVec2 vpMin, ImVec2 vpBoyut, const Vec3f& p, ImVec2& s) { const Vec4f h = vp * Vec4f{p.x, p.y, p.z, 1.0f}; if (h.w <= 1e-4f) return false; s.x = vpMin.x + (h.x / h.w * 0.5f + 0.5f) * vpBoyut.x; s.y = vpMin.y + (h.y / h.w * 0.5f + 0.5f) * vpBoyut.y; return true; } static f32 nokSegMesafe(ImVec2 p, ImVec2 a, ImVec2 b) { // 2B nokta→segment mesafesi const f32 vx = b.x - a.x, vy = b.y - a.y, wx = p.x - a.x, wy = p.y - a.y; const f32 L = vx * vx + vy * vy; f32 t = L > 1e-6f ? (wx * vx + wy * vy) / L : 0.0f; t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t); const f32 dx = a.x + t * vx - p.x, dy = a.y + t * vy - p.y; return std::sqrt(dx * dx + dy * dy); } // Gövde dünya-bbox'ı imleç yakınında mı (ekran-uzayı)? → kenar/nokta taramasını // yalnız imleç altındaki gövdelere sınırla (per-gövde 8 köşe projeksiyonu, ucuz). static bool bboxImlecYakin(const Mat4f& m, const Aabbf& yerel, const Mat4f& vp, ImVec2 vpMin, ImVec2 vpBoyut, ImVec2 imlec, f32 tol) { if (!yerel.gecerliMi()) return true; // sınır yok → güvenli tarafta dahil const Vec3f mn = yerel.asgari, mx = yerel.azami; f32 x0 = 1e30f, y0 = 1e30f, x1 = -1e30f, y1 = -1e30f; for (int c = 0; c < 8; ++c) { const Vec3f p{(c & 1) ? mx.x : mn.x, (c & 2) ? mx.y : mn.y, (c & 4) ? mx.z : mn.z}; ImVec2 s; if (!projekteEkran(vp, vpMin, vpBoyut, dunya(m, p), s)) return true; // kamera arkası → dahil x0 = std::min(x0, s.x); y0 = std::min(y0, s.y); x1 = std::max(x1, s.x); y1 = std::max(y1, s.y); } return imlec.x >= x0 - tol && imlec.x <= x1 + tol && imlec.y >= y0 - tol && imlec.y <= y1 + tol; } bool acik_ = false; int aktif_ = 0; std::vector adaylar_; ImVec2 imlecKonum_{}; }; } // namespace fiko