#include #include #include #include #include #include #include #include "fiko/io/web_io.hpp" // ============================================================================ // VRML97 (V2.0 utf8) mesh okuyucu — CATIA WRL export. Spec §3 (Secenek B: // bagimliliksiz basit parser). IndexedFaceSet + IndexedLineSet + Material. // Transform hiyerarsisi (translation/rotation/scale) vertexlere bakilir // (ornek: kok Transform scale 0.001 → mm→m). DEF/USE: Material renkleri ve // geometri node'lari hatirlanir. Recursive-descent: dogal derinlik/donusum. // ============================================================================ namespace fiko::io { namespace { // ---- Tokenizer: whitespace + , ayirici; { } [ ] tekil token; # yorum ------- struct Tokenlar { std::string metin; const char* p; const char* son; explicit Tokenlar(std::string s) : metin(std::move(s)) { p = metin.data(); son = p + metin.size(); } static bool bosluk(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ','; } static bool tekil(char c) { return c == '{' || c == '}' || c == '[' || c == ']'; } std::string sonraki() { while (p < son) { const char c = *p; if (bosluk(c)) { ++p; continue; } if (c == '#') { while (p < son && *p != '\n') ++p; continue; } break; } if (p >= son) return {}; if (tekil(*p)) return std::string(1, *p++); const char* bas = p; while (p < son && !bosluk(*p) && !tekil(*p) && *p != '#') ++p; return std::string(bas, p); } }; // 3x4 satir-major donusum (dunya = M * yerel). struct Don { f64 m[3][4] = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}}; Vec3d uygula(Vec3d v) const { return {m[0][0] * v.x + m[0][1] * v.y + m[0][2] * v.z + m[0][3], m[1][0] * v.x + m[1][1] * v.y + m[1][2] * v.z + m[1][3], m[2][0] * v.x + m[2][1] * v.y + m[2][2] * v.z + m[2][3]}; } static Don carp(const Don& a, const Don& b) { Don r; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) r.m[i][j] = a.m[i][0] * b.m[0][j] + a.m[i][1] * b.m[1][j] + a.m[i][2] * b.m[2][j]; r.m[i][3] = a.m[i][0] * b.m[0][3] + a.m[i][1] * b.m[1][3] + a.m[i][2] * b.m[2][3] + a.m[i][3]; } return r; } static Don trs(Vec3d t, Vec3d eksen, f64 aci, Vec3d olcek) { Don r; const f64 l = std::sqrt(eksen.x * eksen.x + eksen.y * eksen.y + eksen.z * eksen.z); f64 rot[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; if (l > 1e-9 && std::fabs(aci) > 1e-12) { const f64 x = eksen.x / l, y = eksen.y / l, z = eksen.z / l; const f64 c = std::cos(aci), s = std::sin(aci), t1 = 1 - c; rot[0][0] = c + x * x * t1; rot[0][1] = x * y * t1 - z * s; rot[0][2] = x * z * t1 + y * s; rot[1][0] = y * x * t1 + z * s; rot[1][1] = c + y * y * t1; rot[1][2] = y * z * t1 - x * s; rot[2][0] = z * x * t1 - y * s; rot[2][1] = z * y * t1 + x * s; rot[2][2] = c + z * z * t1; } const f64 sc[3] = {olcek.x, olcek.y, olcek.z}; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) r.m[i][j] = rot[i][j] * sc[j]; r.m[0][3] = t.x; r.m[1][3] = t.y; r.m[2][3] = t.z; return r; } }; inline bool sonlu(const Vec3d& v) { return std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z); } // Recursive-descent VRML parser durumu. struct Parser { Tokenlar tk; WrlSonuc& sonuc; const IlerlemeGeri& ilerle; std::unordered_map materialTablo; // DEF Material → renk std::unordered_map geoTablo; // DEF geometri → cizim std::size_t sayac = 0; std::string ileriToken; // "geri itme" (tek seviye lookahead tamponu) Parser(std::string s, WrlSonuc& r, const IlerlemeGeri& il) : tk(std::move(s)), sonuc(r), ilerle(il) {} std::string al() { if (!ileriToken.empty()) { std::string t = std::move(ileriToken); ileriToken.clear(); return t; } return tk.sonraki(); } void geriit(std::string t) { ileriToken = std::move(t); } // '[' zaten yutulmus — ']' gorunene dek sayi topla. void sayiListesi(std::vector& cikti) { std::string t; while (!(t = al()).empty()) { if (t == "]") break; cikti.push_back(std::strtod(t.c_str(), nullptr)); } } // '{' zaten yutulmus — dengeli '}' e dek atla (ic [ ] dahil). void blokAtla() { int d = 1; std::string t; while (d > 0 && !(t = al()).empty()) { if (t == "{") ++d; else if (t == "}") --d; } } // Bir alan degerini atla: '[' → ']' e dek, '{' → '}' e dek, aksi → tek token. void degerAtla() { std::string t = al(); if (t == "[") { int d = 1; std::string x; while (d > 0 && !(x = al()).empty()) { if (x == "[") ++d; else if (x == "]") --d; } } else if (t == "{") { blokAtla(); } else if (t == "USE") { al(); // USE } else if (t == "DEF") { al(); // DEF {...} degerAtla(); // Node deger olarak atla } else { // Sayi/bool/string VEYA "NodeType { ... }" — sonrasi '{' ise node blogu. std::string nx = al(); if (nx == "{") blokAtla(); else if (nx == "[") { int d = 1; std::string x; while (d > 0 && !(x = al()).empty()) { if (x == "[") ++d; else if (x == "]") --d; } } else geriit(nx); // basit skaler → sonraki token'i geri ver } } // Coordinate/Normal { point/vector [ ... ] } → sayilari 3'erli vertex. void coordOku(std::vector& noktalar) { if (al() != "{") return; std::string t; while (!(t = al()).empty()) { if (t == "}") break; if (t == "point") { if (al() != "[") continue; std::vector ham; sayiListesi(ham); noktalar.reserve(noktalar.size() + ham.size() / 3); for (std::size_t i = 0; i + 2 < ham.size(); i += 3) noktalar.push_back({ham[i], ham[i + 1], ham[i + 2]}); } else { degerAtla(); } } } // IndexedFaceSet/IndexedLineSet govdesi ('{' yutulmus varsayilir). HazirCizim geoOku(bool yuz, const Don& don, Vec3f renk) { HazirCizim c; c.renk = renk; std::vector pts; std::vector idx; std::string t; while (!(t = al()).empty()) { if (t == "}") break; if (t == "coord") { std::string nx = al(); if (nx == "Coordinate") coordOku(pts); else if (nx == "USE") al(); else if (nx == "DEF") { al(); if (al() == "Coordinate") coordOku(pts); } else geriit(nx); } else if (t == "coordIndex") { if (al() != "[") continue; sayiListesi(idx); } else { degerAtla(); // normal/color/texCoord/solid/ccw... } } if (yuz) { MeshVeri& mv = c.yuzey; std::vector poly; auto bitir = [&]() { if (poly.size() >= 3) { for (std::size_t k = 1; k + 1 < poly.size(); ++k) { const u32 a = poly[0], b = poly[k], d2 = poly[k + 1]; if (a >= pts.size() || b >= pts.size() || d2 >= pts.size()) continue; const Vec3d p0 = don.uygula(pts[a]); const Vec3d p1 = don.uygula(pts[b]); const Vec3d p2 = don.uygula(pts[d2]); if (!sonlu(p0) || !sonlu(p1) || !sonlu(p2)) continue; Vec3d n = (p1 - p0).capraz(p2 - p0); 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}; const u32 taban = (u32)mv.vertexler.size(); const Vec3d ps[3] = {p0, p1, p2}; for (const Vec3d& p : ps) { Vertex v{}; v.konum[0] = (f32)p.x; v.konum[1] = (f32)p.y; v.konum[2] = (f32)p.z; v.normal[0] = (f32)n.x; v.normal[1] = (f32)n.y; v.normal[2] = (f32)n.z; v.renk[0] = c.renk.x; v.renk[1] = c.renk.y; v.renk[2] = c.renk.z; v.altKimlik = 1; mv.vertexler.push_back(v); mv.sinir.genislet({v.konum[0], v.konum[1], v.konum[2]}); } mv.indeksler.push_back(taban); mv.indeksler.push_back(taban + 1); mv.indeksler.push_back(taban + 2); } } poly.clear(); }; for (f64 f : idx) { if (f < 0) bitir(); else poly.push_back((u32)(i64)f); } bitir(); mv.yuzSayi = 1; } else { KenarVeri& kv = c.kenar; i64 onceki = -1; for (f64 f : idx) { const i64 ii = (i64)f; if (ii < 0 || (std::size_t)ii >= pts.size()) { onceki = -1; continue; } if (onceki >= 0) { const Vec3d a = don.uygula(pts[onceki]); const Vec3d b = don.uygula(pts[ii]); if (sonlu(a) && sonlu(b)) { KenarVertex va{}, vb{}; va.konum[0] = (f32)a.x; va.konum[1] = (f32)a.y; va.konum[2] = (f32)a.z; va.altKimlik = 1; vb.konum[0] = (f32)b.x; vb.konum[1] = (f32)b.y; vb.konum[2] = (f32)b.z; vb.altKimlik = 1; kv.vertexler.push_back(va); kv.vertexler.push_back(vb); } } onceki = ii; } kv.kenarSayi = (u32)(kv.vertexler.size() / 2); } return c; } // Material { diffuseColor r g b ... } → renk ('{' yutulmus). Vec3f materialOku() { Vec3f renk{0.62f, 0.66f, 0.72f}; std::string t; while (!(t = al()).empty()) { if (t == "}") break; if (t == "diffuseColor") { const f64 r = std::strtod(al().c_str(), nullptr); const f64 g = std::strtod(al().c_str(), nullptr); const f64 b = std::strtod(al().c_str(), nullptr); renk = {(f32)r, (f32)g, (f32)b}; } else { degerAtla(); } } return renk; } // appearance Appearance { material [DEF ad] Material {...} | USE ad } → renk. Vec3f appearanceOku() { Vec3f renk{0.62f, 0.66f, 0.72f}; if (al() != "Appearance") return renk; if (al() != "{") return renk; std::string t; while (!(t = al()).empty()) { if (t == "}") break; if (t == "material") { std::string m = al(); if (m == "Material") { if (al() == "{") renk = materialOku(); } else if (m == "DEF") { const std::string ad = al(); if (al() == "Material" && al() == "{") { renk = materialOku(); materialTablo[ad] = renk; } } else if (m == "USE") { const std::string ad = al(); auto it = materialTablo.find(ad); if (it != materialTablo.end()) renk = it->second; } else if (m == "{") { blokAtla(); } } else { degerAtla(); } } return renk; } // Shape { appearance ... geometry IndexedFaceSet/LineSet {...} } ('{' yutulmus). void shapeOku(const Don& don) { Vec3f renk{0.62f, 0.66f, 0.72f}; std::string t; while (!(t = al()).empty()) { if (t == "}") break; if (t == "appearance") { renk = appearanceOku(); } else if (t == "geometry") { std::string g = al(); std::string defAd; if (g == "DEF") { defAd = al(); g = al(); } if (g == "USE") { const std::string ad = al(); auto it = geoTablo.find(ad); if (it != geoTablo.end()) { HazirCizim c = it->second; c.renk = renk; const bool yuz = !c.yuzey.vertexler.empty(); if (yuz) ++sonuc.yuzMeshSayisi; else ++sonuc.cizgiSetSayisi; sonuc.parcalar.push_back(std::move(c)); } else { ++sonuc.defUseAtlanan; } } else { bool yuz = (g == "IndexedFaceSet"); bool tel = (g == "IndexedLineSet"); if ((yuz || tel) && al() == "{") { HazirCizim c = geoOku(yuz, don, renk); const bool bosMu = c.yuzey.vertexler.empty() && c.kenar.vertexler.empty(); if (!defAd.empty()) geoTablo[defAd] = c; if (!bosMu) { if (yuz) ++sonuc.yuzMeshSayisi; else ++sonuc.cizgiSetSayisi; sonuc.parcalar.push_back(std::move(c)); } } else if (al() == "{") { blokAtla(); // bilinmeyen geometri } } } else { degerAtla(); } } if ((++sayac & 1023) == 0 && ilerle) ilerle(0.5f, "VRML mesh okunuyor"); } // children [ ... ] veya children Node — icindeki node'lari isle ('children' // token'i yutulmus). Verilen donusumu tasiyarak rekursif node dizisi. void childrenIsle(const Don& don) { std::string t = al(); if (t == "[") { while (!(t = al()).empty()) { if (t == "]") break; nodeIsle(t, don); } } else if (!t.empty()) { nodeIsle(t, don); // tek node (kosesiz) } } // Transform/Group/Shape/DEF/USE tek node'unu isle (node adi 'ad' verilmis). void nodeIsle(const std::string& ad, const Don& don) { if (ad == "DEF") { const std::string isim = al(); const std::string tur = al(); // DEF — Node islenirken cikan geometri geoTablo'ya yazilir. nodeIsleDef(tur, don, isim); return; } if (ad == "USE") { const std::string isim = al(); auto it = geoTablo.find(isim); if (it != geoTablo.end()) { HazirCizim c = it->second; const bool yuz = !c.yuzey.vertexler.empty(); if (yuz) ++sonuc.yuzMeshSayisi; else ++sonuc.cizgiSetSayisi; sonuc.parcalar.push_back(std::move(c)); } else { ++sonuc.defUseAtlanan; } return; } if (ad == "Transform") { if (al() != "{") return; Vec3d tr{0, 0, 0}, sc{1, 1, 1}, eksen{0, 0, 1}; f64 aci = 0; std::string x; Don yerel = don; bool donKuruldu = false; while (!(x = al()).empty()) { if (x == "}") break; if (x == "translation") { tr.x = std::strtod(al().c_str(), nullptr); tr.y = std::strtod(al().c_str(), nullptr); tr.z = std::strtod(al().c_str(), nullptr); } else if (x == "scale") { sc.x = std::strtod(al().c_str(), nullptr); sc.y = std::strtod(al().c_str(), nullptr); sc.z = std::strtod(al().c_str(), nullptr); } else if (x == "rotation") { eksen.x = std::strtod(al().c_str(), nullptr); eksen.y = std::strtod(al().c_str(), nullptr); eksen.z = std::strtod(al().c_str(), nullptr); aci = std::strtod(al().c_str(), nullptr); } else if (x == "children") { if (!donKuruldu) { yerel = Don::carp(don, Don::trs(tr, eksen, aci, sc)); donKuruldu = true; } childrenIsle(yerel); } else { degerAtla(); // bboxCenter/bboxSize... } } return; } if (ad == "Group") { if (al() != "{") return; std::string x; while (!(x = al()).empty()) { if (x == "}") break; if (x == "children") childrenIsle(don); else degerAtla(); } return; } if (ad == "Shape") { if (al() != "{") return; shapeOku(don); return; } // Bilinmeyen node: sonrasi '{' ise blogu atla, '[' ise listeyi atla. std::string nx = al(); if (nx == "{") blokAtla(); else if (nx == "[") { int d = 1; std::string y; while (d > 0 && !(y = al()).empty()) { if (y == "[") ++d; else if (y == "]") --d; } } else geriit(nx); } // DEF : geometri node'sa geoTablo'ya kaydet, degilse normal isle. void nodeIsleDef(const std::string& tur, const Don& don, const std::string& isim) { // DEF genelde Group/Material/Coordinate/geometri icin. Group ise iceri gir. if (tur == "Group" || tur == "Transform") { nodeIsle(tur, don); (void)isim; return; } if (tur == "Shape") { nodeIsle(tur, don); (void)isim; return; } // Diger DEF (Material/Coordinate vb.) — normal isle (renk DEF appearance'ta). nodeIsle(tur, don); } }; } // namespace WrlSonuc wrlCoz(const std::string& metin, const IlerlemeGeri& ilerle) { WrlSonuc sonuc; Parser ps(metin, sonuc, ilerle); const Don birim; std::string t; while (!(t = ps.al()).empty()) { ps.nodeIsle(t, birim); } sonuc.ad = "VRML"; sonuc.basarili = !sonuc.parcalar.empty(); if (!sonuc.basarili && sonuc.hata.empty()) sonuc.hata = "VRML dosyasinda mesh bulunamadi"; return sonuc; } WrlSonuc wrlOku(const std::string& yol, const IlerlemeGeri& ilerle) { std::ifstream f(yol, std::ios::binary); if (!f) { WrlSonuc s; s.hata = "VRML dosyasi acilamadi: " + yol; return s; } std::ostringstream ss; ss << f.rdbuf(); WrlSonuc s = wrlCoz(ss.str(), ilerle); const auto sl = yol.find_last_of("/\\"); s.ad = (sl == std::string::npos) ? yol : yol.substr(sl + 1); return s; } } // namespace fiko::io