libmx2 2.2.1
C++20 multimedia and game-engine library (SDL2 / OpenGL / Vulkan)
Loading...
Searching...
No Matches
vk_model.cpp
Go to the documentation of this file.
1/**
2 * @file vk_model.cpp
3 * @brief Implementation of mx::MXModel Vulkan OBJ mesh loader.
4 */
5#include"vk_model.hpp"
6#include<filesystem>
7#include<cmath>
8#include<cstdio>
9
10namespace mx {
11
12 struct Vec2 { float x, y; };
13 struct Vec3 { float x, y, z; };
14
15 static bool ends_with(const std::string &s, const std::string &suf) {
16 return s.size() >= suf.size() && s.compare(s.size() - suf.size(), suf.size(), suf) == 0;
17 }
18
20 if (vertices_.empty()) return;
21 std::vector<VKVertex> unique;
22 std::unordered_map<VKVertex, uint32_t, VKVertexHash> map;
23 std::vector<uint32_t> newIdx;
24 newIdx.reserve(vertices_.size());
25
26 for (auto &v : vertices_) {
27 auto it = map.find(v);
28 if (it != map.end()) {
29 newIdx.push_back(it->second);
30 } else {
31 uint32_t idx = static_cast<uint32_t>(unique.size());
32 unique.push_back(v);
33 map[v] = idx;
34 newIdx.push_back(idx);
35 }
36 }
37
38 size_t before = vertices_.size();
39 vertices_ = std::move(unique);
40 indices_ = std::move(newIdx);
41 std::cout << ">> MXModel::compressIndices: " << before << " verts -> "
42 << vertices_.size() << " unique, " << indices_.size() << " indices\n";
43 }
44
45 void MXModel::load(const std::string &path, float positionScale) {
46 if (ends_with(path, ".obj")) {
47 loadOBJ(path, positionScale);
48 return;
49 }
50 loadMXMOD(path, positionScale);
51 }
52
53 void MXModel::loadOBJ(const std::string &path, float positionScale) {
54 std::ifstream f(path);
55 if (!f.is_open())
56 throw mx::Exception("MXModel::load: failed to open file: " + path);
57
58 std::vector<Vec3> positions;
59 std::vector<Vec2> texcoords;
60 std::vector<Vec3> normals;
61
62 vertices_.clear();
63 indices_.clear();
64 subMeshes_.clear();
65 textureIndex_ = 0;
66
67 std::vector<VKVertex> currentVerts;
68 uint32_t currentTexIdx = 0;
69 std::string currentMtlName;
70 materials_.clear();
71 mtlLibPath_.clear();
72
73 auto finalizeGroup = [&]() {
74 if (currentVerts.empty()) return;
75 SubMesh sm;
76 sm.firstIndex = static_cast<uint32_t>(vertices_.size());
77 sm.indexCount = static_cast<uint32_t>(currentVerts.size());
78 sm.textureIndex = currentTexIdx;
79 sm.materialName = currentMtlName;
80 for (auto &v : currentVerts)
81 vertices_.push_back(v);
82 subMeshes_.push_back(sm);
83 currentVerts.clear();
84 };
85
86 std::string line;
87 while (std::getline(f, line)) {
88 if (line.empty() || line[0] == '#') continue;
89
90 std::istringstream s(line);
91 std::string tag;
92 s >> tag;
93
94 if (tag == "v") {
95 float x, y, z;
96 if (s >> x >> y >> z)
97 positions.push_back({x * positionScale, y * positionScale, z * positionScale});
98 } else if (tag == "vt") {
99 float u, v;
100 if (s >> u >> v)
101 texcoords.push_back({u, v});
102 } else if (tag == "vn") {
103 float x, y, z;
104 if (s >> x >> y >> z)
105 normals.push_back({x, y, z});
106 } else if (tag == "mtllib") {
107 std::string mtlFile;
108 if (s >> mtlFile) {
109 std::filesystem::path objDir = std::filesystem::path(path).parent_path();
110 mtlLibPath_ = (objDir / mtlFile).string();
111 }
112 } else if (tag == "g" || tag == "o" || tag == "usemtl") {
113 finalizeGroup();
114 currentTexIdx = static_cast<uint32_t>(subMeshes_.size());
115 if (tag == "usemtl") {
116 s >> currentMtlName;
117 }
118 } else if (tag == "f") {
119 std::vector<VKVertex> faceVerts;
120 std::string token;
121 while (s >> token) {
122 int vi = 0, ti = 0, ni = 0;
123 if (sscanf(token.c_str(), "%d/%d/%d", &vi, &ti, &ni) == 3) {
124 } else if (sscanf(token.c_str(), "%d//%d", &vi, &ni) == 2) {
125 ti = 0;
126 } else if (sscanf(token.c_str(), "%d/%d", &vi, &ti) == 2) {
127 ni = 0;
128 } else {
129 sscanf(token.c_str(), "%d", &vi);
130 ti = 0; ni = 0;
131 }
132
133 VKVertex vtx{};
134 if (vi != 0) {
135 int idx = vi > 0 ? vi - 1 : static_cast<int>(positions.size()) + vi;
136 if (idx >= 0 && idx < static_cast<int>(positions.size())) {
137 vtx.pos[0] = positions[idx].x;
138 vtx.pos[1] = positions[idx].y;
139 vtx.pos[2] = positions[idx].z;
140 }
141 }
142 if (ti != 0) {
143 int idx = ti > 0 ? ti - 1 : static_cast<int>(texcoords.size()) + ti;
144 if (idx >= 0 && idx < static_cast<int>(texcoords.size())) {
145 vtx.texCoord[0] = texcoords[idx].x;
146 vtx.texCoord[1] = texcoords[idx].y;
147 }
148 }
149 if (ni != 0) {
150 int idx = ni > 0 ? ni - 1 : static_cast<int>(normals.size()) + ni;
151 if (idx >= 0 && idx < static_cast<int>(normals.size())) {
152 vtx.normal[0] = normals[idx].x;
153 vtx.normal[1] = normals[idx].y;
154 vtx.normal[2] = normals[idx].z;
155 }
156 }
157 faceVerts.push_back(vtx);
158 }
159 for (size_t i = 2; i < faceVerts.size(); ++i) {
160 currentVerts.push_back(faceVerts[0]);
161 currentVerts.push_back(faceVerts[i - 1]);
162 currentVerts.push_back(faceVerts[i]);
163 }
164 }
165 }
166
167 finalizeGroup();
168
169 if (!mtlLibPath_.empty())
171
172 if (vertices_.empty())
173 throw mx::Exception("MXModel::load: no vertices found in " + path);
174
175 // If no groups were found, create a single sub-mesh covering everything
176 if (subMeshes_.empty()) {
177 SubMesh sm;
178 sm.firstIndex = 0;
179 sm.indexCount = static_cast<uint32_t>(vertices_.size());
180 sm.textureIndex = 0;
181 subMeshes_.push_back(sm);
182 }
183
184 // Generate flat normals for faces that had no normals
185 bool hasNormals = !normals.empty();
186 if (!hasNormals) {
187 for (size_t i = 0; i + 2 < vertices_.size(); i += 3) {
188 float ax = vertices_[i+1].pos[0] - vertices_[i].pos[0];
189 float ay = vertices_[i+1].pos[1] - vertices_[i].pos[1];
190 float az = vertices_[i+1].pos[2] - vertices_[i].pos[2];
191 float bx = vertices_[i+2].pos[0] - vertices_[i].pos[0];
192 float by = vertices_[i+2].pos[1] - vertices_[i].pos[1];
193 float bz = vertices_[i+2].pos[2] - vertices_[i].pos[2];
194 float nx = ay * bz - az * by;
195 float ny = az * bx - ax * bz;
196 float nz = ax * by - ay * bx;
197 float len = std::sqrt(nx*nx + ny*ny + nz*nz);
198 if (len > 0.0f) { nx /= len; ny /= len; nz /= len; }
199 for (int j = 0; j < 3; ++j) {
200 vertices_[i+j].normal[0] = nx;
201 vertices_[i+j].normal[1] = ny;
202 vertices_[i+j].normal[2] = nz;
203 }
204 }
205 }
206
207 indices_.clear();
208 indices_.reserve(vertices_.size());
209 for (uint32_t i = 0; i < static_cast<uint32_t>(vertices_.size()); ++i)
210 indices_.push_back(i);
211
213 std::cout << ">> MXModel::load (OBJ): " << std::filesystem::path(path).filename().string()
214 << " - " << subMeshes_.size() << " sub-mesh(es) [OK]\n";
215 }
216
217 void MXModel::loadMXMOD(const std::string &path, float positionScale) {
218 std::string text;
219 if (ends_with(path, ".mxmod.z")) {
220 auto buffer = mx::readFile(path);
221 if (buffer.empty())
222 throw mx::Exception("MXModel::load: failed to read file: " + path);
223 text = mx::decompressString(buffer.data(), static_cast<unsigned long>(buffer.size()));
224 } else {
225 std::ifstream f(path);
226 if (!f.is_open())
227 throw mx::Exception("MXModel::load: failed to open file: " + path);
228 std::ostringstream ss;
229 ss << f.rdbuf();
230 text = ss.str();
231 }
232
233 if (text.size() >= 3 &&
234 (unsigned char)text[0] == 0xEF &&
235 (unsigned char)text[1] == 0xBB &&
236 (unsigned char)text[2] == 0xBF) {
237 text.erase(0, 3);
238 }
239
240 std::istringstream file(text);
241 auto trim = [](std::string &s) {
242 size_t b = s.find_first_not_of(" \t\r\n");
243 if (b == std::string::npos) { s.clear(); return; }
244 size_t e = s.find_last_not_of(" \t\r\n");
245 s = s.substr(b, e - b + 1);
246 };
247
248 std::vector<Vec3> pos;
249 std::vector<Vec2> uv;
250 std::vector<Vec3> nrm;
251 std::vector<uint32_t> fileIndices;
252
253 int type = -1;
254 size_t count = 0;
255 std::string line;
256
257 while (std::getline(file, line)) {
258 if (line.empty()) continue;
259 size_t commentPos = line.find('#');
260 if (commentPos != std::string::npos) line = line.substr(0, commentPos);
261 trim(line);
262 if (line.empty()) continue;
263
264
265 if (line.size() >= 3 &&
266 (unsigned char)line[0] == 0xEF &&
267 (unsigned char)line[1] == 0xBB &&
268 (unsigned char)line[2] == 0xBF) {
269 line.erase(0, 3);
270 trim(line);
271 if (line.empty()) continue;
272 }
273
274 std::istringstream s(line);
275
276
277 char c = line[line.find_first_not_of(" \t")];
278 bool isData = (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.';
279
280 if (isData) {
281 float x = 0, y = 0, z = 0;
282 switch (type) {
283 case 0:
284 if (s >> x >> y >> z)
285 pos.push_back({x * positionScale, y * positionScale, z * positionScale});
286 break;
287 case 1:
288 if (s >> x >> y)
289 uv.push_back({x, y});
290 break;
291 case 2:
292 if (s >> x >> y >> z)
293 nrm.push_back({x, y, z});
294 break;
295 case 5: {
296 uint32_t idx;
297 while (s >> idx) fileIndices.push_back(idx);
298 } break;
299 default:
300 break;
301 }
302 continue;
303 }
304
305 std::string tag;
306 s >> tag;
307 if (tag.empty()) continue;
308
309 if (tag == "tri") {
310 uint32_t st = 0, ti = 0;
311 s >> st >> ti;
312 textureIndex_ = ti;
313 type = -1;
314 continue;
315 }
316 if (tag == "vert") { s >> count; type = 0; continue; }
317 if (tag == "tex") { s >> count; type = 1; continue; }
318 if (tag == "norm") { s >> count; type = 2; continue; }
319 if (tag == "indices") { s >> count; type = 5; continue; }
320 }
321
322 if (pos.empty())
323 throw mx::Exception("MXModel::load: no vertices found in " + path);
324
325
326 int vcount = static_cast<int>(pos.size());
327 vertices_.clear();
328 vertices_.reserve(vcount);
329
330 for (int i = 0; i < vcount; ++i) {
331 VKVertex v{};
332 v.pos[0] = pos[i].x;
333 v.pos[1] = pos[i].y;
334 v.pos[2] = pos[i].z;
335 if (i < (int)uv.size()) {
336 v.texCoord[0] = uv[i].x;
337 v.texCoord[1] = uv[i].y;
338 }
339 if (i < (int)nrm.size()) {
340 v.normal[0] = nrm[i].x;
341 v.normal[1] = nrm[i].y;
342 v.normal[2] = nrm[i].z;
343 }
344 vertices_.push_back(v);
345 }
346
347 if (!fileIndices.empty()) {
348 indices_ = std::move(fileIndices);
349 std::cout << ">> MXModel: " << vcount << " verts, " << indices_.size() << " indices from file\n";
350 } else {
351 indices_.clear();
352 indices_.reserve(vcount);
353 for (uint32_t i = 0; i < static_cast<uint32_t>(vcount); ++i)
354 indices_.push_back(i);
356 }
357
358 // MXMOD files produce a single sub-mesh
359 subMeshes_.clear();
360 SubMesh sm;
361 sm.firstIndex = 0;
362 sm.indexCount = static_cast<uint32_t>(indices_.size());
364 subMeshes_.push_back(sm);
365
366 std::cout << ">> MXModel::load: " << std::filesystem::path(path).filename().string() << " - [OK]\n";
367 }
368
369 void MXModel::upload(VkDevice device, VkPhysicalDevice physicalDevice,
370 VkCommandPool commandPool, VkQueue graphicsQueue) {
371 std::cout << ">> MXModel::upload: uploading " << vertices_.size()
372 << " vertices, " << indices_.size() << " indices to GPU...\n";
373 cleanup(device);
374
375 VkDeviceSize vertexBufferSize = sizeof(vertices_[0]) * vertices_.size();
376 VkDeviceSize indexBufferSize = sizeof(indices_[0]) * indices_.size();
377
378
379 VkBuffer stagingVertexBuffer;
380 VkDeviceMemory stagingVertexBufferMemory;
381 createBuffer(device, physicalDevice, vertexBufferSize,
382 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
383 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
384 stagingVertexBuffer, stagingVertexBufferMemory);
385
386
387 VkBuffer stagingIndexBuffer;
388 VkDeviceMemory stagingIndexBufferMemory;
389 createBuffer(device, physicalDevice, indexBufferSize,
390 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
391 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
392 stagingIndexBuffer, stagingIndexBufferMemory);
393
394
395 void *vertexData = nullptr;
396 vkMapMemory(device, stagingVertexBufferMemory, 0, vertexBufferSize, 0, &vertexData);
397 memcpy(vertexData, vertices_.data(), static_cast<size_t>(vertexBufferSize));
398 vkUnmapMemory(device, stagingVertexBufferMemory);
399
400
401 void *indexData = nullptr;
402 vkMapMemory(device, stagingIndexBufferMemory, 0, indexBufferSize, 0, &indexData);
403 memcpy(indexData, indices_.data(), static_cast<size_t>(indexBufferSize));
404 vkUnmapMemory(device, stagingIndexBufferMemory);
405
406
407 createBuffer(device, physicalDevice, vertexBufferSize,
408 VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
409 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
411
412
413 createBuffer(device, physicalDevice, indexBufferSize,
414 VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
415 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
417
418
419 copyBuffer(device, commandPool, graphicsQueue, stagingVertexBuffer, vertexBuffer_, vertexBufferSize);
420 copyBuffer(device, commandPool, graphicsQueue, stagingIndexBuffer, indexBuffer_, indexBufferSize);
421
422
423 vkDestroyBuffer(device, stagingVertexBuffer, nullptr);
424 vkFreeMemory(device, stagingVertexBufferMemory, nullptr);
425 vkDestroyBuffer(device, stagingIndexBuffer, nullptr);
426 vkFreeMemory(device, stagingIndexBufferMemory, nullptr);
427 std::cout << ">> MXModel::upload: GPU buffers created, staging buffers released [OK]\n";
428 }
429
430 void MXModel::cleanup(VkDevice device) {
431 if (vertexBuffer_ != VK_NULL_HANDLE) {
432 std::cout << ">> MXModel::cleanup: destroying vertex and index buffers\n";
433 vkDestroyBuffer(device, vertexBuffer_, nullptr);
434 vkFreeMemory(device, vertexBufferMemory_, nullptr);
435 vertexBuffer_ = VK_NULL_HANDLE;
436 vertexBufferMemory_ = VK_NULL_HANDLE;
437 }
438 if (indexBuffer_ != VK_NULL_HANDLE) {
439 vkDestroyBuffer(device, indexBuffer_, nullptr);
440 vkFreeMemory(device, indexBufferMemory_, nullptr);
441 indexBuffer_ = VK_NULL_HANDLE;
442 indexBufferMemory_ = VK_NULL_HANDLE;
443 }
444 }
445
446 void MXModel::draw(VkCommandBuffer cmd) const {
447 if (vertexBuffer_ == VK_NULL_HANDLE || indexBuffer_ == VK_NULL_HANDLE) {
448 std::cout << ">> MXModel::draw: skipped (no buffers)\n";
449 return;
450 }
451
452 VkBuffer buffers[] = { vertexBuffer_ };
453 VkDeviceSize offsets[] = { 0 };
454 vkCmdBindVertexBuffers(cmd, 0, 1, buffers, offsets);
455 vkCmdBindIndexBuffer(cmd, indexBuffer_, 0, VK_INDEX_TYPE_UINT32);
456 vkCmdDrawIndexed(cmd, indexCount(), 1, 0, 0, 0);
457 }
458
459 void MXModel::drawSubMesh(VkCommandBuffer cmd, size_t index) const {
460 if (vertexBuffer_ == VK_NULL_HANDLE || indexBuffer_ == VK_NULL_HANDLE) {
461 std::cout << ">> MXModel::drawSubMesh: skipped (no buffers)\n";
462 return;
463 }
464 if (index >= subMeshes_.size()) {
465 std::cout << ">> MXModel::drawSubMesh: index " << index << " out of range (" << subMeshes_.size() << " sub-meshes)\n";
466 return;
467 }
468
469 VkBuffer buffers[] = { vertexBuffer_ };
470 VkDeviceSize offsets[] = { 0 };
471 vkCmdBindVertexBuffers(cmd, 0, 1, buffers, offsets);
472 vkCmdBindIndexBuffer(cmd, indexBuffer_, 0, VK_INDEX_TYPE_UINT32);
473
474 const auto &sm = subMeshes_[index];
475 vkCmdDrawIndexed(cmd, sm.indexCount, 1, sm.firstIndex, 0, 0);
476 }
477
478 void MXModel::createBuffer(VkDevice device, VkPhysicalDevice physicalDevice,
479 VkDeviceSize size, VkBufferUsageFlags usage,
480 VkMemoryPropertyFlags properties,
481 VkBuffer &buffer, VkDeviceMemory &bufferMemory) {
482 VkBufferCreateInfo bufferInfo{};
483 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
484 bufferInfo.size = size;
485 bufferInfo.usage = usage;
486 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
487
488 if (vkCreateBuffer(device, &bufferInfo, nullptr, &buffer) != VK_SUCCESS)
489 throw mx::Exception("MXModel: Failed to create buffer!");
490 std::cout << ">> MXModel::createBuffer: allocated " << size << " bytes\n";
491
492 VkMemoryRequirements memRequirements;
493 vkGetBufferMemoryRequirements(device, buffer, &memRequirements);
494
495 VkMemoryAllocateInfo allocInfo{};
496 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
497 allocInfo.allocationSize = memRequirements.size;
498 allocInfo.memoryTypeIndex = findMemoryType(physicalDevice, memRequirements.memoryTypeBits, properties);
499
500 if (vkAllocateMemory(device, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS)
501 throw mx::Exception("MXModel: Failed to allocate buffer memory!");
502
503 vkBindBufferMemory(device, buffer, bufferMemory, 0);
504 }
505
506 uint32_t MXModel::findMemoryType(VkPhysicalDevice physicalDevice,
507 uint32_t typeFilter, VkMemoryPropertyFlags properties) {
508 VkPhysicalDeviceMemoryProperties memProperties;
509 vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
510 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
511 if ((typeFilter & (1 << i)) &&
512 (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
513 return i;
514 }
515 }
516 throw mx::Exception("MXModel: Failed to find suitable memory type!");
517 }
518
519 void MXModel::copyBuffer(VkDevice device, VkCommandPool commandPool,
520 VkQueue graphicsQueue,
521 VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
522 std::cout << ">> MXModel::copyBuffer: copying " << size << " bytes to device-local memory\n";
523 VkCommandBufferAllocateInfo allocInfo{};
524 allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
525 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
526 allocInfo.commandPool = commandPool;
527 allocInfo.commandBufferCount = 1;
528
529 VkCommandBuffer commandBuffer;
530 vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
531
532 VkCommandBufferBeginInfo beginInfo{};
533 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
534 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
535 vkBeginCommandBuffer(commandBuffer, &beginInfo);
536
537 VkBufferCopy copyRegion{};
538 copyRegion.size = size;
539 vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
540
541 vkEndCommandBuffer(commandBuffer);
542
543 VkSubmitInfo submitInfo{};
544 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
545 submitInfo.commandBufferCount = 1;
546 submitInfo.pCommandBuffers = &commandBuffer;
547
548 vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
549 vkQueueWaitIdle(graphicsQueue);
550
551 vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
552 }
553
554 void MXModel::loadMTL(const std::string &path) {
555 std::ifstream f(path);
556 if (!f.is_open()) {
557 std::cerr << ">> MXModel::loadMTL: warning - cannot open: " << path << "\n";
558 return;
559 }
560
561 MXMaterial *current = nullptr;
562 std::string line;
563 while (std::getline(f, line)) {
564 if (line.empty() || line[0] == '#') continue;
565 std::istringstream s(line);
566 std::string tag;
567 s >> tag;
568
569 if (tag == "newmtl") {
570 materials_.emplace_back();
571 current = &materials_.back();
572 s >> current->name;
573 } else if (!current) {
574 continue;
575 } else if (tag == "Ka") {
576 s >> current->ka[0] >> current->ka[1] >> current->ka[2];
577 } else if (tag == "Kd") {
578 s >> current->kd[0] >> current->kd[1] >> current->kd[2];
579 } else if (tag == "Ks") {
580 s >> current->ks[0] >> current->ks[1] >> current->ks[2];
581 } else if (tag == "Ns") {
582 s >> current->ns;
583 } else if (tag == "d") {
584 s >> current->d;
585 } else if (tag == "illum") {
586 s >> current->illum;
587 } else if (tag == "map_Kd") {
588 s >> current->map_kd;
589 }
590 }
591 std::cout << ">> MXModel::loadMTL: loaded " << materials_.size()
592 << " material(s) from " << path << "\n";
593 }
594
595}
General-purpose exception class for the mx library.
Definition exception.hpp:20
std::string mtlLibPath_
Path to .mtl file from OBJ mtllib.
Definition vk_model.hpp:210
uint32_t indexCount() const
Definition vk_model.hpp:176
static void createBuffer(VkDevice device, VkPhysicalDevice physicalDevice, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer &buffer, VkDeviceMemory &bufferMemory)
Allocate a Vulkan buffer with the specified usage and memory flags.
Definition vk_model.cpp:478
void compressIndices()
Deduplicate vertices and rebuild the index list.
Definition vk_model.cpp:19
std::vector< uint32_t > indices_
Parsed index data.
Definition vk_model.hpp:206
std::vector< SubMesh > subMeshes_
Sub-mesh ranges (one per group).
Definition vk_model.hpp:208
VkDeviceMemory indexBufferMemory_
Definition vk_model.hpp:215
void loadOBJ(const std::string &path, float positionScale)
Load a Wavefront OBJ file.
Definition vk_model.cpp:53
std::vector< VKVertex > vertices_
Parsed vertex data.
Definition vk_model.hpp:205
void drawSubMesh(VkCommandBuffer cmd, size_t index) const
Draw a single sub-mesh (binds vertex/index buffers and issues indexed draw).
Definition vk_model.cpp:459
static uint32_t findMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter, VkMemoryPropertyFlags properties)
Find a memory type index satisfying typeFilter and properties.
Definition vk_model.cpp:506
void loadMXMOD(const std::string &path, float positionScale)
Load an MXMOD format file.
Definition vk_model.cpp:217
void upload(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue graphicsQueue)
Upload parsed geometry to device-local Vulkan buffers.
Definition vk_model.cpp:369
VkBuffer vertexBuffer_
Definition vk_model.hpp:212
void draw(VkCommandBuffer cmd) const
Record a bind + indexed draw into a command buffer.
Definition vk_model.cpp:446
uint32_t textureIndex_
Sub-mesh texture index.
Definition vk_model.hpp:207
VkBuffer indexBuffer_
Definition vk_model.hpp:214
std::vector< MXMaterial > materials_
Parsed MTL materials.
Definition vk_model.hpp:209
void cleanup(VkDevice device)
Free all Vulkan buffer resources.
Definition vk_model.cpp:430
void load(const std::string &path, float positionScale=1.0f)
Parse a model file (.mxmod, .mxmod.z, or .obj) and populate the vertex/index vectors.
Definition vk_model.cpp:45
VkDeviceMemory vertexBufferMemory_
Definition vk_model.hpp:213
void loadMTL(const std::string &path)
Parse a Wavefront .mtl file and populate materials_.
Definition vk_model.cpp:554
static void copyBuffer(VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue, VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
Copy bytes from one Vulkan buffer to another via a staging command.
Definition vk_model.cpp:519
Definition cfg.cpp:11
std::string decompressString(void *data, uLong size_)
Decompress a zlib-compressed data block.
Definition util.cpp:46
std::vector< char > readFile(const std::string &filename)
Read an entire file into a byte vector.
Definition util.cpp:77
static bool ends_with(const std::string &s, const std::string &suf)
Definition model.cpp:611
Parsed Wavefront MTL material properties.
Definition model.hpp:45
float ks[3]
Specular colour.
Definition model.hpp:49
int illum
Illumination model.
Definition model.hpp:52
float ka[3]
Ambient colour.
Definition model.hpp:47
float ns
Specular exponent.
Definition model.hpp:50
std::string map_kd
Diffuse texture filename.
Definition model.hpp:53
float d
Opacity (dissolve).
Definition model.hpp:51
float kd[3]
Diffuse colour.
Definition model.hpp:48
std::string name
Material name (newmtl).
Definition model.hpp:46
A range of indices within the shared index buffer, with its own texture.
Definition vk_model.hpp:91
std::string materialName
Material name from OBJ usemtl directive.
Definition vk_model.hpp:95
uint32_t textureIndex
Texture slot for this sub-mesh.
Definition vk_model.hpp:94
uint32_t indexCount
Number of indices in this sub-mesh.
Definition vk_model.hpp:93
uint32_t firstIndex
Offset into the index buffer.
Definition vk_model.hpp:92
Per-vertex data uploaded to the Vulkan vertex buffer.
Definition vk_model.hpp:38
float normal[3]
Surface normal vector.
Definition vk_model.hpp:41
float texCoord[2]
UV texture coordinates.
Definition vk_model.hpp:40
float pos[3]
3-D position (x, y, z).
Definition vk_model.hpp:39
float x
Definition vk_model.cpp:12
float y
Definition vk_model.cpp:12
float y
Definition vk_model.cpp:13
float z
Definition vk_model.cpp:13
float x
Definition vk_model.cpp:13
Vulkan 3-D model loader and GPU buffer manager.