#include "fiko_test.hpp" #include "fiko/geometri/brep.hpp" #include #include using namespace fiko; using namespace fiko::geometri; // undercutRapor — EXACT per-yuz gorunurluk (BREP isin, tessellation YOK). // // Bu dosyanin varlik sebebi (kullanici sikayeti): eski undercut YALNIZ // dot(normal, cekimYonu) bakiyordu. Cekim yonune DIK bir delikte ic duvarin // normalleri 360 derece tarar → yarisi dot>0 (yesil), yarisi dot<0 (kirmizi) // → "yarim boyali delik". Oysa o delik BUTUNUYLE erisilemez. // `yatay_delik_*` testleri tam bu hatayi yakalar. namespace { const Donusumd kBirim{}; const Vec3d Z{0, 0, 1}; const UndercutYuz* bul(const std::vector& r, u32 ord) { for (const auto& u : r) if (u.ordinal == ord) return &u; return nullptr; } u32 say(const std::vector& r, UndercutDurum d) { return (u32)std::count_if(r.begin(), r.end(), [d](const UndercutYuz& u) { return u.durum == d; }); } // Verilen dunya-normale en yakin normale sahip DUZ yuzun ordinali. u32 normalYuz(const BrepGovde& g, Vec3d n) { YuzBilgi yb; for (u32 o = 1; g.yuzBilgi(o, yb, false); ++o) { if (!yb.duzlemMi) continue; const f64 d = yb.normal.x * n.x + yb.normal.y * n.y + yb.normal.z * n.z; if (d > 0.99) return o; } return 0; } } // namespace // ── Kapali kutu: ust yuz ACIK, alt yuz KAPALI, yan duvarlar ACIK ─────────── FIKO_TEST(undercut_kutu_ust_acik_alt_kapali) { const BrepGovde g = kutu(10, 10, 10); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 1.0); FIKO_KONTROL(r.size() == 6); // kutu = 6 yuz const u32 ustO = normalYuz(g, {0, 0, 1}); const u32 altO = normalYuz(g, {0, 0, -1}); FIKO_KONTROL(ustO != 0 && altO != 0); const UndercutYuz* ust = bul(r, ustO); const UndercutYuz* alt = bul(r, altO); FIKO_KONTROL(ust && ust->durum == UndercutDurum::Acik); FIKO_KONTROL(alt && alt->durum == UndercutDurum::Kapali); FIKO_KONTROL(alt->ortuluOran > 0.99f); FIKO_KONTROL(ust->ortuluOran < 0.01f); // Yan duvarlar (cekime PARALEL): isin duvar boyunca yukari gider, normal // boyunca disari itildigi icin kendi yuzune carpmamali → ACIK. for (const Vec3d n : {Vec3d{1, 0, 0}, Vec3d{-1, 0, 0}, Vec3d{0, 1, 0}, Vec3d{0, -1, 0}}) { const UndercutYuz* y = bul(r, normalYuz(g, n)); FIKO_KONTROL(y && y->durum == UndercutDurum::Acik); } // Toplam: 1 kapali (alt), 5 acik. Kismi/Bilinmiyor YOK. FIKO_KONTROL(say(r, UndercutDurum::Kapali) == 1); FIKO_KONTROL(say(r, UndercutDurum::Acik) == 5); FIKO_KONTROL(say(r, UndercutDurum::Bilinmiyor) == 0); } // ── ⭐⭐ IKILI KARAR: DIKEY duvar ACIK'tir (draft acisi ile KARISTIRMA) ───── // Kullanici sikayeti "hic profesyonel degil": tum parca SOMON boyanmisti. Sebep, // shader'in "acik" yuzler icin draft acisi RAMPASINI kullanmasiydi → dikey duvarin // dot(n,+Z)=0 → rampa 0.5 → kirmizi/yesil ortasi = somon. Oysa dikey duvar ustten // pekala CIKARILABILIR → undercut degil. // SOZLESME: undercut IKILIDIR (acik/kapali); draft acisi AYRI analizdir (mod 0). // Bu test, dikey duvarin ACIK cikmasini kilitler — shader onu 1.0 (yesil) boyar. FIKO_TEST(undercut_dikey_duvar_acik_draft_acisi_karismaz) { const BrepGovde g = kutu(10, 10, 10); const auto r = undercutRapor(g, kBirim, Z, 1.0); // dot(normal, +Z) == 0 olan TUM yan duvarlar → ACIK (undercut DEGIL). for (const Vec3d n : {Vec3d{1, 0, 0}, Vec3d{-1, 0, 0}, Vec3d{0, 1, 0}, Vec3d{0, -1, 0}}) { const UndercutYuz* y = bul(r, normalYuz(g, n)); FIKO_KONTROL(y && y->durum == UndercutDurum::Acik); FIKO_KONTROL(y->ortuluOran == 0.0f); // hicbir ornek ortulu degil → tam yesil } // Egik duvar da (dot<0 ama ustten erisilebilir) ACIK olmali: koni yan yuzu. const BrepGovde k = koni(5, 10); const auto rk = undercutRapor(k, kBirim, Z, 0.5); FIKO_KONTROL(!rk.empty()); u32 acikSay = 0; for (const auto& u : rk) if (u.durum == UndercutDurum::Acik) ++acikSay; FIKO_KONTROL(acikSay >= 1); // koninin yan yuzu ustten cikarilabilir } // ── ⭐⭐⭐ TERS YUZ, USTU BOS OLSA BILE ERISILEMEZ (iki kosul sozlesmesi) ──── // Gercek STEP parcasinda yakalandi: normali TAM ASAGI bakan bir yuz (disa tasan // cikintinin alti) ACIK cikiyordu — cunku isin yukari gidince BOSLUGA cikiyor. // Oysa +Z'den o yuzun SIRTINA bakiyorsun; kalip/takim onu o yonden olusturamaz. // SOZLESME: erisilebilir ⟺ (n·d >= 0) VE (onu kapali degil). Gorunurluk TEK // BASINA yetmez; normal kosulu dusurulurse bu test kirmizi verir. FIKO_TEST(undercut_ters_yuz_ustu_bos_olsa_da_kapali) { // "T" profili: ince govde + ustunde DISA TASAN genis baslik. Basligin ALTINDAKI // cikinti yuzeyi (normal −Z) disariya tasar → uzeri BOS (isin kacar) ama +Z'den // ERISILEMEZ (undercut). BrepGovde govde = kutu(10, 40, 20); // x=[-5,5] z=[-10,10] Donusumd db; db.konum = {0, 0, 15}; BrepGovde baslik = donusumUygula(kutu(40, 40, 10), db); // x=[-20,20] z=[10,20] const BrepGovde g = birlesim(govde, baslik); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 0.5); // Basligin ALT yuzu (normal −Z, z=10, x=[-20,-5]∪[5,20]): uzeri BOS ama TERS. u32 altSay = 0, altKapali = 0; for (const auto& u : r) { // ⚠️ YuzBilgi alanlari cagrilar arasi SIFIRLANMAZ — duz olmayan yuzde `normal` // ONCEKI yuzden kalir. HER cagrida TAZE struct sart (teshis dokumumu bu yanilti). YuzBilgi yb2{}; if (!g.yuzBilgi(u.ordinal, yb2, true)) continue; if (yb2.duzlemMi && yb2.normal.z < -0.99 && std::abs(yb2.merkez.z - 10.0) < 0.5) { ++altSay; if (u.durum == UndercutDurum::Kapali) ++altKapali; } } FIKO_KONTROL(altSay >= 1); // basligin alt cikinti yuzu bulundu FIKO_KONTROL(altKapali == altSay); // ⭐ hepsi KAPALI (ustu bos olmasina RAGMEN) // Kontrol: basligin UST yuzu (normal +Z, z=20) ACIK olmali (yanlis-pozitif kapisi). for (const auto& u : r) { YuzBilgi yb3{}; if (!g.yuzBilgi(u.ordinal, yb3, true)) continue; if (yb3.duzlemMi && yb3.normal.z > 0.99 && std::abs(yb3.merkez.z - 20.0) < 0.5) FIKO_KONTROL(u.durum == UndercutDurum::Acik); } } // ── Yon tersine → ust/alt roller degisir (kalibin diger yarisi, ⇅ butonu) ── FIKO_TEST(undercut_ters_yon_simetri) { const BrepGovde g = kutu(10, 10, 10); const auto r = undercutRapor(g, kBirim, {0, 0, -1}, 1.0); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, 1}))->durum == UndercutDurum::Kapali); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, -1}))->durum == UndercutDurum::Acik); } // ── ⭐ ASIL HATA: cekim yonune DIK (yatay) delik BUTUNUYLE kapali ────────── // Kullanici: "yone gore kapali delikleri yarim boyali gosteriyor". // Eski dot(n,yon): silindir duvarinin +Z bakan yarisi YESIL (yanlis). // EXACT gorunurluk: duvarin HER noktasi parcanin ustu tarafindan ortulur → KAPALI. FIKO_TEST(undercut_yatay_delik_butunuyle_kapali) { // X ekseni boyunca delinmis blok: silindir Y etrafinda 90 derece dondurulur. BrepGovde blok = kutu(30, 20, 20); Donusumd d; d.yonelim = Quatd::eksenAci({0, 1, 0}, 1.5707963267948966); // Z → X BrepGovde delici = donusumUygula(silindir(4, 40), d); const BrepGovde g = cikarma(blok, delici); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 0.5); FIKO_KONTROL(!r.empty()); // Deligin IC DUVARI = tek silindirik yuz. Bul ve KAPALI olmasini sart kos. YuzBilgi yb; u32 silO = 0; for (u32 o = 1; g.yuzBilgi(o, yb, false); ++o) if (yb.silindirMi && yb.yaricap > 3.5 && yb.yaricap < 4.5) { silO = o; break; } FIKO_KONTROL(silO != 0); // yatay delik duvari bulundu const UndercutYuz* duvar = bul(r, silO); FIKO_KONTROL(duvar != nullptr); // ⭐ SOZLESME: duvarin TAMAMI erisilemez — normalin isaretinden BAGIMSIZ. // (Eski yol bu yuzu yari yesil/yari kirmizi boyuyordu.) FIKO_KONTROL(duvar->durum == UndercutDurum::Kapali); FIKO_KONTROL(duvar->ortuluOran > 0.99f); } // ── Ustten delinmis (dikey) delik: duvar + dip ACIK (yanlis-pozitif kapisi) ─ FIKO_TEST(undercut_dikey_delik_acik) { // Blogun ustunden asagi delik: silindir Z boyunca, ustten tasar → kor delik. BrepGovde blok = kutu(30, 30, 20); Donusumd d; d.konum = {0, 0, 6}; // dip z=-4'te kalir (kor delik), agiz ustte BrepGovde delici = donusumUygula(silindir(5, 20), d); const BrepGovde g = cikarma(blok, delici); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 0.5); YuzBilgi yb; u32 silO = 0; for (u32 o = 1; g.yuzBilgi(o, yb, false); ++o) if (yb.silindirMi && yb.yaricap > 4.5 && yb.yaricap < 5.5) { silO = o; break; } FIKO_KONTROL(silO != 0); // Delik duvari ustten ERISILEBILIR (isin agizdan cikar) → ACIK. const UndercutYuz* duvar = bul(r, silO); FIKO_KONTROL(duvar && duvar->durum == UndercutDurum::Acik); // Kor deligin DIBI de ustten erisilebilir → ACIK (normal +Z, isin agizdan cikar). const UndercutYuz* dip = bul(r, normalYuz(g, {0, 0, 1})); FIKO_KONTROL(dip && dip->durum == UndercutDurum::Acik); } // ── ⭐ KISMI: ustteki cikinti alttaki tabanin BIR KISMINI orter ──────────── // Hibrit kipin varlik sebebi: bu yuz ne tam acik ne tam kapali → golge haritasi // devreye girer (golgenin gercek sekli). Durum=Kismi bunu isaretler. FIKO_TEST(undercut_kismi_ortulme) { // DIKKAT: "taban + uzerine oturan kule" KISMI VERMEZ — birlesim kulenin oturdugu // alani tabanin ust yuzunden zaten cikarir (yuz delikli olur, kismi degil). // Gercek kismi icin golge DEGMEYEN bir seyden gelmeli → koprü/askidaki tavan: // taban x=[-20,20] z=[-2,2] · kolon x=[-20,-16] · tavan x=[-20,0] z=[22,26] // → tabanin ust yuzu x=[-16,0] arasinda ORTULU, x=[0,20] arasinda ACIK = KISMI. BrepGovde taban = kutu(40, 10, 4); Donusumd dk; dk.konum = {-18, 0, 12}; BrepGovde kolon = donusumUygula(kutu(4, 10, 20), dk); Donusumd dt; dt.konum = {-10, 0, 24}; BrepGovde tavan = donusumUygula(kutu(20, 10, 4), dt); // tabanin YALNIZ solunu orter const BrepGovde g = birlesim(birlesim(taban, kolon), tavan); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 0.5); FIKO_KONTROL(!r.empty()); // +Z bakan duz yuzlerden EN AZ BIRI kismi ortulu olmali (tabanin ust yuzu). YuzBilgi yb; u32 kismiSay = 0; for (const auto& u : r) { if (!g.yuzBilgi(u.ordinal, yb, false)) continue; if (yb.duzlemMi && yb.normal.z > 0.99 && u.durum == UndercutDurum::Kismi) ++kismiSay; } FIKO_KONTROL(kismiSay >= 1); // Kismi yuzun orani tanim geregi (0,1) aciк araliginda. for (const auto& u : r) if (u.durum == UndercutDurum::Kismi) FIKO_KONTROL(u.ortuluOran > 0.0f && u.ortuluOran < 1.0f); } // ── ⭐⭐ "EKSIK YAPIYOR" REGRESYONU: INCE golge SEYREK ornekte KACIYORDU ──── // Kullanici sikayeti: BUYUK taban + uzerinde INCE kaburga → kaburganin golgesi // yuzu kismi ortuyor, ama SABIT 6x6 izgara BUYUK yuzde ~50mm adim demekti → // 4mm'lik golge iki ornek ARASINA dusuyor → yuz "tam acik" sanilip YESIL kaliyordu. // Cozum: ornekleme yuz boyutuna gore UYARLANIR (ornekAralik mm). FIKO_TEST(undercut_ince_golge_kacmaz) { // 200x60 taban + 4mm ince kopru (degmiyor, ustte) → golge SERIDI 4mm. BrepGovde taban = kutu(200, 60, 4); Donusumd dk; dk.konum = {-96, 0, 22}; BrepGovde kolon = donusumUygula(kutu(8, 60, 40), dk); // kenarda tasiyici Donusumd dt; dt.konum = {0, 0, 40}; BrepGovde kopru = donusumUygula(kutu(4, 60, 4), dt); // INCE kaburga, tabanin USTUNDE const BrepGovde g = birlesim(birlesim(taban, kolon), kopru); FIKO_KONTROL(g.gecerliMi()); YuzBilgi yb; // Tabanin BUYUK ust yuzu: +Z bakan, en genis alanli duzlem. u32 ustO = 0; f64 enBuyukAlan = 0; for (u32 o = 1; g.yuzBilgi(o, yb, true); ++o) if (yb.duzlemMi && yb.normal.z > 0.99 && yb.alan > enBuyukAlan) { enBuyukAlan = yb.alan; ustO = o; } FIKO_KONTROL(ustO != 0); // KABA adim (50mm): ince golge KACAR → yuz yanlislikla "acik" (ESKI HATA). const auto kaba = undercutRapor(g, kBirim, Z, 50.0); const UndercutYuz* ukaba = bul(kaba, ustO); FIKO_KONTROL(ukaba != nullptr); FIKO_KONTROL(ukaba->durum == UndercutDurum::Acik); // hatayi BELGELE // INCE adim (1mm): golge YAKALANIR → yuz KISMI (DOGRU). const auto ince = undercutRapor(g, kBirim, Z, 1.0); const UndercutYuz* uince = bul(ince, ustO); FIKO_KONTROL(uince != nullptr); FIKO_KONTROL(uince->durum == UndercutDurum::Kismi); // ⭐ ASIL SOZLESME FIKO_KONTROL(uince->ortuluOran > 0.0f && uince->ortuluOran < 0.2f); // ince serit // OTOMATIK adim (0 → kosegen/150 ≈ 1.4mm bu parcada) da yakalamali. const auto oto = undercutRapor(g, kBirim, Z, 0.0); FIKO_KONTROL(bul(oto, ustO)->durum == UndercutDurum::Kismi); } // ── Uyarlanan izgara: adim kucuklukce ornek SAYISI artmali (tavana kadar) ── FIKO_TEST(undercut_uyarlanan_izgara_tavani) { const BrepGovde g = kutu(100, 100, 10); // Tavan 2 → yuz basina en fazla 2x2=4 ornek; buyuk yuzde bile patlamamali. const auto r = undercutRapor(g, kBirim, Z, 0.01, 2); FIKO_KONTROL(r.size() == 6); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, 1}))->durum == UndercutDurum::Acik); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, -1}))->durum == UndercutDurum::Kapali); } // ── ⭐⭐⭐ EXACT GOLGE BOLGESI: yuzun TAMAMI degil, IZDUSEN YER ───────────── // Kullanici: "yuzeyler golgeden buyuk oldugu icin terse dusen yuzeyleri komple // boyuyor, halbuki izdusulen yeri ayri boyamasi gerekiyor." // undercutGolgeMesh golgeli alt-bolgeyi GERCEK GEOMETRI olarak cikarir → sinir // bir EGRI, cozunurluk/merdiven YOK. Asagidaki test ALANI olcerek dogrular. namespace { // Ucgen mesh'in toplam alani (golge bolgesinin gercek buyuklugu). f64 meshAlan(const std::vector& k, const std::vector& idx) { f64 a = 0; for (size_t i = 0; i + 2 < idx.size(); i += 3) { const Vec3f& p0 = k[idx[i]]; const Vec3f& p1 = k[idx[i + 1]]; const Vec3f& p2 = k[idx[i + 2]]; const Vec3f u = p1 - p0, v = p2 - p0; a += 0.5 * std::sqrt(u.capraz(v).uzunlukKare()); } return a; } } // namespace FIKO_TEST(undercut_golge_mesh_izdusen_alan) { // taban x=[-20,20] y=[-5,5] z=[-2,2] · kolon x=[-20,-16] · tavan x=[-20,0] z=[22,26] // Tabanin ust yuzu: x=[-16,20] (kolon kaynadi) → 36×10 = 360 mm². // Tavan x=[-20,0] orter → GOLGE = x=[-16,0] × y=[-5,5] = 16×10 = 160 mm². BrepGovde taban = kutu(40, 10, 4); Donusumd dk; dk.konum = {-18, 0, 12}; BrepGovde kolon = donusumUygula(kutu(4, 10, 20), dk); Donusumd dt; dt.konum = {-10, 0, 24}; BrepGovde tavan = donusumUygula(kutu(20, 10, 4), dt); const BrepGovde g = birlesim(birlesim(taban, kolon), tavan); FIKO_KONTROL(g.gecerliMi()); // Tabanin BUYUK ust yuzu (+Z, en genis alanli) — YALNIZ onu ver ki alan KESIN // olculebilsin. (Ilk halimde tum Kismi yuzleri verip tek yuz gibi olcuyordum → // baska yuzlerin katkisi alani sisiriyordu; test hatasiydi.) YuzBilgi yb; u32 ustO = 0; f64 enBuyukAlan = 0; for (u32 o = 1; g.yuzBilgi(o, yb, true); ++o) if (yb.duzlemMi && yb.normal.z > 0.99 && yb.alan > enBuyukAlan) { enBuyukAlan = yb.alan; ustO = o; } FIKO_KONTROL(ustO != 0); FIKO_KONTROL(std::abs(enBuyukAlan - 360.0) < 1.0); // yuzun TAMAMI = 36×10 // Gercek akis: undercutRapor bu yuzu KISMI demeli. const auto r = undercutRapor(g, kBirim, Z, 0.5); FIKO_KONTROL(bul(r, ustO)->durum == UndercutDurum::Kismi); std::vector kon, nrm; std::vector idx; FIKO_KONTROL(undercutGolgeMesh(g, kBirim, Z, {ustO}, kon, nrm, idx)); FIKO_KONTROL(!idx.empty() && idx.size() % 3 == 0); FIKO_KONTROL(kon.size() == nrm.size()); for (u32 i : idx) FIKO_KONTROL(i < kon.size()); // indeks siniri // ⭐⭐ ASIL SOZLESME: golge = IZDUSEN YER (16×10=160), yuzun TAMAMI (360) DEGIL. // "Komple boyama" hatasina donulurse bu satir kirmizi verir. const f64 alan = meshAlan(kon, idx); FIKO_KONTROL(std::abs(alan - 160.0) < 5.0); // Golge mesh'i yalniz golge BOLGESINDE: tavan x=[-20,0] orter, x>0 acikta. for (const Vec3f& p : kon) FIKO_KONTROL(p.x < 0.5f); // ...ve yuzun uzerinde (z=2 duzlemi). for (const Vec3f& p : kon) FIKO_KONTROL(std::abs(p.z - 2.0f) < 0.05f); } // ── Golge mesh: TAM ACIK yuz verilirse bos donmeli (yanlis-pozitif kapisi) ─ FIKO_TEST(undercut_golge_mesh_acik_yuz_bos) { const BrepGovde g = kutu(10, 10, 10); const u32 ustO = normalYuz(g, {0, 0, 1}); // ustten tamamen acik std::vector kon, nrm; std::vector idx; FIKO_KONTROL(!undercutGolgeMesh(g, kBirim, Z, {ustO}, kon, nrm, idx)); FIKO_KONTROL(idx.empty()); // Gecersiz girdiler → false, cokme yok. FIKO_KONTROL(!undercutGolgeMesh(g, kBirim, Z, {}, kon, nrm, idx)); FIKO_KONTROL(!undercutGolgeMesh(g, kBirim, {0, 0, 0}, {ustO}, kon, nrm, idx)); FIKO_KONTROL(!undercutGolgeMesh(BrepGovde{}, kBirim, Z, {ustO}, kon, nrm, idx)); } // ── Golge mesh: TAM KAPALI yuz → golge = yuzun TAMAMI (alan esitligi) ────── FIKO_TEST(undercut_golge_mesh_kapali_yuz_tam) { const BrepGovde g = kutu(10, 10, 10); const u32 altO = normalYuz(g, {0, 0, -1}); // alt yuz: ust yuz tarafindan tam ortulu std::vector kon, nrm; std::vector idx; FIKO_KONTROL(undercutGolgeMesh(g, kBirim, Z, {altO}, kon, nrm, idx)); const f64 alan = meshAlan(kon, idx); FIKO_KONTROL(std::abs(alan - 100.0) < 1.0); // 10×10 yuzun TAMAMI golgede } // ── Dejenere / kenar durumlar: cokme yok ────────────────────────────────── FIKO_TEST(undercut_dejenere_guvenli) { FIKO_KONTROL(undercutRapor(BrepGovde{}, kBirim, Z, 1.0).empty()); // gecersiz govde FIKO_KONTROL(undercutRapor(kutu(10, 10, 10), kBirim, {0, 0, 0}, 1.0).empty()); // sifir yon // Izgara 0/1 → ic clamp (>=2), cokme yok. FIKO_KONTROL(undercutRapor(kutu(10, 10, 10), kBirim, Z, 0.0).size() == 6); // Normalize edilmemis yon ayni sonucu vermeli. const auto a = undercutRapor(kutu(10, 10, 10), kBirim, {0, 0, 1}, 1.0); const auto b = undercutRapor(kutu(10, 10, 10), kBirim, {0, 0, 99.0}, 1.0); FIKO_KONTROL(a.size() == b.size()); for (size_t i = 0; i < a.size(); ++i) FIKO_KONTROL(a[i].durum == b[i].durum); } // ── Kure: cekim yonune gore ust yari acik / alt yari kapali → KISMI ──────── FIKO_TEST(undercut_kure_kismi) { const BrepGovde g = kure(10); FIKO_KONTROL(g.gecerliMi()); const auto r = undercutRapor(g, kBirim, Z, 0.5); FIKO_KONTROL(!r.empty()); // Kure tek yuz: ust yari acik, alt yari kendi ustu tarafindan ortulu → KISMI. FIKO_KONTROL(r[0].durum == UndercutDurum::Kismi); FIKO_KONTROL(r[0].ortuluOran > 0.2f && r[0].ortuluOran < 0.8f); } // ── Govde donusumu: cekim yonu DUNYA'da, govde donuk → yerel'e dogru tasinmali FIKO_TEST(undercut_govde_donusumu) { const BrepGovde g = kutu(10, 10, 10); // Govde X etrafinda +90 derece donuk. R_x(90): (0,0,1) → (0,-1,0), yani // yerel +Z dunya −Y'ye bakar (yerel +Y → dunya +Z). Donusumd d; d.yonelim = Quatd::eksenAci({1, 0, 0}, 1.5707963267948966); // DUNYA +Y cekimi → yerel uzayda −Z → govdenin YEREL −Z yuzu acik olmali. const auto r = undercutRapor(g, d, {0, 1, 0}, 1.0); FIKO_KONTROL(r.size() == 6); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, -1}))->durum == UndercutDurum::Acik); FIKO_KONTROL(bul(r, normalYuz(g, {0, 0, 1}))->durum == UndercutDurum::Kapali); FIKO_KONTROL(say(r, UndercutDurum::Kapali) == 1); // ⭐ Donusum YOK SAYILSAYDI: dunya +Y dogrudan yerel +Y'ye vururdu → acik/kapali // cifti yerel ±Y yuzleri olurdu. Asagidaki kontrol o hatayi yakalar: FIKO_KONTROL(bul(r, normalYuz(g, {0, 1, 0}))->durum == UndercutDurum::Acik); FIKO_KONTROL(bul(r, normalYuz(g, {0, -1, 0}))->durum == UndercutDurum::Acik); }