MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk::detail Namespace Reference

Classes

struct  OBJIndex
struct  OBJLoadResult
struct  OBJTriangle
struct  OBJVertex

Functions

void append_obj_triangle (const std::vector< OBJVertex > &face, std::size_t a, std::size_t b, std::size_t c, const std::string &material_name, const std::string &object_name, std::vector< OBJTriangle > &triangles)
bool load_mtl_file (const std::string &path, std::vector< OBJMaterial > &materials, std::string &error)
bool load_obj_file (const std::string &path, OBJLoadResult &result, std::string &error)
float obj_edge_cross (const OBJVertex &a, const OBJVertex &b, const OBJVertex &c, int drop_axis)
std::array< float, 3 > obj_face_normal (const OBJVertex &a, const OBJVertex &b, const OBJVertex &c)
bool obj_point_in_triangle (const OBJVertex &point, const OBJVertex &a, const OBJVertex &b, const OBJVertex &c, int drop_axis, float winding)
float obj_projected_area (const std::vector< OBJVertex > &face, int drop_axis)
std::string parse_mtl_texture_path (std::istream &stream)
bool parse_obj_face_token (const std::string &token, OBJIndex &index)
bool parse_obj_index_value (const std::string &text, int &value)
template<typename T>
int resolve_obj_index (int index, const std::vector< T > &values)
std::string resolve_obj_path (const std::filesystem::path &base, const std::string &path)
void strip_obj_comment (std::string &text)
void triangulate_obj_face (const std::vector< OBJVertex > &face, const std::string &material_name, const std::string &object_name, std::vector< OBJTriangle > &triangles)
void trim_obj_line (std::string &text)

Function Documentation

◆ append_obj_triangle()

void mxvk::detail::append_obj_triangle ( const std::vector< OBJVertex > & face,
std::size_t a,
std::size_t b,
std::size_t c,
const std::string & material_name,
const std::string & object_name,
std::vector< OBJTriangle > & triangles )
inline

Definition at line 283 of file mxvk_math_obj.hpp.

283 {
284 triangles.push_back({{face[a], face[b], face[c]}, material_name, object_name});
285 }

◆ load_mtl_file()

bool mxvk::detail::load_mtl_file ( const std::string & path,
std::vector< OBJMaterial > & materials,
std::string & error )
inlinenodiscard

Definition at line 162 of file mxvk_math_obj.hpp.

162 {
163 std::ifstream file(path);
164 if (!file.is_open()) {
165 error = "could not open material library '" + path + "'";
166 return false;
167 }
168
169 std::vector<OBJMaterial> loaded_materials;
170 OBJMaterial *current = nullptr;
171 std::string line;
172 std::size_t line_number = 0;
173 while (std::getline(file, line)) {
174 ++line_number;
175 strip_obj_comment(line);
176 if (line.empty()) {
177 continue;
178 }
179
180 std::istringstream stream(line);
181 std::string tag;
182 stream >> tag;
183 if (tag == "newmtl") {
184 loaded_materials.emplace_back();
185 current = &loaded_materials.back();
186 std::getline(stream, current->name);
187 trim_obj_line(current->name);
188 if (current->name.empty()) {
189 error = "unnamed material at line " + std::to_string(line_number) + " in '" + path + "'";
190 return false;
191 }
192 continue;
193 }
194 if (current == nullptr) {
195 continue;
196 }
197
198 bool parsed = true;
199 if (tag == "Ka") {
200 parsed = static_cast<bool>(stream >> current->ambient[0] >> current->ambient[1] >> current->ambient[2]);
201 } else if (tag == "Kd") {
202 parsed = static_cast<bool>(stream >> current->diffuse[0] >> current->diffuse[1] >> current->diffuse[2]);
203 } else if (tag == "Ks") {
204 parsed = static_cast<bool>(stream >> current->specular[0] >> current->specular[1] >> current->specular[2]);
205 } else if (tag == "Ns") {
206 parsed = static_cast<bool>(stream >> current->shininess);
207 } else if (tag == "d") {
208 parsed = static_cast<bool>(stream >> current->dissolve);
209 } else if (tag == "Tr") {
210 float transparency = 0.0f;
211 parsed = static_cast<bool>(stream >> transparency);
212 current->dissolve = 1.0f - transparency;
213 } else if (tag == "illum") {
214 parsed = static_cast<bool>(stream >> current->illumination_model);
215 } else if (tag == "map_Kd") {
216 const std::string texture_path = parse_mtl_texture_path(stream);
217 if (!texture_path.empty()) {
218 current->diffuse_map = resolve_obj_path(std::filesystem::path(path).parent_path(), texture_path);
219 }
220 }
221
222 if (!parsed) {
223 error = "invalid '" + tag + "' value at line " + std::to_string(line_number) + " in '" + path + "'";
224 return false;
225 }
226 }
227
228 for (OBJMaterial &material : loaded_materials) {
229 for (float &component : material.ambient) {
230 component = std::clamp(component, 0.0f, 1.0f);
231 }
232 for (float &component : material.diffuse) {
233 component = std::clamp(component, 0.0f, 1.0f);
234 }
235 for (float &component : material.specular) {
236 component = std::clamp(component, 0.0f, 1.0f);
237 }
238 material.dissolve = std::clamp(material.dissolve, 0.0f, 1.0f);
239 }
240
241 materials = std::move(loaded_materials);
242 return true;
243 }
std::filesystem::path texture_path(const std::string &filename, const std::string &asset_path)
Definition main.cpp:147
std::string resolve_obj_path(const std::filesystem::path &base, const std::string &path)
std::string parse_mtl_texture_path(std::istream &stream)
void trim_obj_line(std::string &text)
void strip_obj_comment(std::string &text)
Wavefront material data used by the software rasterizer.
std::array< float, 3 > ambient
std::string diffuse_map
std::array< float, 3 > diffuse
std::array< float, 3 > specular

