载入中...
搜索中...
未找到
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
12#include <assimp/material.h>
13#include <assimp/matrix4x4.h>
14#include <assimp/scene.h>
15#include <assimp/texture.h>
16#include "common/Json.h"
17
18#include <cmath>
19#include <cstdlib>
20#include <filesystem>
21#include <fstream>
22#include <functional>
23#include <memory>
24#include <sstream>
25#include <stdexcept>
26#include <tuple>
27#include <unordered_map>
28#include <unordered_set>
29
30namespace eve::housegen {
31namespace {
32std::string esc(const std::string &v) { std::string o; for (char c : v) { if (c == '\\' || c == '"') o += '\\'; o += c; } return o; }
33
34graphics::Texture *textureFromEmbedded(graphics::Graphics *gfx, const aiTexture *source) {
35 if (!source || !source->pcData) return nullptr;
36 if (source->mHeight == 0) {
37 data::ByteData bytes(source->pcData, static_cast<size_t>(source->mWidth));
38 std::unique_ptr<image::ImageData> decoded(image::Image::create()->newImageData(&bytes));
39 return gfx->newTexture(decoded.get(), graphics::TextureCreateInfo::withMipmaps(true));
40 }
41 std::vector<uint8_t> rgba(size_t(source->mWidth) * size_t(source->mHeight) * 4);
42 for (size_t pixel = 0; pixel < size_t(source->mWidth) * size_t(source->mHeight); ++pixel) {
43 rgba[pixel * 4 + 0] = source->pcData[pixel].r;
44 rgba[pixel * 4 + 1] = source->pcData[pixel].g;
45 rgba[pixel * 4 + 2] = source->pcData[pixel].b;
46 rgba[pixel * 4 + 3] = source->pcData[pixel].a;
47 }
48 return gfx->newTexture(int(source->mWidth), int(source->mHeight), rgba.data(),
50}
51
52graphics::Texture *textureFromFile(graphics::Graphics *gfx, const std::string &modelPath,
53 const std::string &texturePath) {
54 if (texturePath.empty()) return nullptr;
55 std::filesystem::path resolved(texturePath);
56 if (resolved.is_relative()) resolved = std::filesystem::path(modelPath).parent_path() / resolved;
57 try {
58 if (std::filesystem::is_regular_file(resolved)) {
59 std::ifstream input(resolved, std::ios::binary | std::ios::ate);
60 const std::streamsize size = input.tellg();
61 if (size <= 0) return nullptr;
62 input.seekg(0, std::ios::beg);
63 std::vector<uint8_t> bytes(static_cast<size_t>(size));
64 if (!input.read(reinterpret_cast<char *>(bytes.data()), size)) return nullptr;
65 data::ByteData source(bytes.data(), bytes.size());
66 std::unique_ptr<image::ImageData> decoded(
67 image::Image::create()->newImageData(&source));
68 return gfx->newTexture(decoded.get(), graphics::TextureCreateInfo::withMipmaps(true));
69 }
70 return gfx->newTextureFromFile(resolved.lexically_normal().string());
71 } catch (...) {
72 return nullptr;
73 }
74}
75
76graphics::Texture *assimpTexture(graphics::Graphics *gfx, const aiScene *scene,
77 const aiMaterial *material, const std::string &modelPath,
78 aiTextureType primary, aiTextureType fallback) {
79 if (!scene || !material) return nullptr;
80 aiString texturePath;
81 if (material->GetTexture(primary, 0, &texturePath) != AI_SUCCESS &&
82 material->GetTexture(fallback, 0, &texturePath) != AI_SUCCESS)
83 return nullptr;
84 const std::string path = texturePath.C_Str();
85 if (path.empty()) return nullptr;
86 if (path.front() == '*') {
87 const int index = std::atoi(path.c_str() + 1);
88 if (index < 0 || static_cast<unsigned>(index) >= scene->mNumTextures) return nullptr;
89 try {
90 return textureFromEmbedded(gfx, scene->mTextures[index]);
91 } catch (...) {
92 return nullptr;
93 }
94 }
95 return textureFromFile(gfx, modelPath, path);
96}
97
98eve::ref<model3d::ModelData> loadModel(model3d::Model3D *models, const std::string &path) {
99 if (!std::filesystem::is_regular_file(std::filesystem::path(path)))
100 return models->newModelDataFromFile(path); // cache-owned resource
101 std::ifstream input(path, std::ios::binary | std::ios::ate);
102 if (!input) throw std::runtime_error("cannot open model: " + path);
103 const std::streamsize size = input.tellg();
104 if (size <= 0) throw std::runtime_error("model is empty: " + path);
105 input.seekg(0, std::ios::beg);
106 std::vector<uint8_t> bytes(static_cast<size_t>(size));
107 if (!input.read(reinterpret_cast<char *>(bytes.data()), size))
108 throw std::runtime_error("cannot read model: " + path);
109 data::ByteData source(bytes.data(), bytes.size());
110 return models->newModelData(&source, std::filesystem::path(path).extension().string());
111}
112}
113
114void HouseLayout::clear() { instances.clear(); rooms.clear(); diagnostics.clear(); seed = 1; moduleSize = 1.f; floorHeight = 3.f; footprintStyle = "rectangle"; roofStyle = "gable"; entranceSide = "north"; }
115
116std::string HouseLayout::toJson() const {
117 std::ostringstream o;
118 o << "{\"seed\":" << seed << ",\"moduleSize\":" << moduleSize << ",\"floorHeight\":" << floorHeight
119 << ",\"footprintStyle\":\"" << esc(footprintStyle) << "\",\"roofStyle\":\"" << esc(roofStyle)
120 << "\",\"entranceSide\":\"" << esc(entranceSide) << "\",\"instances\":[";
121 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 << '}'; }
122 o << "],\"rooms\":[";
123 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 << ",\"width\":" << v.width << ",\"depth\":" << v.depth << '}'; }
124 o << "],\"diagnostics\":[";
125 for (size_t i = 0; i < diagnostics.size(); ++i) { if (i) o << ','; o << '"' << esc(diagnostics[i]) << '"'; }
126 o << "]}";
127 return o.str();
128}
129
130bool HouseLayout::fromJson(const std::string &json, std::string *error) {
131 using eve::json::Value;
133 if (!doc.valid()) return false;
134 const Value o = doc.root();
135 if (!o.isObject()) { if (error) *error = "layout must be an object"; return false; }
136
137 HouseLayout parsed;
138 parsed.seed = static_cast<unsigned>(o.getInt("seed", 1));
139 parsed.moduleSize = o.getFloat("moduleSize", 1.f);
140 parsed.floorHeight = o.getFloat("floorHeight", 3.f);
141 parsed.footprintStyle = o.getString("footprintStyle", "rectangle");
142 parsed.roofStyle = o.getString("roofStyle", "gable");
143 parsed.entranceSide = o.getString("entranceSide", "north");
144
145 const Value instances = o.get("instances");
146 if (!instances.isArray()) { if (error) *error = "layout has no instances"; return false; }
147 for (size_t i = 0; i < instances.size(); ++i) {
148 const Value v = instances.at(i);
149 // componentId and the cell coordinates are required, not defaulted.
150 if (!v.has("componentId") || !v.has("x") || !v.has("y") || !v.has("z")) {
151 if (error) *error = "instance needs componentId, x, y and z";
152 return false;
153 }
154 parsed.instances.push_back({v.getString("componentId"), v.getInt("x"), v.getInt("y"),
155 v.getInt("z"), v.getInt("rotationDeg", 0)});
156 }
157
158 const Value rooms = o.get("rooms");
159 for (size_t i = 0; i < rooms.size(); ++i) {
160 const Value v = rooms.at(i);
161 if (!v.has("type") || !v.has("x") || !v.has("y") || !v.has("width") || !v.has("depth")) {
162 if (error) *error = "room needs type, x, y, width and depth";
163 return false;
164 }
165 parsed.rooms.push_back({v.getString("type"), v.getInt("x"), v.getInt("y"),
166 v.getInt("width"), v.getInt("depth")});
167 }
168
169 parsed.diagnostics = o.getStringArray("diagnostics");
170 *this = std::move(parsed);
171 return true;
172}
173
174bool HouseLayout::validate(const HouseComponentLibrary &library, std::string *error) const {
175 std::unordered_set<std::string> occupied;
176 std::unordered_set<std::string> floorCells, roofCells;
177 std::vector<std::tuple<int, int, int>> floors;
178 auto cellKey = [](int x, int y, int z) {
179 return std::to_string(x) + ":" + std::to_string(y) + ":" + std::to_string(z);
180 };
181 bool entrance = false, roof = false;
182 for (const auto &i : instances) {
183 const auto *c = library.find(i.componentId);
184 if (!c) { if (error) *error = "unknown component: " + i.componentId; return false; }
185 if (i.rotationDeg % 90 != 0) { if (error) *error = "non-cardinal rotation"; return false; }
186 const bool quarter = (i.rotationDeg / 90) % 2 != 0;
187 const int w = quarter ? c->depth : c->width, d = quarter ? c->width : c->depth;
188 for (int y = 0; y < d; ++y) for (int x = 0; x < w; ++x) {
189 // Boundary cells legitimately carry two perpendicular wall modules at corners.
190 const std::string orientation = (c->category == "wall" || c->category == "door")
191 ? ":" + std::to_string((i.rotationDeg % 360 + 360) % 360)
192 : "";
193 const std::string key = std::to_string(i.x + x) + ":" + std::to_string(i.y + y) + ":" + std::to_string(i.z) + ":" + c->category + orientation;
194 if (!occupied.insert(key).second) { if (error) *error = "overlapping " + c->category + " components"; return false; }
195 if (c->category == "floor") {
196 floorCells.insert(cellKey(i.x + x, i.y + y, i.z));
197 floors.emplace_back(i.x + x, i.y + y, i.z);
198 } else if (c->category == "roof") {
199 roofCells.insert(cellKey(i.x + x, i.y + y, i.z));
200 }
201 }
202 entrance = entrance || (c->category == "door" && i.z == 0);
203 roof = roof || c->category == "roof";
204 }
205 if (!entrance) { if (error) *error = "house has no entrance"; return false; }
206 if (!roof) { if (error) *error = "house has no roof"; return false; }
207 for (const auto &[x, y, z] : floors) {
208 if (z > 0 && !floorCells.contains(cellKey(x, y, z - 1))) {
209 if (error) *error = "upper floor has no structural support";
210 return false;
211 }
212 if (!floorCells.contains(cellKey(x, y, z + 1)) &&
213 !roofCells.contains(cellKey(x, y, z + 1))) {
214 if (error) *error = "floor cell has no roof coverage";
215 return false;
216 }
217 }
218 return true;
219}
220
221std::vector<graphics::Renderable3D *> HouseLayout::instantiate(graphics::Graphics *gfx, model3d::Model3D *models, const HouseComponentLibrary &library, std::string *error) const {
222 std::vector<graphics::Renderable3D *> entities;
223 if (!gfx || !models) { if (error) *error = "graphics and model3d are required"; return entities; }
224 std::unordered_map<std::string, eve::ref<model3d::ModelData>> data;
225 struct CachedPart {
226 graphics::Mesh *mesh = nullptr;
227 graphics::Texture *texture = nullptr;
228 graphics::Texture *normalTexture = nullptr;
229 graphics::Texture *heightTexture = nullptr;
230 float r = 1.f, g = 1.f, b = 1.f, a = 1.f;
231 float metallic = 0.f, roughness = 0.72f;
232 float parallaxScale = 0.f, parallaxMinLayers = 8.f, parallaxMaxLayers = 32.f;
233 float cellBombScale = 4.f, cellBombStrength = 0.f, cellBombRotation = 1.f;
234 };
235 std::unordered_map<std::string, std::vector<CachedPart>> parts;
236 try {
237 for (const auto &i : instances) {
238 const auto *c = library.find(i.componentId); if (!c) continue;
239 // eve::ref cannot represent null, so look up before default-inserting.
240 auto modelIt = data.find(c->modelPath);
241 if (modelIt == data.end())
242 modelIt = data.emplace(c->modelPath, loadModel(models, c->modelPath)).first;
243 model3d::ModelData *model = modelIt->second.get();
244 // Material overrides belong to a component, so two components may safely reuse the
245 // same GLB with different architectural finishes.
246 auto &cached = parts[c->id];
247 if (cached.empty()) {
248 const aiScene *scene = model->getScene();
249 if (!scene || !scene->mRootNode) throw std::runtime_error("model has no scene root: " + c->modelPath);
250 std::function<void(const aiNode *, const aiMatrix4x4 &)> walk =
251 [&](const aiNode *node, const aiMatrix4x4 &parent) {
252 const aiMatrix4x4 world = parent * node->mTransformation;
253 for (unsigned ni = 0; ni < node->mNumMeshes; ++ni) {
254 const unsigned mi = node->mMeshes[ni];
255 if (mi >= scene->mNumMeshes || !scene->mMeshes[mi]) continue;
256 const aiMesh *source = scene->mMeshes[mi];
257 CachedPart part;
258 part.mesh = gfx->newMeshFromAssimp(*source, world);
259 if (scene->mMaterials && source->mMaterialIndex < scene->mNumMaterials) {
260 const aiMaterial *material = scene->mMaterials[source->mMaterialIndex];
261 aiColor4D color(1.f, 1.f, 1.f, 1.f);
262 material->Get(AI_MATKEY_COLOR_DIFFUSE, color);
263 material->Get(AI_MATKEY_BASE_COLOR, color);
264 part.r = color.r; part.g = color.g; part.b = color.b; part.a = color.a;
265 material->Get(AI_MATKEY_METALLIC_FACTOR, part.metallic);
266 material->Get(AI_MATKEY_ROUGHNESS_FACTOR, part.roughness);
267 part.texture = assimpTexture(gfx, scene, material, c->modelPath,
268 aiTextureType_BASE_COLOR,
269 aiTextureType_DIFFUSE);
270 part.normalTexture = assimpTexture(gfx, scene, material, c->modelPath,
271 aiTextureType_NORMALS,
272 aiTextureType_NORMAL_CAMERA);
273 part.heightTexture = assimpTexture(gfx, scene, material, c->modelPath,
274 aiTextureType_HEIGHT,
275 aiTextureType_DISPLACEMENT);
276 }
277 if (c->material.hasBaseColor) {
278 part.r = c->material.baseColorR; part.g = c->material.baseColorG;
279 part.b = c->material.baseColorB; part.a = c->material.baseColorA;
280 }
281 if (!c->material.baseColorTexture.empty())
282 part.texture = textureFromFile(gfx, c->modelPath,
283 c->material.baseColorTexture);
284 if (!c->material.normalTexture.empty())
285 part.normalTexture = textureFromFile(gfx, c->modelPath,
286 c->material.normalTexture);
287 if (!c->material.heightTexture.empty())
288 part.heightTexture = textureFromFile(gfx, c->modelPath,
289 c->material.heightTexture);
290 if (c->material.hasMetallic) part.metallic = c->material.metallic;
291 if (c->material.hasRoughness) part.roughness = c->material.roughness;
292 part.parallaxScale = c->material.parallaxScale;
293 part.parallaxMinLayers = c->material.parallaxMinLayers;
294 part.parallaxMaxLayers = c->material.parallaxMaxLayers;
295 part.cellBombScale = c->material.cellBombScale;
296 part.cellBombStrength = c->material.cellBombStrength;
297 part.cellBombRotation = c->material.cellBombRotation;
298 cached.push_back(part);
299 }
300 for (unsigned child = 0; child < node->mNumChildren; ++child)
301 walk(node->mChildren[child], world);
302 };
303 walk(scene->mRootNode, aiMatrix4x4());
304 }
305 for (const auto &part : cached) {
306 auto *e = graphics::Renderable3D::create();
307 e->setMesh(part.mesh);
308 e->setPosition(i.x * moduleSize, i.z * floorHeight, i.y * moduleSize);
309 e->setYaw(float(i.rotationDeg) * 3.14159265358979323846f / 180.f);
310 e->setTint(part.r, part.g, part.b, part.a);
311 if (part.texture) e->setTexture(part.texture);
312 if (part.normalTexture) e->setNormalTexture(part.normalTexture);
313 if (part.heightTexture) e->setHeightTexture(part.heightTexture);
314 e->setMetallic(part.metallic);
315 e->setRoughness(part.roughness);
316 e->setTexCellBomb(part.cellBombScale, part.cellBombStrength,
317 part.cellBombRotation);
318 if (part.heightTexture && part.parallaxScale > 0.f)
319 e->setParallax(part.parallaxScale, part.parallaxMinLayers,
320 part.parallaxMaxLayers);
321 entities.push_back(e);
322 }
323 }
324 } catch (const std::exception &e) { if (error) *error = e.what(); }
325 return entities;
326}
327
328} // namespace eve::housegen
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
int w
std::string error
uint32_t a
uint32_t b
uint32_t c
float roughness
float metallic
Mesh * mesh
glm::mat4 model
Material * material
Light2D::Data * data
int d
int v
image::ImageData::Colorf color
int parent
Definition TreeMesh.cpp:175
virtual Mesh * newMeshFromAssimp(const ::aiMesh &mesh)=0
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:18
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:17
房屋组件注册表(按 id 索引,支持分类/风格查询)。
const HouseComponent * find(const std::string &id) const
按 id 查询组件。
一次生成的房屋布局:实例 + 房间 + 元信息,可 JSON 序列化 / 实例化。
Definition HouseLayout.h:16
std::string footprintStyle
布局风格结果。
Definition HouseLayout.h:23
std::string toJson() const
序列化为 JSON / 从 JSON 恢复。
std::vector< HouseRoom > rooms
Definition HouseLayout.h:28
bool validate(const HouseComponentLibrary &library, std::string *error=nullptr) const
校验布局是否满足组件库规则。
float moduleSize
生成参数回显。
Definition HouseLayout.h:20
void clear()
清空布局。
std::vector< graphics::Renderable3D * > instantiate(graphics::Graphics *gfx, model3d::Model3D *models, const HouseComponentLibrary &library, std::string *error=nullptr) const
把布局实例化为场景中的 Renderable3D(调用方持有)。
bool fromJson(const std::string &json, std::string *error=nullptr)
std::vector< std::string > diagnostics
Definition HouseLayout.h:29
std::vector< HouseInstance > instances
组件实例 / 房间 / 诊断信息。
Definition HouseLayout.h:27
bool valid() const
Definition Json.h:111
static Document parse(const std::string &text, std::string *error=nullptr)
Definition Json.cpp:449
Value root() const
Definition Json.h:112
Resource module for decoding 3D models via medialoader (Assimp). Produces ModelData; GPU upload is gr...
Definition Model3D.h:41
CPU-side decoded 3D model (Assimp scene owned via medialoader::ModelScene). Does not upload to GPU — ...
Definition ModelData.h:23
const aiScene * getScene() const
Definition ModelData.cpp:65
ref is a smart pointer that automatically calls ref() and unref() on the object. Type T must be a sub...
Definition Object.h:54
static TextureCreateInfo withMipmaps(bool aniso=true, float maxAniso=16.f)