├── .gitignore ├── Makefile ├── smbf.h ├── README.md └── smbf.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .gitattributes 2 | *.o 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX ?= g++ 2 | CXXFLAGS = -std=c++11 -fomit-frame-pointer -fno-rtti -fno-exceptions 3 | DEPS = smbf.h 4 | OBJS = smbf.o 5 | 6 | %.o: %.cpp $(DEPS) 7 | $(CXX) -c -o $@ $< $(CXXFLAGS) 8 | 9 | smbf: $(OBJS) 10 | $(CXX) -o $@ $^ $(CXXFLAGS) 11 | 12 | clean: 13 | rm -f smbf $(OBJS) 14 | -------------------------------------------------------------------------------- /smbf.h: -------------------------------------------------------------------------------- 1 | #ifndef SMBF_HDR 2 | #define SMBF_HDR 3 | #include 4 | 5 | #define SMBF_VERSION_MAJOR 1 6 | #define SMBF_VERSION_MINOR 0 7 | 8 | #define SMBF_VERSION (((SMBF_VERSION_MAJOR) << 8) | (SMBF_VERSION_MINOR)) 9 | 10 | struct header { 11 | uint8_t magic[4]; // Always "SMBF" 12 | uint8_t format; // The vertex format (one of SMFB_FORMAT_*) 13 | uint16_t version; // File format version 14 | uint32_t count; // How many "vertices" 15 | uint32_t indices; // How many "indices" 16 | }; 17 | 18 | enum { 19 | SMBF_FORMAT_P, 20 | SMBF_FORMAT_PN, 21 | SMBF_FORMAT_PNC, 22 | SMBF_FORMAT_PNCT, 23 | SMBF_FORMAT_PNCTB 24 | }; 25 | 26 | #pragma pack(push, 1) 27 | struct smbfP { 28 | float px, py, pz; 29 | }; 30 | 31 | struct smbfPN { 32 | float px, py, pz; 33 | float nx, ny, nz; 34 | }; 35 | 36 | struct smbfPNC { 37 | float px, py, pz; 38 | float nx, ny, nz; 39 | float tu, tv; 40 | }; 41 | 42 | struct smbfPNCT { 43 | float px, py, pz; 44 | float nx, ny, nz; 45 | float tu, tv; 46 | float tx, ty, tz; 47 | }; 48 | 49 | struct smbfPNCTB { 50 | float px, py, pz; 51 | float nx, ny, nz; 52 | float tu, tv; 53 | float tx, ty, tz; 54 | float b; 55 | }; 56 | #pragma pack(pop) 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMBF - Static model binary format 2 | 3 | Static model binary format is just as you expect. A static model format 4 | in binary format designed for renderers. 5 | 6 | Unlike most model formats. SMBF puts an emphasis on being straight forward and 7 | slim. There is no bounding box information, no vertex-edge information, or 8 | anything of that nature. SMBF files contain just the data you need for rendering. 9 | This data includes any of the following things: 10 | 11 | * Position 12 | * Normals (optional) 13 | * Texture coordinates (optional) 14 | * Tangents (optional) 15 | * Bitangents (optional) 16 | 17 | The data provided by SMBF is already in interleaved vertex format because any 18 | other format is wrong. 19 | 20 | The format is so straight forward you don't need any complex API to use, since 21 | rolling a loader for it takes only a couple of lines. We provide a structure of 22 | vertex definitions for you, but you don't have to use them. 23 | 24 | You may be questioning why something so obvious needs to exist at all when it 25 | should already exist. The reality is lots of people roll their own custom formats 26 | specifically like this but never make them publicly available for others to use 27 | because they're considered too trivial. This constant proliferation is 28 | ridiculous, lets at least have a format for others to use too guys. 29 | 30 | # Vertex formats 31 | 32 | The following formats exist: 33 | 34 | * P 35 | * PN 36 | * PNC 37 | * PNCT 38 | * PNCTB 39 | 40 | Symbolically the format refers to these formats with the following enum: 41 | ``` 42 | enum { 43 | SMBF_FORMAT_P, 44 | SMBF_FORMAT_PN, 45 | SMBF_FORMAT_PNC, 46 | SMBF_FORMAT_PNCT, 47 | SMBF_FORMAT_PNCTB 48 | }; 49 | ``` 50 | 51 | ## Legend 52 | 53 | * The P is short for position 54 | * The N is short for normal 55 | * The C is short for coordinate (as in texture coordinate.) 56 | * The T is short for tangent 57 | * The B is short for bitangent 58 | 59 | ## Storage of data 60 | 61 | * P is 3-component float vector. 62 | * N is 3-component float vector. 63 | * C is 2-component float vector. 64 | * T is 3-component float vector. 65 | * B is 1-component float storing sign of `W` 66 | 67 | The reason we only store sign for bitangents is because the cost of bandwidth 68 | far exceeds the cost of a single cross produt. So just reconstruct it like 69 | so. 70 | 71 | ``` 72 | normal0 = (gWorld * vec4(normal, 0.0f)).xyz; 73 | tangent0 = (gWorld * vec4(tangent, 0.0f)).xyz; 74 | bitangent0 = w * cross(normal0, tangent0); 75 | ``` 76 | 77 | # File format 78 | 79 | The file format contains the following header 80 | 81 | ``` 82 | struct header { 83 | uint8_t magic[4]; // Always "SMBF" 84 | uint8_t format; // The vertex format (one of SMFB_FORMAT_*) 85 | uint16_t version; // File format version 86 | uint32_t count; // How many "vertices" 87 | uint32_t indices; // How many "indices" 88 | }; 89 | ``` 90 | 91 | After the header there is `count` vertices of type `format`. 92 | After the vertices there is `indices` indices of type `uint32_t`. 93 | 94 | Everything is written as Little Endian. 95 | 96 | # Bounding Boxes 97 | SMBF does not concern itself with storing information that is trivial to calculate 98 | at runtime and often is faster than the wasted storage space. Since SMBF is for 99 | static models only it's trivial to construct a bounding box; here's an example: 100 | ``` 101 | // calculate the amount of floats for the format type 102 | size_t size = 0; 103 | if (type == SMBF_FORMAT_P) size = sizeof(struct smbfP) / sizeof(float); 104 | if (type == SMBF_FORMAT_PN) size = sizeof(struct smbfPN) / sizeof(float); 105 | if (type == SMBF_FORMAT_PNC) size = sizeof(struct smbfPNC) / sizeof(float); 106 | if (type == SMBF_FORMAT_PNCT) size = sizeof(struct smbfPNCT) / sizeof(float); 107 | if (type == SMBF_FORMAT_PNCTB) size = sizeof(struct smbfPNCTB) / sizeof(float); 108 | 109 | struct header* h = stream; // header 110 | float *v = (float *)h + 1; // vertex data begins after the header 111 | 112 | vec3 min(v[0], v[1], v[2]); 113 | vec3 max(v[0], v[1], v[2]); 114 | for (uint32_t i = 0; i < h->count; i++) 115 | { 116 | for (size_t j = 0; j < 3; j++) 117 | { 118 | if (v[i][j] < min[j]) min[j] = v[i][j]; 119 | if (v[i][j] > max[j]) max[j] = v[i][j]; 120 | } 121 | // move forward in the vertex stream 122 | v += size; 123 | } 124 | ``` 125 | 126 | # Toolkit 127 | 128 | Included is a toolkit for converting OBJ models to SMBF models. To use the 129 | tool just invoke it with the OBJ model as the first argument and it will spit 130 | out a model of the same name with a .smbf extension. You may also enable generation 131 | of tangents with the `-t` option and bitangents with `-b` option. 132 | 133 | All models processed with the toolkit will have their indices optimized using 134 | linear-speed-vertex-cache optimization to reduce potential cache misses when 135 | used for rendering. 136 | 137 | Toolkit does not produce valid files on Big Endian machines yet. 138 | 139 | # License 140 | 141 | The specification and the contents of `smbf.h` are placed in the public domain. 142 | The toolkit code in `smbf.cpp` is released under the MIT license. 143 | -------------------------------------------------------------------------------- /smbf.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dale Weiler 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | // Utilities 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #undef min 34 | #undef max 35 | #undef clamp 36 | 37 | namespace u { 38 | 39 | template 40 | using vector = std::vector; 41 | using string = std::string; 42 | 43 | template 44 | using map = std::unordered_map; 45 | 46 | template 47 | typename std::remove_reference::type&& move(T&& t) { 48 | return std::move(t); 49 | } 50 | 51 | // Little search implementation 52 | template 53 | I1 search(I1 first1, I1 last1, I2 first2, I2 last2) { 54 | for (; ; ++first1) { 55 | I1 it1 = first1; 56 | for (I2 it2 = first2; ; ++it1, ++it2) { 57 | if (it2 == last2) 58 | return first1; 59 | if (it1 == last1) 60 | return last1; 61 | if (!(*it1 == *it2)) 62 | break; 63 | } 64 | } 65 | } 66 | 67 | // Replace all occurrences of a string with a different string in a string 68 | static void replace_all(string &s, const char *before, const char *after) { 69 | const size_t beforeLength = strlen(before); 70 | string result; 71 | string::const_iterator end_ = s.end(); 72 | string::const_iterator current = s.begin(); 73 | string::const_iterator next = search(current, end_, before, before + beforeLength); 74 | while (next != end_) { 75 | result.append(current, next); 76 | result.append(after); 77 | current = next + beforeLength; 78 | next = search(current, end_, before, before + beforeLength); 79 | } 80 | result.append(current, next); 81 | s.swap(result); 82 | } 83 | 84 | // An implementation of swap 85 | template 86 | inline void swap(T &lhs, T &rhs) { 87 | T temp = move(rhs); 88 | rhs = move(lhs); 89 | lhs = move(temp); 90 | } 91 | 92 | // A small implementation of boost.optional 93 | namespace detail { 94 | template 95 | union optional_cast; 96 | 97 | template 98 | union optional_cast { 99 | const void *p; 100 | const T *data; 101 | }; 102 | 103 | template 104 | union optional_cast { 105 | void *p; 106 | T *data; 107 | }; 108 | } 109 | 110 | struct optional_none { }; 111 | 112 | typedef int optional_none::*none_t; 113 | none_t const none = none_t(0); 114 | 115 | template 116 | struct optional { 117 | optional(); 118 | optional(none_t); 119 | optional(const T &value); 120 | optional(const optional &opt); 121 | 122 | ~optional(); 123 | 124 | optional &operator=(const optional &opt); 125 | 126 | operator bool() const; 127 | T &operator *(); 128 | const T &operator*() const; 129 | 130 | private: 131 | void *storage(); 132 | const void *storage() const; 133 | T &get(); 134 | const T &get() const; 135 | void destruct(); 136 | void construct(const T &data); 137 | 138 | bool m_init; 139 | alignas(alignof(T)) unsigned char m_data[sizeof(T)]; 140 | }; 141 | 142 | template 143 | optional::optional() 144 | : m_init(false) 145 | { 146 | } 147 | 148 | template 149 | optional::optional(none_t) 150 | : m_init(false) 151 | { 152 | } 153 | 154 | template 155 | optional::optional(const T &value) 156 | : m_init(true) 157 | { 158 | construct(value); 159 | } 160 | 161 | template 162 | optional::optional(const optional &opt) 163 | : m_init(opt.m_init) 164 | { 165 | if (m_init) 166 | construct(opt.get()); 167 | } 168 | 169 | template 170 | optional::~optional() { 171 | destruct(); 172 | } 173 | 174 | template 175 | optional &optional::operator=(const optional &opt) { 176 | destruct(); 177 | if ((m_init = opt.m_init)) 178 | construct(opt.get()); 179 | return *this; 180 | } 181 | 182 | template 183 | optional::operator bool() const { 184 | return m_init; 185 | } 186 | 187 | template 188 | T &optional::operator *() { 189 | return get(); 190 | } 191 | 192 | template 193 | const T &optional::operator*() const { 194 | return get(); 195 | } 196 | 197 | template 198 | void *optional::storage() { 199 | return m_data; 200 | } 201 | 202 | template 203 | const void *optional::storage() const { 204 | return m_data; 205 | } 206 | 207 | template 208 | T &optional::get() { 209 | return *(detail::optional_cast { storage() }).data; 210 | } 211 | 212 | template 213 | const T &optional::get() const { 214 | return *(detail::optional_cast { storage() }).data; 215 | } 216 | 217 | template 218 | void optional::destruct() { 219 | if (m_init) 220 | get().~T(); 221 | m_init = false; 222 | } 223 | 224 | template 225 | void optional::construct(const T &data) { 226 | new (storage()) T(data); 227 | } 228 | 229 | // A little unique_ptr like file wrapper to achieve RAII. We can't use 230 | // unique_ptr here because unique_ptr doesn't allow null default delters 231 | struct file { 232 | file(); 233 | file(FILE *fp); 234 | ~file(); 235 | 236 | operator FILE*(); 237 | FILE *get(); 238 | 239 | private: 240 | FILE *m_handle; 241 | }; 242 | 243 | 244 | file::file() 245 | : m_handle(nullptr) 246 | { 247 | } 248 | 249 | file::file(FILE *fp) 250 | : m_handle(fp) 251 | { 252 | } 253 | 254 | file::~file() { 255 | if (m_handle) 256 | fclose(m_handle); 257 | } 258 | 259 | file::operator FILE*() { 260 | return m_handle; 261 | } 262 | 263 | FILE *file::get() { 264 | return m_handle; 265 | } 266 | 267 | // Wrapper around fopen for a u::string + path fixing 268 | #ifdef _WIN32 269 | static constexpr int kPathSep = '\\'; 270 | #else 271 | static constexpr int kPathSep = '/'; 272 | #endif 273 | 274 | static inline u::string fixPath(const u::string &path) { 275 | #ifdef _WIN32 276 | u::string fix(path); 277 | const size_t size = fix.size(); 278 | for (size_t i = 0; i < size; i++) { 279 | if (!strchr("/\\", fix[i])) 280 | continue; 281 | fix[i] = u::kPathSep; 282 | } 283 | return fix; 284 | #endif 285 | return path; 286 | } 287 | 288 | u::file fopen(const u::string& infile, const char *type) { 289 | return ::fopen(fixPath(infile).c_str(), type); 290 | } 291 | 292 | // An imeplementation of getline for u::string on a u::file 293 | u::optional getline(u::file &fp) { 294 | u::string s; 295 | for (;;) { 296 | char buf[256]; 297 | if (!fgets(buf, sizeof(buf), fp.get())) { 298 | if (feof(fp.get())) { 299 | if (s.empty()) 300 | return u::none; 301 | else 302 | return u::move(s); 303 | } 304 | abort(); 305 | } 306 | size_t n = strlen(buf); 307 | if (n && buf[n - 1] == '\n') 308 | --n; 309 | s.append(buf, n); 310 | if (n < sizeof(buf) - 1) 311 | return u::move(s); 312 | } 313 | } 314 | 315 | // A C99 compatible sscanf 316 | namespace detail { 317 | int c99vsscanf(const char *s, const char *format, va_list ap) { 318 | #if defined(_WIN32) || defined(_WIN64) 319 | u::string fmt = format; 320 | #ifdef _WIN32 321 | replace_all(fmt, "%zu", "%u"); 322 | #else 323 | replace_all(fmt, "%zu", "%Iu"); 324 | #endif 325 | #else 326 | const char *fmt = format; 327 | #endif 328 | return vsscanf(s, &fmt[0], ap); 329 | } 330 | } 331 | 332 | inline int sscanf(const u::string &thing, const char *fmt, ...) { 333 | va_list va; 334 | va_start(va, fmt); 335 | int value = detail::c99vsscanf(thing.c_str(), fmt, va); 336 | va_end(va); 337 | return value; 338 | } 339 | 340 | // A way to tokenize a string 341 | inline u::vector split(const char *str, char ch = ' ') { 342 | u::vector result; 343 | do { 344 | const char *begin = str; 345 | while (*str != ch && *str) 346 | str++; 347 | result.push_back(u::string(begin, str)); 348 | } while (*str++); 349 | return result; 350 | } 351 | 352 | inline u::vector split(const u::string &str, char ch = ' ') { 353 | return u::split(str.c_str(), ch); 354 | } 355 | 356 | } 357 | 358 | // Maths 359 | namespace m { 360 | 361 | static const float kEpsilon = 0.00001f; 362 | 363 | // Implementation of clamp 364 | template 365 | inline T clamp(const T& current, const T &min, const T &max) { 366 | return (current > max) ? max : ((current < min) ? min : current); 367 | } 368 | 369 | // Fast absolute values 370 | inline float abs(float v) { 371 | union { 372 | float f; 373 | int b; 374 | } data = { v }; 375 | data.b &= 0x7FFFFFFF; 376 | return data.f; 377 | } 378 | 379 | // Implementation of a vec3 380 | struct vec3 { 381 | union { 382 | struct { 383 | float x; 384 | float y; 385 | float z; 386 | }; 387 | float m[3]; 388 | }; 389 | 390 | constexpr vec3(); 391 | constexpr vec3(float nx, float ny, float nz); 392 | constexpr vec3(float a); 393 | 394 | float absSquared() const; 395 | float abs() const; 396 | 397 | void normalize(); 398 | vec3 normalized() const; 399 | bool isNormalized() const; 400 | bool isNull() const; 401 | 402 | bool isNullEpsilon(const float epsilon = kEpsilon) const; 403 | bool equals(const vec3 &cmp, const float epsilon) const; 404 | 405 | void setLength(float scaleLength); 406 | void maxLength(float length); 407 | 408 | vec3 cross(const vec3 &v) const; 409 | 410 | vec3 &operator +=(const vec3 &vec); 411 | vec3 &operator -=(const vec3 &vec); 412 | vec3 &operator *=(float value); 413 | vec3 &operator /=(float value); 414 | vec3 operator -() const; 415 | float operator[](size_t index) const; 416 | float &operator[](size_t index); 417 | 418 | static const vec3 xAxis; 419 | static const vec3 yAxis; 420 | static const vec3 zAxis; 421 | static const vec3 origin; 422 | }; 423 | 424 | const vec3 vec3::xAxis(1.0f, 0.0f, 0.0f); 425 | const vec3 vec3::yAxis(0.0f, 1.0f, 0.0f); 426 | const vec3 vec3::zAxis(0.0f, 0.0f, 1.0f); 427 | const vec3 vec3::origin(0.0f, 0.0f, 0.0f); 428 | 429 | inline constexpr vec3::vec3() 430 | : x(0.0f) 431 | , y(0.0f) 432 | , z(0.0f) 433 | { 434 | } 435 | 436 | inline constexpr vec3::vec3(float nx, float ny, float nz) 437 | : x(nx) 438 | , y(ny) 439 | , z(nz) 440 | { 441 | } 442 | 443 | inline constexpr vec3::vec3(float a) 444 | : x(a) 445 | , y(a) 446 | , z(a) 447 | { 448 | } 449 | 450 | inline float vec3::absSquared() const { 451 | return x * x + y * y + z * z; 452 | } 453 | 454 | inline float vec3::abs() const { 455 | return sqrtf(x * x + y * y + z * z); 456 | } 457 | 458 | inline void vec3::normalize() { 459 | const float length = 1.0f / abs(); 460 | x *= length; 461 | y *= length; 462 | z *= length; 463 | } 464 | 465 | inline vec3 vec3::normalized() const { 466 | const float scale = 1.0f / abs(); 467 | return vec3(x * scale, y * scale, z * scale); 468 | } 469 | 470 | inline bool vec3::isNormalized() const { 471 | return m::abs(abs() - 1.0f) < kEpsilon; 472 | } 473 | 474 | inline bool vec3::isNull() const { 475 | return x == 0.0f && y == 0.0f && z == 0.0f; 476 | } 477 | 478 | inline bool vec3::isNullEpsilon(const float epsilon) const { 479 | return equals(origin, epsilon); 480 | } 481 | 482 | inline bool vec3::equals(const vec3 &cmp, const float epsilon) const { 483 | return (m::abs(x - cmp.x) < epsilon) 484 | && (m::abs(y - cmp.y) < epsilon) 485 | && (m::abs(z - cmp.z) < epsilon); 486 | } 487 | 488 | inline void vec3::setLength(float scaleLength) { 489 | const float length = scaleLength / abs(); 490 | x *= length; 491 | y *= length; 492 | z *= length; 493 | } 494 | 495 | inline void vec3::maxLength(float length) { 496 | const float currentLength = abs(); 497 | if (currentLength > length) { 498 | const float scale = length / currentLength; 499 | x *= scale; 500 | y *= scale; 501 | z *= scale; 502 | } 503 | } 504 | 505 | inline vec3 vec3::cross(const vec3 &v) const { 506 | return vec3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); 507 | } 508 | 509 | inline vec3 &vec3::operator +=(const vec3 &vec) { 510 | x += vec.x; 511 | y += vec.y; 512 | z += vec.z; 513 | return *this; 514 | } 515 | 516 | inline vec3 &vec3::operator -=(const vec3 &vec) { 517 | x -= vec.x; 518 | y -= vec.y; 519 | z -= vec.z; 520 | return *this; 521 | } 522 | 523 | inline vec3 &vec3::operator *=(float value) { 524 | x *= value; 525 | y *= value; 526 | z *= value; 527 | return *this; 528 | } 529 | 530 | inline vec3 &vec3::operator /=(float value) { 531 | const float inv = 1.0f / value; 532 | x *= inv; 533 | y *= inv; 534 | z *= inv; 535 | return *this; 536 | } 537 | 538 | inline vec3 vec3::operator -() const { 539 | return vec3(-x, -y, -z); 540 | } 541 | 542 | inline float vec3::operator[](size_t index) const { 543 | return m[index]; 544 | } 545 | 546 | inline float &vec3::operator[](size_t index) { 547 | return m[index]; 548 | } 549 | 550 | inline vec3 operator+(const vec3 &a, const vec3 &b) { 551 | return vec3(a.x + b.x, a.y + b.y, a.z + b.z); 552 | } 553 | 554 | inline vec3 operator-(const vec3 &a, const vec3 &b) { 555 | return vec3(a.x - b.x, a.y - b.y, a.z - b.z); 556 | } 557 | 558 | inline vec3 operator*(const vec3 &a, float value) { 559 | return vec3(a.x * value, a.y * value, a.z * value); 560 | } 561 | 562 | inline vec3 operator*(float value, const vec3 &a) { 563 | return vec3(a.x * value, a.y * value, a.z * value); 564 | } 565 | 566 | inline vec3 operator/(const vec3 &a, float value) { 567 | const float inv = 1.0f / value; 568 | return vec3(a.x * inv, a.y * inv, a.z * inv); 569 | } 570 | 571 | inline vec3 operator^(const vec3 &a, const vec3 &b) { 572 | return vec3((a.y * b.z - a.z * b.y), 573 | (a.z * b.x - a.x * b.z), 574 | (a.x * b.y - a.y * b.x)); 575 | } 576 | 577 | inline float operator*(const vec3 &a, const vec3 &b) { 578 | return a.x * b.x + a.y * b.y + a.z * b.z; 579 | } 580 | 581 | inline bool operator==(const vec3 &a, const vec3 &b) { 582 | return (fabs(a.x - b.x) < kEpsilon) 583 | && (fabs(a.y - b.y) < kEpsilon) 584 | && (fabs(a.z - b.z) < kEpsilon); 585 | } 586 | 587 | inline bool operator!=(const vec3 &a, const vec3 &b) { 588 | return (m::abs(a.x - b.x) > kEpsilon) 589 | || (m::abs(a.y - b.y) > kEpsilon) 590 | || (m::abs(a.z - b.z) > kEpsilon); 591 | } 592 | 593 | inline vec3 clamp(const vec3 ¤t, const vec3 &min, const vec3 &max) { 594 | return { clamp(current.x, min.x, max.x), 595 | clamp(current.y, min.y, max.y), 596 | clamp(current.z, min.z, max.z) }; 597 | } 598 | 599 | } 600 | 601 | // Tangent stuff 602 | static void calculateTangent(const u::vector &vertices, 603 | const u::vector &coordinates, 604 | size_t v0, 605 | size_t v1, 606 | size_t v2, 607 | m::vec3 &tangent, 608 | m::vec3 &bitangent) 609 | { 610 | const m::vec3 &x = vertices[v0]; 611 | const m::vec3 &y = vertices[v1]; 612 | const m::vec3 &z = vertices[v2]; 613 | const m::vec3 q1(y - x); 614 | const m::vec3 q2(z - x); 615 | const float s1 = coordinates[v1].x - coordinates[v0].x; 616 | const float s2 = coordinates[v2].x - coordinates[v0].x; 617 | const float t1 = coordinates[v1].y - coordinates[v0].y; 618 | const float t2 = coordinates[v2].y - coordinates[v0].y; 619 | const float det = s1*t2 - s2*t1; 620 | if (m::abs(det) <= m::kEpsilon) { 621 | // Unable to compute tangent + bitangent, default tangent along xAxis and 622 | // bitangent along yAxis. 623 | tangent = m::vec3::xAxis; 624 | bitangent = m::vec3::yAxis; 625 | return; 626 | } 627 | 628 | const float inv = 1.0f / det; 629 | tangent = m::vec3(inv * (t2 * q1.x - t1 * q2.x), 630 | inv * (t2 * q1.y - t1 * q2.y), 631 | inv * (t2 * q1.z - t1 * q2.z)); 632 | bitangent = m::vec3(inv * (-s2 * q1.x + s1 * q2.x), 633 | inv * (-s2 * q1.y + s1 * q2.y), 634 | inv * (-s2 * q1.z + s1 * q2.z)); 635 | } 636 | 637 | static void createTangents(const u::vector &vertices, 638 | const u::vector &coordinates, 639 | const u::vector &normals, 640 | const u::vector &indices, 641 | u::vector &tangents_, 642 | u::vector &bitangents_) 643 | { 644 | // Computing Tangent Space Basis Vectors for an Arbitrary Mesh (Lengyel’s Method) 645 | // Section 7.8 (or in Section 6.8 of the second edition). 646 | const size_t vertexCount = vertices.size(); 647 | u::vector tangents; 648 | u::vector bitangents; 649 | 650 | tangents.resize(vertexCount); 651 | bitangents.resize(vertexCount); 652 | 653 | m::vec3 tangent; 654 | m::vec3 bitangent; 655 | 656 | for (size_t i = 0; i < indices.size(); i += 3) { 657 | const size_t x = indices[i+0]; 658 | const size_t y = indices[i+1]; 659 | const size_t z = indices[i+2]; 660 | 661 | calculateTangent(vertices, coordinates, x, y, z, tangent, bitangent); 662 | 663 | tangents[x] += tangent; 664 | tangents[y] += tangent; 665 | tangents[z] += tangent; 666 | bitangents[x] += bitangent; 667 | bitangents[y] += bitangent; 668 | bitangents[z] += bitangent; 669 | } 670 | 671 | for (size_t i = 0; i < vertexCount; i++) { 672 | // Gram-Schmidt orthogonalize 673 | // http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process 674 | const m::vec3 &n = normals[i]; 675 | m::vec3 t = tangents[i]; 676 | const m::vec3 v = (t - n * (n * t)); 677 | tangents_[i] = v.isNullEpsilon() ? v : v.normalized(); 678 | 679 | if (!tangents_[i].isNormalized()) { 680 | // Couldn't calculate vertex tangent for vertex, so we fill it in along 681 | // the x axis. 682 | tangents_[i] = m::vec3::xAxis; 683 | t = tangents_[i]; 684 | } 685 | 686 | // bitangents are only stored by handness in the W component (-1.0f or 1.0f). 687 | bitangents_[i] = (((n ^ t) * bitangents[i]) < 0.0f) ? -1.0f : 1.0f; 688 | } 689 | } 690 | 691 | // Linear-speed vertex cache optimizer 692 | struct vertexCacheData { 693 | u::vector indices; 694 | size_t cachePosition; 695 | float currentScore; 696 | size_t totalValence; 697 | size_t remainingValence; 698 | bool calculated; 699 | 700 | vertexCacheData(); 701 | 702 | size_t findTriangle(size_t tri); 703 | void moveTriangle(size_t tri); 704 | }; 705 | 706 | struct triangleCacheData { 707 | bool rendered; 708 | float currentScore; 709 | size_t vertices[3]; 710 | bool calculated; 711 | 712 | triangleCacheData(); 713 | }; 714 | 715 | struct vertexCache { 716 | void addVertex(size_t vertex); 717 | void clear(); 718 | size_t getCacheMissCount() const; 719 | size_t getCacheMissCount(const u::vector &indices); 720 | size_t getCachedVertex(size_t index) const; 721 | vertexCache(); 722 | private: 723 | size_t findVertex(size_t vertex); 724 | void removeVertex(size_t stackIndex); 725 | size_t m_cache[40]; 726 | size_t m_misses; 727 | }; 728 | 729 | struct vertexCacheOptimizer { 730 | vertexCacheOptimizer(); 731 | 732 | enum result { 733 | kSuccess, 734 | kErrorInvalidIndex, 735 | kErrorNoVertices 736 | }; 737 | 738 | result optimize(u::vector &indices); 739 | 740 | size_t getCacheMissCount() const; 741 | 742 | private: 743 | u::vector m_vertices; 744 | u::vector m_triangles; 745 | u::vector m_indices; 746 | u::vector m_drawList; 747 | vertexCache m_vertexCache; 748 | size_t m_bestTriangle; 749 | 750 | float calcVertexScore(size_t vertex); 751 | size_t fullScoreRecalculation(); 752 | result initialPass(); 753 | result init(u::vector &indices, size_t vertexCount); 754 | void addTriangle(size_t triangle); 755 | bool cleanFlags(); 756 | void triangleScoreRecalculation(size_t triangle); 757 | size_t partialScoreRecalculation(); 758 | bool process(); 759 | }; 760 | 761 | struct face { 762 | face(); 763 | size_t vertex; 764 | size_t normal; 765 | size_t coordinate; 766 | }; 767 | 768 | inline face::face() 769 | : vertex((size_t)-1) 770 | , normal((size_t)-1) 771 | , coordinate((size_t)-1) 772 | { 773 | } 774 | 775 | inline bool operator==(const face &lhs, const face &rhs) { 776 | return lhs.vertex == rhs.vertex 777 | && lhs.normal == rhs.normal 778 | && lhs.coordinate == rhs.coordinate; 779 | } 780 | 781 | // Hash a face 782 | namespace std { 783 | template <> 784 | struct hash<::face> { 785 | size_t operator()(const ::face &f) const { 786 | static constexpr size_t prime1 = 73856093u; 787 | static constexpr size_t prime2 = 19349663u; 788 | static constexpr size_t prime3 = 83492791u; 789 | return (f.vertex * prime1) ^ (f.normal * prime2) ^ (f.coordinate * prime3); 790 | } 791 | }; 792 | } 793 | 794 | ///! vertexCacheData 795 | size_t vertexCacheData::findTriangle(size_t triangle) { 796 | for (size_t i = 0; i < indices.size(); i++) 797 | if (indices[i] == triangle) 798 | return i; 799 | return (size_t)-1; 800 | } 801 | 802 | void vertexCacheData::moveTriangle(size_t triangle) { 803 | size_t index = findTriangle(triangle); 804 | assert(index != (size_t)-1); 805 | 806 | // Erase the index and add it to the end 807 | indices.erase(indices.begin() + index, 808 | indices.begin() + index + 1); 809 | indices.push_back(triangle); 810 | } 811 | 812 | vertexCacheData::vertexCacheData() 813 | : cachePosition((size_t)-1) 814 | , currentScore(0.0f) 815 | , totalValence(0) 816 | , remainingValence(0) 817 | , calculated(false) 818 | { 819 | } 820 | 821 | ///!triangleCacheData 822 | triangleCacheData::triangleCacheData() 823 | : rendered(false) 824 | , currentScore(0.0f) 825 | , calculated(false) 826 | { 827 | vertices[0] = (size_t)-1; 828 | vertices[1] = (size_t)-1; 829 | vertices[2] = (size_t)-1; 830 | } 831 | 832 | ///! vertexCache 833 | size_t vertexCache::findVertex(size_t vertex) { 834 | for (size_t i = 0; i < 32; i++) 835 | if (m_cache[i] == vertex) 836 | return i; 837 | return (size_t)-1; 838 | } 839 | 840 | void vertexCache::removeVertex(size_t stackIndex) { 841 | for (size_t i = stackIndex; i < 38; i++) 842 | m_cache[i] = m_cache[i + 1]; 843 | } 844 | 845 | void vertexCache::addVertex(size_t vertex) { 846 | size_t w = findVertex(vertex); 847 | // remove the vertex for later reinsertion at the top 848 | if (w != (size_t)-1) 849 | removeVertex(w); 850 | else // not found, cache miss! 851 | m_misses++; 852 | // shift all vertices down to make room for the new top vertex 853 | for (size_t i = 39; i != 0; i--) 854 | m_cache[i] = m_cache[i - 1]; 855 | // add new vertex to cache 856 | m_cache[0] = vertex; 857 | } 858 | 859 | void vertexCache::clear() { 860 | for (size_t i = 0; i < 40; i++) 861 | m_cache[i] = (size_t)-1; 862 | m_misses = 0; 863 | } 864 | 865 | size_t vertexCache::getCacheMissCount() const { 866 | return m_misses; 867 | } 868 | 869 | size_t vertexCache::getCacheMissCount(const u::vector &indices) { 870 | clear(); 871 | for (auto &it : indices) 872 | addVertex(it); 873 | return m_misses; 874 | } 875 | 876 | size_t vertexCache::getCachedVertex(size_t index) const { 877 | return m_cache[index]; 878 | } 879 | 880 | vertexCache::vertexCache() { 881 | clear(); 882 | } 883 | 884 | ///! vertexCacheOptimizer 885 | static constexpr float kCacheDecayPower = 1.5f; 886 | static constexpr float kLastTriScore = 0.75f; 887 | static constexpr float kValenceBoostScale = 2.0f; 888 | static constexpr float kValenceBoostPower = 0.5f; 889 | 890 | vertexCacheOptimizer::vertexCacheOptimizer() 891 | : m_bestTriangle(0) 892 | { 893 | } 894 | 895 | vertexCacheOptimizer::result vertexCacheOptimizer::optimize(u::vector &indices) { 896 | size_t find = (size_t)-1; 897 | for (size_t i = 0; i < indices.size(); i++) 898 | if (find == (size_t)-1 || (find != (size_t)-1 && indices[i] > find)) 899 | find = indices[i]; 900 | if (find == (size_t)-1) 901 | return kErrorNoVertices; 902 | 903 | result begin = init(indices, find + 1); 904 | if (begin != kSuccess) 905 | return begin; 906 | 907 | // Process 908 | while (process()) 909 | ; 910 | 911 | // Rewrite the indices 912 | for (size_t i = 0; i < m_drawList.size(); i++) 913 | for (size_t j = 0; j < 3; j++) 914 | indices[3 * i + j] = m_triangles[m_drawList[i]].vertices[j]; 915 | 916 | return kSuccess; 917 | } 918 | 919 | float vertexCacheOptimizer::calcVertexScore(size_t vertex) { 920 | vertexCacheData *v = &m_vertices[vertex]; 921 | if (v->remainingValence == (size_t)-1 || v->remainingValence == 0) 922 | return -1.0f; // No triangle needs this vertex 923 | 924 | float value = 0.0f; 925 | if (v->cachePosition == (size_t)-1) { 926 | // Vertex is not in FIFO cache. 927 | } else { 928 | if (v->cachePosition < 3) { 929 | // This vertex was used in the last triangle. It has fixed score 930 | // in whichever of the tree it's in. 931 | value = kLastTriScore; 932 | } else { 933 | // Points for being heigh in the cache 934 | const float scale = 1.0f / (32 - 3); 935 | value = 1.0f - (v->cachePosition - 3) * scale; 936 | value = powf(value, kCacheDecayPower); 937 | } 938 | } 939 | 940 | // Bonus points for having a low number of triangles. 941 | float valenceBoost = powf(float(v->remainingValence), -kValenceBoostPower); 942 | value += kValenceBoostScale * valenceBoost; 943 | return value; 944 | } 945 | 946 | size_t vertexCacheOptimizer::fullScoreRecalculation() { 947 | // Calculate score for all vertices 948 | for (size_t i = 0; i < m_vertices.size(); i++) 949 | m_vertices[i].currentScore = calcVertexScore(i); 950 | 951 | // Calculate scores for all active triangles 952 | float maxScore = 0.0f; 953 | size_t maxScoreTriangle = (size_t)-1; 954 | bool firstTime = true; 955 | 956 | for (size_t i = 0; i < m_triangles.size(); i++) { 957 | auto &it = m_triangles[i]; 958 | if (it.rendered) 959 | continue; 960 | 961 | // Sum the score of all the triangle's vertices 962 | float sum = m_vertices[it.vertices[0]].currentScore + 963 | m_vertices[it.vertices[1]].currentScore + 964 | m_vertices[it.vertices[2]].currentScore; 965 | it.currentScore = sum; 966 | 967 | if (firstTime || sum > maxScore) { 968 | firstTime = false; 969 | maxScore = sum; 970 | maxScoreTriangle = i; 971 | } 972 | } 973 | 974 | return maxScoreTriangle; 975 | } 976 | 977 | vertexCacheOptimizer::result vertexCacheOptimizer::initialPass() { 978 | for (size_t i = 0; i < m_indices.size(); i++) { 979 | size_t index = m_indices[i]; 980 | if (index == (size_t)-1 || index >= m_vertices.size()) 981 | return kErrorInvalidIndex; 982 | m_vertices[index].totalValence++; 983 | m_vertices[index].remainingValence++; 984 | m_vertices[index].indices.push_back(i / 3); 985 | } 986 | m_bestTriangle = fullScoreRecalculation(); 987 | return kSuccess; 988 | } 989 | 990 | vertexCacheOptimizer::result vertexCacheOptimizer::init(u::vector &indices, size_t maxVertex) { 991 | const size_t triangleCount = indices.size() / 3; 992 | 993 | // Reset draw list 994 | m_drawList.clear(); 995 | m_drawList.reserve(maxVertex); 996 | 997 | // Reset and initialize vertices and triangles 998 | m_vertices.clear(); 999 | m_vertices.reserve(maxVertex); 1000 | for (size_t i = 0; i < maxVertex; i++) 1001 | m_vertices.push_back(vertexCacheData()); 1002 | 1003 | m_triangles.clear(); 1004 | m_triangles.reserve(triangleCount); 1005 | for (size_t i = 0; i < triangleCount; i++) { 1006 | triangleCacheData data; 1007 | for (size_t j = 0; j < 3; j++) 1008 | data.vertices[j] = indices[i * 3 + j]; 1009 | m_triangles.push_back(data); 1010 | } 1011 | 1012 | // Copy the indices 1013 | m_indices.clear(); 1014 | m_indices.reserve(indices.size()); 1015 | for (auto &it : indices) 1016 | m_indices.push_back(it); 1017 | 1018 | // Run the initial pass 1019 | m_vertexCache.clear(); 1020 | m_bestTriangle = (size_t)-1; 1021 | 1022 | return initialPass(); 1023 | } 1024 | 1025 | void vertexCacheOptimizer::addTriangle(size_t triangle) { 1026 | // reset all cache positions 1027 | for (size_t i = 0; i < 32; i++) { 1028 | size_t find = m_vertexCache.getCachedVertex(i); 1029 | if (find == (size_t)-1) 1030 | continue; 1031 | m_vertices[find].cachePosition = (size_t)-1; 1032 | } 1033 | 1034 | triangleCacheData *t = &m_triangles[triangle]; 1035 | if (t->rendered) 1036 | return; 1037 | 1038 | for (size_t i = 0; i < 3; i++) { 1039 | // Add all the triangle's vertices to the cache 1040 | m_vertexCache.addVertex(t->vertices[i]); 1041 | vertexCacheData *v = &m_vertices[t->vertices[i]]; 1042 | 1043 | // Decrease the remaining valence. 1044 | v->remainingValence--; 1045 | 1046 | // Move the added triangle to the end of the vertex's triangle index 1047 | // list such that the first `remainingValence' triangles in the index 1048 | // list are only the active ones. 1049 | v->moveTriangle(triangle); 1050 | } 1051 | 1052 | // It's been rendered, mark it 1053 | m_drawList.push_back(triangle); 1054 | t->rendered = true; 1055 | 1056 | // Update all the vertex cache positions 1057 | for (size_t i = 0; i < 32; i++) { 1058 | size_t index = m_vertexCache.getCachedVertex(i); 1059 | if (index == (size_t)-1) 1060 | continue; 1061 | m_vertices[index].cachePosition = i; 1062 | } 1063 | } 1064 | 1065 | // Avoid duplicate calculations during processing. Triangles and vertices have 1066 | // a `calculated' flag which must be reset at the beginning of the process for 1067 | // all active triangles that have one or more of their vertices currently in 1068 | // cache as well all their other vertices. 1069 | // 1070 | // If there aren't any active triangles in the cache this function returns 1071 | // false and a full recalculation of the tree is performed. 1072 | bool vertexCacheOptimizer::cleanFlags() { 1073 | bool found = false; 1074 | for (size_t i = 0; i < 32; i++) { 1075 | size_t find = m_vertexCache.getCachedVertex(i); 1076 | if (find == (size_t)-1) 1077 | continue; 1078 | 1079 | vertexCacheData *v = &m_vertices[find]; 1080 | for (size_t j = 0; j < v->remainingValence; j++) { 1081 | triangleCacheData *t = &m_triangles[v->indices[j]]; 1082 | found = true; 1083 | // Clear flags 1084 | t->calculated = false; 1085 | for (size_t k = 0; k < 3; k++) 1086 | m_vertices[t->vertices[k]].calculated = false; 1087 | } 1088 | } 1089 | return found; 1090 | } 1091 | 1092 | void vertexCacheOptimizer::triangleScoreRecalculation(size_t triangle) { 1093 | triangleCacheData *t = &m_triangles[triangle]; 1094 | 1095 | // Calculate vertex scores 1096 | float sum = 0.0f; 1097 | for (size_t i = 0; i < 3; i++) { 1098 | vertexCacheData *v = &m_vertices[t->vertices[i]]; 1099 | float score = v->calculated ? v->currentScore : calcVertexScore(t->vertices[i]); 1100 | v->currentScore = score; 1101 | v->calculated = true; 1102 | sum += score; 1103 | } 1104 | 1105 | t->currentScore = sum; 1106 | t->calculated = true; 1107 | } 1108 | 1109 | size_t vertexCacheOptimizer::partialScoreRecalculation() { 1110 | // Iterate through all the vertices of the cache 1111 | bool firstTime = true; 1112 | float maxScore = 0.0f; 1113 | size_t maxScoreTriangle = (size_t)-1; 1114 | for (size_t i = 0; i < 32; i++) { 1115 | size_t find = m_vertexCache.getCachedVertex(i); 1116 | if (find == (size_t)-1) 1117 | continue; 1118 | 1119 | vertexCacheData *v = &m_vertices[find]; 1120 | 1121 | // Iterate through all the active triangles of this vertex 1122 | for (size_t j = 0; j < v->remainingValence; j++) { 1123 | size_t triangle = v->indices[j]; 1124 | triangleCacheData *t = &m_triangles[triangle]; 1125 | 1126 | // Calculate triangle score if it isn't already calculated 1127 | if (!t->calculated) 1128 | triangleScoreRecalculation(triangle); 1129 | 1130 | float score = t->currentScore; 1131 | // Found a triangle to process 1132 | if (firstTime || score > maxScore) { 1133 | firstTime = false; 1134 | maxScore = score; 1135 | maxScoreTriangle = triangle; 1136 | } 1137 | } 1138 | } 1139 | return maxScoreTriangle; 1140 | } 1141 | 1142 | inline bool vertexCacheOptimizer::process() { 1143 | if (m_drawList.size() == m_triangles.size()) 1144 | return false; 1145 | 1146 | // Add the selected triangle to the draw list 1147 | addTriangle(m_bestTriangle); 1148 | 1149 | // Recalculate the vertex and triangle scores and select the best triangle 1150 | // for the next iteration. 1151 | m_bestTriangle = cleanFlags() ? partialScoreRecalculation() : fullScoreRecalculation(); 1152 | 1153 | return true; 1154 | } 1155 | 1156 | size_t vertexCacheOptimizer::getCacheMissCount() const { 1157 | return m_vertexCache.getCacheMissCount(); 1158 | } 1159 | 1160 | // OBJ loading 1161 | struct obj { 1162 | bool load(const u::string &file, int &before, int &after); 1163 | u::vector m_indices; 1164 | u::vector m_positions; 1165 | u::vector m_normals; 1166 | u::vector m_coordinates; 1167 | u::vector m_tangents; 1168 | u::vector m_bitangents; // Sign only 1169 | }; 1170 | 1171 | bool obj::load(const u::string &file, int &before, int &after) { 1172 | u::file fp = u::fopen(file, "r"); 1173 | if (!fp) 1174 | return false; 1175 | 1176 | // Processed vertices, normals and coordinates from the OBJ file 1177 | u::vector vertices; 1178 | u::vector normals; 1179 | u::vector coordinates; 1180 | u::vector tangents; 1181 | u::vector bitangents; 1182 | 1183 | // Unique vertices are stored in a map keyed by face. 1184 | u::map uniques; 1185 | 1186 | size_t count = 0; 1187 | size_t group = 0; 1188 | while (auto get = u::getline(fp)) { 1189 | auto &line = *get; 1190 | // Skip whitespace 1191 | while (line.size() && strchr(" \t", line[0])) 1192 | line.erase(line.begin()); 1193 | // Skip comments 1194 | if (strchr("#$", line[0])) 1195 | continue; 1196 | // Skip empty lines 1197 | if (line.empty()) 1198 | continue; 1199 | 1200 | // Process the individual lines 1201 | float x = 0.0f; 1202 | float y = 0.0f; 1203 | float z = 0.0f; 1204 | if (u::sscanf(line, "v %f %f %f", &x, &y, &z) == 3) { 1205 | // v float float float 1206 | vertices.push_back({x, y, z * -1.0f}); 1207 | } else if (u::sscanf(line, "vn %f %f %f", &x, &y, &z) == 3) { 1208 | // vn float float float 1209 | normals.push_back({x * -1.0f, y * -1.0f, z}); 1210 | } else if (u::sscanf(line, "vt %f %f", &x, &y) == 2) { 1211 | // vt float float 1212 | coordinates.push_back({x, 1.0f - y, 0.0f}); 1213 | } else if (line[0] == 'g') { 1214 | group++; 1215 | } else if (line[0] == 'f' && group == 0) { // Only process the first group faces 1216 | u::vector v; 1217 | u::vector n; 1218 | u::vector t; 1219 | 1220 | // Note: 1 to skip "f" 1221 | auto contents = u::split(line); 1222 | for (size_t i = 1; i < contents.size(); i++) { 1223 | int vi = 0; 1224 | int ni = 0; 1225 | int ti = 0; 1226 | if (u::sscanf(contents[i], "%i/%i/%i", &vi, &ti, &ni) == 3) { 1227 | v.push_back(vi < 0 ? v.size() + vi : vi - 1); 1228 | t.push_back(ti < 0 ? t.size() + ti : ti - 1); 1229 | n.push_back(ni < 0 ? n.size() + ni : ni - 1); 1230 | } else if (u::sscanf(contents[i], "%i//%i", &vi, &ni) == 2) { 1231 | v.push_back(vi < 0 ? v.size() + vi : vi - 1); 1232 | n.push_back(ni < 0 ? n.size() + ni : ni - 1); 1233 | } else if (u::sscanf(contents[i], "%i/%i", &vi, &ti) == 2) { 1234 | v.push_back(vi < 0 ? v.size() + vi : vi - 1); 1235 | t.push_back(ti < 0 ? t.size() + ti : ti - 1); 1236 | } else if (u::sscanf(contents[i], "%i", &vi) == 1) { 1237 | v.push_back(vi < 0 ? v.size() + vi : vi - 1); 1238 | } 1239 | } 1240 | 1241 | // Triangulate the mesh 1242 | for (size_t i = 1; i < v.size() - 1; ++i) { 1243 | auto index = m_indices.size(); 1244 | m_indices.resize(index + 3); 1245 | auto triangulate = [&v, &n, &t, &uniques, &count](size_t index, size_t &out) { 1246 | face triangle; 1247 | triangle.vertex = v[index]; 1248 | if (n.size()) triangle.normal = n[index]; 1249 | if (t.size()) triangle.coordinate = t[index]; 1250 | // Only insert in the map if it doesn't exist 1251 | if (uniques.find(triangle) == uniques.end()) 1252 | uniques[triangle] = count++; 1253 | out = uniques[triangle]; 1254 | }; 1255 | triangulate(0, m_indices[index + 0]); 1256 | triangulate(i + 0, m_indices[index + 1]); 1257 | triangulate(i + 1, m_indices[index + 2]); 1258 | } 1259 | } 1260 | } 1261 | 1262 | // Construct the model, indices are already generated 1263 | m_positions.resize(count); 1264 | m_normals.resize(count); 1265 | m_coordinates.resize(count); 1266 | for (auto &it : uniques) { 1267 | const auto &first = it.first; 1268 | const auto &second = it.second; 1269 | m_positions[second] = vertices[first.vertex]; 1270 | if (normals.size()) 1271 | m_normals[second] = normals[first.normal]; 1272 | if (coordinates.size()) 1273 | m_coordinates[second] = coordinates[first.coordinate]; 1274 | } 1275 | 1276 | // Optimize the indices 1277 | vertexCache cache; 1278 | vertexCacheOptimizer vco; 1279 | vco.optimize(m_indices); 1280 | 1281 | // Change winding order 1282 | for (size_t i = 0; i < m_indices.size(); i += 3) 1283 | u::swap(m_indices[i], m_indices[i + 2]); 1284 | 1285 | // Calculate tangents 1286 | m_tangents.resize(count); 1287 | m_bitangents.resize(count); 1288 | createTangents(m_positions, m_coordinates, m_normals, m_indices, m_tangents, m_bitangents); 1289 | 1290 | before = cache.getCacheMissCount(m_indices); 1291 | after = vco.getCacheMissCount(); 1292 | 1293 | return true; 1294 | } 1295 | 1296 | #include "smbf.h" 1297 | int main(int argc, char **argv) { 1298 | --argc; 1299 | ++argv; 1300 | bool tangents = false; 1301 | bool bitangents = false; 1302 | const char *file = nullptr; 1303 | 1304 | for (int i = 0; i < argc; i++) { 1305 | if (!strcmp(argv[i], "-t")) 1306 | tangents = true; 1307 | else if (!strcmp(argv[i], "-b")) 1308 | bitangents = true; 1309 | else 1310 | file = argv[i]; 1311 | } 1312 | 1313 | if (!file) { 1314 | fprintf(stderr, "expected OBJ file name\n"); 1315 | return 1; 1316 | } 1317 | 1318 | int before = 0; 1319 | int after = 0; 1320 | obj o; 1321 | if (!o.load(file, before, after)) { 1322 | fprintf(stderr, "failed to load OBJ model `%s'\n", file); 1323 | return 1; 1324 | } 1325 | 1326 | int format = SMBF_FORMAT_P; 1327 | if (o.m_normals.size()) 1328 | format = SMBF_FORMAT_PN; 1329 | if (o.m_coordinates.size()) 1330 | format = SMBF_FORMAT_PNC; 1331 | if (o.m_tangents.size() && tangents) 1332 | format = SMBF_FORMAT_PNCT; 1333 | if (o.m_bitangents.size() && bitangents) 1334 | format = SMBF_FORMAT_PNCTB; 1335 | 1336 | 1337 | const char *formats[] = { 1338 | "P", "PN", "PNC", "PNCT", "PNCTB" 1339 | }; 1340 | 1341 | header h; 1342 | h.magic[0] = 'S'; 1343 | h.magic[1] = 'M'; 1344 | h.magic[2] = 'B'; 1345 | h.magic[3] = 'F'; 1346 | h.version = SMBF_VERSION; 1347 | h.format = format; 1348 | h.count = o.m_positions.size(); 1349 | h.indices = o.m_indices.size(); 1350 | 1351 | // Interleave the data 1352 | union data { 1353 | smbfP asP; 1354 | smbfPN asPN; 1355 | smbfPNC asPNC; 1356 | smbfPNCT asPNCT; 1357 | smbfPNCTB asPNCTB; 1358 | }; 1359 | u::vector inter; 1360 | inter.resize(h.count); 1361 | for (size_t i = 0; i < h.count; i++) { 1362 | data &x = inter[i]; 1363 | smbfPNCTB &s = x.asPNCTB; 1364 | s.px = o.m_positions[i].x; 1365 | s.py = o.m_positions[i].y; 1366 | s.pz = o.m_positions[i].z; 1367 | if (o.m_normals.size()) { 1368 | s.nx = o.m_normals[i].x; 1369 | s.ny = o.m_normals[i].y; 1370 | s.nz = o.m_normals[i].z; 1371 | } 1372 | if (o.m_coordinates.size()) { 1373 | s.tu = o.m_coordinates[i].x; 1374 | s.tv = o.m_coordinates[i].y; 1375 | } 1376 | s.tx = o.m_tangents[i].x; 1377 | s.ty = o.m_tangents[i].y; 1378 | s.tz = o.m_tangents[i].z; 1379 | s.b = o.m_bitangents[i]; 1380 | } 1381 | 1382 | u::string name(file, strrchr(file, '.')); 1383 | name += ".smbf"; 1384 | u::file write = u::fopen(name, "w"); 1385 | if (!write) { 1386 | fprintf(stderr, "failed to open `%s' for writing\n", name.c_str()); 1387 | return 1; 1388 | } 1389 | 1390 | fwrite(&h, sizeof(header), 1, write); 1391 | for (auto &it : inter) { 1392 | switch (format) { 1393 | case SMBF_FORMAT_P: 1394 | fwrite(&it.asP, sizeof(smbfP), 1, write); 1395 | break; 1396 | case SMBF_FORMAT_PN: 1397 | fwrite(&it.asPN, sizeof(smbfPN), 1, write); 1398 | break; 1399 | case SMBF_FORMAT_PNC: 1400 | fwrite(&it.asPNC, sizeof(smbfPNC), 1, write); 1401 | break; 1402 | case SMBF_FORMAT_PNCT: 1403 | fwrite(&it.asPNCT, sizeof(smbfPNCT), 1, write); 1404 | break; 1405 | case SMBF_FORMAT_PNCTB: 1406 | fwrite(&it.asPNCTB, sizeof(smbfPNCTB), 1, write); 1407 | break; 1408 | } 1409 | } 1410 | 1411 | for (auto &it : o.m_indices) { 1412 | uint32_t convert = it; 1413 | fwrite(&convert, sizeof(uint32_t), 1, write); 1414 | } 1415 | 1416 | printf("Wrote `%s':\n", name.c_str()); 1417 | printf(" Indices:\n"); 1418 | printf(" Count: %i\n", int(h.indices)); 1419 | printf(" Cache misses:\n"); 1420 | printf(" Before: %i\n", before); 1421 | printf(" After: %i\n", after); 1422 | printf(" Vertices:\n"); 1423 | printf(" Count: %i\n", int(h.count)); 1424 | printf(" Format: %s\n", formats[format]); 1425 | return 0; 1426 | } 1427 | --------------------------------------------------------------------------------