◆ load_obj_file()

bool mxvk::detail::load_obj_file ( const std::string & path,
OBJLoadResult & result,
std::string & error )
inlinenodiscard

Definition at line 355 of file mxvk_math_obj.hpp.

355 {
356 std::ifstream file(path);
357 if (!file.is_open()) {
358 error = "could not open file";
359 return false;
360 }
361
362 std::vector<std::array<float, 3>> positions;
363 std::vector<std::array<float, 2>> texcoords;
364 std::vector<std::array<float, 3>> normals;
365 OBJLoadResult loaded;
366 loaded.object_name = std::filesystem::path(path).stem().string();
367 std::string current_object = loaded.object_name;
368 std::string current_material;
369 std::string line;
370 std::size_t line_number = 0;
371
372 while (std::getline(file, line)) {
373 ++line_number;
374 strip_obj_comment(line);
375 if (line.empty()) {
376 continue;
377 }
378
379 std::istringstream stream(line);
380 std::string tag;
381 stream >> tag;
382 if (tag == "v") {
383 std::array<float, 3> position{};
384 if (!(stream >> position[0] >> position[1] >> position[2]) ||
385 !std::isfinite(position[0]) || !std::isfinite(position[1]) || !std::isfinite(position[2])) {
386 error = "invalid vertex at line " + std::to_string(line_number);
387 return false;
388 }
389 positions.push_back(position);
390 } else if (tag == "vt") {
391 std::array<float, 2> texcoord{};
392 if (!(stream >> texcoord[0] >> texcoord[1]) ||
393 !std::isfinite(texcoord[0]) || !std::isfinite(texcoord[1])) {
394 error = "invalid texture coordinate at line " + std::to_string(line_number);
395 return false;
396 }
397 texcoords.push_back(texcoord);
398 } else if (tag == "vn") {
399 std::array<float, 3> normal{};
400 if (!(stream >> normal[0] >> normal[1] >> normal[2]) ||
401 !std::isfinite(normal[0]) || !std::isfinite(normal[1]) || !std::isfinite(normal[2])) {
402 error = "invalid normal at line " + std::to_string(line_number);
403 return false;
404 }
405 normals.push_back(normal);
406 } else if (tag == "o") {
407 std::string name;
408 std::getline(stream, name);
409 trim_obj_line(name);
410 if (!name.empty()) {
411 current_object = name;
412 loaded.object_name = std::move(name);
413 }
414 } else if (tag == "mtllib") {
415 std::string library;
416 std::getline(stream, library);
417 trim_obj_line(library);
418 if (!library.empty() && loaded.material_library_path.empty()) {
419 loaded.material_library_path = resolve_obj_path(std::filesystem::path(path).parent_path(), library);
420 }
421 } else if (tag == "usemtl") {
422 std::getline(stream, current_material);
423 trim_obj_line(current_material);
424 } else if (tag == "f") {
425 std::vector<OBJVertex> face;
426 std::string token;
427 while (stream >> token) {
428 OBJIndex index;
429 if (!parse_obj_face_token(token, index)) {
430 error = "malformed face token '" + token + "' at line " + std::to_string(line_number);
431 return false;
432 }
433
434 const int position_index = resolve_obj_index(index.position, positions);
435 if (position_index < 0 || position_index >= static_cast<int>(positions.size())) {
436 error = "face position index out of range at line " + std::to_string(line_number);
437 return false;
438 }
439
440 OBJVertex vertex;
441 vertex.position = positions[static_cast<std::size_t>(position_index)];
442 if (index.texcoord != 0) {
443 const int texcoord_index = resolve_obj_index(index.texcoord, texcoords);
444 if (texcoord_index < 0 || texcoord_index >= static_cast<int>(texcoords.size())) {
445 error = "face texture index out of range at line " + std::to_string(line_number);
446 return false;
447 }
448 vertex.texcoord = texcoords[static_cast<std::size_t>(texcoord_index)];
449 }
450 if (index.normal != 0) {
451 const int normal_index = resolve_obj_index(index.normal, normals);
452 if (normal_index < 0 || normal_index >= static_cast<int>(normals.size())) {
453 error = "face normal index out of range at line " + std::to_string(line_number);
454 return false;
455 }
456 }
457 face.push_back(vertex);
458 }
459
460 if (face.size() < 3) {
461 error = "face has fewer than three vertices at line " + std::to_string(line_number);
462 return false;
463 }
464 triangulate_obj_face(face, current_material, current_object, loaded.triangles);
465 }
466 }
467
468 if (loaded.triangles.empty()) {
469 error = "no geometry found";
470 return false;
471 }
472 if (!loaded.material_library_path.empty() &&
473 !load_mtl_file(loaded.material_library_path, loaded.materials, error)) {
474 return false;
475 }
476
477 result = std::move(loaded);
478 return true;
479 }
bool parse_obj_face_token(const std::string &token, OBJIndex &index)
bool load_mtl_file(const std::string &path, std::vector< OBJMaterial > &materials, std::string &error)
void triangulate_obj_face(const std::vector< OBJVertex > &face, const std::string &material_name, const std::string &object_name, std::vector< OBJTriangle > &triangles)
int resolve_obj_index(int index, const std::vector< T > &values)
std::array< float, 3 > position

