#include #include #include "fiko/kutuk/dexel3_motor.hpp" #include "fiko/kutuk/kutuk_motor.hpp" #include "fiko/kutuk/sdf_motor.hpp" #include "fiko_test.hpp" using namespace fiko; using namespace fiko::kutuk; namespace { // 40x40x20 kutu kutuk, koki (0,0,0). Aabbf kutuSinir() { Aabbf s; s.asgari = {0, 0, 0}; s.azami = {40, 40, 20}; return s; } cam::Stok kutuStok() { cam::Stok s; s.tip = cam::StokTip::Kutu; s.boyut = {40, 40, 20}; return s; } TakimGeo duzTakim(f64 cap) { TakimGeo t; t.tip = TakimGeo::Tip::Duz; t.cap = cap; t.boy = 40; t.eksen = {0, 0, 1}; return t; } TakimGeo ballTakim(f64 cap) { TakimGeo t; t.tip = TakimGeo::Tip::Kuresel; t.cap = cap; t.koseR = cap * 0.5; t.boy = 40; t.eksen = {0, 0, 1}; return t; } } // namespace // ============================================================================ // SDF+MC (DALGA 4) testleri — CPU referans motoru // ============================================================================ // (S1) Kup stok hacmi: SDF isareti dogru (ic negatif, dis pozitif). FIKO_TEST(sdf_kup_stok_hacim) { SdfMotoru m; FIKO_KONTROL(m.kutukKur(kutuStok(), kutuSinir(), 1.0)); FIKO_KONTROL(m.doldu()); // Merkez ici, disari disi (noktaIcerideMi = SDF negatif). FIKO_KONTROL(m.noktaIcerideMi({20, 20, 10})); FIKO_KONTROL(!m.noktaIcerideMi({50, 20, 10})); FIKO_KONTROL(!m.noktaIcerideMi({20, 20, 30})); // SDF alani hicbir voxel NaN olmamali (hata 4: POSITIVE_INFINITY/NaN yok). for (f32 v : m.alan()) FIKO_KONTROL(std::isfinite(v)); } // (S2) Ball takim dalis hacmi: kuresel kapak analitik kiyas %5. // Ball uc konvansiyonu: poz = takim ucu (kurenin en alt noktasi), kure merkezi // poz+R*eksen. Uc z=15 -> kure merkezi z=20 (ust yuzey). Kutuk (z<=20) icine // kurenin alt yarisi girer -> kaldirilan ~ yarim kure hacmi. FIKO_TEST(sdf_ball_dalis_hacim) { SdfMotoru m; m.kutukKur(kutuStok(), kutuSinir(), 0.4); const f64 cap = 10.0; const f64 R = cap * 0.5; TakimGeo t = ballTakim(cap); t.boy = 40; m.takimUygula({20, 20, 20.0 - R}, t); // uc z=15 -> kure merkezi z=20 const f64 yarimKure = (2.0 / 3.0) * 3.14159265358979 * R * R * R; const f64 hac = m.kaldirilanHacimMm3(); FIKO_YAKIN(hac, yarimKure, yarimKure * 0.05); } // (S3) MC yuzey kapaliligi: basit kup SDF -> ucgen>0 + NaN yok + vertex sinir ici. FIKO_TEST(sdf_mc_yuzey_kapali) { SdfMotoru m; m.kutukKur(kutuStok(), kutuSinir(), 1.0); MeshVeri mesh; m.meshCikar(mesh); FIKO_KONTROL(mesh.vertexSayi() > 0); FIKO_KONTROL(mesh.ucgenSayi() > 0); // Tum vertexler sonlu + kutuk siniri icinde (kucuk pay ile). const Aabbf s = kutuSinir(); for (size_t i = 0; i < mesh.pozisyon.size(); i += 3) { const f32 x = mesh.pozisyon[i], y = mesh.pozisyon[i + 1], z = mesh.pozisyon[i + 2]; FIKO_KONTROL(std::isfinite(x) && std::isfinite(y) && std::isfinite(z)); FIKO_KONTROL(x >= s.asgari.x - 2 && x <= s.azami.x + 2); FIKO_KONTROL(y >= s.asgari.y - 2 && y <= s.azami.y + 2); FIKO_KONTROL(z >= s.asgari.z - 2 && z <= s.azami.z + 2); } // Normaller birim (yaklasik) + sonlu. for (size_t i = 0; i < mesh.normal.size(); i += 3) { const f32 nx = mesh.normal[i], ny = mesh.normal[i + 1], nz = mesh.normal[i + 2]; FIKO_KONTROL(std::isfinite(nx) && std::isfinite(ny) && std::isfinite(nz)); const f32 uz = std::sqrt(nx * nx + ny * ny + nz * nz); FIKO_KONTROL(uz > 0.5f && uz < 1.5f); } // Bbox sonlu (NaN bbox gotcha yok). FIKO_KONTROL(std::isfinite(mesh.sinir.asgari.x) && std::isfinite(mesh.sinir.azami.z)); } // (S4) interp clamp regresyonu: uc-degerli komsu (buyuk fark) -> t clamp'lenir, // vertex hep iki kose ARASINDA kalir (asiri interp NaN/tasma yok). FIKO_TEST(sdf_interp_clamp) { // Kucuk grid, tek kesim -> keskin gradyan olustur. SdfMotoru m; m.kutukKur(kutuStok(), kutuSinir(), 2.0); TakimGeo t = duzTakim(6.0); t.boy = 40; m.takimUygula({20, 20, 0}, t); // merkez delik -> keskin SDF gecisi MeshVeri mesh; m.meshCikar(mesh); FIKO_KONTROL(mesh.vertexSayi() > 0); // clamp calisiyorsa hicbir vertex grid siniri disina firlamaz (t in [0,1]). const Aabbf s = kutuSinir(); for (size_t i = 0; i < mesh.pozisyon.size(); i += 3) { const f32 x = mesh.pozisyon[i], y = mesh.pozisyon[i + 1], z = mesh.pozisyon[i + 2]; FIKO_KONTROL(std::isfinite(x) && std::isfinite(y) && std::isfinite(z)); // Komsu voxel merkezleri arasi kalmali -> sinir + 1 hucre pay. FIKO_KONTROL(x >= s.asgari.x - 3 && x <= s.azami.x + 3); FIKO_KONTROL(z >= s.asgari.z - 3 && z <= s.azami.z + 3); } } // (S5) Gecersiz girdi + bos kutuk dayanikliligi (ters bbox red, NaN cokmez, // bos kutuk MC calismaz = bos mesh, NaN bbox yok). "Kapasite" CPU'da vector // otomatik buyur (OOB yok) — GPU kapasite guard shader-taraflidir. FIKO_TEST(sdf_gecersiz_ve_bos) { SdfMotoru m; Aabbf ters; ters.asgari = {40, 40, 20}; ters.azami = {0, 0, 0}; FIKO_KONTROL(!m.kutukKur(kutuStok(), ters, 1.0)); FIKO_KONTROL(!m.sonHata().empty()); FIKO_KONTROL(m.kutukKur(kutuStok(), kutuSinir(), 2.0)); TakimGeo t = duzTakim(8.0); t.eksen = {0, 0, 0}; // sifir eksen -> (0,0,1) fallback m.takimUygula({20, 20, 20}, t); const f64 nan = std::numeric_limits::quiet_NaN(); m.takimUygula({nan, 20, 20}, t); // NaN poz -> atla m.sweptUygula({nan, 0, 0}, {10, 10, 10}, t); // NaN uc -> atla FIKO_KONTROL(std::isfinite(m.kaldirilanHacimMm3())); // Bos/dejenerre kutuk -> doldu()=false, bos mesh, NaN bbox ASLA. SdfMotoru bos; Aabbf dej; dej.asgari = {0, 0, 0}; dej.azami = {0, 0, 0}; FIKO_KONTROL(bos.kutukKur(kutuStok(), dej, 1.0)); // basari ama bos FIKO_KONTROL(!bos.doldu()); MeshVeri bosMesh; bos.meshCikar(bosMesh); FIKO_KONTROL(bosMesh.vertexSayi() == 0); FIKO_KONTROL(!bos.noktaIcerideMi({0, 0, 0})); } // (S6) Dexel3 <-> SDF capraz hacim: ayni dalis, ±%10 dogruluk. FIKO_TEST(sdf_dexel_capraz) { const f64 cap = 8.0; TakimGeo t = duzTakim(cap); t.boy = 40; Dexel3Motoru d; d.kutukKur(kutuStok(), kutuSinir(), 0.5); d.takimUygula({20, 20, 0}, t); const f64 hd = d.kaldirilanHacimMm3(); SdfMotoru s; s.kutukKur(kutuStok(), kutuSinir(), 0.5); s.takimUygula({20, 20, 0}, t); const f64 hs = s.kaldirilanHacimMm3(); FIKO_KONTROL(hd > 0 && hs > 0); FIKO_YAKIN(hs, hd, hd * 0.10); } // (S7) Fabrika: SDF_MC artik gecerli (arayuz hazir, kip dogru). FIKO_TEST(sdf_fabrika_kip) { auto s = kutukMotoruYap(KutukSimKip::SDF_MC); FIKO_KONTROL(s != nullptr); FIKO_KONTROL(s->kip() == KutukSimKip::SDF_MC); } // (S8) Paralel MC = seri MC (BIREBIR): meshCikarPar(1) ile meshCikarPar(8) ayni // vertex/normal/indeks uretmeli. Gercek-zaman icin MC k-bantlara bolundu; k-sira // korundugu icin cikti degismemeli (regresyon kilidi: paralel bozma yakalanir). FIKO_TEST(sdf_paralel_mc_seri_ile_ayni) { // AYRI motorlar (cache tek motorda ikinci cagriyi bypass ederdi) → gercek seri↔paralel. auto carve = [](SdfMotoru& m) { m.kutukKur(kutuStok(), kutuSinir(), 1.0); TakimGeo t = duzTakim(6.0); t.boy = 40; m.sweptUygula({8, 20, 8}, {32, 20, 8}, t); m.sweptUygula({20, 8, 12}, {20, 32, 12}, t); }; SdfMotoru ms, mp; carve(ms); carve(mp); MeshVeri seri, paralel; ms.meshCikarPar(seri, 1); mp.meshCikarPar(paralel, 8); FIKO_KONTROL(seri.vertexSayi() > 0); FIKO_KONTROL(seri.pozisyon.size() == paralel.pozisyon.size()); FIKO_KONTROL(seri.normal.size() == paralel.normal.size()); FIKO_KONTROL(seri.indeks.size() == paralel.indeks.size()); for (size_t i = 0; i < seri.pozisyon.size(); ++i) FIKO_KONTROL(seri.pozisyon[i] == paralel.pozisyon[i]); for (size_t i = 0; i < seri.normal.size(); ++i) FIKO_KONTROL(seri.normal[i] == paralel.normal[i]); for (size_t i = 0; i < seri.indeks.size(); ++i) FIKO_KONTROL(seri.indeks[i] == paralel.indeks[i]); // Bbox da ayni (birlesim k-bant genisletmesi = seri genisletme). FIKO_KONTROL(seri.sinir.asgari.x == paralel.sinir.asgari.x); FIKO_KONTROL(seri.sinir.azami.z == paralel.sinir.azami.z); } // (S9) Checkpoint roundtrip (geri-scrub temeli): kaydet → daha oy → geri yukle → // hacim + alan durumu kayit anina DONMELI (0'dan yeniden oymadan geri-scrub). FIKO_TEST(sdf_checkpoint_roundtrip) { SdfMotoru m; m.kutukKur(kutuStok(), kutuSinir(), 1.0); FIKO_KONTROL(m.durumBayt() > 0); // SDF checkpoint destekler TakimGeo t = duzTakim(6.0); t.boy = 40; m.takimUygula({15, 20, 0}, t); const f64 hac1 = m.kaldirilanHacimMm3(); auto snap = m.durumKaydet(); // 1. kesimden SONRAKI durum FIKO_KONTROL(snap != nullptr); m.takimUygula({25, 20, 0}, t); // 2. kesim → hacim artar const f64 hac2 = m.kaldirilanHacimMm3(); FIKO_KONTROL(hac2 > hac1); FIKO_KONTROL(m.durumYukle(snap)); // geri yukle → 1. kesim durumu FIKO_YAKIN(m.kaldirilanHacimMm3(), hac1, 1e-6); // 2. kesim bolgesi geri dolmali (25,20 artik malzeme ici — 1. kesimde deginilmedi). FIKO_KONTROL(m.noktaIcerideMi({25, 20, 15})); // Mesh de kayit anina donmeli (2. kesimden sonraki mesh'ten FARKLI vertex sayisi olabilir). MeshVeri geri; m.meshCikar(geri); FIKO_KONTROL(geri.vertexSayi() > 0); } // (S10) Cached-slab incremental mesh = fresh full mesh: artımlı carve+mesh (kirli // bant cache) sonucu, aynı carve'i tek seferde yapıp meshleyen TAZE motorla BİREBİR. // Dirty-band takibi/cache birleştirmesi doğru mu (regresyon kilidi). FIKO_TEST(sdf_incremental_mesh_fresh_ile_ayni) { TakimGeo t = duzTakim(6.0); t.boy = 40; // Artımlı: carve → mesh → carve → mesh (cache devreye girer, kirli bant yeniden MC). SdfMotoru inc; inc.kutukKur(kutuStok(), kutuSinir(), 1.0); inc.sweptUygula({8, 20, 8}, {32, 20, 8}, t); MeshVeri ara; inc.meshCikar(ara); // 1. mesh — bir kısım bant kirli inc.sweptUygula({20, 8, 14}, {20, 32, 14}, t); MeshVeri artimli; inc.meshCikar(artimli); // 2. mesh — yalnız yeni kirli bantlar yeniden MC + cache birleşir // Taze: aynı iki carve tek motorda, sonra tek mesh (tüm bantlar bir kez). SdfMotoru taze; taze.kutukKur(kutuStok(), kutuSinir(), 1.0); taze.sweptUygula({8, 20, 8}, {32, 20, 8}, t); taze.sweptUygula({20, 8, 14}, {20, 32, 14}, t); MeshVeri tam; taze.meshCikar(tam); FIKO_KONTROL(artimli.pozisyon.size() == tam.pozisyon.size()); FIKO_KONTROL(artimli.indeks.size() == tam.indeks.size()); for (size_t i = 0; i < artimli.pozisyon.size(); ++i) FIKO_KONTROL(artimli.pozisyon[i] == tam.pozisyon[i]); for (size_t i = 0; i < artimli.indeks.size(); ++i) FIKO_KONTROL(artimli.indeks[i] == tam.indeks[i]); } // (S11) Undo jurnali = checkpoint (ince geri-scrub ANINDA): carve → token al → daha // oy → undoGeri(token) → durum + mesh token anıyla BİREBİR (0'dan oymadan geri-scrub). FIKO_TEST(sdf_undo_geri_scrub) { SdfMotoru m; m.kutukKur(kutuStok(), kutuSinir(), 1.0); FIKO_KONTROL(m.undoDestekli()); TakimGeo t = duzTakim(6.0); t.boy = 40; m.sweptUygula({8, 20, 8}, {32, 20, 8}, t); const f64 hac1 = m.kaldirilanHacimMm3(); const u64 token = m.undoIsaret(); // 1. kesim SONRASI jurnal konumu MeshVeri mesh1; m.meshCikar(mesh1); m.sweptUygula({20, 8, 14}, {20, 32, 14}, t); // 2. kesim → jurnal büyür, hacim artar FIKO_KONTROL(m.kaldirilanHacimMm3() > hac1); FIKO_KONTROL(m.undoIsaret() > token); FIKO_KONTROL(m.undoGeri(token)); // token'a geri al (aninda) FIKO_YAKIN(m.kaldirilanHacimMm3(), hac1, 1e-6); // 2. kesim bölgesi geri dolmalı (14 yüksekliğinde malzeme geri geldi). FIKO_KONTROL(m.noktaIcerideMi({20, 8, 14})); // Mesh de 1. kesim anıyla birebir (cache dirty doğru güncellendi mi). MeshVeri mesh2; m.meshCikar(mesh2); FIKO_KONTROL(mesh1.pozisyon.size() == mesh2.pozisyon.size()); for (size_t i = 0; i < mesh1.pozisyon.size(); ++i) FIKO_KONTROL(mesh1.pozisyon[i] == mesh2.pozisyon[i]); } // (S12) Paralel batchUygula = seri per-hamle (BİREBİR): union commutativity ile // k-slab paralel carve, seri sweptUygula/takimUygula ile aynı grid + hacim + mesh. // İleri oyma paralel hızlandırmasının doğruluk kilidi. FIKO_TEST(sdf_batch_seri_ile_ayni) { TakimGeo t = duzTakim(6.0); t.boy = 40; // Bir yol yığını (ilk = nokta damgası, sonrası swept). std::vector hamleler; auto ekle = [&](Vec3d a, Vec3d b, bool ilk) { KutukSimMotoru::KarveHamle h; h.a = a; h.b = b; h.takim = t; h.ilk = ilk; h.kesme = true; hamleler.push_back(h); }; ekle({10, 10, 6}, {10, 10, 6}, true); ekle({10, 10, 6}, {30, 10, 6}, false); ekle({30, 10, 6}, {30, 30, 10}, false); ekle({30, 30, 10}, {10, 30, 10}, false); ekle({10, 30, 10}, {10, 10, 6}, false); // Seri: per-hamle. SdfMotoru ser; ser.kutukKur(kutuStok(), kutuSinir(), 1.0); for (const auto& h : hamleler) { if (h.ilk) ser.takimUygula(h.b, h.takim); else ser.sweptUygula(h.a, h.b, h.takim); } // Paralel: batchUygula. SdfMotoru bat; bat.kutukKur(kutuStok(), kutuSinir(), 1.0); bat.batchUygula(hamleler.data(), hamleler.size()); // Hacim birebir + alan birebir + mesh birebir. FIKO_YAKIN(bat.kaldirilanHacimMm3(), ser.kaldirilanHacimMm3(), 1e-6); FIKO_KONTROL(bat.alan().size() == ser.alan().size()); for (size_t i = 0; i < ser.alan().size(); ++i) FIKO_KONTROL(bat.alan()[i] == ser.alan()[i]); MeshVeri ms, mb; ser.meshCikar(ms); bat.meshCikar(mb); FIKO_KONTROL(ms.pozisyon.size() == mb.pozisyon.size()); for (size_t i = 0; i < ms.pozisyon.size(); ++i) FIKO_KONTROL(ms.pozisyon[i] == mb.pozisyon[i]); } // (S13) Dual Contouring (keskin) temel doğruluk: kapalı mesh + DIŞA sarım (işaretli // hacim POZİTİF + kutu hacmine yakın). Sarım yanlışsa hacim negatif çıkar (winding // kilidi). Keskinlik görsel (runtime); burada topoloji/yön doğrulanır. FIKO_TEST(sdf_dc_keskin_kapali_ve_yon) { SdfMotoru m; m.keskinAta(true); // kur'dan ÖNCE → norm_ (analitik Hermite) ayrılır m.kutukKur(kutuStok(), kutuSinir(), 1.0); TakimGeo t = duzTakim(8.0); t.boy = 40; m.sweptUygula({12, 20, 6}, {28, 20, 6}, t); // carve → analitik takım normalleri yazılır MeshVeri mesh; m.meshCikar(mesh); FIKO_KONTROL(mesh.vertexSayi() > 0); FIKO_KONTROL(mesh.ucgenSayi() > 0); // İşaretli hacim (diverjans): V = (1/6)Σ v0·(v1×v2). Dışa sarım → +. auto gv = [&](u32 vi, int c) { return (f64)mesh.pozisyon[vi * 3 + c]; }; f64 V6 = 0.0; for (size_t t = 0; t + 2 < mesh.indeks.size(); t += 3) { const u32 i0 = mesh.indeks[t], i1 = mesh.indeks[t + 1], i2 = mesh.indeks[t + 2]; const f64 ax = gv(i0, 0), ay = gv(i0, 1), az = gv(i0, 2); const f64 bx = gv(i1, 0), by = gv(i1, 1), bz = gv(i1, 2); const f64 cx2 = gv(i2, 0), cy2 = gv(i2, 1), cz2 = gv(i2, 2); // v0 · (v1 × v2) const f64 crx = by * cz2 - bz * cy2, cry = bz * cx2 - bx * cz2, crz = bx * cy2 - by * cx2; V6 += ax * crx + ay * cry + az * crz; } const f64 V = V6 / 6.0; FIKO_KONTROL(std::isfinite(V)); FIKO_KONTROL(V > 0.0); // DIŞA sarım (winding doğru) // Kutu ~32000 mm³ eksi sığ oyuk; örnekleme kayması ile geniş bant. FIKO_KONTROL(V > 15000.0 && V < 45000.0); // Tüm vertexler sonlu + sınır içi. for (size_t i = 0; i < mesh.pozisyon.size(); ++i) FIKO_KONTROL(std::isfinite(mesh.pozisyon[i])); }