├── examples ├── common │ ├── tiny_obj_loader.cc │ └── tiny_obj_loader.h └── halfedge │ ├── Makefile │ ├── cube.obj │ └── main.cc ├── .clang-format ├── tests ├── Makefile ├── main_test.cc └── acutest.h ├── LICENSE ├── README.md └── half-edge.hh /examples/common/tiny_obj_loader.cc: -------------------------------------------------------------------------------- 1 | #define TINYOBJLOADER_IMPLEMENTATION 2 | #include "tiny_obj_loader.h" 3 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 2 4 | TabWidth: 2 5 | UseTab: Never 6 | BreakBeforeBraces: Attach 7 | Standard: Cpp11 8 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS=-fsanitize=address -Weverything -Werror -Wno-c++98-compat 2 | CXX=clang++ 3 | 4 | all: 5 | $(CXX) -o test $(CXXFLAGS) main_test.cc 6 | -------------------------------------------------------------------------------- /examples/halfedge/Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS=-fsanitize=address -Weverything -Werror -Wno-c++98-compat 2 | CXX=clang++ 3 | 4 | all: 5 | $(CXX) -o halfedge $(CXXFLAGS) main.cc -I../common -I../../ 6 | -------------------------------------------------------------------------------- /examples/halfedge/cube.obj: -------------------------------------------------------------------------------- 1 | mtllib cube.mtl 2 | 3 | v 0.000000 2.000000 2.000000 4 | v 0.000000 0.000000 2.000000 5 | v 2.000000 0.000000 2.000000 6 | v 2.000000 2.000000 2.000000 7 | v 0.000000 2.000000 0.000000 8 | v 0.000000 0.000000 0.000000 9 | v 2.000000 0.000000 0.000000 10 | v 2.000000 2.000000 0.000000 11 | # 8 vertices 12 | 13 | g front cube 14 | usemtl white 15 | f 1 2 3 4 16 | # two white spaces between 'back' and 'cube' 17 | g back cube 18 | # expects white material 19 | f 8 7 6 5 20 | g right cube 21 | usemtl red 22 | f 4 3 7 8 23 | g top cube 24 | usemtl white 25 | f 5 1 4 8 26 | g left cube 27 | usemtl green 28 | f 5 6 2 1 29 | g bottom cube 30 | usemtl white 31 | f 2 6 7 3 32 | # 6 elements 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Syoyo Fujita 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 | 3 | # Tiny Mesh processing utilities in C++11 4 | 5 | Dependency-free, header-only tiny mesh processing utilities in C++11. 6 | 7 | ## Supported platform 8 | 9 | * [x] Windows 10 | * [x] Linux 11 | * [ ] macOS(not tested, but should work) 12 | 13 | ## Features 14 | 15 | * half-edge.hh : Compute half edge structure from polygons. 16 | * option: `TINYMESHUTILS_USE_ROBINMAP` Use robin-map, which would give better performance. 17 | 18 | ### Half-edge 19 | 20 | ``` 21 | bool BuildHalfEdge(const std::vector &face_vert_indices, 22 | const std::vector &face_vert_counts, 23 | std::vector *edges, std::vector *halfedges, 24 | std::string *err); 25 | ``` 26 | 27 | Example use is 28 | 29 | ``` 30 | 31 | // Define this in *one* .cc 32 | #define TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION 33 | #include "half-edge.hh" 34 | 35 | std::vector face_vert_indices = ...; 36 | std::vector face_vert_counts = ...; 37 | std::vector edges; // array of edges(computed from `face_vert_counts` and `face_vert_indices`) 38 | std::vector halfedges; // array of half-edges 39 | std::vector vertex_start_halfedge_indices; // starting half-edge index for each vertex. 40 | 41 | std::string err; // error message 42 | 43 | if (!BuildHalfEdge(face_vert_indices, 44 | face_vert_counts, 45 | &edge, &halfedges, &vertex_start_halfedge_indices, 46 | &err)) { 47 | if (!err.empty())) { 48 | std::cerr << err << "\n"; 49 | } 50 | return false; 51 | } 52 | 53 | // ok 54 | 55 | // 56 | // Get the starting half-edge for a given vertex index vid 57 | // Since i'th halfedge corresnponds i'th vertex, you can use vertex index to look up half-edge 58 | // 59 | 60 | const tinymeshutils::HalfEdge &he = halfedges[vid]; 61 | 62 | // 63 | // Get the starting half-edge for a given face fid 64 | // 65 | 66 | // Assume you'll built face_vert offset. 67 | std::vector face_vert_offset = ...; 68 | 69 | uint32_t vid = face_vert_indices[face_vert_offset[fid]]; 70 | const tinymeshutils::HalfEdge &he = halfedges[vid]; 71 | 72 | ``` 73 | 74 | #### TODO 75 | 76 | * [ ] Add API with user supplied edge information. 77 | 78 | 79 | ## License 80 | 81 | MIT license. 82 | 83 | ### Third party license 84 | 85 | acutest(for unit test) : MIT license https://github.com/mity/ac utest 86 | -------------------------------------------------------------------------------- /tests/main_test.cc: -------------------------------------------------------------------------------- 1 | #if defined(__clang__) 2 | #pragma clang diagnostic push 3 | #pragma clang diagnostic ignored "-Weverything" 4 | #elif defined(__GNUC__) 5 | #pragma GCC diagnostic ignored "-Wmissing-declarations" 6 | #pragma GCC diagnostic ignored "-Wignored-qualifiers" 7 | #pragma GCC diagnostic push 8 | #pragma GCC diagnostic ignored "-Wcast-qual" 9 | #pragma GCC diagnostic ignored "-Wsign-conversion" 10 | #pragma GCC diagnostic ignored "-Wformat" 11 | #pragma GCC diagnostic ignored "-Wswitch-default" 12 | #endif 13 | 14 | #include "acutest.h" 15 | 16 | #if defined(__clang__) 17 | #pragma clang diagnostic pop 18 | #elif defined(__GNUC__) 19 | #pragma GCC diagnostic pop 20 | #endif 21 | 22 | #define TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION 23 | #include "../half-edge.hh" 24 | 25 | #include 26 | 27 | static void test_half_edge() { 28 | std::vector indices{0, 1, 2, 3, 1, 4, 2}; 29 | std::vector counts{4, 3}; 30 | 31 | std::vector edges; 32 | std::vector halfedges; 33 | std::vector vertex_start_halfedge_indices; 34 | std::string err; 35 | 36 | bool ret = tinymeshutils::BuildHalfEdge(indices, counts, &edges, &halfedges, 37 | &vertex_start_halfedge_indices, &err); 38 | TEST_CHECK(true == ret); 39 | std::cerr << err << "\n"; 40 | } 41 | 42 | static void test_invalid_face() { 43 | std::vector indices{0, 1, 2, 3}; 44 | std::vector counts{2, 3}; 45 | 46 | std::vector edges; 47 | std::vector halfedges; 48 | std::vector vertex_start_halfedge_indices; 49 | std::string err; 50 | 51 | bool ret = tinymeshutils::BuildHalfEdge(indices, counts, &edges, &halfedges, 52 | &vertex_start_halfedge_indices, &err); 53 | TEST_CHECK(false == ret); 54 | std::cerr << err << "\n"; 55 | } 56 | 57 | static void test_invalid_topology() { 58 | std::vector indices{0, 1, 2, 0, 1, 2}; 59 | std::vector counts{3, 3}; 60 | 61 | std::vector edges; 62 | std::vector halfedges; 63 | std::vector vertex_start_halfedge_indices; 64 | std::string err; 65 | 66 | bool ret = tinymeshutils::BuildHalfEdge(indices, counts, &edges, &halfedges, 67 | &vertex_start_halfedge_indices, &err); 68 | TEST_CHECK(false == ret); 69 | std::cerr << err << "\n"; 70 | } 71 | 72 | TEST_LIST = {{"test_half_edge", test_half_edge}, 73 | {"test_invalid_face", test_invalid_face}, 74 | {"test_invalid_topology", test_invalid_topology}, 75 | {nullptr, nullptr}}; 76 | -------------------------------------------------------------------------------- /examples/halfedge/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define TINYOBJLOADER_IMPLEMENTATION 5 | #include "tiny_obj_loader.h" 6 | 7 | #define TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION 8 | #include "half-edge.hh" 9 | 10 | #include 11 | 12 | int main(int argc, char **argv) { 13 | if (argc < 2) { 14 | std::cout << "Need input.obj\n"; 15 | return EXIT_FAILURE; 16 | } 17 | 18 | std::string filename = argv[1]; 19 | 20 | tinyobj::attrib_t attrib; 21 | std::vector shapes; 22 | std::vector materials; 23 | std::string warn; 24 | std::string err; 25 | bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, 26 | filename.c_str(), /* basepath */ "", 27 | /* triangulate */ false); 28 | 29 | if (!warn.empty()) { 30 | std::cout << "WARN: " << warn << std::endl; 31 | } 32 | 33 | if (!err.empty()) { 34 | std::cerr << "ERR: " << err << std::endl; 35 | } 36 | 37 | if (!ret) { 38 | printf("Failed to load/parse .obj.\n"); 39 | return EXIT_FAILURE; 40 | } 41 | 42 | // Create single face indices. 43 | std::vector face_vert_indices; 44 | std::vector face_vert_counts; 45 | 46 | for (size_t s = 0; s < shapes.size(); s++) { 47 | for (size_t i = 0; i < shapes[s].mesh.indices.size(); i++) { 48 | face_vert_indices.push_back( 49 | uint32_t(shapes[s].mesh.indices[i].vertex_index)); 50 | } 51 | 52 | for (size_t i = 0; i < shapes[s].mesh.num_face_vertices.size(); i++) { 53 | face_vert_counts.push_back(shapes[s].mesh.num_face_vertices[i]); 54 | } 55 | } 56 | 57 | size_t n = (face_vert_indices.size() > 128) ? 128 : face_vert_indices.size(); 58 | for (size_t i = 0; i < n; i++) { 59 | std::cout << "f[" << i << "] = " << face_vert_indices[i] << "\n"; 60 | } 61 | 62 | n = (face_vert_counts.size() > 128) ? 128 : face_vert_counts.size(); 63 | for (size_t i = 0; i < n; i++) { 64 | std::cout << "fv[" << i << "] = " << face_vert_counts[i] << "\n"; 65 | } 66 | 67 | std::vector edges; 68 | std::vector halfedges; 69 | std::vector vertex_start_halfedge_indices; 70 | 71 | ret = tinymeshutils::BuildHalfEdge(face_vert_indices, face_vert_counts, 72 | &edges, &halfedges, 73 | &vertex_start_halfedge_indices, &err); 74 | 75 | if (!err.empty()) { 76 | std::cerr << "HE ERR: " << err << std::endl; 77 | } 78 | 79 | if (!ret) { 80 | std::cerr << "Failed to construct half-edge structure for a given mesh.\n"; 81 | return EXIT_FAILURE; 82 | } 83 | 84 | std::cout << "===================\n"; 85 | 86 | n = (vertex_start_halfedge_indices.size() > 128) ? 128 : vertex_start_halfedge_indices.size(); 87 | for (size_t i = 0; i < n; i++) { 88 | std::cout << "v[" << i << "].halfedge_index = " << vertex_start_halfedge_indices[i] << "\n"; 89 | } 90 | 91 | n = (halfedges.size() > 128) ? 128 : halfedges.size(); 92 | for (size_t i = 0; i < n; i++) { 93 | std::cout << "halfedge[" << i << "].next = " << halfedges[i].next_halfedge << "\n"; 94 | } 95 | 96 | // build offset table 97 | std::vector f_offsets(face_vert_counts.size()); 98 | f_offsets[0] = 0; 99 | for (size_t i = 1; i < face_vert_counts.size(); i++) { 100 | f_offsets[i] = f_offsets[i - 1] + face_vert_counts[i-1]; 101 | } 102 | 103 | // 104 | // list up edges for the first face. 105 | // for cube.obj, this should print 4 half-edges 106 | // 107 | { 108 | // get the first vertex index for a face `face_id`. 109 | size_t face_id = 0; // Change this as you wish 110 | size_t v_id = face_vert_indices[f_offsets[face_id] + 0]; 111 | std::cout << "v_id = " << v_id << "\n"; 112 | int64_t he_id = vertex_start_halfedge_indices[v_id]; 113 | if (he_id < 0) { 114 | std::cerr << "??? vertex does not have a link to half-edge\n"; 115 | return EXIT_FAILURE; 116 | } 117 | int64_t halfedge_idx = he_id; 118 | 119 | std::cout << "starting halfedge index = " << he_id << "\n"; 120 | uint32_t loop_max = 1000; // to avoid infinite looop. 121 | for (size_t i = 0; i < loop_max; i++) { 122 | 123 | const tinymeshutils::HalfEdge &halfedge = halfedges[size_t(halfedge_idx)]; 124 | 125 | if ((halfedge.next_halfedge == -1)) { 126 | std::cout << "end traverse\n"; 127 | break; 128 | } 129 | 130 | assert(halfedge.edge_index > -1); 131 | const tinymeshutils::Edge &edge = edges[size_t(halfedge.edge_index)]; 132 | 133 | std::cout << "halfedge\n"; 134 | std::cout << " edge_index " << halfedge.edge_index << "\n"; 135 | std::cout << " (" << edge.v0 << ", " << edge.v1 << ")\n"; 136 | std::cout << " opposite_index " << halfedge.opposite_halfedge << "\n"; 137 | std::cout << " next " << halfedge.next_halfedge << "\n"; 138 | 139 | halfedge_idx = halfedge.next_halfedge; 140 | if (halfedge_idx == he_id) { 141 | std::cout << "end traverse\n"; 142 | break; 143 | } 144 | } 145 | } 146 | 147 | // 148 | // walk edges for a vertex. 149 | // for cube.obj, this should print 6 half-edges(3 edges * 2) 150 | // 151 | { 152 | size_t face_id = 0; // Change this as you wish 153 | size_t v_id = face_vert_indices[f_offsets[face_id] + 0]; 154 | std::cout << "v_id = " << v_id << "\n"; 155 | int64_t he_id = vertex_start_halfedge_indices[v_id]; 156 | if (he_id < 0) { 157 | std::cerr << "??? vertex does not have a link to half-edge\n"; 158 | return EXIT_FAILURE; 159 | } 160 | 161 | int64_t halfedge_idx = he_id; 162 | 163 | std::cout << "starting halfedge index = " << he_id << "\n"; 164 | uint32_t loop_max = 100; // to avoid infinite looop. 165 | for (size_t i = 0; i < loop_max; i++) { 166 | 167 | tinymeshutils::HalfEdge &halfedge = halfedges[size_t(halfedge_idx)]; 168 | 169 | { 170 | const tinymeshutils::Edge &edge = edges[size_t(halfedge.edge_index)]; 171 | 172 | std::cout << "halfedge " << halfedge_idx << "\n"; 173 | std::cout << " edge_index " << halfedge.edge_index << "\n"; 174 | std::cout << " (" << edge.v0 << ", " << edge.v1 << ")\n"; 175 | std::cout << " opposite_index " << halfedge.opposite_halfedge << "\n"; 176 | std::cout << " next " << halfedge.next_halfedge << "\n"; 177 | } 178 | 179 | if (halfedge.next_halfedge == -1) { 180 | std::cout << "end traverse\n"; 181 | break; 182 | } 183 | 184 | assert(halfedge.edge_index > -1); 185 | 186 | if (halfedge.opposite_halfedge == -1) { 187 | std::cout << "boundary\n"; 188 | std::cout << "end traverse\n"; 189 | break; 190 | } 191 | 192 | const tinymeshutils::HalfEdge &twin = halfedges[size_t(halfedge.opposite_halfedge)]; 193 | { 194 | const tinymeshutils::Edge &edge = edges[size_t(twin.edge_index)]; 195 | 196 | std::cout << "halfedge(twin) " << halfedge.opposite_halfedge << "\n"; 197 | std::cout << " edge_index " << twin.edge_index << "\n"; 198 | std::cout << " (" << edge.v0 << ", " << edge.v1 << ")\n"; 199 | std::cout << " opposite_index " << twin.opposite_halfedge << "\n"; 200 | std::cout << " next " << twin.next_halfedge << "\n"; 201 | } 202 | 203 | if (twin.next_halfedge == -1) { 204 | std::cout << "???\n"; 205 | std::cout << "end traverse\n"; 206 | break; 207 | } 208 | 209 | halfedge_idx = twin.next_halfedge; 210 | //halfedge = halfedges[size_t(twin.next_halfedge)]; 211 | 212 | if (halfedge_idx == he_id) { 213 | std::cout << "end traverse\n"; 214 | break; 215 | } 216 | 217 | 218 | if (i == (loop_max - 1)) { 219 | std::cout << "??? failed to walk\n"; 220 | } 221 | } 222 | } 223 | 224 | return EXIT_SUCCESS; 225 | } 226 | -------------------------------------------------------------------------------- /half-edge.hh: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2019 Syoyo Fujita 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #ifndef TINYMESHUTILS_HALF_EDGE_HH_ 25 | #define TINYMESHUTILS_HALF_EDGE_HH_ 26 | 27 | // TODO(syoyo): Support uint64_t for Edge. 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | //#include // DBG 34 | 35 | // Using robin-map may give better performance 36 | #if defined(TINYMESHUTILS_USE_ROBINMAP) 37 | #error TODO 38 | #include 39 | 40 | #else 41 | #include 42 | 43 | // namespace umap = std::unordered_map; 44 | 45 | #endif 46 | 47 | namespace tinymeshutils { 48 | 49 | struct Edge { 50 | Edge(const uint32_t _v0, const uint32_t _v1) : v0(_v0), v1(_v1) {} 51 | 52 | // create an 64 bit identifier that is unique for the not oriented edge 53 | operator uint64_t() const { 54 | uint32_t p0 = v0, p1 = v1; 55 | if (p0 < p1) std::swap(p0, p1); 56 | return (uint64_t(p0) << 32) | uint64_t(p1); 57 | } 58 | 59 | uint32_t v0, v1; // start, end 60 | }; 61 | 62 | struct HalfEdge { 63 | // invalid or undefined = -1 64 | int64_t opposite_halfedge{-1}; // index to the halfedges array. 65 | int64_t next_halfedge{-1}; // index to the halfedges array. 66 | // int64_t vertex_index{-1}; // vertex index at the start of the edge 67 | int64_t face_index{-1}; // index to face indices 68 | int64_t edge_index{-1}; // index to edge indices 69 | }; 70 | 71 | // Functor object for Edge 72 | struct EdgeHash { 73 | size_t operator()(const std::pair &k) const { 74 | if (sizeof(size_t) == 8) { 75 | // We can create unique value 76 | return (uint64_t(k.first) << 32) | uint64_t(k.second); 77 | } else { 78 | // 32bit environment. we cannot create unique hash value. 79 | return std::hash()(k.first) ^ std::hash()(k.second); 80 | } 81 | } 82 | }; 83 | 84 | /// 85 | /// Simple half-edge construction for polygons. 86 | /// 87 | /// @param[in] face_vert_indices Array of face vertex indices. 88 | /// @param[in] face_vert_counts Array of the number of vertices for each face(3 89 | /// = triangle, 4 = quad, ...). 90 | /// @param[out] edges Array of edges constructed 91 | /// @param[out] halfedges Array of half-edges comstructed. length = 92 | /// face_vert_indices.size() 93 | /// @param[out] vertex_starting_halfedge_indices Index to starting half-edge in 94 | /// `halfedges` for each vertex. -1 when no half-edge assigned to its vertex. 95 | /// length = the highest value in `face_vert_indices`(= the number of vertices) 96 | /// face_vert_indices.size() 97 | /// @param[out] err Optional error message. 98 | /// 99 | /// @return true upon success. Return false when input mesh has invalid 100 | /// topology. 101 | /// 102 | /// face_vert_indices.size() is equal to the sum of each elements in 103 | /// `face_vert_counts`. Assume edge is defined in v0 -> v1, v1 -> v2, ... v(N-1) 104 | /// -> v0 order. 105 | /// 106 | bool BuildHalfEdge(const std::vector &face_vert_indices, 107 | const std::vector &face_vert_counts, 108 | std::vector *edges, std::vector *halfedges, 109 | std::vector *vertex_starting_halfedge_indices, 110 | std::string *err = nullptr); 111 | 112 | } // namespace tinymeshutils 113 | 114 | #endif // TINYMESHUTILS_HALF_EDGE_HH_ 115 | 116 | #if defined(TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION) 117 | namespace tinymeshutils { 118 | 119 | bool BuildHalfEdge(const std::vector &face_vert_indices, 120 | const std::vector &face_vert_counts, 121 | std::vector *edges, std::vector *halfedges, 122 | std::vector *vertex_starting_halfedge_indices, 123 | std::string *err) { 124 | // Based on documents at Internet and an implementation 125 | // https://github.com/yig/halfedge 126 | 127 | size_t num_indices = 0; 128 | for (size_t i = 0; i < face_vert_counts.size(); i++) { 129 | if (face_vert_counts[i] < 3) { 130 | // invalid # of vertices for a face. 131 | return false; 132 | } 133 | num_indices += face_vert_counts[i]; 134 | } 135 | 136 | // Find larget number = the number of vertices in input mesh. 137 | uint32_t num_vertices = 138 | (*std::max_element(face_vert_indices.begin(), face_vert_indices.end())) + 139 | 1; 140 | std::vector vertex_starting_halfedge_indices_buf(num_vertices, -1); 141 | 142 | // allocate buffer for half-edge. 143 | std::vector halfedge_buf(num_indices); 144 | 145 | std::unordered_map, size_t, EdgeHash> 146 | halfedge_table; // <, index to `half_edges`> 147 | 148 | // 149 | // 1. Build edges. 150 | // 151 | std::vector edge_buf; // linear array of edges. 152 | std::unordered_map 153 | edge_map; // 154 | 155 | { 156 | std::vector 157 | tmp_edges; // linear array of edges. may have duplicated edges 158 | 159 | // list up and register edges. 160 | size_t f_offset = 0; 161 | for (size_t f = 0; f < face_vert_counts.size(); f++) { 162 | uint32_t nv = face_vert_counts[f]; 163 | if (nv < 3) { 164 | if (err) { 165 | (*err) = "Face " + std::to_string(f) + " has invalid # of vertices " + 166 | std::to_string(nv) + "\n"; 167 | } 168 | return false; 169 | } 170 | 171 | uint32_t ne = nv; 172 | 173 | // for each edge 174 | for (size_t e = 0; e < size_t(ne); e++) { 175 | // std::cout << "e = " << e << ", " << (e + 1) % nv << "\n"; 176 | uint32_t v0 = face_vert_indices[f_offset + e]; 177 | uint32_t v1 = face_vert_indices[f_offset + (e + 1) % nv]; 178 | // std::cout << "v0 = " << v0 << ", v1 = " << v1 << "\n"; 179 | 180 | tmp_edges.push_back({v0, v1}); 181 | } 182 | 183 | f_offset += nv; 184 | } 185 | 186 | // create edge_map and unique array of edges. 187 | for (const auto &edge : tmp_edges) { 188 | uint64_t key = edge; 189 | if (!edge_map.count(key)) { 190 | size_t edge_idx = edge_buf.size(); 191 | edge_map[edge] = edge_idx; 192 | 193 | edge_buf.push_back(edge); 194 | } 195 | } 196 | } 197 | 198 | #if 0 199 | // dbg 200 | for (size_t i = 0; i < edge_buf.size(); i++) { 201 | std::cout << "edge[" << i << "] = " << edge_buf[i].v0 << ", " 202 | << edge_buf[i].v1 << "\n"; 203 | } 204 | #endif 205 | 206 | // 207 | // 2. Register half edges 208 | // 209 | { 210 | size_t f_offset = 0; 211 | for (size_t f = 0; f < face_vert_counts.size(); f++) { 212 | // for each edge 213 | uint32_t nv = face_vert_counts[f]; 214 | if (nv < 3) { 215 | if (err) { 216 | (*err) = "Face " + std::to_string(f) + " has invalid # of vertices " + 217 | std::to_string(nv) + "\n"; 218 | } 219 | return false; 220 | } 221 | 222 | uint32_t ne = nv; 223 | 224 | // register 225 | for (size_t e = 0; e < size_t(ne); e++) { 226 | uint32_t v0 = face_vert_indices[f_offset + e]; 227 | uint32_t v1 = face_vert_indices[f_offset + (e + 1) % nv]; 228 | 229 | Edge edge(v0, v1); 230 | 231 | // vertex pair must be unique over the input mesh 232 | if (halfedge_table.count(std::make_pair(v0, v1))) { 233 | if (err) { 234 | (*err) = "(Register half edges). Invalid topology. Edge (v0: " + 235 | std::to_string(v0) + ", v1: " + std::to_string(v1) + 236 | ") must be unique but duplicated one exists for Face " + 237 | std::to_string(f) + "\n"; 238 | } 239 | 240 | return false; 241 | } 242 | 243 | uint64_t eid = edge; // non oriented 244 | 245 | if (!edge_map.count(eid)) { 246 | if (err) { 247 | (*err) = "??? Edge (v0: " + std::to_string(edge.v0) + 248 | ", v1: " + std::to_string(edge.v1) + 249 | ") does not found for Face " + std::to_string(f) + 250 | ". This should not be happen.\n"; 251 | } 252 | return false; 253 | } 254 | 255 | size_t edge_index = edge_map[eid]; 256 | 257 | size_t halfedge_offset = f_offset + e; 258 | 259 | halfedge_table[std::make_pair(v0, v1)] = halfedge_offset; 260 | 261 | halfedge_buf[halfedge_offset].edge_index = int64_t(edge_index); 262 | halfedge_buf[halfedge_offset].face_index = int64_t(f); 263 | halfedge_buf[halfedge_offset].next_halfedge = 264 | int64_t(f_offset + (e + 1) % nv); 265 | 266 | if (size_t(v0) >= vertex_starting_halfedge_indices_buf.size()) { 267 | if (err) { 268 | (*err) = 269 | "Out-of-bounds access. v0 " + std::to_string(v0) + 270 | " must be less than " + 271 | std::to_string(vertex_starting_halfedge_indices_buf.size()) + 272 | "\n"; 273 | } 274 | return false; 275 | } 276 | 277 | if (vertex_starting_halfedge_indices_buf[size_t(v0)] == -1) { 278 | // Set as starting half-edge 279 | vertex_starting_halfedge_indices_buf[size_t(v0)] = 280 | int64_t(halfedge_offset); 281 | } 282 | } 283 | 284 | f_offset += nv; 285 | } 286 | } 287 | 288 | // dbg 289 | // for (size_t i = 0; i < halfedge_buf.size(); i++) { 290 | // std::cout << "halfedge_buf[" << i << "].edge_index = " << 291 | // halfedge_buf[i].edge_index << "\n"; 292 | //} 293 | 294 | // 295 | // 3. Find opposite half edges 296 | // 297 | for (size_t i = 0; i < halfedge_buf.size(); i++) { 298 | HalfEdge &halfedge = halfedge_buf[i]; 299 | if ((halfedge.edge_index == -1) || 300 | (halfedge.edge_index >= int64_t(edge_buf.size()))) { 301 | if (err) { 302 | (*err) = "Invalid edge_index " + std::to_string(halfedge.edge_index) + 303 | ". Must be >= 0 and < " + std::to_string(edge_buf.size()) + 304 | "\n"; 305 | } 306 | 307 | return false; 308 | } 309 | 310 | const Edge &edge = edge_buf[size_t(halfedge.edge_index)]; 311 | 312 | if (halfedge_table.count(std::make_pair(edge.v1, edge.v0)) && 313 | halfedge_table.count(std::make_pair(edge.v0, edge.v1))) { 314 | // Opposite halfedge exists. Make a link. 315 | 316 | size_t halfedge_index0 = 317 | halfedge_table.at(std::make_pair(edge.v0, edge.v1)); 318 | 319 | size_t halfedge_index1 = 320 | halfedge_table.at(std::make_pair(edge.v1, edge.v0)); 321 | 322 | if (halfedge_index0 == halfedge_index1) { 323 | if (err) { 324 | (*err) = "Invalid halfedge_index. Both indices has same value.\n"; 325 | } 326 | return false; 327 | } 328 | 329 | // Check if self-referencing. Choose different index compared to current 330 | // index(`i`). 331 | size_t opposite_halfedge_index = 332 | (halfedge_index0 == i) ? halfedge_index1 : halfedge_index0; 333 | 334 | if (opposite_halfedge_index >= halfedge_buf.size()) { 335 | if (err) { 336 | (*err) = "Invalid halfedge_index " + 337 | std::to_string(opposite_halfedge_index) + ". Must be < " + 338 | std::to_string(halfedge_buf.size()) + "\n"; 339 | } 340 | return false; 341 | } 342 | 343 | HalfEdge &opposite_halfedge = halfedge_buf[opposite_halfedge_index]; 344 | 345 | if (opposite_halfedge.edge_index != halfedge.edge_index) { 346 | if (err) { 347 | (*err) = "Edge id mismatch. opposite_halfedge.edge_index " + 348 | std::to_string(opposite_halfedge.edge_index) + 349 | " must be equal to halfedge.edge_index " + 350 | std::to_string(halfedge.edge_index) + "\n"; 351 | } 352 | return false; 353 | } 354 | 355 | // Make a link. 356 | halfedge.opposite_halfedge = int64_t(opposite_halfedge_index); 357 | } 358 | } 359 | 360 | (*edges) = edge_buf; 361 | (*halfedges) = halfedge_buf; 362 | (*vertex_starting_halfedge_indices) = vertex_starting_halfedge_indices_buf; 363 | 364 | #if 0 365 | // dbg 366 | std::cout << "halfedge_buf.size = " << halfedge_buf.size() << "\n"; 367 | 368 | for (size_t i = 0; i < halfedge_buf.size(); i++) { 369 | std::cout << "halfedge[" << i << "]. face = " << halfedge_buf[i].face_index 370 | << ", edge = " << halfedge_buf[i].edge_index 371 | << ", opposite he = " << halfedge_buf[i].opposite_halfedge 372 | << ", next he = " << halfedge_buf[i].next_halfedge << "\n"; 373 | } 374 | 375 | for (size_t i = 0; i < vertex_starting_halfedge_indices_buf.size(); i++) { 376 | std::cout << "v[" << i << "].halfedge_index = " << vertex_starting_halfedge_indices_buf[i] << "\n"; 377 | } 378 | #endif 379 | 380 | return true; 381 | } 382 | 383 | } // namespace tinymeshutils 384 | #endif // TINYMESHUTILS_HALF_EDGE_IMPLEMENTATION 385 | -------------------------------------------------------------------------------- /tests/acutest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Acutest -- Another C/C++ Unit Test facility 3 | * 4 | * 5 | * Copyright (c) 2013-2019 Martin Mitas 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a 8 | * copy of this software and associated documentation files (the "Software"), 9 | * to deal in the Software without restriction, including without limitation 10 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | * and/or sell copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | * IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef ACUTEST_H__ 27 | #define ACUTEST_H__ 28 | 29 | 30 | /************************ 31 | *** Public interface *** 32 | ************************/ 33 | 34 | /* By default, "acutest.h" provides the main program entry point (function 35 | * main()). However, if the test suite is composed of multiple source files 36 | * which include "acutest.h", then this causes a problem of multiple main() 37 | * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all 38 | * compilation units but one. 39 | */ 40 | 41 | /* Macro to specify list of unit tests in the suite. 42 | * The unit test implementation MUST provide list of unit tests it implements 43 | * with this macro: 44 | * 45 | * TEST_LIST = { 46 | * { "test1_name", test1_func_ptr }, 47 | * { "test2_name", test2_func_ptr }, 48 | * ... 49 | * { 0 } 50 | * }; 51 | * 52 | * The list specifies names of each test (must be unique) and pointer to 53 | * a function implementing it. The function does not take any arguments 54 | * and has no return values, i.e. every test function has to be compatible 55 | * with this prototype: 56 | * 57 | * void test_func(void); 58 | */ 59 | #define TEST_LIST const struct test__ test_list__[] 60 | 61 | 62 | /* Macros for testing whether an unit test succeeds or fails. These macros 63 | * can be used arbitrarily in functions implementing the unit tests. 64 | * 65 | * If any condition fails throughout execution of a test, the test fails. 66 | * 67 | * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows 68 | * also to specify an error message to print out if the condition fails. 69 | * (It expects printf-like format string and its parameters). The macros 70 | * return non-zero (condition passes) or 0 (condition fails). 71 | * 72 | * That can be useful when more conditions should be checked only if some 73 | * preceding condition passes, as illustrated in this code snippet: 74 | * 75 | * SomeStruct* ptr = allocate_some_struct(); 76 | * if(TEST_CHECK(ptr != NULL)) { 77 | * TEST_CHECK(ptr->member1 < 100); 78 | * TEST_CHECK(ptr->member2 > 200); 79 | * } 80 | */ 81 | #define TEST_CHECK_(cond,...) test_check__((cond), __FILE__, __LINE__, __VA_ARGS__) 82 | #define TEST_CHECK(cond) test_check__((cond), __FILE__, __LINE__, "%s", #cond) 83 | 84 | #ifdef __cplusplus 85 | /* Macros to verify that the code (the 1st argument) throws exception of given 86 | * type (the 2nd argument). (Note these macros are only available in C++.) 87 | * 88 | * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like 89 | * message. 90 | * 91 | * For example: 92 | * 93 | * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType); 94 | * 95 | * If the function_that_throw() throws ExpectedExceptionType, the check passes. 96 | * If the function throws anything incompatible with ExpectedExceptionType 97 | * (or if it does not thrown an exception at all), the check fails. 98 | */ 99 | #define TEST_EXCEPTION(code, exctype) \ 100 | do { \ 101 | bool exc_ok__ = false; \ 102 | const char *msg__ = NULL; \ 103 | try { \ 104 | code; \ 105 | msg__ = "No exception thrown."; \ 106 | } catch(exctype const&) { \ 107 | exc_ok__= true; \ 108 | } catch(...) { \ 109 | msg__ = "Unexpected exception thrown."; \ 110 | } \ 111 | test_check__(exc_ok__, __FILE__, __LINE__, #code " throws " #exctype); \ 112 | if(msg__ != NULL) \ 113 | test_message__("%s", msg__); \ 114 | } while(0) 115 | #define TEST_EXCEPTION_(code, exctype, ...) \ 116 | do { \ 117 | bool exc_ok__ = false; \ 118 | const char *msg__ = NULL; \ 119 | try { \ 120 | code; \ 121 | msg__ = "No exception thrown."; \ 122 | } catch(exctype const&) { \ 123 | exc_ok__= true; \ 124 | } catch(...) { \ 125 | msg__ = "Unexpected exception thrown."; \ 126 | } \ 127 | test_check__(exc_ok__, __FILE__, __LINE__, __VA_ARGS__); \ 128 | if(msg__ != NULL) \ 129 | test_message__("%s", msg__); \ 130 | } while(0) 131 | #endif /* #ifdef __cplusplus */ 132 | 133 | 134 | /* Sometimes it is useful to split execution of more complex unit tests to some 135 | * smaller parts and associate those parts with some names. 136 | * 137 | * This is especially handy if the given unit test is implemented as a loop 138 | * over some vector of multiple testing inputs. Using these macros allow to use 139 | * sort of subtitle for each iteration of the loop (e.g. outputting the input 140 | * itself or a name associated to it), so that if any TEST_CHECK condition 141 | * fails in the loop, it can be easily seen which iteration triggers the 142 | * failure, without the need to manually output the iteration-specific data in 143 | * every single TEST_CHECK inside the loop body. 144 | * 145 | * TEST_CASE allows to specify only single string as the name of the case, 146 | * TEST_CASE_ provides all the power of printf-like string formatting. 147 | * 148 | * Note that the test cases cannot be nested. Starting a new test case ends 149 | * implicitly the previous one. To end the test case explicitly (e.g. to end 150 | * the last test case after exiting the loop), you may use TEST_CASE(NULL). 151 | */ 152 | #define TEST_CASE_(...) test_case__(__VA_ARGS__) 153 | #define TEST_CASE(name) test_case__("%s", name); 154 | 155 | 156 | /* printf-like macro for outputting an extra information about a failure. 157 | * 158 | * Intended use is to output some computed output versus the expected value, 159 | * e.g. like this: 160 | * 161 | * if(!TEST_CHECK(produced == expected)) { 162 | * TEST_MSG("Expected: %d", expected); 163 | * TEST_MSG("Produced: %d", produced); 164 | * } 165 | * 166 | * Note the message is only written down if the most recent use of any checking 167 | * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed. 168 | * This means the above is equivalent to just this: 169 | * 170 | * TEST_CHECK(produced == expected); 171 | * TEST_MSG("Expected: %d", expected); 172 | * TEST_MSG("Produced: %d", produced); 173 | * 174 | * The macro can deal with multi-line output fairly well. It also automatically 175 | * adds a final new-line if there is none present. 176 | */ 177 | #define TEST_MSG(...) test_message__(__VA_ARGS__) 178 | 179 | 180 | /* Maximal output per TEST_MSG call. Longer messages are cut. 181 | * You may define another limit prior including "acutest.h" 182 | */ 183 | #ifndef TEST_MSG_MAXSIZE 184 | #define TEST_MSG_MAXSIZE 1024 185 | #endif 186 | 187 | 188 | /* Macro for dumping a block of memory. 189 | * 190 | * Its inteded use is very similar to what TEST_MSG is for, but instead of 191 | * generating any printf-like message, this is for dumping raw block of a 192 | * memory in a hexadecimal form: 193 | * 194 | * TEST_CHECK(size_produced == size_expected && memcmp(addr_produced, addr_expected, size_produced) == 0); 195 | * TEST_DUMP("Expected:", addr_expected, size_expected); 196 | * TEST_DUMP("Produced:", addr_produced, size_produced); 197 | */ 198 | #define TEST_DUMP(title, addr, size) test_dump__(title, addr, size) 199 | 200 | /* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut. 201 | * You may define another limit prior including "acutest.h" 202 | */ 203 | #ifndef TEST_DUMP_MAXSIZE 204 | #define TEST_DUMP_MAXSIZE 1024 205 | #endif 206 | 207 | 208 | /********************** 209 | *** Implementation *** 210 | **********************/ 211 | 212 | /* The unit test files should not rely on anything below. */ 213 | 214 | #include 215 | #include 216 | #include 217 | #include 218 | #include 219 | 220 | #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) 221 | #define ACUTEST_UNIX__ 1 222 | #include 223 | #include 224 | #include 225 | #include 226 | #include 227 | #include 228 | 229 | #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC 230 | #define ACUTEST_HAS_POSIX_TIMER__ 1 231 | #endif 232 | #endif 233 | 234 | #if defined(__gnu_linux__) 235 | #define ACUTEST_LINUX__ 1 236 | #include 237 | #include 238 | #endif 239 | 240 | #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) 241 | #define ACUTEST_WIN__ 1 242 | #include 243 | #include 244 | #endif 245 | 246 | #ifdef __cplusplus 247 | #include 248 | #endif 249 | 250 | 251 | /* Note our global private identifiers end with '__' to mitigate risk of clash 252 | * with the unit tests implementation. */ 253 | 254 | 255 | #ifdef __cplusplus 256 | extern "C" { 257 | #endif 258 | 259 | 260 | struct test__ { 261 | const char* name; 262 | void (*func)(void); 263 | }; 264 | 265 | extern const struct test__ test_list__[]; 266 | 267 | int test_check__(int cond, const char* file, int line, const char* fmt, ...); 268 | void test_case__(const char* fmt, ...); 269 | void test_message__(const char* fmt, ...); 270 | void test_dump__(const char* title, const void* addr, size_t size); 271 | 272 | 273 | #ifndef TEST_NO_MAIN 274 | 275 | static char* test_argv0__ = NULL; 276 | static size_t test_list_size__ = 0; 277 | static const struct test__** tests__ = NULL; 278 | static char* test_flags__ = NULL; 279 | static size_t test_count__ = 0; 280 | static int test_no_exec__ = -1; 281 | static int test_no_summary__ = 0; 282 | static int test_tap__ = 0; 283 | static int test_skip_mode__ = 0; 284 | static int test_worker__ = 0; 285 | static int test_worker_index__ = 0; 286 | static int test_cond_failed__ = 0; 287 | 288 | static int test_stat_failed_units__ = 0; 289 | static int test_stat_run_units__ = 0; 290 | 291 | static const struct test__* test_current_unit__ = NULL; 292 | static int test_current_index__ = 0; 293 | static char test_case_name__[64] = ""; 294 | static int test_current_already_logged__ = 0; 295 | static int test_case_current_already_logged__ = 0; 296 | static int test_verbose_level__ = 2; 297 | static int test_current_failures__ = 0; 298 | static int test_colorize__ = 0; 299 | static int test_timer__ = 0; 300 | 301 | #if defined ACUTEST_WIN__ 302 | static LARGE_INTEGER test_timer_freq__; 303 | static LARGE_INTEGER test_timer_start__; 304 | static LARGE_INTEGER test_timer_end__; 305 | 306 | static void 307 | test_timer_init__(void) 308 | { 309 | QueryPerformanceFrequency(&test_timer_freq__); 310 | } 311 | 312 | static void 313 | test_timer_get_time__(LARGE_INTEGER* ts) 314 | { 315 | QueryPerformanceCounter(ts); 316 | } 317 | 318 | static void 319 | test_timer_print_diff__(void) 320 | { 321 | double duration = test_timer_end__.QuadPart - test_timer_start__.QuadPart; 322 | duration /= test_timer_freq__.QuadPart; 323 | printf("%.6lf secs", duration); 324 | } 325 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 326 | static clockid_t test_timer_id__; 327 | struct timespec test_timer_start__; 328 | struct timespec test_timer_end__; 329 | 330 | static void 331 | test_timer_init__(void) 332 | { 333 | if(test_timer__ == 1) 334 | #ifdef CLOCK_MONOTONIC_RAW 335 | /* linux specific; not subject of NTP adjustements or adjtime() */ 336 | test_timer_id__ = CLOCK_MONOTONIC_RAW; 337 | #else 338 | test_timer_id__ = CLOCK_MONOTONIC; 339 | #endif 340 | else if(test_timer__ == 2) 341 | test_timer_id__ = CLOCK_PROCESS_CPUTIME_ID; 342 | } 343 | 344 | static void 345 | test_timer_get_time__(struct timespec* ts) 346 | { 347 | clock_gettime(test_timer_id__, ts); 348 | } 349 | 350 | static void 351 | test_timer_print_diff__(void) 352 | { 353 | double duration = ((double) test_timer_end__.tv_sec + 354 | (double) test_timer_end__.tv_nsec * 10e-9) 355 | - 356 | ((double) test_timer_start__.tv_sec + 357 | (double) test_timer_start__.tv_nsec * 10e-9); 358 | printf("%.6lf secs", duration); 359 | } 360 | #else 361 | static int test_timer_start__; 362 | static int test_timer_end__; 363 | 364 | void 365 | test_timer_init__(void) 366 | {} 367 | 368 | static void 369 | test_timer_get_time__(int* ts) 370 | { 371 | (void) ts; 372 | } 373 | 374 | static void 375 | test_timer_print_diff__(void) 376 | {} 377 | #endif 378 | 379 | #define TEST_COLOR_DEFAULT__ 0 380 | #define TEST_COLOR_GREEN__ 1 381 | #define TEST_COLOR_RED__ 2 382 | #define TEST_COLOR_DEFAULT_INTENSIVE__ 3 383 | #define TEST_COLOR_GREEN_INTENSIVE__ 4 384 | #define TEST_COLOR_RED_INTENSIVE__ 5 385 | 386 | static int 387 | test_print_in_color__(int color, const char* fmt, ...) 388 | { 389 | va_list args; 390 | char buffer[256]; 391 | int n; 392 | 393 | va_start(args, fmt); 394 | vsnprintf(buffer, sizeof(buffer), fmt, args); 395 | va_end(args); 396 | buffer[sizeof(buffer)-1] = '\0'; 397 | 398 | if(!test_colorize__) { 399 | return printf("%s", buffer); 400 | } 401 | 402 | #if defined ACUTEST_UNIX__ 403 | { 404 | const char* col_str; 405 | switch(color) { 406 | case TEST_COLOR_GREEN__: col_str = "\033[0;32m"; break; 407 | case TEST_COLOR_RED__: col_str = "\033[0;31m"; break; 408 | case TEST_COLOR_GREEN_INTENSIVE__: col_str = "\033[1;32m"; break; 409 | case TEST_COLOR_RED_INTENSIVE__: col_str = "\033[1;31m"; break; 410 | case TEST_COLOR_DEFAULT_INTENSIVE__: col_str = "\033[1m"; break; 411 | default: col_str = "\033[0m"; break; 412 | } 413 | printf("%s", col_str); 414 | n = printf("%s", buffer); 415 | printf("\033[0m"); 416 | return n; 417 | } 418 | #elif defined ACUTEST_WIN__ 419 | { 420 | HANDLE h; 421 | CONSOLE_SCREEN_BUFFER_INFO info; 422 | WORD attr; 423 | 424 | h = GetStdHandle(STD_OUTPUT_HANDLE); 425 | GetConsoleScreenBufferInfo(h, &info); 426 | 427 | switch(color) { 428 | case TEST_COLOR_GREEN__: attr = FOREGROUND_GREEN; break; 429 | case TEST_COLOR_RED__: attr = FOREGROUND_RED; break; 430 | case TEST_COLOR_GREEN_INTENSIVE__: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; 431 | case TEST_COLOR_RED_INTENSIVE__: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; 432 | case TEST_COLOR_DEFAULT_INTENSIVE__: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; 433 | default: attr = 0; break; 434 | } 435 | if(attr != 0) 436 | SetConsoleTextAttribute(h, attr); 437 | n = printf("%s", buffer); 438 | SetConsoleTextAttribute(h, info.wAttributes); 439 | return n; 440 | } 441 | #else 442 | n = printf("%s", buffer); 443 | return n; 444 | #endif 445 | } 446 | 447 | static void 448 | test_begin_test_line__(const struct test__* test) 449 | { 450 | if(!test_tap__) { 451 | if(test_verbose_level__ >= 3) { 452 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s:\n", test->name); 453 | test_current_already_logged__++; 454 | } else if(test_verbose_level__ >= 1) { 455 | int n; 456 | char spaces[48]; 457 | 458 | n = test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Test %s... ", test->name); 459 | memset(spaces, ' ', sizeof(spaces)); 460 | if(n < (int) sizeof(spaces)) 461 | printf("%.*s", (int) sizeof(spaces) - n, spaces); 462 | } else { 463 | test_current_already_logged__ = 1; 464 | } 465 | } 466 | } 467 | 468 | static void 469 | test_finish_test_line__(int result) 470 | { 471 | if(test_tap__) { 472 | const char* str = (result == 0) ? "ok" : "not ok"; 473 | 474 | printf("%s %u - %s\n", str, test_current_index__ + 1, test_current_unit__->name); 475 | 476 | if(result == 0 && test_timer__) { 477 | printf("# Duration: "); 478 | test_timer_print_diff__(); 479 | printf("\n"); 480 | } 481 | } else { 482 | int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE__ : TEST_COLOR_RED_INTENSIVE__; 483 | const char* str = (result == 0) ? "OK" : "FAILED"; 484 | printf("[ "); 485 | test_print_in_color__(color, str); 486 | printf(" ]"); 487 | 488 | if(result == 0 && test_timer__) { 489 | printf(" "); 490 | test_timer_print_diff__(); 491 | } 492 | 493 | printf("\n"); 494 | } 495 | } 496 | 497 | static void 498 | test_line_indent__(int level) 499 | { 500 | static const char spaces[] = " "; 501 | int n = level * 2; 502 | 503 | if(test_tap__ && n > 0) { 504 | n--; 505 | printf("#"); 506 | } 507 | 508 | while(n > 16) { 509 | printf("%s", spaces); 510 | n -= 16; 511 | } 512 | printf("%.*s", n, spaces); 513 | } 514 | 515 | int 516 | test_check__(int cond, const char* file, int line, const char* fmt, ...) 517 | { 518 | const char *result_str; 519 | int result_color; 520 | int verbose_level; 521 | 522 | if(cond) { 523 | result_str = "ok"; 524 | result_color = TEST_COLOR_GREEN__; 525 | verbose_level = 3; 526 | } else { 527 | if(!test_current_already_logged__ && test_current_unit__ != NULL) 528 | test_finish_test_line__(-1); 529 | 530 | result_str = "failed"; 531 | result_color = TEST_COLOR_RED__; 532 | verbose_level = 2; 533 | test_current_failures__++; 534 | test_current_already_logged__++; 535 | } 536 | 537 | if(test_verbose_level__ >= verbose_level) { 538 | va_list args; 539 | 540 | if(!test_case_current_already_logged__ && test_case_name__[0]) { 541 | test_line_indent__(1); 542 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 543 | test_current_already_logged__++; 544 | test_case_current_already_logged__++; 545 | } 546 | 547 | test_line_indent__(test_case_name__[0] ? 2 : 1); 548 | if(file != NULL) { 549 | if(test_verbose_level__ < 3) { 550 | #ifdef ACUTEST_WIN__ 551 | const char* lastsep1 = strrchr(file, '\\'); 552 | const char* lastsep2 = strrchr(file, '/'); 553 | if(lastsep1 == NULL) 554 | lastsep1 = file-1; 555 | if(lastsep2 == NULL) 556 | lastsep2 = file-1; 557 | file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; 558 | #else 559 | const char* lastsep = strrchr(file, '/'); 560 | if(lastsep != NULL) 561 | file = lastsep+1; 562 | #endif 563 | } 564 | printf("%s:%d: Check ", file, line); 565 | } 566 | 567 | va_start(args, fmt); 568 | vprintf(fmt, args); 569 | va_end(args); 570 | 571 | printf("... "); 572 | test_print_in_color__(result_color, result_str); 573 | printf("\n"); 574 | test_current_already_logged__++; 575 | } 576 | 577 | test_cond_failed__ = (cond == 0); 578 | return !test_cond_failed__; 579 | } 580 | 581 | void 582 | test_case__(const char* fmt, ...) 583 | { 584 | va_list args; 585 | 586 | if(test_verbose_level__ < 2) 587 | return; 588 | 589 | if(test_case_name__[0]) { 590 | test_case_current_already_logged__ = 0; 591 | test_case_name__[0] = '\0'; 592 | } 593 | 594 | if(fmt == NULL) 595 | return; 596 | 597 | va_start(args, fmt); 598 | vsnprintf(test_case_name__, sizeof(test_case_name__) - 1, fmt, args); 599 | va_end(args); 600 | test_case_name__[sizeof(test_case_name__) - 1] = '\0'; 601 | 602 | if(test_verbose_level__ >= 3) { 603 | test_line_indent__(1); 604 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Case %s:\n", test_case_name__); 605 | test_current_already_logged__++; 606 | test_case_current_already_logged__++; 607 | } 608 | } 609 | 610 | void 611 | test_message__(const char* fmt, ...) 612 | { 613 | char buffer[TEST_MSG_MAXSIZE]; 614 | char* line_beg; 615 | char* line_end; 616 | va_list args; 617 | 618 | if(test_verbose_level__ < 2) 619 | return; 620 | 621 | /* We allow extra message only when something is already wrong in the 622 | * current test. */ 623 | if(test_current_unit__ == NULL || !test_cond_failed__) 624 | return; 625 | 626 | va_start(args, fmt); 627 | vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); 628 | va_end(args); 629 | buffer[TEST_MSG_MAXSIZE-1] = '\0'; 630 | 631 | line_beg = buffer; 632 | while(1) { 633 | line_end = strchr(line_beg, '\n'); 634 | if(line_end == NULL) 635 | break; 636 | test_line_indent__(test_case_name__[0] ? 3 : 2); 637 | printf("%.*s\n", (int)(line_end - line_beg), line_beg); 638 | line_beg = line_end + 1; 639 | } 640 | if(line_beg[0] != '\0') { 641 | test_line_indent__(test_case_name__[0] ? 3 : 2); 642 | printf("%s\n", line_beg); 643 | } 644 | } 645 | 646 | void 647 | test_dump__(const char* title, const void* addr, size_t size) 648 | { 649 | static const size_t BYTES_PER_LINE = 16; 650 | size_t line_beg; 651 | size_t truncate = 0; 652 | 653 | if(test_verbose_level__ < 2) 654 | return; 655 | 656 | /* We allow extra message only when something is already wrong in the 657 | * current test. */ 658 | if(test_current_unit__ == NULL || !test_cond_failed__) 659 | return; 660 | 661 | if(size > TEST_DUMP_MAXSIZE) { 662 | truncate = size - TEST_DUMP_MAXSIZE; 663 | size = TEST_DUMP_MAXSIZE; 664 | } 665 | 666 | test_line_indent__(test_case_name__[0] ? 3 : 2); 667 | printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title); 668 | 669 | for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) { 670 | size_t line_end = line_beg + BYTES_PER_LINE; 671 | size_t off; 672 | 673 | test_line_indent__(test_case_name__[0] ? 4 : 3); 674 | printf("%08x: ", line_beg); 675 | for(off = line_beg; off < line_end; off++) { 676 | if(off < size) 677 | printf(" %02x", ((unsigned char*)addr)[off]); 678 | else 679 | printf(" "); 680 | } 681 | 682 | printf(" "); 683 | for(off = line_beg; off < line_end; off++) { 684 | unsigned char byte = ((unsigned char*)addr)[off]; 685 | if(off < size) 686 | printf("%c", (iscntrl(byte) ? '.' : byte)); 687 | else 688 | break; 689 | } 690 | 691 | printf("\n"); 692 | } 693 | 694 | if(truncate > 0) { 695 | test_line_indent__(test_case_name__[0] ? 4 : 3); 696 | printf(" ... (and more %u bytes)\n", (unsigned) truncate); 697 | } 698 | } 699 | 700 | static void 701 | test_list_names__(void) 702 | { 703 | const struct test__* test; 704 | 705 | printf("Unit tests:\n"); 706 | for(test = &test_list__[0]; test->func != NULL; test++) 707 | printf(" %s\n", test->name); 708 | } 709 | 710 | static void 711 | test_remember__(int i) 712 | { 713 | if(test_flags__[i]) 714 | return; 715 | else 716 | test_flags__[i] = 1; 717 | 718 | tests__[test_count__] = &test_list__[i]; 719 | test_count__++; 720 | } 721 | 722 | static int 723 | test_name_contains_word__(const char* name, const char* pattern) 724 | { 725 | static const char word_delim[] = " \t-_."; 726 | const char* substr; 727 | size_t pattern_len; 728 | int starts_on_word_boundary; 729 | int ends_on_word_boundary; 730 | 731 | pattern_len = strlen(pattern); 732 | 733 | substr = strstr(name, pattern); 734 | while(substr != NULL) { 735 | starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); 736 | ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); 737 | 738 | if(starts_on_word_boundary && ends_on_word_boundary) 739 | return 1; 740 | 741 | substr = strstr(substr+1, pattern); 742 | } 743 | 744 | return 0; 745 | } 746 | 747 | static int 748 | test_lookup__(const char* pattern) 749 | { 750 | int i; 751 | int n = 0; 752 | 753 | /* Try exact match. */ 754 | for(i = 0; i < (int) test_list_size__; i++) { 755 | if(strcmp(test_list__[i].name, pattern) == 0) { 756 | test_remember__(i); 757 | n++; 758 | break; 759 | } 760 | } 761 | if(n > 0) 762 | return n; 763 | 764 | /* Try word match. */ 765 | for(i = 0; i < (int) test_list_size__; i++) { 766 | if(test_name_contains_word__(test_list__[i].name, pattern)) { 767 | test_remember__(i); 768 | n++; 769 | } 770 | } 771 | if(n > 0) 772 | return n; 773 | 774 | /* Try relaxed match. */ 775 | for(i = 0; i < (int) test_list_size__; i++) { 776 | if(strstr(test_list__[i].name, pattern) != NULL) { 777 | test_remember__(i); 778 | n++; 779 | } 780 | } 781 | 782 | return n; 783 | } 784 | 785 | 786 | /* Called if anything goes bad in Acutest, or if the unit test ends in other 787 | * way then by normal returning from its function (e.g. exception or some 788 | * abnormal child process termination). */ 789 | static void 790 | test_error__(const char* fmt, ...) 791 | { 792 | va_list args; 793 | 794 | if(test_verbose_level__ == 0) 795 | return; 796 | 797 | if(test_verbose_level__ <= 2 && !test_current_already_logged__ && test_current_unit__ != NULL) { 798 | if(test_tap__) { 799 | test_finish_test_line__(-1); 800 | } else { 801 | printf("[ "); 802 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED"); 803 | printf(" ]\n"); 804 | } 805 | } 806 | 807 | if(test_verbose_level__ >= 2) { 808 | test_line_indent__(1); 809 | if(test_verbose_level__ >= 3) 810 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "ERROR: "); 811 | va_start(args, fmt); 812 | vprintf(fmt, args); 813 | va_end(args); 814 | printf("\n"); 815 | } 816 | 817 | if(test_verbose_level__ >= 3) { 818 | printf("\n"); 819 | } 820 | } 821 | 822 | /* Call directly the given test unit function. */ 823 | static int 824 | test_do_run__(const struct test__* test, int index) 825 | { 826 | test_current_unit__ = test; 827 | test_current_index__ = index; 828 | test_current_failures__ = 0; 829 | test_current_already_logged__ = 0; 830 | test_cond_failed__ = 0; 831 | 832 | test_timer_init__(); 833 | 834 | test_begin_test_line__(test); 835 | 836 | #ifdef __cplusplus 837 | try { 838 | #endif 839 | 840 | /* This is good to do for case the test unit e.g. crashes. */ 841 | fflush(stdout); 842 | fflush(stderr); 843 | 844 | test_timer_get_time__(&test_timer_start__); 845 | test->func(); 846 | test_timer_get_time__(&test_timer_end__); 847 | 848 | if(test_verbose_level__ >= 3) { 849 | test_line_indent__(1); 850 | if(test_current_failures__ == 0) { 851 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS: "); 852 | printf("All conditions have passed.\n"); 853 | 854 | if(test_timer__) { 855 | test_line_indent__(1); 856 | printf("Duration: "); 857 | test_timer_print_diff__(); 858 | printf("\n"); 859 | } 860 | } else { 861 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED: "); 862 | printf("%d condition%s %s failed.\n", 863 | test_current_failures__, 864 | (test_current_failures__ == 1) ? "" : "s", 865 | (test_current_failures__ == 1) ? "has" : "have"); 866 | } 867 | printf("\n"); 868 | } else if(test_verbose_level__ >= 1 && test_current_failures__ == 0) { 869 | test_finish_test_line__(0); 870 | } 871 | 872 | test_case__(NULL); 873 | test_current_unit__ = NULL; 874 | return (test_current_failures__ == 0) ? 0 : -1; 875 | 876 | #ifdef __cplusplus 877 | } catch(std::exception& e) { 878 | const char* what = e.what(); 879 | if(what != NULL) 880 | test_error__("Threw std::exception: %s", what); 881 | else 882 | test_error__("Threw std::exception"); 883 | return -1; 884 | } catch(...) { 885 | test_error__("Threw an exception"); 886 | return -1; 887 | } 888 | #endif 889 | } 890 | 891 | /* Trigger the unit test. If possible (and not suppressed) it starts a child 892 | * process who calls test_do_run__(), otherwise it calls test_do_run__() 893 | * directly. */ 894 | static void 895 | test_run__(const struct test__* test, int index) 896 | { 897 | int failed = 1; 898 | 899 | test_current_unit__ = test; 900 | test_current_already_logged__ = 0; 901 | 902 | if(!test_no_exec__) { 903 | 904 | #if defined(ACUTEST_UNIX__) 905 | 906 | pid_t pid; 907 | int exit_code; 908 | 909 | /* Make sure the child starts with empty I/O buffers. */ 910 | fflush(stdout); 911 | fflush(stderr); 912 | 913 | pid = fork(); 914 | if(pid == (pid_t)-1) { 915 | test_error__("Cannot fork. %s [%d]", strerror(errno), errno); 916 | failed = 1; 917 | } else if(pid == 0) { 918 | /* Child: Do the test. */ 919 | failed = (test_do_run__(test, index) != 0); 920 | exit(failed ? 1 : 0); 921 | } else { 922 | /* Parent: Wait until child terminates and analyze its exit code. */ 923 | waitpid(pid, &exit_code, 0); 924 | if(WIFEXITED(exit_code)) { 925 | switch(WEXITSTATUS(exit_code)) { 926 | case 0: failed = 0; break; /* test has passed. */ 927 | case 1: /* noop */ break; /* "normal" failure. */ 928 | default: test_error__("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); 929 | } 930 | } else if(WIFSIGNALED(exit_code)) { 931 | char tmp[32]; 932 | const char* signame; 933 | switch(WTERMSIG(exit_code)) { 934 | case SIGINT: signame = "SIGINT"; break; 935 | case SIGHUP: signame = "SIGHUP"; break; 936 | case SIGQUIT: signame = "SIGQUIT"; break; 937 | case SIGABRT: signame = "SIGABRT"; break; 938 | case SIGKILL: signame = "SIGKILL"; break; 939 | case SIGSEGV: signame = "SIGSEGV"; break; 940 | case SIGILL: signame = "SIGILL"; break; 941 | case SIGTERM: signame = "SIGTERM"; break; 942 | default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; 943 | } 944 | test_error__("Test interrupted by %s", signame); 945 | } else { 946 | test_error__("Test ended in an unexpected way [%d]", exit_code); 947 | } 948 | } 949 | 950 | #elif defined(ACUTEST_WIN__) 951 | 952 | char buffer[512] = {0}; 953 | STARTUPINFOA startupInfo; 954 | PROCESS_INFORMATION processInfo; 955 | DWORD exitCode; 956 | 957 | /* Windows has no fork(). So we propagate all info into the child 958 | * through a command line arguments. */ 959 | _snprintf(buffer, sizeof(buffer)-1, 960 | "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", 961 | test_argv0__, index, test_timer__ ? "--timer" : "", 962 | test_tap__ ? "--tap" : "", test_verbose_level__, 963 | test_colorize__ ? "always" : "never", 964 | test->name); 965 | memset(&startupInfo, 0, sizeof(startupInfo)); 966 | startupInfo.cb = sizeof(STARTUPINFO); 967 | if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { 968 | WaitForSingleObject(processInfo.hProcess, INFINITE); 969 | GetExitCodeProcess(processInfo.hProcess, &exitCode); 970 | CloseHandle(processInfo.hThread); 971 | CloseHandle(processInfo.hProcess); 972 | failed = (exitCode != 0); 973 | } else { 974 | test_error__("Cannot create unit test subprocess [%ld].", GetLastError()); 975 | failed = 1; 976 | } 977 | 978 | #else 979 | 980 | /* A platform where we don't know how to run child process. */ 981 | failed = (test_do_run__(test, index) != 0); 982 | 983 | #endif 984 | 985 | } else { 986 | /* Child processes suppressed through --no-exec. */ 987 | failed = (test_do_run__(test, index) != 0); 988 | } 989 | 990 | test_current_unit__ = NULL; 991 | 992 | test_stat_run_units__++; 993 | if(failed) 994 | test_stat_failed_units__++; 995 | } 996 | 997 | #if defined(ACUTEST_WIN__) 998 | /* Callback for SEH events. */ 999 | static LONG CALLBACK 1000 | test_exception_filter__(EXCEPTION_POINTERS *ptrs) 1001 | { 1002 | test_error__("Unhandled SEH exception %08lx at %p.", 1003 | ptrs->ExceptionRecord->ExceptionCode, 1004 | ptrs->ExceptionRecord->ExceptionAddress); 1005 | fflush(stdout); 1006 | fflush(stderr); 1007 | return EXCEPTION_EXECUTE_HANDLER; 1008 | } 1009 | #endif 1010 | 1011 | 1012 | #define TEST_CMDLINE_OPTFLAG_OPTIONALARG__ 0x0001 1013 | #define TEST_CMDLINE_OPTFLAG_REQUIREDARG__ 0x0002 1014 | 1015 | #define TEST_CMDLINE_OPTID_NONE__ 0 1016 | #define TEST_CMDLINE_OPTID_UNKNOWN__ (-0x7fffffff + 0) 1017 | #define TEST_CMDLINE_OPTID_MISSINGARG__ (-0x7fffffff + 1) 1018 | #define TEST_CMDLINE_OPTID_BOGUSARG__ (-0x7fffffff + 2) 1019 | 1020 | typedef struct TEST_CMDLINE_OPTION__ { 1021 | char shortname; 1022 | const char* longname; 1023 | int id; 1024 | unsigned flags; 1025 | } TEST_CMDLINE_OPTION__; 1026 | 1027 | static int 1028 | test_cmdline_handle_short_opt_group__(const TEST_CMDLINE_OPTION__* options, 1029 | const char* arggroup, 1030 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1031 | { 1032 | const TEST_CMDLINE_OPTION__* opt; 1033 | int i; 1034 | int ret = 0; 1035 | 1036 | for(i = 0; arggroup[i] != '\0'; i++) { 1037 | for(opt = options; opt->id != 0; opt++) { 1038 | if(arggroup[i] == opt->shortname) 1039 | break; 1040 | } 1041 | 1042 | if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 1043 | ret = callback(opt->id, NULL); 1044 | } else { 1045 | /* Unknown option. */ 1046 | char badoptname[3]; 1047 | badoptname[0] = '-'; 1048 | badoptname[1] = arggroup[i]; 1049 | badoptname[2] = '\0'; 1050 | ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG__ : TEST_CMDLINE_OPTID_UNKNOWN__), 1051 | badoptname); 1052 | } 1053 | 1054 | if(ret != 0) 1055 | break; 1056 | } 1057 | 1058 | return ret; 1059 | } 1060 | 1061 | #define TEST_CMDLINE_AUXBUF_SIZE__ 32 1062 | 1063 | static int 1064 | test_cmdline_read__(const TEST_CMDLINE_OPTION__* options, int argc, char** argv, 1065 | int (*callback)(int /*optval*/, const char* /*arg*/)) 1066 | { 1067 | 1068 | const TEST_CMDLINE_OPTION__* opt; 1069 | char auxbuf[TEST_CMDLINE_AUXBUF_SIZE__+1]; 1070 | int after_doubledash = 0; 1071 | int i = 1; 1072 | int ret = 0; 1073 | 1074 | auxbuf[TEST_CMDLINE_AUXBUF_SIZE__] = '\0'; 1075 | 1076 | while(i < argc) { 1077 | if(after_doubledash || strcmp(argv[i], "-") == 0) { 1078 | /* Non-option argument. */ 1079 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1080 | } else if(strcmp(argv[i], "--") == 0) { 1081 | /* End of options. All the remaining members are non-option arguments. */ 1082 | after_doubledash = 1; 1083 | } else if(argv[i][0] != '-') { 1084 | /* Non-option argument. */ 1085 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1086 | } else { 1087 | for(opt = options; opt->id != 0; opt++) { 1088 | if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { 1089 | size_t len = strlen(opt->longname); 1090 | if(strncmp(argv[i]+2, opt->longname, len) == 0) { 1091 | /* Regular long option. */ 1092 | if(argv[i][2+len] == '\0') { 1093 | /* with no argument provided. */ 1094 | if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) 1095 | ret = callback(opt->id, NULL); 1096 | else 1097 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 1098 | break; 1099 | } else if(argv[i][2+len] == '=') { 1100 | /* with an argument provided. */ 1101 | if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG__ | TEST_CMDLINE_OPTFLAG_REQUIREDARG__)) { 1102 | ret = callback(opt->id, argv[i]+2+len+1); 1103 | } else { 1104 | sprintf(auxbuf, "--%s", opt->longname); 1105 | ret = callback(TEST_CMDLINE_OPTID_BOGUSARG__, auxbuf); 1106 | } 1107 | break; 1108 | } else { 1109 | continue; 1110 | } 1111 | } 1112 | } else if(opt->shortname != '\0' && argv[i][0] == '-') { 1113 | if(argv[i][1] == opt->shortname) { 1114 | /* Regular short option. */ 1115 | if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG__) { 1116 | if(argv[i][2] != '\0') 1117 | ret = callback(opt->id, argv[i]+2); 1118 | else if(i+1 < argc) 1119 | ret = callback(opt->id, argv[++i]); 1120 | else 1121 | ret = callback(TEST_CMDLINE_OPTID_MISSINGARG__, argv[i]); 1122 | break; 1123 | } else { 1124 | ret = callback(opt->id, NULL); 1125 | 1126 | /* There might be more (argument-less) short options 1127 | * grouped together. */ 1128 | if(ret == 0 && argv[i][2] != '\0') 1129 | ret = test_cmdline_handle_short_opt_group__(options, argv[i]+2, callback); 1130 | break; 1131 | } 1132 | } 1133 | } 1134 | } 1135 | 1136 | if(opt->id == 0) { /* still not handled? */ 1137 | if(argv[i][0] != '-') { 1138 | /* Non-option argument. */ 1139 | ret = callback(TEST_CMDLINE_OPTID_NONE__, argv[i]); 1140 | } else { 1141 | /* Unknown option. */ 1142 | char* badoptname = argv[i]; 1143 | 1144 | if(strncmp(badoptname, "--", 2) == 0) { 1145 | /* Strip any argument from the long option. */ 1146 | char* assignement = strchr(badoptname, '='); 1147 | if(assignement != NULL) { 1148 | size_t len = assignement - badoptname; 1149 | if(len > TEST_CMDLINE_AUXBUF_SIZE__) 1150 | len = TEST_CMDLINE_AUXBUF_SIZE__; 1151 | strncpy(auxbuf, badoptname, len); 1152 | auxbuf[len] = '\0'; 1153 | badoptname = auxbuf; 1154 | } 1155 | } 1156 | 1157 | ret = callback(TEST_CMDLINE_OPTID_UNKNOWN__, badoptname); 1158 | } 1159 | } 1160 | } 1161 | 1162 | if(ret != 0) 1163 | return ret; 1164 | i++; 1165 | } 1166 | 1167 | return ret; 1168 | } 1169 | 1170 | static void 1171 | test_help__(void) 1172 | { 1173 | printf("Usage: %s [options] [test...]\n", test_argv0__); 1174 | printf("\n"); 1175 | printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); 1176 | printf("tests in the suite but those listed. By default, if no tests are specified\n"); 1177 | printf("on the command line, all unit tests in the suite are run.\n"); 1178 | printf("\n"); 1179 | printf("Options:\n"); 1180 | printf(" -s, --skip Execute all unit tests but the listed ones\n"); 1181 | printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); 1182 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1183 | #if defined ACUTEST_WIN__ 1184 | printf(" -t, --timer Measure test duration\n"); 1185 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1186 | printf(" -t, --timer Measure test duration (real time)\n"); 1187 | printf(" --timer=TIMER Measure test duration, using given timer\n"); 1188 | printf(" (TIMER is one of 'real', 'cpu')\n"); 1189 | #endif 1190 | printf(" -E, --no-exec Same as --exec=never\n"); 1191 | printf(" --no-summary Suppress printing of test results summary\n"); 1192 | printf(" --tap Produce TAP-compliant output\n"); 1193 | printf(" (See https://testanything.org/)\n"); 1194 | printf(" -l, --list List unit tests in the suite and exit\n"); 1195 | printf(" -v, --verbose Make output more verbose\n"); 1196 | printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); 1197 | printf(" 0 ... Be silent\n"); 1198 | printf(" 1 ... Output one line per test (and summary)\n"); 1199 | printf(" 2 ... As 1 and failed conditions (this is default)\n"); 1200 | printf(" 3 ... As 1 and all conditions (and extended summary)\n"); 1201 | printf(" --color[=WHEN] Enable colorized output\n"); 1202 | printf(" (WHEN is one of 'auto', 'always', 'never')\n"); 1203 | printf(" --no-color Same as --color=never\n"); 1204 | printf(" -h, --help Display this help and exit\n"); 1205 | 1206 | if(test_list_size__ < 16) { 1207 | printf("\n"); 1208 | test_list_names__(); 1209 | } 1210 | } 1211 | 1212 | static const TEST_CMDLINE_OPTION__ test_cmdline_options__[] = { 1213 | { 's', "skip", 's', 0 }, 1214 | { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1215 | { 'E', "no-exec", 'E', 0 }, 1216 | #if defined ACUTEST_WIN__ 1217 | { 't', "timer", 't', 0 }, 1218 | #elif defined ACUTEST_HAS_POSIX_TIMER__ 1219 | { 't', "timer", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1220 | #endif 1221 | { 0, "no-summary", 'S', 0 }, 1222 | { 0, "tap", 'T', 0 }, 1223 | { 'l', "list", 'l', 0 }, 1224 | { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1225 | { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG__ }, 1226 | { 0, "no-color", 'C', 0 }, 1227 | { 'h', "help", 'h', 0 }, 1228 | { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG__ }, /* internal */ 1229 | { 0, NULL, 0, 0 } 1230 | }; 1231 | 1232 | static int 1233 | test_cmdline_callback__(int id, const char* arg) 1234 | { 1235 | switch(id) { 1236 | case 's': 1237 | test_skip_mode__ = 1; 1238 | break; 1239 | 1240 | case 'e': 1241 | if(arg == NULL || strcmp(arg, "always") == 0) { 1242 | test_no_exec__ = 0; 1243 | } else if(strcmp(arg, "never") == 0) { 1244 | test_no_exec__ = 1; 1245 | } else if(strcmp(arg, "auto") == 0) { 1246 | /*noop*/ 1247 | } else { 1248 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0__, arg); 1249 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1250 | exit(2); 1251 | } 1252 | break; 1253 | 1254 | case 'E': 1255 | test_no_exec__ = 1; 1256 | break; 1257 | 1258 | case 't': 1259 | #if defined ACUTEST_WIN__ || defined ACUTEST_HAS_POSIX_TIMER__ 1260 | if(arg == NULL || strcmp(arg, "real") == 0) { 1261 | test_timer__ = 1; 1262 | #ifndef ACUTEST_WIN__ 1263 | } else if(strcmp(arg, "cpu") == 0) { 1264 | test_timer__ = 2; 1265 | #endif 1266 | } else { 1267 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --timer.\n", test_argv0__, arg); 1268 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1269 | exit(2); 1270 | } 1271 | #endif 1272 | break; 1273 | 1274 | case 'S': 1275 | test_no_summary__ = 1; 1276 | break; 1277 | 1278 | case 'T': 1279 | test_tap__ = 1; 1280 | break; 1281 | 1282 | case 'l': 1283 | test_list_names__(); 1284 | exit(0); 1285 | 1286 | case 'v': 1287 | test_verbose_level__ = (arg != NULL ? atoi(arg) : test_verbose_level__+1); 1288 | break; 1289 | 1290 | case 'c': 1291 | if(arg == NULL || strcmp(arg, "always") == 0) { 1292 | test_colorize__ = 1; 1293 | } else if(strcmp(arg, "never") == 0) { 1294 | test_colorize__ = 0; 1295 | } else if(strcmp(arg, "auto") == 0) { 1296 | /*noop*/ 1297 | } else { 1298 | fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0__, arg); 1299 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1300 | exit(2); 1301 | } 1302 | break; 1303 | 1304 | case 'C': 1305 | test_colorize__ = 0; 1306 | break; 1307 | 1308 | case 'h': 1309 | test_help__(); 1310 | exit(0); 1311 | 1312 | case 'w': 1313 | test_worker__ = 1; 1314 | test_worker_index__ = atoi(arg); 1315 | break; 1316 | 1317 | case 0: 1318 | if(test_lookup__(arg) == 0) { 1319 | fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0__, arg); 1320 | fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0__); 1321 | exit(2); 1322 | } 1323 | break; 1324 | 1325 | case TEST_CMDLINE_OPTID_UNKNOWN__: 1326 | fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); 1327 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1328 | exit(2); 1329 | 1330 | case TEST_CMDLINE_OPTID_MISSINGARG__: 1331 | fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); 1332 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1333 | exit(2); 1334 | 1335 | case TEST_CMDLINE_OPTID_BOGUSARG__: 1336 | fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); 1337 | fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0__); 1338 | exit(2); 1339 | } 1340 | 1341 | return 0; 1342 | } 1343 | 1344 | 1345 | #ifdef ACUTEST_LINUX__ 1346 | static int 1347 | test_is_tracer_present__(void) 1348 | { 1349 | char buf[256+32+1]; 1350 | int tracer_present = 0; 1351 | int fd; 1352 | ssize_t n_read; 1353 | 1354 | fd = open("/proc/self/status", O_RDONLY); 1355 | if(fd == -1) 1356 | return 0; 1357 | 1358 | n_read = read(fd, buf, sizeof(buf)-1); 1359 | while(n_read > 0) { 1360 | static const char pattern[] = "TracerPid:"; 1361 | const char* field; 1362 | 1363 | buf[n_read] = '\0'; 1364 | field = strstr(buf, pattern); 1365 | if(field != NULL && field < buf + sizeof(buf) - 32) { 1366 | pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); 1367 | tracer_present = (tracer_pid != 0); 1368 | break; 1369 | } 1370 | 1371 | if(n_read == sizeof(buf)-1) { 1372 | memmove(buf, buf + sizeof(buf)-1 - 32, 32); 1373 | n_read = read(fd, buf+32, sizeof(buf)-1-32); 1374 | if(n_read > 0) 1375 | n_read += 32; 1376 | } 1377 | } 1378 | 1379 | close(fd); 1380 | return tracer_present; 1381 | } 1382 | #endif 1383 | 1384 | int 1385 | main(int argc, char** argv) 1386 | { 1387 | int i; 1388 | test_argv0__ = argv[0]; 1389 | 1390 | #if defined ACUTEST_UNIX__ 1391 | test_colorize__ = isatty(STDOUT_FILENO); 1392 | #elif defined ACUTEST_WIN__ 1393 | #if defined __BORLANDC__ 1394 | test_colorize__ = isatty(_fileno(stdout)); 1395 | #else 1396 | test_colorize__ = _isatty(_fileno(stdout)); 1397 | #endif 1398 | #else 1399 | test_colorize__ = 0; 1400 | #endif 1401 | 1402 | /* Count all test units */ 1403 | test_list_size__ = 0; 1404 | for(i = 0; test_list__[i].func != NULL; i++) 1405 | test_list_size__++; 1406 | 1407 | tests__ = (const struct test__**) malloc(sizeof(const struct test__*) * test_list_size__); 1408 | test_flags__ = (char*) malloc(sizeof(char) * test_list_size__); 1409 | if(tests__ == NULL || test_flags__ == NULL) { 1410 | fprintf(stderr, "Out of memory.\n"); 1411 | exit(2); 1412 | } 1413 | memset((void*) test_flags__, 0, sizeof(char) * test_list_size__); 1414 | 1415 | /* Parse options */ 1416 | test_cmdline_read__(test_cmdline_options__, argc, argv, test_cmdline_callback__); 1417 | 1418 | #if defined(ACUTEST_WIN__) 1419 | SetUnhandledExceptionFilter(test_exception_filter__); 1420 | #endif 1421 | 1422 | /* By default, we want to run all tests. */ 1423 | if(test_count__ == 0) { 1424 | for(i = 0; test_list__[i].func != NULL; i++) 1425 | tests__[i] = &test_list__[i]; 1426 | test_count__ = test_list_size__; 1427 | } 1428 | 1429 | /* Guess whether we want to run unit tests as child processes. */ 1430 | if(test_no_exec__ < 0) { 1431 | test_no_exec__ = 0; 1432 | 1433 | if(test_count__ <= 1) { 1434 | test_no_exec__ = 1; 1435 | } else { 1436 | #ifdef ACUTEST_WIN__ 1437 | if(IsDebuggerPresent()) 1438 | test_no_exec__ = 1; 1439 | #endif 1440 | #ifdef ACUTEST_LINUX__ 1441 | if(test_is_tracer_present__()) 1442 | test_no_exec__ = 1; 1443 | #endif 1444 | } 1445 | } 1446 | 1447 | if(test_tap__) { 1448 | /* TAP requires we know test result ("ok", "not ok") before we output 1449 | * anything about the test, and this gets problematic for larger verbose 1450 | * levels. */ 1451 | if(test_verbose_level__ > 2) 1452 | test_verbose_level__ = 2; 1453 | 1454 | /* TAP harness should provide some summary. */ 1455 | test_no_summary__ = 1; 1456 | 1457 | if(!test_worker__) 1458 | printf("1..%d\n", (int) test_count__); 1459 | } 1460 | 1461 | /* Run the tests */ 1462 | if(!test_skip_mode__) { 1463 | /* Run the listed tests. */ 1464 | for(i = 0; i < (int) test_count__; i++) 1465 | test_run__(tests__[i], test_worker_index__ + i); 1466 | } else { 1467 | /* Run all tests except those listed. */ 1468 | int index = test_worker_index__; 1469 | for(i = 0; test_list__[i].func != NULL; i++) { 1470 | if(!test_flags__[i]) 1471 | test_run__(&test_list__[i], index++); 1472 | } 1473 | } 1474 | 1475 | /* Write a summary */ 1476 | if(!test_no_summary__ && test_verbose_level__ >= 1) { 1477 | if(test_verbose_level__ >= 3) { 1478 | test_print_in_color__(TEST_COLOR_DEFAULT_INTENSIVE__, "Summary:\n"); 1479 | 1480 | printf(" Count of all unit tests: %4d\n", (int) test_list_size__); 1481 | printf(" Count of run unit tests: %4d\n", test_stat_run_units__); 1482 | printf(" Count of failed unit tests: %4d\n", test_stat_failed_units__); 1483 | printf(" Count of skipped unit tests: %4d\n", (int) test_list_size__ - test_stat_run_units__); 1484 | } 1485 | 1486 | if(test_stat_failed_units__ == 0) { 1487 | test_print_in_color__(TEST_COLOR_GREEN_INTENSIVE__, "SUCCESS:"); 1488 | printf(" All unit tests have passed.\n"); 1489 | } else { 1490 | test_print_in_color__(TEST_COLOR_RED_INTENSIVE__, "FAILED:"); 1491 | printf(" %d of %d unit tests %s failed.\n", 1492 | test_stat_failed_units__, test_stat_run_units__, 1493 | (test_stat_failed_units__ == 1) ? "has" : "have"); 1494 | } 1495 | 1496 | if(test_verbose_level__ >= 3) 1497 | printf("\n"); 1498 | } 1499 | 1500 | free((void*) tests__); 1501 | free((void*) test_flags__); 1502 | 1503 | return (test_stat_failed_units__ == 0) ? 0 : 1; 1504 | } 1505 | 1506 | 1507 | #endif /* #ifndef TEST_NO_MAIN */ 1508 | 1509 | #ifdef __cplusplus 1510 | } /* extern "C" */ 1511 | #endif 1512 | 1513 | 1514 | #endif /* #ifndef ACUTEST_H__ */ 1515 | -------------------------------------------------------------------------------- /examples/common/tiny_obj_loader.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2012-2018 Syoyo Fujita and many contributors. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | // 26 | // version 2.0.0 : Add new object oriented API. 1.x API is still provided. 27 | // * Support line primitive. 28 | // * Support points primitive. 29 | // * Support multiple search path for .mtl(v1 API). 30 | // version 1.4.0 : Modifed ParseTextureNameAndOption API 31 | // version 1.3.1 : Make ParseTextureNameAndOption API public 32 | // version 1.3.0 : Separate warning and error message(breaking API of LoadObj) 33 | // version 1.2.3 : Added color space extension('-colorspace') to tex opts. 34 | // version 1.2.2 : Parse multiple group names. 35 | // version 1.2.1 : Added initial support for line('l') primitive(PR #178) 36 | // version 1.2.0 : Hardened implementation(#175) 37 | // version 1.1.1 : Support smoothing groups(#162) 38 | // version 1.1.0 : Support parsing vertex color(#144) 39 | // version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) 40 | // version 1.0.7 : Support multiple tex options(#126) 41 | // version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) 42 | // version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) 43 | // version 1.0.4 : Support multiple filenames for 'mtllib'(#112) 44 | // version 1.0.3 : Support parsing texture options(#85) 45 | // version 1.0.2 : Improve parsing speed by about a factor of 2 for large 46 | // files(#105) 47 | // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) 48 | // version 1.0.0 : Change data structure. Change license from BSD to MIT. 49 | // 50 | 51 | // 52 | // Use this in *one* .cc 53 | // #define TINYOBJLOADER_IMPLEMENTATION 54 | // #include "tiny_obj_loader.h" 55 | // 56 | 57 | #ifndef TINY_OBJ_LOADER_H_ 58 | #define TINY_OBJ_LOADER_H_ 59 | 60 | #include 61 | #include 62 | #include 63 | 64 | namespace tinyobj { 65 | 66 | #ifdef __clang__ 67 | #pragma clang diagnostic push 68 | #if __has_warning("-Wzero-as-null-pointer-constant") 69 | #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" 70 | #endif 71 | 72 | #pragma clang diagnostic ignored "-Wpadded" 73 | 74 | #endif 75 | 76 | // https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... 77 | // 78 | // -blendu on | off # set horizontal texture blending 79 | // (default on) 80 | // -blendv on | off # set vertical texture blending 81 | // (default on) 82 | // -boost real_value # boost mip-map sharpness 83 | // -mm base_value gain_value # modify texture map values (default 84 | // 0 1) 85 | // # base_value = brightness, 86 | // gain_value = contrast 87 | // -o u [v [w]] # Origin offset (default 88 | // 0 0 0) 89 | // -s u [v [w]] # Scale (default 90 | // 1 1 1) 91 | // -t u [v [w]] # Turbulence (default 92 | // 0 0 0) 93 | // -texres resolution # texture resolution to create 94 | // -clamp on | off # only render texels in the clamped 95 | // 0-1 range (default off) 96 | // # When unclamped, textures are 97 | // repeated across a surface, 98 | // # when clamped, only texels which 99 | // fall within the 0-1 100 | // # range are rendered. 101 | // -bm mult_value # bump multiplier (for bump maps 102 | // only) 103 | // 104 | // -imfchan r | g | b | m | l | z # specifies which channel of the file 105 | // is used to 106 | // # create a scalar or bump texture. 107 | // r:red, g:green, 108 | // # b:blue, m:matte, l:luminance, 109 | // z:z-depth.. 110 | // # (the default for bump is 'l' and 111 | // for decal is 'm') 112 | // bump -imfchan r bumpmap.tga # says to use the red channel of 113 | // bumpmap.tga as the bumpmap 114 | // 115 | // For reflection maps... 116 | // 117 | // -type sphere # specifies a sphere for a "refl" 118 | // reflection map 119 | // -type cube_top | cube_bottom | # when using a cube map, the texture 120 | // file for each 121 | // cube_front | cube_back | # side of the cube is specified 122 | // separately 123 | // cube_left | cube_right 124 | // 125 | // TinyObjLoader extension. 126 | // 127 | // -colorspace SPACE # Color space of the texture. e.g. 128 | // 'sRGB` or 'linear' 129 | // 130 | 131 | #ifdef TINYOBJLOADER_USE_DOUBLE 132 | //#pragma message "using double" 133 | typedef double real_t; 134 | #else 135 | //#pragma message "using float" 136 | typedef float real_t; 137 | #endif 138 | 139 | typedef enum { 140 | TEXTURE_TYPE_NONE, // default 141 | TEXTURE_TYPE_SPHERE, 142 | TEXTURE_TYPE_CUBE_TOP, 143 | TEXTURE_TYPE_CUBE_BOTTOM, 144 | TEXTURE_TYPE_CUBE_FRONT, 145 | TEXTURE_TYPE_CUBE_BACK, 146 | TEXTURE_TYPE_CUBE_LEFT, 147 | TEXTURE_TYPE_CUBE_RIGHT 148 | } texture_type_t; 149 | 150 | typedef struct { 151 | texture_type_t type; // -type (default TEXTURE_TYPE_NONE) 152 | real_t sharpness; // -boost (default 1.0?) 153 | real_t brightness; // base_value in -mm option (default 0) 154 | real_t contrast; // gain_value in -mm option (default 1) 155 | real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) 156 | real_t scale[3]; // -s u [v [w]] (default 1 1 1) 157 | real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) 158 | int texture_resolution; // -texres resolution (No default value in the spec. We'll use -1) 159 | bool clamp; // -clamp (default false) 160 | char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') 161 | bool blendu; // -blendu (default on) 162 | bool blendv; // -blendv (default on) 163 | real_t bump_multiplier; // -bm (for bump maps only, default 1.0) 164 | 165 | // extension 166 | std::string colorspace; // Explicitly specify color space of stored texel 167 | // value. Usually `sRGB` or `linear` (default empty). 168 | } texture_option_t; 169 | 170 | typedef struct _material_t { 171 | std::string name; 172 | 173 | real_t ambient[3]; 174 | real_t diffuse[3]; 175 | real_t specular[3]; 176 | real_t transmittance[3]; 177 | real_t emission[3]; 178 | real_t shininess; 179 | real_t ior; // index of refraction 180 | real_t dissolve; // 1 == opaque; 0 == fully transparent 181 | // illumination model (see http://www.fileformat.info/format/material/) 182 | int illum; 183 | 184 | int dummy; // Suppress padding warning. 185 | 186 | std::string ambient_texname; // map_Ka 187 | std::string diffuse_texname; // map_Kd 188 | std::string specular_texname; // map_Ks 189 | std::string specular_highlight_texname; // map_Ns 190 | std::string bump_texname; // map_bump, map_Bump, bump 191 | std::string displacement_texname; // disp 192 | std::string alpha_texname; // map_d 193 | std::string reflection_texname; // refl 194 | 195 | texture_option_t ambient_texopt; 196 | texture_option_t diffuse_texopt; 197 | texture_option_t specular_texopt; 198 | texture_option_t specular_highlight_texopt; 199 | texture_option_t bump_texopt; 200 | texture_option_t displacement_texopt; 201 | texture_option_t alpha_texopt; 202 | texture_option_t reflection_texopt; 203 | 204 | // PBR extension 205 | // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr 206 | real_t roughness; // [0, 1] default 0 207 | real_t metallic; // [0, 1] default 0 208 | real_t sheen; // [0, 1] default 0 209 | real_t clearcoat_thickness; // [0, 1] default 0 210 | real_t clearcoat_roughness; // [0, 1] default 0 211 | real_t anisotropy; // aniso. [0, 1] default 0 212 | real_t anisotropy_rotation; // anisor. [0, 1] default 0 213 | real_t pad0; 214 | std::string roughness_texname; // map_Pr 215 | std::string metallic_texname; // map_Pm 216 | std::string sheen_texname; // map_Ps 217 | std::string emissive_texname; // map_Ke 218 | std::string normal_texname; // norm. For normal mapping. 219 | 220 | texture_option_t roughness_texopt; 221 | texture_option_t metallic_texopt; 222 | texture_option_t sheen_texopt; 223 | texture_option_t emissive_texopt; 224 | texture_option_t normal_texopt; 225 | 226 | int pad2; 227 | 228 | std::map unknown_parameter; 229 | 230 | #ifdef TINY_OBJ_LOADER_PYTHON_BINDING 231 | // For pybind11 232 | std::array GetDiffuse() { 233 | std::array values; 234 | values[0] = double(diffuse[0]); 235 | values[1] = double(diffuse[1]); 236 | values[2] = double(diffuse[2]); 237 | 238 | return values; 239 | } 240 | 241 | std::array GetSpecular() { 242 | std::array values; 243 | values[0] = double(specular[0]); 244 | values[1] = double(specular[1]); 245 | values[2] = double(specular[2]); 246 | 247 | return values; 248 | } 249 | 250 | std::array GetTransmittance() { 251 | std::array values; 252 | values[0] = double(transmittance[0]); 253 | values[1] = double(transmittance[1]); 254 | values[2] = double(transmittance[2]); 255 | 256 | return values; 257 | } 258 | 259 | std::array GetEmission() { 260 | std::array values; 261 | values[0] = double(emission[0]); 262 | values[1] = double(emission[1]); 263 | values[2] = double(emission[2]); 264 | 265 | return values; 266 | } 267 | 268 | std::array GetAmbient() { 269 | std::array values; 270 | values[0] = double(ambient[0]); 271 | values[1] = double(ambient[1]); 272 | values[2] = double(ambient[2]); 273 | 274 | return values; 275 | } 276 | 277 | void SetDiffuse(std::array &a) { 278 | diffuse[0] = real_t(a[0]); 279 | diffuse[1] = real_t(a[1]); 280 | diffuse[2] = real_t(a[2]); 281 | } 282 | 283 | void SetAmbient(std::array &a) { 284 | ambient[0] = real_t(a[0]); 285 | ambient[1] = real_t(a[1]); 286 | ambient[2] = real_t(a[2]); 287 | } 288 | 289 | void SetSpecular(std::array &a) { 290 | specular[0] = real_t(a[0]); 291 | specular[1] = real_t(a[1]); 292 | specular[2] = real_t(a[2]); 293 | } 294 | 295 | void SetTransmittance(std::array &a) { 296 | transmittance[0] = real_t(a[0]); 297 | transmittance[1] = real_t(a[1]); 298 | transmittance[2] = real_t(a[2]); 299 | } 300 | 301 | std::string GetCustomParameter(const std::string &key) { 302 | std::map::const_iterator it = 303 | unknown_parameter.find(key); 304 | 305 | if (it != unknown_parameter.end()) { 306 | return it->second; 307 | } 308 | return std::string(); 309 | } 310 | 311 | #endif 312 | 313 | } material_t; 314 | 315 | typedef struct { 316 | std::string name; 317 | 318 | std::vector intValues; 319 | std::vector floatValues; 320 | std::vector stringValues; 321 | } tag_t; 322 | 323 | // Index struct to support different indices for vtx/normal/texcoord. 324 | // -1 means not used. 325 | typedef struct { 326 | int vertex_index; 327 | int normal_index; 328 | int texcoord_index; 329 | } index_t; 330 | 331 | typedef struct { 332 | std::vector indices; 333 | std::vector 334 | num_face_vertices; // The number of vertices per 335 | // face. 3 = triangle, 4 = quad, 336 | // ... Up to 255 vertices per face. 337 | std::vector material_ids; // per-face material ID 338 | std::vector smoothing_group_ids; // per-face smoothing group 339 | // ID(0 = off. positive value 340 | // = group id) 341 | std::vector tags; // SubD tag 342 | } mesh_t; 343 | 344 | // typedef struct { 345 | // std::vector indices; // pairs of indices for lines 346 | //} path_t; 347 | 348 | typedef struct { 349 | // Linear flattened indices. 350 | std::vector indices; // indices for vertices(poly lines) 351 | std::vector num_line_vertices; // The number of vertices per line. 352 | } lines_t; 353 | 354 | typedef struct { 355 | std::vector indices; // indices for points 356 | } points_t; 357 | 358 | typedef struct { 359 | std::string name; 360 | mesh_t mesh; 361 | lines_t lines; 362 | points_t points; 363 | } shape_t; 364 | 365 | // Vertex attributes 366 | struct attrib_t { 367 | std::vector vertices; // 'v'(xyz) 368 | 369 | // For backward compatibility, we store vertex weight in separate array. 370 | std::vector vertex_weights; // 'v'(w) 371 | std::vector normals; // 'vn' 372 | std::vector texcoords; // 'vt'(uv) 373 | 374 | // For backward compatibility, we store texture coordinate 'w' in separate 375 | // array. 376 | std::vector texcoord_ws; // 'vt'(w) 377 | std::vector colors; // extension: vertex colors 378 | 379 | attrib_t() {} 380 | 381 | // 382 | // For pybind11 383 | // 384 | const std::vector &GetVertices() const { return vertices; } 385 | 386 | const std::vector &GetVertexWeights() const { return vertex_weights; } 387 | }; 388 | 389 | typedef struct callback_t_ { 390 | // W is optional and set to 1 if there is no `w` item in `v` line 391 | void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); 392 | void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); 393 | 394 | // y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in 395 | // `vt` line. 396 | void (*texcoord_cb)(void *user_data, real_t x, real_t y, real_t z); 397 | 398 | // called per 'f' line. num_indices is the number of face indices(e.g. 3 for 399 | // triangle, 4 for quad) 400 | // 0 will be passed for undefined index in index_t members. 401 | void (*index_cb)(void *user_data, index_t *indices, int num_indices); 402 | // `name` material name, `material_id` = the array index of material_t[]. -1 403 | // if 404 | // a material not found in .mtl 405 | void (*usemtl_cb)(void *user_data, const char *name, int material_id); 406 | // `materials` = parsed material data. 407 | void (*mtllib_cb)(void *user_data, const material_t *materials, 408 | int num_materials); 409 | // There may be multiple group names 410 | void (*group_cb)(void *user_data, const char **names, int num_names); 411 | void (*object_cb)(void *user_data, const char *name); 412 | 413 | callback_t_() 414 | : vertex_cb(NULL), 415 | normal_cb(NULL), 416 | texcoord_cb(NULL), 417 | index_cb(NULL), 418 | usemtl_cb(NULL), 419 | mtllib_cb(NULL), 420 | group_cb(NULL), 421 | object_cb(NULL) {} 422 | } callback_t; 423 | 424 | class MaterialReader { 425 | public: 426 | MaterialReader() {} 427 | virtual ~MaterialReader(); 428 | 429 | virtual bool operator()(const std::string &matId, 430 | std::vector *materials, 431 | std::map *matMap, std::string *warn, 432 | std::string *err) = 0; 433 | }; 434 | 435 | /// 436 | /// Read .mtl from a file. 437 | /// 438 | class MaterialFileReader : public MaterialReader { 439 | public: 440 | // Path could contain separator(';' in Windows, ':' in Posix) 441 | explicit MaterialFileReader(const std::string &mtl_basedir) 442 | : m_mtlBaseDir(mtl_basedir) {} 443 | virtual ~MaterialFileReader() {} 444 | virtual bool operator()(const std::string &matId, 445 | std::vector *materials, 446 | std::map *matMap, std::string *warn, 447 | std::string *err); 448 | 449 | private: 450 | std::string m_mtlBaseDir; 451 | }; 452 | 453 | /// 454 | /// Read .mtl from a stream. 455 | /// 456 | class MaterialStreamReader : public MaterialReader { 457 | public: 458 | explicit MaterialStreamReader(std::istream &inStream) 459 | : m_inStream(inStream) {} 460 | virtual ~MaterialStreamReader() {} 461 | virtual bool operator()(const std::string &matId, 462 | std::vector *materials, 463 | std::map *matMap, std::string *warn, 464 | std::string *err); 465 | 466 | private: 467 | std::istream &m_inStream; 468 | }; 469 | 470 | // v2 API 471 | struct ObjReaderConfig { 472 | bool triangulate; // triangulate polygon? 473 | 474 | /// Parse vertex color. 475 | /// If vertex color is not present, its filled with default value. 476 | /// false = no vertex color 477 | /// This will increase memory of parsed .obj 478 | bool vertex_color; 479 | 480 | /// 481 | /// Search path to .mtl file. 482 | /// Default = "" = search from the same directory of .obj file. 483 | /// Valid only when loading .obj from a file. 484 | /// 485 | std::string mtl_search_path; 486 | 487 | ObjReaderConfig() : triangulate(true), vertex_color(true) {} 488 | }; 489 | 490 | /// 491 | /// Wavefront .obj reader class(v2 API) 492 | /// 493 | class ObjReader { 494 | public: 495 | ObjReader() : valid_(false) {} 496 | ~ObjReader() {} 497 | 498 | /// 499 | /// Load .obj and .mtl from a file. 500 | /// 501 | /// @param[in] filename wavefront .obj filename 502 | /// @param[in] config Reader configuration 503 | /// 504 | bool ParseFromFile(const std::string &filename, 505 | const ObjReaderConfig &config = ObjReaderConfig()); 506 | 507 | /// 508 | /// Parse .obj from a text string. 509 | /// Need to supply .mtl text string by `mtl_text`. 510 | /// This function ignores `mtllib` line in .obj text. 511 | /// 512 | /// @param[in] obj_text wavefront .obj filename 513 | /// @param[in] mtl_text wavefront .mtl filename 514 | /// @param[in] config Reader configuration 515 | /// 516 | bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, 517 | const ObjReaderConfig &config = ObjReaderConfig()); 518 | 519 | /// 520 | /// .obj was loaded or parsed correctly. 521 | /// 522 | bool Valid() const { return valid_; } 523 | 524 | const attrib_t &GetAttrib() const { return attrib_; } 525 | 526 | const std::vector &GetShapes() const { return shapes_; } 527 | 528 | const std::vector &GetMaterials() const { return materials_; } 529 | 530 | /// 531 | /// Warning message(may be filled after `Load` or `Parse`) 532 | /// 533 | const std::string &Warning() const { return warning_; } 534 | 535 | /// 536 | /// Error message(filled when `Load` or `Parse` failed) 537 | /// 538 | const std::string &Error() const { return error_; } 539 | 540 | private: 541 | bool valid_; 542 | 543 | attrib_t attrib_; 544 | std::vector shapes_; 545 | std::vector materials_; 546 | 547 | std::string warning_; 548 | std::string error_; 549 | }; 550 | 551 | /// ==>>========= Legacy v1 API ============================================= 552 | 553 | /// Loads .obj from a file. 554 | /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data 555 | /// 'shapes' will be filled with parsed shape data 556 | /// Returns true when loading .obj become success. 557 | /// Returns warning message into `warn`, and error message into `err` 558 | /// 'mtl_basedir' is optional, and used for base directory for .mtl file. 559 | /// In default(`NULL'), .mtl file is searched from an application's working 560 | /// directory. 561 | /// 'triangulate' is optional, and used whether triangulate polygon face in .obj 562 | /// or not. 563 | /// Option 'default_vcols_fallback' specifies whether vertex colors should 564 | /// always be defined, even if no colors are given (fallback to white). 565 | bool LoadObj(attrib_t *attrib, std::vector *shapes, 566 | std::vector *materials, std::string *warn, 567 | std::string *err, const char *filename, 568 | const char *mtl_basedir = NULL, bool triangulate = true, 569 | bool default_vcols_fallback = true); 570 | 571 | /// Loads .obj from a file with custom user callback. 572 | /// .mtl is loaded as usual and parsed material_t data will be passed to 573 | /// `callback.mtllib_cb`. 574 | /// Returns true when loading .obj/.mtl become success. 575 | /// Returns warning message into `warn`, and error message into `err` 576 | /// See `examples/callback_api/` for how to use this function. 577 | bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, 578 | void *user_data = NULL, 579 | MaterialReader *readMatFn = NULL, 580 | std::string *warn = NULL, std::string *err = NULL); 581 | 582 | /// Loads object from a std::istream, uses `readMatFn` to retrieve 583 | /// std::istream for materials. 584 | /// Returns true when loading .obj become success. 585 | /// Returns warning and error message into `err` 586 | bool LoadObj(attrib_t *attrib, std::vector *shapes, 587 | std::vector *materials, std::string *warn, 588 | std::string *err, std::istream *inStream, 589 | MaterialReader *readMatFn = NULL, bool triangulate = true, 590 | bool default_vcols_fallback = true); 591 | 592 | /// Loads materials into std::map 593 | void LoadMtl(std::map *material_map, 594 | std::vector *materials, std::istream *inStream, 595 | std::string *warning, std::string *err); 596 | 597 | /// 598 | /// Parse texture name and texture option for custom texture parameter through 599 | /// material::unknown_parameter 600 | /// 601 | /// @param[out] texname Parsed texture name 602 | /// @param[out] texopt Parsed texopt 603 | /// @param[in] linebuf Input string 604 | /// 605 | bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, 606 | const char *linebuf); 607 | 608 | /// =<<========== Legacy v1 API ============================================= 609 | 610 | } // namespace tinyobj 611 | 612 | #endif // TINY_OBJ_LOADER_H_ 613 | 614 | #ifdef TINYOBJLOADER_IMPLEMENTATION 615 | #include 616 | #include 617 | #include 618 | #include 619 | #include 620 | #include 621 | #include 622 | #include 623 | 624 | #include 625 | #include 626 | 627 | namespace tinyobj { 628 | 629 | MaterialReader::~MaterialReader() {} 630 | 631 | struct vertex_index_t { 632 | int v_idx, vt_idx, vn_idx; 633 | vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} 634 | explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} 635 | vertex_index_t(int vidx, int vtidx, int vnidx) 636 | : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} 637 | }; 638 | 639 | // Internal data structure for face representation 640 | // index + smoothing group. 641 | struct face_t { 642 | unsigned int 643 | smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. 644 | int pad_; 645 | std::vector vertex_indices; // face vertex indices. 646 | 647 | face_t() : smoothing_group_id(0), pad_(0) {} 648 | }; 649 | 650 | // Internal data structure for line representation 651 | struct __line_t { 652 | // l v1/vt1 v2/vt2 ... 653 | // In the specification, line primitrive does not have normal index, but 654 | // TinyObjLoader allow it 655 | std::vector vertex_indices; 656 | }; 657 | 658 | // Internal data structure for points representation 659 | struct __points_t { 660 | // p v1 v2 ... 661 | // In the specification, point primitrive does not have normal index and 662 | // texture coord index, but TinyObjLoader allow it. 663 | std::vector vertex_indices; 664 | }; 665 | 666 | struct tag_sizes { 667 | tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} 668 | int num_ints; 669 | int num_reals; 670 | int num_strings; 671 | }; 672 | 673 | struct obj_shape { 674 | std::vector v; 675 | std::vector vn; 676 | std::vector vt; 677 | }; 678 | 679 | // 680 | // Manages group of primitives(face, line, points, ...) 681 | struct PrimGroup { 682 | std::vector faceGroup; 683 | std::vector<__line_t> lineGroup; 684 | std::vector<__points_t> pointsGroup; 685 | 686 | void clear() { 687 | faceGroup.clear(); 688 | lineGroup.clear(); 689 | pointsGroup.clear(); 690 | } 691 | 692 | bool IsEmpty() const { 693 | return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); 694 | } 695 | 696 | // TODO(syoyo): bspline, surface, ... 697 | }; 698 | 699 | // See 700 | // http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf 701 | static std::istream &safeGetline(std::istream &is, std::string &t) { 702 | t.clear(); 703 | 704 | // The characters in the stream are read one-by-one using a std::streambuf. 705 | // That is faster than reading them one-by-one using the std::istream. 706 | // Code that uses streambuf this way must be guarded by a sentry object. 707 | // The sentry object performs various tasks, 708 | // such as thread synchronization and updating the stream state. 709 | 710 | std::istream::sentry se(is, true); 711 | std::streambuf *sb = is.rdbuf(); 712 | 713 | if (se) { 714 | for (;;) { 715 | int c = sb->sbumpc(); 716 | switch (c) { 717 | case '\n': 718 | return is; 719 | case '\r': 720 | if (sb->sgetc() == '\n') sb->sbumpc(); 721 | return is; 722 | case EOF: 723 | // Also handle the case when the last line has no line ending 724 | if (t.empty()) is.setstate(std::ios::eofbit); 725 | return is; 726 | default: 727 | t += static_cast(c); 728 | } 729 | } 730 | } 731 | 732 | return is; 733 | } 734 | 735 | #define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) 736 | #define IS_DIGIT(x) \ 737 | (static_cast((x) - '0') < static_cast(10)) 738 | #define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) 739 | 740 | // Make index zero-base, and also support relative index. 741 | static inline bool fixIndex(int idx, int n, int *ret) { 742 | if (!ret) { 743 | return false; 744 | } 745 | 746 | if (idx > 0) { 747 | (*ret) = idx - 1; 748 | return true; 749 | } 750 | 751 | if (idx == 0) { 752 | // zero is not allowed according to the spec. 753 | return false; 754 | } 755 | 756 | if (idx < 0) { 757 | (*ret) = n + idx; // negative value = relative 758 | return true; 759 | } 760 | 761 | return false; // never reach here. 762 | } 763 | 764 | static inline std::string parseString(const char **token) { 765 | std::string s; 766 | (*token) += strspn((*token), " \t"); 767 | size_t e = strcspn((*token), " \t\r"); 768 | s = std::string((*token), &(*token)[e]); 769 | (*token) += e; 770 | return s; 771 | } 772 | 773 | static inline int parseInt(const char **token) { 774 | (*token) += strspn((*token), " \t"); 775 | int i = atoi((*token)); 776 | (*token) += strcspn((*token), " \t\r"); 777 | return i; 778 | } 779 | 780 | // Tries to parse a floating point number located at s. 781 | // 782 | // s_end should be a location in the string where reading should absolutely 783 | // stop. For example at the end of the string, to prevent buffer overflows. 784 | // 785 | // Parses the following EBNF grammar: 786 | // sign = "+" | "-" ; 787 | // END = ? anything not in digit ? 788 | // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 789 | // integer = [sign] , digit , {digit} ; 790 | // decimal = integer , ["." , integer] ; 791 | // float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; 792 | // 793 | // Valid strings are for example: 794 | // -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 795 | // 796 | // If the parsing is a success, result is set to the parsed value and true 797 | // is returned. 798 | // 799 | // The function is greedy and will parse until any of the following happens: 800 | // - a non-conforming character is encountered. 801 | // - s_end is reached. 802 | // 803 | // The following situations triggers a failure: 804 | // - s >= s_end. 805 | // - parse failure. 806 | // 807 | static bool tryParseDouble(const char *s, const char *s_end, double *result) { 808 | if (s >= s_end) { 809 | return false; 810 | } 811 | 812 | double mantissa = 0.0; 813 | // This exponent is base 2 rather than 10. 814 | // However the exponent we parse is supposed to be one of ten, 815 | // thus we must take care to convert the exponent/and or the 816 | // mantissa to a * 2^E, where a is the mantissa and E is the 817 | // exponent. 818 | // To get the final double we will use ldexp, it requires the 819 | // exponent to be in base 2. 820 | int exponent = 0; 821 | 822 | // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED 823 | // TO JUMP OVER DEFINITIONS. 824 | char sign = '+'; 825 | char exp_sign = '+'; 826 | char const *curr = s; 827 | 828 | // How many characters were read in a loop. 829 | int read = 0; 830 | // Tells whether a loop terminated due to reaching s_end. 831 | bool end_not_reached = false; 832 | bool leading_decimal_dots = false; 833 | 834 | /* 835 | BEGIN PARSING. 836 | */ 837 | 838 | // Find out what sign we've got. 839 | if (*curr == '+' || *curr == '-') { 840 | sign = *curr; 841 | curr++; 842 | if ((curr != s_end) && (*curr == '.')) { 843 | // accept. Somethig like `.7e+2`, `-.5234` 844 | leading_decimal_dots = true; 845 | } 846 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 847 | } else if (*curr == '.') { 848 | // accept. Somethig like `.7e+2`, `-.5234` 849 | leading_decimal_dots = true; 850 | } else { 851 | goto fail; 852 | } 853 | 854 | // Read the integer part. 855 | end_not_reached = (curr != s_end); 856 | if (!leading_decimal_dots) { 857 | while (end_not_reached && IS_DIGIT(*curr)) { 858 | mantissa *= 10; 859 | mantissa += static_cast(*curr - 0x30); 860 | curr++; 861 | read++; 862 | end_not_reached = (curr != s_end); 863 | } 864 | 865 | // We must make sure we actually got something. 866 | if (read == 0) goto fail; 867 | } 868 | 869 | // We allow numbers of form "#", "###" etc. 870 | if (!end_not_reached) goto assemble; 871 | 872 | // Read the decimal part. 873 | if (*curr == '.') { 874 | curr++; 875 | read = 1; 876 | end_not_reached = (curr != s_end); 877 | while (end_not_reached && IS_DIGIT(*curr)) { 878 | static const double pow_lut[] = { 879 | 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 880 | }; 881 | const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; 882 | 883 | // NOTE: Don't use powf here, it will absolutely murder precision. 884 | mantissa += static_cast(*curr - 0x30) * 885 | (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); 886 | read++; 887 | curr++; 888 | end_not_reached = (curr != s_end); 889 | } 890 | } else if (*curr == 'e' || *curr == 'E') { 891 | } else { 892 | goto assemble; 893 | } 894 | 895 | if (!end_not_reached) goto assemble; 896 | 897 | // Read the exponent part. 898 | if (*curr == 'e' || *curr == 'E') { 899 | curr++; 900 | // Figure out if a sign is present and if it is. 901 | end_not_reached = (curr != s_end); 902 | if (end_not_reached && (*curr == '+' || *curr == '-')) { 903 | exp_sign = *curr; 904 | curr++; 905 | } else if (IS_DIGIT(*curr)) { /* Pass through. */ 906 | } else { 907 | // Empty E is not allowed. 908 | goto fail; 909 | } 910 | 911 | read = 0; 912 | end_not_reached = (curr != s_end); 913 | while (end_not_reached && IS_DIGIT(*curr)) { 914 | exponent *= 10; 915 | exponent += static_cast(*curr - 0x30); 916 | curr++; 917 | read++; 918 | end_not_reached = (curr != s_end); 919 | } 920 | exponent *= (exp_sign == '+' ? 1 : -1); 921 | if (read == 0) goto fail; 922 | } 923 | 924 | assemble: 925 | *result = (sign == '+' ? 1 : -1) * 926 | (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) 927 | : mantissa); 928 | return true; 929 | fail: 930 | return false; 931 | } 932 | 933 | static inline real_t parseReal(const char **token, double default_value = 0.0) { 934 | (*token) += strspn((*token), " \t"); 935 | const char *end = (*token) + strcspn((*token), " \t\r"); 936 | double val = default_value; 937 | tryParseDouble((*token), end, &val); 938 | real_t f = static_cast(val); 939 | (*token) = end; 940 | return f; 941 | } 942 | 943 | static inline bool parseReal(const char **token, real_t *out) { 944 | (*token) += strspn((*token), " \t"); 945 | const char *end = (*token) + strcspn((*token), " \t\r"); 946 | double val; 947 | bool ret = tryParseDouble((*token), end, &val); 948 | if (ret) { 949 | real_t f = static_cast(val); 950 | (*out) = f; 951 | } 952 | (*token) = end; 953 | return ret; 954 | } 955 | 956 | static inline void parseReal2(real_t *x, real_t *y, const char **token, 957 | const double default_x = 0.0, 958 | const double default_y = 0.0) { 959 | (*x) = parseReal(token, default_x); 960 | (*y) = parseReal(token, default_y); 961 | } 962 | 963 | static inline void parseReal3(real_t *x, real_t *y, real_t *z, 964 | const char **token, const double default_x = 0.0, 965 | const double default_y = 0.0, 966 | const double default_z = 0.0) { 967 | (*x) = parseReal(token, default_x); 968 | (*y) = parseReal(token, default_y); 969 | (*z) = parseReal(token, default_z); 970 | } 971 | 972 | static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, 973 | const char **token, const double default_x = 0.0, 974 | const double default_y = 0.0, 975 | const double default_z = 0.0, 976 | const double default_w = 1.0) { 977 | (*x) = parseReal(token, default_x); 978 | (*y) = parseReal(token, default_y); 979 | (*z) = parseReal(token, default_z); 980 | (*w) = parseReal(token, default_w); 981 | } 982 | 983 | // Extension: parse vertex with colors(6 items) 984 | static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, 985 | real_t *r, real_t *g, real_t *b, 986 | const char **token, 987 | const double default_x = 0.0, 988 | const double default_y = 0.0, 989 | const double default_z = 0.0) { 990 | (*x) = parseReal(token, default_x); 991 | (*y) = parseReal(token, default_y); 992 | (*z) = parseReal(token, default_z); 993 | 994 | const bool found_color = 995 | parseReal(token, r) && parseReal(token, g) && parseReal(token, b); 996 | 997 | if (!found_color) { 998 | (*r) = (*g) = (*b) = 1.0; 999 | } 1000 | 1001 | return found_color; 1002 | } 1003 | 1004 | static inline bool parseOnOff(const char **token, bool default_value = true) { 1005 | (*token) += strspn((*token), " \t"); 1006 | const char *end = (*token) + strcspn((*token), " \t\r"); 1007 | 1008 | bool ret = default_value; 1009 | if ((0 == strncmp((*token), "on", 2))) { 1010 | ret = true; 1011 | } else if ((0 == strncmp((*token), "off", 3))) { 1012 | ret = false; 1013 | } 1014 | 1015 | (*token) = end; 1016 | return ret; 1017 | } 1018 | 1019 | static inline texture_type_t parseTextureType( 1020 | const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { 1021 | (*token) += strspn((*token), " \t"); 1022 | const char *end = (*token) + strcspn((*token), " \t\r"); 1023 | texture_type_t ty = default_value; 1024 | 1025 | if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { 1026 | ty = TEXTURE_TYPE_CUBE_TOP; 1027 | } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { 1028 | ty = TEXTURE_TYPE_CUBE_BOTTOM; 1029 | } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { 1030 | ty = TEXTURE_TYPE_CUBE_LEFT; 1031 | } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { 1032 | ty = TEXTURE_TYPE_CUBE_RIGHT; 1033 | } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { 1034 | ty = TEXTURE_TYPE_CUBE_FRONT; 1035 | } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { 1036 | ty = TEXTURE_TYPE_CUBE_BACK; 1037 | } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { 1038 | ty = TEXTURE_TYPE_SPHERE; 1039 | } 1040 | 1041 | (*token) = end; 1042 | return ty; 1043 | } 1044 | 1045 | static tag_sizes parseTagTriple(const char **token) { 1046 | tag_sizes ts; 1047 | 1048 | (*token) += strspn((*token), " \t"); 1049 | ts.num_ints = atoi((*token)); 1050 | (*token) += strcspn((*token), "/ \t\r"); 1051 | if ((*token)[0] != '/') { 1052 | return ts; 1053 | } 1054 | 1055 | (*token)++; // Skip '/' 1056 | 1057 | (*token) += strspn((*token), " \t"); 1058 | ts.num_reals = atoi((*token)); 1059 | (*token) += strcspn((*token), "/ \t\r"); 1060 | if ((*token)[0] != '/') { 1061 | return ts; 1062 | } 1063 | (*token)++; // Skip '/' 1064 | 1065 | ts.num_strings = parseInt(token); 1066 | 1067 | return ts; 1068 | } 1069 | 1070 | // Parse triples with index offsets: i, i/j/k, i//k, i/j 1071 | static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, 1072 | vertex_index_t *ret) { 1073 | if (!ret) { 1074 | return false; 1075 | } 1076 | 1077 | vertex_index_t vi(-1); 1078 | 1079 | if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { 1080 | return false; 1081 | } 1082 | 1083 | (*token) += strcspn((*token), "/ \t\r"); 1084 | if ((*token)[0] != '/') { 1085 | (*ret) = vi; 1086 | return true; 1087 | } 1088 | (*token)++; 1089 | 1090 | // i//k 1091 | if ((*token)[0] == '/') { 1092 | (*token)++; 1093 | if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { 1094 | return false; 1095 | } 1096 | (*token) += strcspn((*token), "/ \t\r"); 1097 | (*ret) = vi; 1098 | return true; 1099 | } 1100 | 1101 | // i/j/k or i/j 1102 | if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) { 1103 | return false; 1104 | } 1105 | 1106 | (*token) += strcspn((*token), "/ \t\r"); 1107 | if ((*token)[0] != '/') { 1108 | (*ret) = vi; 1109 | return true; 1110 | } 1111 | 1112 | // i/j/k 1113 | (*token)++; // skip '/' 1114 | if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { 1115 | return false; 1116 | } 1117 | (*token) += strcspn((*token), "/ \t\r"); 1118 | 1119 | (*ret) = vi; 1120 | 1121 | return true; 1122 | } 1123 | 1124 | // Parse raw triples: i, i/j/k, i//k, i/j 1125 | static vertex_index_t parseRawTriple(const char **token) { 1126 | vertex_index_t vi(static_cast(0)); // 0 is an invalid index in OBJ 1127 | 1128 | vi.v_idx = atoi((*token)); 1129 | (*token) += strcspn((*token), "/ \t\r"); 1130 | if ((*token)[0] != '/') { 1131 | return vi; 1132 | } 1133 | (*token)++; 1134 | 1135 | // i//k 1136 | if ((*token)[0] == '/') { 1137 | (*token)++; 1138 | vi.vn_idx = atoi((*token)); 1139 | (*token) += strcspn((*token), "/ \t\r"); 1140 | return vi; 1141 | } 1142 | 1143 | // i/j/k or i/j 1144 | vi.vt_idx = atoi((*token)); 1145 | (*token) += strcspn((*token), "/ \t\r"); 1146 | if ((*token)[0] != '/') { 1147 | return vi; 1148 | } 1149 | 1150 | // i/j/k 1151 | (*token)++; // skip '/' 1152 | vi.vn_idx = atoi((*token)); 1153 | (*token) += strcspn((*token), "/ \t\r"); 1154 | return vi; 1155 | } 1156 | 1157 | bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, 1158 | const char *linebuf) { 1159 | // @todo { write more robust lexer and parser. } 1160 | bool found_texname = false; 1161 | std::string texture_name; 1162 | 1163 | const char *token = linebuf; // Assume line ends with NULL 1164 | 1165 | while (!IS_NEW_LINE((*token))) { 1166 | token += strspn(token, " \t"); // skip space 1167 | if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { 1168 | token += 8; 1169 | texopt->blendu = parseOnOff(&token, /* default */ true); 1170 | } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { 1171 | token += 8; 1172 | texopt->blendv = parseOnOff(&token, /* default */ true); 1173 | } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { 1174 | token += 7; 1175 | texopt->clamp = parseOnOff(&token, /* default */ true); 1176 | } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { 1177 | token += 7; 1178 | texopt->sharpness = parseReal(&token, 1.0); 1179 | } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { 1180 | token += 4; 1181 | texopt->bump_multiplier = parseReal(&token, 1.0); 1182 | } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { 1183 | token += 3; 1184 | parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), 1185 | &(texopt->origin_offset[2]), &token); 1186 | } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { 1187 | token += 3; 1188 | parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), 1189 | &token, 1.0, 1.0, 1.0); 1190 | } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { 1191 | token += 3; 1192 | parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), 1193 | &(texopt->turbulence[2]), &token); 1194 | } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { 1195 | token += 5; 1196 | texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); 1197 | } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { 1198 | token += 7; 1199 | // TODO(syoyo): Check if arg is int type. 1200 | texopt->texture_resolution = parseInt(&token); 1201 | } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { 1202 | token += 9; 1203 | token += strspn(token, " \t"); 1204 | const char *end = token + strcspn(token, " \t\r"); 1205 | if ((end - token) == 1) { // Assume one char for -imfchan 1206 | texopt->imfchan = (*token); 1207 | } 1208 | token = end; 1209 | } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { 1210 | token += 4; 1211 | parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); 1212 | } else if ((0 == strncmp(token, "-colorspace", 11)) && 1213 | IS_SPACE((token[11]))) { 1214 | token += 12; 1215 | texopt->colorspace = parseString(&token); 1216 | } else { 1217 | // Assume texture filename 1218 | #if 0 1219 | size_t len = strcspn(token, " \t\r"); // untile next space 1220 | texture_name = std::string(token, token + len); 1221 | token += len; 1222 | 1223 | token += strspn(token, " \t"); // skip space 1224 | #else 1225 | // Read filename until line end to parse filename containing whitespace 1226 | // TODO(syoyo): Support parsing texture option flag after the filename. 1227 | texture_name = std::string(token); 1228 | token += texture_name.length(); 1229 | #endif 1230 | 1231 | found_texname = true; 1232 | } 1233 | } 1234 | 1235 | if (found_texname) { 1236 | (*texname) = texture_name; 1237 | return true; 1238 | } else { 1239 | return false; 1240 | } 1241 | } 1242 | 1243 | static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { 1244 | if (is_bump) { 1245 | texopt->imfchan = 'l'; 1246 | } else { 1247 | texopt->imfchan = 'm'; 1248 | } 1249 | texopt->bump_multiplier = static_cast(1.0); 1250 | texopt->clamp = false; 1251 | texopt->blendu = true; 1252 | texopt->blendv = true; 1253 | texopt->sharpness = static_cast(1.0); 1254 | texopt->brightness = static_cast(0.0); 1255 | texopt->contrast = static_cast(1.0); 1256 | texopt->origin_offset[0] = static_cast(0.0); 1257 | texopt->origin_offset[1] = static_cast(0.0); 1258 | texopt->origin_offset[2] = static_cast(0.0); 1259 | texopt->scale[0] = static_cast(1.0); 1260 | texopt->scale[1] = static_cast(1.0); 1261 | texopt->scale[2] = static_cast(1.0); 1262 | texopt->turbulence[0] = static_cast(0.0); 1263 | texopt->turbulence[1] = static_cast(0.0); 1264 | texopt->turbulence[2] = static_cast(0.0); 1265 | texopt->texture_resolution = -1; 1266 | texopt->type = TEXTURE_TYPE_NONE; 1267 | } 1268 | 1269 | static void InitMaterial(material_t *material) { 1270 | InitTexOpt(&material->ambient_texopt, /* is_bump */ false); 1271 | InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); 1272 | InitTexOpt(&material->specular_texopt, /* is_bump */ false); 1273 | InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); 1274 | InitTexOpt(&material->bump_texopt, /* is_bump */ true); 1275 | InitTexOpt(&material->displacement_texopt, /* is_bump */ false); 1276 | InitTexOpt(&material->alpha_texopt, /* is_bump */ false); 1277 | InitTexOpt(&material->reflection_texopt, /* is_bump */ false); 1278 | InitTexOpt(&material->roughness_texopt, /* is_bump */ false); 1279 | InitTexOpt(&material->metallic_texopt, /* is_bump */ false); 1280 | InitTexOpt(&material->sheen_texopt, /* is_bump */ false); 1281 | InitTexOpt(&material->emissive_texopt, /* is_bump */ false); 1282 | InitTexOpt(&material->normal_texopt, 1283 | /* is_bump */ false); // @fixme { is_bump will be true? } 1284 | material->name = ""; 1285 | material->ambient_texname = ""; 1286 | material->diffuse_texname = ""; 1287 | material->specular_texname = ""; 1288 | material->specular_highlight_texname = ""; 1289 | material->bump_texname = ""; 1290 | material->displacement_texname = ""; 1291 | material->reflection_texname = ""; 1292 | material->alpha_texname = ""; 1293 | for (int i = 0; i < 3; i++) { 1294 | material->ambient[i] = static_cast(0.0); 1295 | material->diffuse[i] = static_cast(0.0); 1296 | material->specular[i] = static_cast(0.0); 1297 | material->transmittance[i] = static_cast(0.0); 1298 | material->emission[i] = static_cast(0.0); 1299 | } 1300 | material->illum = 0; 1301 | material->dissolve = static_cast(1.0); 1302 | material->shininess = static_cast(1.0); 1303 | material->ior = static_cast(1.0); 1304 | 1305 | material->roughness = static_cast(0.0); 1306 | material->metallic = static_cast(0.0); 1307 | material->sheen = static_cast(0.0); 1308 | material->clearcoat_thickness = static_cast(0.0); 1309 | material->clearcoat_roughness = static_cast(0.0); 1310 | material->anisotropy_rotation = static_cast(0.0); 1311 | material->anisotropy = static_cast(0.0); 1312 | material->roughness_texname = ""; 1313 | material->metallic_texname = ""; 1314 | material->sheen_texname = ""; 1315 | material->emissive_texname = ""; 1316 | material->normal_texname = ""; 1317 | 1318 | material->unknown_parameter.clear(); 1319 | } 1320 | 1321 | // code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html 1322 | template 1323 | static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { 1324 | int i, j, c = 0; 1325 | for (i = 0, j = nvert - 1; i < nvert; j = i++) { 1326 | if (((verty[i] > testy) != (verty[j] > testy)) && 1327 | (testx < 1328 | (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + 1329 | vertx[i])) 1330 | c = !c; 1331 | } 1332 | return c; 1333 | } 1334 | 1335 | // TODO(syoyo): refactor function. 1336 | static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, 1337 | const std::vector &tags, 1338 | const int material_id, const std::string &name, 1339 | bool triangulate, 1340 | const std::vector &v) { 1341 | if (prim_group.IsEmpty()) { 1342 | return false; 1343 | } 1344 | 1345 | shape->name = name; 1346 | 1347 | // polygon 1348 | if (!prim_group.faceGroup.empty()) { 1349 | // Flatten vertices and indices 1350 | for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { 1351 | const face_t &face = prim_group.faceGroup[i]; 1352 | 1353 | size_t npolys = face.vertex_indices.size(); 1354 | 1355 | if (npolys < 3) { 1356 | // Face must have 3+ vertices. 1357 | continue; 1358 | } 1359 | 1360 | vertex_index_t i0 = face.vertex_indices[0]; 1361 | vertex_index_t i1(-1); 1362 | vertex_index_t i2 = face.vertex_indices[1]; 1363 | 1364 | if (triangulate) { 1365 | // find the two axes to work in 1366 | size_t axes[2] = {1, 2}; 1367 | for (size_t k = 0; k < npolys; ++k) { 1368 | i0 = face.vertex_indices[(k + 0) % npolys]; 1369 | i1 = face.vertex_indices[(k + 1) % npolys]; 1370 | i2 = face.vertex_indices[(k + 2) % npolys]; 1371 | size_t vi0 = size_t(i0.v_idx); 1372 | size_t vi1 = size_t(i1.v_idx); 1373 | size_t vi2 = size_t(i2.v_idx); 1374 | 1375 | if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || 1376 | ((3 * vi2 + 2) >= v.size())) { 1377 | // Invalid triangle. 1378 | // FIXME(syoyo): Is it ok to simply skip this invalid triangle? 1379 | continue; 1380 | } 1381 | real_t v0x = v[vi0 * 3 + 0]; 1382 | real_t v0y = v[vi0 * 3 + 1]; 1383 | real_t v0z = v[vi0 * 3 + 2]; 1384 | real_t v1x = v[vi1 * 3 + 0]; 1385 | real_t v1y = v[vi1 * 3 + 1]; 1386 | real_t v1z = v[vi1 * 3 + 2]; 1387 | real_t v2x = v[vi2 * 3 + 0]; 1388 | real_t v2y = v[vi2 * 3 + 1]; 1389 | real_t v2z = v[vi2 * 3 + 2]; 1390 | real_t e0x = v1x - v0x; 1391 | real_t e0y = v1y - v0y; 1392 | real_t e0z = v1z - v0z; 1393 | real_t e1x = v2x - v1x; 1394 | real_t e1y = v2y - v1y; 1395 | real_t e1z = v2z - v1z; 1396 | real_t cx = std::fabs(e0y * e1z - e0z * e1y); 1397 | real_t cy = std::fabs(e0z * e1x - e0x * e1z); 1398 | real_t cz = std::fabs(e0x * e1y - e0y * e1x); 1399 | const real_t epsilon = std::numeric_limits::epsilon(); 1400 | if (cx > epsilon || cy > epsilon || cz > epsilon) { 1401 | // found a corner 1402 | if (cx > cy && cx > cz) { 1403 | } else { 1404 | axes[0] = 0; 1405 | if (cz > cx && cz > cy) axes[1] = 1; 1406 | } 1407 | break; 1408 | } 1409 | } 1410 | 1411 | real_t area = 0; 1412 | for (size_t k = 0; k < npolys; ++k) { 1413 | i0 = face.vertex_indices[(k + 0) % npolys]; 1414 | i1 = face.vertex_indices[(k + 1) % npolys]; 1415 | size_t vi0 = size_t(i0.v_idx); 1416 | size_t vi1 = size_t(i1.v_idx); 1417 | if (((vi0 * 3 + axes[0]) >= v.size()) || 1418 | ((vi0 * 3 + axes[1]) >= v.size()) || 1419 | ((vi1 * 3 + axes[0]) >= v.size()) || 1420 | ((vi1 * 3 + axes[1]) >= v.size())) { 1421 | // Invalid index. 1422 | continue; 1423 | } 1424 | real_t v0x = v[vi0 * 3 + axes[0]]; 1425 | real_t v0y = v[vi0 * 3 + axes[1]]; 1426 | real_t v1x = v[vi1 * 3 + axes[0]]; 1427 | real_t v1y = v[vi1 * 3 + axes[1]]; 1428 | area += (v0x * v1y - v0y * v1x) * static_cast(0.5); 1429 | } 1430 | 1431 | face_t remainingFace = face; // copy 1432 | size_t guess_vert = 0; 1433 | vertex_index_t ind[3]; 1434 | real_t vx[3]; 1435 | real_t vy[3]; 1436 | 1437 | // How many iterations can we do without decreasing the remaining 1438 | // vertices. 1439 | size_t remainingIterations = face.vertex_indices.size(); 1440 | size_t previousRemainingVertices = remainingFace.vertex_indices.size(); 1441 | 1442 | while (remainingFace.vertex_indices.size() > 3 && 1443 | remainingIterations > 0) { 1444 | npolys = remainingFace.vertex_indices.size(); 1445 | if (guess_vert >= npolys) { 1446 | guess_vert -= npolys; 1447 | } 1448 | 1449 | if (previousRemainingVertices != npolys) { 1450 | // The number of remaining vertices decreased. Reset counters. 1451 | previousRemainingVertices = npolys; 1452 | remainingIterations = npolys; 1453 | } else { 1454 | // We didn't consume a vertex on previous iteration, reduce the 1455 | // available iterations. 1456 | remainingIterations--; 1457 | } 1458 | 1459 | for (size_t k = 0; k < 3; k++) { 1460 | ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; 1461 | size_t vi = size_t(ind[k].v_idx); 1462 | if (((vi * 3 + axes[0]) >= v.size()) || 1463 | ((vi * 3 + axes[1]) >= v.size())) { 1464 | // ??? 1465 | vx[k] = static_cast(0.0); 1466 | vy[k] = static_cast(0.0); 1467 | } else { 1468 | vx[k] = v[vi * 3 + axes[0]]; 1469 | vy[k] = v[vi * 3 + axes[1]]; 1470 | } 1471 | } 1472 | real_t e0x = vx[1] - vx[0]; 1473 | real_t e0y = vy[1] - vy[0]; 1474 | real_t e1x = vx[2] - vx[1]; 1475 | real_t e1y = vy[2] - vy[1]; 1476 | real_t cross = e0x * e1y - e0y * e1x; 1477 | // if an internal angle 1478 | if (cross * area < static_cast(0.0)) { 1479 | guess_vert += 1; 1480 | continue; 1481 | } 1482 | 1483 | // check all other verts in case they are inside this triangle 1484 | bool overlap = false; 1485 | for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { 1486 | size_t idx = (guess_vert + otherVert) % npolys; 1487 | 1488 | if (idx >= remainingFace.vertex_indices.size()) { 1489 | // ??? 1490 | continue; 1491 | } 1492 | 1493 | size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); 1494 | 1495 | if (((ovi * 3 + axes[0]) >= v.size()) || 1496 | ((ovi * 3 + axes[1]) >= v.size())) { 1497 | // ??? 1498 | continue; 1499 | } 1500 | real_t tx = v[ovi * 3 + axes[0]]; 1501 | real_t ty = v[ovi * 3 + axes[1]]; 1502 | if (pnpoly(3, vx, vy, tx, ty)) { 1503 | overlap = true; 1504 | break; 1505 | } 1506 | } 1507 | 1508 | if (overlap) { 1509 | guess_vert += 1; 1510 | continue; 1511 | } 1512 | 1513 | // this triangle is an ear 1514 | { 1515 | index_t idx0, idx1, idx2; 1516 | idx0.vertex_index = ind[0].v_idx; 1517 | idx0.normal_index = ind[0].vn_idx; 1518 | idx0.texcoord_index = ind[0].vt_idx; 1519 | idx1.vertex_index = ind[1].v_idx; 1520 | idx1.normal_index = ind[1].vn_idx; 1521 | idx1.texcoord_index = ind[1].vt_idx; 1522 | idx2.vertex_index = ind[2].v_idx; 1523 | idx2.normal_index = ind[2].vn_idx; 1524 | idx2.texcoord_index = ind[2].vt_idx; 1525 | 1526 | shape->mesh.indices.push_back(idx0); 1527 | shape->mesh.indices.push_back(idx1); 1528 | shape->mesh.indices.push_back(idx2); 1529 | 1530 | shape->mesh.num_face_vertices.push_back(3); 1531 | shape->mesh.material_ids.push_back(material_id); 1532 | shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); 1533 | } 1534 | 1535 | // remove v1 from the list 1536 | size_t removed_vert_index = (guess_vert + 1) % npolys; 1537 | while (removed_vert_index + 1 < npolys) { 1538 | remainingFace.vertex_indices[removed_vert_index] = 1539 | remainingFace.vertex_indices[removed_vert_index + 1]; 1540 | removed_vert_index += 1; 1541 | } 1542 | remainingFace.vertex_indices.pop_back(); 1543 | } 1544 | 1545 | if (remainingFace.vertex_indices.size() == 3) { 1546 | i0 = remainingFace.vertex_indices[0]; 1547 | i1 = remainingFace.vertex_indices[1]; 1548 | i2 = remainingFace.vertex_indices[2]; 1549 | { 1550 | index_t idx0, idx1, idx2; 1551 | idx0.vertex_index = i0.v_idx; 1552 | idx0.normal_index = i0.vn_idx; 1553 | idx0.texcoord_index = i0.vt_idx; 1554 | idx1.vertex_index = i1.v_idx; 1555 | idx1.normal_index = i1.vn_idx; 1556 | idx1.texcoord_index = i1.vt_idx; 1557 | idx2.vertex_index = i2.v_idx; 1558 | idx2.normal_index = i2.vn_idx; 1559 | idx2.texcoord_index = i2.vt_idx; 1560 | 1561 | shape->mesh.indices.push_back(idx0); 1562 | shape->mesh.indices.push_back(idx1); 1563 | shape->mesh.indices.push_back(idx2); 1564 | 1565 | shape->mesh.num_face_vertices.push_back(3); 1566 | shape->mesh.material_ids.push_back(material_id); 1567 | shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); 1568 | } 1569 | } 1570 | } else { 1571 | for (size_t k = 0; k < npolys; k++) { 1572 | index_t idx; 1573 | idx.vertex_index = face.vertex_indices[k].v_idx; 1574 | idx.normal_index = face.vertex_indices[k].vn_idx; 1575 | idx.texcoord_index = face.vertex_indices[k].vt_idx; 1576 | shape->mesh.indices.push_back(idx); 1577 | } 1578 | 1579 | shape->mesh.num_face_vertices.push_back( 1580 | static_cast(npolys)); 1581 | shape->mesh.material_ids.push_back(material_id); // per face 1582 | shape->mesh.smoothing_group_ids.push_back( 1583 | face.smoothing_group_id); // per face 1584 | } 1585 | } 1586 | 1587 | shape->mesh.tags = tags; 1588 | } 1589 | 1590 | // line 1591 | if (!prim_group.lineGroup.empty()) { 1592 | // Flatten indices 1593 | for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { 1594 | for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); 1595 | j++) { 1596 | const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; 1597 | 1598 | index_t idx; 1599 | idx.vertex_index = vi.v_idx; 1600 | idx.normal_index = vi.vn_idx; 1601 | idx.texcoord_index = vi.vt_idx; 1602 | 1603 | shape->lines.indices.push_back(idx); 1604 | } 1605 | 1606 | shape->lines.num_line_vertices.push_back( 1607 | int(prim_group.lineGroup[i].vertex_indices.size())); 1608 | } 1609 | } 1610 | 1611 | // points 1612 | if (!prim_group.pointsGroup.empty()) { 1613 | // Flatten & convert indices 1614 | for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { 1615 | for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); 1616 | j++) { 1617 | const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; 1618 | 1619 | index_t idx; 1620 | idx.vertex_index = vi.v_idx; 1621 | idx.normal_index = vi.vn_idx; 1622 | idx.texcoord_index = vi.vt_idx; 1623 | 1624 | shape->points.indices.push_back(idx); 1625 | } 1626 | } 1627 | } 1628 | 1629 | return true; 1630 | } 1631 | 1632 | // Split a string with specified delimiter character. 1633 | // http://stackoverflow.com/questions/236129/split-a-string-in-c 1634 | static void SplitString(const std::string &s, char delim, 1635 | std::vector &elems) { 1636 | std::stringstream ss; 1637 | ss.str(s); 1638 | std::string item; 1639 | while (std::getline(ss, item, delim)) { 1640 | elems.push_back(item); 1641 | } 1642 | } 1643 | 1644 | static std::string JoinPath(const std::string &dir, 1645 | const std::string &filename) { 1646 | if (dir.empty()) { 1647 | return filename; 1648 | } else { 1649 | // check '/' 1650 | char lastChar = *dir.rbegin(); 1651 | if (lastChar != '/') { 1652 | return dir + std::string("/") + filename; 1653 | } else { 1654 | return dir + filename; 1655 | } 1656 | } 1657 | } 1658 | 1659 | void LoadMtl(std::map *material_map, 1660 | std::vector *materials, std::istream *inStream, 1661 | std::string *warning, std::string *err) { 1662 | (void)err; 1663 | 1664 | // Create a default material anyway. 1665 | material_t material; 1666 | InitMaterial(&material); 1667 | 1668 | // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. 1669 | bool has_d = false; 1670 | bool has_tr = false; 1671 | 1672 | std::stringstream warn_ss; 1673 | 1674 | size_t line_no = 0; 1675 | std::string linebuf; 1676 | while (inStream->peek() != -1) { 1677 | safeGetline(*inStream, linebuf); 1678 | line_no++; 1679 | 1680 | // Trim trailing whitespace. 1681 | if (linebuf.size() > 0) { 1682 | linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); 1683 | } 1684 | 1685 | // Trim newline '\r\n' or '\n' 1686 | if (linebuf.size() > 0) { 1687 | if (linebuf[linebuf.size() - 1] == '\n') 1688 | linebuf.erase(linebuf.size() - 1); 1689 | } 1690 | if (linebuf.size() > 0) { 1691 | if (linebuf[linebuf.size() - 1] == '\r') 1692 | linebuf.erase(linebuf.size() - 1); 1693 | } 1694 | 1695 | // Skip if empty line. 1696 | if (linebuf.empty()) { 1697 | continue; 1698 | } 1699 | 1700 | // Skip leading space. 1701 | const char *token = linebuf.c_str(); 1702 | token += strspn(token, " \t"); 1703 | 1704 | assert(token); 1705 | if (token[0] == '\0') continue; // empty line 1706 | 1707 | if (token[0] == '#') continue; // comment line 1708 | 1709 | // new mtl 1710 | if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { 1711 | // flush previous material. 1712 | if (!material.name.empty()) { 1713 | material_map->insert(std::pair( 1714 | material.name, static_cast(materials->size()))); 1715 | materials->push_back(material); 1716 | } 1717 | 1718 | // initial temporary material 1719 | InitMaterial(&material); 1720 | 1721 | has_d = false; 1722 | has_tr = false; 1723 | 1724 | // set new mtl name 1725 | token += 7; 1726 | { 1727 | std::stringstream sstr; 1728 | sstr << token; 1729 | material.name = sstr.str(); 1730 | } 1731 | continue; 1732 | } 1733 | 1734 | // ambient 1735 | if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { 1736 | token += 2; 1737 | real_t r, g, b; 1738 | parseReal3(&r, &g, &b, &token); 1739 | material.ambient[0] = r; 1740 | material.ambient[1] = g; 1741 | material.ambient[2] = b; 1742 | continue; 1743 | } 1744 | 1745 | // diffuse 1746 | if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { 1747 | token += 2; 1748 | real_t r, g, b; 1749 | parseReal3(&r, &g, &b, &token); 1750 | material.diffuse[0] = r; 1751 | material.diffuse[1] = g; 1752 | material.diffuse[2] = b; 1753 | continue; 1754 | } 1755 | 1756 | // specular 1757 | if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { 1758 | token += 2; 1759 | real_t r, g, b; 1760 | parseReal3(&r, &g, &b, &token); 1761 | material.specular[0] = r; 1762 | material.specular[1] = g; 1763 | material.specular[2] = b; 1764 | continue; 1765 | } 1766 | 1767 | // transmittance 1768 | if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || 1769 | (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { 1770 | token += 2; 1771 | real_t r, g, b; 1772 | parseReal3(&r, &g, &b, &token); 1773 | material.transmittance[0] = r; 1774 | material.transmittance[1] = g; 1775 | material.transmittance[2] = b; 1776 | continue; 1777 | } 1778 | 1779 | // ior(index of refraction) 1780 | if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { 1781 | token += 2; 1782 | material.ior = parseReal(&token); 1783 | continue; 1784 | } 1785 | 1786 | // emission 1787 | if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { 1788 | token += 2; 1789 | real_t r, g, b; 1790 | parseReal3(&r, &g, &b, &token); 1791 | material.emission[0] = r; 1792 | material.emission[1] = g; 1793 | material.emission[2] = b; 1794 | continue; 1795 | } 1796 | 1797 | // shininess 1798 | if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { 1799 | token += 2; 1800 | material.shininess = parseReal(&token); 1801 | continue; 1802 | } 1803 | 1804 | // illum model 1805 | if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { 1806 | token += 6; 1807 | material.illum = parseInt(&token); 1808 | continue; 1809 | } 1810 | 1811 | // dissolve 1812 | if ((token[0] == 'd' && IS_SPACE(token[1]))) { 1813 | token += 1; 1814 | material.dissolve = parseReal(&token); 1815 | 1816 | if (has_tr) { 1817 | warn_ss << "Both `d` and `Tr` parameters defined for \"" 1818 | << material.name 1819 | << "\". Use the value of `d` for dissolve (line " << line_no 1820 | << " in .mtl.)" << std::endl; 1821 | } 1822 | has_d = true; 1823 | continue; 1824 | } 1825 | if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { 1826 | token += 2; 1827 | if (has_d) { 1828 | // `d` wins. Ignore `Tr` value. 1829 | warn_ss << "Both `d` and `Tr` parameters defined for \"" 1830 | << material.name 1831 | << "\". Use the value of `d` for dissolve (line " << line_no 1832 | << " in .mtl.)" << std::endl; 1833 | } else { 1834 | // We invert value of Tr(assume Tr is in range [0, 1]) 1835 | // NOTE: Interpretation of Tr is application(exporter) dependent. For 1836 | // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) 1837 | material.dissolve = static_cast(1.0) - parseReal(&token); 1838 | } 1839 | has_tr = true; 1840 | continue; 1841 | } 1842 | 1843 | // PBR: roughness 1844 | if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { 1845 | token += 2; 1846 | material.roughness = parseReal(&token); 1847 | continue; 1848 | } 1849 | 1850 | // PBR: metallic 1851 | if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { 1852 | token += 2; 1853 | material.metallic = parseReal(&token); 1854 | continue; 1855 | } 1856 | 1857 | // PBR: sheen 1858 | if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { 1859 | token += 2; 1860 | material.sheen = parseReal(&token); 1861 | continue; 1862 | } 1863 | 1864 | // PBR: clearcoat thickness 1865 | if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { 1866 | token += 2; 1867 | material.clearcoat_thickness = parseReal(&token); 1868 | continue; 1869 | } 1870 | 1871 | // PBR: clearcoat roughness 1872 | if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { 1873 | token += 4; 1874 | material.clearcoat_roughness = parseReal(&token); 1875 | continue; 1876 | } 1877 | 1878 | // PBR: anisotropy 1879 | if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { 1880 | token += 6; 1881 | material.anisotropy = parseReal(&token); 1882 | continue; 1883 | } 1884 | 1885 | // PBR: anisotropy rotation 1886 | if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { 1887 | token += 7; 1888 | material.anisotropy_rotation = parseReal(&token); 1889 | continue; 1890 | } 1891 | 1892 | // ambient texture 1893 | if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { 1894 | token += 7; 1895 | ParseTextureNameAndOption(&(material.ambient_texname), 1896 | &(material.ambient_texopt), token); 1897 | continue; 1898 | } 1899 | 1900 | // diffuse texture 1901 | if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { 1902 | token += 7; 1903 | ParseTextureNameAndOption(&(material.diffuse_texname), 1904 | &(material.diffuse_texopt), token); 1905 | continue; 1906 | } 1907 | 1908 | // specular texture 1909 | if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { 1910 | token += 7; 1911 | ParseTextureNameAndOption(&(material.specular_texname), 1912 | &(material.specular_texopt), token); 1913 | continue; 1914 | } 1915 | 1916 | // specular highlight texture 1917 | if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { 1918 | token += 7; 1919 | ParseTextureNameAndOption(&(material.specular_highlight_texname), 1920 | &(material.specular_highlight_texopt), token); 1921 | continue; 1922 | } 1923 | 1924 | // bump texture 1925 | if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { 1926 | token += 9; 1927 | ParseTextureNameAndOption(&(material.bump_texname), 1928 | &(material.bump_texopt), token); 1929 | continue; 1930 | } 1931 | 1932 | // bump texture 1933 | if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { 1934 | token += 9; 1935 | ParseTextureNameAndOption(&(material.bump_texname), 1936 | &(material.bump_texopt), token); 1937 | continue; 1938 | } 1939 | 1940 | // bump texture 1941 | if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { 1942 | token += 5; 1943 | ParseTextureNameAndOption(&(material.bump_texname), 1944 | &(material.bump_texopt), token); 1945 | continue; 1946 | } 1947 | 1948 | // alpha texture 1949 | if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { 1950 | token += 6; 1951 | material.alpha_texname = token; 1952 | ParseTextureNameAndOption(&(material.alpha_texname), 1953 | &(material.alpha_texopt), token); 1954 | continue; 1955 | } 1956 | 1957 | // displacement texture 1958 | if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { 1959 | token += 5; 1960 | ParseTextureNameAndOption(&(material.displacement_texname), 1961 | &(material.displacement_texopt), token); 1962 | continue; 1963 | } 1964 | 1965 | // reflection map 1966 | if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { 1967 | token += 5; 1968 | ParseTextureNameAndOption(&(material.reflection_texname), 1969 | &(material.reflection_texopt), token); 1970 | continue; 1971 | } 1972 | 1973 | // PBR: roughness texture 1974 | if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { 1975 | token += 7; 1976 | ParseTextureNameAndOption(&(material.roughness_texname), 1977 | &(material.roughness_texopt), token); 1978 | continue; 1979 | } 1980 | 1981 | // PBR: metallic texture 1982 | if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { 1983 | token += 7; 1984 | ParseTextureNameAndOption(&(material.metallic_texname), 1985 | &(material.metallic_texopt), token); 1986 | continue; 1987 | } 1988 | 1989 | // PBR: sheen texture 1990 | if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { 1991 | token += 7; 1992 | ParseTextureNameAndOption(&(material.sheen_texname), 1993 | &(material.sheen_texopt), token); 1994 | continue; 1995 | } 1996 | 1997 | // PBR: emissive texture 1998 | if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { 1999 | token += 7; 2000 | ParseTextureNameAndOption(&(material.emissive_texname), 2001 | &(material.emissive_texopt), token); 2002 | continue; 2003 | } 2004 | 2005 | // PBR: normal map texture 2006 | if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { 2007 | token += 5; 2008 | ParseTextureNameAndOption(&(material.normal_texname), 2009 | &(material.normal_texopt), token); 2010 | continue; 2011 | } 2012 | 2013 | // unknown parameter 2014 | const char *_space = strchr(token, ' '); 2015 | if (!_space) { 2016 | _space = strchr(token, '\t'); 2017 | } 2018 | if (_space) { 2019 | std::ptrdiff_t len = _space - token; 2020 | std::string key(token, static_cast(len)); 2021 | std::string value = _space + 1; 2022 | material.unknown_parameter.insert( 2023 | std::pair(key, value)); 2024 | } 2025 | } 2026 | // flush last material. 2027 | material_map->insert(std::pair( 2028 | material.name, static_cast(materials->size()))); 2029 | materials->push_back(material); 2030 | 2031 | if (warning) { 2032 | (*warning) = warn_ss.str(); 2033 | } 2034 | } 2035 | 2036 | bool MaterialFileReader::operator()(const std::string &matId, 2037 | std::vector *materials, 2038 | std::map *matMap, 2039 | std::string *warn, std::string *err) { 2040 | if (!m_mtlBaseDir.empty()) { 2041 | #ifdef _WIN32 2042 | char sep = ';'; 2043 | #else 2044 | char sep = ':'; 2045 | #endif 2046 | 2047 | // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g 2048 | std::vector paths; 2049 | std::istringstream f(m_mtlBaseDir); 2050 | 2051 | std::string s; 2052 | while (getline(f, s, sep)) { 2053 | paths.push_back(s); 2054 | } 2055 | 2056 | for (size_t i = 0; i < paths.size(); i++) { 2057 | std::string filepath = JoinPath(paths[i], matId); 2058 | 2059 | std::ifstream matIStream(filepath.c_str()); 2060 | if (matIStream) { 2061 | LoadMtl(matMap, materials, &matIStream, warn, err); 2062 | 2063 | return true; 2064 | } 2065 | } 2066 | 2067 | std::stringstream ss; 2068 | ss << "Material file [ " << matId 2069 | << " ] not found in a path : " << m_mtlBaseDir << std::endl; 2070 | if (warn) { 2071 | (*warn) += ss.str(); 2072 | } 2073 | return false; 2074 | 2075 | } else { 2076 | std::string filepath = matId; 2077 | std::ifstream matIStream(filepath.c_str()); 2078 | if (matIStream) { 2079 | LoadMtl(matMap, materials, &matIStream, warn, err); 2080 | 2081 | return true; 2082 | } 2083 | 2084 | std::stringstream ss; 2085 | ss << "Material file [ " << filepath 2086 | << " ] not found in a path : " << m_mtlBaseDir << std::endl; 2087 | if (warn) { 2088 | (*warn) += ss.str(); 2089 | } 2090 | 2091 | return false; 2092 | } 2093 | } 2094 | 2095 | bool MaterialStreamReader::operator()(const std::string &matId, 2096 | std::vector *materials, 2097 | std::map *matMap, 2098 | std::string *warn, std::string *err) { 2099 | (void)err; 2100 | (void)matId; 2101 | if (!m_inStream) { 2102 | std::stringstream ss; 2103 | ss << "Material stream in error state. " << std::endl; 2104 | if (warn) { 2105 | (*warn) += ss.str(); 2106 | } 2107 | return false; 2108 | } 2109 | 2110 | LoadMtl(matMap, materials, &m_inStream, warn, err); 2111 | 2112 | return true; 2113 | } 2114 | 2115 | bool LoadObj(attrib_t *attrib, std::vector *shapes, 2116 | std::vector *materials, std::string *warn, 2117 | std::string *err, const char *filename, const char *mtl_basedir, 2118 | bool trianglulate, bool default_vcols_fallback) { 2119 | attrib->vertices.clear(); 2120 | attrib->normals.clear(); 2121 | attrib->texcoords.clear(); 2122 | attrib->colors.clear(); 2123 | shapes->clear(); 2124 | 2125 | std::stringstream errss; 2126 | 2127 | std::ifstream ifs(filename); 2128 | if (!ifs) { 2129 | errss << "Cannot open file [" << filename << "]" << std::endl; 2130 | if (err) { 2131 | (*err) = errss.str(); 2132 | } 2133 | return false; 2134 | } 2135 | 2136 | std::string baseDir = mtl_basedir ? mtl_basedir : ""; 2137 | if (!baseDir.empty()) { 2138 | #ifndef _WIN32 2139 | const char dirsep = '/'; 2140 | #else 2141 | const char dirsep = '\\'; 2142 | #endif 2143 | if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; 2144 | } 2145 | MaterialFileReader matFileReader(baseDir); 2146 | 2147 | return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, 2148 | trianglulate, default_vcols_fallback); 2149 | } 2150 | 2151 | bool LoadObj(attrib_t *attrib, std::vector *shapes, 2152 | std::vector *materials, std::string *warn, 2153 | std::string *err, std::istream *inStream, 2154 | MaterialReader *readMatFn /*= NULL*/, bool triangulate, 2155 | bool default_vcols_fallback) { 2156 | std::stringstream errss; 2157 | 2158 | std::vector v; 2159 | std::vector vn; 2160 | std::vector vt; 2161 | std::vector vc; 2162 | std::vector tags; 2163 | PrimGroup prim_group; 2164 | std::string name; 2165 | 2166 | // material 2167 | std::map material_map; 2168 | int material = -1; 2169 | 2170 | // smoothing group id 2171 | unsigned int current_smoothing_id = 2172 | 0; // Initial value. 0 means no smoothing. 2173 | 2174 | int greatest_v_idx = -1; 2175 | int greatest_vn_idx = -1; 2176 | int greatest_vt_idx = -1; 2177 | 2178 | shape_t shape; 2179 | 2180 | bool found_all_colors = true; 2181 | 2182 | size_t line_num = 0; 2183 | std::string linebuf; 2184 | while (inStream->peek() != -1) { 2185 | safeGetline(*inStream, linebuf); 2186 | 2187 | line_num++; 2188 | 2189 | // Trim newline '\r\n' or '\n' 2190 | if (linebuf.size() > 0) { 2191 | if (linebuf[linebuf.size() - 1] == '\n') 2192 | linebuf.erase(linebuf.size() - 1); 2193 | } 2194 | if (linebuf.size() > 0) { 2195 | if (linebuf[linebuf.size() - 1] == '\r') 2196 | linebuf.erase(linebuf.size() - 1); 2197 | } 2198 | 2199 | // Skip if empty line. 2200 | if (linebuf.empty()) { 2201 | continue; 2202 | } 2203 | 2204 | // Skip leading space. 2205 | const char *token = linebuf.c_str(); 2206 | token += strspn(token, " \t"); 2207 | 2208 | assert(token); 2209 | if (token[0] == '\0') continue; // empty line 2210 | 2211 | if (token[0] == '#') continue; // comment line 2212 | 2213 | // vertex 2214 | if (token[0] == 'v' && IS_SPACE((token[1]))) { 2215 | token += 2; 2216 | real_t x, y, z; 2217 | real_t r, g, b; 2218 | 2219 | found_all_colors &= parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); 2220 | 2221 | v.push_back(x); 2222 | v.push_back(y); 2223 | v.push_back(z); 2224 | 2225 | if (found_all_colors || default_vcols_fallback) { 2226 | vc.push_back(r); 2227 | vc.push_back(g); 2228 | vc.push_back(b); 2229 | } 2230 | 2231 | continue; 2232 | } 2233 | 2234 | // normal 2235 | if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { 2236 | token += 3; 2237 | real_t x, y, z; 2238 | parseReal3(&x, &y, &z, &token); 2239 | vn.push_back(x); 2240 | vn.push_back(y); 2241 | vn.push_back(z); 2242 | continue; 2243 | } 2244 | 2245 | // texcoord 2246 | if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { 2247 | token += 3; 2248 | real_t x, y; 2249 | parseReal2(&x, &y, &token); 2250 | vt.push_back(x); 2251 | vt.push_back(y); 2252 | continue; 2253 | } 2254 | 2255 | // line 2256 | if (token[0] == 'l' && IS_SPACE((token[1]))) { 2257 | token += 2; 2258 | 2259 | __line_t line; 2260 | 2261 | while (!IS_NEW_LINE(token[0])) { 2262 | vertex_index_t vi; 2263 | if (!parseTriple(&token, static_cast(v.size() / 3), 2264 | static_cast(vn.size() / 3), 2265 | static_cast(vt.size() / 2), &vi)) { 2266 | if (err) { 2267 | std::stringstream ss; 2268 | ss << "Failed parse `l' line(e.g. zero value for vertex index. " 2269 | "line " 2270 | << line_num << ".)\n"; 2271 | (*err) += ss.str(); 2272 | } 2273 | return false; 2274 | } 2275 | 2276 | line.vertex_indices.push_back(vi); 2277 | 2278 | size_t n = strspn(token, " \t\r"); 2279 | token += n; 2280 | } 2281 | 2282 | prim_group.lineGroup.push_back(line); 2283 | 2284 | continue; 2285 | } 2286 | 2287 | // points 2288 | if (token[0] == 'p' && IS_SPACE((token[1]))) { 2289 | token += 2; 2290 | 2291 | __points_t pts; 2292 | 2293 | while (!IS_NEW_LINE(token[0])) { 2294 | vertex_index_t vi; 2295 | if (!parseTriple(&token, static_cast(v.size() / 3), 2296 | static_cast(vn.size() / 3), 2297 | static_cast(vt.size() / 2), &vi)) { 2298 | if (err) { 2299 | std::stringstream ss; 2300 | ss << "Failed parse `p' line(e.g. zero value for vertex index. " 2301 | "line " 2302 | << line_num << ".)\n"; 2303 | (*err) += ss.str(); 2304 | } 2305 | return false; 2306 | } 2307 | 2308 | pts.vertex_indices.push_back(vi); 2309 | 2310 | size_t n = strspn(token, " \t\r"); 2311 | token += n; 2312 | } 2313 | 2314 | prim_group.pointsGroup.push_back(pts); 2315 | 2316 | continue; 2317 | } 2318 | 2319 | // face 2320 | if (token[0] == 'f' && IS_SPACE((token[1]))) { 2321 | token += 2; 2322 | token += strspn(token, " \t"); 2323 | 2324 | face_t face; 2325 | 2326 | face.smoothing_group_id = current_smoothing_id; 2327 | face.vertex_indices.reserve(3); 2328 | 2329 | while (!IS_NEW_LINE(token[0])) { 2330 | vertex_index_t vi; 2331 | if (!parseTriple(&token, static_cast(v.size() / 3), 2332 | static_cast(vn.size() / 3), 2333 | static_cast(vt.size() / 2), &vi)) { 2334 | if (err) { 2335 | std::stringstream ss; 2336 | ss << "Failed parse `f' line(e.g. zero value for face index. line " 2337 | << line_num << ".)\n"; 2338 | (*err) += ss.str(); 2339 | } 2340 | return false; 2341 | } 2342 | 2343 | greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; 2344 | greatest_vn_idx = 2345 | greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; 2346 | greatest_vt_idx = 2347 | greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; 2348 | 2349 | face.vertex_indices.push_back(vi); 2350 | size_t n = strspn(token, " \t\r"); 2351 | token += n; 2352 | } 2353 | 2354 | // replace with emplace_back + std::move on C++11 2355 | prim_group.faceGroup.push_back(face); 2356 | 2357 | continue; 2358 | } 2359 | 2360 | // use mtl 2361 | if ((0 == strncmp(token, "usemtl", 6))) { 2362 | token += 6; 2363 | std::string namebuf = parseString(&token); 2364 | 2365 | int newMaterialId = -1; 2366 | if (material_map.find(namebuf) != material_map.end()) { 2367 | newMaterialId = material_map[namebuf]; 2368 | } else { 2369 | // { error!! material not found } 2370 | if (warn) { 2371 | (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; 2372 | } 2373 | } 2374 | 2375 | if (newMaterialId != material) { 2376 | // Create per-face material. Thus we don't add `shape` to `shapes` at 2377 | // this time. 2378 | // just clear `faceGroup` after `exportGroupsToShape()` call. 2379 | exportGroupsToShape(&shape, prim_group, tags, material, name, 2380 | triangulate, v); 2381 | prim_group.faceGroup.clear(); 2382 | material = newMaterialId; 2383 | } 2384 | 2385 | continue; 2386 | } 2387 | 2388 | // load mtl 2389 | if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { 2390 | if (readMatFn) { 2391 | token += 7; 2392 | 2393 | std::vector filenames; 2394 | SplitString(std::string(token), ' ', filenames); 2395 | 2396 | if (filenames.empty()) { 2397 | if (warn) { 2398 | std::stringstream ss; 2399 | ss << "Looks like empty filename for mtllib. Use default " 2400 | "material (line " 2401 | << line_num << ".)\n"; 2402 | 2403 | (*warn) += ss.str(); 2404 | } 2405 | } else { 2406 | bool found = false; 2407 | for (size_t s = 0; s < filenames.size(); s++) { 2408 | std::string warn_mtl; 2409 | std::string err_mtl; 2410 | bool ok = (*readMatFn)(filenames[s].c_str(), materials, 2411 | &material_map, &warn_mtl, &err_mtl); 2412 | if (warn && (!warn_mtl.empty())) { 2413 | (*warn) += warn_mtl; 2414 | } 2415 | 2416 | if (err && (!err_mtl.empty())) { 2417 | (*err) += err_mtl; 2418 | } 2419 | 2420 | if (ok) { 2421 | found = true; 2422 | break; 2423 | } 2424 | } 2425 | 2426 | if (!found) { 2427 | if (warn) { 2428 | (*warn) += 2429 | "Failed to load material file(s). Use default " 2430 | "material.\n"; 2431 | } 2432 | } 2433 | } 2434 | } 2435 | 2436 | continue; 2437 | } 2438 | 2439 | // group name 2440 | if (token[0] == 'g' && IS_SPACE((token[1]))) { 2441 | // flush previous face group. 2442 | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, 2443 | triangulate, v); 2444 | (void)ret; // return value not used. 2445 | 2446 | if (shape.mesh.indices.size() > 0) { 2447 | shapes->push_back(shape); 2448 | } 2449 | 2450 | shape = shape_t(); 2451 | 2452 | // material = -1; 2453 | prim_group.clear(); 2454 | 2455 | std::vector names; 2456 | 2457 | while (!IS_NEW_LINE(token[0])) { 2458 | std::string str = parseString(&token); 2459 | names.push_back(str); 2460 | token += strspn(token, " \t\r"); // skip tag 2461 | } 2462 | 2463 | // names[0] must be 'g' 2464 | 2465 | if (names.size() < 2) { 2466 | // 'g' with empty names 2467 | if (warn) { 2468 | std::stringstream ss; 2469 | ss << "Empty group name. line: " << line_num << "\n"; 2470 | (*warn) += ss.str(); 2471 | name = ""; 2472 | } 2473 | } else { 2474 | std::stringstream ss; 2475 | ss << names[1]; 2476 | 2477 | // tinyobjloader does not support multiple groups for a primitive. 2478 | // Currently we concatinate multiple group names with a space to get 2479 | // single group name. 2480 | 2481 | for (size_t i = 2; i < names.size(); i++) { 2482 | ss << " " << names[i]; 2483 | } 2484 | 2485 | name = ss.str(); 2486 | } 2487 | 2488 | continue; 2489 | } 2490 | 2491 | // object name 2492 | if (token[0] == 'o' && IS_SPACE((token[1]))) { 2493 | // flush previous face group. 2494 | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, 2495 | triangulate, v); 2496 | (void)ret; // return value not used. 2497 | 2498 | if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || 2499 | shape.points.indices.size() > 0) { 2500 | shapes->push_back(shape); 2501 | } 2502 | 2503 | // material = -1; 2504 | prim_group.clear(); 2505 | shape = shape_t(); 2506 | 2507 | // @todo { multiple object name? } 2508 | token += 2; 2509 | std::stringstream ss; 2510 | ss << token; 2511 | name = ss.str(); 2512 | 2513 | continue; 2514 | } 2515 | 2516 | if (token[0] == 't' && IS_SPACE(token[1])) { 2517 | const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. 2518 | tag_t tag; 2519 | 2520 | token += 2; 2521 | 2522 | tag.name = parseString(&token); 2523 | 2524 | tag_sizes ts = parseTagTriple(&token); 2525 | 2526 | if (ts.num_ints < 0) { 2527 | ts.num_ints = 0; 2528 | } 2529 | if (ts.num_ints > max_tag_nums) { 2530 | ts.num_ints = max_tag_nums; 2531 | } 2532 | 2533 | if (ts.num_reals < 0) { 2534 | ts.num_reals = 0; 2535 | } 2536 | if (ts.num_reals > max_tag_nums) { 2537 | ts.num_reals = max_tag_nums; 2538 | } 2539 | 2540 | if (ts.num_strings < 0) { 2541 | ts.num_strings = 0; 2542 | } 2543 | if (ts.num_strings > max_tag_nums) { 2544 | ts.num_strings = max_tag_nums; 2545 | } 2546 | 2547 | tag.intValues.resize(static_cast(ts.num_ints)); 2548 | 2549 | for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { 2550 | tag.intValues[i] = parseInt(&token); 2551 | } 2552 | 2553 | tag.floatValues.resize(static_cast(ts.num_reals)); 2554 | for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { 2555 | tag.floatValues[i] = parseReal(&token); 2556 | } 2557 | 2558 | tag.stringValues.resize(static_cast(ts.num_strings)); 2559 | for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { 2560 | tag.stringValues[i] = parseString(&token); 2561 | } 2562 | 2563 | tags.push_back(tag); 2564 | 2565 | continue; 2566 | } 2567 | 2568 | if (token[0] == 's' && IS_SPACE(token[1])) { 2569 | // smoothing group id 2570 | token += 2; 2571 | 2572 | // skip space. 2573 | token += strspn(token, " \t"); // skip space 2574 | 2575 | if (token[0] == '\0') { 2576 | continue; 2577 | } 2578 | 2579 | if (token[0] == '\r' || token[1] == '\n') { 2580 | continue; 2581 | } 2582 | 2583 | if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && 2584 | token[2] == 'f') { 2585 | current_smoothing_id = 0; 2586 | } else { 2587 | // assume number 2588 | int smGroupId = parseInt(&token); 2589 | if (smGroupId < 0) { 2590 | // parse error. force set to 0. 2591 | // FIXME(syoyo): Report warning. 2592 | current_smoothing_id = 0; 2593 | } else { 2594 | current_smoothing_id = static_cast(smGroupId); 2595 | } 2596 | } 2597 | 2598 | continue; 2599 | } // smoothing group id 2600 | 2601 | // Ignore unknown command. 2602 | } 2603 | 2604 | // not all vertices have colors, no default colors desired? -> clear colors 2605 | if (!found_all_colors && !default_vcols_fallback) { 2606 | vc.clear(); 2607 | } 2608 | 2609 | if (greatest_v_idx >= static_cast(v.size() / 3)) { 2610 | if (warn) { 2611 | std::stringstream ss; 2612 | ss << "Vertex indices out of bounds (line " << line_num << ".)\n" 2613 | << std::endl; 2614 | (*warn) += ss.str(); 2615 | } 2616 | } 2617 | if (greatest_vn_idx >= static_cast(vn.size() / 3)) { 2618 | if (warn) { 2619 | std::stringstream ss; 2620 | ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n" 2621 | << std::endl; 2622 | (*warn) += ss.str(); 2623 | } 2624 | } 2625 | if (greatest_vt_idx >= static_cast(vt.size() / 2)) { 2626 | if (warn) { 2627 | std::stringstream ss; 2628 | ss << "Vertex texcoord indices out of bounds (line " << line_num << ".)\n" 2629 | << std::endl; 2630 | (*warn) += ss.str(); 2631 | } 2632 | } 2633 | 2634 | bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, 2635 | triangulate, v); 2636 | // exportGroupsToShape return false when `usemtl` is called in the last 2637 | // line. 2638 | // we also add `shape` to `shapes` when `shape.mesh` has already some 2639 | // faces(indices) 2640 | if (ret || shape.mesh.indices 2641 | .size()) { // FIXME(syoyo): Support other prims(e.g. lines) 2642 | shapes->push_back(shape); 2643 | } 2644 | prim_group.clear(); // for safety 2645 | 2646 | if (err) { 2647 | (*err) += errss.str(); 2648 | } 2649 | 2650 | attrib->vertices.swap(v); 2651 | attrib->vertex_weights.swap(v); 2652 | attrib->normals.swap(vn); 2653 | attrib->texcoords.swap(vt); 2654 | attrib->texcoord_ws.swap(vt); 2655 | attrib->colors.swap(vc); 2656 | 2657 | return true; 2658 | } 2659 | 2660 | bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, 2661 | void *user_data /*= NULL*/, 2662 | MaterialReader *readMatFn /*= NULL*/, 2663 | std::string *warn, /* = NULL*/ 2664 | std::string *err /*= NULL*/) { 2665 | std::stringstream errss; 2666 | 2667 | // material 2668 | std::map material_map; 2669 | int material_id = -1; // -1 = invalid 2670 | 2671 | std::vector indices; 2672 | std::vector materials; 2673 | std::vector names; 2674 | names.reserve(2); 2675 | std::vector names_out; 2676 | 2677 | std::string linebuf; 2678 | while (inStream.peek() != -1) { 2679 | safeGetline(inStream, linebuf); 2680 | 2681 | // Trim newline '\r\n' or '\n' 2682 | if (linebuf.size() > 0) { 2683 | if (linebuf[linebuf.size() - 1] == '\n') 2684 | linebuf.erase(linebuf.size() - 1); 2685 | } 2686 | if (linebuf.size() > 0) { 2687 | if (linebuf[linebuf.size() - 1] == '\r') 2688 | linebuf.erase(linebuf.size() - 1); 2689 | } 2690 | 2691 | // Skip if empty line. 2692 | if (linebuf.empty()) { 2693 | continue; 2694 | } 2695 | 2696 | // Skip leading space. 2697 | const char *token = linebuf.c_str(); 2698 | token += strspn(token, " \t"); 2699 | 2700 | assert(token); 2701 | if (token[0] == '\0') continue; // empty line 2702 | 2703 | if (token[0] == '#') continue; // comment line 2704 | 2705 | // vertex 2706 | if (token[0] == 'v' && IS_SPACE((token[1]))) { 2707 | token += 2; 2708 | // TODO(syoyo): Support parsing vertex color extension. 2709 | real_t x, y, z, w; // w is optional. default = 1.0 2710 | parseV(&x, &y, &z, &w, &token); 2711 | if (callback.vertex_cb) { 2712 | callback.vertex_cb(user_data, x, y, z, w); 2713 | } 2714 | continue; 2715 | } 2716 | 2717 | // normal 2718 | if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { 2719 | token += 3; 2720 | real_t x, y, z; 2721 | parseReal3(&x, &y, &z, &token); 2722 | if (callback.normal_cb) { 2723 | callback.normal_cb(user_data, x, y, z); 2724 | } 2725 | continue; 2726 | } 2727 | 2728 | // texcoord 2729 | if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { 2730 | token += 3; 2731 | real_t x, y, z; // y and z are optional. default = 0.0 2732 | parseReal3(&x, &y, &z, &token); 2733 | if (callback.texcoord_cb) { 2734 | callback.texcoord_cb(user_data, x, y, z); 2735 | } 2736 | continue; 2737 | } 2738 | 2739 | // face 2740 | if (token[0] == 'f' && IS_SPACE((token[1]))) { 2741 | token += 2; 2742 | token += strspn(token, " \t"); 2743 | 2744 | indices.clear(); 2745 | while (!IS_NEW_LINE(token[0])) { 2746 | vertex_index_t vi = parseRawTriple(&token); 2747 | 2748 | index_t idx; 2749 | idx.vertex_index = vi.v_idx; 2750 | idx.normal_index = vi.vn_idx; 2751 | idx.texcoord_index = vi.vt_idx; 2752 | 2753 | indices.push_back(idx); 2754 | size_t n = strspn(token, " \t\r"); 2755 | token += n; 2756 | } 2757 | 2758 | if (callback.index_cb && indices.size() > 0) { 2759 | callback.index_cb(user_data, &indices.at(0), 2760 | static_cast(indices.size())); 2761 | } 2762 | 2763 | continue; 2764 | } 2765 | 2766 | // use mtl 2767 | if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { 2768 | token += 7; 2769 | std::stringstream ss; 2770 | ss << token; 2771 | std::string namebuf = ss.str(); 2772 | 2773 | int newMaterialId = -1; 2774 | if (material_map.find(namebuf) != material_map.end()) { 2775 | newMaterialId = material_map[namebuf]; 2776 | } else { 2777 | // { warn!! material not found } 2778 | if (warn && (!callback.usemtl_cb)) { 2779 | (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; 2780 | } 2781 | } 2782 | 2783 | if (newMaterialId != material_id) { 2784 | material_id = newMaterialId; 2785 | } 2786 | 2787 | if (callback.usemtl_cb) { 2788 | callback.usemtl_cb(user_data, namebuf.c_str(), material_id); 2789 | } 2790 | 2791 | continue; 2792 | } 2793 | 2794 | // load mtl 2795 | if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { 2796 | if (readMatFn) { 2797 | token += 7; 2798 | 2799 | std::vector filenames; 2800 | SplitString(std::string(token), ' ', filenames); 2801 | 2802 | if (filenames.empty()) { 2803 | if (warn) { 2804 | (*warn) += 2805 | "Looks like empty filename for mtllib. Use default " 2806 | "material. \n"; 2807 | } 2808 | } else { 2809 | bool found = false; 2810 | for (size_t s = 0; s < filenames.size(); s++) { 2811 | std::string warn_mtl; 2812 | std::string err_mtl; 2813 | bool ok = (*readMatFn)(filenames[s].c_str(), &materials, 2814 | &material_map, &warn_mtl, &err_mtl); 2815 | 2816 | if (warn && (!warn_mtl.empty())) { 2817 | (*warn) += warn_mtl; // This should be warn message. 2818 | } 2819 | 2820 | if (err && (!err_mtl.empty())) { 2821 | (*err) += err_mtl; 2822 | } 2823 | 2824 | if (ok) { 2825 | found = true; 2826 | break; 2827 | } 2828 | } 2829 | 2830 | if (!found) { 2831 | if (warn) { 2832 | (*warn) += 2833 | "Failed to load material file(s). Use default " 2834 | "material.\n"; 2835 | } 2836 | } else { 2837 | if (callback.mtllib_cb) { 2838 | callback.mtllib_cb(user_data, &materials.at(0), 2839 | static_cast(materials.size())); 2840 | } 2841 | } 2842 | } 2843 | } 2844 | 2845 | continue; 2846 | } 2847 | 2848 | // group name 2849 | if (token[0] == 'g' && IS_SPACE((token[1]))) { 2850 | names.clear(); 2851 | 2852 | while (!IS_NEW_LINE(token[0])) { 2853 | std::string str = parseString(&token); 2854 | names.push_back(str); 2855 | token += strspn(token, " \t\r"); // skip tag 2856 | } 2857 | 2858 | assert(names.size() > 0); 2859 | 2860 | if (callback.group_cb) { 2861 | if (names.size() > 1) { 2862 | // create const char* array. 2863 | names_out.resize(names.size() - 1); 2864 | for (size_t j = 0; j < names_out.size(); j++) { 2865 | names_out[j] = names[j + 1].c_str(); 2866 | } 2867 | callback.group_cb(user_data, &names_out.at(0), 2868 | static_cast(names_out.size())); 2869 | 2870 | } else { 2871 | callback.group_cb(user_data, NULL, 0); 2872 | } 2873 | } 2874 | 2875 | continue; 2876 | } 2877 | 2878 | // object name 2879 | if (token[0] == 'o' && IS_SPACE((token[1]))) { 2880 | // @todo { multiple object name? } 2881 | token += 2; 2882 | 2883 | std::stringstream ss; 2884 | ss << token; 2885 | std::string object_name = ss.str(); 2886 | 2887 | if (callback.object_cb) { 2888 | callback.object_cb(user_data, object_name.c_str()); 2889 | } 2890 | 2891 | continue; 2892 | } 2893 | 2894 | #if 0 // @todo 2895 | if (token[0] == 't' && IS_SPACE(token[1])) { 2896 | tag_t tag; 2897 | 2898 | token += 2; 2899 | std::stringstream ss; 2900 | ss << token; 2901 | tag.name = ss.str(); 2902 | 2903 | token += tag.name.size() + 1; 2904 | 2905 | tag_sizes ts = parseTagTriple(&token); 2906 | 2907 | tag.intValues.resize(static_cast(ts.num_ints)); 2908 | 2909 | for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { 2910 | tag.intValues[i] = atoi(token); 2911 | token += strcspn(token, "/ \t\r") + 1; 2912 | } 2913 | 2914 | tag.floatValues.resize(static_cast(ts.num_reals)); 2915 | for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { 2916 | tag.floatValues[i] = parseReal(&token); 2917 | token += strcspn(token, "/ \t\r") + 1; 2918 | } 2919 | 2920 | tag.stringValues.resize(static_cast(ts.num_strings)); 2921 | for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { 2922 | std::stringstream ss; 2923 | ss << token; 2924 | tag.stringValues[i] = ss.str(); 2925 | token += tag.stringValues[i].size() + 1; 2926 | } 2927 | 2928 | tags.push_back(tag); 2929 | } 2930 | #endif 2931 | 2932 | // Ignore unknown command. 2933 | } 2934 | 2935 | if (err) { 2936 | (*err) += errss.str(); 2937 | } 2938 | 2939 | return true; 2940 | } 2941 | 2942 | bool ObjReader::ParseFromFile(const std::string &filename, 2943 | const ObjReaderConfig &config) { 2944 | std::string mtl_search_path; 2945 | 2946 | if (config.mtl_search_path.empty()) { 2947 | // 2948 | // split at last '/'(for unixish system) or '\\'(for windows) to get 2949 | // the base directory of .obj file 2950 | // 2951 | if (filename.find_last_of("/\\") != std::string::npos) { 2952 | mtl_search_path = filename.substr(0, filename.find_last_of("/\\")); 2953 | } 2954 | } else { 2955 | mtl_search_path = config.mtl_search_path; 2956 | } 2957 | 2958 | valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, 2959 | filename.c_str(), mtl_search_path.c_str(), 2960 | config.triangulate, config.vertex_color); 2961 | 2962 | return valid_; 2963 | } 2964 | 2965 | bool ObjReader::ParseFromString(const std::string &obj_text, 2966 | const std::string &mtl_text, 2967 | const ObjReaderConfig &config) { 2968 | std::stringbuf obj_buf(obj_text); 2969 | std::stringbuf mtl_buf(mtl_text); 2970 | 2971 | std::istream obj_ifs(&obj_buf); 2972 | std::istream mtl_ifs(&mtl_buf); 2973 | 2974 | MaterialStreamReader mtl_ss(mtl_ifs); 2975 | 2976 | valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, 2977 | &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); 2978 | 2979 | return valid_; 2980 | } 2981 | 2982 | #ifdef __clang__ 2983 | #pragma clang diagnostic pop 2984 | #endif 2985 | } // namespace tinyobj 2986 | 2987 | #endif 2988 | --------------------------------------------------------------------------------