◆ obj_edge_cross()

float mxvk::detail::obj_edge_cross ( const OBJVertex & a,
const OBJVertex & b,
const OBJVertex & c,
int drop_axis )
inlinenodiscard

Definition at line 266 of file mxvk_math_obj.hpp.

266 {
267 const float ax = a.position[(drop_axis + 1) % 3];
268 const float ay = a.position[(drop_axis + 2) % 3];
269 const float bx = b.position[(drop_axis + 1) % 3];
270 const float by = b.position[(drop_axis + 2) % 3];
271 const float cx = c.position[(drop_axis + 1) % 3];
272 const float cy = c.position[(drop_axis + 2) % 3];
273 return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
274 }

◆ obj_face_normal()

std::array< float, 3 > mxvk::detail::obj_face_normal ( const OBJVertex & a,
const OBJVertex & b,
const OBJVertex & c )
inlinenodiscard

Definition at line 245 of file mxvk_math_obj.hpp.

245 {
246 const float ux = b.position[0] - a.position[0];
247 const float uy = b.position[1] - a.position[1];
248 const float uz = b.position[2] - a.position[2];
249 const float vx = c.position[0] - a.position[0];
250 const float vy = c.position[1] - a.position[1];
251 const float vz = c.position[2] - a.position[2];
252 return {uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx};
253 }

◆ obj_point_in_triangle()

bool mxvk::detail::obj_point_in_triangle ( const OBJVertex & point,
const OBJVertex & a,
const OBJVertex & b,
const OBJVertex & c,
int drop_axis,
float winding )
inlinenodiscard

Definition at line 276 of file mxvk_math_obj.hpp.

