载入中...
搜索中...
未找到
HouseLayout.cpp
浏览该文件的文档.
3
4#include "graphics/Graphics.h"
6#include "data/ByteData.h"
7#include "image/Image.h"
8#include "image/ImageData.h"
9#include "model3d/Model3D.h"
10#include "model3d/ModelData.h"
11#include "procgen/Semantic.h"
12
13#include <assimp/material.h>
14#include <assimp/matrix4x4.h>
15#include <assimp/scene.h>
16#include <assimp/texture.h>
17#include "common/Json.h"
18
19#include <algorithm>
20#include <cmath>
21#include <cstdlib>
22#include <filesystem>
23#include <fstream>
24#include <functional>
25#include <memory>
26#include <sstream>
27#include <stdexcept>
28#include <tuple>
29#include <unordered_map>
30#include <unordered_set>
31#include <utility>
32
33namespace eve::housegen {
34namespace {
35std::string esc(const std::string &v) { std::string o; for (char c : v) { if (c == '\\' || c == '"') o += '\\'; o += c; } return o; }
36
37graphics::Texture *textureFromEmbedded(graphics::Graphics *gfx, const aiTexture *source) {
38 if (!source || !source->pcData) return nullptr;
39 if (source->mHeight == 0) {
40 data::ByteData bytes(source->pcData, static_cast<size_t>(source->mWidth));
41 std::unique_ptr<image::ImageData> decoded(image::Image::create()->newImageData(&bytes));
42 return gfx->newTexture(decoded.get(), graphics::TextureCreateInfo::withMipmaps(true));
43 }
44 std::vector<uint8_t> rgba(size_t(source->mWidth) * size_t(source->mHeight) * 4);
45 for (size_t pixel = 0; pixel < size_t(source->mWidth) * size_t(source->mHeight); ++pixel) {
46 rgba[pixel * 4 + 0] = source->pcData[pixel].r;
47 rgba[pixel * 4 + 1] = source->pcData[pixel].g;
48 rgba[pixel * 4 + 2] = source->pcData[pixel].b;
49 rgba[pixel * 4 + 3] = source->pcData[pixel].a;
50 }
51 return gfx->newTexture(int(source->mWidth), int(source->mHeight), rgba.data(),
53}
54
55graphics::Texture *textureFromFile(graphics::Graphics *gfx, const std::string &modelPath,
56 const std::string &texturePath) {
57 if (texturePath.empty()) return nullptr;
58 std::filesystem::path resolved(texturePath);
59 if (resolved.is_relative()) resolved = std::filesystem::path(modelPath).parent_path() / resolved;
60 try {
61 if (std::filesystem::is_regular_file(resolved)) {
62 std::ifstream input(resolved, std::ios::binary | std::ios::ate);
63 const std::streamsize size = input.tellg();
64 if (size <= 0) return nullptr;
65 input.seekg(0, std::ios::beg);
66 std::vector<uint8_t> bytes(static_cast<size_t>(size));
67 if (!input.read(reinterpret_cast<char *>(bytes.data()), size)) return nullptr;
68 data::ByteData source(bytes.data(), bytes.size());
69 std::unique_ptr<image::ImageData> decoded(
70 image::Image::create()->newImageData(&source));
71 return gfx->newTexture(decoded.get(), graphics::TextureCreateInfo::withMipmaps(true));
72 }
73 return gfx->newTextureFromFile(resolved.lexically_normal().string());
74 } catch (...) {
75 return nullptr;
76 }
77}
78
79graphics::Texture *assimpTexture(graphics::Graphics *gfx, const aiScene *scene,
80 const aiMaterial *material, const std::string &modelPath,
81 aiTextureType primary, aiTextureType fallback) {
82 if (!scene || !material) return nullptr;
83 aiString texturePath;
84 if (material->GetTexture(primary, 0, &texturePath) != AI_SUCCESS &&
85 material->GetTexture(fallback, 0, &texturePath) != AI_SUCCESS)
86 return nullptr;
87 const std::string path = texturePath.C_Str();
88 if (path.empty()) return nullptr;
89 if (path.front() == '*') {
90 const int index = std::atoi(path.c_str() + 1);
91 if (index < 0 || static_cast<unsigned>(index) >= scene->mNumTextures) return nullptr;
92 try {
93 return textureFromEmbedded(gfx, scene->mTextures[index]);
94 } catch (...) {
95 return nullptr;
96 }
97 }
98 return textureFromFile(gfx, modelPath, path);
99}
100
107struct ModelHandle {
110
112 [[nodiscard]] model3d::ModelData *get() const noexcept {
113 // The cache registry stores Resource, so a pinned entry needs the same explicit
114 // downcast the cache's own loaders use.
115 return owned ? owned.get() : static_cast<model3d::ModelData *>(pinned.get());
116 }
117};
118
119ModelHandle loadModel(model3d::Model3D *models, const std::string &path) {
120 if (!std::filesystem::is_regular_file(std::filesystem::path(path))) {
121 // The module hands back the cache-owned resource, so pin it: this layout keeps
122 // using the model after the cache entry may have been unloaded.
123 model3d::ModelData *cached = models->newModelDataFromFile(path);
124 if (cached == nullptr) return {};
126 if (!pinned.ok()) return {};
127 ModelHandle handle;
128 handle.pinned = std::move(pinned).takeValue();
129 return handle;
130 }
131 std::ifstream input(path, std::ios::binary | std::ios::ate);
132 if (!input) throw std::runtime_error("cannot open model: " + path);
133 const std::streamsize size = input.tellg();
134 if (size <= 0) throw std::runtime_error("model is empty: " + path);
135 input.seekg(0, std::ios::beg);
136 std::vector<uint8_t> bytes(static_cast<size_t>(size));
137 if (!input.read(reinterpret_cast<char *>(bytes.data()), size))
138 throw std::runtime_error("cannot read model: " + path);
139 data::ByteData source(bytes.data(), bytes.size());
140 ModelHandle handle;
141 handle.owned.reset(models->newModelData(&source, std::filesystem::path(path).extension().string()));
142 return handle;
143}
144} // namespace
145
147 instances.clear();
148 rooms.clear();
149 diagnostics.clear();
150 footprintMask.clear();
151 seed = 1;
152 moduleSize = 1.f;
153 floorHeight = 3.f;
154 footprintWidth = 0;
155 footprintDepth = 0;
156 footprintStyle = "rectangle";
157 roofStyle = "gable";
158 entranceSide = "north";
159}
160
161std::string HouseLayout::toJson() const {
162 std::ostringstream o;
163 o << "{\"schema\":\"eve.house-layout\",\"version\":1,\"seed\":" << seed << ",\"moduleSize\":" << moduleSize
164 << ",\"floorHeight\":" << floorHeight << ",\"footprintStyle\":\"" << esc(footprintStyle) << "\",\"roofStyle\":\""
165 << esc(roofStyle) << "\",\"entranceSide\":\"" << esc(entranceSide) << "\",\"footprintWidth\":" << footprintWidth
166 << ",\"footprintDepth\":" << footprintDepth << ",\"footprintMask\":\"";
167 for (const uint8_t cell : footprintMask) o << (cell ? '1' : '0');
168 o << "\",\"instances\":[";
169 for (size_t i = 0; i < instances.size(); ++i) { const auto &v = instances[i]; if (i) o << ','; o << "{\"componentId\":\"" << esc(v.componentId) << "\",\"x\":" << v.x << ",\"y\":" << v.y << ",\"z\":" << v.z << ",\"rotationDeg\":" << v.rotationDeg << '}'; }
170 o << "],\"rooms\":[";
171 for (size_t i = 0; i < rooms.size(); ++i) { const auto &v = rooms[i]; if (i) o << ','; o << "{\"type\":\"" << esc(v.type) << "\",\"x\":" << v.x << ",\"y\":" << v.y << ",\"z\":" << v.z << ",\"width\":" << v.width << ",\"depth\":" << v.depth << '}'; }
172 o << "],\"diagnostics\":[";
173 for (size_t i = 0; i < diagnostics.size(); ++i) { if (i) o << ','; o << '"' << esc(diagnostics[i]) << '"'; }
174 o << "]}";
175 return o.str();
176}
177
179 using eve::json::Value;
180 std::string parseError;
181 const eve::json::Document doc = eve::json::Document::parse(std::string(json), &parseError);
182 if (!doc.valid())
184 eve::DiagnosticCode::ParseError, parseError.empty() ? "invalid house layout JSON" : parseError, {}, {},
185 "housegen.layout"));
186 const Value o = doc.root();
187 if (!o.isObject())
189 eve::DiagnosticCode::ParseError, "layout must be an object", {}, {}, "housegen.layout"));
190 const std::string schema = o.getString("schema", "eve.house-layout");
191 const int version = o.getInt("version", 1);
192 if (schema != "eve.house-layout" || version != 1)
194 "unsupported house layout schema or version", "schema",
195 {}, "housegen.layout"));
196
197 HouseLayout parsed;
198 parsed.seed = static_cast<unsigned>(o.getInt("seed", 1));
199 parsed.moduleSize = o.getFloat("moduleSize", 1.f);
200 parsed.floorHeight = o.getFloat("floorHeight", 3.f);
201 parsed.footprintStyle = o.getString("footprintStyle", "rectangle");
202 parsed.roofStyle = o.getString("roofStyle", "gable");
203 parsed.entranceSide = o.getString("entranceSide", "north");
204 parsed.footprintWidth = o.getInt("footprintWidth", 0);
205 parsed.footprintDepth = o.getInt("footprintDepth", 0);
206 const std::string footprintBits = o.getString("footprintMask", "");
207 if (parsed.footprintWidth < 0 || parsed.footprintDepth < 0 ||
208 size_t(parsed.footprintWidth) * size_t(parsed.footprintDepth) != footprintBits.size())
210 "invalid canonical footprint mask", "footprintMask",
211 {}, "housegen.layout"));
212 for (const char bit : footprintBits) {
213 if (bit != '0' && bit != '1')
215 "footprint mask must contain only 0 or 1",
216 "footprintMask", {}, "housegen.layout"));
217 parsed.footprintMask.push_back(bit == '1' ? 1 : 0);
218 }
219 if (!std::isfinite(parsed.moduleSize) || parsed.moduleSize <= 0.f || !std::isfinite(parsed.floorHeight) ||
220 parsed.floorHeight <= 0.f)
222 eve::DiagnosticCode::InvalidArgument, "moduleSize and floorHeight must be finite and positive", {}, {},
223 "housegen.layout"));
224
225 const Value instances = o.get("instances");
226 if (!instances.isArray())
228 eve::DiagnosticCode::ParseError, "layout has no instances", "instances", {}, "housegen.layout"));
229 for (size_t i = 0; i < instances.size(); ++i) {
230 const Value v = instances.at(i);
231 // componentId and the cell coordinates are required, not defaulted.
232 if (!v.has("componentId") || !v.has("x") || !v.has("y") || !v.has("z"))
234 "instance needs componentId, x, y and z",
235 "instances", {}, "housegen.layout"));
236 parsed.instances.push_back({v.getString("componentId"), v.getInt("x"), v.getInt("y"),
237 v.getInt("z"), v.getInt("rotationDeg", 0)});
238 }
239
240 const Value rooms = o.get("rooms");
241 for (size_t i = 0; i < rooms.size(); ++i) {
242 const Value v = rooms.at(i);
243 if (!v.has("type") || !v.has("x") || !v.has("y") || !v.has("width") || !v.has("depth"))
245 "room needs type, x, y, width and depth", "rooms",
246 {}, "housegen.layout"));
247 parsed.rooms.push_back({v.getString("type"), v.getInt("x"), v.getInt("y"),
248 v.getInt("z", 0), v.getInt("width"), v.getInt("depth")});
249 }
250
251 parsed.diagnostics = o.getStringArray("diagnostics");
252 *this = std::move(parsed);
254}
255
257 if (!std::isfinite(moduleSize) || moduleSize <= 0.f || !std::isfinite(floorHeight) || floorHeight <= 0.f)
259 eve::DiagnosticCode::InvalidArgument, "moduleSize and floorHeight must be finite and positive", {}, {},
260 "housegen.layout"));
261 if (footprintWidth <= 0 || footprintDepth <= 0 ||
262 footprintMask.size() != size_t(footprintWidth) * size_t(footprintDepth))
264 "house layout has no canonical footprint mask",
265 "footprintMask", {}, "housegen.layout"));
266 for (const auto& instance : instances)
267 if (instance.x >= footprintWidth || instance.y >= footprintDepth ||
268 !footprintMask[size_t(instance.y * footprintWidth + instance.x)])
270 eve::DiagnosticCode::InvalidArgument, "house instance origin is outside the canonical footprint",
271 instance.componentId, {}, "housegen.layout"));
272 for (const HouseRoom& room : rooms) {
273 if (room.x < 0 || room.y < 0 || room.width <= 0 || room.depth <= 0 || room.x + room.width > footprintWidth ||
274 room.y + room.depth > footprintDepth)
276 "room lies outside the canonical footprint",
277 "rooms", {}, "housegen.layout"));
278 for (int y = room.y; y < room.y + room.depth; ++y)
279 for (int x = room.x; x < room.x + room.width; ++x)
280 if (!footprintMask[size_t(y * footprintWidth + x)])
282 "room contains an inactive footprint cell",
283 "rooms", {}, "housegen.layout"));
284 }
285 std::unordered_set<std::string> occupied;
286 std::unordered_set<std::string> floorCells, roofCells;
287 std::vector<std::tuple<int, int, int>> floors;
288 auto cellKey = [](int x, int y, int z) {
289 return std::to_string(x) + ":" + std::to_string(y) + ":" + std::to_string(z);
290 };
291 bool entrance = false, roof = false;
292 for (const auto &i : instances) {
293 const auto component = library.find(i.componentId);
294 if (!component)
296 "unknown component: " + i.componentId,
297 "componentId", {}, "housegen.layout"));
298 const HouseComponent &c = component->get();
299 const int normalizedRotation = (i.rotationDeg % 360 + 360) % 360;
300 if (normalizedRotation % 90 != 0)
302 eve::DiagnosticCode::InvalidArgument, "non-cardinal rotation", "rotationDeg", {}, "housegen.layout"));
303 const bool rotationAllowed = std::any_of(c.rotations.begin(), c.rotations.end(), [&](int allowed) {
304 return (allowed % 360 + 360) % 360 == normalizedRotation;
305 });
306 if (!rotationAllowed)
308 "component does not allow the requested rotation",
309 "rotationDeg", {}, "housegen.layout"));
310 const bool quarter = (normalizedRotation / 90) % 2 != 0;
311 const int w = quarter ? c.depth : c.width, d = quarter ? c.width : c.depth;
312 for (int y = 0; y < d; ++y) for (int x = 0; x < w; ++x) {
313 // Boundary cells legitimately carry two perpendicular wall modules at corners.
314 const bool orientedBoundary = c.category == "wall" || c.category == "door" ||
315 c.category == "interior_wall" || c.category == "interior_door";
316 const std::string orientation =
317 orientedBoundary ? ":" + std::to_string((i.rotationDeg % 360 + 360) % 360) : "";
318 const std::string key = std::to_string(i.x + x) + ":" + std::to_string(i.y + y) + ":" +
319 std::to_string(i.z) + ":" + c.category + orientation;
320 if (!occupied.insert(key).second)
322 "overlapping " + c.category + " components",
323 {}, {}, "housegen.layout"));
324 if (c.category == "floor") {
325 floorCells.insert(cellKey(i.x + x, i.y + y, i.z));
326 floors.emplace_back(i.x + x, i.y + y, i.z);
327 } else if (c.category == "roof") {
328 roofCells.insert(cellKey(i.x + x, i.y + y, i.z));
329 }
330 }
331 entrance = entrance || (c.category == "door" && i.z == 0);
332 roof = roof || c.category == "roof";
333 }
334 if (!entrance)
336 "house has no entrance", {}, {}, "housegen.layout"));
337 if (!roof)
339 "house has no roof", {}, {}, "housegen.layout"));
340 for (const auto &[x, y, z] : floors) {
341 if (z > 0 && !floorCells.contains(cellKey(x, y, z - 1))) {
343 "upper floor has no structural support", {}, {},
344 "housegen.layout"));
345 }
346 if (!floorCells.contains(cellKey(x, y, z + 1)) &&
347 !roofCells.contains(cellKey(x, y, z + 1))) {
349 eve::DiagnosticCode::InvalidArgument, "floor cell has no roof coverage", {}, {}, "housegen.layout"));
350 }
351 }
353}
354
356 if (!std::isfinite(moduleSize) || moduleSize <= 0.f || !std::isfinite(floorHeight) || floorHeight <= 0.f)
358 eve::DiagnosticCode::InvalidArgument, "moduleSize and floorHeight must be finite and positive", {}, {},
359 "housegen.layout"));
360 for (const auto &instance : instances) {
361 if (instance.x < 0 || instance.y < 0 || instance.z < 0)
363 eve::DiagnosticCode::InvalidArgument, "house instance has a negative grid coordinate",
364 instance.componentId, {}, "housegen.layout"));
365 }
366 if (footprintWidth <= 0 || footprintDepth <= 0 ||
367 footprintMask.size() != size_t(footprintWidth) * size_t(footprintDepth))
369 "house layout has no canonical footprint mask",
370 "footprintMask", {}, "housegen.layout"));
371
372 procgen::Grid2D candidate;
374 candidate.fill(int(procgen::Semantic::Empty));
375 for (int y = 0; y < footprintDepth; ++y)
376 for (int x = 0; x < footprintWidth; ++x)
377 if (footprintMask[size_t(y * footprintWidth + x)] != 0)
378 candidate.setCell(x, y, int(procgen::Semantic::Floor));
379 for (const auto &instance : instances) {
380 if (instance.x >= footprintWidth || instance.y >= footprintDepth) continue;
381 candidate.setDetail(instance.x, instance.y,
382 std::min(255, std::max(candidate.getDetail(instance.x, instance.y), instance.z + 1)));
383 }
384 candidate.setMeta("domain", "house.footprint");
385 candidate.setMeta("seed", std::to_string(seed));
386 candidate.setMeta("footprint", footprintStyle);
387 candidate.setMeta("roof", roofStyle);
388 candidate.setMeta("entrance", entranceSide);
389 out = std::move(candidate);
391}
392
394 if (!std::isfinite(moduleSize) || moduleSize <= 0.f || !std::isfinite(floorHeight) || floorHeight <= 0.f)
396 eve::DiagnosticCode::InvalidArgument, "moduleSize and floorHeight must be finite and positive", {}, {},
397 "housegen.layout"));
398 if (footprintWidth <= 0 || footprintDepth <= 0 ||
399 footprintMask.size() != size_t(footprintWidth) * size_t(footprintDepth))
401 "house layout has no canonical footprint mask",
402 "footprintMask", {}, "housegen.layout"));
403 for (const auto& instance : instances) {
404 const int rotation = (instance.rotationDeg % 360 + 360) % 360;
405 if (instance.x < 0 || instance.y < 0 || instance.z < 0 || instance.x >= footprintWidth ||
406 instance.y >= footprintDepth || !footprintMask[size_t(instance.y * footprintWidth + instance.x)] ||
407 rotation % 90 != 0)
409 "invalid house component point placement",
410 instance.componentId, {}, "housegen.layout"));
411 }
412 procgen::PointSet candidate;
413 candidate.reserve(instances.size());
414 std::uint64_t ordinal = 0;
415 const std::uint64_t pointNamespace = (std::uint64_t(seed) << 32u) | 0x484f5553u;
416 for (const auto &instance : instances) {
417 const int index = candidate.add(float(instance.x) * moduleSize, float(instance.z) * floorHeight,
418 float(instance.y) * moduleSize);
419 candidate.setYaw(index, float(instance.rotationDeg));
420 candidate.setPointSeed(index, seed);
421 candidate.setBounds(index, 0.f, 0.f, 0.f, moduleSize, floorHeight, moduleSize);
422 auto id = candidate.trySetPointId(index, procgen::derivePointId(pointNamespace, ++ordinal));
423 if (!id.ok()) return eve::Result<void>::failure(id.status());
424 auto component = candidate.trySetStringAttribute(index, "component_id", instance.componentId);
425 if (!component.ok()) return eve::Result<void>::failure(component.status());
426 auto cellX = candidate.trySetIntAttribute(index, "cell_x", instance.x);
427 if (!cellX.ok()) return eve::Result<void>::failure(cellX.status());
428 auto cellY = candidate.trySetIntAttribute(index, "cell_y", instance.y);
429 if (!cellY.ok()) return eve::Result<void>::failure(cellY.status());
430 auto floor = candidate.trySetIntAttribute(index, "floor", instance.z);
431 if (!floor.ok()) return eve::Result<void>::failure(floor.status());
432 }
433 out = std::move(candidate);
435}
436
438 const HouseComponentLibrary &library) const {
439 const auto valid = validate(library);
440 if (!valid.ok()) return eve::Result<std::vector<ecs::EntityHandle>>::failure(valid.status());
441 std::vector<ecs::EntityHandle> entities;
442 const auto destroyCreated = [&entities]() noexcept {
443 for (const auto &handle : entities) {
444 if (auto *entity = ecs::try_get(handle)) ecs::DestroyEntity(entity);
445 }
446 entities.clear();
447 };
448 std::unordered_map<std::string, ModelHandle> data;
449 struct CachedPart {
450 graphics::Mesh *mesh = nullptr;
451 graphics::Texture *texture = nullptr;
452 graphics::Texture *normalTexture = nullptr;
453 graphics::Texture *heightTexture = nullptr;
454 float r = 1.f, g = 1.f, b = 1.f, a = 1.f;
455 float metallic = 0.f, roughness = 0.72f;
456 float parallaxScale = 0.f, parallaxMinLayers = 8.f, parallaxMaxLayers = 32.f;
457 float cellBombScale = 4.f, cellBombStrength = 0.f, cellBombRotation = 1.f;
458 };
459 std::unordered_map<std::string, std::vector<CachedPart>> parts;
460 try {
461 for (const auto &i : instances) {
462 const auto component = library.find(i.componentId);
463 if (!component) {
464 destroyCreated();
466 eve::Diagnostic::error(eve::DiagnosticCode::NotFound, "unknown component: " + i.componentId,
467 "componentId", {}, "housegen.layout"));
468 }
469 const HouseComponent &c = component->get();
470 // Loads once per path: the handle either owns the decode or pins the cache entry.
471 auto modelIt = data.find(c.modelPath);
472 if (modelIt == data.end()) modelIt = data.emplace(c.modelPath, loadModel(&models, c.modelPath)).first;
473 model3d::ModelData *model = modelIt->second.get();
474 // Material overrides belong to a component, so two components may safely reuse the
475 // same GLB with different architectural finishes.
476 auto &cached = parts[c.id];
477 if (cached.empty()) {
478 const aiScene *scene = model->getScene();
479 if (!scene || !scene->mRootNode) throw std::runtime_error("model has no scene root: " + c.modelPath);
480 std::function<void(const aiNode *, const aiMatrix4x4 &)> walk =
481 [&](const aiNode *node, const aiMatrix4x4 &parent) {
482 const aiMatrix4x4 world = parent * node->mTransformation;
483 for (unsigned ni = 0; ni < node->mNumMeshes; ++ni) {
484 const unsigned mi = node->mMeshes[ni];
485 if (mi >= scene->mNumMeshes || !scene->mMeshes[mi]) continue;
486 const aiMesh *source = scene->mMeshes[mi];
487 CachedPart part;
488 part.mesh = gfx.newMeshFromAssimp(*source, world);
489 if (scene->mMaterials && source->mMaterialIndex < scene->mNumMaterials) {
490 const aiMaterial *material = scene->mMaterials[source->mMaterialIndex];
491 aiColor4D color(1.f, 1.f, 1.f, 1.f);
492 material->Get(AI_MATKEY_COLOR_DIFFUSE, color);
493 material->Get(AI_MATKEY_BASE_COLOR, color);
494 part.r = color.r; part.g = color.g; part.b = color.b; part.a = color.a;
495 material->Get(AI_MATKEY_METALLIC_FACTOR, part.metallic);
496 material->Get(AI_MATKEY_ROUGHNESS_FACTOR, part.roughness);
497 part.texture = assimpTexture(&gfx, scene, material, c.modelPath,
498 aiTextureType_BASE_COLOR, aiTextureType_DIFFUSE);
499 part.normalTexture = assimpTexture(&gfx, scene, material, c.modelPath,
500 aiTextureType_NORMALS, aiTextureType_NORMAL_CAMERA);
501 part.heightTexture = assimpTexture(&gfx, scene, material, c.modelPath,
502 aiTextureType_HEIGHT, aiTextureType_DISPLACEMENT);
503 }
504 if (c.material.hasBaseColor) {
505 part.r = c.material.baseColorR;
506 part.g = c.material.baseColorG;
507 part.b = c.material.baseColorB;
508 part.a = c.material.baseColorA;
509 }
510 if (!c.material.baseColorTexture.empty())
511 part.texture = textureFromFile(&gfx, c.modelPath, c.material.baseColorTexture);
512 if (!c.material.normalTexture.empty())
513 part.normalTexture = textureFromFile(&gfx, c.modelPath, c.material.normalTexture);
514 if (!c.material.heightTexture.empty())
515 part.heightTexture = textureFromFile(&gfx, c.modelPath, c.material.heightTexture);
516 if (c.material.hasMetallic) part.metallic = c.material.metallic;
517 if (c.material.hasRoughness) part.roughness = c.material.roughness;
518 part.parallaxScale = c.material.parallaxScale;
519 part.parallaxMinLayers = c.material.parallaxMinLayers;
520 part.parallaxMaxLayers = c.material.parallaxMaxLayers;
521 part.cellBombScale = c.material.cellBombScale;
522 part.cellBombStrength = c.material.cellBombStrength;
523 part.cellBombRotation = c.material.cellBombRotation;
524 cached.push_back(part);
525 }
526 for (unsigned child = 0; child < node->mNumChildren; ++child)
527 walk(node->mChildren[child], world);
528 };
529 walk(scene->mRootNode, aiMatrix4x4());
530 }
531 for (const auto &part : cached) {
532 auto *e = graphics::Renderable3D::create();
533 if (!e) {
534 destroyCreated();
536 eve::Diagnostic::error(eve::DiagnosticCode::Failed, "failed to create Renderable3D entity", {},
537 {}, "housegen.layout"));
538 }
539 e->setMesh(part.mesh);
540 e->setPosition(i.x * moduleSize, i.z * floorHeight, i.y * moduleSize);
541 e->setYaw(float(i.rotationDeg) * 3.14159265358979323846f / 180.f);
542 e->setTint(part.r, part.g, part.b, part.a);
543 if (part.texture) e->setTexture(part.texture);
544 if (part.normalTexture) e->setNormalTexture(part.normalTexture);
545 if (part.heightTexture) e->setHeightTexture(part.heightTexture);
546 e->setMetallic(part.metallic);
547 e->setRoughness(part.roughness);
548 e->setTexCellBomb(part.cellBombScale, part.cellBombStrength,
549 part.cellBombRotation);
550 if (part.heightTexture && part.parallaxScale > 0.f)
551 e->setParallax(part.parallaxScale, part.parallaxMinLayers,
552 part.parallaxMaxLayers);
553 entities.push_back(ecs::handle_of(e));
554 }
555 }
556 } catch (const std::exception &e) {
557 destroyCreated();
559 eve::Diagnostic::error(eve::DiagnosticCode::Failed, e.what(), {}, {}, "housegen.layout"));
560 } catch (...) {
561 destroyCreated();
563 eve::DiagnosticCode::Failed, "house layout instantiation failed", {}, {}, "housegen.layout"));
564 }
565 return eve::Result<std::vector<ecs::EntityHandle>>::success(std::move(entities),
567}
568
569} // namespace eve::housegen
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::vector< eve::artifact::PartView > parts
ActiveSource owned
std::vector< BuildingInstanceSnapshot > instances
EvpackChunkInput input
Definition Evpack.cpp:170
tensor::Graph g
Definition GpuGraph.cpp:7
std::uint32_t key
wgpu::PopErrorScopeStatus status
double r
float v
std::int32_t c
eve::ResourcePin pinned
std::array< float, 4 > rotation
std::int32_t parent
std::uint64_t bytes
bool valid
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float roughness
float metallic
World3D * world
std::string path
Definition PlayHost.cpp:110
std::uint32_t seed
Definition PointSet.cpp:807
std::vector< ecs::EntityHandle > entities
std::weak_ptr< PrimitiveScene > scene
PrimitiveHandle handle
float d
Mesh * mesh
glm::mat4 model
Material * material
const RoadNode * node
bool occupied
Cell cell
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
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
eve::Result< ResourcePin > pin(Resource *resource)
Keeps one cached resource alive across unload()/clear()/reload.
Definition Resource.cpp:311
static ResourceManager & getInstance()
Returns the instance.
Definition Resource.cpp:15
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 isObject() const noexcept
Return true when this value is an object.
Definition Value.h:99
Value & at(std::size_t index)
Return a bounds-checked array element.
Definition Value.cpp:106
virtual Mesh * newMeshFromAssimp(const ::aiMesh &mesh)=0
Creates a mesh from assimp. @ownership Caller deletes unless documented otherwise.
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
房屋组件注册表(按 id 索引,支持分类/风格查询)。
eve::OptionalRef< const HouseComponent > find(std::string_view id) const
按 id 查询组件;缺失是正常查询结果。
一次生成的房屋布局:实例 + 房间 + 元信息,可 JSON 序列化 / 实例化。
Definition HouseLayout.h:23
std::string footprintStyle
布局风格结果。
Definition HouseLayout.h:30
eve::Result< void > validate(const HouseComponentLibrary &library) const
校验布局是否满足组件库规则。
std::string toJson() const
Serialize or restore the eve.house-layout version 1 JSON schema.
std::vector< HouseRoom > rooms
Definition HouseLayout.h:39
std::vector< uint8_t > footprintMask
Definition HouseLayout.h:36
float moduleSize
生成参数回显。
Definition HouseLayout.h:27
void clear()
清空布局。
eve::Result< void > writeComponentPoints(procgen::PointSet &out) const
Export component placements as canonical attributed procgen points.
eve::Result< void > fromJson(std::string_view json)
From json.
int footprintWidth
Canonical base-floor occupancy mask owned by this layout.
Definition HouseLayout.h:34
eve::Result< void > writeFootprintGrid(procgen::Grid2D &out) const
Export the generated footprint as the canonical procgen grid value.
std::vector< std::string > diagnostics
Definition HouseLayout.h:40
std::vector< HouseInstance > instances
组件实例 / 房间 / 诊断信息。
Definition HouseLayout.h:38
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
Resource module for decoding 3D models via medialoader (Assimp). Produces ModelData; GPU upload is gr...
Definition Model3D.h:43
CPU-side decoded 3D model (Assimp scene owned via medialoader::ModelScene). Does not upload to GPU — ...
Definition ModelData.h:39
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
Definition Grid2D.h:36
void setDetail(int x, int y, int value)
Per-cell detail layer (0..255), parallel to cells. Semantics stay in cells (palette/GID compatible); ...
Definition Grid2D.cpp:37
int getDetail(int x, int y) const
Returns the detail.
Definition Grid2D.cpp:42
void resize(int width, int height)
Resize.
Definition Grid2D.cpp:13
void fill(int semantic)
Fill.
Definition Grid2D.cpp:33
void setCell(int x, int y, int semantic)
Sets the cell.
Definition Grid2D.cpp:23
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
Definition Grid2D.cpp:47
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
Result< void > trySetPointId(int index, std::uint64_t id)
Assign a unique non-zero point identity without mutating on failure.
Definition PointSet.cpp:294
int add(float x, float y, float z)
Definition PointSet.cpp:97
Result< void > trySetStringAttribute(int index, const std::string &name, const std::string &value)
Canonical checked string metadata write.
Definition PointSet.cpp:421
void setBounds(int index, float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
Set local-space point bounds before scale and rotation are applied.
Definition PointSet.cpp:202
void setYaw(int index, float yaw)
Sets the yaw.
Definition PointSet.cpp:155
void setPointSeed(int index, uint32_t seed)
Sets the point seed.
Definition PointSet.cpp:281
Result< void > trySetIntAttribute(int index, const std::string &name, std::int64_t value)
Canonical checked signed integer metadata write.
Definition PointSet.cpp:351
void reserve(std::size_t count)
Reserve point storage without changing point or attribute row counts.
Definition PointSet.cpp:60
RAII keep-alive for one RuntimeObjectRegistry entry.
T * get() const noexcept
Borrows the pinned object; valid until the pin is released.
constexpr uint32_t Floor
Definition Semantic.h:14
constexpr uint32_t Empty
Definition Semantic.h:12
std::uint64_t derivePointId(std::uint64_t namespaceId, std::uint64_t ordinal)
Deterministically derive a non-zero stable point identity.
Definition PointSet.cpp:469
std::unique_ptr< T > Owned
C++ owning storage used by an object factory.
static TextureCreateInfo withMipmaps(bool aniso=true, float maxAniso=16.f)
With mipmaps.
房屋组件模板(几何 + 连接点 + 权重)。
布局中的房间标记。
glm::vec4 color