├── 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 | --------------------------------------------------------------------------------