#include #include #include #include #include "fiko/dokum/sivi_yuzey.hpp" #include "fiko_test.hpp" using namespace fiko; using namespace fiko::dokum; namespace { VoxelKafes kafesYap(u32 n) { VoxelKafes k; Aabbd s; s.genislet({-1, -1, -1}); s.genislet({1, 1, 1}); k.kur(s, n, 0.0, 1.0); return k; } f64 hucreBoy(const VoxelKafes& k) { return k.merkez(1, 0, 0).x - k.merkez(0, 0, 0).x; } } // namespace FIKO_TEST(sivi_kure_izo_yuzey) { VoxelKafes k = kafesYap(32); FIKO_KONTROL(k.gecerliMi()); const u32 N = k.hucreSayi(); const f64 r = 0.7; std::vector alan(N); for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const u32 idx = k.indeks(i, j, kk); k.tip[idx] = HucreTip::Ic; alan[idx] = k.merkez(i, j, kk).uzunluk() - r; } SiviYuzeyAyar ayar; ayar.parcaSiniriKirp = false; ayar.yumusat = false; const SiviYuzeySonuc s = siviYuzeyKur(k, alan, 0.0f, ayar); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.ucgenSayi > 100); const f64 h = hucreBoy(k); f64 azamiSapma = 0; f32 asgariDot = 1; for (const Vertex& v : s.mesh.vertexler) { const Vec3f p{v.konum[0], v.konum[1], v.konum[2]}; const f32 uz = p.uzunluk(); azamiSapma = std::max(azamiSapma, (f64)std::fabs(uz - (f32)r)); const Vec3f radyal = p * (1.0f / uz); asgariDot = std::min(asgariDot, radyal.x * v.normal[0] + radyal.y * v.normal[1] + radyal.z * v.normal[2]); } FIKO_KONTROL(azamiSapma <= h); // kenar-interp: vertex yarıçapa ±h FIKO_KONTROL(asgariDot > 0.9f); // gradyan normal ~radyal (dışa) } FIKO_TEST(sivi_duzlem_yuzey) { VoxelKafes k = kafesYap(24); const u32 N = k.hucreSayi(); std::vector alan(N); for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const u32 idx = k.indeks(i, j, kk); k.tip[idx] = HucreTip::Ic; alan[idx] = k.merkez(i, j, kk).z; } SiviYuzeyAyar ayar; ayar.parcaSiniriKirp = false; ayar.yumusat = false; const f32 izo = 0.1f; const SiviYuzeySonuc s = siviYuzeyKur(k, alan, izo, ayar); FIKO_KONTROL(s.gecerli); bool duz = true, normalDogru = true; for (const Vertex& v : s.mesh.vertexler) { if (std::fabs(v.konum[2] - izo) > 1e-4f) duz = false; if (v.normal[2] < 0.99f) normalDogru = false; // iç = z alan(k.hucreSayi(), 1.0); for (auto& t : k.tip) t = HucreTip::Ic; const SiviYuzeySonuc s1 = siviYuzeyKur(k, alan, 0.0f, {}); FIKO_KONTROL(!s1.gecerli); FIKO_KONTROL(s1.mesh.vertexler.empty()); std::vector yanlisBoy(3, 0.0); const SiviYuzeySonuc s2 = siviYuzeyKur(k, yanlisBoy, 0.0f, {}); FIKO_KONTROL(!s2.gecerli); } FIKO_TEST(sivi_parca_siniri_tasma_yok) { // Küre parça içinde yarım dolum (z alan(N); u32 metal = 0; for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const u32 idx = k.indeks(i, j, kk); const Vec3d m = k.merkez(i, j, kk); const bool ic = m.uzunluk() <= r; k.tip[idx] = ic ? HucreTip::Ic : HucreTip::Dis; metal += ic; alan[idx] = m.z; // alttan dolum benzeri alan } FIKO_KONTROL(metal > 0); SiviYuzeyAyar ayar; // parcaSiniriKirp = true, yumusat = true (gerçek yapı) const f32 izo = 0.2f; const SiviYuzeySonuc s = siviYuzeyKur(k, alan, izo, ayar); FIKO_KONTROL(s.gecerli); FIKO_KONTROL(s.ucgenSayi > 0); const f64 h = hucreBoy(k); f64 azamiUz = 0, azamiZ = -10; for (const Vertex& v : s.mesh.vertexler) { const Vec3f p{v.konum[0], v.konum[1], v.konum[2]}; azamiUz = std::max(azamiUz, (f64)p.uzunluk()); azamiZ = std::max(azamiZ, (f64)p.z); } FIKO_KONTROL(azamiUz <= r + 0.5 * h + 1e-6); // parça dışına TAŞMA YOK FIKO_KONTROL(azamiZ <= izo + 0.5 * h + 1e-6); // sıvı cephesi izo üstüne çıkmaz } FIKO_TEST(sivi_sinir_kabuklu_tasma_yok) { // GERÇEK sınıflama düzeni: Ic çekirdek + parça-DIŞI Sinir kabuğu (voxelize // sözleşmesi). Sinir hücreler dolu sıvı alanı taşısa bile yüzey parça (Ic // bölgesi) sınırından yarım hücreden fazla ÇIKAMAZ — kabuk maskeye girerse // taşma ~1.5 hücreye çıkar (canlı testte görülen regresyon). VoxelKafes k = kafesYap(24); const u32 N = k.hucreSayi(); const f64 L = 0.6; // parça: |x|,|y|,|z| <= L kutusu std::vector alan(N, 1e30); for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const u32 idx = k.indeks(i, j, kk); const Vec3d m = k.merkez(i, j, kk); const bool ic = std::fabs(m.x) <= L && std::fabs(m.y) <= L && std::fabs(m.z) <= L; k.tip[idx] = ic ? HucreTip::Ic : HucreTip::Dis; } // Metale komşu dış → Sinir (voxelize::kafesiSinifla ikizi) + alan doldur. auto icMi = [&](int i, int j, int kk) { if (i < 0 || j < 0 || kk < 0 || i >= (int)k.nx() || j >= (int)k.ny() || kk >= (int)k.nz()) return false; return k.tip[k.indeks((u32)i, (u32)j, (u32)kk)] == HucreTip::Ic; }; for (int kk = 0; kk < (int)k.nz(); ++kk) for (int j = 0; j < (int)k.ny(); ++j) for (int i = 0; i < (int)k.nx(); ++i) { const u32 idx = k.indeks((u32)i, (u32)j, (u32)kk); if (k.tip[idx] == HucreTip::Dis && (icMi(i - 1, j, kk) || icMi(i + 1, j, kk) || icMi(i, j - 1, kk) || icMi(i, j + 1, kk) || icMi(i, j, kk - 1) || icMi(i, j, kk + 1))) k.tip[idx] = HucreTip::Sinir; if (k.tip[idx] != HucreTip::Dis) alan[idx] = k.merkez((u32)i, (u32)j, (u32)kk).z; // alttan dolum; kabuk DA dolu } SiviYuzeyAyar ayar; // parcaSiniriKirp = true (gerçek yapı) const SiviYuzeySonuc s = siviYuzeyKur(k, alan, 0.2f, ayar); FIKO_KONTROL(s.gecerli && s.ucgenSayi > 0); const f64 h = hucreBoy(k); f64 azami = 0; for (const Vertex& v : s.mesh.vertexler) azami = std::max({azami, (f64)std::fabs(v.konum[0]), (f64)std::fabs(v.konum[1]), (f64)std::fabs(v.konum[2])}); FIKO_KONTROL(azami <= L + 0.5 * h + 1e-6); // Sinir kabuğuna TAŞMA YOK } namespace { // Yarım dolu küp parça senaryosu: gerçek dolum alanı gibi sentinel'li alan + // parça sınırı kırpma ile üretilen yüzeyin KAPALI hacim sarması beklenir. struct KapalilikOzet { u32 acikKenar = 0; // tek üçgene ait kenar (kapalı yüzeyde 0) u32 ucgen = 0; bool yon[6] = {}; // ±x ±y ±z baskın normalli üçgen var mı }; // İmzalı hacim (kapalı yüzeyde dışa-dönük sarımda pozitif). f64 imzaliHacim(const MeshVeri& m) { f64 v = 0; for (size_t t = 0; t + 2 < m.indeksler.size(); t += 3) { auto p = [&](int c) { const auto& q = m.vertexler[m.indeksler[t + c]]; return Vec3d{q.konum[0], q.konum[1], q.konum[2]}; }; v += p(0).nokta(p(1).capraz(p(2))) / 6.0; } return v; } KapalilikOzet kapalilikOlc(const MeshVeri& m) { KapalilikOzet o; std::map, int> kenar; auto anahtar = [](u32 a, u32 b) { return a < b ? std::make_pair(a, b) : std::make_pair(b, a); }; for (size_t t = 0; t + 2 < m.indeksler.size(); t += 3) { const u32 v[3] = {m.indeksler[t], m.indeksler[t + 1], m.indeksler[t + 2]}; ++o.ucgen; for (int e = 0; e < 3; ++e) ++kenar[anahtar(v[e], v[(e + 1) % 3])]; auto p = [&](int c) { const auto& q = m.vertexler[v[c]]; return Vec3f{q.konum[0], q.konum[1], q.konum[2]}; }; const Vec3f n = (p(1) - p(0)).capraz(p(2) - p(0)); const f32 ax = std::fabs(n.x), ay = std::fabs(n.y), az = std::fabs(n.z); if (ax >= ay && ax >= az) o.yon[n.x >= 0 ? 0 : 1] = true; else if (ay >= az) o.yon[n.y >= 0 ? 2 : 3] = true; else o.yon[n.z >= 0 ? 4 : 5] = true; } for (auto& [k, s] : kenar) if (s == 1) ++o.acikKenar; return o; } // Küp parça (boy L, mold payı 0.4) + alttan-yukarı dolum benzeri varış alanı; // izo anında yarısı dolu. res/L taraması regresyonu yakalar. struct YarimDoluSonuc { KapalilikOzet ozet; f64 hacim = 0; // imzalı hacim (dışa dönük sarımda pozitif) }; YarimDoluSonuc yarimDoluKup(u32 res, f64 L) { VoxelKafes k; Aabbd s; s.genislet({0, 0, 0}); s.genislet({L, L, L}); if (!k.kur(s, res, 0.4, 0.001)) return {}; const u32 N = k.hucreSayi(); DolumSonuc d; d.varisZamani.assign(N, -1.0); d.hazir = true; const f64 T = 2.0; // toplam dolum süresi [s] for (u32 kk = 0; kk < k.nz(); ++kk) for (u32 j = 0; j < k.ny(); ++j) for (u32 i = 0; i < k.nx(); ++i) { const u32 idx = k.indeks(i, j, kk); const Vec3d m = k.merkez(i, j, kk); const bool ic = m.x >= 0 && m.x <= L && m.y >= 0 && m.y <= L && m.z >= 0 && m.z <= L; k.tip[idx] = ic ? HucreTip::Ic : HucreTip::Dis; if (ic) d.varisZamani[idx] = (m.z / L) * T; // alttan dolar } const std::vector alan = dolumAlanKur(k, d); SiviYuzeyAyar ayar; // gerçek yapı: parcaSiniriKirp=true, yumusat=true const SiviYuzeySonuc sy = siviYuzeyKur(k, alan, (f32)(0.5 * T), ayar); if (!sy.gecerli) return {}; return {kapalilikOlc(sy.mesh), imzaliHacim(sy.mesh)}; } } // namespace FIKO_TEST(sivi_yarim_dolu_kapali_hacim) { // res × parça boyu taraması: yüzey her kombinasyonda KAPALI (açık kenar yok), // 6 yönde de üçgen var (cephe + ıslak duvarlar + taban — levha değil) ve // imzalı hacim POZİTİF (dışa dönük sarım; BACK-cull altında görünür kalır). const u32 resler[] = {8, 16, 32}; const f64 boylar[] = {10.0, 100.0}; for (u32 res : resler) for (f64 L : boylar) { const YarimDoluSonuc r = yarimDoluKup(res, L); FIKO_KONTROL(r.ozet.ucgen > 0); FIKO_KONTROL(r.ozet.acikKenar == 0); bool hepsi = true; for (bool y : r.ozet.yon) hepsi = hepsi && y; FIKO_KONTROL(hepsi); FIKO_KONTROL(r.hacim > 0); // İnce res'te hacim yarım küpe yaklaşır (SDF ±0.5 hücre içeri çekme payı). if (res >= 32) FIKO_KONTROL(r.hacim > 0.6 * 0.5 * L * L * L); } } FIKO_TEST(sivi_dolum_alan_kur) { VoxelKafes k = kafesYap(8); const u32 N = k.hucreSayi(); for (auto& t : k.tip) t = HucreTip::Dis; k.tip[0] = HucreTip::Ic; k.tip[1] = HucreTip::Ic; DolumSonuc d; d.varisZamani.assign(N, -1.0); d.varisZamani[0] = 0.5; // dolan d.hazir = true; const std::vector alan = dolumAlanKur(k, d); FIKO_KONTROL(alan.size() == N); FIKO_KONTROL(std::fabs(alan[0] - 0.5) < 1e-12); FIKO_KONTROL(alan[1] > 1e20); // metal ama dolmadı → hiç sıvı olmaz FIKO_KONTROL(alan[2] > 1e20); // metal-dışı → hiç sıvı olmaz }