载入中...
搜索中...
未找到
HouseComponentLibrary.cpp
浏览该文件的文档.
2
3#include "common/Json.h"
4
5#include <algorithm>
6#include <filesystem>
7#include <fstream>
8#include <sstream>
9#include <utility>
10
11namespace eve::housegen {
12namespace {
13
15
16eve::Result<SocketDirection> parseDirection(std::string_view value) {
24 eve::DiagnosticCode::InvalidArgument, "socket direction is invalid", "direction", {}, "housegen.components"));
25}
26
27eve::Result<HouseComponent> parseComponent(Value object) {
28 if (!object.isObject())
30 eve::DiagnosticCode::ParseError, "component must be an object", {}, {}, "housegen.components"));
31
32 HouseComponent out;
33 out.id = object.getString("id");
34 out.modelPath = object.getString("model");
35 out.category = object.getString("category");
36 out.width = object.getInt("width", 1);
37 out.depth = object.getInt("depth", 1);
38 out.height = object.getInt("height", 1);
39 out.weight = object.getInt("weight", 1);
40 out.tags = object.getStringArray("tags");
41 if (object.get("rotations").isArray()) out.rotations = object.getIntArray("rotations");
42
43 const Value sockets = object.get("sockets");
44 for (size_t i = 0; i < sockets.size(); ++i) {
45 const Value socketObject = sockets.at(i);
46 if (!socketObject.isObject())
48 eve::DiagnosticCode::ParseError, "socket must be an object", "sockets", {}, "housegen.components"));
49 auto direction = parseDirection(socketObject.getString("direction"));
51
52 HouseSocket socket;
53 socket.direction = std::move(direction).takeValue();
54 socket.type = socketObject.getString("type");
55 socket.accepts = socketObject.getStringArray("accepts");
56 if (socket.type.empty())
58 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "socket needs a valid direction and type",
59 "sockets", {}, "housegen.components"));
60 out.sockets.push_back(std::move(socket));
61 }
62
63 const Value material = object.get("material");
64 if (material.isObject()) {
65 const Value color = material.get("baseColor");
66 if (color.isArray()) {
67 if (color.size() != 3 && color.size() != 4)
69 eve::DiagnosticCode::InvalidArgument, "material baseColor needs 3 or 4 values",
70 "material.baseColor", {}, "housegen.components"));
71 out.material.hasBaseColor = true;
72 out.material.baseColorR = color.at(0).asFloat();
73 out.material.baseColorG = color.at(1).asFloat();
74 out.material.baseColorB = color.at(2).asFloat();
75 out.material.baseColorA = color.size() == 4 ? color.at(3).asFloat() : 1.f;
76 }
77 out.material.baseColorTexture = material.getString("baseColorTexture");
78 out.material.normalTexture = material.getString("normalTexture");
79 out.material.heightTexture = material.getString("heightTexture");
80 if (material.has("metallic")) {
81 out.material.hasMetallic = true;
82 out.material.metallic = material.getFloat("metallic");
83 }
84 if (material.has("roughness")) {
85 out.material.hasRoughness = true;
86 out.material.roughness = material.getFloat("roughness");
87 }
88 out.material.parallaxScale = material.getFloat("parallaxScale", 0.f);
89 out.material.parallaxMinLayers = material.getFloat("parallaxMinLayers", 8.f);
90 out.material.parallaxMaxLayers = material.getFloat("parallaxMaxLayers", 32.f);
91 out.material.cellBombScale = material.getFloat("cellBombScale", 4.f);
92 out.material.cellBombStrength = material.getFloat("cellBombStrength", 0.f);
93 out.material.cellBombRotation = material.getFloat("cellBombRotation", 1.f);
94 }
95 return eve::Result<HouseComponent>::success(std::move(out));
96}
97
98} // namespace
99
101 if (component.id.empty() || component.modelPath.empty() || component.category.empty())
103 "component needs id, model and category", {}, {},
104 "housegen.components"));
105 if (component.width < 1 || component.depth < 1 || component.height < 1 || component.weight < 1)
107 "component dimensions and weight must be positive", {},
108 {}, "housegen.components"));
109 for (const int rotation : component.rotations) {
110 if (rotation < 0 || rotation >= 360 || rotation % 90 != 0)
112 "rotations must be cardinal degrees", "rotations",
113 {}, "housegen.components"));
114 }
115
116 const auto inUnitRange = [](float value) { return value >= 0.f && value <= 1.f; };
117 if ((component.material.hasBaseColor &&
118 (!inUnitRange(component.material.baseColorR) || !inUnitRange(component.material.baseColorG) ||
119 !inUnitRange(component.material.baseColorB) || !inUnitRange(component.material.baseColorA))) ||
120 (component.material.hasMetallic && !inUnitRange(component.material.metallic)) ||
121 (component.material.hasRoughness && !inUnitRange(component.material.roughness)) ||
122 !inUnitRange(component.material.cellBombStrength) || !inUnitRange(component.material.cellBombRotation))
124 "material values must be between 0 and 1", "material",
125 {}, "housegen.components"));
126 if (component.material.parallaxScale < 0.f || component.material.parallaxScale > 0.2f ||
127 component.material.parallaxMinLayers < 1.f ||
129 component.material.cellBombScale <= 0.f)
131 "material sampling parameters are invalid", "material",
132 {}, "housegen.components"));
133 if (components_.contains(component.id))
135 eve::DiagnosticCode::Conflict, "duplicate component id: " + component.id, "id", {}, "housegen.components"));
136
137 components_.emplace(component.id, component);
139}
140
142 std::string parseError;
143 const eve::json::Document document = eve::json::Document::parse(std::string(json), &parseError);
144 if (!document.valid())
146 eve::DiagnosticCode::ParseError, parseError.empty() ? "invalid component manifest" : parseError, {}, {},
147 "housegen.components"));
148
149 const Value root = document.root();
150 const Value values = root.isArray() ? root : root.get("components");
151 if (!values.isArray())
153 eve::DiagnosticCode::ParseError, "expected components array", "components", {}, "housegen.components"));
154
155 HouseComponentLibrary candidate;
156 for (size_t i = 0; i < values.size(); ++i) {
157 auto component = parseComponent(values.at(i));
158 if (!component.ok()) return eve::Result<void>::failure(component.status());
159 auto registered = candidate.registerComponent(std::move(component).takeValue());
160 if (!registered.ok()) return registered;
161 }
162 components_ = std::move(candidate.components_);
164}
165
167 const std::string path(filename);
168 std::ifstream input(path, std::ios::binary);
169 if (!input)
171 eve::DiagnosticCode::NotFound, "cannot open component manifest: " + path, path, {}, "housegen.components"));
172
173 std::ostringstream json;
174 json << input.rdbuf();
175 HouseComponentLibrary candidate;
176 auto loaded = candidate.loadFromJson(json.str());
177 if (!loaded.ok()) return loaded;
178
179 const std::filesystem::path assetRoot = std::filesystem::absolute(std::filesystem::path(path)).parent_path();
180 for (auto &[_, component] : candidate.components_) {
181 std::filesystem::path model(component.modelPath);
182 if (model.is_relative()) component.modelPath = (assetRoot / model).lexically_normal().string();
183 }
184 components_ = std::move(candidate.components_);
186}
187
188void HouseComponentLibrary::clear() { components_.clear(); }
189
191 const auto it = components_.find(std::string(id));
192 if (it == components_.end()) return {};
193 return std::cref(it->second);
194}
195
196int HouseComponentLibrary::count() const { return int(components_.size()); }
197
198std::vector<std::string> HouseComponentLibrary::ids() const {
199 std::vector<std::string> result;
200 result.reserve(components_.size());
201 for (const auto &[id, _] : components_) result.push_back(id);
202 std::sort(result.begin(), result.end());
203 return result;
204}
205
206std::vector<std::reference_wrapper<const HouseComponent>> HouseComponentLibrary::byCategory(
207 std::string_view category, std::string_view style) const {
208 std::vector<std::reference_wrapper<const HouseComponent>> result;
209 for (const auto &[_, component] : components_) {
210 if (component.category == category &&
211 (style.empty() || std::find(component.tags.begin(), component.tags.end(), style) != component.tags.end()))
212 result.push_back(std::cref(component));
213 }
214 if (result.empty() && !style.empty()) return byCategory(category, {});
215 std::sort(result.begin(), result.end(),
216 [](const auto &left, const auto &right) { return left.get().id < right.get().id; });
217 return result;
218}
219
220bool HouseComponentLibrary::hasCompletePack(std::string_view style) const {
221 if (style.empty()) return false;
222 static constexpr const char *core[] = {"foundation", "floor", "wall", "door", "roof"};
223 for (const char *category : core) {
224 bool present = false;
225 for (const auto &[_, component] : components_)
226 if (component.category == category &&
227 std::find(component.tags.begin(), component.tags.end(), style) != component.tags.end()) {
228 present = true;
229 break;
230 }
231 if (!present) return false;
232 }
233 return true;
234}
235
236std::vector<std::string> HouseComponentLibrary::completePacks() const {
237 std::vector<std::string> styles;
238 for (const auto &[_, component] : components_)
239 for (const std::string &tag : component.tags)
240 if (std::find(styles.begin(), styles.end(), tag) == styles.end()) styles.push_back(tag);
241 std::vector<std::string> result;
242 for (const std::string &style : styles)
243 if (hasCompletePack(style)) result.push_back(style);
244 std::sort(result.begin(), result.end());
245 return result;
246}
247
248} // namespace eve::housegen
double value
int root
Definition AnimSmr.cpp:119
std::map< std::string, Var > values
EvpackChunkInput input
Definition Evpack.cpp:170
HexVec3 left
HexVec3 right
std::array< float, 4 > rotation
const std::string * tag
std::string path
Definition PlayHost.cpp:110
glm::mat4 model
Material * material
RoadLaneDirection direction
const char * category
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
bool isArray() const noexcept
Return true when this value is an array.
Definition Value.h:97
房屋组件注册表(按 id 索引,支持分类/风格查询)。
eve::Result< void > loadFromJson(std::string_view json)
从 JSON / 文件加载组件。
std::vector< std::string > completePacks() const
所有能构成完整组件包(五个核心分类齐全)的风格标签。
eve::Result< void > loadFromFile(std::string_view filename)
eve::OptionalRef< const HouseComponent > find(std::string_view id) const
按 id 查询组件;缺失是正常查询结果。
std::vector< std::reference_wrapper< const HouseComponent > > byCategory(std::string_view category, std::string_view style={}) const
按分类(可选风格)查询组件。
eve::Result< void > registerComponent(const HouseComponent &component)
注册单个组件(id 重复会报错)。
std::vector< std::string > ids() const
全部组件 id。
bool hasCompletePack(std::string_view style) const
报告一个风格是否构成完整组件包。
EVENGINE_API_FOUNDATION public API.
Definition Json.h:136
bool valid() const
Valid.
Definition Json.h:157
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
Value root() const
Root.
Definition Json.h:159
EVENGINE_API_FOUNDATION public API.
Definition Json.h:38
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
std::optional< std::reference_wrapper< T > > OptionalRef
Optional borrowed reference; it does not extend T's lifetime.
Definition BorrowedRef.h:24
房屋组件模板(几何 + 连接点 + 权重)。
int width
占地尺寸(格)。
std::vector< int > rotations
允许的旋转(度)。
HouseMaterialOverride material
材质覆盖。
int weight
生成权重(越大越常被选中)。
glm::vec4 color