#include "environment.h" #include "m2_tokvec.h" // read_file #include #include #include #include namespace fmt { namespace { bool ieq(const std::string& a, const char* b) { if (a.size() != std::strlen(b)) return false; for (size_t i = 0; i < a.size(); ++i) if (std::tolower((unsigned char)a[i]) != std::tolower((unsigned char)b[i])) return false; return true; } std::vector tok_line(const std::string& line) { std::vector out; size_t i = 0, n = line.size(); while (i < n) { char c = line[i]; if (c == '#' || (c == '/' && i + 1 < n && line[i + 1] == '/')) break; if (std::isspace((unsigned char)c)) { ++i; continue; } if (c == '{' || c == '}') { out.emplace_back(1, c); ++i; continue; } if (c == '"') { ++i; std::string s; while (i < n && line[i] != '"') s += line[i++]; if (i < n) ++i; out.push_back(s); continue; } std::string s; while (i < n && !std::isspace((unsigned char)line[i]) && line[i] != '{' && line[i] != '}') s += line[i++]; out.push_back(s); } return out; } struct Block { std::string name; bool is_list = false; std::vector> lines; // kv lines (lines[k][0] = key) std::vector> rows; // List rows std::vector children; const std::vector* kv(const char* key) const { for (auto& l : lines) if (!l.empty() && ieq(l[0], key)) return &l; return nullptr; } const Block* child(const char* nm) const { for (auto& c : children) if (ieq(c.name, nm)) return &c; return nullptr; } float f(const char* key, int idx, float def = 0) const { auto* l = kv(key); return (l && (int)l->size() > idx) ? (float)std::atof((*l)[idx].c_str()) : def; } int i(const char* key, int def = 0) const { auto* l = kv(key); return (l && l->size() > 1) ? std::atoi((*l)[1].c_str()) : def; } std::string s(const char* key, const std::string& def = "") const { auto* l = kv(key); return (l && l->size() > 1) ? (*l)[1] : def; } }; bool parse_tree(const std::string& text, Block& root, std::string* err) { std::istringstream in(text); std::string line; std::vector stack{&root}; std::string pending; // name of a Group/List awaiting '{' bool pending_list = false; while (std::getline(in, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); auto t = tok_line(line); for (size_t k = 0; k < t.size(); ++k) { const std::string& x = t[k]; if (x == "{") { Block b; b.name = pending; b.is_list = pending_list; stack.back()->children.push_back(std::move(b)); stack.push_back(&stack.back()->children.back()); pending.clear(); pending_list = false; } else if (x == "}") { if (stack.size() <= 1) { if (err) *err = "msenv: 多余的 }"; return false; } stack.pop_back(); } else if (ieq(x, "Group") || ieq(x, "List")) { pending_list = ieq(x, "List"); pending = (k + 1 < t.size()) ? t[k + 1] : ""; ++k; } else { // 一行剩余 token 作为一条记录 std::vector rec(t.begin() + k, t.end()); if (stack.back()->is_list) { std::vector row; for (auto& v : rec) row.push_back((float)std::atof(v.c_str())); stack.back()->rows.push_back(std::move(row)); } else { stack.back()->lines.push_back(std::move(rec)); } break; // 行内其余 token 已并入本记录 } } } if (stack.size() != 1) { if (err) *err = "msenv: 块未闭合"; return false; } return true; } Rgba rgba(const std::vector* l, int off = 1) { Rgba c{{0, 0, 0, 1}}; if (l) for (int k = 0; k < 4 && off + k < (int)l->size(); ++k) c[k] = (float)std::atof((*l)[off + k].c_str()); return c; } Rgba row_rgba(const std::vector& r) { Rgba c{{0, 0, 0, 0}}; for (int k = 0; k < 4 && k < (int)r.size(); ++k) c[k] = r[k]; return c; } } // namespace bool parse_environment(const std::string& text, Environment& out, std::string* err) { Block root; if (!parse_tree(text, root, err)) return false; out = Environment{}; if (auto* l = root.kv("ScriptType")) out.script_type = l->size() > 1 ? (*l)[1] : ""; out.script_version = root.f("ScriptVersion", 1); if (const Block* dl = root.child("DirectionalLight")) { if (auto* d = dl->kv("Direction")) for (int k = 0; k < 3 && k + 1 < (int)d->size(); ++k) out.dir_light.direction[k] = (float)std::atof((*d)[k + 1].c_str()); if (const Block* bg = dl->child("Background")) { out.dir_light.bg_enable = bg->i("Enable") != 0; out.dir_light.bg_diffuse = rgba(bg->kv("Diffuse")); out.dir_light.bg_ambient = rgba(bg->kv("Ambient")); } if (const Block* ch = dl->child("Character")) { out.dir_light.ch_enable = ch->i("Enable") != 0; out.dir_light.ch_diffuse = rgba(ch->kv("Diffuse")); out.dir_light.ch_ambient = rgba(ch->kv("Ambient")); } } if (const Block* m = root.child("Material")) { out.material.diffuse = rgba(m->kv("Diffuse")); out.material.ambient = rgba(m->kv("Ambient")); out.material.emissive = rgba(m->kv("Emissive")); } if (const Block* fg = root.child("Fog")) { out.fog.fog_level = fg->i("foglevel", 0); out.fog.near_distance = fg->f("NearDistance", 1); out.fog.far_distance = fg->f("FarDistance", 1); out.fog.color = rgba(fg->kv("Color")); out.fog.enable = (fg->kv("Enable") && fg->i("Enable") != 0) || out.fog.fog_level > 0; } if (const Block* sb = root.child("SkyBox")) { for (int k = 0; k < 3; ++k) out.sky.scale[k] = sb->f("Scale", k + 1); out.sky.gradient_level_upper = sb->i("GradientLevelUpper"); out.sky.gradient_level_lower = sb->i("GradientLevelLower"); for (int k = 0; k < 2; ++k) { out.sky.cloud_scale[k] = sb->f("CloudScale", k + 1); out.sky.cloud_texture_scale[k] = sb->f("CloudTextureScale", k + 1); out.sky.cloud_speed[k] = sb->f("CloudSpeed", k + 1); } out.sky.cloud_height = sb->f("CloudHeight", 1); out.sky.cloud_texture = sb->s("CloudTextureFileName"); if (const Block* cc = sb->child("CloudColor")) for (auto& r : cc->rows) out.sky.cloud_color.push_back(row_rgba(r)); if (const Block* gr = sb->child("Gradient")) for (auto& r : gr->rows) out.sky.gradient.push_back(row_rgba(r)); } if (const Block* lf = root.child("LensFlare")) { out.lens_flare.enable = lf->i("Enable") != 0; out.lens_flare.brightness_color = rgba(lf->kv("BrightnessColor")); out.lens_flare.max_brightness = lf->f("MaxBrightness", 1); out.lens_flare.main_flare_enable = lf->i("MainFlareEnable") != 0; out.lens_flare.main_flare_texture = lf->s("MainFlareTextureFileName"); out.lens_flare.main_flare_size = lf->f("MainFlareSize", 1); } return true; } bool parse_environment_file(const std::string& path, Environment& out, std::string* err) { std::string text; if (!read_file(path, text)) { if (err) *err = "打不开 " + path; return false; } return parse_environment(text, out, err); } } // namespace fmt