#include "fiko/kutuk/sdf_motor.hpp" #include #include #include #include #include "fiko/kutuk/paralel_havuz.hpp" #include "mc_tablolari.hpp" namespace fiko::kutuk { SdfMotoru::SdfMotoru() = default; // havuz_ tam tip burada (ctor/dtor .cpp'de) SdfMotoru::~SdfMotoru() = default; namespace { inline bool sonluMu(f64 v) { return std::isfinite(v); } inline bool sonluMu(const Vec3d& v) { return sonluMu(v.x) && sonluMu(v.y) && sonluMu(v.z); } // Gecerli (birim) eksen; NaN/sifir -> (0,0,1) (spec §5 guard). Vec3d gecerliEksen(Vec3d e) { if (!sonluMu(e)) return {0, 0, 1}; const f64 u2 = e.uzunlukKare(); if (u2 < 1e-12) return {0, 0, 1}; return e / std::sqrt(u2); } // Birim normal → u32 (oct-encode, 2×16-bit snorm). Kompakt normal saklama. inline u32 octPaketle(Vec3d n) { const f64 s = std::fabs(n.x) + std::fabs(n.y) + std::fabs(n.z); if (s < 1e-12) return 0u; f64 x = n.x / s, y = n.y / s; if (n.z < 0.0) { const f64 ox = (1.0 - std::fabs(y)) * (x >= 0 ? 1.0 : -1.0); const f64 oy = (1.0 - std::fabs(x)) * (y >= 0 ? 1.0 : -1.0); x = ox; y = oy; } auto q = [](f64 v) -> u32 { v = std::clamp(v, -1.0, 1.0); return (u32)(u16)(i16)std::lround(v * 32767.0); }; return q(x) | (q(y) << 16); } inline Vec3d octCoz(u32 p) { if (p == 0u) return {0, 0, 1}; auto d = [](u32 h) { return (f64)(i16)(u16)h / 32767.0; }; f64 x = d(p & 0xFFFFu), y = d(p >> 16); const f64 z = 1.0 - std::fabs(x) - std::fabs(y); if (z < 0.0) { const f64 ox = (1.0 - std::fabs(y)) * (x >= 0 ? 1.0 : -1.0); const f64 oy = (1.0 - std::fabs(x)) * (y >= 0 ? 1.0 : -1.0); x = ox; y = oy; } Vec3d n{x, y, z}; const f64 l2 = n.uzunlukKare(); return (l2 < 1e-20) ? Vec3d{0, 0, 1} : n / std::sqrt(l2); } // Sonsuz silindir SDF: eksen T (birim) poz'dan gecer, yaricap R. // Dis pozitif, ic negatif. rp = p-poz. inline f64 silindirSdf(const Vec3d& rp, const Vec3d& T, f64 R) { const f64 ax = rp.nokta(T); const f64 rad2 = std::max(0.0, rp.uzunlukKare() - ax * ax); // √ oncesi max(0,·) return std::sqrt(rad2) - R; } } // namespace // --------------------------------------------------------------------------- // takimSdf — analitik takim SDF (spec §3): flat/ball/torus // ---------------------------------------------------------------------------- // Takim ucu poz'da, eksen T yukari (+boy govde). Uc profili tipe gore: // Duz = max(silindir, kapak) (yari-uzay: ax>=0 tabani duz) // Ball = min(silindir, kure) (uc yarikuresi) // Torus= flat bolge + kose torus (bull-nose) // Konvansiyon: ax = eksen boyunca mesafe (uc=0, +yon govde). Negatif=malzeme ici. // --------------------------------------------------------------------------- f64 SdfMotoru::takimSdf(Vec3d p, Vec3d poz, const TakimGeo& takim) { const Vec3d T = takim.eksen; // birim (cagiranda garanti) const f64 R = takim.cap * 0.5; const f64 boy = takim.boy; const Vec3d rp = p - poz; const f64 ax = rp.nokta(T); const f64 rad2 = std::max(0.0, rp.uzunlukKare() - ax * ax); const f64 rad = std::sqrt(rad2); const bool kuresel = takim.tip == TakimGeo::Tip::Kuresel; const bool torus = takim.tip == TakimGeo::Tip::Torus; // Govde silindiri (sonsuz radyal) SDF: rad - R. const f64 radSdf = rad - R; if (kuresel) { // Ball: alt yari kure (merkez poz+R*T... web konvansiyonu: uc kuresi merkezi // poz'da degil poz+R*T'de). Basit ve dogru: uc kuresi merkezi = poz + R*T, // govde silindiri [R, boy]. Birlesim = min. const Vec3d merk = poz + T * R; const f64 kureSdf = (p - merk).uzunluk() - R; // Govde: ax in [R, boy], radyal R. Ust kapak boy'da. const f64 govdeUst = ax - boy; // ax<=boy ici const f64 govdeAlt = R - ax; // ax>=R ici (kure alani devralir) const f64 govde = std::max({radSdf, govdeUst, govdeAlt}); return std::min(kureSdf, govde); } if (torus) { // Bull-nose: duz bolge yaricapi flatR = R-rc, kose torus radyusu rc. const f64 rc = std::clamp(takim.koseR, 0.0, R); const f64 flatR = std::max(0.0, R - rc); // Torus merkez halkasi z=rc'de (uc'ten rc yukarida), halka yaricapi flatR. // 2B profil: (q.x=rad, q.y=ax). Torus SDF = length(vec2(length(q.xy-halka)))... // Kanonik: dış = max( radyal duvar, torus alt ). Basit birlesim: const f64 govdeUst = ax - boy; // Torus kesiti: merkez (flatR, rc). d = uzaklik halka merkezinden. const f64 dx = rad - flatR; const f64 dy = ax - rc; const f64 torusSdf = std::sqrt(dx * dx + dy * dy) - rc; // Duz merkez sutunu (rad<=flatR): govde silindiri flatR, taban ax=0. const f64 duzTaban = -ax; // ax>=0 ici const f64 duzRad = rad - flatR; const f64 duzMerkez = std::max(duzRad, duzTaban); // Birlesim: alt kose (torus) + duz merkez sutunu, ust kapak. const f64 alt = std::min(torusSdf, duzMerkez); return std::max(alt, std::max(govdeUst, -1e9)); // ust kapakla kirp } // Duz (flat): silindir govde [0,boy] + duz uc/kapak. const f64 kapakAlt = -ax; // ax>=0 ici (taban) const f64 kapakUst = ax - boy; // ax<=boy ici (tavan) return std::max({radSdf, kapakAlt, kapakUst}); } // --------------------------------------------------------------------------- // sweptSdf — A->B segment supurmesi (sdCapsule): en-yakin-nokta + takimSdf // --------------------------------------------------------------------------- f64 SdfMotoru::sweptSdf(Vec3d p, Vec3d a, Vec3d b, const TakimGeo& takim) { const Vec3d ab = b - a; const f64 ab2 = ab.uzunlukKare(); f64 t = 0.0; if (ab2 > 1e-12) t = std::clamp((p - a).nokta(ab) / ab2, 0.0, 1.0); const Vec3d en = a + ab * t; // segment uzeri en-yakin nokta return takimSdf(p, en, takim); } // --------------------------------------------------------------------------- // stokSdf — dolu (kati) stok SDF: kutu / silindir / offset(~kutu) // --------------------------------------------------------------------------- f64 SdfMotoru::stokSdf(Vec3d p) const { const Vec3f merkF = sinir_.merkez(); const Vec3f boyF = sinir_.boyut(); const Vec3d merk{merkF.x, merkF.y, merkF.z}; if (stok_.tip == cam::StokTip::Silindir) { // XY radyal + Z kapak (eksen Z varsayilir — web konvansiyonu). const f64 R = stok_.boyut.x * 0.5; const f64 yariZ = boyF.z * 0.5; const f64 dx = p.x - merk.x, dy = p.y - merk.y; const f64 rad = std::sqrt(dx * dx + dy * dy); const f64 radial = rad - R; const f64 zKapak = std::fabs(p.z - merk.z) - yariZ; // Kati silindir SDF: max(radial, zKapak) — negatif ici. return std::max(radial, zKapak); } // Kutu / GovdeOffset(ilk surum kutu yaklasik): kutu SDF. const f64 hx = boyF.x * 0.5, hy = boyF.y * 0.5, hz = boyF.z * 0.5; const f64 qx = std::fabs(p.x - merk.x) - hx; const f64 qy = std::fabs(p.y - merk.y) - hy; const f64 qz = std::fabs(p.z - merk.z) - hz; const f64 dOut = std::sqrt(std::max(0.0, qx) * std::max(0.0, qx) + std::max(0.0, qy) * std::max(0.0, qy) + std::max(0.0, qz) * std::max(0.0, qz)); const f64 dIn = std::min(0.0, std::max({qx, qy, qz})); return dOut + dIn; } // --------------------------------------------------------------------------- // kutukKur — GERCEK SDF ile doldur (POSITIVE_INFINITY init NaN hatasini duzeltir) // --------------------------------------------------------------------------- bool SdfMotoru::kutukKur(const cam::Stok& stok, Aabbf sinir, f64 cozunurluk) { sonHata_.clear(); kuruldu_ = false; doldu_ = false; kaldirilanHacim_ = 0.0; stok_ = stok; // Gecersiz bbox (min>max) reddi (MEMORY gotcha: NaN kamera fit korunur). if (!sinir.gecerliMi() || !sonluMu(Vec3d{sinir.asgari.x, sinir.asgari.y, sinir.asgari.z}) || !sonluMu(Vec3d{sinir.azami.x, sinir.azami.y, sinir.azami.z})) { sonHata_ = "Gecersiz kutuk siniri (min>max veya NaN)."; return false; } if (!(cozunurluk > 1e-9) || !sonluMu(cozunurluk)) { sonHata_ = "Gecersiz cozunurluk."; return false; } const Vec3f boyF = sinir.boyut(); const f64 bx = boyF.x, by = boyF.y, bz = boyF.z; cozunurluk_ = cozunurluk; h_ = cozunurluk; sinir_ = sinir; // Bos/dejenerre kutuk -> bos grid (NaN uretme). Basari ama malzeme yok. if (bx <= 1e-9 || by <= 1e-9 || bz <= 1e-9) { nx_ = ny_ = nz_ = 0; alan_.clear(); kuruldu_ = true; doldu_ = false; // MC calismaz (bos) return true; } auto sayi = [&](f64 uzunluk) -> u32 { // +1: hucre merkezi ornekleme icin sınır boyunca yeterli voxel. return std::max(2, static_cast(std::ceil(uzunluk / h_)) + 1); }; nx_ = sayi(bx); ny_ = sayi(by); nz_ = sayi(bz); // Bellek butcesi guard (asiri cozunurluk -> devasa grid engelle). const u64 toplam = static_cast(nx_) * ny_ * nz_; if (toplam > 64ull * 1024 * 1024) { // ~64M voxel = 256 MB float ustu red sonHata_ = "Cozunurluk cok yuksek (voxel bellek butcesi asildi)."; nx_ = ny_ = nz_ = 0; alan_.clear(); return false; } alan_.assign(toplam, 0.0f); // Analitik Hermite normalleri (keskin DC) — yalnız keskin_ iken ayrılır (bellek). if (keskin_) norm_.assign(toplam, 0u); else { norm_.clear(); norm_.shrink_to_fit(); } const f64 ne = h_ * 0.25; // GERCEK stok SDF ile doldur (POSITIVE_INFINITY YOK — NaN guard) + stok normali. for (u32 k = 0; k < nz_; ++k) for (u32 j = 0; j < ny_; ++j) for (u32 i = 0; i < nx_; ++i) { const Vec3d p = voxelDunya(i, j, k); const f32 sd = (f32)stokSdf(p); alan_[idx(i, j, k)] = sd; if (keskin_ && std::fabs(sd) <= (f32)(1.6 * h_)) { // stok yüzey bandı normali Vec3d g{stokSdf({p.x + ne, p.y, p.z}) - stokSdf({p.x - ne, p.y, p.z}), stokSdf({p.x, p.y + ne, p.z}) - stokSdf({p.x, p.y - ne, p.z}), stokSdf({p.x, p.y, p.z + ne}) - stokSdf({p.x, p.y, p.z - ne})}; const f64 l2 = g.uzunlukKare(); if (l2 > 1e-20) norm_[idx(i, j, k)] = octPaketle(g / std::sqrt(l2)); } } voxHac_ = h_ * h_ * h_; undoLog_.clear(); undoLog_.shrink_to_fit(); undoTasti_ = false; bantMesh_.clear(); // cache yeni grid'e gore yeniden kurulur bantKirli_.clear(); if (!havuz_) havuz_ = std::make_unique(); // kalici thread havuzu (bir kez) kuruldu_ = true; doldu_ = true; return true; } void SdfMotoru::sifirla() { undoLog_.clear(); // jurnal gecersiz (grid komple stok'a doner) undoTasti_ = false; tumBantlariKirlet(); // tum mesh cache'i bayat if (!kuruldu_ || nx_ == 0) { kaldirilanHacim_ = 0.0; return; } kaldirilanHacim_ = 0.0; const bool normVar = !norm_.empty(); const f64 ne = h_ * 0.25; for (u32 k = 0; k < nz_; ++k) for (u32 j = 0; j < ny_; ++j) for (u32 i = 0; i < nx_; ++i) { const Vec3d p = voxelDunya(i, j, k); const f32 sd = (f32)stokSdf(p); alan_[idx(i, j, k)] = sd; if (normVar) { norm_[idx(i, j, k)] = 0u; if (std::fabs(sd) <= (f32)(1.6 * h_)) { Vec3d g{stokSdf({p.x + ne, p.y, p.z}) - stokSdf({p.x - ne, p.y, p.z}), stokSdf({p.x, p.y + ne, p.z}) - stokSdf({p.x, p.y - ne, p.z}), stokSdf({p.x, p.y, p.z + ne}) - stokSdf({p.x, p.y, p.z - ne})}; const f64 l2 = g.uzunlukKare(); if (l2 > 1e-20) norm_[idx(i, j, k)] = octPaketle(g / std::sqrt(l2)); } } } doldu_ = true; } // --------------------------------------------------------------------------- // Checkpoint — SDF alani + hacim kopyasi (geri-scrub O(1) geri yukleme) // --------------------------------------------------------------------------- namespace { struct SdfSnapshot { std::vector alan; f64 hacim = 0.0; }; } // namespace std::shared_ptr SdfMotoru::durumKaydet() const { if (!kuruldu_ || !doldu_ || alan_.empty()) return nullptr; auto s = std::make_shared(); s->alan = alan_; // float grid kopyasi (durumBayt() kadar) s->hacim = kaldirilanHacim_; return s; } bool SdfMotoru::durumYukle(const std::shared_ptr& s) { if (!s || !kuruldu_) return false; auto* snap = static_cast(s.get()); if (snap->alan.size() != alan_.size()) return false; // grid boyutu degisti → gecersiz alan_ = snap->alan; kaldirilanHacim_ = snap->hacim; doldu_ = true; undoLog_.clear(); // checkpoint atladi → jurnal artik uyusmuyor undoTasti_ = false; tumBantlariKirlet(); // grid komple degisti → tum mesh cache bayat return true; } // --------------------------------------------------------------------------- // undoGeri — jurnali token'a kadar geri al (ince geri-scrub → ANINDA) // --------------------------------------------------------------------------- bool SdfMotoru::undoGeri(u64 token) { if (undoTasti_) return false; // tavan asilmisti → checkpoint'e dus if (token > undoLog_.size()) return false; // ileri gidilemez const u32 kats = nx_ * ny_; while (undoLog_.size() > token) { const auto [id, eski] = undoLog_.back(); undoLog_.pop_back(); const f32 cur = alan_[id]; // Un-carve: cur dis(>=0) + eski ic(<0) idiyse hacim geri ver. if (cur >= 0.0f && eski < 0.0f) kaldirilanHacim_ -= voxHac_; alan_[id] = eski; if (kats) bantlariKirlet(id / kats, id / kats); // bu voxel k'sinin bandi bayat } return true; } // --------------------------------------------------------------------------- // Cached-slab yardimcilari — band cache boyutlama + kirletme // --------------------------------------------------------------------------- void SdfMotoru::bantlariGarantile() { if (nz_ < 2) { bantMesh_.clear(); bantKirli_.clear(); return; } const u32 kSon = nz_ - 1; // MC hucre araligi [0,kSon) const u32 adet = (kSon + bantYuk_ - 1) / bantYuk_; if (bantMesh_.size() != adet) { bantMesh_.assign(adet, MeshVeri{}); bantKirli_.assign(adet, 1); // yeni → hepsi kirli } } void SdfMotoru::tumBantlariKirlet() { std::fill(bantKirli_.begin(), bantKirli_.end(), (u8)1); } void SdfMotoru::bantlariKirlet(u32 vk0, u32 vk1) { if (bantKirli_.empty() || nz_ < 2) return; const u32 kSon = nz_ - 1; // Voxel katmani L, MC hucreleri L-1 ve L'yi etkiler → [vk0-1, vk1] kirli. u32 c0 = (vk0 == 0) ? 0 : vk0 - 1; u32 c1 = (vk1 >= kSon) ? (kSon - 1) : vk1; if (c1 >= kSon) c1 = kSon - 1; const u32 b0 = c0 / bantYuk_; const u32 b1 = c1 / bantYuk_; for (u32 b = b0; b <= b1 && b < bantKirli_.size(); ++b) bantKirli_[b] = 1; } // --------------------------------------------------------------------------- // voxelAralik — dunya kutu -> voxel indeks araligi (clamp). GPU aynasi paylasir. // --------------------------------------------------------------------------- bool SdfMotoru::voxelAralik(Vec3d kutuMin, Vec3d kutuMax, u32& i0, u32& i1, u32& j0, u32& j1, u32& k0, u32& k1) const { auto araliki = [&](f64 lo, f64 hi, f64 o0, u32 n, u32& a, u32& b) { const f64 fl = (lo - o0) / h_ - 0.5; const f64 fh = (hi - o0) / h_ - 0.5; if (fh < 0 || fl > n - 1) { a = 1; b = 0; return; } a = static_cast(std::max(0.0, std::floor(fl))); b = static_cast(std::min(n - 1, std::ceil(fh))); }; araliki(kutuMin.x, kutuMax.x, sinir_.asgari.x, nx_, i0, i1); araliki(kutuMin.y, kutuMax.y, sinir_.asgari.y, ny_, j0, j1); araliki(kutuMin.z, kutuMax.z, sinir_.asgari.z, nz_, k0, k1); return !(i0 > i1 || j0 > j1 || k0 > k1); } // --------------------------------------------------------------------------- // etkiKutusu / etkiKutusuSwept — takim dunya AABB (GPU aynasi ile ortak) // --------------------------------------------------------------------------- void SdfMotoru::etkiKutusu(Vec3d poz, const TakimGeo& t, Vec3d& mn, Vec3d& mx) { const f64 R = t.cap * 0.5; const Vec3d ekb = t.eksen * t.boy; mn = {std::min(poz.x, poz.x + ekb.x) - R, std::min(poz.y, poz.y + ekb.y) - R, std::min(poz.z, poz.z + ekb.z) - R}; mx = {std::max(poz.x, poz.x + ekb.x) + R, std::max(poz.y, poz.y + ekb.y) + R, std::max(poz.z, poz.z + ekb.z) + R}; } void SdfMotoru::etkiKutusuSwept(Vec3d a, Vec3d b, const TakimGeo& t, Vec3d& mn, Vec3d& mx) { const f64 R = t.cap * 0.5; const Vec3d ekb = t.eksen * t.boy; auto mm = [](f64 x1, f64 x2, f64 x3, f64 x4, f64& lo, f64& hi) { lo = std::min({x1, x2, x3, x4}); hi = std::max({x1, x2, x3, x4}); }; mm(a.x, b.x, a.x + ekb.x, b.x + ekb.x, mn.x, mx.x); mm(a.y, b.y, a.y + ekb.y, b.y + ekb.y, mn.y, mx.y); mm(a.z, b.z, a.z + ekb.z, b.z + ekb.z, mn.z, mx.z); mn -= Vec3d{R, R, R}; mx += Vec3d{R, R, R}; } // --------------------------------------------------------------------------- // bolgeKes — CSG cikarma: result = max(stock, -toolSDF) (etki-kutusu daralt) // --------------------------------------------------------------------------- void SdfMotoru::bolgeKes(Vec3d kutuMin, Vec3d kutuMax, const std::function& sdfFn) { if (nx_ == 0 || !doldu_) return; u32 i0, i1, j0, j1, k0, k1; if (!voxelAralik(kutuMin, kutuMax, i0, i1, j0, j1, k0, k1)) return; const f64 voxHac = h_ * h_ * h_; const bool undoYaz = undoAcik_ && !undoTasti_; for (u32 k = k0; k <= k1; ++k) { for (u32 j = j0; j <= j1; ++j) { for (u32 i = i0; i <= i1; ++i) { const u32 id = idx(i, j, k); const f32 eski = alan_[id]; const f64 tsdf = sdfFn(voxelDunya(i, j, k)); // CSG cikarma: result = max(stock, -toolSDF). const f32 yeni = static_cast(std::max(eski, -tsdf)); if (yeni == eski) continue; // degisim yok → jurnale yazma (log kucuk kalir) // UNDO: degisen voxelin ESKI degeri (ince geri-scrub'ta geri yuklenir). if (undoYaz) { undoLog_.emplace_back(id, eski); if (undoLog_.size() > undoTavan_) undoTasti_ = true; // tavan → undo kapan } // Kaldirilan hacim: bir voxel ici(eski<0) -> dis(yeni>=0) gecti ise say // (isaret-degisimi hucre sayimi yaklasik — spec §3). if (eski < 0.0f && yeni >= 0.0f) kaldirilanHacim_ += voxHac; alan_[id] = yeni; if (!norm_.empty()) normGuncelle(id, voxelDunya(i, j, k), yeni, sdfFn); } } } bantlariKirlet(k0, k1); // bu k-araligini etkileyen MC bantlari yeniden MC gerekir } // Yüzeye yakın voxel'i analitik takım normaliyle güncelle (keskin DC). Field=-toolSdf // orada → dışa (malzeme) normali = -grad(toolSdf). Analitik → CSG kenarında keskin. void SdfMotoru::normGuncelle(u32 id, Vec3d p, f32 yeniDeger, const std::function& toolSdf) { if (std::fabs(yeniDeger) > (f32)(1.6 * h_)) return; // yalnız yüzey bandı (derin voxel gereksiz) const f64 e = h_ * 0.25; const f64 gx = toolSdf({p.x + e, p.y, p.z}) - toolSdf({p.x - e, p.y, p.z}); const f64 gy = toolSdf({p.x, p.y + e, p.z}) - toolSdf({p.x, p.y - e, p.z}); const f64 gz = toolSdf({p.x, p.y, p.z + e}) - toolSdf({p.x, p.y, p.z - e}); Vec3d n{-gx, -gy, -gz}; // dışa normal = -grad(tool) const f64 l2 = n.uzunlukKare(); if (l2 < 1e-20) return; norm_[id] = octPaketle(n / std::sqrt(l2)); } // --------------------------------------------------------------------------- // takimUygula / sweptUygula // --------------------------------------------------------------------------- void SdfMotoru::takimUygula(Vec3d poz, const TakimGeo& takim) { if (!kuruldu_ || nx_ == 0) return; if (!sonluMu(poz)) return; TakimGeo t = takim; t.eksen = gecerliEksen(takim.eksen); if (!(t.cap > 0)) return; Vec3d mn, mx; etkiKutusu(poz, t, mn, mx); bolgeKes(mn, mx, [&](Vec3d p) { return takimSdf(p, poz, t); }); } void SdfMotoru::sweptUygula(Vec3d a, Vec3d b, const TakimGeo& takim) { if (!kuruldu_ || nx_ == 0) return; if (!sonluMu(a) || !sonluMu(b)) return; TakimGeo t = takim; t.eksen = gecerliEksen(takim.eksen); if (!(t.cap > 0)) return; Vec3d mn, mx; etkiKutusuSwept(a, b, t, mn, mx); bolgeKes(mn, mx, [&](Vec3d p) { return sweptSdf(p, a, b, t); }); } // --------------------------------------------------------------------------- // batchUygula — çok-hamle PARALEL carve (k-slab bölerek, yarışsız) // ---------------------------------------------------------------------------- // Talaş kaldırma UNION (max) → hamle sırası önemsiz. Her thread bir k-slab'ı sahiplenir // (disjoint voxel → yarış YOK), yığındaki TÜM hamleleri kendi slab'ına uygular. SDF // analitik inline (std::function yok). Undo/hacim/dirty per-thread yerel → sonra birleşir. // Sonuç seri per-hamle ile BİREBİR (test kilitli). İleri oyma darboğazı burada çözülür. // --------------------------------------------------------------------------- void SdfMotoru::batchUygula(const KarveHamle* h, size_t n) { if (!kuruldu_ || nx_ == 0 || n == 0 || !doldu_) return; // Hamle voxel-AABB'lerini önceden hesapla (geçersiz/kesmesiz hamleyi ele). struct HZ { u32 i0, i1, j0, j1, k0, k1; Vec3d a, b; TakimGeo t; bool ilk; bool aktif = false; }; std::vector hz(n); for (size_t m = 0; m < n; ++m) { HZ& z = hz[m]; if (!h[m].ilk && !h[m].kesme) continue; // swept ama kesme değil → malzeme kaldırmaz TakimGeo t = h[m].takim; t.eksen = gecerliEksen(t.eksen); if (!(t.cap > 0)) continue; Vec3d mn, mx; if (h[m].ilk) { if (!sonluMu(h[m].b)) continue; etkiKutusu(h[m].b, t, mn, mx); z.a = z.b = h[m].b; } else { if (!sonluMu(h[m].a) || !sonluMu(h[m].b)) continue; etkiKutusuSwept(h[m].a, h[m].b, t, mn, mx); z.a = h[m].a; z.b = h[m].b; } if (!voxelAralik(mn, mx, z.i0, z.i1, z.j0, z.j1, z.k0, z.k1)) continue; z.t = t; z.ilk = h[m].ilk; z.aktif = true; } // Yığının birleşik k-aralığı — thread'leri BURAYA böl (boş slab'a thread harcama; // lokalize pass'te bile çekirdekler dolu). Aktif hamle yoksa çık. u32 gK0 = nz_, gK1 = 0; for (const HZ& z : hz) if (z.aktif) { gK0 = std::min(gK0, z.k0); gK1 = std::max(gK1, z.k1); } if (gK0 > gK1) return; // hiç aktif hamle yok const u32 kAdet = gK1 - gK0 + 1; unsigned T = havuz_ ? havuz_->isciSayisi() : 1; if (T > 16) T = 16; if (T > kAdet) T = std::max(1u, kAdet); const bool undoYaz = undoAcik_ && !undoTasti_; const f64 voxHac = voxHac_; const bool normVar = !norm_.empty(); // keskin DC: analitik normal güncelle const f32 yuzeyBant = (f32)(1.6 * h_); // yalnız yüzey bandı voxel'i std::vector>> yerelUndo(T); std::vector yerelHac(T, 0.0); std::vector kMinT(T, nz_), kMaxT(T, 0); // per-thread kirli k-aralığı auto isBirimi = [&](unsigned tid, u32 kt0, u32 kt1) { auto& ul = yerelUndo[tid]; f64 hac = 0.0; u32 kmin = nz_, kmax = 0; for (size_t m = 0; m < n; ++m) { const HZ& z = hz[m]; if (!z.aktif) continue; const u32 k0 = std::max(z.k0, kt0); const u32 k1 = std::min(z.k1, kt1 - 1); if (k0 > k1) continue; for (u32 k = k0; k <= k1; ++k) for (u32 j = z.j0; j <= z.j1; ++j) for (u32 i = z.i0; i <= z.i1; ++i) { const u32 id = idx(i, j, k); const f32 eski = alan_[id]; const Vec3d p = voxelDunya(i, j, k); const f64 tsdf = z.ilk ? takimSdf(p, z.b, z.t) : sweptSdf(p, z.a, z.b, z.t); const f32 yeni = static_cast(std::max(eski, -tsdf)); if (yeni == eski) continue; if (undoYaz) ul.emplace_back(id, eski); if (eski < 0.0f && yeni >= 0.0f) hac += voxHac; alan_[id] = yeni; // Analitik Hermite normali (keskin DC): yüzeye yakınsa takım gradyanı. if (normVar && std::fabs(yeni) <= yuzeyBant) { const f64 e = h_ * 0.25; auto ts = [&](Vec3d q) { return z.ilk ? takimSdf(q, z.b, z.t) : sweptSdf(q, z.a, z.b, z.t); }; Vec3d g{ts({p.x + e, p.y, p.z}) - ts({p.x - e, p.y, p.z}), ts({p.x, p.y + e, p.z}) - ts({p.x, p.y - e, p.z}), ts({p.x, p.y, p.z + e}) - ts({p.x, p.y, p.z - e})}; const f64 l2 = g.uzunlukKare(); if (l2 > 1e-20) norm_[id] = octPaketle(Vec3d{-g.x, -g.y, -g.z} / std::sqrt(l2)); } if (k < kmin) kmin = k; if (k > kmax) kmax = k; } } yerelHac[tid] = hac; kMinT[tid] = kmin; kMaxT[tid] = kmax; }; const u32 slab = (kAdet + T - 1) / T; auto gorev = [&](unsigned t) { const u32 a = gK0 + t * slab; if (a > gK1) return; const u32 b = std::min(gK1 + 1, a + slab); isBirimi(t, a, b); }; if (havuz_) havuz_->kos(gorev, T); else for (unsigned t = 0; t < T; ++t) gorev(t); // Birleştir: hacim + undo (disjoint voxel → sıra önemsiz) + kirli bant. for (unsigned t = 0; t < T; ++t) { kaldirilanHacim_ += yerelHac[t]; if (undoYaz && !yerelUndo[t].empty()) undoLog_.insert(undoLog_.end(), yerelUndo[t].begin(), yerelUndo[t].end()); if (kMaxT[t] >= kMinT[t] && kMinT[t] < nz_) bantlariKirlet(kMinT[t], kMaxT[t]); } if (undoYaz && undoLog_.size() > undoTavan_) undoTasti_ = true; } // --------------------------------------------------------------------------- // noktaIcerideMi — SDF trilineer ornekleme (negatif = ici) // --------------------------------------------------------------------------- bool SdfMotoru::noktaIcerideMi(Vec3d p) const { if (!kuruldu_ || nx_ == 0 || !doldu_ || !sonluMu(p)) return false; // En-yakin voxel merkez (basit; interp gerekli degil isaret icin). const f64 fi = (p.x - sinir_.asgari.x) / h_ - 0.5; const f64 fj = (p.y - sinir_.asgari.y) / h_ - 0.5; const f64 fk = (p.z - sinir_.asgari.z) / h_ - 0.5; const int i = static_cast(std::lround(fi)); const int j = static_cast(std::lround(fj)); const int k = static_cast(std::lround(fk)); if (i < 0 || j < 0 || k < 0 || i >= static_cast(nx_) || j >= static_cast(ny_) || k >= static_cast(nz_)) return false; return alan_[idx(static_cast(i), static_cast(j), static_cast(k))] < 0.0f; } // --------------------------------------------------------------------------- // meshCikar — Marching Cubes (izo=0, gercek-mesafe interp CLAMP'li + normal) // ---------------------------------------------------------------------------- // WEB HATA 1 DUZELTME: interp t = clamp((iso-v1)/(v2-v1),0,1) + |v1-v2| Vec3d { auto S = [&](int a, int b, int c) -> f32 { a = std::clamp(a, 0, static_cast(nx_) - 1); b = std::clamp(b, 0, static_cast(ny_) - 1); c = std::clamp(c, 0, static_cast(nz_) - 1); return alan_[idx(static_cast(a), static_cast(b), static_cast(c))]; }; Vec3d g{static_cast(S(i + 1, j, k) - S(i - 1, j, k)), static_cast(S(i, j + 1, k) - S(i, j - 1, k)), static_cast(S(i, j, k + 1) - S(i, j, k - 1))}; const f64 u2 = g.uzunlukKare(); if (u2 < 1e-20) return {0, 0, 1}; return g / std::sqrt(u2); }; // Kenar interp (CLAMP'li gercek-mesafe): kenar iki kose degeri v1,v2 arasi. auto interp = [&](const Vec3d& p1, const Vec3d& p2, f32 v1, f32 v2) -> Vec3d { const f32 fark = v2 - v1; f32 t; if (std::fabs(fark) < 1e-6f) { t = 0.5f; // |v1-v2| orta nokta } else { t = (iso - v1) / fark; t = std::clamp(t, 0.0f, 1.0f); // WEB HATA 1: clamp EKLENDI } return p1 + (p2 - p1) * static_cast(t); }; const Vec3d o{sinir_.asgari.x, sinir_.asgari.y, sinir_.asgari.z}; auto ekle = [&](const Vec3d& p, const Vec3d& n) -> u32 { const u32 vi = cikti.vertexSayi(); cikti.pozisyon.push_back(static_cast(p.x)); cikti.pozisyon.push_back(static_cast(p.y)); cikti.pozisyon.push_back(static_cast(p.z)); cikti.normal.push_back(static_cast(n.x)); cikti.normal.push_back(static_cast(n.y)); cikti.normal.push_back(static_cast(n.z)); cikti.sinir.genislet( {static_cast(p.x), static_cast(p.y), static_cast(p.z)}); return vi; }; // Kup taramasi: k-bandi [k0,k1) (cell k; i/j tam eksen). Paralel bant yardimcisi. for (u32 k = k0; k < k1; ++k) { for (u32 j = 0; j + 1 < ny_; ++j) { for (u32 i = 0; i + 1 < nx_; ++i) { // 8 kose SDF degeri + dunya konumu. f32 deg[8]; Vec3d koz[8]; int idx8[8][3]; u32 kod = 0; for (int c = 0; c < 8; ++c) { const int ci = static_cast(i) + mc::kKoseOfset[c][0]; const int cj = static_cast(j) + mc::kKoseOfset[c][1]; const int ck = static_cast(k) + mc::kKoseOfset[c][2]; idx8[c][0] = ci; idx8[c][1] = cj; idx8[c][2] = ck; deg[c] = alan_[idx(static_cast(ci), static_cast(cj), static_cast(ck))]; koz[c] = {o.x + (ci + 0.5) * h_, o.y + (cj + 0.5) * h_, o.z + (ck + 0.5) * h_}; if (deg[c] < iso) kod |= (1u << c); // ic = negatif } const int maske = mc::kKenarMaske[kod]; if (maske == 0) continue; // tam ici veya tam disi -> ucgen yok // Her kesilen kenar icin vertex uret (12 kenar). Vec3d kenarP[12]; Vec3d kenarN[12]; for (int e = 0; e < 12; ++e) { if (!(maske & (1 << e))) continue; const int a = mc::kKenarKose[e][0], b = mc::kKenarKose[e][1]; kenarP[e] = interp(koz[a], koz[b], deg[a], deg[b]); // Normal: iki kose gradyani interp (basit ortalama yerine ayni t). const Vec3d na = grad(idx8[a][0], idx8[a][1], idx8[a][2]); const Vec3d nb = grad(idx8[b][0], idx8[b][1], idx8[b][2]); Vec3d n = (na + nb) * 0.5; const f64 u2 = n.uzunlukKare(); kenarN[e] = (u2 < 1e-20) ? Vec3d{0, 0, 1} : n / std::sqrt(u2); } // Ucgenler. const int* tri = mc::kUcgen[kod]; for (int t = 0; tri[t] != -1; t += 3) { const int e0 = tri[t], e1 = tri[t + 1], e2 = tri[t + 2]; const u32 v0 = ekle(kenarP[e0], kenarN[e0]); const u32 v1 = ekle(kenarP[e1], kenarN[e1]); const u32 v2 = ekle(kenarP[e2], kenarN[e2]); // Sarım TERS'ti (ÖLÇÜLDÜ 07-19 kutuk_disari.obj: sarım↔normal uyum %0, // 251389/251391 üçgen ters → arka-yüz kültürü iç-dışı gösteriyordu). MC // tablosu sarımı, gradyan normaliyle (dışa) ters çıkıyor → v1↔v2 çevir. cikti.indeks.push_back(v0); cikti.indeks.push_back(v2); cikti.indeks.push_back(v1); } } } } } // --------------------------------------------------------------------------- // meshCikar / meshCikarPar — Marching Cubes (seri sarmalayici + paralel bant) // --------------------------------------------------------------------------- void SdfMotoru::meshCikar(MeshVeri& cikti) { if (keskin_) meshCikarKeskin(cikti); // Dual Contouring (keskin, cache'siz tam grid) else meshCikarPar(cikti, 0); // Marching Cubes (incremental cached-slab, hızlı) } void SdfMotoru::meshCikarPar(MeshVeri& cikti, unsigned isSayisi) { cikti.temizle(); if (!kuruldu_ || nx_ < 2 || ny_ < 2 || nz_ < 2 || !doldu_) return; const u32 kSon = nz_ - 1; // MC hucre araligi [0,kSon) bantlariGarantile(); const u32 nb = (u32)bantMesh_.size(); if (nb == 0) return; // KIRLI bantlari topla — YALNIZ onlar yeniden MC'lenir (digerleri cache'ten). // Carve yalnız takım cevresini degistirdiginden mesh maliyeti O(grid)→O(kirli). std::vector kirli; for (u32 b = 0; b < nb; ++b) if (bantKirli_[b]) kirli.push_back(b); auto bantMcle = [&](u32 b) { const u32 a = b * bantYuk_; const u32 c = std::min(kSon, a + bantYuk_); bantMesh_[b].temizle(); if (a < c) mcBant(a, c, bantMesh_[b]); bantKirli_[b] = 0; }; // isSayisi==1 → seri (test/ölçüm); aksi + havuz → kirli bantlar paralel (iş-çalma; // bantlar disjoint → yarışsız). Her bant bir görev; havuz worker'lara dağıtır. const bool paralel = (isSayisi != 1) && havuz_ && kirli.size() > 1; if (!paralel) { for (u32 b : kirli) bantMcle(b); } else { havuz_->kos([&](unsigned i) { bantMcle(kirli[(size_t)i]); }, (unsigned)kirli.size()); } // Tum bantlari k-SIRASINDA birlestir (cache + taze) → cikti. Indeks = yerel + // o ana kadarki toplam vertex (offset). Sonuc full-MC ile BIREBIR ayni. u32 topV = 0, topI = 0; for (const MeshVeri& p : bantMesh_) { topV += (u32)(p.pozisyon.size() / 3); topI += (u32)p.indeks.size(); } cikti.pozisyon.reserve(topV * 3); cikti.normal.reserve(topV * 3); cikti.indeks.reserve(topI); u32 ofset = 0; for (const MeshVeri& p : bantMesh_) { cikti.pozisyon.insert(cikti.pozisyon.end(), p.pozisyon.begin(), p.pozisyon.end()); cikti.normal.insert(cikti.normal.end(), p.normal.begin(), p.normal.end()); for (u32 id : p.indeks) cikti.indeks.push_back(id + ofset); if (p.sinir.gecerliMi()) { cikti.sinir.genislet(p.sinir.asgari); cikti.sinir.genislet(p.sinir.azami); } ofset += (u32)(p.pozisyon.size() / 3); } } // --------------------------------------------------------------------------- // Dual Contouring — KESKIN kenar/kose korur (MC yuvarlar) // ---------------------------------------------------------------------------- // Her hucre icin: kesilen kenarlarda (SDF isaret degisimi) crossing + normal // (Hermite). Hucre vertex'i QEF min: Σ(nᵉ·(v−pᵉ))² (mass-point'e regularize + // hucre kutusuna kirp). Sonra her kesilen primal kenar icin cevredeki 4 hucre // vertex'ini QUAD bagla (dual). Duz yuzde v ortada, keskin kenar/kose'de QEF // oturur → keskinlik korunur. Cache'siz tam grid (kalite gorunumu). // --------------------------------------------------------------------------- namespace { // 3x3 simetrik sistem (A benzersiz 6 girdi: 00,01,02,11,12,22) çöz → x. det~0 red. bool coz3x3(const f64 A[6], const f64 b[3], f64 x[3]) { const f64 a00 = A[0], a01 = A[1], a02 = A[2], a11 = A[3], a12 = A[4], a22 = A[5]; const f64 c00 = a11 * a22 - a12 * a12; const f64 c01 = a02 * a12 - a01 * a22; const f64 c02 = a01 * a12 - a02 * a11; const f64 det = a00 * c00 + a01 * c01 + a02 * c02; if (std::fabs(det) < 1e-10) return false; const f64 c11 = a00 * a22 - a02 * a02; const f64 c12 = a02 * a01 - a00 * a12; const f64 c22 = a00 * a11 - a01 * a01; const f64 inv = 1.0 / det; x[0] = inv * (c00 * b[0] + c01 * b[1] + c02 * b[2]); x[1] = inv * (c01 * b[0] + c11 * b[1] + c12 * b[2]); x[2] = inv * (c02 * b[0] + c12 * b[1] + c22 * b[2]); return true; } } // namespace void SdfMotoru::meshCikarKeskin(MeshVeri& cikti) const { cikti.temizle(); if (!kuruldu_ || nx_ < 2 || ny_ < 2 || nz_ < 2 || !doldu_) return; const u32 cx = nx_ - 1, cy = ny_ - 1, cz = nz_ - 1; // hücre sayıları const f32 iso = 0.0f; const Vec3d o{sinir_.asgari.x, sinir_.asgari.y, sinir_.asgari.z}; auto S = [&](int a, int b, int c) -> f32 { a = std::clamp(a, 0, (int)nx_ - 1); b = std::clamp(b, 0, (int)ny_ - 1); c = std::clamp(c, 0, (int)nz_ - 1); return alan_[idx((u32)a, (u32)b, (u32)c)]; }; const bool normVar = !norm_.empty(); // analitik Hermite normalleri var mı (keskin) auto grad = [&](int i, int j, int k) -> Vec3d { Vec3d g{(f64)(S(i + 1, j, k) - S(i - 1, j, k)), (f64)(S(i, j + 1, k) - S(i, j - 1, k)), (f64)(S(i, j, k + 1) - S(i, j, k - 1))}; const f64 u2 = g.uzunlukKare(); return (u2 < 1e-20) ? Vec3d{0, 0, 1} : g / std::sqrt(u2); }; // Köşe (voxel) normali: analitik varsa oct-çöz (CSG kenarında keskin), yoksa grad. auto nrm = [&](int i, int j, int k) -> Vec3d { if (normVar && i >= 0 && j >= 0 && k >= 0 && i < (int)nx_ && j < (int)ny_ && k < (int)nz_) return octCoz(norm_[idx((u32)i, (u32)j, (u32)k)]); return grad(i, j, k); }; auto vpos = [&](u32 i, u32 j, u32 k) -> Vec3d { return {o.x + (i + 0.5) * h_, o.y + (j + 0.5) * h_, o.z + (k + 0.5) * h_}; }; // Hücre vertex indeksi (-1 = yok). cx*cy*cz int (opsiyonel keskin yol; kabul). std::vector hucreV((size_t)cx * cy * cz, -1); auto hidx = [&](u32 i, u32 j, u32 k) { return ((size_t)k * cy + j) * cx + i; }; // Pass 1: hücre başına QEF vertex. for (u32 k = 0; k < cz; ++k) for (u32 j = 0; j < cy; ++j) for (u32 i = 0; i < cx; ++i) { f32 d[8]; u32 kod = 0; for (int c = 0; c < 8; ++c) { d[c] = alan_[idx(i + mc::kKoseOfset[c][0], j + mc::kKoseOfset[c][1], k + mc::kKoseOfset[c][2])]; if (d[c] < iso) kod |= (1u << c); } if (kod == 0 || kod == 0xFF) continue; // tam içi/dışı → kenar yok f64 A[6] = {0, 0, 0, 0, 0, 0}, bb[3] = {0, 0, 0}; Vec3d mass{0, 0, 0}; int cnt = 0; for (int e = 0; e < 12; ++e) { const int a = mc::kKenarKose[e][0], b = mc::kKenarKose[e][1]; const bool ina = d[a] < iso, inb = d[b] < iso; if (ina == inb) continue; f64 t = (f64)(iso - d[a]) / (f64)(d[b] - d[a]); t = std::clamp(t, 0.0, 1.0); const Vec3d pa = vpos(i + mc::kKoseOfset[a][0], j + mc::kKoseOfset[a][1], k + mc::kKoseOfset[a][2]); const Vec3d pb = vpos(i + mc::kKoseOfset[b][0], j + mc::kKoseOfset[b][1], k + mc::kKoseOfset[b][2]); const Vec3d pe = pa + (pb - pa) * t; const Vec3d na = nrm((int)i + mc::kKoseOfset[a][0], (int)j + mc::kKoseOfset[a][1], (int)k + mc::kKoseOfset[a][2]); const Vec3d nb = nrm((int)i + mc::kKoseOfset[b][0], (int)j + mc::kKoseOfset[b][1], (int)k + mc::kKoseOfset[b][2]); Vec3d ne = na + (nb - na) * t; const f64 nl = ne.uzunlukKare(); ne = (nl < 1e-20) ? Vec3d{0, 0, 1} : ne / std::sqrt(nl); A[0] += ne.x * ne.x; A[1] += ne.x * ne.y; A[2] += ne.x * ne.z; A[3] += ne.y * ne.y; A[4] += ne.y * ne.z; A[5] += ne.z * ne.z; const f64 dp = ne.nokta(pe); bb[0] += ne.x * dp; bb[1] += ne.y * dp; bb[2] += ne.z * dp; mass += pe; ++cnt; } if (cnt == 0) continue; mass = mass / (f64)cnt; // Mass-point'e regularize (düz yüz/az kısıtta kararlı; keskinde QEF baskın). const f64 lam = 0.06; A[0] += lam; A[3] += lam; A[5] += lam; bb[0] += lam * mass.x; bb[1] += lam * mass.y; bb[2] += lam * mass.z; f64 x[3]; Vec3d v = coz3x3(A, bb, x) ? Vec3d{x[0], x[1], x[2]} : mass; // Hücre kutusuna kırp (QEF taşmasını önle). const Vec3d cmn = vpos(i, j, k); const Vec3d cmx = vpos(i + 1, j + 1, k + 1); v.x = std::clamp(v.x, cmn.x, cmx.x); v.y = std::clamp(v.y, cmn.y, cmx.y); v.z = std::clamp(v.z, cmn.z, cmx.z); Vec3d nv{0, 0, 0}; for (int c = 0; c < 8; ++c) nv += nrm((int)i + mc::kKoseOfset[c][0], (int)j + mc::kKoseOfset[c][1], (int)k + mc::kKoseOfset[c][2]); const f64 nvl = nv.uzunlukKare(); nv = (nvl < 1e-20) ? Vec3d{0, 0, 1} : nv / std::sqrt(nvl); hucreV[hidx(i, j, k)] = (int)(cikti.pozisyon.size() / 3); cikti.pozisyon.push_back((f32)v.x); cikti.pozisyon.push_back((f32)v.y); cikti.pozisyon.push_back((f32)v.z); cikti.normal.push_back((f32)nv.x); cikti.normal.push_back((f32)nv.y); cikti.normal.push_back((f32)nv.z); cikti.sinir.genislet({(f32)v.x, (f32)v.y, (f32)v.z}); } // Pass 2: her kesilen primal kenar → cevredeki 4 hücre vertex'i QUAD (2 üçgen). auto quad = [&](int a, int b, int c, int e, bool ters) { if (a < 0 || b < 0 || c < 0 || e < 0) return; // eksik hücre → atla (sınır) // İki üçgen: (a,b,c)+(a,c,e). ters ise sarımı çevir. (Winding S13 işaretli-hacim // testiyle kilitli — dışa sarım POZİTİF hacim verir.) if (ters) { cikti.indeks.push_back((u32)a); cikti.indeks.push_back((u32)b); cikti.indeks.push_back((u32)c); cikti.indeks.push_back((u32)a); cikti.indeks.push_back((u32)c); cikti.indeks.push_back((u32)e); } else { cikti.indeks.push_back((u32)a); cikti.indeks.push_back((u32)c); cikti.indeks.push_back((u32)b); cikti.indeks.push_back((u32)a); cikti.indeks.push_back((u32)e); cikti.indeks.push_back((u32)c); } }; auto V = [&](u32 i, u32 j, u32 k) { return hucreV[hidx(i, j, k)]; }; // X-kenar (i,j,k)->(i+1,j,k): 4 hücre cj∈{j-1,j}, ck∈{k-1,k}. j,k ∈ [1,cy]/[1,cz]. for (u32 k = 1; k < cz; ++k) for (u32 j = 1; j < cy; ++j) for (u32 i = 0; i < cx; ++i) { const bool s0 = alan_[idx(i, j, k)] < iso, s1 = alan_[idx(i + 1, j, k)] < iso; if (s0 == s1) continue; quad(V(i, j - 1, k - 1), V(i, j, k - 1), V(i, j, k), V(i, j - 1, k), s0); } // Y-kenar (i,j,k)->(i,j+1,k): 4 hücre ci∈{i-1,i}, ck∈{k-1,k}. for (u32 k = 1; k < cz; ++k) for (u32 j = 0; j < cy; ++j) for (u32 i = 1; i < cx; ++i) { const bool s0 = alan_[idx(i, j, k)] < iso, s1 = alan_[idx(i, j + 1, k)] < iso; if (s0 == s1) continue; quad(V(i - 1, j, k - 1), V(i, j, k - 1), V(i, j, k), V(i - 1, j, k), !s0); } // Z-kenar (i,j,k)->(i,j,k+1): 4 hücre ci∈{i-1,i}, cj∈{j-1,j}. for (u32 k = 0; k < cz; ++k) for (u32 j = 1; j < cy; ++j) for (u32 i = 1; i < cx; ++i) { const bool s0 = alan_[idx(i, j, k)] < iso, s1 = alan_[idx(i, j, k + 1)] < iso; if (s0 == s1) continue; quad(V(i - 1, j - 1, k), V(i, j - 1, k), V(i, j, k), V(i - 1, j, k), s0); } } } // namespace fiko::kutuk