#include "fiko/sahne/isin_bvh.hpp" #include #include #include namespace fiko { bool& bvhArkaplan() { static bool acik = true; // AyarSistemi "secim.bvh_arkaplan" return acik; } namespace { constexpr u32 kYaprakEsik = 8; // yaprak başına en çok üçgen constexpr u32 kIptalAdim = 65536; // iptal bayrağı kontrol aralığı (üçgen) constexpr u32 kParalelEsik = 262144; // bundan az üçgen → tek çekirdek yeter struct KurulumBaglam { const std::atomic* dur = nullptr; std::vector merkez; // üçgen merkezleri (split anahtarı) std::vector kutu; // üçgen AABB'leri IsinBvh* bvh = nullptr; }; bool durduMu(const KurulumBaglam& kb) { return kb.dur && kb.dur->load(std::memory_order_relaxed); } // nth_element dev dilimde bölünemez → iptal edilebilirlik karşılaştırıcı içinden // sağlanır (64k karşılaştırmada bir bayrak oku; işaretliyse fırlat → kurulum // anında terk edilir, ucgen[] yarım-permütasyonu zararsız çünkü ağaç çöpe gider). // iptal() böylece mesh mutasyonu öncesi EN ÇOK birkaç ms bekler. struct KurulumIptal {}; auto medianKars(const KurulumBaglam& kb, int eksen, u32& sayac) { return [&kb, eksen, &sayac](u32 t1, u32 t2) { if (((++sayac) & 0xFFFFu) == 0 && durduMu(kb)) throw KurulumIptal{}; return (&kb.merkez[t1].x)[eksen] < (&kb.merkez[t2].x)[eksen]; }; } // [bas,son) aralığındaki ucgen[] dilimi için düğüm kur, HEDEF dizideki indeksini // döndür. Paralel alt-ağaçlar kendi yerel dizisine kurar (ucgen[] dilimleri ayrık // → nth_element yarışsız); sonra ofsetle ana diziye eklenir. İptalde erken döner // (çağıran durduMu ile ağacı çöpe atar). u32 dugumKur(KurulumBaglam& kb, std::vector& dallar, u32 bas, u32 son) { if (durduMu(kb)) return 0; const u32 kendi = (u32)dallar.size(); dallar.emplace_back(); Aabbf kutu; for (u32 i = bas; i < son; ++i) kutu.genislet(kb.kutu[kb.bvh->ucgen[i]]); dallar[kendi].kutu = kutu; const u32 sayi = son - bas; if (sayi <= kYaprakEsik) { dallar[kendi].yaprak = true; dallar[kendi].a = bas; dallar[kendi].b = sayi; return kendi; } // En uzun eksende merkez-median split (dengeli derinlik garantisi). const Vec3f boy = kutu.azami - kutu.asgari; const int eksen = (boy.x >= boy.y && boy.x >= boy.z) ? 0 : (boy.y >= boy.z ? 1 : 2); const u32 orta = bas + sayi / 2; u32 sayac = 0; std::nth_element(kb.bvh->ucgen.begin() + bas, kb.bvh->ucgen.begin() + orta, kb.bvh->ucgen.begin() + son, medianKars(kb, eksen, sayac)); const u32 sol = dugumKur(kb, dallar, bas, orta); const u32 sag = dugumKur(kb, dallar, orta, son); dallar[kendi].a = sol; dallar[kendi].b = sag; return kendi; } } // namespace std::shared_ptr isinBvhKur(const Vec3f* konum, size_t konumSayi, const u32* indeks, size_t indeksSayi, const std::atomic* dur) { const u32 ucgenSayi = (u32)(indeksSayi / 3); if (ucgenSayi == 0 || konumSayi == 0) return nullptr; auto bvh = std::make_shared(); bvh->ucgen.resize(ucgenSayi); KurulumBaglam kb; kb.dur = dur; kb.bvh = bvh.get(); kb.merkez.resize(ucgenSayi); kb.kutu.resize(ucgenSayi); const u32 donanim = std::max(1u, std::thread::hardware_concurrency()); const u32 isciSayi = ucgenSayi >= kParalelEsik ? std::min(donanim, 16u) : 1u; // Üçgen merkez/AABB hazırlığı — ayrık dilimlere salt yazım → yarışsız paralel. const auto hazirla = [&](u32 t0, u32 t1) { for (u32 t = t0; t < t1; ++t) { if ((t & (kIptalAdim - 1)) == 0 && durduMu(kb)) return; bvh->ucgen[t] = t; const Vec3f& a = konum[indeks[t * 3]]; const Vec3f& b = konum[indeks[t * 3 + 1]]; const Vec3f& c = konum[indeks[t * 3 + 2]]; Aabbf k; k.genislet(a); k.genislet(b); k.genislet(c); kb.kutu[t] = k; kb.merkez[t] = {(a.x + b.x + c.x) / 3.0f, (a.y + b.y + c.y) / 3.0f, (a.z + b.z + c.z) / 3.0f}; } }; if (isciSayi <= 1) { hazirla(0, ucgenSayi); } else { std::vector isciler; isciler.reserve(isciSayi); const u32 adim = (ucgenSayi + isciSayi - 1) / isciSayi; for (u32 i = 0; i < isciSayi; ++i) isciler.emplace_back(hazirla, i * adim, std::min(ucgenSayi, (i + 1) * adim)); for (auto& t : isciler) t.join(); } if (durduMu(kb)) return nullptr; if (isciSayi <= 1) { bvh->dallar.reserve(ucgenSayi / 3 + 8); try { dugumKur(kb, bvh->dallar, 0, ucgenSayi); } catch (const KurulumIptal&) { return nullptr; } return durduMu(kb) ? nullptr : bvh; } // Paralel ağaç: Faz A (seri iskelet) kökten en büyük parçayı bölerek ~2×işçi // ayrık [bas,son) parçası çıkarır; Faz B her parçanın alt-ağacını paralel kurar // (yerel dizide); Faz C yerel ağaçlar ofset düzeltmesiyle ana diziye eklenir. struct Parca { u32 dugum; u32 bas, son; }; const u32 hedefParca = std::min(isciSayi * 2, 32); std::vector parcalar; bvh->dallar.reserve(ucgenSayi / 3 + 8); bvh->dallar.emplace_back(); // kök (indeks 0) parcalar.push_back({0, 0, ucgenSayi}); try { while ((u32)parcalar.size() < hedefParca) { // En çok üçgenli parçayı böl → dengesiz dağılımda da eşit işçi yükü. size_t enB = 0; for (size_t i = 1; i < parcalar.size(); ++i) if (parcalar[i].son - parcalar[i].bas > parcalar[enB].son - parcalar[enB].bas) enB = i; const Parca p = parcalar[enB]; const u32 sayi = p.son - p.bas; if (sayi <= kYaprakEsik * 4) break; // bölünecek anlamlı parça kalmadı parcalar.erase(parcalar.begin() + enB); Aabbf kutu; for (u32 i = p.bas; i < p.son; ++i) kutu.genislet(kb.kutu[bvh->ucgen[i]]); bvh->dallar[p.dugum].kutu = kutu; const Vec3f boy = kutu.azami - kutu.asgari; const int eksen = (boy.x >= boy.y && boy.x >= boy.z) ? 0 : (boy.y >= boy.z ? 1 : 2); const u32 orta = p.bas + sayi / 2; u32 sayac = 0; std::nth_element(bvh->ucgen.begin() + p.bas, bvh->ucgen.begin() + orta, bvh->ucgen.begin() + p.son, medianKars(kb, eksen, sayac)); const u32 sol = (u32)bvh->dallar.size(); bvh->dallar.emplace_back(); const u32 sag = (u32)bvh->dallar.size(); bvh->dallar.emplace_back(); bvh->dallar[p.dugum].a = sol; bvh->dallar[p.dugum].b = sag; parcalar.push_back({sol, p.bas, orta}); parcalar.push_back({sag, orta, p.son}); if (durduMu(kb)) return nullptr; } } catch (const KurulumIptal&) { return nullptr; } std::vector> yereller(parcalar.size()); { std::vector> isler; isler.reserve(parcalar.size()); for (size_t i = 0; i < parcalar.size(); ++i) isler.push_back(std::async(std::launch::async, [&, i] { yereller[i].reserve((parcalar[i].son - parcalar[i].bas) / 4 + 8); dugumKur(kb, yereller[i], parcalar[i].bas, parcalar[i].son); })); bool iptalOldu = false; for (auto& f : isler) { // HEPSİNİ bekle (erken dönüş = koşan işçi UAF'ı) try { f.get(); } catch (const KurulumIptal&) { iptalOldu = true; } } if (iptalOldu) return nullptr; } if (durduMu(kb)) return nullptr; // Faz C: yerel[0] iskelet yer-tutucusuna kopyalanır, yerel[1..] ofsetle eklenir. // Yaprakların a/b'si ucgen[] tabanı/sayısı (global dizi) → ofset UYGULANMAZ. for (size_t i = 0; i < parcalar.size(); ++i) { std::vector& yerel = yereller[i]; if (yerel.empty()) return nullptr; // iptal yarışı — güvenli çöp const u32 ofset = (u32)bvh->dallar.size() - 1; // yerel L (L≥1) → ofset+L IsinBvh::Dal kokDal = yerel[0]; if (!kokDal.yaprak) { kokDal.a += ofset; kokDal.b += ofset; } bvh->dallar[parcalar[i].dugum] = kokDal; for (size_t L = 1; L < yerel.size(); ++L) { IsinBvh::Dal dal = yerel[L]; if (!dal.yaprak) { dal.a += ofset; dal.b += ofset; } bvh->dallar.push_back(dal); } } return bvh; } namespace { // Nokta→AABB kare uzaklık (nokta kutu içindeyse 0). Budama alt-sınırı. f32 noktaAabbMesafeKare(const Aabbf& k, const Vec3f& p) { f32 s = 0.0f; for (int e = 0; e < 3; ++e) { const f32 v = (&p.x)[e]; const f32 mn = (&k.asgari.x)[e], mx = (&k.azami.x)[e]; if (v < mn) { const f32 d = mn - v; s += d * d; } else if (v > mx) { const f32 d = v - mx; s += d * d; } } return s; } // Nokta→üçgen en yakın nokta (Ericson, Real-Time Collision Detection §5.1.5). // Baryzentrik bölge testi: köşe/kenar/iç Voronoi bölgeleri → tam en yakın nokta. Vec3f noktaUcgenEnYakin(const Vec3f& p, const Vec3f& a, const Vec3f& b, const Vec3f& c) { const Vec3f ab = b - a, ac = c - a, ap = p - a; const f32 d1 = ab.nokta(ap), d2 = ac.nokta(ap); if (d1 <= 0.0f && d2 <= 0.0f) return a; // köşe A const Vec3f bp = p - b; const f32 d3 = ab.nokta(bp), d4 = ac.nokta(bp); if (d3 >= 0.0f && d4 <= d3) return b; // köşe B const f32 vc = d1 * d4 - d3 * d2; if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) // kenar AB return a + ab * (d1 / (d1 - d3)); const Vec3f cp = p - c; const f32 d5 = ab.nokta(cp), d6 = ac.nokta(cp); if (d6 >= 0.0f && d5 <= d6) return c; // köşe C const f32 vb = d5 * d2 - d1 * d6; if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) // kenar AC return a + ac * (d2 / (d2 - d6)); const f32 va = d3 * d6 - d5 * d4; if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) { // kenar BC const f32 w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); return b + (c - b) * w; } const f32 denom = 1.0f / (va + vb + vc); // iç → baryzentrik return a + ab * (vb * denom) + ac * (vc * denom); } } // namespace f32 bvhEnYakinMesafeKare(const IsinBvh& bvh, const Vec3f* konum, const u32* indeks, Vec3f nokta, Vec3f* enYakinCik, Vec3f* enYakinNormalCik) { if (bvh.dallar.empty()) return 3.0e30f; f32 enIyi = 3.0e30f; Vec3f enIyiNokta = nokta; Vec3f enIyiNormal{0, 0, 1}; // Yığın traversal + AABB budama: kutu alt-sınırı ≥ mevcut en iyi → dal atlanır. // Yakın çocuğu SONRA it (önce pop) → en iyi çabuk düşer, daha çok budama. u32 yigin[64]; int sp = 0; yigin[sp++] = 0; while (sp > 0) { const IsinBvh::Dal& dal = bvh.dallar[yigin[--sp]]; if (noktaAabbMesafeKare(dal.kutu, nokta) >= enIyi) continue; if (dal.yaprak) { for (u32 u = 0; u < dal.b; ++u) { const size_t i = (size_t)bvh.ucgen[dal.a + u] * 3; const Vec3f& a = konum[indeks[i]]; const Vec3f& b = konum[indeks[i + 1]]; const Vec3f& c = konum[indeks[i + 2]]; const Vec3f q = noktaUcgenEnYakin(nokta, a, b, c); const f32 dk = (q - nokta).uzunlukKare(); if (dk < enIyi) { enIyi = dk; enIyiNokta = q; if (enYakinNormalCik) enIyiNormal = (b - a).capraz(c - a); // normalize sonra } } } else if (sp + 2 <= (int)(sizeof(yigin) / sizeof(yigin[0]))) { // Yakın çocuğu en son it (en üstte → önce işlenir). const f32 dA = noktaAabbMesafeKare(bvh.dallar[dal.a].kutu, nokta); const f32 dB = noktaAabbMesafeKare(bvh.dallar[dal.b].kutu, nokta); if (dA < dB) { yigin[sp++] = dal.b; yigin[sp++] = dal.a; } else { yigin[sp++] = dal.a; yigin[sp++] = dal.b; } } } if (enYakinCik) *enYakinCik = enIyiNokta; if (enYakinNormalCik) { const f32 u = enIyiNormal.uzunluk(); *enYakinNormalCik = u > 1e-12f ? enIyiNormal * (1.0f / u) : Vec3f{0, 0, 1}; } return enIyi; } IsinBvhKurucu::~IsinBvhKurucu() { std::unordered_map kalan; { std::lock_guard k(kilit_); kalan = std::move(isler_); isler_.clear(); } for (auto& [kimlik, is] : kalan) { is.dur->store(true, std::memory_order_relaxed); if (is.sonuc.valid()) is.sonuc.wait(); } } void IsinBvhKurucu::baslat(u32 kimlik, const Vec3f* konum, size_t konumSayi, const u32* indeks, size_t indeksSayi) { std::lock_guard k(kilit_); if (isler_.count(kimlik)) return; // zaten sürüyor/bitti (al devralacak) Is is; is.dur = std::make_shared>(false); is.sonuc = std::async(std::launch::async, [konum, konumSayi, indeks, indeksSayi, dur = is.dur] { return isinBvhKur(konum, konumSayi, indeks, indeksSayi, dur.get()); }); isler_.emplace(kimlik, std::move(is)); } std::shared_ptr IsinBvhKurucu::al(u32 kimlik, bool& suruyor) { std::lock_guard k(kilit_); suruyor = false; const auto it = isler_.find(kimlik); if (it == isler_.end()) return nullptr; if (it->second.sonuc.wait_for(std::chrono::seconds(0)) != std::future_status::ready) { suruyor = true; return nullptr; } auto sonuc = it->second.sonuc.get(); isler_.erase(it); return sonuc; } void IsinBvhKurucu::iptal(u32 kimlik) { Is is; { std::lock_guard k(kilit_); const auto it = isler_.find(kimlik); if (it == isler_.end()) return; is = std::move(it->second); isler_.erase(it); } is.dur->store(true, std::memory_order_relaxed); if (is.sonuc.valid()) is.sonuc.wait(); // veri pointer'ları serbest kalmadan İŞ DURUR } } // namespace fiko