// FarkHesap (CAD Fark) çekirdeği: bilinen geometrilerle simetrik Hausdorff, // istatistik, histogram, eklenen/silinen ve iptal sözleşmesi. GPU yolu testte // koşmaz (Vulkan cihazı yok — vb=nullptr → CPU çok-çekirdek yolu doğrulanır; // GPU shader CPU ile BIREBIR algoritma taşır, uygulamada fallback garantili). #include #include #include #include #include #include #include #include "fiko/sahne/fark_hesap.hpp" #include "fiko_test.hpp" using namespace fiko; namespace { // z=z0 düzleminde n×n quad ızgara (2 üçgen/hücre). void duzlem(u32 n, f32 z0, std::vector& konum, std::vector& indeks) { for (u32 gy = 0; gy < n; ++gy) for (u32 gx = 0; gx < n; ++gx) { const f32 x = (f32)gx, y = (f32)gy; const u32 t = (u32)konum.size(); konum.push_back({x, y, z0}); konum.push_back({x + 1, y, z0}); konum.push_back({x + 1, y + 1, z0}); konum.push_back({x, y + 1, z0}); indeks.push_back(t); indeks.push_back(t + 1); indeks.push_back(t + 2); indeks.push_back(t); indeks.push_back(t + 2); indeks.push_back(t + 3); } } // z=z0 düzleminde nx×ny quad ızgara (dikdörtgen; bölge tip sınıflandırma testi için). void duzlemDik(u32 nx, u32 ny, f32 z0, std::vector& konum, std::vector& indeks) { for (u32 gy = 0; gy < ny; ++gy) for (u32 gx = 0; gx < nx; ++gx) { const f32 x = (f32)gx, y = (f32)gy; const u32 t = (u32)konum.size(); konum.push_back({x, y, z0}); konum.push_back({x + 1, y, z0}); konum.push_back({x + 1, y + 1, z0}); konum.push_back({x, y + 1, z0}); indeks.push_back(t); indeks.push_back(t + 1); indeks.push_back(t + 2); indeks.push_back(t); indeks.push_back(t + 2); indeks.push_back(t + 3); } } // Merkezi orijinde, yarı-boyut h eksen-hizalı kapalı küp (6 yüz × 2 üçgen). Köşe // konumları yüzler arasında ÇAKIŞIK → konum-bazlı kümeleme tek hacimsel bölge verir. // Yüz winding'i DIŞA-bakan (CCW): işaretli sapma testi geometrik normale güvenir. void kutu(f32 h, std::vector& konum, std::vector& indeks) { const Vec3f v[8] = {{-h, -h, -h}, {+h, -h, -h}, {+h, +h, -h}, {-h, +h, -h}, {-h, -h, +h}, {+h, -h, +h}, {+h, +h, +h}, {-h, +h, +h}}; const u32 yuz[6][4] = {{0, 3, 2, 1}, {4, 5, 6, 7}, {0, 1, 5, 4}, {3, 7, 6, 2}, {0, 4, 7, 3}, {1, 2, 6, 5}}; for (const auto& q : yuz) { const u32 t = (u32)konum.size(); for (int i = 0; i < 4; ++i) konum.push_back(v[q[i]]); indeks.push_back(t); indeks.push_back(t + 1); indeks.push_back(t + 2); indeks.push_back(t); indeks.push_back(t + 2); indeks.push_back(t + 3); } } FarkGirdi girdi(const std::vector& k, const std::vector& i) { FarkGirdi g; g.konum = k.data(); g.konumSayi = k.size(); g.indeks = i.data(); g.indeksSayi = i.size(); return g; } } // namespace FIKO_TEST(fark_ayni_mesh_sifir_sapma) { std::vector k; std::vector idx; duzlem(12, 0.0f, k, idx); const FarkSonuc s = farkHesapla(girdi(k, idx), girdi(k, idx)); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.maksSapma < 1e-5f); FIKO_KONTROL(s.eklenenVertex == 0 && s.silinenVertex == 0); FIKO_KONTROL(s.sapmaYeni.size() == k.size() && s.sapmaEski.size() == k.size()); FIKO_KONTROL(!s.gpuKullanildi); // vb yok → CPU yolu } FIKO_TEST(fark_kaydirilmis_duzlem_bilinen_sapma) { // z=0 vs z=2 düzlem: iç vertexlerin en yakını dik izdüşüm → sapma tam 2. std::vector kE, kY; std::vector iE, iY; duzlem(20, 0.0f, kE, iE); duzlem(20, 2.0f, kY, iY); FarkSecenek sec; sec.degisimTolerans = 0.5f; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(std::abs(s.maksSapma - 2.0f) < 1e-4f); FIKO_KONTROL(std::abs(s.ortSapma - 2.0f) < 1e-4f); FIKO_KONTROL(std::abs(s.rmsSapma - 2.0f) < 1e-4f); // Tüm vertexler toleransın (0.5) üstünde saptı → hepsi eklenen + silinen. FIKO_KONTROL(s.eklenenVertex == (u32)kY.size()); FIKO_KONTROL(s.silinenVertex == (u32)kE.size()); // Histogram: tüm örnekler son kovada (sapma = maks). u32 toplam = 0; for (u32 c : s.histogram) toplam += c; FIKO_KONTROL(toplam == (u32)(kE.size() + kY.size())); FIKO_KONTROL(s.histogram.back() == toplam); // Maks-sapma noktası z=0 veya z=2 düzleminde; karşısı diğer düzlemde. FIKO_KONTROL(std::abs(std::abs(s.maksNokta.z - s.maksKarsi.z) - 2.0f) < 1e-3f); } FIKO_TEST(fark_donusum_dunya_uzayinda) { // Aynı yerel mesh, yeni model donusum ile z+3 → dünya uzayında sapma 3. std::vector k; std::vector idx; duzlem(10, 0.0f, k, idx); FarkGirdi e = girdi(k, idx), y = girdi(k, idx); y.donusum.konum = {0.0, 0.0, 3.0}; const FarkSonuc s = farkHesapla(e, y); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(std::abs(s.maksSapma - 3.0f) < 1e-4f); } FIKO_TEST(fark_asimetrik_ve_oto_tolerans) { std::vector kE, kY; std::vector iE, iY; duzlem(10, 0.0f, kE, iE); duzlem(10, 1.0f, kY, iY); FarkSecenek sec; sec.simetrik = false; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.sapmaEski.empty()); // tek yön FIKO_KONTROL(s.sapmaYeni.size() == kY.size()); FIKO_KONTROL(s.silinenVertex == 0); FIKO_KONTROL(s.tolerans > 0.0f); // oto tolerans çözüldü } FIKO_TEST(fark_bolge_kumeleme) { // Kaydırılmış düzlem: tüm vertexler bağlantılı tek küme × iki yön = 2 bölge. std::vector kE, kY; std::vector iE, iY; duzlem(20, 0.0f, kE, iE); duzlem(20, 2.0f, kY, iY); FarkSecenek sec; sec.degisimTolerans = 0.5f; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.bolgeler.size() == 2); if (s.bolgeler.size() == 2) FIKO_KONTROL(s.bolgeler[0].eklenen != s.bolgeler[1].eklenen); // biri yeni biri eski for (const FarkBolge& b : s.bolgeler) { FIKO_KONTROL(std::abs(b.maksSapma - 2.0f) < 1e-4f); FIKO_KONTROL(b.vertexSayi == (u32)kE.size()); FIKO_KONTROL(std::abs(b.merkez.x - 10.0f) < 1e-3f); // 20×20 düzlem merkezi FIKO_KONTROL(std::abs(b.merkez.y - 10.0f) < 1e-3f); FIKO_KONTROL(b.yaricap > 10.0f); // köşegen/2 ≈ 14.1 } // Aynı mesh → bölge yok. FIKO_KONTROL(farkHesapla(girdi(kE, iE), girdi(kE, iE)).bolgeler.empty()); } FIKO_TEST(fark_bolge_tip_siniflandirma) { // Kaydırılmış tam düzlem: bölge x-y'de geniş, z'de ince → yassı levha = "Duz" (yüzey). std::vector kE, kY; std::vector iE, iY; duzlem(20, 0.0f, kE, iE); duzlem(20, 2.0f, kY, iY); FarkSecenek sec; sec.degisimTolerans = 0.5f; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.bolgeler.size() == 2); for (const FarkBolge& b : s.bolgeler) { FIKO_KONTROL(b.boyut.x > 18.0f && b.boyut.y > 18.0f); // geniş x-y (AABB dolduruldu) FIKO_KONTROL(std::abs(b.boyut.z) < 1e-3f); // tek düzlem → z ince FIKO_KONTROL(b.tip == BolgeTip::Duz); // yassı → yüzey sınıfı } // bolgeTipAdi eşleme sözleşmesi (eklenen/silinen × tip) — UI/rapor bu dizilere güvenir. FarkBolge b; b.tip = BolgeTip::Duz; b.eklenen = true; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Yüzey kabarması") == 0); b.eklenen = false; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Yüzey çöküğü") == 0); b.tip = BolgeTip::Uzun; b.eklenen = true; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Kenar/kabartı") == 0); b.eklenen = false; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Kenar/radius") == 0); b.tip = BolgeTip::Kompakt; b.eklenen = true; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Çıkıntı/ekleme") == 0); b.eklenen = false; FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), "Oyuk/delik") == 0); } FIKO_TEST(fark_bolge_tip_uzun) { // İnce uzun şerit (2 geniş × 20 uzun) kaydırık → e0/mx<0.18 && e1/mx<0.35 → Uzun (kenar). std::vector kE, kY; std::vector iE, iY; duzlemDik(2, 20, 0.0f, kE, iE); duzlemDik(2, 20, 2.0f, kY, iY); FarkSecenek sec; sec.degisimTolerans = 0.5f; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.bolgeler.size() == 2); for (const FarkBolge& b : s.bolgeler) { FIKO_KONTROL(b.boyut.y > 18.0f); // uzun eksen FIKO_KONTROL(b.boyut.x < 3.0f); // ince kısa eksen FIKO_KONTROL(b.tip == BolgeTip::Uzun); FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), b.eklenen ? "Kenar/kabartı" : "Kenar/radius") == 0); } } FIKO_TEST(fark_bolge_tip_kompakt) { // İç içe iki küp (h=2 vs h=4): tüm yüzey ~2 dışa saptı → gövde başına tek hacimsel // bölge, AABB tüm eksenlerde benzer → Kompakt (çıkıntı/oyuk). std::vector kE, kY; std::vector iE, iY; kutu(2.0f, kE, iE); kutu(4.0f, kY, iY); FarkSecenek sec; sec.degisimTolerans = 0.5f; const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY), sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(!s.bolgeler.empty()); for (const FarkBolge& b : s.bolgeler) { f32 e[3] = {std::abs(b.boyut.x), std::abs(b.boyut.y), std::abs(b.boyut.z)}; std::sort(e, e + 3); FIKO_KONTROL(e[0] / std::max(e[2], 1e-6f) > 0.22f); // dengeli AABB (Kompakt eşiği) FIKO_KONTROL(b.tip == BolgeTip::Kompakt); FIKO_KONTROL(std::strcmp(bolgeTipAdi(b), b.eklenen ? "Çıkıntı/ekleme" : "Oyuk/delik") == 0); } } FIKO_TEST(fark_isaretli_sapma_yonu) { // Profesyonel metroloji işareti (GOM/PolyWorks): + dışa/fazla malzeme, − içe/eksik. std::vector k2, k4; std::vector i2, i4; kutu(2.0f, k2, i2); kutu(4.0f, k4, i4); // Büyüyen küp (eski h2 → yeni h4): yeni yüzey eskinin DIŞINDA → sapmaYeni > 0. const FarkSonuc bu = farkHesapla(girdi(k2, i2), girdi(k4, i4)); FIKO_KONTROL(bu.gecerli); int arti = 0, eksi = 0; for (f32 d : bu.sapmaYeni) { if (d > 0.1f) ++arti; else if (d < -0.1f) ++eksi; } FIKO_KONTROL(arti > 0 && eksi == 0); // fazla malzeme → hepsi dışa (+) // Küçülen küp (eski h4 → yeni h2): yeni yüzey içeride → sapmaYeni < 0. const FarkSonuc ku = farkHesapla(girdi(k4, i4), girdi(k2, i2)); FIKO_KONTROL(ku.gecerli); arti = 0; eksi = 0; for (f32 d : ku.sapmaYeni) { if (d > 0.1f) ++arti; else if (d < -0.1f) ++eksi; } FIKO_KONTROL(eksi > 0 && arti == 0); // eksik malzeme → hepsi içe (−) } FIKO_TEST(fark_skala_p95_aykiri_bastirir) { // Eğik düzlem (z = 0.3·y): sapma y ile ~doğrusal 0→~8.6 artar. Renk skalası ucu // farkSkalaYuzdelik (vars. P95) → skalaSapma ≈ 0.95·maks < maks: tek aykırı büyük uç // skalayı ele geçirmez (tur 3 kalibrasyonu — ince farklar daha renkli). std::vector kE, kY; std::vector iE, iY; duzlem(30, 0.0f, kE, iE); duzlem(30, 0.0f, kY, iY); for (auto& v : kY) v.z = 0.3f * v.y; // eğim → sapma dağılımı (tek değer değil) const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY)); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.skalaSapma > 0.0f); FIKO_KONTROL(s.skalaSapma <= s.maksSapma + 1e-4f); // skala ucu maksı AŞMAZ FIKO_KONTROL(s.skalaSapma < s.maksSapma * 0.99f); // P95 < maks (uç bastırıldı) } FIKO_TEST(fark_yerel_eksen_hizalama) { // Aynı geometri farklı konumda (z+5 kaydırık): normal karşılaştırmada sapma 5, // yerel eksende (centroid hizalama) sapma ~0 — web "yerel eksen sistemi" davranışı. std::vector k; std::vector idx; duzlem(10, 0.0f, k, idx); FarkGirdi e = girdi(k, idx), y = girdi(k, idx); y.donusum.konum = {0.0, 0.0, 5.0}; FIKO_KONTROL(std::abs(farkHesapla(e, y).maksSapma - 5.0f) < 1e-4f); FarkSecenek sec; sec.yerelEksen = true; const FarkSonuc s = farkHesapla(e, y, sec); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.maksSapma < 1e-4f); FIKO_KONTROL(s.eklenenVertex == 0 && s.silinenVertex == 0); } FIKO_TEST(fark_iptal_ve_bos_giris) { std::vector k; std::vector idx; duzlem(10, 0.0f, k, idx); std::atomic dur{true}; FarkSecenek sec; sec.dur = &dur; FIKO_KONTROL(!farkHesapla(girdi(k, idx), girdi(k, idx), sec).gecerli); FIKO_KONTROL(!farkHesapla(FarkGirdi{}, girdi(k, idx)).gecerli); } FIKO_TEST(fark_perf_buyuk_izgara) { // PERF olcumu: 150×150 izgara (90k vertex/mesh) CPU cok-cekirdek yolu — GPU'suz // makinede/fallback'te fark hesabinin bekleme suresi. Sure assert edilmez. std::vector kE, kY; std::vector iE, iY; duzlem(150, 0.0f, kE, iE); duzlem(150, 0.5f, kY, iY); const auto t0 = std::chrono::steady_clock::now(); const FarkSonuc s = farkHesapla(girdi(kE, iE), girdi(kY, iY)); const f64 ms = std::chrono::duration( std::chrono::steady_clock::now() - t0).count(); std::printf("[perf] farkHesapla CPU: %zu+%zu vertex, %.1f ms (maks %.3f)\n", kE.size(), kY.size(), ms, (f64)s.maksSapma); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(std::abs(s.maksSapma - 0.5f) < 0.01f); // duzlemler arasi 0.5 }