276 {
277 constexpr float OBJ_EPSILON = 1.0e-6f;
278 return obj_edge_cross(a, b, point, drop_axis) * winding >= -OBJ_EPSILON &&
279 obj_edge_cross(b, c, point, drop_axis) * winding >= -OBJ_EPSILON &&
280 obj_edge_cross(c, a, point, drop_axis) * winding >= -OBJ_EPSILON;
281 }
float obj_edge_cross(const OBJVertex &a, const OBJVertex &b, const OBJVertex &c, int drop_axis)

◆ obj_projected_area()

float mxvk::detail::obj_projected_area ( const std::vector< OBJVertex > & face,
int drop_axis )
inlinenodiscard

Definition at line 255 of file mxvk_math_obj.hpp.

255 {
256 float area = 0.0f;
257 for (std::size_t index = 0; index < face.size(); ++index) {
258 const OBJVertex &a = face[index];
259 const OBJVertex &b = face[(index + 1) % face.size()];
260 area += a.position[(drop_axis + 1) % 3] * b.position[(drop_axis + 2) % 3] -
261 b.position[(drop_axis + 1) % 3] * a.position[(drop_axis + 2) % 3];
262 }
263 return area;
264 }

◆ parse_mtl_texture_path()

std::string mxvk::detail::parse_mtl_texture_path ( std::istream & stream)
inlinenodiscard

Definition at line 138 of file mxvk_math_obj.hpp.

138 {
139 std::string path;
140 std::string token;
141 while (stream >> token) {
142 if (!token.empty() && token.front() == '-') {
143 if (token == "-blendu" || token == "-blendv" || token == "-cc" ||
144 token == "-clamp" || token == "-imfchan" || token == "-type" ||
145 token == "-bm" || token == "-boost" || token == "-texres") {
146 stream >> token;
147 } else if (token == "-mm") {
148 stream >> token >> token;
149 } else if (token == "-o" || token == "-s" || token == "-t") {
150 stream >> token >> token >> token;
151 }
152 continue;
153 }
154 if (!path.empty()) {
155 path += ' ';
156 }
157 path += token;
158 }
159 return path;
160 }

◆ parse_obj_face_token()

bool mxvk::detail::parse_obj_face_token ( const std::string & token,
OBJIndex & index )
inlinenodiscard

Definition at line 94 of file mxvk_math_obj.hpp.

94 {
95 std::array<std::string, 3> fields{};
96 std::size_t field_index = 0;
97 std::size_t field_begin = 0;
98 while (true) {
99 if (field_index >= fields.size()) {
100 return false;
101 }
102 const std::size_t slash = token.find('/', field_begin);
103 fields[field_index++] = token.substr(field_begin, slash == std::string::npos ? std::string::npos : slash - field_begin);
104 if (slash == std::string::npos) {
105 break;
106 }
107 field_begin = slash + 1;
108 }
109
110 return parse_obj_index_value(fields[0], index.position) &&
111 parse_obj_index_value(fields[1], index.texcoord) &&
112 parse_obj_index_value(fields[2], index.normal) &&
113 index.position != 0;
114 }
bool parse_obj_index_value(const std::string &text, int &value)

◆ parse_obj_index_value()

bool mxvk::detail::parse_obj_index_value ( const std::string & text,
int & value )
inlinenodiscard

Definition at line 79 of file mxvk_math_obj.hpp.

79 {
80 if (text.empty()) {
81 value = 0;
82 return true;
83 }
84
85 std::size_t consumed = 0;
86 try {
87 value = std::stoi(text, &consumed, 10);
88 } catch (const std::exception &) {
89 return false;
90 }
91 return consumed == text.size();
92 }

◆ resolve_obj_index()

template<typename T>
int mxvk::detail::resolve_obj_index ( int index,
const std::vector< T > & values )
inlinenodiscard

Definition at line 117 of file mxvk_math_obj.hpp.

117 {
118 if (index > 0) {
119 return index - 1;
120 }
121 if (index < 0) {
122 return static_cast<int>(values.size()) + index;
123 }
124 return -1;
125 }

◆ resolve_obj_path()

std::string mxvk::detail::resolve_obj_path ( const std::filesystem::path & base,
const std::string & path )
inlinenodiscard

Definition at line 127 of file mxvk_math_obj.hpp.

127 {
128 if (path.empty()) {
129 return {};
130 }
131 const std::filesystem::path value(path);
132 if (value.is_absolute()) {
133 return value.lexically_normal().string();
134 }
135 return (base / value).lexically_normal().string();
136 }

◆ strip_obj_comment()

void mxvk::detail::strip_obj_comment ( std::string & text)
inline

