#include "fiko/sahne/sahne.hpp" #include #include #include #include #include #include #include #include #include "fiko/core/gunluk.hpp" #include "fiko/geometri/kalite.hpp" // kabaLodSapma() — orbit LOD deflection (AyarSistemi) #include "fiko/render/secim_gpu.hpp" // pickAyar().yolSecilemez (takım yolu pick dışı) #include "fiko/render/vulkan_baglam.hpp" #include "fiko/sahne/isin_bvh.hpp" #include "fiko/sketch/eskiz_segment.hpp" // sketchKenarYenile: Spline segmentleri (merkezi) #include "fiko/sahne/geometri_nesne.hpp" #include "fiko/sahne/grup_nesne.hpp" #include "fiko/sahne/parametre_nesne.hpp" #include "fiko/sahne/referans_nesne.hpp" #include "fiko/sahne/pafta_nesne.hpp" #include "fiko/sahne/pmi_nesne.hpp" #include "fiko/sahne/sketch_nesne.hpp" namespace fiko { // Bu üçgen sayısına dek BVH ilk kullanımda senkron kurulur (birkaç ms, hissedilmez); // üstü arka-plan kurucuya gider (dev mesh'te ana-thread donması YASAK). static constexpr size_t kBvhSenkronUcgen = 65536; IsinBvhKurucu& Sahne::bvhKurucu() const { if (!bvhKurucu_) bvhKurucu_ = std::make_shared(); return *bvhKurucu_; } void Sahne::bvhIsIptal(u32 kimlik) const { if (bvhKurucu_) bvhKurucu_->iptal(kimlik); } // KenarVertex LINE_LIST → CPU Vec3f kopyasi (alan-secim mesh'siz hassas testi). // Merkezi: mesh/referans/GSD tum kenar yollari bunu cagirir → tek kaynak. static void cpuKenarKopyala(Dugum& d, const std::vector& kv) { d.cpuKenar.clear(); d.cpuKenarAlt.clear(); d.cpuKenar.reserve(kv.size()); d.cpuKenarAlt.reserve(kv.size()); for (const KenarVertex& v : kv) { d.cpuKenar.push_back({v.konum[0], v.konum[1], v.konum[2]}); d.cpuKenarAlt.push_back(v.altKimlik); // Selection Stack kenar ordinali } } // Köşe noktaları (nokta pick) → CPU kopya + vertex ordinali (Selection Stack). static void cpuNoktaKopyala(Dugum& d, const std::vector& nv) { d.cpuNokta.clear(); d.cpuNoktaAlt.clear(); d.cpuNokta.reserve(nv.size()); d.cpuNoktaAlt.reserve(nv.size()); for (const KenarVertex& v : nv) { d.cpuNokta.push_back({v.konum[0], v.konum[1], v.konum[2]}); d.cpuNoktaAlt.push_back(v.altKimlik); } } // Tek-yuz vurgusu icin yuz ordinali -> indeks alt-araligi. Ucgenler yuze gore bitisikse // (STEP/OCCT tessellation kalibi) secim/hover overlay'i tum govdeyi degil yalniz o yuzu // cizer (olculdu: 481k ucgenli govdede secili yuz 60->25 FPS, GPU 24 ms/kare). Herhangi bir // sureksizlik/tutarsizlikta tablo BOS birakilir -> cagiran eski tam cizime duser. static void yuzAralikKur(Dugum& d) { d.yuzIndeksAralik.clear(); const std::size_t ind = d.cpuIndeks.size(); if (ind < 3 || d.cpuAlt.empty()) return; u32 maks = 0; for (u32 a : d.cpuAlt) maks = std::max(maks, a); if (maks == 0) return; std::vector> t(maks + 1, {0u, 0u}); u32 oncekiYuz = 0; for (std::size_t i = 0; i + 2 < ind; i += 3) { const u32 v = d.cpuIndeks[i]; if (v >= d.cpuAlt.size()) return; const u32 y = d.cpuAlt[v]; if (y == 0 || y > maks) return; if (y != oncekiYuz) { if (t[y].second != 0) return; // yuz ikinci kez basliyor -> sureksiz t[y].first = (u32)i; oncekiYuz = y; } t[y].second += 3; } d.yuzIndeksAralik.swap(t); } // CPU ucgen kopyasi (isin-yuzey kesisimi / analiz eslemesi) + BVH bayatla. // Merkezi: tamponlariYukle / hazirMeshEkle / hazirMeshGuncelle ayni yoldan gecer. static void cpuMeshKopyala(Dugum& d, const MeshVeri& veri) { // Yeni BVH arka planda kurulana dek isinVur bu gövdeyi ATLAMASIN diye eski BVH + // ONUNLA EŞLEŞEN konum/indeks/alt'ı sakla (yalnız gerçek bir BVH varsa saklamaya // değer — yoksa zaten atlanacak/kullanılacak bir şey yok). d.cpuKonum/cpuIndeks/ // cpuAlt aşağıda temizlenip YENİ veriyle dolduruluyor → eskisini move ile devral. if (d.isinBvh) { d.isinBvhEski = std::move(d.isinBvh); d.cpuKonumEski = std::move(d.cpuKonum); d.cpuIndeksEski = std::move(d.cpuIndeks); d.cpuAltEski = std::move(d.cpuAlt); } else { d.isinBvhEski.reset(); d.cpuKonumEski.clear(); d.cpuIndeksEski.clear(); d.cpuAltEski.clear(); } d.cpuKonum.clear(); d.cpuNormal.clear(); d.cpuAlt.clear(); d.cpuRenk.clear(); d.cpuRenkBaz.clear(); d.cpuAlfa.clear(); d.isinBvh.reset(); // mesh değişti → BVH önbelleği bayat (yeni kurulum tetiklenecek) d.cpuKonum.reserve(veri.vertexler.size()); d.cpuNormal.reserve(veri.vertexler.size()); d.cpuAlt.reserve(veri.vertexler.size()); d.cpuRenk.reserve(veri.vertexler.size()); d.cpuRenkBaz.reserve(veri.vertexler.size()); d.cpuAlfa.reserve(veri.vertexler.size()); // TABAN renk (cpuRenkBaz): override YOKMUŞ gibi yüzün gerçek rengi = STEP yüz rengi (varsa) // yoksa mesh vertex rengi. Override kalkınca (ogeRenkYenile) yüz TAM buraya döner → başka // yüzler ETKİLENMEZ, renk KAYBOLMAZ (tahmin yok, saklı gerçek değer). yuzOz override → cpuRenk. const bool ovVar = !d.yuzOz.empty(); const std::vector* yuzR = (d.brep && d.brep->yuzRenkliMi()) ? &d.brep->yuzRenkleri() : nullptr; for (const auto& v : veri.vertexler) { d.cpuKonum.push_back({v.konum[0], v.konum[1], v.konum[2]}); d.cpuNormal.push_back({v.normal[0], v.normal[1], v.normal[2]}); d.cpuAlt.push_back(v.altKimlik); Vec3f baz{v.renk[0], v.renk[1], v.renk[2]}; if (yuzR && v.altKimlik >= 1 && (std::size_t)(v.altKimlik - 1) < yuzR->size()) baz = (*yuzR)[v.altKimlik - 1]; // STEP taban rengi d.cpuRenkBaz.push_back(baz); Vec3f renk = baz; f32 alfa = 1.0f; if (ovVar) { const auto it = d.yuzOz.find(v.altKimlik); if (it != d.yuzOz.end()) { renk = it->second.renk; alfa = it->second.gorunur ? it->second.alfa : 0.0f; } } d.cpuRenk.push_back(renk); d.cpuAlfa.push_back(alfa); } d.cpuIndeks = veri.indeksler; yuzAralikKur(d); } // Kaba (LOD) mesh per-vertex renk/alt/alfa: cpuMeshKopyala (ince) ile AYNI kural — // STEP taban rengi (yuzRenkleri[alt-1]) > vertex rengi, yuzOz override uygulanır. // yenidenInsa'da bir kez pişer; ogeRenkYenile canlı düzenlemede kabaRenk/kabaAlfa'yı tazeler. static void kabaRenkPisir(Dugum& d, const std::vector& vtx) { const std::size_t n = vtx.size(); d.kabaAlt.clear(); d.kabaAlt.reserve(n); d.kabaRenk.clear(); d.kabaRenk.reserve(n); d.kabaRenkBaz.clear(); d.kabaRenkBaz.reserve(n); d.kabaAlfa.clear(); d.kabaAlfa.reserve(n); const bool ovVar = !d.yuzOz.empty(); const std::vector* yuzR = (d.brep && d.brep->yuzRenkliMi()) ? &d.brep->yuzRenkleri() : nullptr; for (const auto& v : vtx) { d.kabaAlt.push_back(v.altKimlik); Vec3f baz{v.renk[0], v.renk[1], v.renk[2]}; if (yuzR && v.altKimlik >= 1 && (std::size_t)(v.altKimlik - 1) < yuzR->size()) baz = (*yuzR)[v.altKimlik - 1]; d.kabaRenkBaz.push_back(baz); Vec3f renk = baz; f32 alfa = 1.0f; if (ovVar) { const auto it = d.yuzOz.find(v.altKimlik); if (it != d.yuzOz.end()) { renk = it->second.renk; alfa = it->second.gorunur ? it->second.alfa : 0.0f; } } d.kabaRenk.push_back(renk); d.kabaAlfa.push_back(alfa); } } // Kaba GPU vertex dizisi: kabaRenkPisir SONRASI düzeltilmiş per-yüz renk/alfa ile kurulur // (ham ucgenle rengi DEĞİL). Bireysel orbit/hover LOD yolu bu tamponu çizer → renkler ince // vertexTampon'la birebir (STEP taban + yuzOz override + canlı düzenleme) ve LOD geçişinde // "renkler gidip geliyor" atlaması olmaz. kabaMeshUret + ogeRenkYenile ortak kullanır. static std::vector kabaVertexKur(const Dugum& d) { const std::size_t n = d.kabaKonum.size(); std::vector vtx(n); for (std::size_t i = 0; i < n; ++i) { Vertex v{}; const Vec3f& k = d.kabaKonum[i]; const Vec3f nm = i < d.kabaNormal.size() ? d.kabaNormal[i] : Vec3f{0, 0, 1}; const Vec3f c = i < d.kabaRenk.size() ? d.kabaRenk[i] : d.renk; v.konum[0] = k.x; v.konum[1] = k.y; v.konum[2] = k.z; v.normal[0] = nm.x; v.normal[1] = nm.y; v.normal[2] = nm.z; v.renk[0] = c.x; v.renk[1] = c.y; v.renk[2] = c.z; v.altKimlik = i < d.kabaAlt.size() ? d.kabaAlt[i] : 0; v.alfa = i < d.kabaAlfa.size() ? d.kabaAlfa[i] : 1.0f; v.globalYuzIndeks = v.altKimlik > 0 ? d.yuzBaz_ + v.altKimlik - 1 : 0; vtx[i] = v; } return vtx; } // vertexTampon'a yüklenecek Vertex dizisi: d.yuzOz'da GERÇEK override edilen yüzlerin // per-vertex rengini override renkle değiştirir (diğerleri STEP/varsayılan korunur). // Override yoksa EKSTRA KOPYA yapmadan veri.vertexler'i döner. cpuMeshKopyala'daki // cpuRenk ile aynı renkleri üretir → bireysel çizim ile mega-parti tutarlı. static const std::vector& ogeRenkliVertexler(const Dugum& d, const MeshVeri& veri, std::vector& ovBuf) { // Aşama 0d: globalYuzIndeks (yuzBaz+altKimlik-1) scene-kavramı (ucgenle bilmez) → burada kur. const bool ovVar = !d.yuzOz.empty(); if (!ovVar && d.yuzBaz_ == 0) return veri.vertexler; // ne override ne SSBO indeksi → zero-copy ovBuf = veri.vertexler; for (Vertex& v : ovBuf) { v.globalYuzIndeks = v.altKimlik > 0 ? d.yuzBaz_ + v.altKimlik - 1 : 0; // SSBO global indeks if (ovVar) { const auto it = d.yuzOz.find(v.altKimlik); if (it != d.yuzOz.end()) { v.renk[0] = it->second.renk.x; v.renk[1] = it->second.renk.y; v.renk[2] = it->second.renk.z; v.alfa = it->second.gorunur ? it->second.alfa : 0.0f; } } } return ovBuf; } std::string Sahne::uygunAd(DugumTip tip) { static const char* adlar[] = {"Kutu", "Silindir", "Kure", "Koni", "Birlesim", "Cikarma", "Kesisim", "Govde"}; const int i = (int)tip; return std::string(adlar[i]) + "." + std::to_string(++tipSayac_[i]); } Dugum& Sahne::hamEkle(DugumTip tip) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = tip; d.ad = uygunAd(tip); Dugum& ref = dunya_.ekle(v, std::move(d)); // Kök objeleri bağla (App/Gui): her geometri düğümü DokumanNesnesi + görünüm alır. dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); // CATIA: her yeni geometri (primitif/import/boolean sonucu) VARSAYILAN aktif PartBody // altında yaşar (kök seviyesi = yalnız Part). Özel yollar (import hiyerarşi ust'u, GSD, // feature sonuç düğümü) bunu sonradan ustAyarla ile override eder — son yazan kazanır. dunya_.ekle(v, HiyerarsiBilesen{partBodyAl()}); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return ref; } DugumPusula Sahne::anlik(u32 kimlik) const { DugumPusula p; const Dugum* d = bul(kimlik); if (!d) return p; // kimlik=0 → yok p.kimlik = d->kimlik; p.tip = d->tip; p.ad = d->ad; p.gorunur = d->gorunur; p.p1 = d->p1; p.p2 = d->p2; p.p3 = d->p3; p.aciZDerece = d->aciZDerece; p.renk = d->renk; p.donusum = d->donusum; p.gorselOran = d->gorselOran; p.brep = d->brep; // paylas (ithal/boolean icin gerekli) ogeOzellikPusulaya(*d, p); // yüz/kenar/nokta görsel özellikleri (undo/redo renk korunur) if (const HiyerarsiBilesen* h = dunya_.bul(kimlik)) p.ust = h->ust; // Sketch: SketchNesnesi kök state'ini (düzlem + eskiz veri) yakala (diril'de geri kurulur). if (d->tip == DugumTip::Sketch) { if (const NesneKok* nk = dunya_.bul(kimlik)) if (const auto* sk = dynamic_cast(nk->get())) { p.sketchDuzlem = sk->duzlem(); p.sketchEskiz = std::make_shared(sk->eskiz()); } } // PMI: defter bağını yakala (veri defterde; diril'de düğüm aynı öğeye yeniden bağlanır). if (d->tip == DugumTip::Pmi) { if (const NesneKok* nk = dunya_.bul(kimlik)) if (const auto* pn = dynamic_cast(nk->get())) { p.pmiSistem = pn->sistem(); p.pmiKimlik = pn->pmiKimlik(); } } // Pafta: teknik resim verisini (sayfa/görünümler/ölçüler) yakala — diril'de geri kurulur. if (d->tip == DugumTip::Pafta) { if (const pafta::Pafta* pv = paftaVerisi(const_cast(dunya_), kimlik)) p.paftaVeri = std::make_shared(PaftaPusulaVeri{*pv}); } return p; } void Sahne::diril(const DugumPusula& p) { if (p.kimlik == 0) return; const Varlik v = dunya_.dirilt(p.kimlik); Dugum d; d.kimlik = v; d.tip = p.tip; d.ad = p.ad; d.gorunur = p.gorunur; d.p1 = p.p1; d.p2 = p.p2; d.p3 = p.p3; d.aciZDerece = p.aciZDerece; d.renk = p.renk; d.donusum = p.donusum; d.gorselOran = p.gorselOran; d.brep = p.brep; // primitifMi() false ise mevcut brep mesh'lenir ogeOzellikDugume(p, d); // yüz/kenar/nokta görsel özellikleri geri yüklenir const bool referansMi = p.tip == DugumTip::Duzlem || p.tip == DugumTip::Eksen || p.tip == DugumTip::EksenSistemi; Dugum& ref = dunya_.ekle(v, std::move(d)); // Üst-grup (HiyerarsiBilesen) bağını geri kur → sil→undo'da düğüm ürün ağacındaki yerini // (parent cascade görünürlük/transform) korur; köke kaçmaz. if (p.ust != VARLIK_YOK) dunya_.ekle(v, HiyerarsiBilesen{p.ust}); // Kök objeleri yeniden bağla (TİP-UYGUN kök). Sketch → SketchNesnesi (düzlem parametrik link // + eskiz veri geri yüklenir); referans → ReferansNesnesi; diğer → GeometriNesnesi. if (p.tip == DugumTip::Sketch) { auto sk = std::make_unique(v, &dunya_, p.sketchDuzlem); if (p.sketchEskiz) sk->eskiz() = *p.sketchEskiz; // entity + kısıt verisini geri yükle dunya_.ekle(v, std::move(sk)); dunya_.ekle(v, std::make_unique(v, &dunya_)); } else if (p.tip == DugumTip::Pmi) { dunya_.ekle(v, std::make_unique(v, &dunya_, p.pmiSistem, p.pmiKimlik)); dunya_.ekle(v, std::make_unique(v, &dunya_, p.pmiSistem, p.pmiKimlik)); } else if (p.tip == DugumTip::Pafta) { auto pn = std::make_unique(v, &dunya_); if (p.paftaVeri) pn->veri() = p.paftaVeri->veri; // sayfa/görünümler/ölçüler geri dunya_.ekle(v, std::move(pn)); dunya_.ekle(v, std::make_unique(v, &dunya_)); } else if (referansMi) { dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); } else { dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); } olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); // GSD Tel/Yuzey: yenidenInsa KATI yolu kullanir — Tel'de ucgenle bos doner ve erken // cikilir (kenar tamponu hic kurulmaz → dirilen tel GORUNMEZdi; bilinen acik bug'in // bugunku mekanizmasi). Yaratilis yolunun kendisi (gsdDugumGuncelle: kenar/nokta + // Yuzey'de DoubleSide mesh) kullanilir → sil-undo yaratilisla birebir. if ((p.tip == DugumTip::Tel || p.tip == DugumTip::Yuzey) && p.brep) { gsdDugumGuncelle(v, p.brep); } else { yenidenInsa(ref); // primitif: parametreden brep; degilse mevcut brep mesh'lenir } } void Sahne::bekleyenleriYenidenInsa() { kabaMeshBekleyenUygula(); // biten arka-plan kaba LOD işleri (kare başı; ucuz no-op) asyncDirektPompa(); // biten DM direct-op işleri (kare başı; ucuz no-op) for (auto& d : dunya_.depo().yogun()) { if (d.yenidenInsaGerek) { d.yenidenInsaGerek = false; yenidenInsa(d); } } } // ---- DM DIRECT-OP ASYNC (Yüz Kaldır/kabuk/draft... feature'sız tek-atış işlemler) ---- // Worker: op + remap-geometrik birleşim + renk sigortası + İNCE mesh (feature async // worker'ı ile aynı ölçütler) → pompa yalnız GPU upload + swap yapar (main donmaz). void Sahne::asyncDirektBaslat(u32 govde, std::function&)> op, std::function bitti) { if (!bul(govde) || !op) { if (bitti) bitti(false); return; } DirektIs is; is.govde = govde; is.op = std::move(op); is.bitti = std::move(bitti); const bool ilk = direktIsler_.empty(); direktIsler_.push_back(std::move(is)); // Yalnız kuyruk başı hemen başlar; sonrakiler pompa sırası gelince (girdileri o anda // çözülür → aynı gövdeye zincirlenen işler birbirinin sonucunu görür). if (ilk && !direktBaslat(direktIsler_.front())) { DirektIs dusen = std::move(direktIsler_.front()); direktIsler_.pop_front(); if (dusen.bitti) dusen.bitti(false); } } bool Sahne::direktBaslat(DirektIs& is) { Dugum* d = bul(is.govde); if (!d || !d->brep || !d->brep->gecerliMi()) return false; is.girdi = d->brep; // Girdi ordinal önbelleklerini MAIN'de ısıt: worker'daki geometrik eşleme/yuzBilgi, // main-thread hover ile aynı mutable önbelleğe yazmasın (feature async kalıbı). { geometri::YuzBilgi yb; (void)is.girdi->yuzBilgi(1, yb, /*alanHesapla*/ false); geometri::KenarBilgi kb; (void)is.girdi->kenarBilgi(1, kb); } auto girdi = is.girdi; const Vec3f renk = d->renk; is.sonuc = std::async(std::launch::async, [girdi, renk, op = std::move(is.op)] { geometri::occtSinyalKur(); // OCCT sinyal koruması her worker thread'de kurulmalı DirektIs::Sonuc s; s.govde = op(*girdi, s.remap); if (!s.govde.doluMu()) return s; std::size_t dolu = 0; for (int v : s.remap) if (v) ++dolu; if (s.remap.empty() || dolu * 2 < s.remap.size()) { std::vector geo = geometri::yuzGeometrikEsle(*girdi, s.govde); if (s.remap.empty()) s.remap = std::move(geo); else for (std::size_t j = 0; j < s.remap.size() && j < geo.size(); ++j) if (s.remap[j] == 0) s.remap[j] = geo[j]; } // Renk sigortası (mesh pişmeden ÖNCE): sonuç renksizse girdiden geri-haritayla kur. if (!s.govde.yuzRenkliMi() && girdi->yuzRenkliMi()) { const auto& gr = girdi->yuzRenkleri(); std::vector renkler(s.remap.size(), renk); for (std::size_t j = 0; j < s.remap.size(); ++j) if (s.remap[j] > 0 && (std::size_t)s.remap[j] <= gr.size()) renkler[j] = gr[(std::size_t)s.remap[j] - 1]; s.govde.yuzRenkleriAta(std::move(renkler)); } s.veri = s.govde.ucgenle(renk); s.kenarlar = s.govde.kenarMesh(-1.0, girdi.get()); s.noktalar = s.govde.noktaMesh(); return s; }); return true; } void Sahne::asyncDirektPompa() { while (!direktIsler_.empty()) { DirektIs& is = direktIsler_.front(); if (!is.sonuc.valid()) { // sırası yeni geldi → girdiyi ŞİMDİ çöz + worker başlat if (!direktBaslat(is)) { DirektIs dusen = std::move(direktIsler_.front()); direktIsler_.pop_front(); if (dusen.bitti) dusen.bitti(false); continue; } } if (is.sonuc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) return; DirektIs::Sonuc s = is.sonuc.get(); Dugum* d = bul(is.govde); bool ok = false; // Düğüm silinmiş/brep bu arada değişmişse (başka apply) sonuç bayat → sessizce atılır. if (d && d->brep && d->brep.get() == is.girdi.get() && s.govde.doluMu() && !s.veri.vertexler.empty()) { // yuzOz (kullanıcı renk/alfa) yeni ordinallere taşı — pushPullUygula ile aynı kural // (remap worker'da geometrikle birleştirildi; eşleşmeyen yeni yüz override'sız kalır). if (!d->yuzOz.empty()) { std::unordered_map yeniOz; for (std::size_t j = 0; j < s.remap.size(); ++j) if (s.remap[j] > 0) { const auto it = d->yuzOz.find((u32)s.remap[j]); if (it != d->yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } d->yuzOz = std::move(yeniOz); } d->brep = std::make_shared(std::move(s.govde)); d->yenidenInsaGerek = false; tamponlariYukle(*d, s.veri, s.kenarlar, s.noktalar); d->sonMeshBrep = d->brep.get(); kabaMeshUret(*d, /*zorlaArkaplan*/ true); secimTemizle(); // yüz/kenar ordinalleri değişti → seçim geçersiz (senkronla aynı) ok = true; } // GÜVENLİ SIRA: callback'i çıkar + kuyruktan ÇIKAR, SONRA çağır. bitti() cekirdek.uygula // (DuzenleIslem) tetikler; olası re-entrans/yeni-iş kuyruklamada tüketilmiş front yeniden // başlatılmaz (çift-apply hazardı elenir). auto bittiKopya = std::move(is.bitti); direktIsler_.pop_front(); if (bittiKopya) bittiKopya(ok); } } Dugum& Sahne::ekle(DugumTip tip) { Dugum& yeni = hamEkle(tip); switch (tip) { case DugumTip::Kutu: yeni.p1 = 2.0f; yeni.p2 = 2.0f; yeni.p3 = 2.0f; break; case DugumTip::Silindir: yeni.p1 = 0.8f; yeni.p3 = 2.0f; break; case DugumTip::Kure: yeni.p1 = 1.0f; break; case DugumTip::Koni: yeni.p1 = 1.0f; yeni.p3 = 2.0f; break; default: break; } yenidenInsa(yeni); yeni.donusum.konum.z = (f64)(-yeni.yerelSinir.asgari.z); return yeni; } void Sahne::yenidenInsa(Dugum& d, const geometri::BrepGovde* kenarKaynak) { // Süren kaba-mesh işi AYNI brep'i okuyorsa bitmesini bekle (ince ucgenle orijinal // TShape üçgenlemelerine yazar; eşzamanlı okuma-yazma önlenir — nadir: kalite değişimi). kabaIsAyniBrepBekle(d); if (d.primitifMi()) { geometri::BrepGovde govde; switch (d.tip) { case DugumTip::Kutu: govde = geometri::kutu(d.p1, d.p2, d.p3); break; case DugumTip::Silindir: govde = geometri::silindir(d.p1, d.p3); break; case DugumTip::Kure: govde = geometri::kure(d.p1); break; case DugumTip::Koni: govde = geometri::koni(d.p1, d.p3); break; default: break; } d.brep = std::make_shared(std::move(govde)); } // Gecersiz/bos geometri yollarinda yerelSinir'i asla atamasiz birakma: // varsayilan ters Aabb → gecerliMi()==false → NaN kamera fit → viewport // kaybolur (K2 nuksu). Guvenli minik-pay bbox ata. auto sinirGuvenceye = [&] { if (!d.yerelSinir.gecerliMi()) { const Vec3f m{0, 0, 0}; d.yerelSinir = {{m.x - 0.01f, m.y - 0.01f, m.z - 0.01f}, {m.x + 0.01f, m.y + 0.01f, m.z + 0.01f}}; } }; if (!d.brep || !d.brep->gecerliMi()) { gunluk::uyari("Gecersiz govde: {}", d.ad); sinirGuvenceye(); return; } // Faz zamanlaması ("önce ölç"): devasa modelde apply maliyetinin NEREDE olduğu görülsün. // Yalnız yavaş inşalarda loglanır (eşik altı sessiz — küçük modelde log gürültüsü yok). const auto t0 = std::chrono::steady_clock::now(); const MeshVeri veri = d.brep->ucgenle(d.renk); const auto t1 = std::chrono::steady_clock::now(); if (veri.vertexler.empty()) { sinirGuvenceye(); return; } const KenarVeri kenarlar = d.brep->kenarMesh(-1.0, kenarKaynak); const NoktaVeri noktalar = d.brep->noktaMesh(); const auto t2 = std::chrono::steady_clock::now(); tamponlariYukle(d, veri, kenarlar, noktalar); const auto t3 = std::chrono::steady_clock::now(); d.sonMeshBrep = d.brep.get(); // bu brep mesh'lendi → gorunuruMesh tekrar etmesin kabaMeshUret(d); // orbit/hover LOD kaba mesh (arka planda; apply bloklanmaz) const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; const f64 toplamMs = ms(t0, t3); if (toplamMs > 50.0) gunluk::bilgi("[perf] yenidenInsa '{}': ucgen {:.0f}ms + kenar/nokta {:.0f}ms + yukle {:.0f}ms " "= {:.0f}ms ({} vtx, {} kenar)", d.ad, ms(t0, t1), ms(t1, t2), ms(t2, t3), toplamMs, veri.vertexler.size(), kenarlar.kenarSayi); } // Orbit/hover-anı LOD: KABA mesh (yüksek deflection ~10×, çok az üçgen) üretir + GPU'ya yükler. // yenidenInsa VE ithalEkleHazir çağırır → import edilmiş mesh'te de coarse OLUR (yoksa hover-move // ince mesh'e düşüp milyon üçgeni her kare çizer = "stabil değil" kökü). Kaba mesh navigasyon/ // hover-move'da mega-parti tarafından çizilir + YÜZ pick'te kullanılır (az üçgen). bool& kabaMeshArkaplan() { static bool v = true; return v; } bool& importParalelMesh() { static bool v = true; // import taze gövde worker'da → paralel BRepMesh güvenli return v; } void Sahne::kabaMeshUret(Dugum& d, bool zorlaArkaplan) { if (!d.brep || !d.brep->gecerliMi()) { d.kabaIndeksSayi = 0; return; } if (kabaMeshArkaplan() || zorlaArkaplan) { // ARKA PLAN: OCCT kaba tessellation worker'da (ucgenleKaba bağımsız KOPYADA mesh'ler + // değişmeyen yüzleri önceki önbellekten TRANSFER eder → kaba da incremental). Hazır olana // dek kabaIndeksSayi=0 → çizim/pick İNCE mesh'e düşer (bayat kaba ordinal uyumsuz olurdu). auto eski = kabaIsler_.find(d.kimlik); if (eski != kabaIsler_.end()) { kabaCop_.push_back(std::move(eski->second)); // eski iş çöpe (bloklamadan) kabaIsler_.erase(eski); } d.kabaIndeksSayi = 0; KabaMeshIs is; is.brep = d.brep; auto brep = d.brep; const Vec3f renk = d.renk; std::shared_ptr eskiOnb; // salt-okunur snapshot (worker'a) if (auto o = kabaOnb_.find(d.kimlik); o != kabaOnb_.end()) eskiOnb = o->second; is.sonuc = std::async(std::launch::async, [brep, renk, eskiOnb] { geometri::occtSinyalKur(); // OCCT sinyal koruması her worker thread'de kurulmalı KabaMeshSonuc ks; ks.veri = brep->ucgenleKaba(renk, eskiOnb.get(), ks.onb); // BRepCheck ÖN-ISITMA: fillet/pah girdi-sağlık kontrolü (~2sn) burada önbelleğe girer → // kullanıcının İLK fillet'i main-thread'de bu bedeli ödemez. (hacim/alan ayrı işte.) (void)geometri::govdeSaglikliOnbellekli(*brep); return ks; }); kabaIsler_[d.kimlik] = std::move(is); return; } std::shared_ptr eskiOnb; if (auto o = kabaOnb_.find(d.kimlik); o != kabaOnb_.end()) eskiOnb = o->second; std::shared_ptr yeniOnb; const MeshVeri kabaVeri = d.brep->ucgenleKaba(d.renk, eskiOnb.get(), yeniOnb); if (yeniOnb) kabaOnb_[d.kimlik] = std::move(yeniOnb); kabaMeshVeriUygula(d, kabaVeri); } void Sahne::hacimIsteEkle(const Dugum& d) { if (!d.brep || !d.brep->gecerliMi()) return; // Aynı düğümün BAŞLAMAMIŞ isteği güncellenir (koşan [0] dokunulmaz — bayat sonuç // pompada brep karşılaştırmasıyla zaten atılır); yoksa kuyruğa yeni tanım eklenir. for (auto it = hacimKuyruk_.begin(); it != hacimKuyruk_.end(); ++it) { if (it->kimlik != d.kimlik) continue; if (it == hacimKuyruk_.begin() && it->sonuc.valid()) continue; // koşana dokunma it->brep = d.brep; return; } hacimKuyruk_.emplace_back(); hacimKuyruk_.back().kimlik = d.kimlik; hacimKuyruk_.back().brep = d.brep; } void Sahne::hacimOncelikle(u32 kimlik) { // Hacim/alan hesabını öncelikli kuyruğa al: varsa başa al (koşan → dokunma), // yoksa ekle. Ağırlık widget'ı açık gövdeyi beklediğinde hesaplanıyorsa // "HESAPLANIYOR" göstermek için kullanılır. Dugum* d = bul(kimlik); if (!d || !d->brep || !d->brep->gecerliMi()) return; for (auto it = hacimKuyruk_.begin(); it != hacimKuyruk_.end(); ++it) { if (it->kimlik != kimlik) continue; if (it != hacimKuyruk_.begin()) { // splice: node'u koparıp başa bağlar — HacimIs taşınmaz/kopyalanmaz. hacimKuyruk_.splice(hacimKuyruk_.begin(), hacimKuyruk_, it); } return; } hacimIsteEkle(*d); } void Sahne::kabaIsAyniBrepBekle(Dugum& d) { auto it = kabaIsler_.find(d.kimlik); if (it == kabaIsler_.end() || it->second.brep.get() != d.brep.get()) return; if (it->second.sonuc.valid()) it->second.sonuc.wait(); // aynı brep'e eşzamanlı mesh yazımı önlenir kabaIsler_.erase(it); } void Sahne::kabaMeshBekleyenUygula() { // hacim/alan TEK-İŞÇİ kuyruğu: yalnız kuyruk başı koşar; hazırsa düğüme yaz (brep // değiştiyse/düğüm silindiyse at) + sıradakini başlat. Paralel GProp seli yok → // CPU tekelleşmez, kapanışta ~Sahne en fazla 1 işi bekler. while (!hacimKuyruk_.empty()) { HacimIs& is = hacimKuyruk_.front(); if (!is.sonuc.valid()) { // sırası geldi → şimdi başlat Dugum* d = bul(is.kimlik); if (!d || !d->brep || d->brep.get() != is.brep.get()) { // bayat istek → düş hacimKuyruk_.pop_front(); continue; } auto brep = is.brep; is.sonuc = std::async(std::launch::async, [brep] { geometri::occtSinyalKur(); return std::make_pair(brep->hacim(), brep->alan()); }); } if (is.sonuc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) break; const auto [h, a] = is.sonuc.get(); Dugum* d = bul(is.kimlik); if (d && d->brep.get() == is.brep.get()) { d->hacim = h; d->alan = a; } hacimKuyruk_.pop_front(); } // Üzerine yazılmış işler: bitince sessizce düşür (bekleme yok — future hazır mı bak). for (auto it = kabaCop_.begin(); it != kabaCop_.end();) { if (it->sonuc.valid() && it->sonuc.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { it->sonuc.get(); it = kabaCop_.erase(it); } else { ++it; } } // S3 stabilite (render_stabilite_performans.md): kabaMeshVeriUygula içindeki GpuTampon // ctor'ları (vertex+indeks) toplu oturum DIŞINDAYSA her biri anindaGonder ile ayrı // submit+fence yapar → kare kaydı sürerken GPU'yu serileştirir (mid-frame stall/hitch). // Orbit/hover sırasında aynı karede birden fazla gövdenin kaba-LOD işi bitebiliyor → // gövde başına 2 ayrı bekleme birikiyordu. Bu döngüyü TEK toplu oturumla sarmalayınca // GpuTampon ctor'u otomatik topluKopyaKaydet yoluna düşer (bkz. gpu_tampon.cpp: "eğer // vb.topluYuklemeAktif() ise") → bu karenin tüm kaba-mesh yüklemeleri tek (veya eşik- // tetikli birkaç) submit+fence'e iner. tamponlariYukle'deki topluBizde ile AYNI kalıp/ // aynı bayrak (AyarSistemi: gorunum.tampon_toplu_yukleme) — yeni davranış değil, mevcut // toplu-yükleme yolunun bu çağrı sitesine de uygulanması. const bool kabaTopluBizde = tamponTopluYukleme() && !vb_.topluYuklemeAktif(); if (kabaTopluBizde) vb_.topluYuklemeBasla(); for (auto it = kabaIsler_.begin(); it != kabaIsler_.end();) { if (!it->second.sonuc.valid() || it->second.sonuc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { ++it; continue; } KabaMeshSonuc ks = it->second.sonuc.get(); Dugum* d = bul(it->first); if (d && d->brep.get() == it->second.brep.get()) { if (ks.onb) kabaOnb_[it->first] = std::move(ks.onb); kabaMeshVeriUygula(*d, ks.veri); } it = kabaIsler_.erase(it); // düğüm silinmiş/brep değişmişse sonuç sessizce atılır } if (kabaTopluBizde) vb_.topluYuklemeBitir(); } void Sahne::kabaMeshVeriUygula(Dugum& d, const MeshVeri& kabaVeri) { d.kabaKonum.clear(); d.kabaNormal.clear(); d.kabaKonum.reserve(kabaVeri.vertexler.size()); d.kabaNormal.reserve(kabaVeri.vertexler.size()); for (const auto& v : kabaVeri.vertexler) { d.kabaKonum.push_back({v.konum[0], v.konum[1], v.konum[2]}); d.kabaNormal.push_back({v.normal[0], v.normal[1], v.normal[2]}); } d.kabaIndeks = kabaVeri.indeksler; // Kaba mesh per-yüz renk/alt/alfa (ince cpuMeshKopyala ile AYNI kural): STEP taban rengi // (yuzRenkleri[alt-1]) > vertex rengi, yuzOz override uygulanır → orbit/hover'da renk KORUNUR. kabaRenkPisir(d, kabaVeri.vertexler); // Kaba mesh'i GPU'ya da yükle → YÜZ PICK bunu kullanır (az üçgen VS, hover hızlanır). // altKimlik (yüz ordinali) ucgenle'den geldiği için kaba mesh'te de doğru. if (!kabaVeri.vertexler.empty() && !kabaVeri.indeksler.empty()) { // Düzeltilmiş renk/alfa'lı vertexler (kabaVertexKur) yüklenir — ham ucgenle çıktısı // yuzOz/canlı düzenlemeyi taşımıyordu → bireysel LOD geçişinde renk atlaması yapıyordu. const std::vector kvtx = kabaVertexKur(d); d.kabaVertexTampon = GpuTampon(vb_, kvtx.data(), kvtx.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.kabaIndeksTampon = GpuTampon(vb_, kabaVeri.indeksler.data(), kabaVeri.indeksler.size() * 4, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); d.kabaIndeksSayi = (u32)kabaVeri.indeksler.size(); } else { d.kabaIndeksSayi = 0; } } // Onceden uretilmis mesh verisini GPU'ya yukler + CPU kopyayi/olcumu gunceller. // Yalniz main-thread'de cagrilir (Vulkan + olay). yenidenInsa ile ithalEkleHazir // ayni yoldan gecer; mesh nerede uretildigi (main/worker) farketmez. bool& tamponTopluYukleme() { static bool a = true; return a; } void Sahne::tamponlariYukle(Dugum& d, const MeshVeri& veri, const KenarVeri& kenarlar, const NoktaVeri& noktalar) { const auto ty0 = std::chrono::steady_clock::now(); // faz ölçümü ("önce ölç" — >100ms loglanır) // Süren arka-plan BVH işi eski cpuKonum/cpuIndeks bloklarını okuyor olabilir → // vektörlere dokunmadan ÖNCE durdur (kısa bekleme; kurucu sık iptal-kontrollü). bvhIsIptal(d.kimlik); // CPU ucgen kopyasi (isin-yuzey kesisimi icin) cpuMeshKopyala(d, veri); const auto ty1 = std::chrono::steady_clock::now(); // Per-yüz GPU attribute buffer (SSBO) global aralık ayır (Aşama 0 — CPU tarafı, render // dokunmaz). Yüz sayısı = en büyük altKimlik ordinali. Yalnız değişince yeniden ayır // (remesh'te sayı sabitse aralık korunur → gereksiz büyüme yok; feature yüz ekler/silerse yeni aralık). { u32 yeniYuzSayi = 0; for (u32 a : d.cpuAlt) yeniYuzSayi = std::max(yeniYuzSayi, a); if (yeniYuzSayi != 0 && (yeniYuzSayi != d.yuzSayi_ || d.yuzBaz_ == 0)) { d.yuzBaz_ = yuzAralikAyir(yeniYuzSayi); d.yuzSayi_ = yeniYuzSayi; } } // hacim/alan GProp kirli/devasa BREP'te ~9sn (yalnız bilgi paneli) → AYARDAN BAĞIMSIZ // her zaman arka planda; TEK-İŞÇİLİ TEMBEL KUYRUK (paralel GProp seli CPU'yu tekelliyor, // kapanışta dakikalarca join'e dönüşüyordu — canlı vaka). hacimIsteEkle(d); const auto ty2 = std::chrono::steady_clock::now(); olaylar_.yayinla(BrepDegisti{d.kimlik}, d.ad.c_str()); const auto ty3 = std::chrono::steady_clock::now(); // TOPLU YÜKLEME: bir düğümün vertex/indeks/kenar/nokta tamponları AYRI AYRI anlık // submit+fence yapıyordu. ÖLÇÜLDÜ 07-21: her anlık transfer submit 0.0 ms + fence // beklemesi ~60 ms (grafik kuyruğunda uçuştaki karenin arkasına giriyor) → düğüm // başına ~16 transfer ≈ 1 SANİYE. Toplu oturumda kopyalar tek komut tamponuna // kaydedilir ve submit+fence BİR KEZ ödenir (GpuTampon zaten toplu-farkında). // Ayar `gorunum.tampon_toplu_yukleme` ile eski (düğüm başına anlık) yola dönülür. const bool topluBizde = tamponTopluYukleme() && !vb_.topluYuklemeAktif(); if (topluBizde) vb_.topluYuklemeBasla(); // Eski tamponlar ucusta olabilir → yikim GpuTampon move-assign icinde EMEKLI // listesine ertelenir (vb_.tamponEmekliEt); device-idle beklemeye gerek yok. std::vector ovBuf; const std::vector& vtx = ogeRenkliVertexler(d, veri, ovBuf); d.vertexTampon = GpuTampon(vb_, vtx.data(), vtx.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.indeksTampon = GpuTampon(vb_, veri.indeksler.data(), veri.indeksler.size() * 4, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); d.indeksSayi = (u32)veri.indeksler.size(); d.yerelSinir = veri.sinir; cpuKenarKopyala(d, kenarlar.vertexler); if (!kenarlar.vertexler.empty()) { d.kenarTampon = GpuTampon(vb_, kenarlar.vertexler.data(), kenarlar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.kenarVertexSayi = (u32)kenarlar.vertexler.size(); } else { d.kenarVertexSayi = 0; } cpuNoktaKopyala(d, noktalar.vertexler); if (!noktalar.vertexler.empty()) { d.noktaTampon = GpuTampon(vb_, noktalar.vertexler.data(), noktalar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.noktaSayi = (u32)noktalar.vertexler.size(); } else { d.noktaSayi = 0; } if (topluBizde) vb_.topluYuklemeBitir(); // tek submit + tek fence // Büyük mesh: BVH'yi ŞİMDİ arka planda kurmaya başla (ilk hover'ı bekleme — // kullanıcı modele geldiğinde çoktan hazır; yüz-milyon üçgende ilk-hover // ana-thread donması tarihe karışır). Küçük mesh isinVur'da senkron kurulur. if (bvhArkaplan() && d.cpuIndeks.size() / 3 > kBvhSenkronUcgen) bvhKurucu().baslat(d.kimlik, d.cpuKonum.data(), d.cpuKonum.size(), d.cpuIndeks.data(), d.cpuIndeks.size()); // Renk mesh per-vertex'te (cpuRenkBaz) ama yüz-özelliği (yuzOz) boşsa → yuzOz'a KÖPRÜLE. // Renk artık yüz-özelliği: undo snapshot (DugumPusula.yuzOz) + ordinal remap KORUR, tekdüze // ucgenle ile silinmez. İMPORT anında çalışır (yuzOz boş); push/pull sonrası yuzOz remap ile // DOLU → koşul false → atlanır. Yalnız gerçekten renkli mesh (≥2 farklı renk) köprülenir. if (d.yuzOz.empty() && d.cpuRenkBaz.size() == d.cpuAlt.size() && d.cpuRenkBaz.size() >= 2) { const Vec3f r0 = d.cpuRenkBaz[0]; bool renkli = false; for (const Vec3f& c : d.cpuRenkBaz) if (c.x != r0.x || c.y != r0.y || c.z != r0.z) { renkli = true; break; } if (renkli) for (std::size_t i = 0; i < d.cpuAlt.size(); ++i) { const u32 o = d.cpuAlt[i]; if (o) d.yuzOzellikYaz(o).renk = d.cpuRenkBaz[i]; } } const auto tyMs = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; if (tyMs(ty0, std::chrono::steady_clock::now()) > 100.0) gunluk::bilgi("[perf] tamponlariYukle '{}': kopya {:.0f}ms + hacim/alan {:.0f}ms + olay {:.0f}ms " "+ gpu/kenar/köprü {:.0f}ms", d.ad, tyMs(ty0, ty1), tyMs(ty1, ty2), tyMs(ty2, ty3), tyMs(ty3, std::chrono::steady_clock::now())); } void Sahne::ogeRenkYenile(Dugum& d) { const std::size_t n = d.cpuKonum.size(); if (n == 0) return; // mesh yok → dokunma (yuzOz boş olsa bile: silinen override'lar tabana döner) if (d.cpuAlfa.size() != n) d.cpuAlfa.assign(n, 1.0f); // eski mesh (alfa'sız kopyalanmış) güvencesi const bool bazVar = d.cpuRenkBaz.size() == n; // taban renk saklı mı (cpuMeshKopyala doldurdu) // 1) cpuRenk/cpuAlfa: override edilen yüz → override değeri; kaldırılan yüz → SAKLI TABAN renk // (cpuRenkBaz — override YOKMUŞ gibi gerçek renk) + opak. Tahmin YOK → başka yüz etkilenmez. for (std::size_t i = 0; i < n; ++i) { const auto it = d.yuzOz.find(d.cpuAlt[i]); if (it != d.yuzOz.end()) { d.cpuRenk[i] = it->second.renk; d.cpuAlfa[i] = it->second.gorunur ? it->second.alfa : 0.0f; } else { if (bazVar) d.cpuRenk[i] = d.cpuRenkBaz[i]; // override kaldırıldı → gerçek tabana dön d.cpuAlfa[i] = 1.0f; } } // 2) vertexTampon'u yeniden kur (konum/normal/alt sabit; renk+alfa güncel) — bireysel çizim. std::vector vtx(n); for (std::size_t i = 0; i < n; ++i) { const Vec3f& k = d.cpuKonum[i]; const Vec3f nm = i < d.cpuNormal.size() ? d.cpuNormal[i] : Vec3f{0, 0, 1}; const Vec3f& c = d.cpuRenk[i]; Vertex v{}; v.konum[0] = k.x; v.konum[1] = k.y; v.konum[2] = k.z; v.normal[0] = nm.x; v.normal[1] = nm.y; v.normal[2] = nm.z; v.renk[0] = c.x; v.renk[1] = c.y; v.renk[2] = c.z; v.altKimlik = i < d.cpuAlt.size() ? d.cpuAlt[i] : 0; v.alfa = d.cpuAlfa[i]; v.globalYuzIndeks = v.altKimlik > 0 ? d.yuzBaz_ + v.altKimlik - 1 : 0; // Aşama 0d (SSBO global) vtx[i] = v; } d.vertexTampon = GpuTampon(vb_, vtx.data(), n * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); // (konum/normal değişmedi → BVH önbelleği geçerli, reset gerekmez.) // 3) Mega-parti yeniden pişsin: imza cpuRenk/cpuAlfa.data() ptr'larını hash'ler → tahsisi // tazele (fresh vektör = yeni adres; içerik zaten güncel, salt cache busting). std::vector tazeRenk(d.cpuRenk.begin(), d.cpuRenk.end()); d.cpuRenk = std::move(tazeRenk); std::vector tazeAlfa(d.cpuAlfa.begin(), d.cpuAlfa.end()); d.cpuAlfa = std::move(tazeAlfa); // 4) Kaba (LOD) mesh renk/alfa da güncel → orbit/hover sırasında canlı düzenleme görünür, // renk KAYBOLMAZ. Aynı override kuralı; taze vektör = yeni ptr → mega-parti imza busting. const std::size_t kn = d.kabaAlt.size(); if (d.kabaRenk.size() == kn && d.kabaAlfa.size() == kn && kn) { const bool kbazVar = d.kabaRenkBaz.size() == kn; for (std::size_t i = 0; i < kn; ++i) { const auto it = d.yuzOz.find(d.kabaAlt[i]); if (it != d.yuzOz.end()) { d.kabaRenk[i] = it->second.renk; d.kabaAlfa[i] = it->second.gorunur ? it->second.alfa : 0.0f; } else { if (kbazVar) d.kabaRenk[i] = d.kabaRenkBaz[i]; d.kabaAlfa[i] = 1.0f; } } std::vector tk(d.kabaRenk.begin(), d.kabaRenk.end()); d.kabaRenk = std::move(tk); std::vector tka(d.kabaAlfa.begin(), d.kabaAlfa.end()); d.kabaAlfa = std::move(tka); // 5) Kaba GPU tamponu da yenilensin (bireysel orbit/hover LOD yolu bunu çizer) — // yalnız CPU kabaRenk güncellemek yetmiyordu: GPU'da eski renk kalıp fare // gezerken ince↔kaba geçişinde renkler gidip geliyordu. if (d.kabaVertexTampon.tampon() && d.kabaKonum.size() == kn) { const std::vector kvtx = kabaVertexKur(d); d.kabaVertexTampon = GpuTampon(vb_, kvtx.data(), kvtx.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); } } } Dugum& Sahne::ithalEkleHazir(geometri::IthalGovde ig, MeshVeri veri, KenarVeri kenarlar, NoktaVeri noktalar, Varlik ust) { Dugum& d = hamEkle(DugumTip::Ithal); d.renk = ig.renkVar ? ig.renk : Vec3f{0.62f, 0.66f, 0.72f}; d.brep = std::make_shared(std::move(ig.govde)); // STEP per-yüz renkleri → öğe özellik deposu (undo/redo + dosyada korunur). if (d.brep && d.brep->yuzRenkliMi()) ogeOzellikBrepYuzRenkUygula(d, *d.brep); if (ust != VARLIK_YOK) dunya_.ekle(d.kimlik, HiyerarsiBilesen{ust}); // GSD Tel (wireframe) yüz mesh'i BOŞ olur → kenar/nokta varsa yine yükle (0-boyut // yüz tamponu GpuTampon'da güvenli). Yalnız üçü de boşsa (dejenere) atla. if (d.brep && d.brep->gecerliMi() && (!veri.vertexler.empty() || !kenarlar.vertexler.empty() || !noktalar.vertexler.empty())) tamponlariYukle(d, veri, kenarlar, noktalar); // Coarse LOD import'ta yalnız ARKA-PLAN modunda üretilir (worker; ana thread donmaz). // Senkron mod eski davranışı korur (import'ta coarse yok — 2. milyon-üçgen tessellation // yükleme donması yapıyordu); coarse ilk yenidenInsa'da gelir. if (kabaMeshArkaplan() && d.brep && d.brep->gecerliMi()) kabaMeshUret(d); // Boş mesh (geçerli brep ama üçgenlenemedi) → geçersiz yerelSinir bırakma: sentinel bbox // (yenidenInsa ile aynı sözleşme; NaN kamera fit nüksüne karşı kaynakta savunma). if (!d.yerelSinir.gecerliMi()) d.yerelSinir = {{-0.01f, -0.01f, -0.01f}, {0.01f, 0.01f, 0.01f}}; return d; } namespace { // CATIA "Part" iskelet gruplarına role-özel varsayılan ağaç ikonu. Klasör "▤" yerine // her rol kendine has glif alır (PartBody/Parameters/Geometrical Sets ayrışsın). Tek // kaynak: hem ilk oluşturma hem .fikocad yükleme (belgeUygula → grupEkle) buradan geçer, // serileştirme değişmeden ikon kalıcıdır. Kullanıcı override'ı (per-node ASMG / tip Ayar) // coz() içinde bu sağlayıcı glifin ÜSTÜNDE çözülür → default ezilmez. const char* grupIskeletSimge(const std::string& ad) { if (ad == "PartBody" || ad.rfind("Body.", 0) == 0) return "🧊"; // katı gövde konteyneri (▣'ten ayrı) if (ad == "Parameters") return "🔢"; // parametre grubu (ƒ'ten ayrı) if (ad == "Geometrical Sets") return "🌐"; // yüzey/tel geometri KONTEYNERİ if (ad.rfind("Geometrical Set ", 0) == 0) return "◈"; // bireysel set (konteynerden ayrı) if (ad == "Referans") return "⌖"; // referans geometri seti if (ad == "Origin") return "🧭"; // eksen sistemi kökü if (ad == "Part") return "🧩"; // Part kökü return nullptr; // diğer gruplar → klasör "▤" } } // namespace Varlik Sahne::grupEkle(std::string ad, Varlik ust) { const Varlik v = dunya_.olustur(); const char* iskelet = grupIskeletSimge(ad); dunya_.ekle(v, AdBilesen{std::move(ad)}); dunya_.ekle(v, GorunurBilesen{true}); dunya_.ekle(v, HiyerarsiBilesen{ust}); if (iskelet) dunya_.ekle(v, SimgeBilesen{iskelet}); // rol ikonu (varsayılan) dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); const AdBilesen* a = dunya_.bul(v); olaylar_.yayinla(VarlikEklendi{v}, a ? a->ad.c_str() : nullptr); return v; } namespace { // Referans geometri boyutu (yerel yari-uzunluk). ±L birim → düzlem 2L x 2L. constexpr f32 REF_L = 5.0f; // Referans geometri rengi — CATIA klasik: nötr açık gri (seçim/vurgu ayrı renklendirir). constexpr Vec3f REF_RENK{0.80f, 0.82f, 0.86f}; // XY yerel düzleminde sade kare çerçeve (LINE_LIST). Grid yok — CATIA tarzı temiz // düzlem. Yönlendirme donusum.yonelim ile dışarıda yapılır. void duzlemKenarlari(std::vector& cikti) { auto ekle = [&](f32 x0, f32 y0, f32 x1, f32 y1) { cikti.push_back({{x0, y0, 0.0f}, 0}); cikti.push_back({{x1, y1, 0.0f}, 0}); }; ekle(-REF_L, -REF_L, REF_L, -REF_L); ekle(REF_L, -REF_L, REF_L, REF_L); ekle(REF_L, REF_L, -REF_L, REF_L); ekle(-REF_L, REF_L, -REF_L, -REF_L); } // Eksen: yerel +Z boyunca orijinden dışarı DÜZ tek çizgi (0 → +L). CATIA tarzı; // donusum ile X/Y/Z'ye yönlendirilir, uçta x/y/z etiketi (main.cpp overlay). void eksenKenarlari(std::vector& cikti) { cikti.push_back({{0.0f, 0.0f, 0.0f}, 0}); cikti.push_back({{0.0f, 0.0f, REF_L}, 0}); } // Referans NOKTA: yerel orijinde 3 eksenli küçük hac (X/Y/Z bacakları). Ekran-sabit // ölçekli (Eksen gibi) → zoom'dan bağımsız aynı işaret boyutu. altKimlik=0. void noktaHacKenarlari(std::vector& c) { const f32 r = REF_L * 0.22f; // hac bacak yarı-uzunluğu (kompakt CATIA işareti) c.push_back({{-r, 0, 0}, 0}); c.push_back({{r, 0, 0}, 0}); c.push_back({{0, -r, 0}, 0}); c.push_back({{0, r, 0}, 0}); c.push_back({{0, 0, -r}, 0}); c.push_back({{0, 0, r}, 0}); } // ---- CATIA Eksen Sistemi: TEK dugumun birlesik kenar + nokta tamponlari ---- // Alt-ordinal her vertex'in altKimlik alaninda gomulu → GPU pick + kenar hover // izole shader'i parca-parca (yalniz hedeflenen alt-oge) calisir. Origin uzayinda // (yerel = dunya, donusum birim); yonelim disarida uygulanmaz. constexpr f32 EKS_L = REF_L; // eksen cizgi uzunlugu (+yon) constexpr f32 EKS_LKOSE = REF_L * 0.28f; // L-kose bacak uzunlugu (~%28 mesafe, CATIA) // 3 eksen cizgisi (X/Y/Z), altKimlik 1/2/3. LINE_LIST (2 vertex/segment). void eksenSisKenarlari(std::vector& e) { using namespace eksensis; e.push_back({{0, 0, 0}, EKSEN_X}); e.push_back({{EKS_L, 0, 0}, EKSEN_X}); e.push_back({{0, 0, 0}, EKSEN_Y}); e.push_back({{0, EKS_L, 0}, EKSEN_Y}); e.push_back({{0, 0, 0}, EKSEN_Z}); e.push_back({{0, 0, EKS_L}, EKSEN_Z}); // L-koseler: her düzlem cifti icin iki kisa bacak (o düzlemin iki ekseni boyunca, // birbirine kose ile baglanir). altKimlik 4/5/6 = XY/YZ/ZX. const f32 d = EKS_LKOSE; auto lkose = [&](u32 alt, Vec3f a, Vec3f b, Vec3f c) { // a→b, b→c iki bacak e.push_back({{a.x, a.y, a.z}, alt}); e.push_back({{b.x, b.y, b.z}, alt}); e.push_back({{b.x, b.y, b.z}, alt}); e.push_back({{c.x, c.y, c.z}, alt}); }; lkose(DUZLEM_XY, {d, 0, 0}, {d, d, 0}, {0, d, 0}); // XY düzlemi (X ve Y bacak) lkose(DUZLEM_YZ, {0, d, 0}, {0, d, d}, {0, 0, d}); // YZ düzlemi (Y ve Z bacak) lkose(DUZLEM_ZX, {0, 0, d}, {d, 0, d}, {d, 0, 0}); // ZX düzlemi (Z ve X bacak) } // Orijin noktasi (0,0,0), altKimlik 7. POINT_LIST tek vertex. void eksenSisNoktalari(std::vector& n) { n.push_back({{0, 0, 0}, eksensis::ORIJIN}); } } // namespace Varlik Sahne::originKur() { // CATIA "Part" iskeleti: kimlik = ağaç sırası olduğundan Part > Origin > Parameters > // Geometrical Sets > PartBody SIRAYLA önden oluşturulur (lazy metotlar sonradan hazır bulur). partKokAl(); const Varlik origin = grupEkle("Origin", partKok_); // CATIA tarzı TEK "Eksen Sistemi" düğümü: 3 eksen çizgisi + 3 L-köşe düzlem + // orijin nokta hepsi tek nesnede (alt-ordinal ile ayrılır). Ürün ağacında X/Y/Z // ve düzlemler AYRI satır GÖZÜKMEZ; seçilebilirlik alt-ordinalle korunur. std::vector ev, nv; eksenSisKenarlari(ev); eksenSisNoktalari(nv); const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::EksenSistemi; d.ad = "Eksen Sistemi"; d.renk = REF_RENK; d.yerelSinir = {{-REF_L, -REF_L, -REF_L}, {REF_L, REF_L, REF_L}}; Dugum& ref = dunya_.ekle(v, std::move(d)); dunya_.ekle(v, HiyerarsiBilesen{origin}); dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); cpuKenarKopyala(ref, ev); // datum salt-kenar alan-secim testi (birlesik cizgiler) ref.kenarTampon = GpuTampon(vb_, ev.data(), ev.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ref.kenarVertexSayi = (u32)ev.size(); ref.noktaTampon = GpuTampon(vb_, nv.data(), nv.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ref.noktaSayi = (u32)nv.size(); eksenSis_ = v; olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); // Kalan Part alt-grupları CATIA sırasında önden kurulur (boş da olsa ağaçta görünür): // Parameters → Geometrical Sets → PartBody. (Origin zaten yukarıda ilk sırada.) parametreGrupAl(); geometrikSetKokAl(); partBodyAl(); return origin; } Varlik Sahne::eksenSistemiDuzlemi(u32 alt) { if (!eksensis::duzlemMi(alt)) return VARLIK_YOK; const int i = (int)(alt - eksensis::DUZLEM_XY); // 0=XY,1=YZ,2=ZX // Ayni L-kose icin daha once türetilmis düzlem varsa onu ver (mükerrer yok). if (eksenDuzlem_[i] && bul(eksenDuzlem_[i])) return eksenDuzlem_[i]; const f64 yariPi = std::numbers::pi / 2.0; struct DT { const char* ad; Quatd yon; }; const DT duzlemler[3] = { {"XY Düzlemi", Quatd{}}, // yerel +Z = +Z {"YZ Düzlemi", Quatd::eksenAci({0, 1, 0}, yariPi)}, // +Z→+X {"ZX Düzlemi", Quatd::eksenAci({1, 0, 0}, yariPi)}, // +Z→+Y }; std::vector dv; duzlemKenarlari(dv); // Origin altinda yasar (Eksen Sistemi ile ayni grup) — referans DÜZLEM dugumu. Dugum* es = bul(eksenSis_); const Varlik ust = es ? (dunya_.bul(eksenSis_) ? dunya_.bul(eksenSis_)->ust : VARLIK_YOK) : VARLIK_YOK; Dugum& ref = referansEkle(DugumTip::Duzlem, duzlemler[i].ad, ust, REF_RENK, duzlemler[i].yon, dv); eksenDuzlem_[i] = ref.kimlik; return ref.kimlik; } Dugum& Sahne::referansEkle(DugumTip tip, const std::string& ad, Varlik ust, Vec3f renk, Quatd yonelim, const std::vector& kenarlar) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = tip; d.ad = ad; d.renk = renk; d.donusum.yonelim = yonelim; // origin'de duruyor; yalnız yönelim // Referans geometri boyutuna göre yerel sınır (tümünü sığdır bozulmasın). d.yerelSinir = {{-REF_L, -REF_L, -REF_L}, {REF_L, REF_L, REF_L}}; Dugum& ref = dunya_.ekle(v, std::move(d)); if (ust != VARLIK_YOK) dunya_.ekle(v, HiyerarsiBilesen{ust}); dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); // Kenar tamponu doğrudan (tamponlariYukle mesh ister; referansta mesh yok). cpuKenarKopyala(ref, kenarlar); // datum salt-kenar hassas alan-secim testi if (!kenarlar.empty()) { ref.kenarTampon = GpuTampon(vb_, kenarlar.data(), kenarlar.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ref.kenarVertexSayi = (u32)kenarlar.size(); } olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return ref; } u32 Sahne::kenarNesneEkle(const std::string& ad, Varlik ust, Vec3f renk, const std::vector& kenarlar, const Aabbf& sinir) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::TakimYolu; d.ad = ad; d.renk = renk; d.yerelSinir = sinir; Dugum& ref = dunya_.ekle(v, std::move(d)); if (ust != VARLIK_YOK) dunya_.ekle(v, HiyerarsiBilesen{ust}); dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); cpuKenarKopyala(ref, kenarlar); if (!kenarlar.empty()) { ref.kenarTampon = GpuTampon(vb_, kenarlar.data(), kenarlar.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ref.kenarVertexSayi = (u32)kenarlar.size(); } olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return v; } u32 Sahne::hazirMeshEkle(std::string ad, Varlik ust, Vec3f renk, MeshVeri veri, KenarVeri kenarlar, NoktaVeri noktalar) { Dugum& d = hamEkle(DugumTip::Ithal); d.ad = std::move(ad); d.renk = renk; d.brep.reset(); // mesh-only: B-rep yok if (ust != VARLIK_YOK) dunya_.ekle(d.kimlik, HiyerarsiBilesen{ust}); // CPU ucgen kopyasi (isin-yuzey kesisimi) cpuMeshKopyala(d, veri); // Büyük mesh-only import: BVH'yi şimdi arka planda kur (tamponlariYukle ikizi). if (bvhArkaplan() && d.cpuIndeks.size() / 3 > kBvhSenkronUcgen) bvhKurucu().baslat(d.kimlik, d.cpuKonum.data(), d.cpuKonum.size(), d.cpuIndeks.data(), d.cpuIndeks.size()); d.yerelSinir = veri.sinir; // Boş mesh → veri.sinir default geçersiz: sentinel bbox (geçersiz yerelSinir bırakma). if (!d.yerelSinir.gecerliMi()) d.yerelSinir = {{-0.01f, -0.01f, -0.01f}, {0.01f, 0.01f, 0.01f}}; if (!veri.vertexler.empty()) { std::vector ovBuf; const std::vector& vtx = ogeRenkliVertexler(d, veri, ovBuf); d.vertexTampon = GpuTampon(vb_, vtx.data(), vtx.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.indeksTampon = GpuTampon(vb_, veri.indeksler.data(), veri.indeksler.size() * 4, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); d.indeksSayi = (u32)veri.indeksler.size(); } cpuKenarKopyala(d, kenarlar.vertexler); if (!kenarlar.vertexler.empty()) { d.kenarTampon = GpuTampon(vb_, kenarlar.vertexler.data(), kenarlar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.kenarVertexSayi = (u32)kenarlar.vertexler.size(); } cpuNoktaKopyala(d, noktalar.vertexler); if (!noktalar.vertexler.empty()) { d.noktaTampon = GpuTampon(vb_, noktalar.vertexler.data(), noktalar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d.noktaSayi = (u32)noktalar.vertexler.size(); } // NOT: hamEkle zaten VarlikEklendi yayinladi (ad varsayilan; final ad ust satirda). return d.kimlik; } bool Sahne::hazirMeshGuncelle(u32 kimlik, const MeshVeri& veri, const KenarVeri& kenarlar, const NoktaVeri& noktalar) { Dugum* d = bul(kimlik); // Yalniz mesh-only (brep'siz Ithal) dugumler: brep'li dugumde mesh tek dogruluk // kaynagi degildir (yenidenInsa turetir) — desync olusmasin. if (!d || d->tip != DugumTip::Ithal || d->brep) return false; // Mega-parti imzasi cpuKonum.data() adresini hash'ler: eski tahsisi yeni tahsis // yapilana dek CANLI tutarak adresin her guncellemede DEGISMESI garanti edilir → // dinamikMesh filtresi main.cpp'ye eklenene kadar bile bayat parti gorunmez // (en kotu: bugunku gibi parti yeniden pisirilir, asla yanlis cizilmez). bvhIsIptal(kimlik); // eski cpuKonum/cpuIndeks bloklarını okuyan iş varsa durdur const std::vector eskiKonum = std::move(d->cpuKonum); d->cpuKonum = {}; cpuMeshKopyala(*d, veri); if (veri.sinir.gecerliMi()) d->yerelSinir = veri.sinir; d->dinamikMesh = true; // render toplu (mega-parti) yolundan haric tutulur // GPU: AYNI tamponlara ping-pong yaz — buffer create / submit / fence / device-idle // YOK (kapasite yeterse salt memcpy). Olay yayini yok (kare-kare spam olmasin; // dugum kimligi/donusumu degismiyor). if (!veri.vertexler.empty()) { std::vector ovBuf; const std::vector& vtx = ogeRenkliVertexler(*d, veri, ovBuf); d->vertexTampon.guncelle(vb_, vtx.data(), vtx.size() * sizeof(Vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->indeksTampon.guncelle(vb_, veri.indeksler.data(), veri.indeksler.size() * 4, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); d->indeksSayi = (u32)veri.indeksler.size(); } else { d->indeksSayi = 0; // bos an (henuz sivi yok) — tamponlar sonraki kare icin durur } cpuKenarKopyala(*d, kenarlar.vertexler); if (!kenarlar.vertexler.empty()) { d->kenarTampon.guncelle(vb_, kenarlar.vertexler.data(), kenarlar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->kenarVertexSayi = (u32)kenarlar.vertexler.size(); } else { d->kenarVertexSayi = 0; } cpuNoktaKopyala(*d, noktalar.vertexler); if (!noktalar.vertexler.empty()) { d->noktaTampon.guncelle(vb_, noktalar.vertexler.data(), noktalar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->noktaSayi = (u32)noktalar.vertexler.size(); } else { d->noktaSayi = 0; } return true; } void Sahne::kenarNesneGuncelle(u32 kimlik, const std::vector& kenarlar, const Aabbf& sinir) { Dugum* d = bul(kimlik); if (!d || d->tip != DugumTip::TakimYolu) return; d->yerelSinir = sinir; cpuKenarKopyala(*d, kenarlar); if (!kenarlar.empty()) { // Ping-pong yerinde yaz (hazirMeshGuncelle kalıbı): kapasite yeterse salt memcpy, // her çağrıda buffer create/destroy YOK (canlı animasyonda kare-başı churn kasıyordu). d->kenarTampon.guncelle(vb_, kenarlar.data(), kenarlar.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->kenarVertexSayi = (u32)kenarlar.size(); } else { d->kenarVertexSayi = 0; // tampon dursun (çizim sayi==0'da atlar; sonraki dolu kare için) } } void Sahne::kenarNesneCizAralik(u32 kimlik, u32 bas, u32 adet) { Dugum* d = bul(kimlik); if (!d || d->tip != DugumTip::TakimYolu) return; d->kenarCizBas = bas; d->kenarCizSayi = adet; } void Sahne::kenarNesneUstte(u32 kimlik, bool ustte) { Dugum* d = bul(kimlik); if (!d || d->tip != DugumTip::TakimYolu) return; d->kenarUstte = ustte; } void Sahne::kenarNesneKalin(u32 kimlik, bool kalin) { Dugum* d = bul(kimlik); if (!d || d->tip != DugumTip::TakimYolu) return; d->kenarKalin = kalin; } u32 Sahne::gsdDugumEkle(DugumTip tip, const std::string& ad, Varlik ust, Vec3f renk) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = tip; d.ad = ad; d.renk = renk; d.yerelSinir = {{-0.001f, -0.001f, -0.001f}, {0.001f, 0.001f, 0.001f}}; // guvenli baslangic Dugum& ref = dunya_.ekle(v, std::move(d)); if (ust != VARLIK_YOK) dunya_.ekle(v, HiyerarsiBilesen{ust}); dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return v; } bool Sahne::gsdDugumGuncelle(u32 kimlik, std::shared_ptr brep) { Dugum* d = bul(kimlik); if (!d || (d->tip != DugumTip::Tel && d->tip != DugumTip::Yuzey)) return false; if (!brep || !brep->gecerliMi()) return false; // GOTCHA: gecersiz → eski korunur const KenarVeri kenarlar = brep->kenarMesh(); const NoktaVeri noktalar = brep->noktaMesh(); if (d->tip == DugumTip::Yuzey) { // Acik shell/face: DoubleSide mesh (kati ile birlesmez) + kenar + nokta. const MeshVeri veri = brep->ucgenle(d->renk); d->brep = std::move(brep); if (!veri.vertexler.empty()) { tamponlariYukle(*d, veri, kenarlar, noktalar); return true; } // Mesh yok (dejenerе yuzey): en azindan kenar/sinir kur (asagi dus). } else { d->brep = std::move(brep); } // Tel (veya mesh'siz Yuzey): salt kenar + nokta. Sinir kenar/nokta'dan turetilir // (gecerli bbox → NaN kamera fit engellenir). Aabbf sinir; bool sinirVar = false; const auto genislet = [&](const Vec3f& p) { sinir.genislet(p); sinirVar = true; }; // Eski tamponlarin yikimi GpuTampon icinde emekli listesine ertelenir (bekleme yok). cpuKenarKopyala(*d, kenarlar.vertexler); // Tel/mesh'siz Yuzey hassas alan-secim if (!kenarlar.vertexler.empty()) { for (const auto& kv : kenarlar.vertexler) genislet({kv.konum[0], kv.konum[1], kv.konum[2]}); d->kenarTampon = GpuTampon(vb_, kenarlar.vertexler.data(), kenarlar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->kenarVertexSayi = (u32)kenarlar.vertexler.size(); } else { d->kenarVertexSayi = 0; } cpuNoktaKopyala(*d, noktalar.vertexler); if (!noktalar.vertexler.empty()) { for (const auto& nv : noktalar.vertexler) genislet({nv.konum[0], nv.konum[1], nv.konum[2]}); d->noktaTampon = GpuTampon(vb_, noktalar.vertexler.data(), noktalar.vertexler.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->noktaSayi = (u32)noktalar.vertexler.size(); } else { d->noktaSayi = 0; } // Tel'de yuzey mesh yok: eski mesh tamponlarini bosalt. if (d->tip == DugumTip::Tel) { bvhIsIptal(d->kimlik); // vektörler boşaltılmadan önce süren BVH işini durdur d->indeksSayi = 0; d->cpuKonum.clear(); d->cpuNormal.clear(); d->cpuIndeks.clear(); d->cpuAlt.clear(); d->isinBvh.reset(); d->isinBvhEski.reset(); // hayalet pick kalmasın: yüzey kalıcı olarak kaldırıldı d->cpuKonumEski.clear(); d->cpuIndeksEski.clear(); d->cpuAltEski.clear(); } if (sinirVar) { // Tek nokta/dejenerе → sifir-hacim bbox olmasin (fit icin minik pay). if (sinir.kosegen() < 1e-4f) { const Vec3f m = sinir.merkez(); sinir = {{m.x - 0.01f, m.y - 0.01f, m.z - 0.01f}, {m.x + 0.01f, m.y + 0.01f, m.z + 0.01f}}; } d->yerelSinir = sinir; } else if (!d->yerelSinir.gecerliMi()) { // Dejenerе wireframe (kenar+nokta bos): yerelSinir'i asla gecersiz birakma // (K3 nuksu — NaN kamera fit). Mevcut deger gecerliyse koru, degilse sentinel. d->yerelSinir = {{-0.01f, -0.01f, -0.01f}, {0.01f, 0.01f, 0.01f}}; } // Tel/mesh'siz-Yuzey'de hacim() HER ZAMAN ~0 döner (VolumeProperties açık/kapalı- // olmayan yüzeyde anlamlı sonuç veremez) → hacim() içindeki meshTetrahedraHacmi // fallback'i HER SEFERİNDE tetiklenir; bu fallback salt-okunur OLMAYAN ucgenle() // çağrısıyla OCCT şekline YAZAR. STEP assemblelerinde paylaşılan TShape'ler aynı // anda import worker'ı tarafından da mesh'lendiği (ucgenleKaba) için hacimIsteEkle // üzerinden burada da hacim() çağırmak eşzamanlı thread-unsafe OCCT triangülasyon // yazımına → çökmeye yol açıyordu (kök neden bulundu, bkz. proje notları). Bu yolda // hacim() zaten hiçbir zaman anlamlı olmadığından hiç çağrılmıyor; eski senkron // alan() güvenlidir (yalnız SurfaceProperties/LinearProperties, triangülasyon yok). d->hacim = 0; d->alan = d->brep->alan(); olaylar_.yayinla(BrepDegisti{d->kimlik}, d->ad.c_str()); return true; } Varlik Sahne::sketchEkle(Varlik duzlem) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::Sketch; d.ad = "Sketch." + std::to_string(v); d.renk = {0.9f, 0.9f, 0.35f}; d.yerelSinir = {{-REF_L, -REF_L, -REF_L}, {REF_L, REF_L, REF_L}}; Dugum& ref = dunya_.ekle(v, std::move(d)); // Sketch AKTİF BODY (PartBody) altında yaşar; düzlem yalnızca referans // (SketchNesnesi.duzlem + bagimliliklar) — CATIA/FreeCAD spec-tree. dunya_.ekle(v, HiyerarsiBilesen{partBodyAl()}); dunya_.ekle(v, std::make_unique(v, &dunya_, duzlem)); dunya_.ekle(v, std::make_unique(v, &dunya_)); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return v; } Varlik Sahne::pmiEkle(PmiSistemi* ps, u32 pmiKimlik, std::string ad) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::Pmi; d.ad = ad.empty() ? ("PMI." + std::to_string(v)) : std::move(ad); d.renk = {1.0f, 0.77f, 0.25f}; d.gorunur = true; // gorunurluk defter ogesinden okunur (PmiGorunum) d.yerelSinir = {{-0.01f, -0.01f, -0.01f}, {0.01f, 0.01f, 0.01f}}; // NaN-fit koruması Dugum& ref = dunya_.ekle(v, std::move(d)); dunya_.ekle(v, HiyerarsiBilesen{VARLIK_YOK}); dunya_.ekle(v, std::make_unique(v, &dunya_, ps, pmiKimlik)); dunya_.ekle(v, std::make_unique(v, &dunya_, ps, pmiKimlik)); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); olaylar_.yayinla(PmiEklendi{pmiKimlik}, ref.ad.c_str()); return v; } Varlik Sahne::paftaEkle(std::string ad) { const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::Pafta; d.ad = ad.empty() ? ("Teknik Resim." + std::to_string(v)) : std::move(ad); d.renk = {0.65f, 0.8f, 0.95f}; d.gorunur = false; // 3B sahnede cizilmez (cizim mod icinde 2B sayfada) Dugum& ref = dunya_.ekle(v, std::move(d)); dunya_.ekle(v, HiyerarsiBilesen{VARLIK_YOK}); // kok seviyesi dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return v; } void Sahne::sketchKenarYenile(Varlik sketch) { Dugum* d = bul(sketch); if (!d || d->tip != DugumTip::Sketch) return; // SketchNesnesi'ne eriş (eskiz veri + bağlı düzlem) — sketch_nesne.cpp eskizVeri deseni. NesneKok* nk = dunya_.bul(sketch); auto* sn = (nk && *nk) ? dynamic_cast(nk->get()) : nullptr; const Dugum* dz = sn ? bul(sn->duzlem()) : nullptr; if (!sn || !dz) { d->cpuKenar.clear(); d->cpuKenarAlt.clear(); d->cpuNokta.clear(); d->cpuNoktaAlt.clear(); return; } const eskiz::EskizVeri& ev = sn->eskiz(); // Düzlem-yerel (u,v,0) → dünya. Sketch düğümü dönüşümü identity → dünya=yerel // (Selection Stack cpuKenar'ı d.matris() ile çarpar; identity → nokta korunur). const auto uvW = [&](f64 u, f64 v) -> Vec3f { const Vec3d w = dz->donusum.noktaUygula({u, v, 0.0}); return {(f32)w.x, (f32)w.y, (f32)w.z}; }; std::vector kv; // LINE_LIST segmentleri (2i,2i+1 bir segment) std::vector nv; // köşe/merkez noktaları (nokta seçimi) constexpr int YAY_SEG = 48; // daire/yay/elips tesselasyonu (pürüzsüz seçim silüeti) for (const eskiz::Geometri& g : ev.geometriler()) { const u32 ord = g.kimlik; // stabil 1-tabanlı → segment↔eskiz geometri eşlemesi doğrudan const auto seg = [&](const Vec3f& a, const Vec3f& b) { kv.push_back({{a.x, a.y, a.z}, ord}); kv.push_back({{b.x, b.y, b.z}, ord}); }; if (g.tip == eskiz::GeoTip::Cizgi) { const Vec3f a = uvW(ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)); const Vec3f b = uvW(ev.noktaX(g.kimlik, 1), ev.noktaY(g.kimlik, 1)); seg(a, b); nv.push_back({{a.x, a.y, a.z}, ord}); nv.push_back({{b.x, b.y, b.z}, ord}); } else if (g.tip == eskiz::GeoTip::Nokta) { const Vec3f a = uvW(ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)); nv.push_back({{a.x, a.y, a.z}, ord}); } else if (g.tip == eskiz::GeoTip::Spline) { // Merkezi segmentleyici (eskiz_segment Catmull-Rom) — daire dalına düşse // ev.px(g.ir=-1) TAŞARDI (Tur 18 tip-listesi taraması yakaladı). Uçlar // seçilebilir nokta (a/b — kısıt/snap bunlara bağlanır). std::vector s2; eskiz::geoSegmentle(ev, g, s2); for (const eskiz::Seg2B& s : s2) seg(uvW(s.a.x, s.a.y), uvW(s.b.x, s.b.y)); const Vec3f a = uvW(ev.noktaX(g.kimlik, 0), ev.noktaY(g.kimlik, 0)); const Vec3f b = uvW(ev.noktaX(g.kimlik, 1), ev.noktaY(g.kimlik, 1)); nv.push_back({{a.x, a.y, a.z}, ord}); nv.push_back({{b.x, b.y, b.z}, ord}); } else { // Daire / Yay / Elips (+ eliptik yay) — merkez etrafında tesselasyon const f64 cx = ev.noktaX(g.kimlik, 2), cy = ev.noktaY(g.kimlik, 2); const bool elips = g.tip == eskiz::GeoTip::Elips; const f64 ra = ev.px(g.ir); // Daire/Yay yarıçap; Elips büyük yarıçap const f64 rb = elips ? ev.px(g.ir2) : ra; // Elips küçük yarıçap const f64 rot = elips ? ev.px(g.iaci) : 0.0; f64 a0 = 0.0, a1 = 2.0 * std::numbers::pi; // tam çember/elips varsayılan if (g.ib1 >= 0 && g.ib2 >= 0) { a0 = ev.px(g.ib1); a1 = ev.px(g.ib2); } // yay aralığı const f64 cs = std::cos(rot), sr = std::sin(rot); Vec3f onceki{}; bool oncekiVar = false; for (int i = 0; i <= YAY_SEG; ++i) { const f64 t = a0 + (a1 - a0) * (f64)i / (f64)YAY_SEG; const f64 lx = ra * std::cos(t), ly = rb * std::sin(t); const Vec3f p = uvW(cx + lx * cs - ly * sr, cy + lx * sr + ly * cs); if (oncekiVar) seg(onceki, p); onceki = p; oncekiVar = true; } const Vec3f c = uvW(cx, cy); // merkez seçilebilir nokta nv.push_back({{c.x, c.y, c.z}, ord}); } } cpuKenarKopyala(*d, kv); // cpuKenar + cpuKenarAlt (ordinal) — Selection Stack kenar adayı cpuNoktaKopyala(*d, nv); // cpuNokta + cpuNoktaAlt — Selection Stack nokta adayı // GPU pick tamponu: normal tık (Alt'sız) segment seçimi için kenarTampon/noktaTampon // YÜKLENİR (altKimlik=segment ordinali). Çizim değil YALNIZ pick amaçlı — belge_ciz // 3B kenar/mega-parti döngüleri Sketch düğümünü atlar (çift-render yok; 2B overlay çizer). // Sketch duzenlenirken sik yenilenir → dinamik guncelle() yolu (kapasite yeterse // salt memcpy; buffer create + submit + fence STALL yok, ctor'un aksine). if (!kv.empty()) { d->kenarTampon.guncelle(vb_, kv.data(), kv.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->kenarVertexSayi = (u32)kv.size(); } else { d->kenarTampon = GpuTampon(); d->kenarVertexSayi = 0; } if (!nv.empty()) { d->noktaTampon.guncelle(vb_, nv.data(), nv.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); d->noktaSayi = (u32)nv.size(); } else { d->noktaTampon = GpuTampon(); d->noktaSayi = 0; } } Varlik Sahne::yuzdenDuzlemEkle(u32 govde, u32 yuzOrdinal) { Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi() || yuzOrdinal == 0) return VARLIK_YOK; geometri::YuzBilgi yb; Vec3d merkezYerel, normalYerel; if (d->brep->yuzBilgi(yuzOrdinal, yb) && yb.duzlemMi) { merkezYerel = yb.merkez; normalYerel = yb.normal; // düz yüz: centroid + düzlem normali } else if (!geometri::yuzYuzeyNokta(*d->brep, yuzOrdinal, merkezYerel, normalYerel)) { return VARLIK_YOK; // eğri yüz → UV-ortasında TEĞET düzlem (yüzey noktası + normali) } // Yüz merkezi/normali gövde-YERELdir → gövde dönüşümüyle dünyaya taşı. const Vec3d merkezD = d->donusum.noktaUygula(merkezYerel); const Vec3d normalD = d->donusum.yonelim.dondur(normalYerel).birim(); // Düzlemin yerel +Z'si = yüz normali (dünya). Konum = yüz ağırlık merkezi. const Quatd yonelim = Quatd::zYonunden(normalD); std::vector dv; duzlemKenarlari(dv); Dugum& ref = referansEkle(DugumTip::Duzlem, "Yüz Düzlemi", partBodyAl(), REF_RENK, yonelim, dv); ref.donusum.konum = merkezD; // yüz üzerinde konumlandır (referansEkle origin'de bırakır) return ref.kimlik; } Varlik Sahne::referansDuzlemGeriYukle(const Donusumd& donusum, const std::string& ad, Varlik ust) { std::vector dv; duzlemKenarlari(dv); Dugum& ref = referansEkle(DugumTip::Duzlem, ad.empty() ? "Sketch Düzlemi" : ad, ust == VARLIK_YOK ? partBodyAl() : ust, REF_RENK, donusum.yonelim, dv); ref.donusum.konum = donusum.konum; // referansEkle origin'de birakir → tam konumu geri koy ref.donusum.olcek = donusum.olcek; return ref.kimlik; } // ---- CATIA Reference Elements (Nokta / Cizgi / Duzlem) ---- namespace { // Verilen tipteki mevcut referans dugum sayisi + 1 → "Nokta.n" oto-adlandirma. u32 refOrdinal(Sahne& s, DugumTip t) { u32 n = 0; for (const Dugum& d : s.dugumler()) if (d.tip == t) ++n; return n + 1; } } // namespace Varlik Sahne::referansNoktaEkle(const Vec3d& konum, const std::string& ad, Varlik ust) { std::vector kv; noktaHacKenarlari(kv); // ekran-sabit hac isareti (yerel orijinde) const std::string isim = ad.empty() ? ("Nokta." + std::to_string(refOrdinal(*this, DugumTip::Nokta))) : ad; Dugum& ref = referansEkle(DugumTip::Nokta, isim, ust == VARLIK_YOK ? referansGrupAl() : ust, REF_RENK, Quatd{}, kv); ref.donusum.konum = konum; // gp_Pnt dunya konumu (referansEkle origin'de birakir) return ref.kimlik; } Varlik Sahne::referansCizgiEkle(const Vec3d& p0, const Vec3d& p1, const std::string& ad, Varlik ust) { const Vec3d delta = p1 - p0; if (delta.uzunluk() < 1e-9) return VARLIK_YOK; // dejenere (p0==p1) // Cizgi DUNYA-olcekli: yerel uzayda (0 → delta), dugum konumu = p0 → uclar sabit. const Vec3f d1{(f32)delta.x, (f32)delta.y, (f32)delta.z}; std::vector kv; kv.push_back({{0.0f, 0.0f, 0.0f}, 0}); kv.push_back({{d1.x, d1.y, d1.z}, 0}); const Varlik v = dunya_.olustur(); Dugum d; d.kimlik = v; d.tip = DugumTip::Cizgi; d.ad = ad.empty() ? ("Çizgi." + std::to_string(refOrdinal(*this, DugumTip::Cizgi))) : ad; d.renk = REF_RENK; d.donusum.konum = p0; // Gercek yerel sinir (0..delta) → dunya frustum kultu dogru calisir. d.yerelSinir.genislet({0.0f, 0.0f, 0.0f}); d.yerelSinir.genislet(d1); Dugum& ref = dunya_.ekle(v, std::move(d)); dunya_.ekle(v, HiyerarsiBilesen{ust == VARLIK_YOK ? referansGrupAl() : ust}); dunya_.ekle(v, std::make_unique(v, &dunya_)); dunya_.ekle(v, std::make_unique(v, &dunya_)); cpuKenarKopyala(ref, kv); // datum salt-kenar alan-secim testi ref.kenarTampon = GpuTampon(vb_, kv.data(), kv.size() * sizeof(KenarVertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); ref.kenarVertexSayi = (u32)kv.size(); olaylar_.yayinla(VarlikEklendi{v}, ref.ad.c_str()); return v; } Varlik Sahne::referansDuzlemEkle(const Vec3d& konum, const Vec3d& normal, const std::string& ad, Varlik ust) { if (normal.uzunluk() < 1e-9) return VARLIK_YOK; // normal dejenere std::vector dv; duzlemKenarlari(dv); const std::string isim = ad.empty() ? ("Düzlem." + std::to_string(refOrdinal(*this, DugumTip::Duzlem))) : ad; // Yerel +Z = normal (gp_Pln). Sketch/Pad/Mirror bu DugumTip::Duzlem'i dogrudan kullanir. Dugum& ref = referansEkle(DugumTip::Duzlem, isim, ust == VARLIK_YOK ? referansGrupAl() : ust, REF_RENK, Quatd::zYonunden(normal), dv); ref.donusum.konum = konum; // gp_Pln orijini (referansEkle origin'de birakir) return ref.kimlik; } Varlik Sahne::referansOfsetDuzlemEkle(Varlik taban, f64 mesafe, const std::string& ad, Varlik ust) { const Dugum* b = bul(taban); if (!b || b->tip != DugumTip::Duzlem) return VARLIK_YOK; const Vec3d n = b->donusum.yonelim.dondur({0, 0, 1}); if (n.uzunluk() < 1e-9) return VARLIK_YOK; const Vec3d nb = n.birim(); const Vec3d konum = b->donusum.konum + nb * mesafe; const Varlik v = referansDuzlemEkle(konum, nb, ad, ust); if (v != VARLIK_YOK) { DatumTanim t; t.tip = DatumTanim::OfsetDuzlem; t.kaynak = taban; t.mesafe = mesafe; datumTanim_[v] = t; // kaynak tasininca datumRecompute takip eder } return v; } void Sahne::datumRecompute() { // Parametrik datumlari kaynagina gore yeniden coz. Su an yalniz OfsetDuzlem: taban // DUZLEM dugumunun canli konum/normalinden ofset uygula (origin taban → kaynak=0, sabit → atla). for (const auto& [kimlik, t] : datumTanim_) { if (t.tip != DatumTanim::OfsetDuzlem || t.kaynak == 0) continue; Dugum* d = bul(kimlik); if (!d || d->tip != DugumTip::Duzlem) continue; // datum silinmis → tanim bayat, atla const Dugum* b = bul(t.kaynak); if (!b || b->tip != DugumTip::Duzlem) continue; // kaynak yok → eski konum korunur Vec3d nrm = b->donusum.yonelim.dondur({0, 0, 1}); if (nrm.uzunluk() < 1e-9) continue; nrm = nrm.birim(); d->donusum.konum = b->donusum.konum + nrm * t.mesafe; d->donusum.yonelim = Quatd::zYonunden(nrm); // yerel +Z = taban normali } } Varlik Sahne::seciliSketch() const { for (u32 g : seciliGovdeler()) if (const Dugum* d = bul(g); d && d->tip == DugumTip::Sketch) return g; return VARLIK_YOK; } Varlik Sahne::seciliGsdYuzey() const { for (u32 g : seciliGovdeler()) if (const Dugum* d = bul(g); d && d->tip == DugumTip::Yuzey) return g; return VARLIK_YOK; } Varlik Sahne::seciliGsdTel() const { for (u32 g : seciliGovdeler()) if (const Dugum* d = bul(g); d && d->tip == DugumTip::Tel) return g; return VARLIK_YOK; } std::string Sahne::kimlikMetni(const SecimOge& oge) { if (!oge.govde || !oge.alt) return {}; // gövde modu / boş seçim → alt-öğe kimliği yok Dugum* d = bul(oge.govde); if (!d || !d->brep) return {}; if (geometri::kaliciKimlikAcikMi() && !d->brep->kimlikliMi()) d->brep->kimlikleriBaslat(); // lazy doğum-etiketleme (ilk seçimde; additive metadata → güvenli) u64 kid = 0; char tip = 'F'; switch (kip_) { case SecimKip::Yuz: kid = d->brep->yuzKimlik(oge.alt); tip = 'F'; break; case SecimKip::Kenar: kid = d->brep->kenarKimlik(oge.alt); tip = 'E'; break; case SecimKip::Nokta: kid = d->brep->noktaKimlik(oge.alt); tip = 'V'; break; default: return {}; } if (!kid) return {}; return kyaAdBicimle(tip, kid, d->ad); // format↔parse (kyaAdCoz) round-trip garantili } std::string kyaAdBicimle(char tip, u64 kid, const std::string& govdeAd) { const std::string son = std::string(1, tip) + std::to_string(kid); return govdeAd.empty() ? son : govdeAd + ":" + son; // adsız gövde → "F7" (baştaki ":" çöpü yok) } bool kyaAdCoz(const std::string& metin, char& tip, u64& kid) { const std::size_t a = metin.find_first_not_of(" \t\r\n"); // baştaki/sondaki boşluğu kırp if (a == std::string::npos) return false; const std::size_t b = metin.find_last_not_of(" \t\r\n"); std::string s = metin.substr(a, b - a + 1); const std::size_t p = s.rfind(':'); // "Kutu.1:F7" → "F7" (gövde adı prefiksi kozmetik) if (p != std::string::npos) s = s.substr(p + 1); if (s.size() < 2) return false; const char t = s[0]; if (t == 'f' || t == 'F') tip = 'F'; // büyük/küçük harf duyarsız else if (t == 'e' || t == 'E') tip = 'E'; else if (t == 'v' || t == 'V') tip = 'V'; else return false; kid = 0; for (std::size_t i = 1; i < s.size(); ++i) { if (s[i] < '0' || s[i] > '9') return false; // saf sayı (stoull yerine — sondaki çöpü de reddet) kid = kid * 10 + (u64)(s[i] - '0'); } return kid > 0; } bool Sahne::kimlikMetniSec(const std::string& metin) { char tip = 0; u64 kid = 0; if (!kyaAdCoz(metin, tip, kid)) return false; const SecimKip yeniKip = tip == 'F' ? SecimKip::Yuz : tip == 'E' ? SecimKip::Kenar : SecimKip::Nokta; for (Dugum& d : dugumler()) { if (!d.brep) continue; const u32 ord = tip == 'F' ? d.brep->yuzKimlikOrd(kid) : tip == 'E' ? d.brep->kenarKimlikOrd(kid) : d.brep->noktaKimlikOrd(kid); if (ord) { if (kip_ != yeniKip) kipAyarla(yeniKip); // kip değişimi mevcut seçimi temizler sec({d.kimlik, ord}, false); return true; } } return false; // etiket hiçbir gövdede yok (silinmiş öğe / yanlış ad) } Varlik Sahne::partKokAl() { if (partKok_ == VARLIK_YOK) partKok_ = grupEkle("Part", VARLIK_YOK); return partKok_; } Varlik Sahne::parametreGrupAl() { if (parametreGrup_ == VARLIK_YOK) parametreGrup_ = grupEkle("Parameters", partKokAl()); return parametreGrup_; } Varlik Sahne::geometrikSetKokAl() { if (gsKok_ == VARLIK_YOK) gsKok_ = grupEkle("Geometrical Sets", partKokAl()); return gsKok_; } Varlik Sahne::referansGrupAl() { // CATIA Reference Elements → Geometrical Sets altında "Referans" seti (lazy). if (refGrup_ == VARLIK_YOK) refGrup_ = grupEkle("Referans", geometrikSetKokAl()); return refGrup_; } Varlik Sahne::partBodyAl() { // Aktif body SİLİNMİŞSE sıfırla → canlı bir body'ye geç, yoksa yeni PartBody (dangling'e karşı). if (aktifBody_ != VARLIK_YOK && !dunya_.yasiyor(aktifBody_)) aktifBody_ = VARLIK_YOK; if (aktifBody_ == VARLIK_YOK) for (Varlik b : bodyler_) if (dunya_.yasiyor(b)) { aktifBody_ = b; break; } if (aktifBody_ == VARLIK_YOK) { aktifBody_ = grupEkle("PartBody", partKokAl()); bodyler_.push_back(aktifBody_); // ilk body listeye (çoklu-body kaydı) if (bodySayac_ < 1) bodySayac_ = 1; } return aktifBody_; } Varlik Sahne::yeniBody(std::string ad) { partBodyAl(); // ilk PartBody + Part kökü garanti (kimlik sırası bozulmasın) if (ad.empty()) ad = "Body." + std::to_string(++bodySayac_); const Varlik b = grupEkle(std::move(ad), partKokAl()); bodyler_.push_back(b); aktifBody_ = b; // yeni body AKTİF (In Work Object) — sonraki sketch/feature buraya inWorkSet_ = false; // In Work = body (set altı çizgisi kalkar) return b; } Varlik Sahne::geometrikSetHedefAl() { if (aktifGSet_ != VARLIK_YOK && !dunya_.yasiyor(aktifGSet_)) aktifGSet_ = VARLIK_YOK; if (aktifGSet_ == VARLIK_YOK) { // rotalama düşme yolu — In Work ayıracına DOKUNMA const bool iw = inWorkSet_; yeniGeometrikSet(); inWorkSet_ = iw; } return aktifGSet_; } Varlik Sahne::yeniGeometrikSet(std::string ad) { if (ad.empty()) { // "Geometrical Set N" — mevcut set adlarına göre sıradaki numara int say = 0; for (auto [v, a] : dunya_.depo()) if (a.ad.rfind("Geometrical Set ", 0) == 0) ++say; ad = "Geometrical Set " + std::to_string(say + 1); } // CATIA: her Geometrical Set, Part'ın "Geometrical Sets" konteyneri altında yaşar. aktifGSet_ = grupEkle(std::move(ad), geometrikSetKokAl()); inWorkSet_ = true; // yeni set In Work Object olur (CATIA Insert > Geometrical Set) return aktifGSet_; } u32 Sahne::parametreEkle(std::string ad, f64 deger, std::string birim, Varlik ust) { const Varlik v = dunya_.olustur(); auto pn = std::make_unique(v, std::move(ad), deger, std::move(birim)); pn->senkronAyarla([this] { parametreleriCoz(); }); // ozellikler() düzenleyince yeniden çöz dunya_.ekle(v, std::move(pn)); dunya_.ekle(v, std::make_unique(v)); dunya_.ekle( v, HiyerarsiBilesen{ust == VARLIK_YOK ? parametreGrupAl() : ust}); olaylar_.yayinla(VarlikEklendi{v}, "Parametre"); parametreleriCoz(); // yeni parametreyi depoya al + çöz return v; } void Sahne::parametreleriCoz() { Depo& depo = dunya_.depo(); // 1) Tüm parametre düğümlerinin ifadesini depoya yükle. for (auto [v, kok] : depo) { if (!kok) continue; if (auto* pn = dynamic_cast(kok.get())) paramDepo_.ayarla(pn->ad(), pn->ifade(), pn->birim()); } // 2) Topolojik çöz (döngü/bilinmeyen ref → ilgili parametre hata alır, diğerleri çözülür). paramDepo_.cozumle(); // 3) Çözülen değerleri geri yaz + her parametre için ParametreDegisti yayınla // (bağımlı feature'lar recompute olur). degerAyarla HAM setter → senkron özyinelemez. for (auto [v, kok] : depo) { if (!kok) continue; auto* pn = dynamic_cast(kok.get()); if (!pn) continue; f64 cozulen = 0; if (paramDepo_.deger(pn->ad(), cozulen)) pn->degerAyarla(cozulen); olaylar_.yayinla(ParametreDegisti{v}, "Parametre"); } // Parametre/feature recompute kaynak duzlemleri tasimis olabilir → ofset datumlari tazele. datumRecompute(); } u32 Sahne::parametreVarlik(const std::string& ad) const { for (auto [v, kok] : dunya_.depo()) { if (!kok) continue; if (auto* pn = dynamic_cast(kok.get()); pn && pn->ad() == ad) return v; } return 0; } void Sahne::ustAyarla(u32 kimlik, Varlik ust) { dunya_.ekle(kimlik, HiyerarsiBilesen{ust}); gorunurlukKirli_ = true; // A#4: hiyerarşi değişti → görünürlük cache bayat } bool Sahne::kendiGorunur(u32 v) const { // Kendi gorunurlugu: Dugum varsa oradan, yoksa GorunurBilesen'den (grup dugumu). if (const Dugum* d = dunya_.bul(v)) return d->gorunur; const GorunurBilesen* g = dunya_.bul(v); return g ? g->gorunur : true; } bool Sahne::agacGorunur(u32 kimlik) const { if (!kendiGorunur(kimlik)) return false; // Ust zincirini yuru; herhangi bir ust gizliyse gizli. Dongu korumasi. Varlik cari = kimlik; for (int guard = 0; guard < 4096; ++guard) { const HiyerarsiBilesen* h = dunya_.bul(cari); if (!h || h->ust == VARLIK_YOK) return true; if (!kendiGorunur(h->ust)) return false; cari = h->ust; } return true; // asiri derinlik: guvenli varsayilan } // Etkin gorunurlugu ust-zincir boyunca memoize eder: zincirdeki her dugum, tepede // cozulen degeri (ilk gizli ata → 0, koke kadar gorunur → 1) paylasir. Zincir // paylasimi sayesinde tazele() tum sahneyi O(N) amortize eder (dugum-basi tekrar yuruyus yok). u8 Sahne::gorunurHesapMemo(u32 kimlik) const { gorunurYol_.clear(); u8 taban = 1; // koke ulasilirsa gorunur (asiri derinlik/dongu icin de guvenli varsayilan) Varlik cari = kimlik; for (int guard = 0; guard < 4096; ++guard) { const auto it = gorunurCache_.find(cari); if (it != gorunurCache_.end()) { taban = it->second; break; } // cozulmus ata if (!kendiGorunur(cari)) { gorunurCache_[cari] = 0; taban = 0; break; } // kendisi gizli gorunurYol_.push_back(cari); const HiyerarsiBilesen* h = dunya_.bul(cari); if (!h || h->ust == VARLIK_YOK) { taban = 1; break; } // koke ulasti → gorunur cari = h->ust; } for (u32 v : gorunurYol_) gorunurCache_[v] = taban; // zincir: hepsi tepe degerini alir return taban; } void Sahne::gorunurlukTazele() { // A#4: ağaç görünürlük/hiyerarşi/düğüm-kümesi değişmediyse (pan/rotate/zoom/hover) O(N) // hashmap clear+rebuild ATLA — cache geçerli kalır. Kirlilik olay-tetikli (constructor // abonelikleri + ustAyarla). Miss olursa agacGorunurHizli kendini onarır → güvenli. if (!gorunurlukKirli_) return; gorunurCache_.clear(); auto& dugumler = dunya_.depo().yogun(); gorunurCache_.reserve(dugumler.size() * 2); // dugum + ust-grup girdileri // Her düğüme çözülmüş görünürlüğü yaz (per-Dugum kare-cache) → render/pick döngüleri // agacGorunurHizli hashmap-find yerine d.gorunurBit_ okur. for (const auto& d : dugumler) d.gorunurBit_ = gorunurHesapMemo(d.kimlik) != 0; gorunurlukKirli_ = false; } bool Sahne::agacGorunurHizli(u32 kimlik) const { const auto it = gorunurCache_.find(kimlik); if (it != gorunurCache_.end()) return it->second != 0; return gorunurHesapMemo(kimlik) != 0; // miss → hesapla (tazele cagrilmadiysa da dogru) } u32 Sahne::booleanUygula(DugumTip islem) { const auto govdeler = seciliGovdeler(); if (govdeler.size() != 2) return 0; Dugum* a = bul(govdeler[0]); Dugum* b = bul(govdeler[1]); if (!a || !b || !a->brep || !b->brep) return 0; const geometri::BrepGovde da = geometri::donusumUygula(*a->brep, a->donusum); const geometri::BrepGovde db = geometri::donusumUygula(*b->brep, b->donusum); geometri::BrepGovde sonuc; switch (islem) { case DugumTip::Birlesim: sonuc = geometri::birlesim(da, db); break; case DugumTip::Cikarma: sonuc = geometri::cikarma(da, db); break; case DugumTip::Kesisim: sonuc = geometri::kesisim(da, db); break; default: return 0; } if (!sonuc.doluMu()) { gunluk::uyari("Boolean sonucu bos"); return 0; } const Vec3f renk = a->renk; const u32 silinecekA = a->kimlik, silinecekB = b->kimlik; Dugum& yeni = hamEkle(islem); yeni.renk = renk; yeni.brep = std::make_shared(std::move(sonuc)); yenidenInsa(yeni); const u32 yeniKimlik = yeni.kimlik; sil(silinecekA); sil(silinecekB); sec({yeniKimlik, 0}, false); return yeniKimlik; } // Boolean/offset işlemi sonrası per-yüz renk/alfa (yuzOz) KORU: yeni brep yüzlerini eskiye // GEOMETRİK eşle (merkez+normal+alan) → eski yuzOz'u yeni ordinallere taşı. Topoloji-remap'siz // ops (kabuk/kalınlık/draft/ayna/dizi/lump) için ortak — renk kaybı kök-çözümü [[fikatia-renk-mimarisi]]. static void yuzOzGeometrikKoru(Dugum& d, const std::shared_ptr& eskiBrep, const std::shared_ptr& yeniBrep) { if (d.yuzOz.empty() || !eskiBrep || !yeniBrep) return; const std::vector geo = geometri::yuzGeometrikEsle(*eskiBrep, *yeniBrep); std::unordered_map yeniOz; for (std::size_t j = 0; j < geo.size(); ++j) if (geo[j] > 0) { const auto it = d.yuzOz.find((u32)geo[j]); if (it != d.yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } d.yuzOz = std::move(yeniOz); } bool Sahne::pahYuvarlatGovde(u32 govde, const std::vector& kenarlar, f64 deger, bool yuvarlat) { if (deger <= 1e-9 || kenarlar.empty()) return false; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return false; std::shared_ptr eskiBrep = d->brep; // renk remap için işlem-öncesi geometri::BrepGovde sonuc = yuvarlat ? geometri::yuvarlat(*d->brep, kenarlar, deger) : geometri::pahla(*d->brep, kenarlar, deger); if (!sonuc.doluMu()) { gunluk::uyari("3B pah/yuvarlatma başarısız: {}", d->ad); return false; } auto yeniBrep = std::make_shared(std::move(sonuc)); // Per-yüz renk KORU: fillet/pah yüzeyi eklenir, değişmeyen yüzler geometrik eşleşir → // yuzOz yeni ordinallere taşınır (renk kaybı önlenir; fillet yüzeyi eşleşmezse varsayılan). // Fillet'te değişmeyen yüzler sabit kalır → geometrik eşleme (merkez+normal) güvenilir. if (!d->yuzOz.empty()) { const std::vector h = geometri::yuzGeometrikEsle(*eskiBrep, *yeniBrep); std::unordered_map yeniOz; for (std::size_t j = 0; j < h.size(); ++j) if (h[j] > 0) { const auto it = d->yuzOz.find((u32)h[j]); if (it != d->yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } d->yuzOz = std::move(yeniOz); } d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); // brep değişti → mesh + kenar + ölçüm yeniden kurulur return true; } int Sahne::pahYuvarlatUygula(f64 deger, bool yuvarlat) { if (deger <= 1e-9) return 0; // Seçili kenar ordinallerini gövdeye göre grupla (yalnız Kenar ögeleri). std::unordered_map> govdeKenar; for (const SecimOge& o : secililer_) if (o.alt > 0) govdeKenar[o.govde].push_back(o.alt); if (govdeKenar.empty()) return 0; int degisen = 0; for (auto& [govde, kenarlar] : govdeKenar) if (pahYuvarlatGovde(govde, kenarlar, deger, yuvarlat)) ++degisen; if (degisen) secimTemizle(); // kenar ordinalleri değişti → seçim geçersiz return degisen; } bool Sahne::pahYuvarlatUygulaKenarlar(u32 govde, const std::vector& kenarlar, f64 deger, bool yuvarlat) { if (!pahYuvarlatGovde(govde, kenarlar, deger, yuvarlat)) return false; secimTemizle(); // kenar ordinalleri değişti → mevcut seçim geçersiz (seçimli yolla aynı) return true; } int Sahne::pushPullUygula(u32 govde, u32 yuzOrdinal, f64 mesafe) { if (std::abs(mesafe) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; // BRepFeat_MakePrism: gerçek yüzü (delikleriyle) local feature ile öteler → topoloji // korunur (delik yeniden-inşa YOK), boolean fuse/cut'tan hızlı. std::shared_ptr eskiBrep = d->brep; // remap için işlem-öncesi std::vector remap; // yeni ordinal → eski ordinal (BRepFeat topoloji izleme) geometri::BrepGovde sonuc = geometri::yuzItCek(*d->brep, yuzOrdinal, mesafe, &remap); if (!sonuc.doluMu()) { gunluk::uyari("Push/Pull başarısız (geçersiz sonuç): {}", d->ad); return 0; } auto yeniBrep = std::make_shared(std::move(sonuc)); // Öğe-özellik (per-yüz renk/alfa) KORU: yüz ordinalleri değişti → topoloji remap ile eski // yuzOz'u yeni ordinallere taşı (renk kaybı kök-çözümü; brep renk taşımaz, yuzOz taşır). // Topolojide eşleşmeyen (0) yüzler için geometrik eşleme fallback (merkez+normal). if (!d->yuzOz.empty()) { std::vector geo; // geometrik fallback (lazy: yalnız gerekince hesapla) std::unordered_map yeniOz; for (std::size_t j = 0; j < remap.size(); ++j) { int eskiOrd = remap[j]; if (eskiOrd <= 0) { // topoloji eşleşmedi → geometrik dene if (geo.empty()) geo = geometri::yuzGeometrikEsle(*eskiBrep, *yeniBrep); if (j < geo.size()) eskiOrd = geo[j]; } if (eskiOrd > 0) { const auto it = d->yuzOz.find((u32)eskiOrd); if (it != d->yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } } d->yuzOz = std::move(yeniOz); } d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); // brep değişti → mesh + kenar + ölçüm yeniden kurulur secimTemizle(); // yüz/kenar ordinalleri değişti → seçim geçersiz return 1; } int Sahne::sketchYuzeKabartUygula(u32 sketchVarlik, u32 hedefGovde, u32 hedefYuzOrd, f64 mesafe, bool fuseMi) { if (std::abs(mesafe) < 1e-6) return 0; Dugum* d = bul(hedefGovde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; // Sketch → 2D profil (SketchNesnesi.eskiz üzerinden ham u,v). NesneKok* nk = dunya_.bul(sketchVarlik); auto* sn = (nk && *nk) ? dynamic_cast(nk->get()) : nullptr; if (!sn) { gunluk::uyari("Emboss: sketch bulunamadı"); return 0; } std::vector konturlar; if (!sketchKontur2d(sn->eskiz(), konturlar) || konturlar.empty()) { gunluk::uyari("Emboss: sketch kapalı profil çıkarılamadı"); return 0; } // Çok-kontur/delikli profil (konturlar[0]=dış, 1..=delik) tümü emboss'a geçer. std::shared_ptr eskiBrep = d->brep; // renk remap için işlem-öncesi geometri::BrepGovde sonuc = geometri::yuzeKabart(*d->brep, hedefYuzOrd, konturlar, mesafe, fuseMi); if (!sonuc.doluMu()) { gunluk::uyari("Emboss başarısız (geçersiz sonuç): {}", d->ad); return 0; } auto yeniBrep = std::make_shared(std::move(sonuc)); // Renk koru: yuzeKabart Boolean → topoloji remap YOK → geometrik eşleme (merkez+normal) // fallback ile eski yuzOz'u yeni ordinallere taşı (yeni emboss yüzleri eşleşmez → renksiz). if (!d->yuzOz.empty()) { const std::vector geo = geometri::yuzGeometrikEsle(*eskiBrep, *yeniBrep); // Yeni emboss yüzleri (boss/pocket duvar+tepe) eski hiçbir yüze eşleşmez → HEDEF yüzün // rengini miras al (renksiz/gri kalmasın; aynı malzeme → doğal). Hedef renksizse atlanır. const auto hedefRenkIt = d->yuzOz.find(hedefYuzOrd); const bool hedefRenkli = hedefRenkIt != d->yuzOz.end(); std::unordered_map yeniOz; for (std::size_t j = 0; j < geo.size(); ++j) { if (geo[j] > 0) { const auto it = d->yuzOz.find((u32)geo[j]); if (it != d->yuzOz.end()) yeniOz[(u32)(j + 1)] = it->second; } else if (hedefRenkli) { yeniOz[(u32)(j + 1)] = hedefRenkIt->second; // yeni emboss yüzü → hedef rengi } } d->yuzOz = std::move(yeniOz); } d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::sketchKatiUygula(u32 sketchVarlik, u32 hedefGovde, f64 mesafe, bool kesMi) { if (std::abs(mesafe) < 1e-6) return 0; NesneKok* nk = dunya_.bul(sketchVarlik); auto* sn = (nk && *nk) ? dynamic_cast(nk->get()) : nullptr; if (!sn) { gunluk::uyari("Sketch Katı: sketch bulunamadı"); return 0; } std::vector k2d; if (!sketchKontur2d(sn->eskiz(), k2d) || k2d.empty()) { gunluk::uyari("Sketch Katı: kapalı profil çıkarılamadı"); return 0; } // Sketch düzlem bazı (dünya): orijin + u/v/normal — 2D profili gerçek-geometriye taşır. Dugum* dz = bul(sn->duzlem()); if (!dz) { gunluk::uyari("Sketch Katı: düzlem yok"); return 0; } const Vec3d orijin = dz->donusum.konum; const Vec3d u = dz->donusum.yonelim.dondur({1, 0, 0}); const Vec3d v = dz->donusum.yonelim.dondur({0, 1, 0}); const Vec3d normal = dz->donusum.yonelim.dondur({0, 0, 1}); std::vector konturlar; konturlar.reserve(k2d.size()); for (const auto& kk : k2d) konturlar.push_back(geometri::profil2dDunyaya(kk, orijin, u, v)); // Pad: düzlemden dışa (+normal) mesafe. Cep: düzlemi az aşıp içe (−normal) mesafe → koplanar // kesim yüzü sorunu önlenir (prizma taban yüzü gövde yüzüyle çakışmaz). Kaynak gövde yoksa // yeni gövde → tek yön çift-simetriksiz Pad (kesMi anlamsız → düz prizma). geometri::BrepGovde prizma = (kesMi && hedefGovde) ? geometri::prizmaMultiEgri(konturlar, normal, 0.05, mesafe) : geometri::prizmaMultiEgri(konturlar, normal, mesafe, 0.0); if (!prizma.gecerliMi()) { gunluk::uyari("Sketch Katı: prizma kurulamadı"); return 0; } // ---- Kaynak gövde YOK (referans düzlemde sketch) → sıfırdan YENİ katı gövde ---- if (hedefGovde == 0) { Dugum& yeni = hamEkle(DugumTip::Ithal); yeni.ad = "Pad"; yeni.renk = {0.72f, 0.74f, 0.80f}; // varsayılan katı rengi yeni.brep = std::make_shared(std::move(prizma)); // move-only dunya_.ekle(yeni.kimlik, HiyerarsiBilesen{partBodyAl()}); yenidenInsa(yeni); const u32 yk = yeni.kimlik; secimTemizle(); sec({yk, 0}, false); return 1; } Dugum* d = bul(hedefGovde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; std::shared_ptr eskiBrep = d->brep; // renk remap için işlem-öncesi geometri::BrepGovde sonuc = kesMi ? geometri::cikarma(*d->brep, prizma) : geometri::birlesim(*d->brep, prizma); if (!sonuc.doluMu()) { gunluk::uyari("Sketch Katı: Boolean başarısız: {}", d->ad); return 0; } const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } Sahne::SketchItCekBilgi Sahne::sketchItCekBilgi(u32 sketchVarlik) const { SketchItCekBilgi r; NesneKok* nk = dunya_.bul(sketchVarlik); auto* sn = (nk && *nk) ? dynamic_cast(nk->get()) : nullptr; if (!sn) return r; std::vector k2d; if (!sketchKontur2d(sn->eskiz(), k2d) || k2d.empty()) return r; const Dugum* dz = bul(sn->duzlem()); if (!dz) return r; const Vec3d orijin = dz->donusum.konum; const Vec3d u = dz->donusum.yonelim.dondur({1, 0, 0}); const Vec3d v = dz->donusum.yonelim.dondur({0, 1, 0}); r.normal = dz->donusum.yonelim.dondur({0, 0, 1}); const auto W = [&](f64 x, f64 y) { return orijin + u * x + v * y; }; // düzlem→dünya Vec3d toplam{0, 0, 0}; std::size_t say = 0; using T2 = geometri::ProfilEgri2d::Tip; for (const auto& kontur : k2d) { std::vector poli; const auto koy = [&](f64 x, f64 y) { const Vec3d w = W(x, y); poli.push_back({(f32)w.x, (f32)w.y, (f32)w.z}); toplam += w; ++say; }; for (const geometri::ProfilEgri2d& e : kontur) { if (e.tip == T2::Cizgi) { koy(e.p0.x, e.p0.y); // uç-uca zincir: her kenar başlangıcı (kapanış otomatik) } else { // Cember/Yay/Elips → örnekle (endpoint hariç; zincir devamı ekler) const bool tam = (e.tip == T2::Cember); const f64 a0 = tam ? 0.0 : e.aci0; f64 a1 = tam ? 2.0 * 3.14159265358979 : e.aci1; if (a1 <= a0) a1 += 2.0 * 3.14159265358979; const int N = tam ? 32 : 16; const f64 ra = e.yaricap, rb = (e.kucukYaricap > 1e-9 ? e.kucukYaricap : e.yaricap); const f64 cs = std::cos(e.rot), sr = std::sin(e.rot); for (int i = 0; i < N; ++i) { const f64 t = a0 + (a1 - a0) * (f64)i / (f64)N; const f64 lx = ra * std::cos(t), ly = rb * std::sin(t); // elips yerel eksenler koy(e.merkez.x + lx * cs - ly * sr, e.merkez.y + lx * sr + ly * cs); } } } if (poli.size() >= 2) r.poliler.push_back(std::move(poli)); } if (say == 0 || r.poliler.empty()) return r; r.merkez = toplam * (1.0 / (f64)say); r.kaynakGovde = sn->kaynakGovde(); r.gecerli = true; return r; } int Sahne::yuzTasiUygula(u32 govde, u32 yuzOrdinal, Vec3d vec) { Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::yuzTasi(*d->brep, yuzOrdinal, vec); if (!sonuc.doluMu()) { gunluk::uyari("Yüz taşı başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::kenarTasiUygula(u32 govde, u32 kenarOrdinal, Vec3d vec) { Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::katiKenarTasi(*d->brep, kenarOrdinal, vec); if (!sonuc.doluMu()) { gunluk::uyari("Kenar taşı başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::yuzKaldirUygula(u32 govde, const std::vector& yuzler) { if (yuzler.empty()) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::yuzKaldir(*d->brep, yuzler); if (!sonuc.doluMu()) { gunluk::uyari("Yüz kaldır başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::yuzuSilUygula(u32 govde, const std::vector& yuzler) { if (yuzler.empty()) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::yuzuSil(*d->brep, yuzler); if (!sonuc.gecerliMi()) { // açık shell → doluMu() değil gecerliMi() (solid şartı yok) gunluk::uyari("Yüz sil başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } u32 Sahne::yuzKopyalaUygula(u32 govde, const std::vector& yuzler) { if (yuzler.empty()) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde kopya = geometri::yuzKopyala(*d->brep, yuzler); if (!kopya.gecerliMi()) { gunluk::uyari("Yüz kopyala başarısız: {}", d->ad); return 0; } // Yeni GSD Yüzey düğümü (kaynak katı DEĞİŞMEZ) — üst grup kaynakla aynı (yan yana). const HiyerarsiBilesen* h = dunya_.bul(govde); const u32 yeni = gsdDugumEkle(DugumTip::Yuzey, d->ad + " Yüzey", h ? h->ust : VARLIK_YOK, d->renk); if (!gsdDugumGuncelle(yeni, std::make_shared(std::move(kopya)))) { sil(yeni); // mesh kurulamadı → yarım düğüm bırakma return 0; } return yeni; } u32 Sahne::govdeKopyalaUygula(u32 govde) { Dugum* src = bul(govde); if (!src || !src->brep || !src->brep->gecerliMi()) return 0; const HiyerarsiBilesen* h = dunya_.bul(govde); const Varlik ust = h ? h->ust : VARLIK_YOK; if (src->tip == DugumTip::Yuzey || src->tip == DugumTip::Tel) { const u32 yeni = gsdDugumEkle(src->tip, src->ad + " Kopya", ust, src->renk); if (!gsdDugumGuncelle(yeni, src->brep)) { sil(yeni); return 0; } // brep PAYLAŞILIR if (Dugum* d = bul(yeni)) d->donusum = src->donusum; return yeni; } // Katı/Ithal: hamEkle + paylaşılan brep + kaynak transform (yuzeyKatilastirUygula kalıbı). Dugum& yeni = hamEkle(DugumTip::Ithal); yeni.renk = src->renk; yeni.ad = src->ad + " Kopya"; yeni.brep = src->brep; // shared_ptr paylaş — op'lar pointer değiştirir, mutasyon yok yeni.donusum = src->donusum; if (ust != VARLIK_YOK) dunya_.ekle(yeni.kimlik, HiyerarsiBilesen{ust}); yenidenInsa(yeni); return yeni.kimlik; } u32 Sahne::yuzeyKatilastirUygula(u32 yuzeyGovde, f64 kalinlik) { Dugum* src = bul(yuzeyGovde); if (!src || !src->brep || !src->brep->gecerliMi()) return 0; geometri::BrepGovde kati = geometri::yuzeyKatilastir(*src->brep, kalinlik); if (!kati.gecerliMi()) { gunluk::uyari("Yüzey kalınlaştır başarısız: {}", src->ad); return 0; } // Yeni KATI (Ithal) düğümü — kaynak yüzey DEĞİŞMEZ (booleanUygula sonuç kalıbı). const HiyerarsiBilesen* h = dunya_.bul(yuzeyGovde); Dugum& yeni = hamEkle(DugumTip::Ithal); yeni.renk = src->renk; yeni.ad = src->ad + " Katı"; yeni.brep = std::make_shared(std::move(kati)); if (h) dunya_.ekle(yeni.kimlik, HiyerarsiBilesen{h->ust}); yenidenInsa(yeni); const u32 yeniKimlik = yeni.kimlik; sec({yeniKimlik, 0}, false); return yeniKimlik; } u32 Sahne::yuzeyOfsetUygula(u32 yuzeyGovde, f64 mesafe) { Dugum* src = bul(yuzeyGovde); if (!src || !src->brep || !src->brep->gecerliMi()) return 0; geometri::BrepGovde ofs = geometri::yuzeyOfset(*src->brep, mesafe); if (!ofs.gecerliMi()) { gunluk::uyari("Yüzey offset başarısız: {}", src->ad); return 0; } const HiyerarsiBilesen* h = dunya_.bul(yuzeyGovde); const u32 yeni = gsdDugumEkle(DugumTip::Yuzey, src->ad + " Offset", h ? h->ust : VARLIK_YOK, src->renk); if (!gsdDugumGuncelle(yeni, std::make_shared(std::move(ofs)))) { sil(yeni); return 0; } return yeni; } u32 Sahne::kabuktanKatiUygula(u32 yuzeyGovde) { Dugum* src = bul(yuzeyGovde); if (!src || !src->brep || !src->brep->gecerliMi()) return 0; geometri::BrepGovde kati = geometri::kabuktanKati(*src->brep); if (!kati.gecerliMi()) { gunluk::uyari("Kabuktan katı başarısız: {}", src->ad); return 0; } const HiyerarsiBilesen* h = dunya_.bul(yuzeyGovde); Dugum& yeni = hamEkle(DugumTip::Ithal); yeni.renk = src->renk; yeni.ad = src->ad + " Katı"; yeni.brep = std::make_shared(std::move(kati)); if (h) dunya_.ekle(yeni.kimlik, HiyerarsiBilesen{h->ust}); yenidenInsa(yeni); const u32 yeniKimlik = yeni.kimlik; sec({yeniKimlik, 0}, false); return yeniKimlik; } int Sahne::yuzeyIleBolUygula(u32 katiGovde, u32 yuzeyGovde) { Dugum* kati = bul(katiGovde); Dugum* yuz = bul(yuzeyGovde); if (!kati || !yuz || !kati->brep || !kati->brep->gecerliMi() || !yuz->brep || !yuz->brep->gecerliMi()) return 0; std::vector parcalar = geometri::yuzeyIleBol(*kati->brep, *yuz->brep); if (parcalar.size() < 2) { gunluk::uyari("Yüzey ile böl: bölünemedi (yüzey katıyı kesmiyor?)"); return 0; } const Vec3f renk = kati->renk; const HiyerarsiBilesen* h = dunya_.bul(katiGovde); const Varlik ust = h ? h->ust : VARLIK_YOK; const std::string tabanAd = kati->ad; int idx = 0; for (auto& p : parcalar) { Dugum& yeni = hamEkle(DugumTip::Ithal); yeni.renk = renk; yeni.ad = tabanAd + " Parça " + std::to_string(++idx); yeni.brep = std::make_shared(std::move(p)); if (ust != VARLIK_YOK) dunya_.ekle(yeni.kimlik, HiyerarsiBilesen{ust}); yenidenInsa(yeni); } // Orijinali GİZLE (silme → veri kaybı yok, geri alınabilir). Yeniden bul (hamEkle vektörü // reallocate etmiş olabilir → 'kati' pointer'ı geçersizleşebilir). if (Dugum* k2 = bul(katiGovde)) k2->gorunur = false; gorunurlukKirli_ = true; secimTemizle(); return idx; } int Sahne::kabukUygula(u32 govde, const std::vector& yuzler, f64 kalinlik) { if (yuzler.empty() || std::abs(kalinlik) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::kabuk(*d->brep, yuzler, kalinlik); if (!sonuc.doluMu()) { gunluk::uyari("Kabuk başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); // brep değişti → mesh + kenar + ölçüm yeniden kurulur secimTemizle(); // yüz/kenar ordinalleri değişti → seçim geçersiz return 1; } int Sahne::kalinlastirUygula(u32 govde, f64 mesafe) { if (std::abs(mesafe) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::kalinlastir(*d->brep, mesafe); if (!sonuc.doluMu()) { gunluk::uyari("Kalınlık başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::aynalaUygula(u32 govde, u32 yuzOrdinal) { Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::YuzBilgi yb; if (!d->brep->yuzBilgi(yuzOrdinal, yb) || !yb.duzlemMi) { gunluk::uyari("Ayna: ayna düzlemi için düz yüz seçin"); return 0; } geometri::BrepGovde aynaKopya = geometri::aynala(*d->brep, yb.merkez, yb.normal); if (!aynaKopya.gecerliMi()) { gunluk::uyari("Ayna kopya başarısız: {}", d->ad); return 0; } geometri::BrepGovde sonuc = geometri::birlesim(*d->brep, aynaKopya); if (!sonuc.doluMu()) { gunluk::uyari("Ayna birleşim başarısız: {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::diziDaireselUygula(u32 govde, u32 yuzOrdinal, u32 adet, f64 toplamAci) { if (adet < 2) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::YuzBilgi yb; if (!d->brep->yuzBilgi(yuzOrdinal, yb) || !yb.silindirMi) { gunluk::uyari("Dizi: eksen için silindirik/dairesel yüz seçin"); return 0; } geometri::BrepGovde sonuc = geometri::diziDairesel(*d->brep, yb.eksenNokta, yb.eksen, toplamAci, adet); if (!sonuc.doluMu()) { gunluk::uyari("Dizi başarısız: {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::diziLineerUygula(u32 govde, u32 yuzOrdinal, u32 adet, f64 mesafe) { if (adet < 2 || std::abs(mesafe) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::YuzBilgi yb; if (!d->brep->yuzBilgi(yuzOrdinal, yb) || !yb.duzlemMi) { gunluk::uyari("Doğrusal dizi: yön için düz yüz seçin"); return 0; } std::vector ofsetler; ofsetler.reserve(adet); for (u32 i = 0; i < adet; ++i) ofsetler.push_back(yb.normal * (mesafe * (f64)i)); // i=0 orijin geometri::BrepGovde sonuc = geometri::diziKonum(*d->brep, ofsetler); if (!sonuc.doluMu()) { gunluk::uyari("Doğrusal dizi başarısız: {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::lumpUygula(u32 govde, const std::vector& yuzler, bool tut) { if (yuzler.empty()) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::BrepGovde sonuc = geometri::lumpAyikla(*d->brep, yuzler, tut); if (!sonuc.doluMu()) { gunluk::uyari("Parça ayır başarısız (tek parça olabilir): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::draftUygula(u32 govde, const std::vector& yuzler, f64 aciDerece, Vec3d cekmeYon, Vec3d notralNokta, Vec3d notralNormal) { if (yuzler.empty() || std::abs(aciDerece) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; // notralNormal verilmişse (uzunluk>0) o düzlem nötr çizgi olur (ör. çıkıntı taban düzlemi → // duvar tabandan döner); aksi halde yuzDraft bbox-merkez varsayılanına düşer. geometri::BrepGovde sonuc = geometri::yuzDraft(*d->brep, yuzler, aciDerece, cekmeYon, notralNokta, notralNormal, false); if (!sonuc.doluMu()) { gunluk::uyari("Draft başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::yuzDondurUygula(u32 govde, u32 yuzOrdinal, f64 aciDerece, Vec3d eksenNokta, Vec3d eksenYon, Vec3d tabanNokta, Vec3d tabanNormal) { if (std::abs(aciDerece) < 1e-6) return 0; Dugum* d = bul(govde); if (!d || !d->brep || !d->brep->gecerliMi()) return 0; geometri::YuzBilgi yb; if (!d->brep->yuzBilgi(yuzOrdinal, yb)) return 0; // seed = üst yüz merkezi (çıkıntı ayırma) geometri::BrepGovde sonuc = geometri::yuzDondur(*d->brep, tabanNokta, tabanNormal, eksenNokta, eksenYon, aciDerece, yb.merkez); if (!sonuc.doluMu()) { gunluk::uyari("Yüz döndür başarısız (geçersiz sonuç): {}", d->ad); return 0; } const auto eskiBrep = d->brep; // renk remap için (işlem-öncesi) const auto yeniBrep = std::make_shared(std::move(sonuc)); yuzOzGeometrikKoru(*d, eskiBrep, yeniBrep); // per-yüz renk KORU (geometrik eşleme) d->brep = yeniBrep; yenidenInsa(*d, eskiBrep.get()); secimTemizle(); return 1; } int Sahne::stepIceAktar(const std::string& yol) { auto govdeler = geometri::stepYukle(yol); int eklenen = 0; for (auto& g : govdeler) { if (!g.govde.gecerliMi()) continue; Dugum& d = hamEkle(DugumTip::Ithal); d.renk = g.renkVar ? g.renk : Vec3f{0.62f, 0.66f, 0.72f}; d.brep = std::make_shared(std::move(g.govde)); yenidenInsa(d); ++eklenen; } return eklenen; } int Sahne::vrmlIceAktar(const std::string& yol, bool mmCevir) { // Feature edge (IndexedLineSet) + per-yüz renk (diffuseColor DEF/USE) + yüz→Shape // eşlemesi topla → temiz kafes + renkli yüzey + BREP-yüz gibi seçim (web paritesi). KenarVeri kenarlar; std::vector yuzRenkleri; std::vector yuzShape; // her üçgen-yüzün ait olduğu Shape (1-tabanlı) geometri::BrepGovde govde = geometri::vrmlOku(yol, &kenarlar, &yuzRenkleri, &yuzShape, mmCevir); if (!govde.gecerliMi()) return 0; if (!yuzRenkleri.empty()) govde.yuzRenkleriAta(std::move(yuzRenkleri)); // per-yüz renk MeshVeri mesh = govde.ucgenle(Vec3f{0.62f, 0.66f, 0.72f}); // yüzey (renkliyse yüz-renkli) // Seçim ordinali remap: üçgen-yüz ordinali → Shape ordinali. Böylece bir yüze tıklayınca // TÜM Shape (CATIA yüzü) seçilir/vurgulanır, tek üçgen değil (web `grup` attribute eşi). if (!yuzShape.empty()) { u32 maksShape = 0; for (auto& v : mesh.vertexler) if (v.altKimlik >= 1 && v.altKimlik <= yuzShape.size()) { v.altKimlik = yuzShape[v.altKimlik - 1]; if (v.altKimlik > maksShape) maksShape = v.altKimlik; } mesh.yuzSayi = maksShape; } geometri::IthalGovde ig; ig.govde = std::move(govde); // Hazır yol: verilen mesh + FEATURE kenarları kullanılır (brep->kenarMesh üçgen // kenarı çıkarmaz). kenar boşsa (line set yok) kafessiz sadece yüzey. ithalEkleHazir(std::move(ig), std::move(mesh), std::move(kenarlar), {}, VARLIK_YOK); return 1; } bool Sahne::disaAktar(const std::string& yol, bool yalnizSecili) const { // Dünya-konumuna taşınmış gövdeleri topla (STEP export kalıbı). std::vector dunyaGovdeler; for (const auto& d : dunya_.depo().yogun()) { if (yalnizSecili && !govdeSeciliMi(d.kimlik)) continue; if (!d.brep || !d.brep->gecerliMi()) continue; dunyaGovdeler.push_back(geometri::donusumUygula(*d.brep, d.donusum)); } std::vector liste; for (const auto& g : dunyaGovdeler) liste.push_back(&g); if (liste.empty()) return false; // Uzantıya göre format seç (STL/OBJ/IGES; varsayılan STEP). std::string uz; if (const size_t n = yol.find_last_of('.'); n != std::string::npos) { uz = yol.substr(n + 1); for (char& c : uz) c = (char)std::tolower((unsigned char)c); } if (uz == "stl") return geometri::stlKaydet(liste, yol); if (uz == "obj") return geometri::objKaydet(liste, yol); if (uz == "iges" || uz == "igs") return geometri::igesKaydet(liste, yol); return geometri::stepKaydet(liste, yol); // .step/.stp veya bilinmeyen → STEP } bool Sahne::stepDisaAktar(const std::string& yol, bool yalnizSecili) const { return disaAktar(yol, yalnizSecili); // geriye dönük: STEP yolu → disaAktar dispatch } void Sahne::sil(u32 kimlik) { bvhIsIptal(kimlik); // cpuKonum/cpuIndeks serbest kalmadan süren BVH işini durdur // Kaba-mesh işi/önbelleği: süren iş çöpe (worker yalnız shared_ptr okur — bloklamaz), // önbellek düşer (silinen düğüm için sızıntı olmasın). if (auto it = kabaIsler_.find(kimlik); it != kabaIsler_.end()) { kabaCop_.push_back(std::move(it->second)); kabaIsler_.erase(it); } kabaOnb_.erase(kimlik); // hacim kuyruğu: başlamamış istekler düşer; koşan [0] kalır (shared_ptr brep'i canlı // tutar, sonuç pompada "düğüm yok" diye zaten atılır — bekleme yok). std::erase_if(hacimKuyruk_, [&](const HacimIs& h) { return h.kimlik == kimlik && !h.sonuc.valid(); }); // Dugum tamponlari GpuTampon yikiminda emekli listesine gider (ertelenmis yok // etme) → device-idle beklemeye gerek yok (dokum scrubber'da buyuk stall'di). dunya_.yokEt(kimlik); std::erase_if(secililer_, [&](const SecimOge& o) { return o.govde == kimlik; }); if (vurgu.govde == kimlik) vurgu = {}; olaylar_.yayinla(VarlikSilindi{kimlik}); } Dugum* Sahne::bul(u32 kimlik) { return dunya_.bul(kimlik); } const Dugum* Sahne::bul(u32 kimlik) const { return dunya_.bul(kimlik); } void Sahne::kipAyarla(SecimKip k) { if (kip_ == k) return; kip_ = k; secililer_.clear(); vurgu = {}; olaylar_.yayinla(KipDegisti{(u32)k}); } void Sahne::sec(SecimOge oge, bool coklu) { secimAgactan_ = false; // varsayılan: 3D-pick/programatik (ağaç paneli sonrasında override eder) if (!coklu) secililer_.clear(); if (oge.govde == 0) { if (!coklu) olaylar_.yayinla(SecimDegisti{}); // temizleme de secim degisimidir return; } const auto konum = std::find(secililer_.begin(), secililer_.end(), oge); if (konum != secililer_.end()) { if (coklu) secililer_.erase(konum); } else { secililer_.push_back(oge); } olaylar_.yayinla(SecimDegisti{}); } void Sahne::secTopluEkle(const std::vector& govdeler) { secimAgactan_ = false; // varsayılan; ağaç paneli çağrısından SONRA true'ya override eder std::unordered_set mevcut; mevcut.reserve(secililer_.size() + govdeler.size()); for (const auto& o : secililer_) if (o.alt == 0) mevcut.insert(o.govde); // yalnız gövde-düzeyi (alt=0) seçimlerle çakışma denetimi bool degisti = false; for (u32 g : govdeler) { if (g == 0 || !mevcut.insert(g).second) continue; // 0 veya zaten seçili → atla (O(1)) secililer_.push_back({g, 0}); degisti = true; } if (degisti) olaylar_.yayinla(SecimDegisti{}); // TEK olay (N değil) → aboneler bir kez koşar } void Sahne::secimAyarla(std::vector yeni) { if (yeni == secililer_) return; secimAgactan_ = false; secililer_ = std::move(yeni); olaylar_.yayinla(SecimDegisti{}); } void Sahne::secimCikar(SecimOge oge) { secimAgactan_ = false; const auto konum = std::find(secililer_.begin(), secililer_.end(), oge); if (konum == secililer_.end()) return; secililer_.erase(konum); olaylar_.yayinla(SecimDegisti{}); } bool Sahne::seciliMi(SecimOge oge) const { return std::find(secililer_.begin(), secililer_.end(), oge) != secililer_.end(); } bool Sahne::govdeSeciliMi(u32 govde) const { for (const auto& o : secililer_) if (o.govde == govde) return true; return false; } void Sahne::kirpDuzlemleriAyarla(const std::vector& d) { // Her kare çağrılır → değişmediyse dokunma (tahsis/kopya yok). if (kirpDuzlemler_.size() == d.size()) { bool ayni = true; for (size_t i = 0; i < d.size() && ayni; ++i) { const auto& a = kirpDuzlemler_[i]; const auto& b = d[i]; ayni = a.d == b.d && a.normal.x == b.normal.x && a.normal.y == b.normal.y && a.normal.z == b.normal.z; } if (ayni) return; } kirpDuzlemler_ = d; } bool Sahne::isinVur(Vec3f orij, Vec3f yon, IsinVurus& sonuc, bool* bvhEksik) const { bool bulundu = false; sonuc.t = 3.0e30f; sonuc.alt = 0; for (const auto& d : dunya_.depo().yogun()) { if (!agacGorunurHizli(d.kimlik) || d.cpuIndeks.empty()) continue; // Işını nesne yerel uzayına taşı (ucgenler yerelde) const Donusumd ters = d.donusum.ters(); const Vec3d o0 = ters.noktaUygula({(f64)orij.x, (f64)orij.y, (f64)orij.z}); const Vec3d o1 = ters.noktaUygula( {(f64)(orij.x + yon.x), (f64)(orij.y + yon.y), (f64)(orij.z + yon.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(); // Işın-AABB slab testi (yerel uzay). Geçersiz kutu → kes(il)medi say (güvenli). const auto kutuKes = [&](const Aabbf& kutu) -> bool { if (!kutu.gecerliMi()) return true; f32 tGir = -3.0e30f, tCik = 3.0e30f; for (int ek = 0; ek < 3; ++ek) { const f32 o = (&lo.x)[ek], yn = (&ld.x)[ek]; const f32 mn = (&kutu.asgari.x)[ek], mx = (&kutu.azami.x)[ek]; if (std::abs(yn) < 1e-9f) { if (o < mn || o > mx) return false; // eksene paralel, kutu dışı } else { const f32 inv = 1.0f / yn; f32 t1 = (mn - o) * inv, t2 = (mx - o) * inv; if (t1 > t2) std::swap(t1, t2); tGir = std::max(tGir, t1); tCik = std::min(tCik, t2); if (tGir > tCik) return false; } } return tCik >= 0.0f; }; if (!kutuKes(d.yerelSinir)) continue; // düğüm kutusu kesilmiyor → atla // Tek üçgen testi (Möller–Trumbore; t = i*3 tabanlı üçgen numarası). konum/indeks/alt // PARAMETRE: GÜNCEL mesh (d.cpuKonum/İndeks/Alt) ya da BVH henüz arka planda // kurulurken kullanılan ESKİ anlık görüntü (d.cpuKonumEski/İndeksEski/AltEski) ile // çağrılabilir — ikisi de kendi içinde tutarlı (aynı üretimden) üçlü. const auto ucgenTest = [&](const std::vector& konum, const std::vector& indeks, const std::vector& alt, size_t i) { const Vec3f& a = konum[indeks[i]]; const Vec3f& b = konum[indeks[i + 1]]; const Vec3f& c = konum[indeks[i + 2]]; const Vec3f e1 = b - a, e2 = c - a; const Vec3f p = ld.capraz(e2); const f32 det = e1.nokta(p); if (std::abs(det) < 1e-8f) return; const f32 invDet = 1.0f / det; const Vec3f tv = lo - a; const f32 u = tv.nokta(p) * invDet; if (u < 0.0f || u > 1.0f) return; const Vec3f q = tv.capraz(e1); const f32 v = ld.nokta(q) * invDet; if (v < 0.0f || u + v > 1.0f) return; const f32 t = e2.nokta(q) * invDet; if (t < 1e-4f) return; // Yerel vuruş → dünya const Vec3f yerelHit = lo + ld * t; const Vec3d dh = d.donusum.noktaUygula( {(f64)yerelHit.x, (f64)yerelHit.y, (f64)yerelHit.z}); const Vec3f dunyaHit{(f32)dh.x, (f32)dh.y, (f32)dh.z}; // Kesitle kırpılan (ekranda GÖRÜNMEYEN) bölge seçilemez; ışın devam eder → // arkadaki gerçek (kesit yüzeyi/iç) vuruş kazanır. if (!kirpDuzlemler_.empty() && kirpildiMi(dunyaHit)) return; const f32 dunyaT = (dunyaHit - orij).nokta(yon); if (dunyaT < sonuc.t) { // Yüz normali (yerel) → dünya (donusum yalnız yönelim) Vec3f yn = e1.capraz(e2).birim(); // Kameraya bakacak yönde if (yn.nokta(ld) > 0) yn = yn * -1.0f; const Vec3d wn = d.donusum.yonelim.dondur({(f64)yn.x, (f64)yn.y, (f64)yn.z}); sonuc.t = dunyaT; sonuc.govde = d.kimlik; // Yüz ordinali (altKimlik) = vuran üçgenin ilk verteksinin alt'ı → hover/seçim // CPU BVH ile GPU render'sız (milyon üçgende O(log n)). alt yoksa 0. sonuc.alt = (alt.size() == konum.size() && indeks[i] < alt.size()) ? alt[indeks[i]] : 0; sonuc.nokta = dunyaHit; sonuc.normal = Vec3f{(f32)wn.x, (f32)wn.y, (f32)wn.z}.birim(); bulundu = true; } }; // BVH: küçük mesh'te ilk kullanımda senkron (eski davranış, birkaç ms); // büyük mesh'te ARKA-PLAN kurucudan devral. Hazır değilse: gövdeyi TAMAMEN // ATLAMAK yerine (eski davranış → tıklama arkadaki nesneye geçer ya da hiçbir // şey seçilmez/highlight olmaz — büyük STEP'lerde sık görülen sorun) bir önceki // BVH + ONUNLA EŞLEŞEN konum/indeks/alt anlık görüntüsüyle (isinBvhEski) pick'e // devam et: birkaç karelik BAYAT ama DOĞRU sonuç, yanlış-öne-geçişten iyidir. // bvhEksik yine true kalır (çağıran GPU pick'i destekleyici koşturabilir) ama // bu döngüde artık düğüm atlanmıyor. Yedek de yoksa (ilk kurulum) eski davranış. bool eskiyeDus = false; if (!d.isinBvh) { if (!bvhArkaplan() || d.cpuIndeks.size() / 3 <= kBvhSenkronUcgen) { d.isinBvh = isinBvhKur(d.cpuKonum, d.cpuIndeks); } else { bool suruyor = false; if (auto hazir = bvhKurucu().al(d.kimlik, suruyor)) { d.isinBvh = hazir; d.isinBvhEski.reset(); // yeni BVH devralındı → bayat yedeğe artık gerek yok d.cpuKonumEski.clear(); d.cpuIndeksEski.clear(); d.cpuAltEski.clear(); } else { if (!suruyor) bvhKurucu().baslat(d.kimlik, d.cpuKonum.data(), d.cpuKonum.size(), d.cpuIndeks.data(), d.cpuIndeks.size()); if (bvhEksik) *bvhEksik = true; if (!d.isinBvhEski) continue; // bayat yedek de yok → önceki davranış (atla) eskiyeDus = true; } } } if (eskiyeDus) { u32 yigin[64]; int sp = 0; yigin[sp++] = 0; while (sp > 0) { const IsinBvh::Dal& dal = d.isinBvhEski->dallar[yigin[--sp]]; if (!kutuKes(dal.kutu)) continue; if (dal.yaprak) { for (u32 u2 = 0; u2 < dal.b; ++u2) ucgenTest(d.cpuKonumEski, d.cpuIndeksEski, d.cpuAltEski, (size_t)d.isinBvhEski->ucgen[dal.a + u2] * 3); } else if (sp + 2 <= (int)(sizeof(yigin) / sizeof(yigin[0]))) { yigin[sp++] = dal.a; yigin[sp++] = dal.b; } } } else if (d.isinBvh) { u32 yigin[64]; int sp = 0; yigin[sp++] = 0; while (sp > 0) { const IsinBvh::Dal& dal = d.isinBvh->dallar[yigin[--sp]]; if (!kutuKes(dal.kutu)) continue; if (dal.yaprak) { for (u32 u2 = 0; u2 < dal.b; ++u2) ucgenTest(d.cpuKonum, d.cpuIndeks, d.cpuAlt, (size_t)d.isinBvh->ucgen[dal.a + u2] * 3); } else if (sp + 2 <= (int)(sizeof(yigin) / sizeof(yigin[0]))) { yigin[sp++] = dal.a; yigin[sp++] = dal.b; } } } else { for (size_t i = 0; i + 2 < d.cpuIndeks.size(); i += 3) ucgenTest(d.cpuKonum, d.cpuIndeks, d.cpuAlt, i); } } return bulundu; } u32 Sahne::isinKureEnYakin(Vec3f orij, Vec3f yon, Vec3f& merkez) const { const Vec3f d0 = yon.uzunlukKare() > 1e-20f ? yon.birim() : Vec3f{0, 0, 1}; u32 enYakin = 0; f32 enT = 3.0e30f; for (const auto& d : dunya_.depo().yogun()) { if (!agacGorunurHizli(d.kimlik) || d.cpuIndeks.empty()) continue; // yalnız katı (üçgenli) gövde const Vec3f c = d.dunyaMerkez(); f32 t; if (!isinKureKes(orij, d0, c, d.dunyaYaricap(), t)) continue; if (t < enT) { enT = t; enYakin = d.kimlik; merkez = c; } } return enYakin; } std::vector Sahne::isinVurCoklu(Vec3f orij, Vec3f yon, bool* bvhEksik) const { std::vector hepsi; for (const auto& d : dunya_.depo().yogun()) { if (!agacGorunurHizli(d.kimlik) || d.cpuIndeks.empty()) continue; const Donusumd ters = d.donusum.ters(); const Vec3d o0 = ters.noktaUygula({(f64)orij.x, (f64)orij.y, (f64)orij.z}); const Vec3d o1 = ters.noktaUygula( {(f64)(orij.x + yon.x), (f64)(orij.y + yon.y), (f64)(orij.z + yon.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(); const auto kutuKes = [&](const Aabbf& kutu) -> bool { if (!kutu.gecerliMi()) return true; f32 tGir = -3.0e30f, tCik = 3.0e30f; for (int ek = 0; ek < 3; ++ek) { const f32 o = (&lo.x)[ek], yn = (&ld.x)[ek]; const f32 mn = (&kutu.asgari.x)[ek], mx = (&kutu.azami.x)[ek]; if (std::abs(yn) < 1e-9f) { if (o < mn || o > mx) return false; } else { const f32 inv = 1.0f / yn; f32 t1 = (mn - o) * inv, t2 = (mx - o) * inv; if (t1 > t2) std::swap(t1, t2); tGir = std::max(tGir, t1); tCik = std::min(tCik, t2); if (tGir > tCik) return false; } } return tCik >= 0.0f; }; if (!kutuKes(d.yerelSinir)) continue; // Her kesişen üçgeni topla (en yakın filtresi YOK — tüm katmanlar). konum/indeks/alt // PARAMETRE: isinVur'daki AYNI güncel/eski anlık görüntü seçimi burada da geçerli. const auto ucgenTest = [&](const std::vector& konum, const std::vector& indeks, const std::vector& alt, size_t i) { const Vec3f& a = konum[indeks[i]]; const Vec3f& b = konum[indeks[i + 1]]; const Vec3f& c = konum[indeks[i + 2]]; const Vec3f e1 = b - a, e2 = c - a; const Vec3f p = ld.capraz(e2); const f32 det = e1.nokta(p); if (std::abs(det) < 1e-8f) return; const f32 invDet = 1.0f / det; const Vec3f tv = lo - a; const f32 u = tv.nokta(p) * invDet; if (u < 0.0f || u > 1.0f) return; const Vec3f q = tv.capraz(e1); const f32 v = ld.nokta(q) * invDet; if (v < 0.0f || u + v > 1.0f) return; const f32 t = e2.nokta(q) * invDet; if (t < 1e-4f) return; const Vec3f yerelHit = lo + ld * t; const Vec3d dh = d.donusum.noktaUygula( {(f64)yerelHit.x, (f64)yerelHit.y, (f64)yerelHit.z}); const Vec3f dunyaHit{(f32)dh.x, (f32)dh.y, (f32)dh.z}; if (!kirpDuzlemler_.empty() && kirpildiMi(dunyaHit)) return; // kesit dışı → seçilemez const f32 dunyaT = (dunyaHit - orij).nokta(yon); if (dunyaT < 1e-4f) return; Vec3f yn = e1.capraz(e2).birim(); if (yn.nokta(ld) > 0) yn = yn * -1.0f; const Vec3d wn = d.donusum.yonelim.dondur({(f64)yn.x, (f64)yn.y, (f64)yn.z}); IsinVurus vr; vr.govde = d.kimlik; vr.alt = indeks[i] < alt.size() ? alt[indeks[i]] : 0; vr.nokta = dunyaHit; vr.normal = Vec3f{(f32)wn.x, (f32)wn.y, (f32)wn.z}.birim(); vr.t = dunyaT; hepsi.push_back(vr); }; // BVH devralma isinVur ile AYNI politika: hazır değilse ESKİ (bayat) BVH varsa onunla // devam et, yoksa atla (donma yok). Bkz. isinVur — aynı gerekçe. bool eskiyeDus = false; if (!d.isinBvh) { if (!bvhArkaplan() || d.cpuIndeks.size() / 3 <= kBvhSenkronUcgen) { d.isinBvh = isinBvhKur(d.cpuKonum, d.cpuIndeks); } else { bool suruyor = false; if (auto hazir = bvhKurucu().al(d.kimlik, suruyor)) { d.isinBvh = hazir; d.isinBvhEski.reset(); d.cpuKonumEski.clear(); d.cpuIndeksEski.clear(); d.cpuAltEski.clear(); } else { if (!suruyor) bvhKurucu().baslat(d.kimlik, d.cpuKonum.data(), d.cpuKonum.size(), d.cpuIndeks.data(), d.cpuIndeks.size()); if (bvhEksik) *bvhEksik = true; if (!d.isinBvhEski) continue; eskiyeDus = true; } } } if (eskiyeDus) { u32 yigin[64]; int sp = 0; yigin[sp++] = 0; while (sp > 0) { const IsinBvh::Dal& dal = d.isinBvhEski->dallar[yigin[--sp]]; if (!kutuKes(dal.kutu)) continue; if (dal.yaprak) { for (u32 u2 = 0; u2 < dal.b; ++u2) ucgenTest(d.cpuKonumEski, d.cpuIndeksEski, d.cpuAltEski, (size_t)d.isinBvhEski->ucgen[dal.a + u2] * 3); } else if (sp + 2 <= (int)(sizeof(yigin) / sizeof(yigin[0]))) { yigin[sp++] = dal.a; yigin[sp++] = dal.b; } } } else if (d.isinBvh) { u32 yigin[64]; int sp = 0; yigin[sp++] = 0; while (sp > 0) { const IsinBvh::Dal& dal = d.isinBvh->dallar[yigin[--sp]]; if (!kutuKes(dal.kutu)) continue; if (dal.yaprak) { for (u32 u2 = 0; u2 < dal.b; ++u2) ucgenTest(d.cpuKonum, d.cpuIndeks, d.cpuAlt, (size_t)d.isinBvh->ucgen[dal.a + u2] * 3); } else if (sp + 2 <= (int)(sizeof(yigin) / sizeof(yigin[0]))) { yigin[sp++] = dal.a; yigin[sp++] = dal.b; } } } else { for (size_t i = 0; i + 2 < d.cpuIndeks.size(); i += 3) ucgenTest(d.cpuKonum, d.cpuIndeks, d.cpuAlt, i); } } // Mesafeye sırala + (gövde,yüz) başına en yakını tut (tek katman-adayı). std::sort(hepsi.begin(), hepsi.end(), [](const IsinVurus& a, const IsinVurus& b) { return a.t < b.t; }); std::vector sonuc; std::unordered_map gorulen; for (const IsinVurus& vr : hepsi) { const u64 anahtar = ((u64)vr.govde << 32) | vr.alt; if (gorulen.emplace(anahtar, 1).second) sonuc.push_back(vr); } return sonuc; } bool Sahne::cpuKenarVur(Vec3f orij, Vec3f yon, const Mat4f& vp, f32 imlecPxX, f32 imlecPxY, f32 vpG, f32 vpY, f32 pikselTol, KenarVurus& sonuc) const { sonuc = {}; const f32 tol2 = pikselTol * pikselTol; f32 enPm2 = tol2; // yalnız tolerans içindekiler; en yakın 2B kazanır bool bulundu = false; for (const auto& d : dunya_.depo().yogun()) { if (!agacGorunurHizli(d.kimlik) || d.cpuKenar.empty()) continue; // Datum/referans (Cizgi/Duzlem/Eksen/…) GPU pass'ta kalır (derinlik-testsiz, az primitif, // occlusion-muaf semantiği korunur). CPU kenar pick = MODEL kenarları (asıl perf yükü). if (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta || d.tip == DugumTip::Cizgi) continue; // CAM takım yolu görselleştirmedir, seçilebilir geometri DEĞİL: hover/seçim dışı // (aksi halde 200k+ vertexli yol "komple seçili" parlıyor + her hover O(kenar)). if (pickAyar().yolSecilemez && d.tip == DugumTip::TakimYolu) continue; const Mat4f M = d.matris(); 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) { const Vec3f wa = (M * Vec4f(d.cpuKenar[i], 1.0f)).xyz(); // yerel → dünya const Vec3f wb = (M * Vec4f(d.cpuKenar[i + 1], 1.0f)).xyz(); const Vec4f ca = vp * Vec4f(wa, 1.0f); // dünya → clip const Vec4f cb = vp * Vec4f(wb, 1.0f); if (ca.w <= 1e-6f || cb.w <= 1e-6f) continue; // kamera arkası/yakın // NDC → piksel (belge_ciz konvansiyonu: px = (ndc*0.5+0.5)*boyut). const f32 axp = (ca.x / ca.w * 0.5f + 0.5f) * vpG, ayp = (ca.y / ca.w * 0.5f + 0.5f) * vpY; const f32 bxp = (cb.x / cb.w * 0.5f + 0.5f) * vpG, byp = (cb.y / cb.w * 0.5f + 0.5f) * vpY; // İmleç → 2B segment [A,B] mesafe (param s klemplenir). const f32 ex = bxp - axp, ey = byp - ayp, el2 = ex * ex + ey * ey; f32 s = el2 > 1e-9f ? ((imlecPxX - axp) * ex + (imlecPxY - ayp) * ey) / el2 : 0.0f; s = std::clamp(s, 0.0f, 1.0f); const f32 dx = imlecPxX - (axp + ex * s), dy = imlecPxY - (ayp + ey * s); const f32 pm2 = dx * dx + dy * dy; if (pm2 >= enPm2) continue; // daha uzak → geç const Vec3f wp = wa + (wb - wa) * s; // dünya vuruş noktası if (!kirpDuzlemler_.empty() && kirpildiMi(wp)) continue; // kesitle görünmez → seçilemez const f32 t = (wp - orij).nokta(yon); if (t < 1e-4f) continue; enPm2 = pm2; sonuc.govde = d.kimlik; sonuc.alt = d.cpuKenarAlt[i]; sonuc.t = t; sonuc.pikselMesafe = std::sqrt(pm2); sonuc.nokta = wp; bulundu = true; } } return bulundu; } std::vector Sahne::seciliGovdeler() const { std::vector sonuc; std::unordered_set gorulen; sonuc.reserve(secililer_.size()); gorulen.reserve(secililer_.size()); for (const auto& o : secililer_) if (gorulen.insert(o.govde).second) sonuc.push_back(o.govde); // ilk gorulme sirasi korunur return sonuc; } }