#include "fiko/sahne/alan_secim.hpp" #include #include #include "fiko/cekirdek/olay.hpp" #include "fiko/render/referans_olcek.hpp" namespace fiko { const char* trapAdi(TrapTip t) { switch (t) { case TrapTip::Dikdortgen: return "Dikdörtgen (İçinde)"; case TrapTip::KesenDikdortgen: return "Kesen Dikdörtgen"; case TrapTip::Poligon: return "Poligon"; case TrapTip::FircaIzi: return "Fırça İzi"; case TrapTip::DisRadikdortgen: return "Dışında (İçinde)"; case TrapTip::DisKesen: return "Dışında Kesen"; } return "Alan Seçimi"; } // ---- Saf ekran-uzayi matematigi (main.cpp `ekrana` ile ayni; test paylasir) ---- bool ekranProjekte(const Mat4f& vp, const Vec3f& dunya, const Vec2f& vpMin, const Vec2f& vpBoyut, Vec2f& ekran) { const Vec4f h = vp * Vec4f{dunya.x, dunya.y, dunya.z, 1.0f}; if (h.w <= 1e-4f) return false; // kamera arkasi / klip ekran.x = vpMin.x + (h.x / h.w * 0.5f + 0.5f) * vpBoyut.x; ekran.y = vpMin.y + (h.y / h.w * 0.5f + 0.5f) * vpBoyut.y; return true; } bool noktaDikdortgende(const Vec2f& p, const Vec2f& asg, const Vec2f& azm) { return p.x >= asg.x && p.x <= azm.x && p.y >= asg.y && p.y <= azm.y; } bool noktaPoligonda(const Vec2f& p, const std::vector& poli) { const std::size_t n = poli.size(); if (n < 3) return false; bool ic = false; for (std::size_t i = 0, j = n - 1; i < n; j = i++) { const Vec2f& a = poli[i]; const Vec2f& b = poli[j]; if (((a.y > p.y) != (b.y > p.y)) && (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)) { ic = !ic; } } return ic; } bool aabbDikdortgende(const Vec2f& asgA, const Vec2f& azmA, const Vec2f& asgB, const Vec2f& azmB) { return asgA.x <= azmB.x && azmA.x >= asgB.x && asgA.y <= azmB.y && azmA.y >= asgB.y; } bool segmentAabbKesiyor(const Vec2f& a, const Vec2f& b, const Vec2f& asg, const Vec2f& azm) { // Uc icinde → kesin kesim. if (noktaDikdortgende(a, asg, azm) || noktaDikdortgende(b, asg, azm)) return true; // Segment AABB'sinin dikdortgeni hic ortmuyorsa → hayir. const Vec2f sAsg{std::min(a.x, b.x), std::min(a.y, b.y)}; const Vec2f sAzm{std::max(a.x, b.x), std::max(a.y, b.y)}; if (!aabbDikdortgende(sAsg, sAzm, asg, azm)) return false; // Segmentin dikdortgen 4 kenarindan biriyle kesisimi (yon isareti testi). const Vec2f d = b - a; auto tarafi = [&](const Vec2f& k) { return d.x * (k.y - a.y) - d.y * (k.x - a.x); }; const f32 t0 = tarafi({asg.x, asg.y}); const f32 t1 = tarafi({azm.x, asg.y}); const f32 t2 = tarafi({azm.x, azm.y}); const f32 t3 = tarafi({asg.x, azm.y}); // 4 kose segment cizgisinin ayni tarafinda ise kesisim yok. const bool hepPoz = t0 > 0 && t1 > 0 && t2 > 0 && t3 > 0; const bool hepNeg = t0 < 0 && t1 < 0 && t2 < 0 && t3 < 0; return !(hepPoz || hepNeg); } bool segmentKesisiyor(const Vec2f& p1, const Vec2f& p2, const Vec2f& p3, const Vec2f& p4) { // Iki segment (p1-p2, p3-p4) kesisiyor mu (yon-isareti / orientation testi). auto yon = [](const Vec2f& a, const Vec2f& b, const Vec2f& c) { const f32 v = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); return (v > 0.0f) ? 1 : (v < 0.0f) ? -1 : 0; }; const int d1 = yon(p3, p4, p1); const int d2 = yon(p3, p4, p2); const int d3 = yon(p1, p2, p3); const int d4 = yon(p1, p2, p4); return (d1 != d2) && (d3 != d4); } bool segmentPoligonKesiyor(const Vec2f& a, const Vec2f& b, const std::vector& poli) { if (poli.size() < 2) return false; // Uc icinde → kesin kesim (tek noktali "poligon" izi de dahil segment kabul). if (noktaPoligonda(a, poli) || noktaPoligonda(b, poli)) return true; const std::size_t n = poli.size(); for (std::size_t i = 0, j = n - 1; i < n; j = i++) if (segmentKesisiyor(a, b, poli[j], poli[i])) return true; return false; } // ---- AlanSecim yasam dongusu ---- void AlanSecim::basla(f32 x, f32 y) { if (tip == TrapTip::Poligon) { if (!aktif_) { aktif_ = true; iz_.clear(); } iz_.push_back({x, y}); return; } aktif_ = true; bas_ = {x, y}; son_ = {x, y}; if (tip == TrapTip::FircaIzi) { iz_.clear(); iz_.push_back(bas_); } } void AlanSecim::guncelle(f32 x, f32 y) { if (!aktif_) return; son_ = {x, y}; if (tip == TrapTip::FircaIzi) { // Yalniz belirgin hareket noktalari biriktir (yeniden kullanilan uye buffer). if (iz_.empty() || (iz_.back() - son_).uzunlukKare() > 9.0f) iz_.push_back(son_); } } void AlanSecim::poligonNokta(f32 x, f32 y) { if (!aktif_) { aktif_ = true; iz_.clear(); } iz_.push_back({x, y}); } void AlanSecim::iptal() { aktif_ = false; iz_.clear(); } bool AlanSecim::bolgeUyumlu(bool tamIcinde, bool kesiyor) const { switch (tip) { case TrapTip::Dikdortgen: case TrapTip::Poligon: return tamIcinde; case TrapTip::KesenDikdortgen: case TrapTip::FircaIzi: return kesiyor; case TrapTip::DisRadikdortgen: return !kesiyor; // tamamen disinda (hic degmiyor) case TrapTip::DisKesen: return !kesiyor; // bolgeye hic degmeyen } return false; } // Bir ekran-nokta kumesini bolge (dikdortgen/poligon/iz) ile test → oge sayilir mi. namespace { struct BolgeTest { TrapTip tip; Vec2f asg, azm; // dikdortgen (ve iz/poligon AABB'si) const std::vector* poli; // Poligon koseleri / FircaIzi noktalari }; // nk: aday ogenin GERCEK GEOMETRI ekran-uzayi ornekleri (mesh vertex / uc / merkez). // kenarlar: aday geometrinin ekran-uzayi kenar segmentleri (mesh ucgen kenarlari // veya kenar/iz cizgisi) — index cifti (i,i+1) her segment. Kesen traplarda bolge // kenari geometri-kenarini kesiyor mu piksel-hassas test edilir (bbox DEGIL). // Ekran-disi nokta guard: en az bir gecerli nokta gerekir. void kumeTest(const BolgeTest& b, const std::vector& nk, const std::vector>* kenarlar, bool& tamIcinde, bool& kesiyor) { tamIcinde = !nk.empty(); kesiyor = false; const bool poli = (b.tip == TrapTip::Poligon || b.tip == TrapTip::FircaIzi); auto icteMi = [&](const Vec2f& p) { return poli ? noktaPoligonda(p, *b.poli) : noktaDikdortgende(p, b.asg, b.azm); }; for (const Vec2f& p : nk) { if (icteMi(p)) kesiyor = true; // en az bir GERCEK vertex bolge icinde else tamIcinde = false; // en az bir vertex disinda → tamamen-icinde degil } // Konkav poligon duzeltmesi: tum vertexler icerideyken bir geometri kenari // cukurdan disari tasabilir → CATIA "tamamen icinde" DEGIL (gorsel == secim). if (tamIcinde && b.tip == TrapTip::Poligon && kenarlar && b.poli) { const std::vector& pg = *b.poli; const std::size_t n = pg.size(); for (const auto& e : *kenarlar) { bool tasti = false; for (std::size_t i = 0, j = n - 1; i < n && !tasti; j = i++) if (segmentKesisiyor(nk[e.first], nk[e.second], pg[j], pg[i])) tasti = true; if (tasti) { tamIcinde = false; break; } } } if (nk.empty() || kesiyor) return; // Hicbir vertex iceride degil: bolge geometrinin kenarlarini kesiyor mu? // (Bolge geometri govdesinin ICINDE ama vertexi disinda kalabilir; ya da // kenarlar bolgeyi delip gecebilir.) GERCEK KENAR-KESISIMI — bbox degil. if (b.tip == TrapTip::FircaIzi) { // Firca izi: iz segmentleri geometri kenarlarini kesiyor mu. if (b.poli && b.poli->size() >= 2 && kenarlar) { for (const auto& e : *kenarlar) { for (std::size_t i = 1; i < b.poli->size(); ++i) if (segmentKesisiyor((*b.poli)[i - 1], (*b.poli)[i], nk[e.first], nk[e.second])) { kesiyor = true; return; } } } } else if (b.tip == TrapTip::Poligon) { // Poligon kenari geometri kenarini kesiyor mu. if (kenarlar) { for (const auto& e : *kenarlar) if (segmentPoligonKesiyor(nk[e.first], nk[e.second], *b.poli)) { kesiyor = true; return; } } } else { // Dikdortgen (icinde/kesen/dis): dikdortgen kenari geometri kenarini kesiyor mu. if (kenarlar) { for (const auto& e : *kenarlar) if (segmentAabbKesiyor(nk[e.first], nk[e.second], b.asg, b.azm)) { kesiyor = true; return; } } } } } // namespace void AlanSecim::dugumTest(const Dugum& d, const Mat4f& vp, const Vec2f& vpMin, const Vec2f& vpBoyut, std::vector& cikti) const { if (!sahne_.agacGorunur(d.kimlik)) return; // Bolge tanimini kur (dikdortgen sirali koseler / poligon-iz + AABB). BolgeTest bt{}; bt.tip = tip; if (tip == TrapTip::Poligon || tip == TrapTip::FircaIzi) { bt.poli = &iz_; Vec2f asg{1e30f, 1e30f}, azm{-1e30f, -1e30f}; for (const Vec2f& p : iz_) { asg = {std::min(asg.x, p.x), std::min(asg.y, p.y)}; azm = {std::max(azm.x, p.x), std::max(azm.y, p.y)}; } bt.asg = asg; bt.azm = azm; } else { bt.poli = nullptr; bt.asg = {std::min(bas_.x, son_.x), std::min(bas_.y, son_.y)}; bt.azm = {std::max(bas_.x, son_.x), std::max(bas_.y, son_.y)}; } // Ekran-sabit datum (Duzlem/Eksen): render/GPU-pick ile AYNI olcek → secim alani // gorsel cizgiyle eslesir (aksi halde dunya-boyu gorunmez kutu!). Datum icin // efektif VP = referansGorselMvp (yerel nokta → klip); donusum icinde gomulu. // Nokta ekran-sabit (Eksen gibi) → datum olceginde; Cizgi dunya-olcekli → HARIC. const bool datum = (d.tip == DugumTip::Duzlem || d.tip == DugumTip::Eksen || d.tip == DugumTip::EksenSistemi || d.tip == DugumTip::Nokta); Mat4f etkinVp = vp; bool datumOlcekli = false; if (datum && kameraMesafe_ > 0.0f) { const Vec3f pivot{(f32)d.donusum.konum.x, (f32)d.donusum.konum.y, (f32)d.donusum.konum.z}; // EksenSistemi eksen çizgisi baskın → eksen oranıyla ölçekle (görsel = seçim). const f32 tipOran = (d.tip == DugumTip::Duzlem) ? REFERANS_ORAN_DUZLEM : REFERANS_ORAN_EKSEN; etkinVp = referansGorselMvp(vp, d.matris(), pivot, kameraMesafe_, tipOran, d.gorselOran); datumOlcekli = true; // etkinVp yerel nokta bekler (donusum gomulu) } // Yerel nokta → ekran. Datum-olcekli: etkinVp*yerel; diger: vp*(donusum*yerel). auto projekte = [&](const Vec3d& yerel, Vec2f& e) -> bool { if (datumOlcekli) return ekranProjekte(etkinVp, {(f32)yerel.x, (f32)yerel.y, (f32)yerel.z}, vpMin, vpBoyut, e); const Vec3d dw = d.donusum.noktaUygula(yerel); return ekranProjekte(vp, {(f32)dw.x, (f32)dw.y, (f32)dw.z}, vpMin, vpBoyut, e); }; // --- Sketcher segment çerçeve seçimi (CATIA: aktif sketch'te sadece eskiz öğeleri) --- // sketchKisit_ set → yalnız bu sketch düğümü, her segment (cpuKenar 2i,2i+1) ayrı test. // Aynı geonun segmentleri cpuKenar'da ARDIŞIK (sketchKenarYenile) → geo (cpuKenarAlt // vertex ordinali) bazında ardışık-tekilleştir, allocation'sız. Sonuç {sketch, geo}. if (sketchKisit_ != 0) { if (d.kimlik != sketchKisit_) return; std::vector& nk = nkBuf_; std::vector>& kenarlar = kenarBuf_; u32 sonGeo = 0xFFFFFFFFu; for (u32 i = 0; i + 1 < (u32)d.cpuKenar.size(); i += 2) { const u32 geo = (i < (u32)d.cpuKenarAlt.size()) ? d.cpuKenarAlt[i] : 0u; if (geo == sonGeo) continue; // bu geo zaten seçildi (ardışık segment bloğu) Vec2f a, b; const Vec3f& p0 = d.cpuKenar[i]; const Vec3f& p1 = d.cpuKenar[i + 1]; if (!projekte({(f64)p0.x, (f64)p0.y, (f64)p0.z}, a)) continue; if (!projekte({(f64)p1.x, (f64)p1.y, (f64)p1.z}, b)) continue; nk.clear(); nk.push_back(a); nk.push_back(b); kenarlar.clear(); kenarlar.push_back({0u, 1u}); bool ti = false, ks = false; kumeTest(bt, nk, &kenarlar, ti, ks); if (bolgeUyumlu(ti, ks)) { cikti.push_back({d.kimlik, geo}); sonGeo = geo; } } return; } // Katman A — govde ekran-AABB on-elemesi: yerelSinir 8 kosesi → ekran (datum // olcekli). Datum'da bbox dunya-boyu ama etkinVp gorsel olcegi uyguladigi icin // ekran-AABB gorsel cizgiyle orantili kucuk kalir. if (!d.yerelSinir.gecerliMi()) return; // gecersiz sinir guard (bos mesh) std::vector govdeEkran; govdeEkran.reserve(8); Vec2f gAsg{1e30f, 1e30f}, gAzm{-1e30f, -1e30f}; for (int i = 0; i < 8; ++i) { const Vec3d yerel{(i & 1) ? d.yerelSinir.azami.x : d.yerelSinir.asgari.x, (i & 2) ? d.yerelSinir.azami.y : d.yerelSinir.asgari.y, (i & 4) ? d.yerelSinir.azami.z : d.yerelSinir.asgari.z}; Vec2f e; if (!projekte(yerel, e)) continue; // ekran-disi kose atla govdeEkran.push_back(e); gAsg = {std::min(gAsg.x, e.x), std::min(gAsg.y, e.y)}; gAzm = {std::max(gAzm.x, e.x), std::max(gAzm.y, e.y)}; } if (govdeEkran.empty()) return; // tumu ekran-disi // Kesen olmayan (icinde/dis) traplar: govde ekran-AABB'si bolgeye hic degmiyorsa // hicbir alt-oge de degemez → tum dugumu ele (buyuk sahne hizlandirmasi). const bool bolgeyeDeger = (tip == TrapTip::Poligon) ? true // poligon-AABB zaten bt.asg/azm; konservatif gec : aabbDikdortgende(gAsg, gAzm, bt.asg, bt.azm); const bool dis = (tip == TrapTip::DisRadikdortgen || tip == TrapTip::DisKesen); if (!bolgeyeDeger && !dis) return; // Katman B — kip'e gore hassas test. GERCEK GEOMETRI (mesh vertex + kenar // segmenti) kullanilir; govde ekran-AABB YALNIZ Katman A on-elemesiydi. const SecimKip kip = sahne_.kip(); std::vector& nk = nkBuf_; std::vector>& kenarlar = kenarBuf_; // Tamamen-icinde erken-kabul: govde ekran-AABB bolgenin (dikdortgen) icindeyse // TUM mesh de icindedir → geometriyi taramaya gerek yok (dogru YON: bbox icinde // ise mesh icinde). Yalniz "icinde" traplarinda ve dikdortgen bolgede gecerli. const bool govdeTamIcinde = (tip == TrapTip::Dikdortgen) && noktaDikdortgende(gAsg, bt.asg, bt.azm) && noktaDikdortgende(gAzm, bt.asg, bt.azm); auto ekle = [&](u32 alt, const std::vector>* ke) { bool tamIcinde = false, kesiyor = false; kumeTest(bt, nk, ke, tamIcinde, kesiyor); if (bolgeUyumlu(tamIcinde, kesiyor)) cikti.push_back({d.kimlik, alt}); }; // Yerel nokta dizisini ekrana projekte et (nk doldur). polyline=true ise ardisik // (i,i+1) kenar segmentleri uretir; false ise nk yalniz nokta (kenar yok). auto ornekle = [&](const std::vector& yerelNk, bool polyline) { nk.clear(); kenarlar.clear(); for (const Vec3d& p : yerelNk) { Vec2f e; if (projekte(p, e)) nk.push_back(e); } if (polyline) for (u32 i = 1; i < (u32)nk.size(); ++i) kenarlar.push_back({i - 1, i}); }; // GERCEK MESH ornekleme (Govde kipi ve B-rep-yok fallback): cpuKonum vertexleri // + cpuIndeks ucgen kenarlari → nk/kenarlar. Ucgen kenarlari kesen testler icin // belirleyici (gercek siluet/govde kenarlari), bbox DEGIL. auto meshOrnekle = [&]() -> bool { nk.clear(); kenarlar.clear(); if (d.cpuKonum.empty()) return false; // gercek mesh yok → cpuKenar'a dus const std::size_t vs = d.cpuKonum.size(); // Vertex projeksiyonu (indeks korunur; ekran-disi vertex icin sentinel). nk.reserve(vs); for (const Vec3f& v : d.cpuKonum) { Vec2f e{1e30f, 1e30f}; projekte({(f64)v.x, (f64)v.y, (f64)v.z}, e); nk.push_back(e); } // Ucgen kenarlari (kesen testler icin gercek siluet/govde kenarlari). for (std::size_t i = 0; i + 2 < d.cpuIndeks.size(); i += 3) { const u32 a = d.cpuIndeks[i], b2 = d.cpuIndeks[i + 1], c = d.cpuIndeks[i + 2]; if (a >= vs || b2 >= vs || c >= vs) continue; kenarlar.push_back({a, b2}); kenarlar.push_back({b2, c}); kenarlar.push_back({c, a}); } return !nk.empty(); }; // KENAR ornekleme (mesh'siz datum/Tel: Duzlem kare kenari, Eksen tek cizgi, Tel // OCCT edge). cpuKenar LINE_LIST (2i,2i+1 = bir segment) → gercek cizgi testi. // Datum'da projekte() ekran-sabit olcegi uygular → gorunmez dunya-boyu kutu YOK. auto kenarOrnekle = [&]() -> bool { nk.clear(); kenarlar.clear(); if (d.cpuKenar.size() < 2) return false; // cizgi de yok → seçilmez nk.reserve(d.cpuKenar.size()); for (const Vec3f& v : d.cpuKenar) { Vec2f e{1e30f, 1e30f}; projekte({(f64)v.x, (f64)v.y, (f64)v.z}, e); nk.push_back(e); } for (u32 i = 0; i + 1 < (u32)nk.size(); i += 2) kenarlar.push_back({i, i + 1}); return !nk.empty(); }; // Mesh yoksa kenar'a dus (datum/Tel); ikisi de yoksa seçilmez (görünmez kutu YOK). auto govdeGeomOrnekle = [&]() -> bool { if (!d.cpuIndeks.empty() && meshOrnekle()) return true; return kenarOrnekle(); }; if (kip == SecimKip::Govde) { // Tamamen-icinde dikdortgen: (datum-olcekli) bbox bolgede → geometri icinde. if (govdeTamIcinde) { cikti.push_back({d.kimlik, 0}); return; } if (!govdeGeomOrnekle()) return; // gercek geometri yok → seçilmez ekle(0, &kenarlar); return; } const geometri::BrepGovde* brep = d.brep.get(); if (!brep || !brep->gecerliMi()) { // B-rep yok (referans/datum/CAM cizgi dugumu): GERCEK MESH veya KENAR ile tek oge. if (govdeTamIcinde) { cikti.push_back({d.kimlik, 0}); return; } if (!govdeGeomOrnekle()) return; ekle(0, &kenarlar); return; } // Ordinal say bilinmiyor → accessor false donene kadar gez (sinirli). if (kip == SecimKip::Yuz) { for (u32 ord = 1; ord <= 4096; ++ord) { const std::vector yp = brep->yuzNoktalari(ord); if (yp.empty()) break; // ordinal bitti ornekle(yp, /*polyline*/ true); // yuz sinir ornekleri: kenar segmentleri if (!nk.empty()) ekle(ord, &kenarlar); } } else if (kip == SecimKip::Kenar) { for (u32 ord = 1; ord <= 8192; ++ord) { const std::vector kp = brep->kenarNoktalari(ord); if (kp.empty()) break; ornekle(kp, /*polyline*/ true); // kenar polyline: ardisik segmentler if (!nk.empty()) ekle(ord, &kenarlar); } } else { // Nokta Vec3d konum; for (u32 ord = 1; ord <= 16384; ++ord) { if (!brep->noktaKonum(ord, konum)) break; ornekle({konum}, /*polyline*/ false); // tek nokta: kenar yok if (!nk.empty()) ekle(ord, nullptr); } } } void AlanSecim::adaylariTopla(const Mat4f& vp, const Vec2f& vpMin, const Vec2f& vpBoyut, std::vector& cikti) const { cikti.clear(); // Sketcher çerçeve seçimi: YALNIZ aktif sketch düğümü (3D katıları taramaya gerek yok). if (sketchKisit_ != 0) { if (const Dugum* d = sahne_.bul(sketchKisit_)) dugumTest(*d, vp, vpMin, vpBoyut, cikti); return; } for (const Dugum& d : sahne_.dugumler()) dugumTest(d, vp, vpMin, vpBoyut, cikti); } u32 AlanSecim::adaySayisi(const Mat4f& vp, const Vec2f& vpMin, const Vec2f& vpBoyut) const { if (!aktif_) return 0; std::vector aday; adaylariTopla(vp, vpMin, vpBoyut, aday); return (u32)aday.size(); } u32 AlanSecim::bitir(const Mat4f& vp, const Vec2f& vpMin, const Vec2f& vpBoyut, TrapModifier m) { if (!aktif_) return 0; std::vector aday; adaylariTopla(vp, vpMin, vpBoyut, aday); if (m == TrapModifier::Degistir && !aday.empty()) sahne_.secimTemizle(); for (const SecimOge& o : aday) { if (m == TrapModifier::Cikar) sahne_.secimCikar(o); else if (m == TrapModifier::Ekle) // K4: Ctrl = TOGGLE (tek-tık/ağaç ile tutarlı). Zaten seçiliyse çıkar. (sahne_.seciliMi(o) ? sahne_.secimCikar(o) : sahne_.sec(o, /*coklu*/ true)); else sahne_.sec(o, /*coklu*/ true); // Degistir: temizlendi → birikir } const u32 adet = (u32)aday.size(); iptal(); olaylar_.yayinla(AlanSecimYapildi{(u32)tip, adet}); return adet; } std::vector AlanSecim::bitirSketch(const Mat4f& vp, const Vec2f& vpMin, const Vec2f& vpBoyut) { std::vector geolar; if (!aktif_ || sketchKisit_ == 0) { iptal(); return geolar; } std::vector aday; adaylariTopla(vp, vpMin, vpBoyut, aday); // {sketchKimlik, geoKimlik} geolar.reserve(aday.size()); for (const SecimOge& o : aday) if (o.alt != 0) geolar.push_back(o.alt); // eskiz geometri kimliği const u32 adet = (u32)geolar.size(); iptal(); // trap durumunu kapat (3D sahne seçimi KİRLENMEDİ) olaylar_.yayinla(AlanSecimYapildi{(u32)tip, adet}); return geolar; } } // namespace fiko