Files
mtgodot-poc/formats/environment.cpp
T

219 lines
8.5 KiB
C++

#include "environment.h"
#include "m2_tokvec.h" // read_file
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <sstream>
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<std::string> tok_line(const std::string& line) {
std::vector<std::string> 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<std::vector<std::string>> lines; // kv lines (lines[k][0] = key)
std::vector<std::vector<float>> rows; // List rows
std::vector<Block> children;
const std::vector<std::string>* 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<Block*> 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<std::string> rec(t.begin() + k, t.end());
if (stack.back()->is_list) {
std::vector<float> 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<std::string>* 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<float>& 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);
out.reserved = root.i("Reserved") != 0;
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.density_fog = fg->i("IsDensity") != 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* flt = root.child("Filter")) {
out.filter.enable = flt->i("Enable") != 0;
out.filter.color = rgba(flt->kv("Color"));
out.filter.alpha_src = flt->i("AlphaSrc", out.filter.alpha_src);
out.filter.alpha_dest = flt->i("AlphaDest", out.filter.alpha_dest);
}
if (const Block* sb = root.child("SkyBox")) {
out.sky.texture_render_mode = sb->i("BTextureRenderMode") != 0;
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");
static constexpr const char* kFaceKeys[6] = {
"FrontFaceFileName", "BackFaceFileName", "LeftFaceFileName",
"RightFaceFileName", "TopFaceFileName", "BottomFaceFileName"};
for (int k = 0; k < 6; ++k)
out.sky.face_textures[k] = sb->s(kFaceKeys[k]);
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