diff --git a/log750-lab.pro b/log750-lab.pro index 5699035..0cb26d0 100644 --- a/log750-lab.pro +++ b/log750-lab.pro @@ -29,7 +29,8 @@ SOURCES += QGLViewer/camera.cpp \ src/viewer/simpleViewer.cpp \ src/glnodes/glnode.cpp \ src/glnodes/shapes.cpp \ - src/glnodes/scenegroup.cpp + src/glnodes/scenegroup.cpp \ + src/libs/OBJLoader.cpp HEADERS += QGLViewer/camera.h \ QGLViewer/config.h \ @@ -49,7 +50,8 @@ HEADERS += QGLViewer/camera.h \ src/glnodes/shapes.h \ src/interfaces/ivisitable.h \ src/interfaces/visitor.h \ - src/glnodes/scenegroup.h + src/glnodes/scenegroup.h \ + src/libs/OBJLoader.h DISTFILES += src/shaders/basicShader.vert \ src/shaders/basicShader.frag \ diff --git a/src/libs/OBJLoader.cpp b/src/libs/OBJLoader.cpp new file mode 100644 index 0000000..3ca3278 --- /dev/null +++ b/src/libs/OBJLoader.cpp @@ -0,0 +1,436 @@ +#include "OBJLoader.h" + +#include +#include +#include + +using namespace OBJLoader; + +namespace +{ + // 2D/3D point data structures + struct Point3D + { + Point3D() : x(0), y(0), z(0) {} + + float x,y,z; + }; + + struct Point2D + { + Point2D() : x(0), y(0) {} + + float x,y; + }; + + // Extract path from a string + std::string extractPath(const std::string& filepathname) + { + std::size_t pos = filepathname.find_last_of("/\\"); + + if (pos == std::string::npos) + return std::string("."); + + return filepathname.substr(0, pos); + } +} + +//-------------------------------------------------------------------------------------------------- +// Constructors / Destructors +Loader::Loader() + : _isLoaded(false) +{} + +Loader::Loader(const std::string& filename) + : _isLoaded(false) +{ + loadFile(filename); +} + +Loader::~Loader() +{} + +//-------------------------------------------------------------------------------------------------- +// Load file +bool Loader::loadFile(const std::string& filename) +{ + // Clear current data + unload(); + + // Open the input file + std::ifstream file(filename, std::ifstream::in); + if (!file.is_open()) + { + std::cout << "Error: Failed to open file " << filename << " for reading!" << std::endl; + return false; + } + + // Extract path. It will be useful later when loading the mtl file + std::string path = extractPath(filename); + + // Create the default material + Material defaultMat; + defaultMat.Ka[0] = 1.0; defaultMat.Ka[1] = 1.0; defaultMat.Ka[2] = 1.0; defaultMat.Ka[3] = 1.0; + defaultMat.Ke[0] = 0.0; defaultMat.Ke[1] = 0.0; defaultMat.Ke[2] = 0.0; defaultMat.Ke[3] = 1.0; + defaultMat.Kd[0] = 1.0; defaultMat.Kd[1] = 1.0; defaultMat.Kd[2] = 1.0; defaultMat.Kd[3] = 1.0; + defaultMat.Ks[0] = 1.0; defaultMat.Ks[1] = 1.0; defaultMat.Ks[2] = 1.0; defaultMat.Ks[3] = 1.0; + defaultMat.Kn = 128; + defaultMat.name = "(Default)"; + _materials.push_back(defaultMat); + + + unsigned int currentMaterial = 0; + + // Create default mesh (default group) + Mesh defaultMesh; + _meshes.push_back(defaultMesh); + + unsigned int currentMesh = 0; + + // Create vertices' position, normal, and uv lists with default values + std::vector vertices(1); + std::vector normals(1); + std::vector uvs(1); + + // Read file + std::string line; + while (std::getline(file, line)) + { + if (line[0] == '#') + { + // Comments... just ignore the line + continue; + } + else if (line[0] == 'v' && line[1] == ' ') + { + // Vertex! Add it to the list. + Point3D v; + std::stringstream ss(line.substr(2)); + ss >> v.x >> v.y >> v.z; + vertices.push_back(v); + + } + else if (line[0] == 'v' && line[1] == 'n') + { + // Normal! Add it to the list. + Point3D n; + std::stringstream ss(line.substr(3)); + ss >> n.x >> n.y >> n.z; + normals.push_back(n); + } + else if (line[0] == 'v' && line[1] == 't') + { + // Tex coord! Add it to the list + Point2D uv; + std::stringstream ss(line.substr(3)); + ss >> uv.x >> uv.y; + uvs.push_back(uv); + } + else if (line[0] == 'u') + { + // usemtl! First, get the material's name + std::string name; + std::string dummy; + std::stringstream ss(line); + ss >> dummy >> name; + + // Find it, and attach it to the current mesh + currentMaterial = findMaterial(name); + _meshes[currentMesh].materialID = currentMaterial; + } + else if (line[0] == 'g') + { + // Group! Set it as the current mesh + std::string dummy; + std::string name; + std::stringstream ss(line); + ss >> dummy >> name; + + currentMesh = getMesh(name); + _meshes[currentMesh].materialID = currentMaterial; + } + else if (line[0] == 'f') + { + // Face! First, get its vertices data + std::string vertexData; + std::string dummy; + std::stringstream ssLine(line.substr(2)); + std::vector vertexIDs; + std::vector uvIDs; + std::vector normalIDs; + while (std::getline(ssLine, vertexData, ' ')) + { + unsigned int index = vertexIDs.size(); + vertexIDs.push_back(0); + uvIDs.push_back(0); + normalIDs.push_back(0); + + std::stringstream ss(vertexData); + std::string stringVal; + std::getline(ss, stringVal, '/'); + std::stringstream ss2(stringVal); + ss2 >> vertexIDs[index]; + + std::getline(ss, stringVal, '/'); + std::stringstream ss3(stringVal); + ss3 >> uvIDs[index]; + + std::getline(ss, stringVal, '/'); + std::stringstream ss4(stringVal); + ss4 >> normalIDs[index]; + } + + // Create first triangle + if (vertexIDs.size() < 3) + continue; + + for (unsigned int i=0; i<3; ++i) + { + Vertex v; + v.position[0] = vertices[vertexIDs[i]].x; + v.position[1] = vertices[vertexIDs[i]].y; + v.position[2] = vertices[vertexIDs[i]].z; + + v.normal[0] = normals[normalIDs[i]].x; + v.normal[1] = normals[normalIDs[i]].y; + v.normal[2] = normals[normalIDs[i]].z; + + v.uv[0] = uvs[uvIDs[i]].x; + v.uv[1] = uvs[uvIDs[i]].y; + + _meshes[currentMesh].vertices.push_back(v); + } + + // Create subsequent triangles (1 per additional vertices) + // Note: These triangles are created using a triangle fan approach + for (unsigned int i=3; i> dummy >> filename; + + // Add path to filename + std::string pathname = path; +#ifdef Q_OS_WIN32 + pathname.append("\\"); +#else + pathname.append("/"); +#endif + pathname.append(filename); + + // Load file + loadMtlFile(pathname); + } + } + + // Everything is loaded! Now remove empty meshes (this generally happens with the default group) + std::vector::iterator it = _meshes.begin(); + while (it != _meshes.end()) + { + if ((*it).vertices.size() == 0) + { + it = _meshes.erase(it); + } + else + { + ++it; + } + } + + + // Close file + file.close(); + _isLoaded = true; + + return true; +} + +//-------------------------------------------------------------------------------------------------- +// Load material file +void Loader::loadMtlFile(const std::string& filename) +{ + // Open the input file + std::ifstream file(filename, std::ifstream::in); + if (!file.is_open()) + { + std::cout << "Error: Failed to open material file " << filename << " for reading!" << std::endl; + return; + } + + // Read file + std::string line; + unsigned int currentMaterial = 0; + while (std::getline(file, line)) + { + if (line[0] == '#') + { + // Comments... just ignore this line + continue; + } + else if (line[0] == 'n') + { + // newmtl! Create the new material + Material newMtl; + newMtl.Ka[0] = 0.0; newMtl.Ka[1] = 0.0; newMtl.Ka[2] = 0.0; newMtl.Ka[3] = 0.0; + newMtl.Ke[0] = 0.0; newMtl.Ke[1] = 0.0; newMtl.Ke[2] = 0.0; newMtl.Ke[3] = 0.0; + newMtl.Kd[0] = 0.0; newMtl.Kd[1] = 0.0; newMtl.Kd[2] = 0.0; newMtl.Kd[3] = 0.0; + newMtl.Ks[0] = 0.0; newMtl.Ks[1] = 0.0; newMtl.Ks[2] = 0.0; newMtl.Ks[3] = 0.0; + newMtl.Kn = 0; + + // Get its name + std::string dummy; + std::stringstream ss(line); + ss >> dummy >> newMtl.name; + + // Add it to the list and set as current material + currentMaterial = _materials.size(); + _materials.push_back(newMtl); + } + else if (line[0] == 'N') + { + // Shininess + std::stringstream ss(line); + std::string dummy; + float shininess; + ss >> dummy >> shininess; + + // Change from range [0, 1000] to range [0, 128] + shininess /= 1000.0; + shininess *= 128.0; + + _materials[currentMaterial].Kn = shininess; + } + else if (line[0] == 'K') + { + Material& mat = _materials[currentMaterial]; + std::string dummy; + std::stringstream ss(line); + + if (line[1] == 'd') + { + // Diffuse coefficient + ss >> dummy >> mat.Kd[0] >> mat.Kd[1] >> mat.Kd[2]; + mat.Kd[3] = 1.0f; + } + else if (line[1] == 's') + { + // Diffuse coefficient + ss >> dummy >> mat.Ks[0] >> mat.Ks[1] >> mat.Ks[2]; + mat.Ks[3] = 1.0f; + } + else if (line[1] == 'a') + { + // Diffuse coefficient + ss >> dummy >> mat.Ka[0] >> mat.Ka[1] >> mat.Ka[2]; + mat.Ka[3] = 1.0f; + } + else if (line[1] == 'e') + { + // Diffuse coefficient + ss >> dummy >> mat.Ke[0] >> mat.Ke[1] >> mat.Ke[2]; + mat.Ke[3] = 1.0f; + } + } + } + + // Close file +} + +//-------------------------------------------------------------------------------------------------- +// Find a material by its name +unsigned int Loader::findMaterial(const std::string& name) +{ + unsigned int id = 0; + for (unsigned int i=0; i<_materials.size(); ++i) + { + const Material& mat = _materials[i]; + if (mat.name.compare(name) == 0) + { + id = i; + break; + } + } + + return id; +} + +//-------------------------------------------------------------------------------------------------- +// Find a mesh by its name +unsigned int Loader::getMesh(const std::string& name) +{ + unsigned int id = 0; + bool found = false; + for (unsigned int i=0; i<_meshes.size(); ++i) + { + const Mesh& mesh = _meshes[i]; + if (mesh.name.compare(name) == 0) + { + id = i; + found = true; + } + } + + if (!found) + { + Mesh newMesh; + newMesh.name = name; + + id = _meshes.size(); + _meshes.push_back(newMesh); + } + + return id; +} + +//-------------------------------------------------------------------------------------------------- +// Clear data +void Loader::unload() +{ + // Clear everything! + _meshes.clear(); + _materials.clear(); + _isLoaded = false; +} diff --git a/src/libs/OBJLoader.h b/src/libs/OBJLoader.h new file mode 100644 index 0000000..37d7b5d --- /dev/null +++ b/src/libs/OBJLoader.h @@ -0,0 +1,67 @@ +#ifndef OBJLOADER_H +#define OBJLOADER_H + +#include +#include + +namespace OBJLoader +{ + // Structure used to store a material's properties + struct Material + { + float Ka[4]; // Ambient color + float Ke[4]; // Emissive color + float Kd[4]; // Diffuse color + float Ks[4]; // Specular color + float Kn; // Specular exponent + + std::string name; // Material's name + }; + + // Structure used to store a vertex + struct Vertex + { + float position[3]; + float normal[3]; + float uv[2]; + }; + + // Structure used to store a mesh data. + // Each triplet of vertices forms a triangle. + struct Mesh + { + Mesh() : materialID(0), name("") {} + + std::vector vertices; + unsigned int materialID; + std::string name; + }; + + // Class responsible for loading all the meshes included in an OBJ file + class Loader + { + public: + Loader(); + Loader(const std::string& filename); + ~Loader(); + + bool loadFile(const std::string& filename); + bool isLoaded() const { return _isLoaded; } + void unload(); + + const std::vector& getMeshes() const { return _meshes; } + const std::vector& getMaterials() const { return _materials; } + + private: + void loadMtlFile(const std::string& filename); + unsigned int findMaterial(const std::string& name); + unsigned int getMesh(const std::string& name); + + std::vector _meshes; + std::vector _materials; + + bool _isLoaded; + }; +} + +#endif // OBJLOADER_H diff --git a/src/shaders/basicShader.frag b/src/shaders/basicShader.frag index d1eec2b..c0bf6cc 100644 --- a/src/shaders/basicShader.frag +++ b/src/shaders/basicShader.frag @@ -30,7 +30,7 @@ vec4 calcDirLight(vec4 tex, vec3 fPos, vec3 fNorm) { if(useNormalMap) { vec3 vNorm = texture(texNormal, texCoords).rgb; - nfNormal = normalize(normalMatrix * vNorm); + nfNormal = -normalize(normalMatrix * vNorm); } else { nfNormal = normalize(fNorm); } @@ -60,7 +60,7 @@ vec4 calcPointLight(vec4 tex, vec3 fPos, vec3 fNorm, int i) { if(useNormalMap) { vec3 vNorm = texture(texNormal, texCoords).rgb; - nfNormal = normalize(normalMatrix * vNorm); + nfNormal = -normalize(normalMatrix * vNorm); } else { nfNormal = normalize(fNorm); } diff --git a/src/shaders/objShader.frag b/src/shaders/objShader.frag index e69de29..c6512d1 100644 --- a/src/shaders/objShader.frag +++ b/src/shaders/objShader.frag @@ -0,0 +1,87 @@ +#version 400 core +uniform vec3 Kd; +uniform vec3 Ks; +uniform float Kn; + +#version 400 core +uniform sampler2D texCol; +uniform sampler2D texNormal; + +uniform vec3 lDirection; +uniform bool isPhong; +uniform vec3 pointLight[3]; +uniform vec4 pointLightCol[3]; +uniform mat3 normalMatrix; + +in vec3 fNormal; +in vec3 fPosition; + +out vec4 fColor; + + +vec4 calcDirLight(vec4 tex, vec3 fPos, vec3 fNorm) { + // Get lighting vectors + vec3 LightDirection = normalize(lDirection); + vec3 nviewDirection = normalize(fPos); + vec3 nfNormal; + + // Compute diffuse component + float diff = 0.2*max(0.0, dot(nfNormal, -LightDirection)); + + // Compute specular component + vec3 Rl = reflect(LightDirection, nfNormal); + float spec = 0.2*pow(max(0.0, dot(/*normalMatrix */ Rl, nviewDirection)), 64); + + // Compute ambient component + float amb = 0.2; + + float mult = 1;//max(0.0, -LightDirection.y+1.5); + + //return vec4(0); + return vec4(tex.xyz * (diff + amb + spec) * mult, tex.w); +} + +vec4 calcPointLight(vec4 tex, vec3 fPos, vec3 fNorm, int i) { + // Get lighting vectors + vec3 LightDirection = normalize(pointLight[i] + fPos); + // vec3 nfNormal = normalize(fNorm); + vec3 nviewDirection = normalize(fPos); + vec3 nfNormal; + + // Attenuation + float distance = length(nviewDirection - pointLight[i] - fPos) / 3; + float attenuation = 0.5 + 1 / max(0.25, distance * distance); + + // Compute diffuse component + float diff = 0.3 * max(0.0, dot(nfNormal, LightDirection)); + + // Compute specular component + vec3 Rl = reflect(-LightDirection, /*normalMatrix */ nfNormal); + float spec = 0.5 * pow(max(0.0, dot(Rl, nviewDirection)), 32); + + // Compute ambient component + float amb = 0.2; + + return vec4(pointLightCol[i].xyz * attenuation * (amb + diff + spec) * tex.xyz, pointLightCol[i].w); +} + +void +main() +{ + vec4 texColor; + + texColor = ifColor; + if(isPhong) { + // Get lighting vectors + vec3 LightDirection = normalize(lDirection); + vec3 nfNormal = normalize(fNormal); + vec3 nviewDirection = normalize(fPosition); + + fColor = calcDirLight(texColor, fPosition, fNormal) + + calcPointLight(texColor, fPosition, fNormal, 0)/4 + + calcPointLight(texColor, fPosition, fNormal, 1)/4 + + calcPointLight(texColor, fPosition, fNormal, 2)/4; + } else { + fColor = texColor; + } +} diff --git a/src/shaders/objShader.vert b/src/shaders/objShader.vert index e69de29..31cda7c 100644 --- a/src/shaders/objShader.vert +++ b/src/shaders/objShader.vert @@ -0,0 +1,68 @@ +#version 400 core +uniform mat4 mvMatrix; +uniform mat4 projMatrix; +uniform mat3 normalMatrix; +uniform bool isPhong; +uniform vec3 lDirection; + +uniform vec3 pointLight[3]; + +in vec4 vPosition; +in vec3 vNormal; +out vec3 fNormal; +out vec3 fPosition; + +vec4 calcDirLight(vec4 eye, vec3 fPos, vec3 fNorm) { + // Get lighting vectors + vec3 LightDirection = normalize(lDirection); + vec3 nfNormal = normalize(fNorm); + vec3 nviewDirection = normalize(fPos); + + // Compute diffuse component + float diff = 0.65 * max(0.0, dot(nfNormal, LightDirection)); + + // Compute specular component + //vec3 Rl = normalize(-LightDirection+2.0*nfNormal*dot(nfNormal,LightDirection)); + + vec3 Rl = reflect(-LightDirection, fNorm); + float spec = 0.1*pow(max(0.0, dot(Rl, nviewDirection)), 16); + + // Compute ambient component + vec3 amb = vec3(0.1); + + return vec4(amb + diff + spec, 1); +} + +vec4 calcPointLight(vec4 eye, vec3 fPos, vec3 fNorm, int i) { + // Get lighting vectors + vec3 LightDirection = normalize(pointLight[i] - fPos); + vec3 nfNormal = normalize(fNorm); + vec3 nviewDirection = normalize(fPos); + + // Attenuation + float distance = length(pointLight[i] - nviewDirection); + float attenuation = 1.0f / (distance * distance); + + // Compute diffuse component + float diff = attenuation * 0.65 * max(0.0, dot(nfNormal, LightDirection)); + + // Compute specular component + vec3 Rl = normalize(-LightDirection+2.0*nfNormal*dot(nfNormal,LightDirection)); + float spec = attenuation * 0.1*pow(max(0.0, dot(Rl, nviewDirection)), 16); + + // Compute ambient component + vec3 amb = attenuation * vec3(0.1); + + return 0.3 * vec4(amb + diff + spec, 1); +} + +void +main() +{ + vec4 vEyeCoord = mvMatrix * vPosition; + gl_Position = projMatrix * vEyeCoord; + + fPosition = vEyeCoord.xyz; + fNormal = normalMatrix*vNormal; +} + diff --git a/src/viewer/simpleViewer.cpp b/src/viewer/simpleViewer.cpp index 50f238b..93a1b30 100644 --- a/src/viewer/simpleViewer.cpp +++ b/src/viewer/simpleViewer.cpp @@ -24,12 +24,12 @@ #include "../interfaces/visitor.h" #include "../glnodes/scenegroup.h" #include "../glnodes/shapes.h" +#include "../libs/OBJLoader.h" #include #include #include - -#include +#include #include #include @@ -93,6 +93,9 @@ void Viewer::cleanup() delete m_program; m_program = 0; + delete objShader; + objShader = 0; + // Delete buffers glDeleteBuffers(NumBuffers, m_Buffers); glDeleteVertexArrays(NumVAOs, m_VAOs); @@ -131,6 +134,58 @@ void Viewer::drawSkybox() glDrawArrays(GL_TRIANGLES, 0, 36); } +void Viewer::loadToolObj() { + + // Load the obj file + OBJLoader::Loader loader("../data/sonic_screwdriver.obj"); + + // Create a GL object for each mesh extracted from the OBJ file + const std::vector& meshes = loader.getMeshes(); + const std::vector& materials = loader.getMaterials(); + for (unsigned int i=0; i _meshesGL; }; #endif // SIMPLEVIEWER_H