#include "fiko/cae/govde_kafes.hpp" #include #include #include #include "fiko/dokum/voxel_kafes.hpp" #include "fiko/dokum/voxelize.hpp" namespace fiko::cae { namespace { // Üçgen–AABB örtüşmesi (Akenine-Möller ayırıcı eksen testi, 13 eksen). // Kutu MERKEZ + yarı-boyut ile verilir. Konservatif voxelize'ın çekirdeği: // yüzeyi kesen her hücre malzeme sayılır → ince cidar h'den küçük olsa bile YAŞAR. bool ucgenKutuKesisir(const Vec3d& merkez, f64 yari, const Vec3d& a, const Vec3d& b, const Vec3d& c) { const Vec3d v0{a.x - merkez.x, a.y - merkez.y, a.z - merkez.z}; const Vec3d v1{b.x - merkez.x, b.y - merkez.y, b.z - merkez.z}; const Vec3d v2{c.x - merkez.x, c.y - merkez.y, c.z - merkez.z}; const Vec3d e0{v1.x - v0.x, v1.y - v0.y, v1.z - v0.z}; const Vec3d e1{v2.x - v1.x, v2.y - v1.y, v2.z - v1.z}; const Vec3d e2{v0.x - v2.x, v0.y - v2.y, v0.z - v2.z}; // 9 çapraz eksen (kenar × kutu ekseni). const auto eksenTesti = [&](f64 ax, f64 ay, f64 az) { const f64 p0 = ax * v0.x + ay * v0.y + az * v0.z; const f64 p1 = ax * v1.x + ay * v1.y + az * v1.z; const f64 p2 = ax * v2.x + ay * v2.y + az * v2.z; const f64 r = yari * (std::abs(ax) + std::abs(ay) + std::abs(az)); const f64 mn = std::min(p0, std::min(p1, p2)); const f64 mx = std::max(p0, std::max(p1, p2)); return !(mn > r || mx < -r); }; const Vec3d ke[3] = {e0, e1, e2}; for (int i = 0; i < 3; ++i) { if (!eksenTesti(0.0, -ke[i].z, ke[i].y)) return false; if (!eksenTesti(ke[i].z, 0.0, -ke[i].x)) return false; if (!eksenTesti(-ke[i].y, ke[i].x, 0.0)) return false; } // 3 kutu ekseni. if (std::min(v0.x, std::min(v1.x, v2.x)) > yari || std::max(v0.x, std::max(v1.x, v2.x)) < -yari) return false; if (std::min(v0.y, std::min(v1.y, v2.y)) > yari || std::max(v0.y, std::max(v1.y, v2.y)) < -yari) return false; if (std::min(v0.z, std::min(v1.z, v2.z)) > yari || std::max(v0.z, std::max(v1.z, v2.z)) < -yari) return false; // Üçgen düzlemi. const Vec3d n{e0.y * e1.z - e0.z * e1.y, e0.z * e1.x - e0.x * e1.z, e0.x * e1.y - e0.y * e1.x}; const f64 d = -(n.x * v0.x + n.y * v0.y + n.z * v0.z); const f64 r = yari * (std::abs(n.x) + std::abs(n.y) + std::abs(n.z)); return std::abs(d) <= r; } } // namespace HexKafes govdedenKafes(const geometri::BrepGovde& govde, const KafesKurulum& kur, dokum::VoxelKafes* voxelCikti) { HexKafes kf; if (!govde.gecerliMi() || kur.enUzunHucre == 0) return kf; const dokum::UcgenCorba corba = dokum::corbaBrepten(govde, kur.kaliteOrani); if (corba.ucgenSayi() == 0) return kf; dokum::VoxelKafes vk; // pay=0 → kafes yalnız PARÇAYI sarar (döküm kalıp payı FEM'de anlamsız; pay // verilirse hücrelerin çoğu boş kalır ve çözünürlük boşa harcanır). if (!vk.kur(corba.sinir, kur.enUzunHucre, 0.0, 1.0)) return kf; dokum::kafesiSinifla(vk, corba); kf.nx = vk.nx(); kf.ny = vk.ny(); kf.nz = vk.nz(); kf.h = vk.hMesh(); kf.dolu.assign((std::size_t)kf.nx * kf.ny * kf.nz, 0); for (u32 k = 0; k < kf.nz; ++k) for (u32 j = 0; j < kf.ny; ++j) for (u32 i = 0; i < kf.nx; ++i) { const dokum::HucreTip t = vk.tip[vk.indeks(i, j, k)]; // Sinir = yüzey kabuğu; malzemenin parçasıdır (dışarısı yalnız Dis). if (t == dokum::HucreTip::Ic || t == dokum::HucreTip::Sinir) kf.dolu[kf.hucreIndeks(i, j, k)] = 1; } // KONSERVATİF EK: yüzeyi kesen hücreleri de doldur (merkez sınıflaması ince // cidarı yutuyordu — ölçüldü: 2 mm plaka kaba kafeste TAMAMEN kayboluyordu). if (kur.konservatif) { const f64 hh = vk.hMesh(), yari = hh * 0.5; // Grid orijini (asgari köşe): merkez(0,0,0) − h/2. const Vec3d m0 = vk.merkez(0, 0, 0); const Vec3d kok{m0.x - yari, m0.y - yari, m0.z - yari}; const std::vector& v = corba.vertler; // Üçgen başına YALNIZ kendi AABB'sine düşen hücre ARALIĞI gezilir. Tüm kafesi // taramak O(üçgen × hücre) idi (ölçüldü 07-21: 110k hücrede +75 ms; sınıflama // paralelleştirildikten sonra toplamın yarısı buydu). for (std::size_t t = 0; t + 2 < v.size(); t += 3) { Aabbd ub; ub.genislet(v[t]); ub.genislet(v[t + 1]); ub.genislet(v[t + 2]); const auto aralik = [&](f64 alt, f64 ust, u32 n, f64 k0, u32& bas, u32& son) { const f64 a = (alt - yari - k0) / hh, b = (ust + yari - k0) / hh; bas = (u32)std::max(0.0, std::floor(a)); son = (u32)std::min((f64)n, std::ceil(b) + 1.0); if (bas > n) bas = n; }; u32 i0, i1, j0, j1, k0i, k1; aralik(ub.asgari.x, ub.azami.x, kf.nx, kok.x, i0, i1); aralik(ub.asgari.y, ub.azami.y, kf.ny, kok.y, j0, j1); aralik(ub.asgari.z, ub.azami.z, kf.nz, kok.z, k0i, k1); for (u32 k = k0i; k < k1; ++k) for (u32 j = j0; j < j1; ++j) for (u32 i = i0; i < i1; ++i) { const u32 h = kf.hucreIndeks(i, j, k); if (kf.dolu[h]) continue; if (ucgenKutuKesisir(vk.merkez(i, j, k), yari, v[t], v[t + 1], v[t + 2])) kf.dolu[h] = 1; } } } if (voxelCikti) *voxelCikti = std::move(vk); return kf; } u32 enIncedeHucre(const HexKafes& kafes) { if (!kafes.gecerliMi()) return 0; u32 i0 = kafes.nx, i1 = 0, j0 = kafes.ny, j1 = 0, k0 = kafes.nz, k1 = 0; bool var = false; for (u32 k = 0; k < kafes.nz; ++k) for (u32 j = 0; j < kafes.ny; ++j) for (u32 i = 0; i < kafes.nx; ++i) { if (!kafes.dolu[kafes.hucreIndeks(i, j, k)]) continue; var = true; i0 = std::min(i0, i); i1 = std::max(i1, i); j0 = std::min(j0, j); j1 = std::max(j1, j); k0 = std::min(k0, k); k1 = std::max(k1, k); } if (!var) return 0; return std::min({i1 - i0 + 1, j1 - j0 + 1, k1 - k0 + 1}); } std::vector yuzDugumleri(const HexKafes& kafes, KafesYuz yuz) { std::vector dugumler; if (!kafes.gecerliMi()) return dugumler; // Yüzdeki DOLU hücrenin o yüze bakan 4 düğümü seçilir (boş bölge dahil edilmez). const auto ekle = [&](u32 i, u32 j, u32 k) { if (!kafes.dolu[kafes.hucreIndeks(i, j, k)]) return; for (int a = 0; a < 2; ++a) for (int b = 0; b < 2; ++b) { u32 di = i, dj = j, dk = k; switch (yuz) { case KafesYuz::EksiX: dj = j + a; dk = k + b; break; case KafesYuz::ArtiX: di = i + 1; dj = j + a; dk = k + b; break; case KafesYuz::EksiY: di = i + a; dk = k + b; break; case KafesYuz::ArtiY: di = i + a; dj = j + 1; dk = k + b; break; case KafesYuz::EksiZ: di = i + a; dj = j + b; break; case KafesYuz::ArtiZ: di = i + a; dj = j + b; dk = k + 1; break; } dugumler.push_back(kafes.dugumIndeks(di, dj, dk)); } }; switch (yuz) { case KafesYuz::EksiX: case KafesYuz::ArtiX: { const u32 i = (yuz == KafesYuz::EksiX) ? 0 : kafes.nx - 1; for (u32 k = 0; k < kafes.nz; ++k) for (u32 j = 0; j < kafes.ny; ++j) ekle(i, j, k); break; } case KafesYuz::EksiY: case KafesYuz::ArtiY: { const u32 j = (yuz == KafesYuz::EksiY) ? 0 : kafes.ny - 1; for (u32 k = 0; k < kafes.nz; ++k) for (u32 i = 0; i < kafes.nx; ++i) ekle(i, j, k); break; } case KafesYuz::EksiZ: case KafesYuz::ArtiZ: { const u32 k = (yuz == KafesYuz::EksiZ) ? 0 : kafes.nz - 1; for (u32 j = 0; j < kafes.ny; ++j) for (u32 i = 0; i < kafes.nx; ++i) ekle(i, j, k); break; } } std::sort(dugumler.begin(), dugumler.end()); dugumler.erase(std::unique(dugumler.begin(), dugumler.end()), dugumler.end()); return dugumler; } u8 yuzNormalMaske(KafesYuz yuz) { switch (yuz) { case KafesYuz::EksiX: case KafesYuz::ArtiX: return 0x1; case KafesYuz::EksiY: case KafesYuz::ArtiY: return 0x2; default: return 0x4; } } std::vector yuzKisitlari(const HexKafes& kafes, KafesYuz yuz, u8 eksenMaskesi) { std::vector kisitlar; for (u32 d : yuzDugumleri(kafes, yuz)) kisitlar.push_back({d, eksenMaskesi}); return kisitlar; } std::vector yuzeYayliKuvvet(const HexKafes& kafes, KafesYuz yuz, Vec3d toplamKuvvet) { std::vector yukler; if (!kafes.gecerliMi()) return yukler; // ALAN PAYLI dağıtım: bir düğümün payı, ona dokunan DOLU yüz hücresi sayısıdır // (iç düğüm 4, kenar 2, köşe 1). Kuvveti düğümlere EŞİT bölmek köşe/kenarı 2-4 kat // aşırı yükler → yük yüzü yerel olarak fazla uzar (ölçüldü 07-21: eşit bölmede // eksenel uzama analitikten %1.9 sapıyordu, alan paylıda sapma çözücü mertebesine // indi). Delikli yüzeyde de doğru: eksik hücre payı otomatik düşer. std::vector> pay; // (düğüm, ağırlık) const auto ekleHucre = [&](u32 i, u32 j, u32 k) { if (!kafes.dolu[kafes.hucreIndeks(i, j, k)]) return; for (int a = 0; a < 2; ++a) for (int b = 0; b < 2; ++b) { u32 di = i, dj = j, dk = k; switch (yuz) { case KafesYuz::EksiX: dj = j + a; dk = k + b; break; case KafesYuz::ArtiX: di = i + 1; dj = j + a; dk = k + b; break; case KafesYuz::EksiY: di = i + a; dk = k + b; break; case KafesYuz::ArtiY: di = i + a; dj = j + 1; dk = k + b; break; case KafesYuz::EksiZ: di = i + a; dj = j + b; break; case KafesYuz::ArtiZ: di = i + a; dj = j + b; dk = k + 1; break; } pay.push_back({kafes.dugumIndeks(di, dj, dk), 1.0}); } }; switch (yuz) { case KafesYuz::EksiX: case KafesYuz::ArtiX: { const u32 i = (yuz == KafesYuz::EksiX) ? 0 : kafes.nx - 1; for (u32 k = 0; k < kafes.nz; ++k) for (u32 j = 0; j < kafes.ny; ++j) ekleHucre(i, j, k); break; } case KafesYuz::EksiY: case KafesYuz::ArtiY: { const u32 j = (yuz == KafesYuz::EksiY) ? 0 : kafes.ny - 1; for (u32 k = 0; k < kafes.nz; ++k) for (u32 i = 0; i < kafes.nx; ++i) ekleHucre(i, j, k); break; } case KafesYuz::EksiZ: case KafesYuz::ArtiZ: { const u32 k = (yuz == KafesYuz::EksiZ) ? 0 : kafes.nz - 1; for (u32 j = 0; j < kafes.ny; ++j) for (u32 i = 0; i < kafes.nx; ++i) ekleHucre(i, j, k); break; } } if (pay.empty()) return yukler; std::sort(pay.begin(), pay.end()); f64 toplamAgirlik = 0.0; for (std::size_t i = 0; i < pay.size();) { std::size_t j = i; f64 a = 0.0; while (j < pay.size() && pay[j].first == pay[i].first) { a += pay[j].second; ++j; } yukler.push_back({pay[i].first, {0.0, 0.0, 0.0}}); yukler.back().kuvvet = {a, 0.0, 0.0}; // geçici: ağırlık x alanında taşınır toplamAgirlik += a; i = j; } for (DugumKuvvet& y : yukler) { const f64 o = y.kuvvet.x / toplamAgirlik; y.kuvvet = {toplamKuvvet.x * o, toplamKuvvet.y * o, toplamKuvvet.z * o}; } return yukler; } } // namespace fiko::cae