3D City v-1.0
Computer Graphics Project
model.h
Go to the documentation of this file.
1
2#pragma once
3
4#ifdef USEASSIMP
5#include "assimp/Importer.hpp"
6#include "assimp/postprocess.h"
7#include "assimp/scene.h"
8#endif
9
10#define STB_IMAGE_IMPLEMENTATION
11#include "stb_image.h"
12
13#include "tinyobj/tiny_obj_loader.h"
14
15#include <glm/gtc/matrix_transform.hpp>
16
17#include "drawable.h"
18#include "node.h"
19#include <fstream>
20#include <iostream>
21#include <unordered_map>
22#include <sstream>
23#include <string>
24#include <vector>
25
26static std::string directory;
27static std::vector<std::string> textures_loaded;
28static std::unordered_map<std::string, node> models_loaded;
29static std::unordered_map<std::string, node *> models_loaded_path;
30static std::unordered_map<std::string, Mesh> meshes_loaded;
31
35namespace Model {
36
37#ifdef USEASSIMP
38
39// checks all material textures of a given type and loads the textures if they're not loaded yet.
40// the required info is returned as a Texture struct.
41static std::string loadMaterialTexture(aiMaterial *mat, aiTextureType type, const std::string &typeName) {
42 std::vector<std::string> textures;
43
44 if (mat->GetTextureCount(type) > 1) {
45 printf("there were multiple textures but only single was processed\n");
46 /* code */
47 }
48
49 for (unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
50 aiString str;
51 mat->GetTexture(type, i, &str);
52 // check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
53 std::string relPath(str.C_Str());
54
55#ifndef _WIN32
56 size_t pos = 0;
57 pos = relPath.find("\\");
58 while (pos != std::string::npos) {
59 relPath.replace(pos, 1, "/");
60 pos = relPath.find("\\");
61 }
62#endif
63
64 textures.push_back(directory + "/" + relPath);
65 }
66
67 if (!textures.empty())
68 return textures[0];
69 return std::string();
70}
71
72static drawable<Vertex> *processMesh(aiMesh *mesh, const aiScene *scene) {
73 // data to fill
74 std::vector<Vertex> vertices;
75 std::vector<unsigned int> indices;
76 std::vector<std::string> textures;
77
78 // walk through each of the mesh's vertices
79 for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
80 Vertex vertex;
81 glm::vec3 vector; // we declare a placeholder vector since assimp uses its own vector class that doesn't directly convert to glm's vec3 class so we transfer the data to this placeholder glm::vec3 first.
82 // positions
83 vector.x = mesh->mVertices[i].x;
84 vector.y = mesh->mVertices[i].y;
85 vector.z = mesh->mVertices[i].z;
86 vertex.position = vector;
87 // normals
88 if (mesh->HasNormals()) {
89 vector.x = mesh->mNormals[i].x;
90 vector.y = mesh->mNormals[i].y;
91 vector.z = mesh->mNormals[i].z;
92 vertex.normal = vector;
93 }
94 // texture coordinates
95 if (mesh->mTextureCoords[0]) // does the mesh contain texture coordinates?
96 {
97 glm::vec2 vec;
98 // a vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't
99 // use models where a vertex can have multiple texture coordinates so we always take the first set (0).
100 vec.x = mesh->mTextureCoords[0][i].x;
101 vec.y = mesh->mTextureCoords[0][i].y;
102 vertex.texCoord = vec;
103 // tangent
104 vector.x = mesh->mTangents[i].x;
105 vector.y = mesh->mTangents[i].y;
106 vector.z = mesh->mTangents[i].z;
107 vertex.tangent = vector;
108 // bitangent
109 vector.x = mesh->mBitangents[i].x;
110 vector.y = mesh->mBitangents[i].y;
111 vector.z = mesh->mBitangents[i].z;
112 vertex.bitangent = vector;
113 } else
114 vertex.texCoord = glm::vec2(0.0f, 0.0f);
115
116 vertices.push_back(vertex);
117 }
118
119 // now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
120 for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
121 aiFace face = mesh->mFaces[i];
122 // retrieve all indices of the face and store them in the indices vector
123 for (unsigned int j = 0; j < face.mNumIndices; j++)
124 indices.push_back(face.mIndices[j]);
125 }
126 // process materials
127 aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
128
129 // 0. ambient maps
130 std::string ambientMaps = loadMaterialTexture(material, aiTextureType_AMBIENT, "texture_ambient");
131 textures.push_back(ambientMaps);
132 // 1. diffuse maps
133 std::string diffuseMaps = loadMaterialTexture(material, aiTextureType_DIFFUSE, "texture_diffuse");
134 textures.push_back(diffuseMaps);
135 // 2. specular maps
136 std::string specularMaps = loadMaterialTexture(material, aiTextureType_SPECULAR, "texture_specular");
137 textures.push_back(specularMaps);
138 // 3. normal maps
139 std::string normalMaps = loadMaterialTexture(material, aiTextureType_HEIGHT, "texture_normal");
140 textures.push_back(normalMaps);
141
142 std::string name(mesh->mName.C_Str());
143
144 while (meshes_loaded.find(name) != meshes_loaded.end()) {
145 name += "_copy";
146 }
147 meshes_loaded[name] = Mesh(vertices, indices, name, textures);
148 Mesh &m = meshes_loaded[name];
149
150 aiColor4D diffuse, specular;
151 float shininess = 0;
152 m.material.diffuseColor = AI_SUCCESS != aiGetMaterialColor(material, AI_MATKEY_COLOR_DIFFUSE, &diffuse) ? vec3(1, 0, 0) : vec3(diffuse.r, diffuse.g, diffuse.b);
153 m.material.specularColor = AI_SUCCESS != aiGetMaterialColor(material, AI_MATKEY_COLOR_SPECULAR, &specular) ? vec3(1, 1, 1) : vec3(specular.r, specular.g, specular.b);
154 m.material.shininess = AI_SUCCESS != aiGetMaterialFloat(material, AI_MATKEY_SHININESS, &shininess) ? 512 : shininess;
155
156 return &m;
157}
158
159// processes a node in a recursive fashion. Processes each individual mesh located at the node and repeats this process on its children nodes (if any).
160static void processNode(aiNode *node, const aiScene *scene, std::vector<drawable<Vertex> *> &meshes, int num) {
161 int temp = num;
162 // printf("processing %d node \n", temp);
163
164 // process each mesh located at the current node
165 for (unsigned int i = 0; i < node->mNumMeshes; i++) {
166 // the node object only contains indices to index the actual objects in the scene.
167 // the scene contains all the data, node is just to keep stuff organized (like relations between nodes).
168 aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
169 auto me = processMesh(mesh, scene);
170 meshes.push_back(me);
171 // printf("\t \t mesh %d processed\n", i);
172 }
173 // after we've processed all of the meshes (if any) we then recursively process each of the children nodes
174 for (unsigned int i = 0; i < node->mNumChildren; i++) {
175 processNode(node->mChildren[i], scene, meshes, ++num);
176 }
177 // printf("node %d processed \n", temp);
178}
179
180node *loadModel(std::string const &path, const std::string &name, bool flipUV = false) {
181
182 if (models_loaded_path.find(path) != models_loaded_path.end()) {
183 if (models_loaded_path[path] == &models_loaded[name]) {
184 std::cout << "already loaded same module with same name and path os returning refrence to previous obj\n";
186 }
187 auto previous_node = *models_loaded_path[path];
188 node temp;
189 for (auto &m : previous_node.meshes) {
190 Mesh temp_mesh;
191 temp_mesh.m_vertices = m->m_vertices;
192 temp_mesh.m_indices = m->m_indices;
193 temp_mesh.material = m->material;
194 auto name = m->name + "_copy";
195 while (meshes_loaded.find(name) != meshes_loaded.end())
196 name += "_copy";
197
198 temp_mesh.name = name;
199 meshes_loaded[temp_mesh.name] = std::move(temp_mesh);
200 temp.meshes.push_back(&meshes_loaded[name]);
201 }
202 models_loaded[name] = std::move(temp);
203 return &models_loaded[name];
204 }
205
206 if (models_loaded.find(name) != models_loaded.end()) {
207 return &models_loaded[name];
208 }
209 // read file via ASSIMP
210 Assimp::Importer importer;
211 auto flags = aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace;
212 if (flipUV) {
213 flags |= aiProcess_FlipUVs;
214 }
215 const aiScene *scene = importer.ReadFile(path, flags);
216 // check for errors
217 if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
218 {
219 std::cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << std::endl;
221 return nullptr;
222 }
223 // retrieve the directory path of the filepath
224 directory = path.substr(0, path.find_last_of('/'));
225 node temp;
226
227 // std::vector<drawable<Vertex>> meshes;
228
229 // process ASSIMP's root node recursively
230 processNode(scene->mRootNode, scene, temp.meshes, 0);
231
232 models_loaded[name] = std::move(temp);
233 models_loaded_path[path] = &models_loaded[name];
234 return &models_loaded[name];
235}
236#endif
237
241node *loadModel_obj(std::string const &path, const std::string &name, bool flipUV = false) {
242
243 if (models_loaded_path.find(path) != models_loaded_path.end()) {
244 if (models_loaded_path[path] == &models_loaded[name]) {
245 std::cout << "already loaded same model with same name and path so returning refrence to previous model\n";
247 }
248 auto previous_node = *models_loaded_path[path];
249 node temp;
250 for (auto &m : previous_node.meshes) {
251 Mesh temp_mesh;
252 temp_mesh.m_vertices = m->m_vertices;
253 temp_mesh.m_indices = m->m_indices;
254 temp_mesh.material = m->material;
255 auto name = m->name + "_copy";
256 while (meshes_loaded.find(name) != meshes_loaded.end())
257 name += "_copy";
258
259 temp_mesh.name = name;
260 meshes_loaded[temp_mesh.name] = std::move(temp_mesh);
261 temp.meshes.push_back(&meshes_loaded[name]);
262 }
263
264 models_loaded[name] = std::move(temp);
265 return &models_loaded[name];
266 }
267
268 if (models_loaded.find(name) != models_loaded.end()) {
269 return &models_loaded[name];
270 }
271
272 // retrieve the directory path of the filepath
273 directory = path.substr(0, path.find_last_of('/'));
274 node temp;
275
276 tinyobj::ObjReaderConfig reader_config;
277 reader_config.mtl_search_path = directory; // Path to material files
278 reader_config.triangulate = true; // Path to material files
279 directory += "/";
280
281 tinyobj::ObjReader reader;
282
283 if (!reader.ParseFromFile(path, reader_config)) {
284 if (!reader.Error().empty()) {
285 std::cerr << "TinyObjReader: " << reader.Error();
286 }
287 exit(1);
288 }
289
290 if (!reader.Warning().empty()) {
291 std::cout << "[WARNING]: TinyObjReader: " << reader.Warning();
292 }
293
294 if (!reader.Error().empty()) {
295 std::cout << "[Error]: TinyObjReader: " << reader.Error();
296 }
297
298 const tinyobj::attrib_t &attrib = reader.GetAttrib();
299 auto &shapes = reader.GetShapes();
300 auto &materials = reader.GetMaterials();
301
302 std::unordered_map<int, std::string> material_to_mesh_map;
303
304 // Loop over shapes
305 for (size_t s = 0; s < shapes.size(); s++) {
306
307 auto name = shapes[s].name;
308 Mesh *m1 = new Mesh();
309 bool loaded = false;
310
311 size_t index_offset = 0;
312 uint32_t current_index = 0;
313
314 // Loop over faces(polygon)
315 for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
316
317 // per-face material
318 int current_material_id = shapes[s].mesh.material_ids[f];
319 if ((current_material_id < 0) || (current_material_id >= static_cast<int>(materials.size()))) {
320 // Invaid material ID. Use default material.
321 current_material_id = materials.size() - 1; // Default material is added to the last item in `materials`.
322 }
323
324 m1->material.id = m1->material.id == -1 ? current_material_id : m1->material.id;
325 if (current_material_id != m1->material.id) {
326 name += materials[m1->material.id].name;
327 if (name == std::string("CubeMaterial.005")) {
329 }
330 m1->name = name;
331 meshes_loaded[name] = std::move(*m1);
332 temp.meshes.push_back(&meshes_loaded[name]);
333 current_index = 0;
334 Mesh *t = m1;
335 //delete m1;
336 if (material_to_mesh_map.find(current_material_id) == material_to_mesh_map.end()) {
337 m1 = new Mesh();
338 m1->material.id = current_material_id;
339 name = shapes[s].name;
340 } else {
341 m1 = &meshes_loaded[material_to_mesh_map[current_material_id]];
342 loaded = true;
343 name = shapes[s].name;
344 }
345
346 material_to_mesh_map[t->material.id] = name;
347 }
348
349 //if (m1->material.diffuse.m_data == nullptr && !materials[current_material_id].diffuse_texname.empty()) {
350 if (m1->material.diffuse.m_data == nullptr && current_material_id >= 0) {
351 unsigned char *m_LocalBuffer;
352 stbi_set_flip_vertically_on_load(1);
353 if (!materials[current_material_id].diffuse_texname.empty()) {
354 m_LocalBuffer = stbi_load((directory + materials[current_material_id].diffuse_texname).c_str(), &m1->material.diffuse.w, &m1->material.diffuse.h, &m1->material.diffuse.m_bpp, 0);
355 if (!m_LocalBuffer) {
356 std::cout << "texture file unable to load" << directory + materials[current_material_id].diffuse_texname << std::endl;
357 ASSERT(false);
358 }
359 m1->material.diffuse.m_data = new unsigned char[(size_t)m1->material.diffuse.w * m1->material.diffuse.h * m1->material.diffuse.m_bpp];
360 memcpy(m1->material.diffuse.m_data, m_LocalBuffer, (size_t)m1->material.diffuse.w * m1->material.diffuse.h * m1->material.diffuse.m_bpp * sizeof(unsigned char));
361 stbi_image_free(m_LocalBuffer);
362 }
363
364 if (!materials[current_material_id].specular_texname.empty()) {
365 m_LocalBuffer = stbi_load((directory + materials[current_material_id].specular_texname).c_str(), &m1->material.specular.w, &m1->material.specular.h, &m1->material.diffuse.m_bpp, 0);
366 if (!m_LocalBuffer) {
367 std::cout << "texture file unable to load" << directory + materials[current_material_id].specular_texname << std::endl;
368 ASSERT(false);
369 }
370 m1->material.specular.m_data = new unsigned char[(size_t)m1->material.specular.w * m1->material.specular.h * m1->material.specular.m_bpp];
371 memcpy(m1->material.specular.m_data, m_LocalBuffer, (size_t)m1->material.specular.w * m1->material.specular.h * m1->material.specular.m_bpp * sizeof(unsigned char));
372 stbi_image_free(m_LocalBuffer);
373 }
374 if (m1->material.diffuseColor == color(255)) {
375 m1->material.diffuseColor = color(materials[current_material_id].diffuse[0] * 255, materials[current_material_id].diffuse[1] * 255, materials[current_material_id].diffuse[2] * 255);
376 m1->material.specularColor = color(materials[current_material_id].specular[0] * 255, materials[current_material_id].specular[1] * 255, materials[current_material_id].specular[2] * 255);
377 m1->material.shininess = materials[current_material_id].shininess;
378 }
379 }
380
381 size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]);
382 // indices = shapes[s].mesh.indices;
383
384 // Loop over vertices in the face.
385 for (size_t v = 0; v < fv; v++) {
386 tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
387 Vertex vertex;
388 vertex.position = vec3(attrib.vertices[3 * size_t(idx.vertex_index) + 0], attrib.vertices[3 * size_t(idx.vertex_index) + 1], attrib.vertices[3 * size_t(idx.vertex_index) + 2]);
389
390 // Check if `normal_index` is zero or positive. negative = no normal data
391 if (idx.normal_index >= 0) {
392 vertex.normal = vec3(attrib.normals[3 * size_t(idx.normal_index) + 0], attrib.normals[3 * size_t(idx.normal_index) + 1], attrib.normals[3 * size_t(idx.normal_index) + 2]);
393 }
394
395 // Check if `texcoord_index` is zero or positive. negative = no texcoord data
396 if (idx.texcoord_index >= 0) {
397 vertex.texCoord = vec2(attrib.texcoords[2 * size_t(idx.texcoord_index) + 0], attrib.texcoords[2 * size_t(idx.texcoord_index) + 1]);
398 }
399 m1->m_vertices.push_back(vertex);
400 m1->m_indices.push_back(current_index++);
401 }
402
403 // if (!m1->material.normalMap.empty())
404 // {
405 // calculate_tangent2((&m1->m_vertices.back()) - 2);
406 // }
407 index_offset += fv;
408 }
409
410 if (!loaded) {
411 auto current_material_id = m1->material.id;
412 if ((current_material_id < 0) || (current_material_id >= static_cast<int>(materials.size()))) {
413 // Invaid material ID. Use default material.
414 current_material_id = materials.size() - 1; // Default material is added to the last item in `materials`.
415 }
416 if ((current_material_id >= 0)) {
417 name += materials[current_material_id].name;
418 }
419
420 while (meshes_loaded.find(name) != meshes_loaded.end()) {
421 name += "_copy";
422 }
423
424 m1->name = name;
425 meshes_loaded[name] = std::move(*m1);
426 temp.meshes.push_back(&meshes_loaded[name]);
427 }
428 }
429
430 models_loaded[name] = std::move(temp);
431 models_loaded_path[path] = &models_loaded[name];
432 return &models_loaded[name];
433}
434
435} // namespace Model
A general matrix class for any size of matrix.
Definition: maths.h:447
#define DEBUG_BREAK
Definition: core.h:30
#define ASSERT(x)
Definition: core.h:95
drawable< Vertex > Mesh
Definition: drawable.h:63
vec2_T< float > vec2
Definition: maths.h:153
vec3_T< float > vec3
Definition: maths.h:305
all functions related to model loading and managing
Definition: model.h:35
node * loadModel_obj(std::string const &path, const std::string &name, bool flipUV=false)
loads model with given path and register it with given name.
Definition: model.h:241
int id
Definition: material.h:21
color diffuseColor
Definition: material.h:27
texture diffuse
Definition: material.h:28
float shininess
Definition: material.h:26
texture specular
Definition: material.h:29
color specularColor
Definition: material.h:25
Definition: core.h:108
vec2 texCoord
Definition: core.h:111
vec3 normal
Definition: core.h:110
vec4 position
Definition: core.h:109
class that holds color with 8 bits per channel with 4 channels rgba.
Definition: core.h:165
this class represents all drawable entities that has a material and vertices
Definition: drawable.h:12
std::string name
Definition: drawable.h:18
std::vector< uint32_t > m_indices
Definition: drawable.h:15
Material material
Definition: drawable.h:16
std::vector< T > m_vertices
Definition: drawable.h:14
anything that can be transformed( has modelmatrix )
Definition: node.h:14
std::vector< drawable< Vertex > * > meshes
Definition: node.h:16
int h
Definition: material.h:13
int m_bpp
Definition: material.h:13
int w
Definition: material.h:13
unsigned char * m_data
Definition: material.h:11
T x
Definition: maths.h:26
T x
Definition: maths.h:162
T x
Definition: maths.h:314