#include #include #include #include #include #include #include #include "fiko/io/web_io.hpp" #include "simdjson.h" // ============================================================================ // .FikoOp (Fikoperasyon) okuyucu — gzip (miniz) → JSON (simdjson On-Demand) → // eksen + mesh cizimler + delta-decode takim yollari. Spec §1. // Buyuk dosya (~53 MB JSON): On-Demand akisli iterasyon, DOM materialize YOK. // ============================================================================ namespace fiko::io { namespace sj = simdjson; namespace { constexpr f64 INT_SCALE = 100000.0; // Web INT/FLOAT ayrimi (spec §1.5.2): tam sayi VE esik-ustu ise int → olcekle. inline f64 koordCoz(f64 v) { // esik 1000 (koordinat) return (v == std::floor(v) && std::fabs(v) > 1000.0) ? v / INT_SCALE : v; } // Takim yonu (ty) — per-component decode. Encoder ×100000 (FikoVektorTakimYol3d // _tyInt). Web decoder ty[0]'a bakar; ty[0]=0 (duz asagi/yukari) ise algilayamaz // ve ham birakir, sonra kullanirken normalize eder. Per-component ÷100000 encoder // niyetiyle birebir (5-eksende de ayni) + geometrik dogru → tercih edildi. inline f64 tyCoz(f64 v) { // esik 100 (takim yonu) return (v == std::floor(v) && std::fabs(v) > 100.0) ? v / INT_SCALE : v; } inline f64 sureCoz(f64 v) { // esik 100 → ÷100 (saniye) return (v == std::floor(v) && v > 100.0) ? v / 100.0 : v; } // hex renk "#rrggbb" → Vec3f (0..1). Gecersiz → beyaz. Vec3f hexRenk(std::string_view s) { if (s.size() >= 7 && s[0] == '#') { auto h = [&](int i) -> int { char c = s[i]; if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; }; const int r = h(1) * 16 + h(2), g = h(3) * 16 + h(4), b = h(5) * 16 + h(6); return {r / 255.0f, g / 255.0f, b / 255.0f}; } return {1, 1, 1}; } // Vec3d oku {"x":..,"y":..,"z":..} (On-Demand obje). Hata → false. bool vec3Oku(sj::ondemand::value v, Vec3d& out) { double x, y, z; auto o = v.get_object(); if (o.error()) return false; if (o["x"].get(x) || o["y"].get(y) || o["z"].get(z)) return false; out = {x, y, z}; return true; } // 4x4 matrix (column-major, THREE) → mesh vertexlerine dogrudan uygula. struct Mat4 { f64 e[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; Vec3d uygula(Vec3d p) const { // THREE.Matrix4.elements column-major: e[0..3]=kol0, e[12..14]=ceviri. return {e[0] * p.x + e[4] * p.y + e[8] * p.z + e[12], e[1] * p.x + e[5] * p.y + e[9] * p.z + e[13], e[2] * p.x + e[6] * p.y + e[10] * p.z + e[14]}; } bool birimDisi() const { return e[12] != 0 || e[13] != 0 || e[14] != 0 || e[0] != 1 || e[5] != 1 || e[10] != 1 || e[1] != 0 || e[2] != 0 || e[4] != 0 || e[6] != 0 || e[8] != 0 || e[9] != 0; } }; // matrix [16] dizisi oku. bool matrixOku(sj::ondemand::value v, Mat4& m) { auto arr = v.get_array(); if (arr.error()) return false; int i = 0; for (auto el : arr) { if (i >= 16) break; double d; if (el.get(d)) return false; m.e[i++] = d; } return i == 16; } // BufferGeometry attribute array → düz float dizisi. position/normal/color. // array bir JSON dizisi degilse (string/base64) false → sikistirilmis geometri. bool attrArrayOku(sj::ondemand::object data, const char* attr, std::vector& cikti, bool& sikistirilmis) { auto attrs = data["attributes"]; if (attrs.error()) return false; auto ao = attrs.get_object(); if (ao.error()) return false; auto a = ao[attr]; if (a.error()) return true; // attribute yok (color opsiyonel) → bos, hata degil auto aobj = a.get_object(); if (aobj.error()) return false; auto arr = aobj["array"]; if (arr.error()) return false; auto asarr = arr.get_array(); if (asarr.error()) { sikistirilmis = true; return false; } // string/base64 for (auto el : asarr) { double d; if (el.get(d)) return false; cikti.push_back((f32)d); } return true; } inline bool sonlu3(f32 x, f32 y, f32 z) { return std::isfinite(x) && std::isfinite(y) && std::isfinite(z); } // BufferGeometry objesinden position dizisini cek (yalin["...":geo] value). // value On-Demand ileri-yonlu: yalniz position okunur (data→attributes→position). bool geoPosOku(sj::ondemand::value geo, std::vector& pos, bool& sikistirilmis) { auto o = geo.get_object(); if (o.error()) return false; auto data = o.value_unsafe()["data"]; if (data.error()) return false; auto datao = data.value_unsafe().get_object(); if (datao.error()) return false; return attrArrayOku(datao.value_unsafe(), "position", pos, sikistirilmis); } // Bir FikoCizimYalin → HazirCizim. ONEMLI (simdjson On-Demand): alanlar dosya // SIRASINDA okunur (yuzeyler → sinirlar → noktalar → matrix → isim). matrix // mesh'ten SONRA geldigi icin ham konumlar tamponlanip matrix okununca bakilir. bool cizimYalinOku(sj::ondemand::object yalin, HazirCizim& c, bool& sikistirilmis) { std::vector yuzPos, kenPos; Mat4 mat; for (auto alan : yalin) { auto key = alan.unescaped_key(); if (key.error()) continue; const std::string_view k = key.value_unsafe(); if (k == "yuzeyler") { geoPosOku(alan.value(), yuzPos, sikistirilmis); } else if (k == "sinirlar") { bool c2 = false; geoPosOku(alan.value(), kenPos, c2); } else if (k == "matrix") { matrixOku(alan.value(), mat); } else if (k == "isim") { std::string_view isim; if (!alan.value().get(isim)) c.ad = std::string(isim); } // noktalar/konumlar/type/uuid → atla (deger otomatik tuketilir) } const bool bak = mat.birimDisi(); // YÜZEY mesh (non-indexed ucgen soup → duz normal) if (!yuzPos.empty()) { MeshVeri& mv = c.yuzey; mv.vertexler.reserve(yuzPos.size() / 3); for (std::size_t i = 0; i + 2 < yuzPos.size(); i += 3) { Vec3d p{yuzPos[i], yuzPos[i + 1], yuzPos[i + 2]}; if (bak) p = mat.uygula(p); Vertex v{}; v.konum[0] = (f32)p.x; v.konum[1] = (f32)p.y; v.konum[2] = (f32)p.z; v.renk[0] = c.renk.x; v.renk[1] = c.renk.y; v.renk[2] = c.renk.z; v.altKimlik = 1; if (!sonlu3(v.konum[0], v.konum[1], v.konum[2])) continue; mv.vertexler.push_back(v); mv.sinir.genislet({v.konum[0], v.konum[1], v.konum[2]}); } mv.indeksler.reserve(mv.vertexler.size()); for (std::size_t i = 0; i + 2 < mv.vertexler.size(); i += 3) { Vec3d a{mv.vertexler[i].konum[0], mv.vertexler[i].konum[1], mv.vertexler[i].konum[2]}; Vec3d b{mv.vertexler[i + 1].konum[0], mv.vertexler[i + 1].konum[1], mv.vertexler[i + 1].konum[2]}; Vec3d d{mv.vertexler[i + 2].konum[0], mv.vertexler[i + 2].konum[1], mv.vertexler[i + 2].konum[2]}; Vec3d n = (b - a).capraz(d - a); const f64 nl = std::sqrt(n.x * n.x + n.y * n.y + n.z * n.z); n = (nl > 1e-12) ? n / nl : Vec3d{0, 0, 1}; for (int kk = 0; kk < 3; ++kk) { mv.vertexler[i + kk].normal[0] = (f32)n.x; mv.vertexler[i + kk].normal[1] = (f32)n.y; mv.vertexler[i + kk].normal[2] = (f32)n.z; } mv.indeksler.push_back((u32)i); mv.indeksler.push_back((u32)i + 1); mv.indeksler.push_back((u32)i + 2); } mv.yuzSayi = 1; } // KENAR cizgileri (LineSegments position → 2'serli LINE_LIST) if (!kenPos.empty()) { KenarVeri& kv = c.kenar; kv.vertexler.reserve(kenPos.size() / 3); for (std::size_t i = 0; i + 2 < kenPos.size(); i += 3) { Vec3d p{kenPos[i], kenPos[i + 1], kenPos[i + 2]}; if (bak) p = mat.uygula(p); KenarVertex v{}; v.konum[0] = (f32)p.x; v.konum[1] = (f32)p.y; v.konum[2] = (f32)p.z; v.altKimlik = 1; if (sonlu3(v.konum[0], v.konum[1], v.konum[2])) kv.vertexler.push_back(v); } kv.kenarSayi = (u32)(kv.vertexler.size() / 2); } return !c.yuzey.vertexler.empty() || !c.kenar.vertexler.empty(); } // cizimler.children[].children[] → FikoCizimYalin'lar (2 seviye grup). void cizimlerOku(sj::ondemand::value cizimler, FikoOpSonuc& sonuc) { auto co = cizimler.get_object(); if (co.error()) return; auto ch = co.value_unsafe()["children"]; if (ch.error()) return; auto cha = ch.value_unsafe().get_array(); if (cha.error()) return; for (auto grup : cha) { auto go = grup.get_object(); if (go.error()) continue; auto gch = go.value_unsafe()["children"]; if (gch.error()) { // child dogrudan FikoCizimYalin olabilir HazirCizim c; bool comp = false; if (cizimYalinOku(go.value_unsafe(), c, comp)) sonuc.cizimler.push_back(std::move(c)); else if (comp) ++sonuc.atlananCizim; continue; } auto gcha = gch.value_unsafe().get_array(); if (gcha.error()) continue; for (auto yalin : gcha) { auto yo = yalin.get_object(); if (yo.error()) continue; HazirCizim c; bool comp = false; if (cizimYalinOku(yo.value_unsafe(), c, comp)) sonuc.cizimler.push_back(std::move(c)); else if (comp) ++sonuc.atlananCizim; } } } // eksen objesi → merkez + xYon/yYon/zYon. Alanlar DOSYA SIRASINDA gezilir // (simdjson On-Demand ileri-yonlu — sirasiz erisim OUT_OF_ORDER hatasi riski). void eksenOku(sj::ondemand::value eksen, FikoOpSonuc& sonuc) { auto eo = eksen.get_object(); if (eo.error()) return; for (auto alan : eo.value_unsafe()) { auto key = alan.unescaped_key(); if (key.error()) continue; const std::string_view k = key.value_unsafe(); Vec3d t; if (k == "merkez") { if (vec3Oku(alan.value(), t)) sonuc.eksenMerkez = t; } else if (k == "xYon") { if (vec3Oku(alan.value(), t)) sonuc.eksenX = t; } else if (k == "yYon") { if (vec3Oku(alan.value(), t)) sonuc.eksenY = t; } else if (k == "zYon") { if (vec3Oku(alan.value(), t)) sonuc.eksenZ = t; } } } // Tek nokta objesini delta-decode et (son* durum guncellenir). struct DeltaDurum { FikoOpTakim takim; bool takimVar = false; Vec3d ty{0, 0, 1}; f64 hiz = 0, devir = 0; std::string tip = "linear"; Vec3f renk{1, 1, 1}; f64 guncelSure = 0, toplamSure = 0; std::string ekIsim; }; // 3-eleman sayi dizisi oku (eksenKoord baz / ty). Donen: okunan eleman sayisi. int arr3Oku(sj::ondemand::value v, double out[3]) { auto arr = v.get_array(); if (arr.error()) return 0; int i = 0; for (auto el : arr) { if (i < 3) { double d; if (!el.get(d)) out[i] = d; } ++i; } return i; } // takimYolu.noktalar[] → delta decode → fp.noktalar (akisli, materialize YOK). // takimIndeks: benzersiz takimlari sonuc.takimlar'a toplar. void takimYoluOku(sj::ondemand::value takimYolu, FikoOpProgram& fp, std::unordered_map& takimIndeks, FikoOpSonuc& sonuc) { for (auto tya : takimYolu.get_object()) { auto tk2 = tya.unescaped_key(); if (tk2.error() || tk2.value_unsafe() != "noktalar") continue; auto nka = tya.value().get_array(); if (nka.error()) continue; DeltaDurum s; for (auto nokta : nka.value_unsafe()) { auto no = nokta.get_object(); if (no.error()) continue; FikoOpNokta n; // Nokta alanlarini DOSYA SIRASINDA gez. for (auto na : no.value_unsafe()) { auto nk2 = na.unescaped_key(); if (nk2.error()) continue; const std::string_view f = nk2.value_unsafe(); if (f == "x") { double d; if (!na.value().get(d)) n.konum.x = koordCoz(d); } else if (f == "y") { double d; if (!na.value().get(d)) n.konum.y = koordCoz(d); } else if (f == "z") { double d; if (!na.value().get(d)) n.konum.z = koordCoz(d); } else if (f == "takim") { auto tko = na.value().get_object(); if (!tko.error()) { double cap = 0, rad = 0, boy = 0, kb = 0; for (auto ta : tko.value_unsafe()) { auto tak = ta.unescaped_key(); if (tak.error()) continue; const std::string_view tf = tak.value_unsafe(); double d; if (tf == "cap") { if (!ta.value().get(d)) cap = d; } else if (tf == "radius") { if (!ta.value().get(d)) rad = d; } else if (tf == "boy") { if (!ta.value().get(d)) boy = d; } else if (tf == "kesmeBoy") { if (!ta.value().get(d)) kb = d; } } const bool capInt = (cap == std::floor(cap) && cap > 100.0); s.takim.cap = capInt ? cap / INT_SCALE : cap; s.takim.radius = capInt ? rad / INT_SCALE : rad; s.takim.boy = capInt ? boy / 100.0 : boy; s.takim.kesmeBoy = capInt ? kb / 100.0 : kb; s.takimVar = true; } } else if (f == "ekBilgiler") { auto ekbo = na.value().get_object(); if (!ekbo.error()) for (auto ba : ekbo.value_unsafe()) { auto bk = ba.unescaped_key(); if (!bk.error() && bk.value_unsafe() == "ismi") { std::string_view ismi; if (!ba.value().get(ismi)) { s.ekIsim = std::string(ismi); s.takim.ismi = s.ekIsim; } } } } else if (f == "hiz") { double d; if (!na.value().get(d)) s.hiz = d; } else if (f == "devir") { double d; if (!na.value().get(d)) s.devir = d; } else if (f == "tip") { std::string_view tsv; if (!na.value().get(tsv)) s.tip = std::string(tsv); } else if (f == "toplamSure") { double d; if (!na.value().get(d)) s.toplamSure = sureCoz(d); } else if (f == "ty") { double v[3]; if (arr3Oku(na.value(), v) >= 3) s.ty = {tyCoz(v[0]), tyCoz(v[1]), tyCoz(v[2])}; } else if (f == "r") { std::string_view rsv; if (!na.value().get(rsv)) s.renk = hexRenk(rsv); } else if (f == "guncelSure") { double d; if (!na.value().get(d)) s.guncelSure = sureCoz(d); } } n.takimYonu = s.ty; n.hiz = s.hiz; n.devir = s.devir; n.tip = s.tip; n.renk = s.renk; n.guncelSure = s.guncelSure; n.toplamSure = s.toplamSure; // Benzersiz takim → sonuc.takimlar if (s.takimVar) { const std::string ad = s.takim.ismi.empty() ? ("cap" + std::to_string((int)std::round(s.takim.cap * 1000))) : s.takim.ismi; auto it = takimIndeks.find(ad); if (it == takimIndeks.end()) { n.takimIndeks = (int)sonuc.takimlar.size(); takimIndeks[ad] = n.takimIndeks; sonuc.takimlar.push_back(s.takim); } else { n.takimIndeks = it->second; } } if (std::isfinite(n.konum.x) && std::isfinite(n.konum.y) && std::isfinite(n.konum.z)) fp.noktalar.push_back(std::move(n)); } // nokta dongusu } // takimYolu alan dongusu (noktalar) } // Bir program dugumu (Fikogram / FikoAPTCAT): takimYolu alani VARSA program // uretir; children[] VARSA ozyineler (Fikogramlar→Fikogram→FikoAPTCAT nesting). // Boylece web'in hiyerarsik programlar agacindaki TUM takim yollari toplanir. void programDugumOku(sj::ondemand::value node, FikoOpSonuc& sonuc, std::unordered_map& takimIndeks) { auto o = node.get_object(); if (o.error()) return; FikoOpProgram fp; bool takimYoluVar = false; // Alanlari DOSYA SIRASINDA gez (On-Demand ileri-yonlu). for (auto alan : o.value_unsafe()) { auto key = alan.unescaped_key(); if (key.error()) continue; const std::string_view k = key.value_unsafe(); if (k == "isim") { std::string_view sv; if (!alan.value().get(sv)) fp.ad = std::string(sv); } else if (k == "operasyonIsmi") { std::string_view sv; if (!alan.value().get(sv)) fp.operasyonAdi = std::string(sv); } else if (k == "tarihi") { std::string_view sv; if (!alan.value().get(sv)) fp.tarih = std::string(sv); } else if (k == "kureMerkezi") { bool km; if (!alan.value().get(km)) fp.kureMerkezi = km; } else if (k == "eksenKoord") { for (auto ea : alan.value().get_object()) { auto ek = ea.unescaped_key(); if (ek.error()) continue; double v[3]; const std::string_view ekk = ek.value_unsafe(); if (arr3Oku(ea.value(), v) >= 3) { if (ekk == "x") fp.cerceveX = {v[0], v[1], v[2]}; else if (ekk == "y") fp.cerceveY = {v[0], v[1], v[2]}; else if (ekk == "z") fp.cerceveZ = {v[0], v[1], v[2]}; else if (ekk == "o") fp.cerceveOrigin = {v[0], v[1], v[2]}; } } } else if (k == "takimYolu") { takimYoluOku(alan.value(), fp, takimIndeks, sonuc); takimYoluVar = true; } else if (k == "children") { // Fikogram gibi ara grup → ozyinele (kendi takimYolu'su olan cocuklar). auto cha = alan.value().get_array(); if (!cha.error()) for (auto cocuk : cha.value_unsafe()) programDugumOku(cocuk.value_unsafe(), sonuc, takimIndeks); } } if (takimYoluVar) { sonuc.toplamNokta += fp.noktalar.size(); sonuc.programlar.push_back(std::move(fp)); } } // programlar (Fikogramlar).children[] → ozyinelemeli program dugumleri. void programlarOku(sj::ondemand::value programlar, FikoOpSonuc& sonuc) { auto po = programlar.get_object(); if (po.error()) return; auto ch = po.value_unsafe()["children"]; if (ch.error()) return; auto cha = ch.value_unsafe().get_array(); if (cha.error()) return; std::unordered_map takimIndeks; // ismi → sonuc.takimlar idx for (auto prog : cha.value_unsafe()) programDugumOku(prog.value_unsafe(), sonuc, takimIndeks); } } // namespace FikoOpSonuc fikoOpOku(const std::string& yol, const IlerlemeGeri& ilerle) { FikoOpSonuc sonuc; std::ifstream f(yol, std::ios::binary); if (!f) { sonuc.hata = "FikoOp dosyasi acilamadi: " + yol; return sonuc; } std::ostringstream ss; ss << f.rdbuf(); const std::string ham = ss.str(); if (ilerle) ilerle(0.1f, "FikoOp aciliyor (gzip)"); std::string json; std::string hata; if (!gzipCoz(ham, json, hata)) { sonuc.hata = hata; return sonuc; } if (ilerle) ilerle(0.3f, "FikoOp JSON cozumleniyor"); // simdjson padded gereksinimi: SIMDJSON_PADDING kadar tampon. json.reserve(json.size() + sj::SIMDJSON_PADDING); sj::ondemand::parser parser; sj::padded_string_view pjson(json.data(), json.size(), json.capacity()); sj::ondemand::document doc; auto err = parser.iterate(pjson).get(doc); if (err) { sonuc.hata = std::string("FikoOp JSON hatasi: ") + sj::error_message(err); return sonuc; } try { auto root = doc.get_object(); if (root.error()) { sonuc.hata = "FikoOp kok obje degil"; return sonuc; } std::string_view isim; if (!root.value_unsafe()["isim"].get(isim)) sonuc.ad = std::string(isim); // NOT (On-Demand): alanlar JSON'daki SIRAYLA okunmali (eksen→cizimler→ // programlar dosyada bu sirada). Sira dosya duzenine gore. auto eksen = root.value_unsafe()["eksen"]; if (!eksen.error()) eksenOku(eksen.value_unsafe(), sonuc); if (ilerle) ilerle(0.5f, "Mesh cizimler okunuyor"); auto cizimler = root.value_unsafe()["cizimler"]; if (!cizimler.error()) cizimlerOku(cizimler.value_unsafe(), sonuc); if (ilerle) ilerle(0.7f, "Takim yollari okunuyor"); auto programlar = root.value_unsafe()["programlar"]; if (!programlar.error()) programlarOku(programlar.value_unsafe(), sonuc); } catch (const sj::simdjson_error& e) { sonuc.hata = std::string("FikoOp cozumleme hatasi: ") + e.what(); return sonuc; } if (ilerle) ilerle(1.0f, "FikoOp yuklendi"); sonuc.basarili = true; return sonuc; } } // namespace fiko::io