DHART
Loading...
Searching...
No Matches
objloader.cpp
Go to the documentation of this file.
1
7
8#include <objloader.h>
9#include <Dense>
10#include <meshinfo.h>
11#define TINYOBJLOADER_IMPLEMENTATION
12#define TINYOBJLOADER_USE_DOUBLE
13#include <tiny_obj_loader.h>
14#include <robin_hood.h>
15#include <HFExceptions.h>
16#include <iostream>
17#include <vector>
18#include <filesystem>
19
20using std::vector;
21using std::array;
22using std::string;
23
24using tinyobj::ObjReader;
25
26// [nanoRT]
27namespace HF::nanoGeom {
28
29 bool LoadObj(Mesh& mesh, const char* filename) {
30 // Need to have mesh.vertices, mesh.faces, mesh.num_faces
31
32 tinyobj::attrib_t attrib;
33 std::vector<tinyobj::shape_t> shapes;
34 std::vector<tinyobj::material_t> materials;
35
36 std::string warn;
37 std::string err;
38
39 bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename);
40
41 // Check for errors in the loading
42 if (!err.empty()) {
43 std::cerr << " Loading: " << err << std::endl;
44 }
45 if (!ret) {
46 exit(1);
47 }
48
49 // Get the number of faces (likely number of triangles)
50 size_t num_faces = 0;
51 // Loop over shapes
52 for (size_t s = 0; s < shapes.size(); s++)
53 {
54 // Increment the number of faces by getting the size of the indices and dividing by 3 (number of verts per face)
55 // Must be triangles for this calculation to make sense (I think)
56 num_faces += shapes[s].mesh.indices.size() / 3;
57 }
58
59 // Set the number of vertices by finding the total size of the vertex array and dividing by 3 (number of axis in point: x,y,z)
60 size_t num_vertices = attrib.vertices.size() / 3;
61 mesh.num_faces = num_faces;
62 mesh.num_vertices = num_vertices;
63 // Set a new array of doubles to the size of the number of vertices times 3 (number of axis in point)
64 mesh.vertices = new double[num_vertices * 3];
65 // Set a new array of doubles to the size of the number of faces times 3 (number of verts per face)
66 // While this is called faces, its really the indices
67 mesh.faces = new unsigned int[num_faces * 3];
68
69 // Loop over shapes
70 for (size_t s = 0; s < shapes.size(); s++)
71 {
72 // Loop over faces(polygon)
73 size_t index_offset = 0;
74 for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++)
75 {
76 int fv = shapes[s].mesh.num_face_vertices[f];
77
78 // Loop over vertices in the face.
79 for (size_t v = 0; v < fv; v++)
80 {
81 // extract the index of the start of the mesh indices
82 tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
83 // Assign the xyz values from the flat array
84 tinyobj::real_t vx = attrib.vertices[3 * idx.vertex_index + 0];
85 tinyobj::real_t vy = attrib.vertices[3 * idx.vertex_index + 1];
86 tinyobj::real_t vz = attrib.vertices[3 * idx.vertex_index + 2];
87
88 // set vertices to the mesh data object
89 // This could probably all be done in one step with the above 3 lines
90 mesh.vertices[3 * idx.vertex_index + 0] = vx;
91 mesh.vertices[3 * idx.vertex_index + 1] = vy;
92 mesh.vertices[3 * idx.vertex_index + 2] = vz;
93 }
94 index_offset += fv;
95 }
96
97 // Loop through the mesh indices and assign each vertices index to the face index
98 for (size_t vi = 0; vi < shapes[s].mesh.indices.size(); vi++)
99 {
100 // This should make it clearer that this really is the mesh indices despite the name "faces"
101 mesh.faces[vi] = shapes[s].mesh.indices[vi].vertex_index;
102 }
103 }
104 return true;
105 }
106
107}
108// end [nanoRT]
109
110namespace HF::Geometry {
111
112 static robin_hood::unordered_map<string, string> test_model_paths{
113 {"teapot", "teapot.obj"},
114 {"plane", "plane.obj" },
115 {"big teapot", "big_teapot.obj" },
116 {"energy blob", "energy_blob.obj" },
117 {"sibenik", "sibenik.obj" },
118 };
119
120 tinyobj::ObjReader CreateReader(const std::string & path) {
121 // First, attempt to load the obj
122 tinyobj::ObjReader objloader;
123
124 // See if the filepath exists at all
125 if (!std::filesystem::exists(path)) {
126 std::cerr << " No file exists at " << path << std::endl;
128 }
129
130 tinyobj::ObjReaderConfig config;
131 config.triangulate = true;
132 // Load the OBJ using tinyobj.
133 objloader.ParseFromFile(path, config);
134
135 // Throw if the OBJ isn't valid
136 if (!objloader.Valid() || objloader.GetShapes().size() == 0)
137 {
138 std::cerr << " The given file did not produce a valid mesh " << std::endl;
140 }
141
142 return objloader;
143 }
144
145 template <typename T>
146 tinyobj_shape<T> MakeShape(const tinyobj::shape_t & shape) {
147 tinyobj_shape<T> out_shape;
148
149 const int num_indices = shape.mesh.indices.size();
150 out_shape.indices.resize(num_indices);
151 for (int i = 0; i < num_indices; i++)
152 out_shape.indices[i] = shape.mesh.indices[i].vertex_index;
153
154 out_shape.mat_ids = shape.mesh.material_ids;
155 return out_shape;
156 }
157
158 template <typename T>
159 vector<tinyobj_shape<T>> MakeShapes(const vector<tinyobj::shape_t>& shapes) {
160 vector<tinyobj_shape<T>> out_shapes(shapes.size());
161
162 for (int i = 0; i < shapes.size(); i++)
163 out_shapes[i] = MakeShape<T>(shapes[i]);
164
165 return out_shapes;
166 }
167
168 vector<tinyobj_material> MakeMaterials(const vector<tinyobj::material_t> & materials) {
169 vector<tinyobj_material> out_mats(materials.size());
170
171 for (int i = 0; i < materials.size(); i++)
172 out_mats[i].name = materials[i].name;
173
174 return out_mats;
175 }
176
178 {
180 ObjReader reader = CreateReader(path);
181
182 // get information from the reader.
183 auto mats = reader.GetMaterials(); // Materials of the mesh at path
184 tinyobj::attrib_t attributes = reader.GetAttrib(); // Attributes of the mesh at path
185 vector<tinyobj::shape_t> shapes = reader.GetShapes(); // Submeshes of the mesh at path
186
187 // Get the info about shapes for this mesh
188 out_obj.attributes.vertices = attributes.vertices;
189 out_obj.shapes = MakeShapes<double>(shapes);
190 out_obj.materials = MakeMaterials(mats);
191
192 return out_obj;
193 }
194
195 vector<MeshInfo<float>> LoadMeshObjects(std::string path, GROUP_METHOD gm, bool change_coords, int scale)
196 {
197 // First, attempt to load the obj
198 tinyobj::ObjReader objloader;
199
200 // See if the filepath exists at all
201 if (!std::filesystem::exists(path)) {
202 std::cerr << " No file exists at " << path << std::endl;
204 }
205
206 // Load the OBJ using tinyobj.
207 objloader.ParseFromFile(path);
208
209 // Throw if the OBJ isn't valid
210 if (!objloader.Valid() || objloader.GetShapes().size() == 0)
211 {
212 std::cerr << " The given file did not produce a valid mesh " << std::endl;
214 }
215
216 // get the materials, attributes, and shapes
217 vector<MeshInfo<float>> MI; // Vector of meshinf
218 auto mats = objloader.GetMaterials(); // Materials of the mesh at path
219 tinyobj::attrib_t attributes = objloader.GetAttrib(); // Attributes of the mesh at path
220 vector<tinyobj::shape_t> shapes = objloader.GetShapes(); // Submeshes of the mesh at path
221 const vector<tinyobj::real_t>& verts = attributes.vertices; // Vertices of the mesh at path.
222
223 // This will behave differently depending on the group_type being used.
224 switch (gm) {
226 // A single mesh will just need to have its index arrays combined
227 int id = 0;
228 std::string name = "EntireFile";
229
230 // Must be included in the other method types. It's not clear why the float conversion
231 // wasn't done above with the attributes.vertices
232 auto vert_array = static_cast<vector<double>>(verts);
233 auto vert_scaled = vert_array;
234 // Multiply each element of array by the user defined scale value (default of 1 which does not change vertex)
235 std::transform(vert_array.begin(), vert_array.end(), vert_scaled.begin(),[&](float i) { return i * scale; });
236 // assign the float vector to the scaled method
237
238 // Cast to float
239 vector<float> vertexes(vert_array.size());
240 for (int i = 0; i < vert_array.size(); i++)
241 vertexes[i] = static_cast<float>(vert_array[i]);
242
243 vector<int> indices;
244
245 // Count total indexes
246 int index_count = 0;
247 for (auto& shape : shapes)
248 index_count += shape.mesh.indices.size();
249
250 // Create a giant vector based on those indices
251 vector<int> index_array(index_count);
252
253 // Copy index arrays from every shape in shapes into the single index array we defined previously.
254 int last_index = 0;
255 for (auto& shape : shapes)
256 for (auto& this_shape_index : shape.mesh.indices)
257 index_array[last_index++] = this_shape_index.vertex_index;
258
259 // Add this mesh to the output_array
260 MI.emplace_back(MeshInfo(vertexes, index_array, 0, name));
261
262 // Change from Y-up to Z-up if specified.
263 if (change_coords) MI[0].ConvertToRhinoCoordinates();
264 break;
265 }
266
268 // Each group represents a different mesh.
269
270 // Allocate a space for every shape in shapes.
271 // Note: Shapes are OBJ groups.
272 MI.resize(shapes.size());
273 vector<array<float, 3>> current_vertices;
274
275 // Iterate through every shape in shapes.
276 for (int k = 0; k < shapes.size(); k++) {
277 auto& shape = shapes[k];
278 // Set MeshInfo parameters for shape
279 std::string name = shape.name;
280 int id = k;
281 auto& indicies = shape.mesh.indices;
282
283 // Add vertices to shape's list of vertices.
284 for (int i = 0; i < indicies.size(); i++) {
285 tinyobj::index_t idx = indicies[i];
286
287 int vertex_index = 3 * idx.vertex_index;
288 float x = verts[vertex_index];
289 float y = verts[1 + vertex_index];
290 float z = verts[2 + vertex_index];
291
292 current_vertices.emplace_back(array<float, 3>{x, y, z});
293 }
294
295 // Finally add this mesh to the list of meshes, then rotate it to Z-up if specified.
296 MI[k] = MeshInfo(current_vertices, id, name);
297 if (change_coords) MI[k].ConvertToRhinoCoordinates();
298 }
299 break;
300 }
302 // If there are no materials, just put every vert into the same mesh
303 if (mats.empty()) {
304 std::cerr << "[C++] No materials found in model " << path << ". Grouping by obj group";
305 return LoadMeshObjects(path, BY_GROUP);
306 }
307
308 // Create a vector of [mesh_id:"material"] for every unique material
309 MI.resize(mats.size());
310 vector<vector< array<float, 3 >>> verts_by_mat_id(mats.size());
311
312 // Create and fill a vector of names for each material.
313 vector<std::string> names;
314 for (int i = 0; i < mats.size(); i++)
315 names.emplace_back(path + "/" + mats[i].name);
316
317 // Loop through every shape to fill the vector
318 for (int k = 0; k < shapes.size(); k++) {
319 auto& shape = shapes[k];
320 vector<int>& mat_ids = shape.mesh.material_ids;
321 vector<tinyobj::index_t>& indicies = shape.mesh.indices;
322
323 // Iterate through every index in indices.
324 for (int i = 0; i < indicies.size(); i++) {
325 tinyobj::index_t idx = indicies[i];
326
327 int vertex_index = 3 * idx.vertex_index;
328
329 float x = verts[vertex_index];
330 float y = verts[1 + vertex_index];
331 float z = verts[2 + vertex_index];
332
333 // Get the index of this face (equal to i/3) since every 3 indices is a face
334 int face = static_cast<int>(floor(i / 3));
335
336 // Get the material index of face.
337 if (mats.size() > 0 && mat_ids.size() > face) {
338 int mat_index = mat_ids[face];
339
340 verts_by_mat_id[mat_index].emplace_back(array<float, 3>{x, y, z});
341 }
342 }
343 }
344
345 // Reassign meshids and cull materials with no geometry.
346 for (int i = 0; i < names.size(); i++) {
347 auto& mesh = verts_by_mat_id[i];
348 if (mesh.empty()) continue; // Ignore unused materials
349
350 // add to list;
351 MI.push_back(MeshInfo(mesh, i, names[i]));
352 if (change_coords) MI[i].ConvertToRhinoCoordinates();
353 }
354 break;
355 }
356
357 // Throw because we were given an invalid group mode.
358 default:
359 std::cerr << "Mesh group mode" << gm << " doesn't exist!" << std::endl;
360 throw std::exception();
361 break;
362 }
363
364 // Throw if the obj file didn't contain any geometry.
365 if (MI.size() == 0) {
367 }
368 return MI;
369 }
370
371 vector<array<float, 3>> LoadRawVertices(std::string path)
372 {
373 // First, attempt to load the obj
374 tinyobj::ObjReader objloader;
375 objloader.ParseFromFile(path);
376 if (!objloader.Valid()) throw HF::Exceptions::InvalidOBJ();
377
378 // get the materials, attributes, and shapes
379 auto mats = objloader.GetMaterials();
380 tinyobj::attrib_t attributes = objloader.GetAttrib();
381 vector<tinyobj::shape_t> shapes = objloader.GetShapes();
382 vector<tinyobj::real_t>& verts = attributes.vertices;
383
384 // Create out_verts array
385 vector<array<float, 3>> out_verts;
386
387 // iterate through every shape in shapes
388 for (auto& shape : shapes) {
389 auto& indicies = shape.mesh.indices;
390
391 // Copy into a new array.
392 for (int i = 0; i < indicies.size(); i++) {
393 tinyobj::index_t idx = indicies[i];
394
395 int vertex_index = 3 * idx.vertex_index;
396
397 float x = verts[vertex_index];
398 float y = verts[1 + vertex_index];
399 float z = verts[2 + vertex_index];
400 out_verts.emplace_back(array<float, 3>{x, y, z});
401 }
402 }
403
404 return out_verts;
405 }
406
407 std::string GetTestOBJPath(std::string key)
408 {
409 return test_model_paths.at(key);
410 }
411
412 vector<MeshInfo<float>> LoadMeshObjects(vector<std::string>& path, GROUP_METHOD gm, bool change_coords, int scale)
413 {
414 // Create a vector of vectors and gather all individual results
415 vector<vector<MeshInfo<float>>> MeshObjects(path.size());
416 for (int i = 0; i < path.size(); i++)
417 MeshObjects[i] = LoadMeshObjects(path[i], gm, change_coords, scale);
418
419 // Compress all vectors into a single mesh and reassign meshid.
420 vector<MeshInfo<float>> MI;
421 int lid = 0;
422 for (int i = 0; i < MeshObjects.size(); i++) {
423 for (auto& mi : MeshObjects[i]) {
424 MI.push_back(mi);
425 MI.back().SetMeshID(lid++);
426 }
427 }
428
429 return MI;
430 }
431}
Contains definitions for the Exceptions namespace.
Contains definitions for the MeshInfo class.
Contains definitions for the Geometry namespace.
Manipulate and load geometry from disk.
Definition: meshinfo.cpp:21
vector< tinyobj_shape< T > > MakeShapes(const vector< tinyobj::shape_t > &shapes)
Definition: objloader.cpp:159
vector< MeshInfo< float > > LoadMeshObjects(std::string path, GROUP_METHOD gm, bool change_coords, int scale)
Create MeshInfo instances from the OBJ at path.
Definition: objloader.cpp:195
std::vector< int > mat_ids
Definition: objloader.h:81
static robin_hood::unordered_map< string, string > test_model_paths
Definition: objloader.cpp:112
tinyobj_shape< T > MakeShape(const tinyobj::shape_t &shape)
Definition: objloader.cpp:146
std::vector< tinyobj_material > materials
Definition: objloader.h:92
tinyobj_attr< T > attributes
Definition: objloader.h:91
std::vector< tinyobj_shape< T > > shapes
Definition: objloader.h:90
tinyobj_geometry< double > LoadMeshesFromTinyOBJ(std::string path)
Definition: objloader.cpp:177
GROUP_METHOD
Method of grouping submeshes in OBJ files.
Definition: objloader.h:64
@ ONLY_FILE
Treat all the geometry in the file as a single mesh.
Definition: objloader.h:65
@ BY_MATERIAL
Create a new MeshInfo instance for every different material in the file.
Definition: objloader.h:67
@ BY_GROUP
Create a new MeshInfo instance for every OBJ group in the file.
Definition: objloader.h:66
vector< tinyobj_material > MakeMaterials(const vector< tinyobj::material_t > &materials)
Definition: objloader.cpp:168
std::vector< int > indices
Definition: objloader.h:80
std::string GetTestOBJPath(std::string key)
Get the path to the OBJ with the given key.
Definition: objloader.cpp:407
vector< array< float, 3 > > LoadRawVertices(std::string path)
Load a list of vertices directly from an OBJ file.
Definition: objloader.cpp:371
tinyobj::ObjReader CreateReader(const std::string &path)
Definition: objloader.cpp:120
unsigned int * faces
[xyz] * 3(triangle) * num_faces
Definition: meshinfo.h:28
size_t num_faces
Definition: meshinfo.h:21
size_t num_vertices
Definition: meshinfo.h:20
double * vertices
Definition: meshinfo.h:22
bool LoadObj(Mesh &mesh, const char *filename)
Definition: objloader.cpp:29
Thrown when desired file is not found.
Definition: HFExceptions.h:56
The OBJ file was not valid.
Definition: HFExceptions.h:65
A collection of vertices and indices representing geometry.
Definition: meshinfo.h:124