219 lines
8.5 KiB
C++
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
|