├── LICENSE
├── README.md
├── obj2c.cpp
└── spatial_sort.hpp
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Guilherme Lampert
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # obj2c
3 |
4 | Simple command line tool to convert Wavefront OBJ models to C/C++ data arrays.
5 |
6 |
7 | Usage:
8 | $ obj2c source-file target-file [options]
9 |
10 | Options:
11 | -h, --help Shows this help text.
12 | -v, --verbose Be verbose; output a lot of info and timings.
13 | -s, --static_arrays If present, add the 'static' qualifier to array declarations.
14 | -c, --write_counts Write lengths of data arrays as constants.
15 | --inc_file[=name] If flag present, generate an include file externing the array variables.
16 | Incompatible with 'static_arrays'. If no filename provided, uses the target file name.
17 | -n, --smooth_normals If set, gen smooth per-vertex normals. Default are shared per-face 'flat' normals.
18 | -f, --vb_friendly Make the output 'Vertex Buffer friendly'. That is, single index per-vertex.
19 | --ib_type=type Index buffer data type for when using 'vb_friendly'.
20 | Possible values are 16, 16std, 32 and 32std.
21 | The 'std' suffix causes the use of the standard C data types found in cstdint/stdint.h
22 | --no_uvs Don't output mesh UVs, even if they are present in the OBJ file.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/obj2c.cpp:
--------------------------------------------------------------------------------
1 |
2 | // ================================================================================================
3 | // -*- C++ -*-
4 | // File: obj2c.cpp
5 | // Author: Guilherme R. Lampert
6 | // Created on: 04/08/16
7 | //
8 | // Brief: Very basic command line tool that converts Wavefront Object (.obj) 3D mesh
9 | // files into C/C++ arrays of data that can be directly embedded in source code.
10 | //
11 | // Released under the MIT license. See the accompanying LICENSE file
12 | // or visit
13 | // ================================================================================================
14 |
15 | // c++ -std=c++11 -Wall -Wextra -Wshadow -pedantic obj2c.cpp -o obj2c
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #include "spatial_sort.hpp"
33 |
34 | // ========================================================
35 |
36 | //
37 | // Command line options/flags:
38 | //
39 | static struct {
40 | bool verbose = false; // -v, --verbose: Be verbose? If set timings are also printed at the end.
41 | bool staticArrays = false; // -s, --static_arrays: Write arrays/sizes with the 'static' qualifier?
42 | bool writeCounts = false; // -c, --write_counts: Write array lengths as constants?
43 | bool smoothNormals = false; // -n, --smooth_normals: Gen smooth per-vertex normals? Default is flat per-face. Requires vb_friendly!
44 | bool vbFriendly = false; // -f, --vb_friendly: Make output Vertex Buffer friendly? That is, single index per-vertex.
45 | bool noUVs = false; // --no_uvs: If set, don't output UVs (texture coordinates).
46 | bool genIncludeFile = false; // --inc_file: Generate an include file with name equal to gIncFileName.
47 | bool stdintData = false; // Use data types from cstdint/stdint.h if vb_friendly is set.
48 | int indexDataSize = 32; // Index data size in bits. 16 or 32. Only relevant if vb_friendly == true.
49 | } gOptions;
50 |
51 | //
52 | // Input/output file names:
53 | //
54 | static std::string gSourceFileName; // Name of source .obj file.
55 | static std::string gTargetFileName; // Name of .c/.cpp file to write.
56 | static std::string gIncFileName; // Optional output include file for when 'genIncludeFile' option is set.
57 | static std::string gCmdlineStr; // Whole command line into a string for printing in the output file.
58 |
59 | //
60 | // Clock to measure the taken to do our work:
61 | //
62 | using Clock = std::chrono::high_resolution_clock;
63 | using TimeUnit = std::chrono::milliseconds;
64 | static const char * gTimeUnitSuffix = "milliseconds";
65 |
66 | //
67 | // OBJ file structures:
68 | //
69 | struct ObjFace
70 | {
71 | // NOTE: Only triangles are supported right now!
72 | std::uint32_t vertexIndexes[3];
73 | std::uint32_t normalIndexes[3];
74 | std::uint32_t texCoordIndexes[3];
75 | };
76 | struct ObjModel
77 | {
78 | std::vector vertexes;
79 | std::vector normals;
80 | std::vector texCoords;
81 | std::vector faces;
82 | std::vector indexBuffer; // Only filled if 'vb_friendly' is set.
83 | };
84 |
85 | // The object being imported from file.
86 | static ObjModel gObjModel;
87 |
88 | // ========================================================
89 |
90 | #if defined(__GNUC__) || defined(__clang__)
91 | static bool errorF(const char * fmt, ...) __attribute__((format(printf, 1, 2)));
92 | static void verbosePrintF(const char * fmt, ...) __attribute__((format(printf, 1, 2)));
93 | #endif // GNU || Clang
94 |
95 | static bool errorF(const char * fmt, ...)
96 | {
97 | std::printf("ERROR: ");
98 |
99 | va_list vaList;
100 | va_start(vaList, fmt);
101 | std::vprintf(fmt, vaList);
102 | va_end(vaList);
103 |
104 | std::printf("\n");
105 | return false;
106 | }
107 |
108 | static void verbosePrintF(const char * fmt, ...)
109 | {
110 | if (!gOptions.verbose)
111 | {
112 | return;
113 | }
114 |
115 | va_list vaList;
116 | va_start(vaList, fmt);
117 | std::vprintf(fmt, vaList);
118 | va_end(vaList);
119 |
120 | std::printf("\n");
121 | }
122 |
123 | static std::size_t maxOfN(const std::size_t first, ...)
124 | {
125 | va_list vaList;
126 | std::size_t num = first;
127 | std::size_t largestNum = 0;
128 |
129 | va_start(vaList, first);
130 | while (num != std::size_t(~0))
131 | {
132 | if (num > largestNum)
133 | {
134 | largestNum = num;
135 | }
136 | num = va_arg(vaList, std::size_t);
137 | }
138 | va_end(vaList);
139 |
140 | return largestNum;
141 | }
142 |
143 | static std::uint32_t numberPad(const std::size_t num)
144 | {
145 | if (num <= 9) return 1;
146 | if (num <= 99) return 2;
147 | if (num <= 999) return 3;
148 | if (num <= 9999) return 4;
149 | if (num <= 99999) return 5;
150 | if (num <= 999999) return 6;
151 | if (num <= 9999999) return 7;
152 | if (num <= 99999999) return 8;
153 | if (num <= 999999999) return 9;
154 | return 10;
155 | }
156 |
157 | static void printStats(const char * const progName, const TimeUnit timeTaken)
158 | {
159 | // Print some stats about the program execution
160 | // just before exiting successfully.
161 |
162 | std::printf("%s ran in %llu %s.\n",
163 | progName, static_cast(timeTaken.count()), gTimeUnitSuffix);
164 |
165 | const std::size_t largestNum = maxOfN(
166 | gObjModel.vertexes.size(),
167 | gObjModel.normals.size(),
168 | gObjModel.texCoords.size(),
169 | gObjModel.faces.size(),
170 | std::size_t(~0));
171 |
172 | const std::uint32_t pad = numberPad(largestNum);
173 |
174 | std::printf("Outputted:\n");
175 | std::printf("%0*zu vertex positions.\n", pad, gObjModel.vertexes.size());
176 | std::printf("%0*zu vertex normals.\n", pad, gObjModel.normals.size());
177 | std::printf("%0*zu texture vertexes.\n", pad, gObjModel.texCoords.size());
178 |
179 | if (gOptions.vbFriendly)
180 | {
181 | std::printf("%0*zu indexes\n", pad, gObjModel.indexBuffer.size());
182 | }
183 | else
184 | {
185 | std::printf("%0*zu faces\n", pad, gObjModel.faces.size());
186 | }
187 | }
188 |
189 | static void printHelpText(const char * const progName)
190 | {
191 | std::printf(
192 | "Convert Wavefront OBJ mesh file to C/C++ data arrays.\n\n"
193 | "Usage:\n"
194 | " $ %s [options]\n"
195 | "Options:\n"
196 | " -h, --help Shows this help text.\n"
197 | " -v, --verbose Be verbose; output a lot of info and timings.\n"
198 | " -s, --static_arrays If present, add the 'static' qualifier to array declarations.\n"
199 | " -c, --write_counts Write lengths of data arrays as constants.\n"
200 | " --inc_file[=name] If flag present, generate an include file externing the array variables.\n"
201 | " Incompatible with 'static_arrays'. If no filename provided, uses the target file name.\n"
202 | " -n, --smooth_normals If set, gen smooth per-vertex normals. Default are shared per-face 'flat' normals.\n"
203 | " -f, --vb_friendly Make the output 'Vertex Buffer friendly'. That is, single index per-vertex.\n"
204 | " --ib_type= Index buffer data type for when using 'vb_friendly'.\n"
205 | " Possible values are 16, 16std, 32 and 32std.\n"
206 | " The 'std' suffix causes the use of the standard C data types found in \n"
207 | " --no_uvs Don't output mesh UVs, even if they are present in the OBJ file.\n"
208 | "\n"
209 | "Created by Guilherme R. Lampert, %s.\n\n",
210 | progName, __DATE__);
211 | }
212 |
213 | static bool parseCommandLine(const int argc, const char * argv[])
214 | {
215 | // Help run?
216 | if (argc == 2 && (std::strcmp(argv[1], "-h") == 0 || std::strcmp(argv[1], "--help") == 0))
217 | {
218 | printHelpText(argv[0]);
219 | return false;
220 | }
221 |
222 | if (argc < 3) // prog_name + source_file + target_file at least
223 | {
224 | errorF("Not enough arguments!\n");
225 | printHelpText(argv[0]);
226 | return false;
227 | }
228 |
229 | gSourceFileName = argv[1];
230 | gTargetFileName = argv[2];
231 |
232 | // Saved for later...
233 | gCmdlineStr += argv[0]; gCmdlineStr += " ";
234 | gCmdlineStr += argv[1]; gCmdlineStr += " ";
235 | gCmdlineStr += argv[2]; gCmdlineStr += " ";
236 |
237 | std::string arg;
238 | char incFileStr[512] = {'\0'};
239 | char ibTypeFlagStr[64] = {'\0'};
240 |
241 | for (int i = 3; i < argc; ++i)
242 | {
243 | // Put the command line aside for later:
244 | gCmdlineStr += argv[i];
245 | gCmdlineStr += " ";
246 |
247 | // Check for the accepted flags:
248 | arg = argv[i];
249 | if (arg == "-v" || arg == "--verbose")
250 | {
251 | gOptions.verbose = true;
252 | }
253 | else if (arg == "-s" || arg == "--static_arrays")
254 | {
255 | gOptions.staticArrays = true;
256 | }
257 | else if (arg == "-c" || arg == "--write_counts")
258 | {
259 | gOptions.writeCounts = true;
260 | }
261 | else if (arg == "-n" || arg == "--smooth_normals")
262 | {
263 | gOptions.smoothNormals = true;
264 | }
265 | else if (arg == "-f" || arg == "--vb_friendly")
266 | {
267 | gOptions.vbFriendly = true;
268 | }
269 | else if (arg == "--no_uvs")
270 | {
271 | gOptions.noUVs = true;
272 | }
273 | else if (arg.compare(0, std::strlen("--inc_file"), "--inc_file") == 0)
274 | {
275 | gOptions.genIncludeFile = true;
276 |
277 | if (std::sscanf(arg.c_str(), "--inc_file=%s", incFileStr) == 1)
278 | {
279 | gIncFileName = incFileStr;
280 | }
281 | else // Use name of target file replacing extension with '.h'
282 | {
283 | const auto lastDot = gTargetFileName.find_last_of('.');
284 | if (lastDot != std::string::npos)
285 | {
286 | gIncFileName = gTargetFileName.substr(0, lastDot);
287 | }
288 | else
289 | {
290 | gIncFileName = gTargetFileName;
291 | }
292 | gIncFileName += ".h";
293 | }
294 | }
295 | else if (arg.compare(0, std::strlen("--ib_type"), "--ib_type") == 0)
296 | {
297 | if (std::sscanf(arg.c_str(), "--ib_type=%s", ibTypeFlagStr) != 1)
298 | {
299 | return errorF("Missing value after 'ib_type=...'! Use 16, 16std, 32 or 32std.");
300 | }
301 |
302 | if (std::strcmp(ibTypeFlagStr, "16") == 0)
303 | {
304 | gOptions.indexDataSize = 16;
305 | gOptions.stdintData = false;
306 | }
307 | else if (std::strcmp(ibTypeFlagStr, "16std") == 0)
308 | {
309 | gOptions.indexDataSize = 16;
310 | gOptions.stdintData = true;
311 | }
312 | else if (std::strcmp(ibTypeFlagStr, "32") == 0)
313 | {
314 | gOptions.indexDataSize = 32;
315 | gOptions.stdintData = false;
316 | }
317 | else if (std::strcmp(ibTypeFlagStr, "32std") == 0)
318 | {
319 | gOptions.indexDataSize = 32;
320 | gOptions.stdintData = true;
321 | }
322 | else
323 | {
324 | return errorF("'ib_type' flag value must be equal to 16, 16std, 32 or 32std.");
325 | }
326 | }
327 | }
328 |
329 | if (gOptions.smoothNormals && !gOptions.vbFriendly)
330 | {
331 | return errorF("'smooth_normals' flag requires 'vb_friendly'!");
332 | }
333 | else if (gOptions.genIncludeFile && gOptions.staticArrays)
334 | {
335 | return errorF("'inc_file' option is incompatible with 'static_arrays'!");
336 | }
337 |
338 | if (gOptions.verbose)
339 | {
340 | verbosePrintF("- Source file: '%s'", gSourceFileName.c_str());
341 | verbosePrintF("- Target file: '%s'", gTargetFileName.c_str());
342 | verbosePrintF("- Include file: '%s'", gIncFileName.c_str());
343 | verbosePrintF("- Static arrays? %s", (gOptions.staticArrays ? "yes" : "no"));
344 | verbosePrintF("- Output array counts? %s", (gOptions.writeCounts ? "yes" : "no"));
345 | verbosePrintF("- VB friendly? %s", (gOptions.vbFriendly ? "yes" : "no"));
346 | verbosePrintF("- Smooth normals? %s", (gOptions.smoothNormals ? "yes" : "no"));
347 | verbosePrintF("- Omit UVs? %s", (gOptions.noUVs ? "yes" : "no"));
348 | verbosePrintF("- Gen include file? %s", (gOptions.genIncludeFile ? "yes" : "no"));
349 | verbosePrintF("- stdint data types? %s", (gOptions.stdintData ? "yes" : "no"));
350 | verbosePrintF("- Index data size: %i", gOptions.indexDataSize);
351 | }
352 | return true;
353 | }
354 |
355 | static bool openFile(FILE ** outHandle, const char * const filename, const char * const modeStr)
356 | {
357 | FILE * file;
358 | errno = 0;
359 |
360 | // fopen_s avoids a deprecation warning for std::fopen on MSVC.
361 | #ifdef _MSC_VER
362 | if (fopen_s(&file, filename, modeStr) != 0)
363 | {
364 | file = nullptr;
365 | }
366 | #else // !_MSC_VER
367 | file = std::fopen(filename, modeStr);
368 | #endif // _MSC_VER
369 |
370 | if (file == nullptr)
371 | {
372 | (*outHandle) = nullptr;
373 | return errorF("Unable to open file \"%s\" with mode '%s' => %s",
374 | filename, modeStr, std::strerror(errno));
375 | }
376 | else
377 | {
378 | (*outHandle) = file;
379 | return true;
380 | }
381 | }
382 |
383 | static bool allocateObjData(const std::size_t nVertexes, const std::size_t nNormals,
384 | const std::size_t nTexCoords, const std::size_t nFaces)
385 | {
386 | try
387 | {
388 | if (nVertexes != 0)
389 | {
390 | gObjModel.vertexes.resize(nVertexes);
391 | std::memset(gObjModel.vertexes.data(), 0, gObjModel.vertexes.size() * sizeof(Vec3));
392 | verbosePrintF("- Allocated %zu vertexes.", nVertexes);
393 | }
394 | if (nNormals != 0)
395 | {
396 | gObjModel.normals.resize(nNormals);
397 | std::memset(gObjModel.normals.data(), 0, gObjModel.normals.size() * sizeof(Vec3));
398 | verbosePrintF("- Allocated %zu vertex normals.", nNormals);
399 | }
400 | if (nTexCoords != 0)
401 | {
402 | gObjModel.texCoords.resize(nTexCoords);
403 | std::memset(gObjModel.texCoords.data(), 0, gObjModel.texCoords.size() * sizeof(Vec2));
404 | verbosePrintF("- Allocated %zu texture vertexes.", nTexCoords);
405 | }
406 | if (nFaces != 0)
407 | {
408 | gObjModel.faces.resize(nFaces);
409 | std::memset(gObjModel.faces.data(), 0, gObjModel.faces.size() * sizeof(ObjFace));
410 | verbosePrintF("- Allocated %zu faces.", nFaces);
411 | }
412 | return true;
413 | }
414 | catch (...)
415 | {
416 | return false;
417 | }
418 | }
419 |
420 | static bool readObjTriFace(const char * const buffer, const std::size_t faceIndex)
421 | {
422 | // FIXME: This could be more robust.
423 | // Currently if whitespace doesn't match the expected this function might fail.
424 |
425 | assert(faceIndex < gObjModel.faces.size());
426 | ObjFace & face = gObjModel.faces[faceIndex];
427 | std::uint32_t v1, t1, n1, v2, t2, n2, v3, t3, n3;
428 |
429 | if (std::sscanf(buffer, "f %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3) == 9)
430 | {
431 | // Vertex + Texture + Normal:
432 | face.vertexIndexes[0] = --v1;
433 | face.vertexIndexes[1] = --v2;
434 | face.vertexIndexes[2] = --v3;
435 | face.normalIndexes[0] = --n1;
436 | face.normalIndexes[1] = --n2;
437 | face.normalIndexes[2] = --n3;
438 | face.texCoordIndexes[0] = --t1;
439 | face.texCoordIndexes[1] = --t2;
440 | face.texCoordIndexes[2] = --t3;
441 | return true;
442 | }
443 | else if (std::sscanf(buffer, "f %u/%u %u/%u %u/%u", &v1, &t1, &v2, &t2, &v3, &t3) == 6)
444 | {
445 | // Vertex + Texture:
446 | face.vertexIndexes[0] = --v1;
447 | face.vertexIndexes[1] = --v2;
448 | face.vertexIndexes[2] = --v3;
449 | face.texCoordIndexes[0] = --t1;
450 | face.texCoordIndexes[1] = --t2;
451 | face.texCoordIndexes[2] = --t3;
452 | return true;
453 | }
454 | else if (std::sscanf(buffer, "f %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3) == 6)
455 | {
456 | // Vertex + Normal:
457 | face.vertexIndexes[0] = --v1;
458 | face.vertexIndexes[1] = --v2;
459 | face.vertexIndexes[2] = --v3;
460 | face.normalIndexes[0] = --n1;
461 | face.normalIndexes[1] = --n2;
462 | face.normalIndexes[2] = --n3;
463 | return true;
464 | }
465 | else if (std::sscanf(buffer, "f %u %u %u", &v1, &v2, &v3) == 3)
466 | {
467 | // Vertex Only:
468 | face.vertexIndexes[0] = --v1;
469 | face.vertexIndexes[1] = --v2;
470 | face.vertexIndexes[2] = --v3;
471 | return true;
472 | }
473 |
474 | return false;
475 | }
476 |
477 | static bool stringToFloat(const char * nPtr, char ** endPtr, float * result)
478 | {
479 | char * ep = nullptr;
480 | *result = static_cast(std::strtod(nPtr, &ep));
481 | *endPtr = ep;
482 | return (ep != nullptr && ep != nPtr);
483 | }
484 |
485 | static bool readVecFloat(const char * const buffer, const int count, float * outFloats)
486 | {
487 | const char * p = buffer;
488 | char * endPtr = nullptr;
489 | int numbersGot = 0;
490 |
491 | while (p && numbersGot < count)
492 | {
493 | if (std::isdigit(*p) || *p == '-' || *p == '+' || *p == '.')
494 | {
495 | if (stringToFloat(p, &endPtr, &outFloats[numbersGot]))
496 | {
497 | ++numbersGot;
498 | p = endPtr;
499 | continue;
500 | }
501 | }
502 | ++p;
503 | }
504 | return numbersGot == count;
505 | }
506 |
507 | static bool readObjFile()
508 | {
509 | verbosePrintF("- Preparing to read input OBJ file...");
510 |
511 | FILE * fp;
512 | if (!openFile(&fp, gSourceFileName.c_str(), "rt"))
513 | {
514 | return false;
515 | }
516 |
517 | std::size_t facesCount = 0;
518 | std::size_t vertexCount = 0;
519 | std::size_t normalsCount = 0;
520 | std::size_t texCoordsCount = 0;
521 | char line[2048] = {'\0'};
522 |
523 | // First, count all the stuff, so we can allocate exact memory:
524 | while (!std::feof(fp))
525 | {
526 | std::fgets(line, sizeof(line), fp);
527 | switch (line[0])
528 | {
529 | case 'v':
530 | {
531 | switch (line[1])
532 | {
533 | case ' ':
534 | case '\t':
535 | ++vertexCount;
536 | break;
537 | case 'n':
538 | ++normalsCount;
539 | break;
540 | case 't':
541 | ++texCoordsCount;
542 | break;
543 | default:
544 | break;
545 | } // switch (line[1])
546 | break;
547 | }
548 | case 'f':
549 | ++facesCount;
550 | break;
551 | default:
552 | break;
553 | } // switch (line[0])
554 | } // while
555 |
556 | // Attempt memory allocation:
557 | if (!allocateObjData(vertexCount, normalsCount, texCoordsCount, facesCount))
558 | {
559 | std::fclose(fp);
560 | return errorF("Memory allocation failed! OBJ file is too big!");
561 | }
562 |
563 | // Rewind file for the second pass:
564 | std::rewind(fp);
565 |
566 | float xyz[3];
567 | float uv[2];
568 | std::size_t vIndex = 0;
569 | std::size_t nIndex = 0;
570 | std::size_t tIndex = 0;
571 | std::size_t fIndex = 0;
572 |
573 | // Now read in the stuff:
574 | while (!std::feof(fp))
575 | {
576 | std::fgets(line, sizeof(line), fp);
577 | switch (line[0])
578 | {
579 | case 'v': // Mesh vertex
580 | {
581 | // Vertex position "v ":
582 | if ((line[1] == ' ' || line[1] == '\t') && readVecFloat(line, 3, xyz))
583 | {
584 | gObjModel.vertexes[vIndex].x = xyz[0];
585 | gObjModel.vertexes[vIndex].y = xyz[1];
586 | gObjModel.vertexes[vIndex].z = xyz[2];
587 | ++vIndex;
588 | }
589 | // Normal vector "vn":
590 | else if (line[1] == 'n' && readVecFloat(line, 3, xyz))
591 | {
592 | gObjModel.normals[nIndex].x = xyz[0];
593 | gObjModel.normals[nIndex].y = xyz[1];
594 | gObjModel.normals[nIndex].z = xyz[2];
595 | ++nIndex;
596 | }
597 | // Texture coordinate "vt":
598 | else if (line[1] == 't' && readVecFloat(line, 2, uv))
599 | {
600 | gObjModel.texCoords[tIndex].x = uv[0];
601 | gObjModel.texCoords[tIndex].y = uv[1];
602 | ++tIndex;
603 | }
604 | break;
605 | }
606 | case 'f': // Obj face def
607 | {
608 | if (readObjTriFace(line, fIndex))
609 | {
610 | ++fIndex;
611 | }
612 | else
613 | {
614 | std::printf("WARNING: Unhandled polygon type found. Faces must be triangulated first!\n");
615 | }
616 | break;
617 | }
618 | default:
619 | break;
620 | } // switch (line[0])
621 | } // while
622 |
623 | std::fclose(fp);
624 |
625 | // Only if we got something wrong, but to be sure...
626 | assert(fIndex == facesCount);
627 | assert(vIndex == vertexCount);
628 | assert(nIndex == normalsCount);
629 | assert(tIndex == texCoordsCount);
630 |
631 | verbosePrintF("- Done reading input file!");
632 | return true;
633 | }
634 |
635 | //
636 | // Following is used to create a vertex buffer for the vb_friendly option.
637 | //
638 | struct PackedVertex
639 | {
640 | Vec3 position;
641 | Vec3 normal;
642 | Vec2 uv;
643 |
644 | bool operator == (const PackedVertex & other) const
645 | {
646 | const bool positionsEq = (position.x == other.position.x &&
647 | position.y == other.position.y &&
648 | position.z == other.position.z);
649 |
650 | const bool normalsEq = (normal.x == other.normal.x &&
651 | normal.y == other.normal.y &&
652 | normal.z == other.normal.z);
653 |
654 | const bool uvsEq = (uv.x == other.uv.x &&
655 | uv.y == other.uv.y);
656 |
657 | return positionsEq && normalsEq && uvsEq;
658 | }
659 | };
660 |
661 | struct PackedVertexHasher
662 | {
663 | std::size_t operator()(const PackedVertex & packed) const
664 | {
665 | constexpr std::size_t count = sizeof(PackedVertex);
666 | const auto bytes = reinterpret_cast(&packed);
667 |
668 | // Simple and fast One-at-a-Time (OAT) hash algorithm:
669 | // http://en.wikipedia.org/wiki/Jenkins_hash_function
670 | //
671 | std::uint32_t h = 0;
672 | for (std::size_t i = 0; i < count; ++i)
673 | {
674 | h += bytes[i];
675 | h += (h << 10);
676 | h ^= (h >> 6);
677 | }
678 | h += (h << 3);
679 | h ^= (h >> 11);
680 | h += (h << 15);
681 | return h;
682 | }
683 | };
684 |
685 | using VertToIndexMap = std::unordered_map;
686 |
687 | static bool findSimilarVertexIndex(const PackedVertex & packed,
688 | const VertToIndexMap & vertexMap,
689 | std::uint32_t * result)
690 | {
691 | const auto it = vertexMap.find(packed);
692 | if (it == std::end(vertexMap))
693 | {
694 | return false;
695 | }
696 |
697 | (*result) = it->second;
698 | return true;
699 | }
700 |
701 | static void createIB(const std::vector & inVertexes,
702 | std::vector & outIndexes,
703 | std::vector & outPositions,
704 | std::vector & outNormals,
705 | std::vector & outUVs)
706 | {
707 | VertToIndexMap vertexMap;
708 | const std::size_t inVertexCount = inVertexes.size();
709 |
710 | // Worst case we will have a 1:1 mapping with the vertex count.
711 | outIndexes.reserve(inVertexCount);
712 | outPositions.reserve(inVertexCount);
713 | outNormals.reserve(inVertexCount);
714 | outUVs.reserve(inVertexCount);
715 |
716 | // For each input vertex:
717 | for (std::size_t i = 0; i < inVertexCount; ++i)
718 | {
719 | const PackedVertex & packed = inVertexes[i];
720 |
721 | // Try to find a similar vertex already in the output:
722 | std::uint32_t index;
723 | if (findSimilarVertexIndex(packed, vertexMap, &index))
724 | {
725 | // An equivalent vertex is already in the VB, use it instead:
726 | outIndexes.push_back(index);
727 | }
728 | else
729 | {
730 | // If not, it needs to be added in the output data:
731 | outPositions.push_back(inVertexes[i].position);
732 | outNormals.push_back(inVertexes[i].normal);
733 | outUVs.push_back(inVertexes[i].uv);
734 |
735 | const auto newIndex = static_cast(outPositions.size() - 1);
736 | outIndexes.push_back(newIndex);
737 |
738 | // Add it to the map of unique vertexes:
739 | vertexMap[packed] = newIndex;
740 | }
741 | }
742 | }
743 |
744 | static bool makeVBFriendly()
745 | {
746 | verbosePrintF("- Making output data Vertex Buffer friendly (creating an Index Buffer)...");
747 |
748 | try
749 | {
750 | bool noNormals = false;
751 | bool noTexCoords = false;
752 |
753 | // To make our lives easier in case some data is
754 | // missing allocate some placeholder arrays:
755 | if (gObjModel.normals.empty())
756 | {
757 | gObjModel.normals.resize(gObjModel.vertexes.size(), { 0.0f, 0.0f, 0.0f });
758 | noNormals = true;
759 | }
760 | if (gObjModel.texCoords.empty())
761 | {
762 | gObjModel.texCoords.resize(gObjModel.vertexes.size(), { 0.0f, 0.0f });
763 | noTexCoords = true;
764 | }
765 |
766 | std::vector packedVerts;
767 | packedVerts.reserve(gObjModel.faces.size() * 3);
768 |
769 | const std::size_t facesCount = gObjModel.faces.size();
770 | for (std::size_t fi = 0; fi < facesCount; ++fi)
771 | {
772 | const ObjFace & face = gObjModel.faces[fi];
773 | for (std::size_t vi = 0; vi < 3; ++vi)
774 | {
775 | const Vec3 & v = gObjModel.vertexes[face.vertexIndexes[vi]];
776 | const Vec3 & vn = gObjModel.normals[face.normalIndexes[vi]];
777 | const Vec2 & vt = gObjModel.texCoords[face.texCoordIndexes[vi]];
778 |
779 | PackedVertex packed;
780 | packed.position = v;
781 | packed.normal = vn;
782 | packed.uv = vt;
783 | packedVerts.push_back(packed);
784 | }
785 | }
786 |
787 | // Discard current data, no longer needed.
788 | gObjModel.vertexes.clear();
789 | gObjModel.normals.clear();
790 | gObjModel.texCoords.clear();
791 | gObjModel.indexBuffer.clear();
792 |
793 | // Create an index buffer, replacing the old data:
794 | createIB(packedVerts, gObjModel.indexBuffer, gObjModel.vertexes, gObjModel.normals, gObjModel.texCoords);
795 |
796 | if (noNormals)
797 | {
798 | gObjModel.normals.clear();
799 | }
800 | if (noTexCoords)
801 | {
802 | gObjModel.texCoords.clear();
803 | }
804 |
805 | return true;
806 | }
807 | catch (...)
808 | {
809 | return errorF("Unable to create index buffer! Possibly out of memory!");
810 | }
811 | }
812 |
813 | static void computeMeshBounds(const Vec3 * const vertexPositions, const std::size_t vertCount, Vec3 * outMins, Vec3 * outMaxs)
814 | {
815 | auto minPerElem = [](const Vec3 & vec0, const Vec3 & vec1) -> Vec3
816 | {
817 | return { std::min(vec0.x, vec1.x),
818 | std::min(vec0.y, vec1.y),
819 | std::min(vec0.z, vec1.z) };
820 | };
821 | auto maxPerElem = [](const Vec3 & vec0, const Vec3 & vec1) -> Vec3
822 | {
823 | return { std::max(vec0.x, vec1.x),
824 | std::max(vec0.y, vec1.y),
825 | std::max(vec0.z, vec1.z) };
826 | };
827 |
828 | Vec3 mins = { INFINITY, INFINITY, INFINITY };
829 | Vec3 maxs = { -INFINITY, -INFINITY, -INFINITY };
830 |
831 | for (std::size_t v = 0; v < vertCount; ++v)
832 | {
833 | mins = minPerElem(vertexPositions[v], mins);
834 | maxs = maxPerElem(vertexPositions[v], maxs);
835 | }
836 |
837 | (*outMins) = mins;
838 | (*outMaxs) = maxs;
839 | }
840 |
841 | static float computePositionEpsilon(const Vec3 * const vertexPositions, const std::size_t vertCount)
842 | {
843 | constexpr float epsilon = 1e-4f;
844 |
845 | // Calculate the position bounds so we have a reliable
846 | // epsilon to check position differences against.
847 | Vec3 minVec, maxVec;
848 | computeMeshBounds(vertexPositions, vertCount, &minVec, &maxVec);
849 |
850 | const float dx = maxVec.x - minVec.x;
851 | const float dy = maxVec.y - minVec.y;
852 | const float dz = maxVec.z - minVec.z;
853 | return std::sqrt((dx * dx) + (dy * dy) + (dz * dz)) * epsilon;
854 | }
855 |
856 | static void computeFaceNormals(PackedVertex * inOutVertexes, const std::uint32_t * const indexes, const std::size_t indexCount)
857 | {
858 | if ((indexCount % 3) != 0)
859 | {
860 | errorF("Expected triangles! 'smooth_normals' option might not work...");
861 | }
862 |
863 | // For every triangle...
864 | for (std::size_t i = 0; i < indexCount; i += 3)
865 | {
866 | const std::uint32_t idx0 = indexes[i + 0];
867 | const std::uint32_t idx1 = indexes[i + 1];
868 | const std::uint32_t idx2 = indexes[i + 2];
869 |
870 | const Vec3 v0 = inOutVertexes[idx0].position;
871 | const Vec3 v1 = inOutVertexes[idx1].position;
872 | const Vec3 v2 = inOutVertexes[idx2].position;
873 |
874 | Vec3 a;
875 | a.x = v1.x - v0.x;
876 | a.y = v1.y - v0.y;
877 | a.z = v1.z - v0.z;
878 |
879 | Vec3 b;
880 | b.x = v2.x - v0.x;
881 | b.y = v2.y - v0.y;
882 | b.z = v2.z - v0.z;
883 |
884 | // Cross product and normalize:
885 | Vec3 normal;
886 | normal.x = (a.y * b.z) - (a.z * b.y);
887 | normal.y = (a.z * b.x) - (a.x * b.z);
888 | normal.z = (a.x * b.y) - (a.y * b.x);
889 |
890 | const float len = 1.0f / std::sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z));
891 | normal.x *= len;
892 | normal.y *= len;
893 | normal.z *= len;
894 |
895 | inOutVertexes[idx0].normal = normal;
896 | inOutVertexes[idx1].normal = normal;
897 | inOutVertexes[idx2].normal = normal;
898 | }
899 | }
900 |
901 | static void computeSmoothNormals(PackedVertex * inOutVertexes, const std::size_t vertCount,
902 | const std::uint32_t * const indexes, const std::size_t indexCount,
903 | const float posEpsilon, const int maxAngleDegs)
904 | {
905 | //
906 | // This implementation is largely based on code found in the ASSIMP mesh importer library.
907 | // http://www.assimp.org/
908 | //
909 |
910 | std::vector vertsFound;
911 | std::vector newNormals;
912 | SpatialSort vertexFinder;
913 |
914 | // Compute per-face normals but store them per-vertex:
915 | computeFaceNormals(inOutVertexes, indexes, indexCount);
916 |
917 | // Set up a SpatialSort to quickly find all vertexes close to a given position.
918 | vertexFinder.fillStructured(inOutVertexes, vertCount, true);
919 | newNormals.resize(vertCount, { 0.0f, 0.0f, 0.0f });
920 |
921 | if (maxAngleDegs >= 175.0f)
922 | {
923 | // There is no angle limit. Thus all vertexes with positions close
924 | // to each other will receive the same vertex normal. This allows us
925 | // to optimize the whole algorithm a little bit.
926 | std::vector had(vertCount, false);
927 | for (std::size_t i = 0; i < vertCount; ++i)
928 | {
929 | if (had[i])
930 | {
931 | continue;
932 | }
933 |
934 | // Get all vertexes that share this one:
935 | vertexFinder.findPositions(inOutVertexes[i].position, posEpsilon, &vertsFound);
936 | const std::size_t numVertexesFound = vertsFound.size();
937 |
938 | Vec3 normal;
939 | for (std::size_t a = 0; a < numVertexesFound; ++a)
940 | {
941 | const Vec3 v = inOutVertexes[vertsFound[a]].normal;
942 | if (!std::isnan(v.x) && !std::isnan(v.y) && !std::isnan(v.z))
943 | {
944 | normal.x += v.x;
945 | normal.y += v.y;
946 | normal.z += v.z;
947 | }
948 | }
949 |
950 | const float len = 1.0f / std::sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z));
951 | normal.x *= len;
952 | normal.y *= len;
953 | normal.z *= len;
954 |
955 | // Write the smoothed normal back to all affected normals:
956 | for (std::size_t a = 0; a < numVertexesFound; ++a)
957 | {
958 | const auto idx = vertsFound[a];
959 | newNormals[idx] = normal;
960 | had[idx] = true;
961 | }
962 | }
963 | }
964 | else
965 | {
966 | // Slower code path if a smooth angle is set. There are many ways to achieve
967 | // the effect, this one is the most straightforward one.
968 | const float fLimit = std::cos(maxAngleDegs * (M_PI / 180.0));
969 |
970 | for (std::size_t i = 0; i < vertCount; ++i)
971 | {
972 | // Get all vertexes that share this one:
973 | vertexFinder.findPositions(inOutVertexes[i].position, posEpsilon, &vertsFound);
974 |
975 | Vec3 normal;
976 | for (std::size_t a = 0; a < vertsFound.size(); ++a)
977 | {
978 | const Vec3 v = inOutVertexes[vertsFound[a]].normal;
979 | const Vec3 vi = inOutVertexes[i].normal;
980 | const float d = (v.x * vi.x) + (v.y * vi.y) + (v.z * vi.z);
981 |
982 | if (std::isnan(d) || d < fLimit)
983 | {
984 | continue;
985 | }
986 |
987 | normal.x += v.x;
988 | normal.y += v.y;
989 | normal.z += v.z;
990 | }
991 |
992 | const float len = 1.0f / std::sqrt((normal.x * normal.x) + (normal.y * normal.y) + (normal.z * normal.z));
993 | normal.x *= len;
994 | normal.y *= len;
995 | normal.z *= len;
996 | newNormals[i] = normal;
997 | }
998 | }
999 |
1000 | // Copy new normals to input:
1001 | for (std::size_t i = 0; i < vertCount; ++i)
1002 | {
1003 | inOutVertexes[i].normal = newNormals[i];
1004 | }
1005 | }
1006 |
1007 | static void genSmoothNormals()
1008 | {
1009 | if (gObjModel.normals.size() < gObjModel.vertexes.size())
1010 | {
1011 | gObjModel.normals.resize(gObjModel.vertexes.size(), { 0.0f, 0.0f, 0.0f });
1012 | }
1013 |
1014 | const float posEpsilon = computePositionEpsilon(gObjModel.vertexes.data(), gObjModel.vertexes.size());
1015 | verbosePrintF("- Mesh pos epsilon for normal smoothing: %f", posEpsilon);
1016 |
1017 | PackedVertex pv{};
1018 | std::vector packedVerts;
1019 | packedVerts.reserve(gObjModel.vertexes.size());
1020 |
1021 | for (std::size_t i = 0; i < gObjModel.vertexes.size(); ++i)
1022 | {
1023 | pv.position = gObjModel.vertexes[i];
1024 | pv.normal = gObjModel.normals[i];
1025 | // UVs are not required for normal computation.
1026 | packedVerts.push_back(pv);
1027 | }
1028 |
1029 | computeSmoothNormals(
1030 | /* inOutVertexes = */ packedVerts.data(),
1031 | /* vertCount = */ packedVerts.size(),
1032 | /* indexes = */ gObjModel.indexBuffer.data(),
1033 | /* indexCount = */ gObjModel.indexBuffer.size(),
1034 | /* posEpsilon = */ posEpsilon,
1035 | /* maxAngleDegs = */ 175.0f); // TODO should the angle be configurable?
1036 |
1037 | // Replace current flat normals with the smoothed ones:
1038 | assert(gObjModel.normals.size() == packedVerts.size());
1039 | for (std::size_t i = 0; i < gObjModel.normals.size(); ++i)
1040 | {
1041 | gObjModel.normals[i] = packedVerts[i].normal;
1042 | }
1043 |
1044 | verbosePrintF("- Smooth vertex normals computed for the model...");
1045 | }
1046 |
1047 | static bool isIntegerNumStr(const char * str)
1048 | {
1049 | for (; *str != '\0'; ++str)
1050 | {
1051 | if (*str == '.')
1052 | {
1053 | return false; // If the string has a dot, it is a decimal number.
1054 | }
1055 | }
1056 | return true;
1057 | }
1058 |
1059 | static const char * strFloat(const float value)
1060 | {
1061 | static char str[128];
1062 | std::snprintf(str, sizeof(str), "%f", value);
1063 |
1064 | // Trim trailing zeros:
1065 | for (char * ptr = str; *ptr != '\0'; ++ptr)
1066 | {
1067 | if (*ptr == '.')
1068 | {
1069 | while (*++ptr) // Find the end of the string.
1070 | {
1071 | }
1072 | while (*--ptr == '0') // Remove trailing zeros.
1073 | {
1074 | *ptr = '\0';
1075 | }
1076 | if (*ptr == '.') // If the dot was left alone at the end, remove it too.
1077 | {
1078 | *ptr = '\0';
1079 | }
1080 | break;
1081 | }
1082 | }
1083 | return str;
1084 | }
1085 |
1086 | static const char * strVecFloat(const float * const vec, const int elementCount)
1087 | {
1088 | assert(elementCount <= 4);
1089 |
1090 | static char str[512];
1091 | const char * fval[4] = { nullptr, nullptr, nullptr, nullptr };
1092 |
1093 | std::memset(str, 0, sizeof(str));
1094 | for (int i = 0; i < elementCount; ++i)
1095 | {
1096 | if (vec[i] == 0.0f)
1097 | {
1098 | std::strcat(str, "0.0f");
1099 | }
1100 | else
1101 | {
1102 | fval[i] = strFloat(vec[i]);
1103 | std::strcat(str, fval[i]);
1104 |
1105 | if (isIntegerNumStr(fval[i]))
1106 | {
1107 | std::strcat(str, ".0f");
1108 | }
1109 | else
1110 | {
1111 | std::strcat(str, "f");
1112 | }
1113 | }
1114 |
1115 | if (i != (elementCount - 1))
1116 | {
1117 | std::strcat(str, ", ");
1118 | }
1119 | }
1120 |
1121 | return str;
1122 | }
1123 |
1124 | static void trimString(std::string * s)
1125 | {
1126 | // LTrim
1127 | const auto firstNonBlank = s->find_first_not_of(" \t\r\n\v\f");
1128 | s->erase(0, firstNonBlank);
1129 |
1130 | // RTrim
1131 | const auto lastNonBlank = s->find_last_not_of(" \t\r\n\v\f");
1132 | s->erase(lastNonBlank != std::string::npos ? lastNonBlank + 1 : 0);
1133 | }
1134 |
1135 | static const char * getIBTypeStr()
1136 | {
1137 | bool isCFile;
1138 | const auto lastDot = gTargetFileName.find_last_of('.');
1139 |
1140 | if (lastDot != std::string::npos)
1141 | {
1142 | const std::string ext = gTargetFileName.substr(lastDot);
1143 | isCFile = (ext == ".c" || ext == ".C");
1144 | }
1145 | else
1146 | {
1147 | isCFile = false;
1148 | }
1149 |
1150 | if (gOptions.indexDataSize == 16)
1151 | {
1152 | if (isCFile) { return gOptions.stdintData ? "uint16_t" : "unsigned short"; }
1153 | else { return gOptions.stdintData ? "std::uint16_t" : "unsigned short"; }
1154 | }
1155 | else // indexDataSize == 32
1156 | {
1157 | if (isCFile) { return gOptions.stdintData ? "uint32_t" : "unsigned int"; }
1158 | else { return gOptions.stdintData ? "std::uint32_t" : "unsigned int"; }
1159 | }
1160 | }
1161 |
1162 | static void assembleName(std::string * outName, const std::string & prefix, const char * const suffix)
1163 | {
1164 | // Example:
1165 | // in: prefix="CubeLowPoly" / suffix="Verts"
1166 | // out: "cubeLowPolyVerts"
1167 |
1168 | (*outName) = prefix;
1169 | (*outName) += suffix;
1170 | (*outName)[0] = std::tolower((*outName)[0]);
1171 | trimString(outName);
1172 | }
1173 |
1174 | static std::string baseName(std::string filename)
1175 | {
1176 | const auto lastSlash = filename.find_last_of("\\/");
1177 | if (lastSlash != std::string::npos)
1178 | {
1179 | filename = filename.substr(lastSlash + 1);
1180 | }
1181 |
1182 | const auto lastDot = filename.find_last_of('.');
1183 | if (lastDot != std::string::npos)
1184 | {
1185 | filename = filename.substr(0, lastDot);
1186 | }
1187 |
1188 | return filename;
1189 | }
1190 |
1191 | static bool writeIncludeFile(const std::string & externDecls)
1192 | {
1193 | FILE * fp;
1194 | if (!openFile(&fp, gIncFileName.c_str(), "wt"))
1195 | {
1196 | return false;
1197 | }
1198 |
1199 | std::string headerGuardName = gIncFileName;
1200 |
1201 | // Replace dots with underscores:
1202 | std::string::size_type dot;
1203 | while ((dot = headerGuardName.find_last_of('.')) != std::string::npos)
1204 | {
1205 | headerGuardName[dot] = '_';
1206 | }
1207 |
1208 | // Make it all uppercase for a C-style macro name:
1209 | for (char & ch : headerGuardName)
1210 | {
1211 | ch = std::toupper(ch);
1212 | }
1213 |
1214 | std::fprintf(fp, "\n");
1215 | std::fprintf(fp, "#ifndef %s\n", headerGuardName.c_str());
1216 | std::fprintf(fp, "#define %s\n", headerGuardName.c_str());
1217 | std::fprintf(fp, "\n// File automatically generated by obj2c.\n\n");
1218 |
1219 | if (gOptions.vbFriendly && gOptions.stdintData)
1220 | {
1221 | std::fprintf(fp, "#ifdef __cplusplus\n"
1222 | " #include \n"
1223 | "#else // !__cplusplus\n"
1224 | " #include \n"
1225 | "#endif // __cplusplus\n\n");
1226 | }
1227 |
1228 | std::fprintf(fp, "%s", externDecls.c_str());
1229 | std::fprintf(fp, "#endif // %s\n", headerGuardName.c_str());
1230 |
1231 | std::fclose(fp);
1232 | verbosePrintF("- Optional include file successfully written!");
1233 | return true;
1234 | }
1235 |
1236 | static void writeFileHeader(FILE * fp)
1237 | {
1238 | std::fprintf(fp, "\n");
1239 | std::fprintf(fp, "//\n");
1240 | std::fprintf(fp, "// File automatically generated by obj2c from command line:\n");
1241 | std::fprintf(fp, "// %s\n", gCmdlineStr.c_str());
1242 | std::fprintf(fp, "//\n");
1243 | std::fprintf(fp, "\n");
1244 |
1245 | if (gOptions.vbFriendly && gOptions.stdintData)
1246 | {
1247 | std::fprintf(fp, "#ifdef __cplusplus\n"
1248 | " #include \n"
1249 | "#else // !__cplusplus\n"
1250 | " #include \n"
1251 | "#endif // __cplusplus\n\n");
1252 | }
1253 | }
1254 |
1255 | static bool writeOutputFiles()
1256 | {
1257 | verbosePrintF("- Preparing to write output file(s)...");
1258 |
1259 | FILE * fp;
1260 | std::string externDecls;
1261 | std::string arrayName;
1262 |
1263 | if (!openFile(&fp, gTargetFileName.c_str(), "wt"))
1264 | {
1265 | return false;
1266 | }
1267 |
1268 | writeFileHeader(fp);
1269 |
1270 | // Get just the base name of the output file without extension or path.
1271 | // This will be used to name the data arrays.
1272 | const std::string filename = baseName(gTargetFileName);
1273 |
1274 | // Vertex positions: -----------------------------------------------------------
1275 | if (!gObjModel.vertexes.empty())
1276 | {
1277 | assembleName(&arrayName, filename, "Verts");
1278 | verbosePrintF("- Outputting vertex array '%s'...", arrayName.c_str());
1279 |
1280 | if (gOptions.staticArrays)
1281 | {
1282 | std::fprintf(fp, "static const float %s[][3] = {\n", arrayName.c_str());
1283 | }
1284 | else
1285 | {
1286 | std::fprintf(fp, "const float %s[][3] = {\n", arrayName.c_str());
1287 |
1288 | externDecls += "extern const float " + arrayName + "[][3];\n";
1289 | if (gOptions.writeCounts)
1290 | {
1291 | externDecls += "extern const int " + arrayName + "Count;\n\n";
1292 | }
1293 | }
1294 |
1295 | const std::size_t vertCount = gObjModel.vertexes.size();
1296 | for (std::size_t i = 0; i < vertCount; ++i)
1297 | {
1298 | std::fprintf(fp, " { %s }", strVecFloat(reinterpret_cast(&gObjModel.vertexes[i]), 3));
1299 | if (i != (vertCount - 1))
1300 | {
1301 | std::fprintf(fp, ",\n");
1302 | }
1303 | }
1304 | std::fprintf(fp, "\n};\n");
1305 |
1306 | if (gOptions.writeCounts)
1307 | {
1308 | if (gOptions.staticArrays)
1309 | {
1310 | std::fprintf(fp, "static const int %s = %zu;\n", (arrayName + "Count").c_str(), vertCount);
1311 | }
1312 | else
1313 | {
1314 | std::fprintf(fp, "const int %s = %zu;\n", (arrayName + "Count").c_str(), vertCount);
1315 | }
1316 | }
1317 | std::fprintf(fp, "\n");
1318 | }
1319 |
1320 | // Vertex normals: -------------------------------------------------------------
1321 | if (!gObjModel.normals.empty())
1322 | {
1323 | assembleName(&arrayName, filename, "Normals");
1324 | verbosePrintF("- Outputting normals array '%s'...", arrayName.c_str());
1325 |
1326 | if (gOptions.staticArrays)
1327 | {
1328 | std::fprintf(fp, "static const float %s[][3] = {\n", arrayName.c_str());
1329 | }
1330 | else
1331 | {
1332 | std::fprintf(fp, "const float %s[][3] = {\n", arrayName.c_str());
1333 |
1334 | externDecls += "extern const float " + arrayName + "[][3];\n";
1335 | if (gOptions.writeCounts)
1336 | {
1337 | externDecls += "extern const int " + arrayName + "Count;\n\n";
1338 | }
1339 | }
1340 |
1341 | const std::size_t normalsCount = gObjModel.normals.size();
1342 | for (std::size_t i = 0; i < normalsCount; ++i)
1343 | {
1344 | std::fprintf(fp, " { %s }", strVecFloat(reinterpret_cast(&gObjModel.normals[i]), 3));
1345 | if (i != (normalsCount - 1))
1346 | {
1347 | std::fprintf(fp, ",\n");
1348 | }
1349 | }
1350 | std::fprintf(fp, "\n};\n");
1351 |
1352 | if (gOptions.writeCounts)
1353 | {
1354 | if (gOptions.staticArrays)
1355 | {
1356 | std::fprintf(fp, "static const int %s = %zu;\n", (arrayName + "Count").c_str(), normalsCount);
1357 | }
1358 | else
1359 | {
1360 | std::fprintf(fp, "const int %s = %zu;\n", (arrayName + "Count").c_str(), normalsCount);
1361 | }
1362 | }
1363 | std::fprintf(fp, "\n");
1364 | }
1365 |
1366 | // Texture coordinates: --------------------------------------------------------
1367 | if (!gObjModel.texCoords.empty() && !gOptions.noUVs)
1368 | {
1369 | assembleName(&arrayName, filename, "TexCoords");
1370 | verbosePrintF("- Outputting tex coords array '%s'...", arrayName.c_str());
1371 |
1372 | if (gOptions.staticArrays)
1373 | {
1374 | std::fprintf(fp, "static const float %s[][2] = {\n", arrayName.c_str());
1375 | }
1376 | else
1377 | {
1378 | std::fprintf(fp, "const float %s[][2] = {\n", arrayName.c_str());
1379 |
1380 | externDecls += "extern const float " + arrayName + "[][2];\n";
1381 | if (gOptions.writeCounts)
1382 | {
1383 | externDecls += "extern const int " + arrayName + "Count;\n\n";
1384 | }
1385 | }
1386 |
1387 | const std::size_t texCoordsCount = gObjModel.texCoords.size();
1388 | for (std::size_t i = 0; i < texCoordsCount; ++i)
1389 | {
1390 | std::fprintf(fp, " { %s }", strVecFloat(reinterpret_cast(&gObjModel.texCoords[i]), 2));
1391 | if (i != (texCoordsCount - 1))
1392 | {
1393 | std::fprintf(fp, ",\n");
1394 | }
1395 | }
1396 | std::fprintf(fp, "\n};\n");
1397 |
1398 | if (gOptions.writeCounts)
1399 | {
1400 | if (gOptions.staticArrays)
1401 | {
1402 | std::fprintf(fp, "static const int %s = %zu;\n", (arrayName + "Count").c_str(), texCoordsCount);
1403 | }
1404 | else
1405 | {
1406 | std::fprintf(fp, "const int %s = %zu;\n", (arrayName + "Count").c_str(), texCoordsCount);
1407 | }
1408 | }
1409 | std::fprintf(fp, "\n");
1410 | }
1411 |
1412 | // Object faces: ---------------------------------------------------------------
1413 | if (!gObjModel.faces.empty() && !gOptions.vbFriendly)
1414 | {
1415 | assembleName(&arrayName, filename, "FaceIndexes");
1416 | verbosePrintF("- Outputting OBJ face indexes array '%s'...", arrayName.c_str());
1417 |
1418 | if (gOptions.staticArrays)
1419 | {
1420 | std::fprintf(fp, "static const unsigned int %s[][3][3] = {\n", arrayName.c_str());
1421 | }
1422 | else
1423 | {
1424 | std::fprintf(fp, "const unsigned int %s[][3][3] = {\n", arrayName.c_str());
1425 |
1426 | externDecls += "extern const unsigned int " + arrayName + "[][3][3];\n";
1427 | if (gOptions.writeCounts)
1428 | {
1429 | externDecls += "extern const int " + arrayName + "Count;\n\n";
1430 | }
1431 | }
1432 |
1433 | const std::size_t facesCount = gObjModel.faces.size();
1434 | for (std::size_t i = 0; i < facesCount; ++i)
1435 | {
1436 | const ObjFace & face = gObjModel.faces[i];
1437 | std::fprintf(fp, " { { %u, %u, %u }, { %u, %u, %u }, { %u, %u, %u } }",
1438 | face.vertexIndexes[0], face.vertexIndexes[1], face.vertexIndexes[2],
1439 | face.normalIndexes[0], face.normalIndexes[1], face.normalIndexes[2],
1440 | face.texCoordIndexes[0], face.texCoordIndexes[1], face.texCoordIndexes[2]);
1441 |
1442 | if (i != (facesCount - 1))
1443 | {
1444 | std::fprintf(fp, ",\n");
1445 | }
1446 | }
1447 | std::fprintf(fp, "\n};\n");
1448 |
1449 | if (gOptions.writeCounts)
1450 | {
1451 | if (gOptions.staticArrays)
1452 | {
1453 | std::fprintf(fp, "static const int %s = %zu;\n", (arrayName + "Count").c_str(), facesCount);
1454 | }
1455 | else
1456 | {
1457 | std::fprintf(fp, "const int %s = %zu;\n", (arrayName + "Count").c_str(), facesCount);
1458 | }
1459 | }
1460 | std::fprintf(fp, "\n");
1461 | }
1462 |
1463 | // Index buffer: ---------------------------------------------------------------
1464 | if (!gObjModel.indexBuffer.empty())
1465 | {
1466 | assert(gOptions.vbFriendly == true);
1467 |
1468 | assembleName(&arrayName, filename, "Indexes");
1469 | verbosePrintF("- Outputting index array/buffer '%s'...", arrayName.c_str());
1470 |
1471 | if (gOptions.staticArrays)
1472 | {
1473 | std::fprintf(fp, "static const %s %s[] = {\n", getIBTypeStr(), arrayName.c_str());
1474 | }
1475 | else
1476 | {
1477 | std::fprintf(fp, "const %s %s[] = {\n", getIBTypeStr(), arrayName.c_str());
1478 |
1479 | externDecls += "extern const " + std::string(getIBTypeStr()) + " " + arrayName + "[];\n";
1480 | if (gOptions.writeCounts)
1481 | {
1482 | externDecls += "extern const int " + arrayName + "Count;\n\n";
1483 | }
1484 | }
1485 |
1486 | const std::size_t indexesCount = gObjModel.indexBuffer.size();
1487 | for (std::size_t i = 0; i < indexesCount; ++i)
1488 | {
1489 | std::fprintf(fp, " %u", gObjModel.indexBuffer[i]);
1490 | if (i != (indexesCount - 1))
1491 | {
1492 | std::fprintf(fp, ",\n");
1493 | }
1494 | }
1495 | std::fprintf(fp, "\n};\n");
1496 |
1497 | if (gOptions.writeCounts)
1498 | {
1499 | if (gOptions.staticArrays)
1500 | {
1501 | std::fprintf(fp, "static const int %s = %zu;\n", (arrayName + "Count").c_str(), indexesCount);
1502 | }
1503 | else
1504 | {
1505 | std::fprintf(fp, "const int %s = %zu;\n", (arrayName + "Count").c_str(), indexesCount);
1506 | }
1507 | }
1508 | std::fprintf(fp, "\n");
1509 | }
1510 |
1511 | std::fclose(fp);
1512 | verbosePrintF("- Done writing output file!");
1513 |
1514 | // Write the optional include file: --------------------------------------------
1515 | if (gOptions.genIncludeFile)
1516 | {
1517 | if (!writeIncludeFile(externDecls))
1518 | {
1519 | return false;
1520 | }
1521 | }
1522 |
1523 | return true;
1524 | }
1525 |
1526 | // ========================================================
1527 |
1528 | int main(const int argc, const char * argv[])
1529 | {
1530 | try
1531 | {
1532 | const auto startTime = Clock::now();
1533 |
1534 | if (!parseCommandLine(argc, argv))
1535 | {
1536 | return EXIT_FAILURE;
1537 | }
1538 |
1539 | if (!readObjFile())
1540 | {
1541 | return EXIT_FAILURE;
1542 | }
1543 |
1544 | if (gOptions.vbFriendly && !makeVBFriendly())
1545 | {
1546 | return EXIT_FAILURE;
1547 | }
1548 |
1549 | if (gOptions.smoothNormals && gOptions.vbFriendly)
1550 | {
1551 | genSmoothNormals();
1552 | }
1553 |
1554 | if (!writeOutputFiles())
1555 | {
1556 | return EXIT_FAILURE;
1557 | }
1558 |
1559 | const auto endTime = Clock::now();
1560 | const TimeUnit timeTaken = std::chrono::duration_cast(endTime - startTime);
1561 |
1562 | if (gOptions.verbose)
1563 | {
1564 | printStats(argv[0], timeTaken);
1565 | }
1566 |
1567 | return EXIT_SUCCESS;
1568 | }
1569 | catch (...)
1570 | {
1571 | errorF("Ooops! Looks like we hit a bump on the road... Try running it again ;)");
1572 | return EXIT_FAILURE;
1573 | }
1574 | }
1575 |
--------------------------------------------------------------------------------
/spatial_sort.hpp:
--------------------------------------------------------------------------------
1 |
2 | // ================================================================================================
3 | // -*- C++ -*-
4 | // File: spatial_sort.hpp
5 | // Author: Guilherme R. Lampert
6 | // Created on: 07/08/16
7 | // Brief: Spatial sorting for mesh vertexes. Allow quick search of neighboring vertexes.
8 | // ================================================================================================
9 |
10 | /*
11 | Code in this file is largely based on SpatialSort.h found in the ASSIMP library.
12 |
13 |
14 | Original copyright notice:
15 |
16 | Open Asset Import Library (ASSIMP)
17 | ----------------------------------------------------------------------
18 |
19 | Copyright (c) 2006-2010, ASSIMP Development Team
20 | All rights reserved.
21 |
22 | Redistribution and use of this software in source and binary forms,
23 | with or without modification, are permitted provided that the
24 | following conditions are met:
25 |
26 | * Redistributions of source code must retain the above
27 | copyright notice, this list of conditions and the
28 | following disclaimer.
29 |
30 | * Redistributions in binary form must reproduce the above
31 | copyright notice, this list of conditions and the
32 | following disclaimer in the documentation and/or other
33 | materials provided with the distribution.
34 |
35 | * Neither the name of the ASSIMP team, nor the names of its
36 | contributors may be used to endorse or promote products
37 | derived from this software without specific prior
38 | written permission of the ASSIMP Development Team.
39 |
40 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
41 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
42 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
43 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
44 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
46 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
47 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
48 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
49 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
50 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 |
52 | ----------------------------------------------------------------------
53 | */
54 |
55 | #ifndef SPATIAL_SORT_HPP
56 | #define SPATIAL_SORT_HPP
57 |
58 | #include
59 | #include
60 | #include
61 |
62 | #include
63 | #include
64 |
65 | //
66 | // 2 dimensional and 3 dimensional vectors or points:
67 | //
68 | struct Vec2
69 | {
70 | float x,y;
71 | };
72 | struct Vec3
73 | {
74 | float x,y,z;
75 | };
76 |
77 | // ========================================================
78 | // class SpatialSort:
79 | // ========================================================
80 |
81 | //
82 | // A little helper class to quickly find all vertexes in the epsilon environment of a given
83 | // position. Construct an instance with an array of positions. The class stores the given positions
84 | // by their indexes and sorts them by their distance to an arbitrary chosen plane.
85 | // You can then query the instance for all vertexes close to a given position in an average O(log n)
86 | // time, with O(n) worst case complexity when all vertexes lay on the plane. The plane is chosen
87 | // so that it avoids common planes in usual data sets.
88 | //
89 | class SpatialSort final
90 | {
91 | public:
92 |
93 | SpatialSort()
94 | {
95 | // Define the reference plane. We choose some arbitrary vector away from all basic axes
96 | // in the hope that no model spreads all its vertexes along this plane.
97 | planeNormal.x = 0.85230f;
98 | planeNormal.y = 0.34321f;
99 | planeNormal.z = 0.57360f;
100 |
101 | const float len = 1.0f / std::sqrt((planeNormal.x * planeNormal.x) +
102 | (planeNormal.y * planeNormal.y) +
103 | (planeNormal.z * planeNormal.z));
104 | planeNormal.x *= len;
105 | planeNormal.y *= len;
106 | planeNormal.z *= len;
107 | }
108 |
109 | // Constructs a spatially sorted representation from the given position array.
110 | SpatialSort(const Vec3 * const positionList, const std::size_t numPositions)
111 | : SpatialSort()
112 | {
113 | fill(positionList, numPositions, true);
114 | }
115 |
116 | // Sets the input data for the SpatialSort. This replaces existing data, if any.
117 | // Finalization is required in order to use findPosition() or generateMappingTable().
118 | // If you haven't finalized yet, you can use append() to add data from other sources.
119 | void fill(const Vec3 * const positionList, const std::size_t numPositions, const bool doFinalize)
120 | {
121 | positions.clear();
122 | append(positionList, numPositions, doFinalize);
123 | }
124 |
125 | // Same as fill() but expects a structured vertex type with a 'position' member.
126 | template
127 | void fillStructured(const VertexType * const vertsList, const std::size_t vertCount, const bool doFinalize)
128 | {
129 | positions.clear();
130 | positions.reserve(doFinalize ? vertCount : (vertCount * 2));
131 |
132 | for (std::uint32_t i = 0; i < vertCount; ++i)
133 | {
134 | // Store position by index and distance:
135 | const float distance = dotProduct(vertsList[i].position, planeNormal);
136 | positions.emplace_back(vertsList[i].position, distance, i);
137 | }
138 |
139 | if (doFinalize)
140 | {
141 | finalize();
142 | }
143 | }
144 |
145 | // Same as fill(), except the method appends to existing data in the SpatialSort.
146 | // Requires finalization.
147 | void append(const Vec3 * const positionList, const std::size_t numPositions, const bool doFinalize)
148 | {
149 | const std::size_t initial = positions.size();
150 | positions.reserve(initial + (doFinalize ? numPositions : (numPositions * 2)));
151 |
152 | for (std::size_t i = 0; i < numPositions; ++i)
153 | {
154 | // Store position by index and distance:
155 | const float distance = dotProduct(positionList[i], planeNormal);
156 | positions.emplace_back(positionList[i], distance, static_cast(i + initial));
157 | }
158 |
159 | if (doFinalize)
160 | {
161 | finalize();
162 | }
163 | }
164 |
165 | // Finalize the spatial sorting data structure. This can be useful after
166 | // multiple calls to append() with the 'finalize' parameter set to false.
167 | // This is required before one of findPositions() and generateMappingTable()
168 | // can be called to query the spatial sort.
169 | void finalize()
170 | {
171 | std::sort(positions.begin(), positions.end());
172 | }
173 |
174 | // Returns a list for all positions close to the given position.
175 | void findPositions(const Vec3 & position, const float radius, std::vector * outResults) const
176 | {
177 | const float dist = dotProduct(position, planeNormal);
178 | const float minDist = (dist - radius);
179 | const float maxDist = (dist + radius);
180 |
181 | outResults->clear();
182 |
183 | // Quick check for positions outside the range:
184 | if (positions.empty())
185 | {
186 | return;
187 | }
188 | if (maxDist < positions.front().distance)
189 | {
190 | return;
191 | }
192 | if (minDist > positions.back().distance)
193 | {
194 | return;
195 | }
196 |
197 | // Do a binary search for the minimal distance to start the iteration there:
198 | std::uint32_t index = static_cast(positions.size() / 2);
199 | std::uint32_t binaryStepSize = static_cast(positions.size() / 4);
200 |
201 | while (binaryStepSize > 1)
202 | {
203 | if (positions[index].distance < minDist)
204 | {
205 | index += binaryStepSize;
206 | }
207 | else
208 | {
209 | index -= binaryStepSize;
210 | }
211 | binaryStepSize /= 2;
212 | }
213 |
214 | // Depending on the direction of the last step we need to single step a bit
215 | // back or forth to find the actual beginning element of the range.
216 | while ((index > 0) && (positions[index].distance > minDist))
217 | {
218 | --index;
219 | }
220 | while ((index < (positions.size() - 1)) && (positions[index].distance < minDist))
221 | {
222 | ++index;
223 | }
224 |
225 | // Now start iterating from there until the first position lays outside of the distance range.
226 | // Add all positions inside the distance range within the given radius to the result array.
227 | EntryArray::const_iterator it = (positions.begin() + index);
228 | const float squaredRadius = radius * radius;
229 |
230 | while (it->distance < maxDist)
231 | {
232 | Vec3 diff;
233 | diff.x = it->position.x - position.x;
234 | diff.y = it->position.y - position.y;
235 | diff.z = it->position.z - position.z;
236 |
237 | const float lengthSqr = dotProduct(diff, diff);
238 | if (lengthSqr < squaredRadius)
239 | {
240 | outResults->push_back(it->index);
241 | }
242 |
243 | ++it;
244 | if (it == positions.end())
245 | {
246 | break;
247 | }
248 | }
249 | }
250 |
251 | // Fills an array with indexes of all positions identical to the given position.
252 | // In opposite to findPositions(), it is not an epsilon that is used but a (very low)
253 | // tolerance of four floating-point units.
254 | void findIdenticalPositions(const Vec3 & position, std::vector * outResults) const
255 | {
256 | // Epsilons have a huge disadvantage: they are of constant precision, while floating-point
257 | // values are of log2 precision. If you apply e=0.01 to 100, the epsilon is rather small,
258 | // but if you apply it to 0.001, it is enormous.
259 | //
260 | // The best way to overcome this is the unit in the last place (ULP). A precision of 2 ULPs
261 | // tells us that a float does not differ more than 2 bits from the "real" value. ULPs are of
262 | // logarithmic precision - around 1, they are 1÷(2^24) and around 10000, they are 0.00125.
263 | //
264 | // For standard C math, we can assume a precision of 0.5 ULPs according to IEEE 754.
265 | // The incoming vertex positions might have already been transformed, probably using rather
266 | // inaccurate SSE instructions, so we assume a tolerance of 4 ULPs to safely identify
267 | // identical vertex positions.
268 | static const int toleranceInULPs = 4;
269 |
270 | // An interesting point is that the inaccuracy grows linear with the number of operations:
271 | // multiplying two numbers, each inaccurate to four ULPs, results in an inaccuracy of four ULPs
272 | // plus 0.5 ULPs for the multiplication.
273 | // To compute the distance to the plane, a dot product is needed - that is a multiplication and
274 | // an addition on each number.
275 | static const int distanceToleranceInULPs = toleranceInULPs + 1;
276 |
277 | // The squared distance between two 3D vectors is computed the same way, but with an additional subtraction.
278 | static const int distance3DToleranceInULPs = distanceToleranceInULPs + 1;
279 |
280 | // Convert the plane distance to its signed integer representation so the ULPs tolerance can be applied.
281 | const BinFloat minDistBinary = floatToBinary(dotProduct(position, planeNormal)) - distanceToleranceInULPs;
282 | const BinFloat maxDistBinary = minDistBinary + 2 * distanceToleranceInULPs;
283 |
284 | outResults->clear();
285 |
286 | // Do a binary search for the minimal distance to start the iteration there:
287 | std::uint32_t index = static_cast(positions.size() / 2);
288 | std::uint32_t binaryStepSize = static_cast(positions.size() / 4);
289 |
290 | while (binaryStepSize > 1)
291 | {
292 | if (minDistBinary > floatToBinary(positions[index].distance))
293 | {
294 | index += binaryStepSize;
295 | }
296 | else
297 | {
298 | index -= binaryStepSize;
299 | }
300 | binaryStepSize /= 2;
301 | }
302 |
303 | // Depending on the direction of the last step we need to single step a bit back or forth
304 | // to find the actual beginning element of the range.
305 | while ((index > 0) && (minDistBinary < floatToBinary(positions[index].distance)))
306 | {
307 | --index;
308 | }
309 | while ((index < (positions.size() - 1)) && (minDistBinary > floatToBinary(positions[index].distance)))
310 | {
311 | ++index;
312 | }
313 |
314 | // Now start iterating from there until the first position lays outside of the distance range.
315 | // Add all positions inside the distance range within the tolerance to the result array.
316 | EntryArray::const_iterator it = (positions.begin() + index);
317 |
318 | while (floatToBinary(it->distance) < maxDistBinary)
319 | {
320 | Vec3 diff;
321 | diff.x = it->position.x - position.x;
322 | diff.y = it->position.y - position.y;
323 | diff.z = it->position.z - position.z;
324 |
325 | const float lengthSqr = dotProduct(diff, diff);
326 | if (distance3DToleranceInULPs >= floatToBinary(lengthSqr))
327 | {
328 | outResults->push_back(it->index);
329 | }
330 |
331 | ++it;
332 | if (it == positions.end())
333 | {
334 | break;
335 | }
336 | }
337 | }
338 |
339 | private:
340 |
341 | // Normal of the sorting plane. The center is always at (0, 0, 0).
342 | Vec3 planeNormal;
343 |
344 | static float dotProduct(const Vec3 & a, const Vec3 & b)
345 | {
346 | return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
347 | }
348 |
349 | // An entry in a spatially sorted position array. Consists of a vertex index,
350 | // its position and its pre-calculated distance from the reference plane.
351 | struct Entry
352 | {
353 | Vec3 position;
354 | float distance;
355 | std::uint32_t index;
356 |
357 | Entry() = default;
358 | Entry(const Vec3 & pos, const float dist, const std::uint32_t idx)
359 | : position{ pos }, distance{ dist }, index{ idx }
360 | { }
361 |
362 | bool operator < (const Entry & e) const { return distance < e.distance; }
363 | };
364 |
365 | // All positions, sorted by distance to the sorting plane:
366 | using EntryArray = std::vector;
367 | EntryArray positions;
368 |
369 | // Binary, signed-integer representation of a single-precision floating-point value.
370 | // IEEE 754 says: "If two floating-point numbers in the same format are ordered then they are
371 | // ordered the same way when their bits are reinterpreted as sign-magnitude integers."
372 | // This allows us to convert all floating-point numbers to signed integers of arbitrary size
373 | // and then use them to work with ULPs (Units in the Last Place, for high-precision
374 | // computations) or to compare them (integer comparisons are still faster than floating-point
375 | // comparisons on most platforms).
376 | using BinFloat = signed int;
377 |
378 | static BinFloat floatToBinary(const float & value)
379 | {
380 | // If this assertion fails, signed int is not big enough to store a
381 | // float on your platform. Correct the declaration of BinFloat as needed.
382 | static_assert(sizeof(BinFloat) >= sizeof(float), "Redefine 'BinFloat' to something >= sizeof(float)!");
383 |
384 | // A union should avoid strict-aliasing issues.
385 | union
386 | {
387 | float asFloat;
388 | BinFloat asBin;
389 | } conversion;
390 |
391 | conversion.asBin = 0; // zero empty space in case sizeof(BinFloat) > sizeof(float)
392 | conversion.asFloat = value;
393 | const BinFloat binValue = conversion.asBin;
394 |
395 | // Floating-point numbers are of sign-magnitude format, so find out what
396 | // signed number representation we must convert negative values to.
397 | // See:
398 |
399 | // Two's complement?
400 | if ((-42 == (~42 + 1)) && (binValue & 0x80000000))
401 | {
402 | return BinFloat(1 << (CHAR_BIT * sizeof(BinFloat) - 1)) - binValue;
403 | }
404 |
405 | // One's complement?
406 | if ((-42 == ~42) && (binValue & 0x80000000))
407 | {
408 | return BinFloat(-0) - binValue;
409 | }
410 |
411 | // Sign-magnitude?
412 | if ((-42 == (42 | (-0))) && (binValue & 0x80000000)) // -0 = 1000... Binary
413 | {
414 | return binValue;
415 | }
416 |
417 | return binValue;
418 | }
419 | };
420 |
421 | #endif // SPATIAL_SORT_HPP
422 |
--------------------------------------------------------------------------------