Definition at line 71 of file mxvk_math_obj.hpp.

71 {
72 const std::size_t comment = text.find('#');
73 if (comment != std::string::npos) {
74 text.erase(comment);
75 }
76 trim_obj_line(text);
77 }

◆ triangulate_obj_face()

void mxvk::detail::triangulate_obj_face ( const std::vector< OBJVertex > & face,
const std::string & material_name,
const std::string & object_name,
std::vector< OBJTriangle > & triangles )
inline

Definition at line 287 of file mxvk_math_obj.hpp.

287 {
288 if (face.size() < 3) {
289 return;
290 }
291 if (face.size() == 3) {
292 append_obj_triangle(face, 0, 1, 2, material_name, object_name, triangles);
293 return;
294 }
295
296 const std::array<float, 3> normal = obj_face_normal(face[0], face[1], face[2]);
297 int drop_axis = 0;
298 if (std::fabs(normal[1]) > std::fabs(normal[0]) && std::fabs(normal[1]) >= std::fabs(normal[2])) {
299 drop_axis = 1;
300 } else if (std::fabs(normal[2]) > std::fabs(normal[0]) && std::fabs(normal[2]) > std::fabs(normal[1])) {
301 drop_axis = 2;
302 }
303
304 const float area = obj_projected_area(face, drop_axis);
305 if (std::fabs(area) <= 1.0e-6f) {
306 for (std::size_t index = 2; index < face.size(); ++index) {
307 append_obj_triangle(face, 0, index - 1, index, material_name, object_name, triangles);
308 }
309 return;
310 }
311 const float winding = area >= 0.0f ? 1.0f : -1.0f;
312
313 std::vector<std::size_t> remaining(face.size());
314 for (std::size_t index = 0; index < remaining.size(); ++index) {
315 remaining[index] = index;
316 }
317 while (remaining.size() > 3) {
318 bool clipped_ear = false;
319 for (std::size_t index = 0; index < remaining.size(); ++index) {
320 const std::size_t previous = remaining[(index + remaining.size() - 1) % remaining.size()];
321 const std::size_t current = remaining[index];
322 const std::size_t next = remaining[(index + 1) % remaining.size()];
323 if (obj_edge_cross(face[previous], face[current], face[next], drop_axis) * winding <= 1.0e-6f) {
324 continue;
325 }
326
327 bool contains_point = false;
328 for (const std::size_t test : remaining) {
329 if (test != previous && test != current && test != next &&
330 obj_point_in_triangle(face[test], face[previous], face[current], face[next], drop_axis, winding)) {
331 contains_point = true;
332 break;
333 }
334 }
335 if (contains_point) {
336 continue;
337 }
338
339 append_obj_triangle(face, previous, current, next, material_name, object_name, triangles);
340 remaining.erase(remaining.begin() + static_cast<std::ptrdiff_t>(index));
341 clipped_ear = true;
342 break;
343 }
344
345 if (!clipped_ear) {
346 for (std::size_t index = 2; index < remaining.size(); ++index) {
347 append_obj_triangle(face, remaining[0], remaining[index - 1], remaining[index], material_name, object_name, triangles);
348 }
349 return;
350 }
351 }
352 append_obj_triangle(face, remaining[0], remaining[1], remaining[2], material_name, object_name, triangles);
353 }
bool obj_point_in_triangle(const OBJVertex &point, const OBJVertex &a, const OBJVertex &b, const OBJVertex &c, int drop_axis, float winding)
float obj_projected_area(const std::vector< OBJVertex > &face, int drop_axis)
std::array< float, 3 > obj_face_normal(const OBJVertex &a, const OBJVertex &b, const OBJVertex &c)
void append_obj_triangle(const std::vector< OBJVertex > &face, std::size_t a, std::size_t b, std::size_t c, const std::string &material_name, const std::string &object_name, std::vector< OBJTriangle > &triangles)

◆ trim_obj_line()

void mxvk::detail::trim_obj_line ( std::string & text)
inline

Definition at line 61 of file mxvk_math_obj.hpp.

61 {
62 const std::size_t begin = text.find_first_not_of(" \t\r\n");
63 if (begin == std::string::npos) {
64 text.clear();
65 return;
66 }
67 const std::size_t end = text.find_last_not_of(" \t\r\n");
68 text = text.substr(begin, end - begin + 1);
69 }