qtk/src/qtk/model.cpp

376 lines
13 KiB
C++
Raw Normal View History

2021-09-03 16:56:57 +00:00
/*##############################################################################
## Author: Shaun Reed ##
2022-03-06 16:54:05 +00:00
## Legal: All Content (c) 2022 Shaun Reed, all rights reserved ##
2021-09-03 16:56:57 +00:00
## About: Model classes for importing with Assimp ##
## From following tutorials on learnopengl.com ##
## ##
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
##############################################################################*/
#include <QFileInfo>
#include "model.h"
#include "scene.h"
#include "texture.h"
2021-09-03 16:56:57 +00:00
using namespace Qtk;
2021-09-03 16:56:57 +00:00
Model::ModelManager Model::mManager;
// Static function to access ModelManager for getting Models by name
2022-11-24 22:26:53 +00:00
Model * Model::getInstance(const char * name) {
2021-09-03 16:56:57 +00:00
return mManager[name];
}
/*******************************************************************************
* ModelMesh Private Member Functions
******************************************************************************/
2022-11-24 22:26:53 +00:00
void ModelMesh::initMesh(const char * vert, const char * frag) {
initializeOpenGLFunctions();
2021-09-03 16:56:57 +00:00
// Create VAO, VBO, EBO
mVAO->create();
mVBO->create();
mEBO->create();
mVAO->bind();
// Allocate VBO
mVBO->setUsagePattern(QOpenGLBuffer::StaticDraw);
mVBO->bind();
2022-11-24 22:26:53 +00:00
mVBO->allocate(mVertices.data(), mVertices.size() * sizeof(mVertices[0]));
2021-09-03 16:56:57 +00:00
// Allocate EBO
mEBO->setUsagePattern(QOpenGLBuffer::StaticDraw);
mEBO->bind();
2022-11-24 22:26:53 +00:00
mEBO->allocate(mIndices.data(), mIndices.size() * sizeof(mIndices[0]));
2021-09-03 16:56:57 +00:00
mEBO->release();
// Load and link shaders
mProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, vert);
mProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, frag);
mProgram->link();
mProgram->bind();
// Positions
mProgram->enableAttributeArray(0);
2022-11-24 22:26:53 +00:00
mProgram->setAttributeBuffer(
0, GL_FLOAT, offsetof(ModelVertex, mPosition), 3, sizeof(ModelVertex));
2021-09-03 16:56:57 +00:00
// Normals
mProgram->enableAttributeArray(1);
2022-11-24 22:26:53 +00:00
mProgram->setAttributeBuffer(
1, GL_FLOAT, offsetof(ModelVertex, mNormal), 3, sizeof(ModelVertex));
2021-09-03 16:56:57 +00:00
// Texture Coordinates
mProgram->enableAttributeArray(2);
2022-11-24 22:26:53 +00:00
mProgram->setAttributeBuffer(
2, GL_FLOAT, offsetof(ModelVertex, mTextureCoord), 2,
sizeof(ModelVertex));
2021-09-03 16:56:57 +00:00
// Vertex tangents
mProgram->enableAttributeArray(3);
2022-11-24 22:26:53 +00:00
mProgram->setAttributeBuffer(
3, GL_FLOAT, offsetof(ModelVertex, mTangent), 3, sizeof(ModelVertex));
2021-09-03 16:56:57 +00:00
// Vertex bitangents
mProgram->enableAttributeArray(4);
2022-11-24 22:26:53 +00:00
mProgram->setAttributeBuffer(
4, GL_FLOAT, offsetof(ModelVertex, mBitangent), 3, sizeof(ModelVertex));
2021-09-03 16:56:57 +00:00
mProgram->release();
mVBO->release();
mVAO->release();
}
/*******************************************************************************
* ModelMesh Public Member Functions
******************************************************************************/
2022-11-24 22:26:53 +00:00
void ModelMesh::draw(QOpenGLShaderProgram & shader) {
2021-09-03 16:56:57 +00:00
mVAO->bind();
// Bind shader
shader.bind();
// Set Model View Projection values
shader.setUniformValue("uModel", mTransform.toMatrix());
2022-11-26 18:24:38 +00:00
shader.setUniformValue("uView", Scene::getViewMatrix());
shader.setUniformValue("uProjection", Scene::getProjectionMatrix());
2021-09-03 16:56:57 +00:00
GLuint diffuseCount = 1;
GLuint specularCount = 1;
GLuint normalCount = 1;
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < mTextures.size(); i++) {
2021-09-03 16:56:57 +00:00
// Activate the current texture index by adding offset to GL_TEXTURE0
glActiveTexture(GL_TEXTURE0 + i);
mTextures[i].mTexture->bind();
// Get a name for the texture using a known convention -
// Diffuse: material.texture_diffuse1, material.texture_diffuse2, ...
// Specular: material.texture_specular1, material.texture_specular2, ...
std::string number;
std::string name = mTextures[i].mType;
2022-11-24 22:26:53 +00:00
if(name == "texture_diffuse") {
number = std::to_string(diffuseCount++);
}
if(name == "texture_specular") {
number = std::to_string(specularCount++);
}
if(name == "texture_normal") {
number = std::to_string(normalCount++);
}
2021-09-03 16:56:57 +00:00
// Set the uniform to track this texture ID using our naming convention
shader.setUniformValue((name + number).c_str(), i);
}
// Draw the mesh
2022-11-24 22:26:53 +00:00
glDrawElements(
GL_TRIANGLES, mIndices.size(), GL_UNSIGNED_INT, mIndices.data());
2021-09-03 16:56:57 +00:00
// Release shader, textures
2022-11-24 22:26:53 +00:00
for(const auto & texture : mTextures) {
2021-09-03 16:56:57 +00:00
texture.mTexture->release();
}
shader.release();
mVAO->release();
glActiveTexture(GL_TEXTURE0);
}
/*******************************************************************************
* Model Public Member Functions
******************************************************************************/
2022-11-24 22:26:53 +00:00
void Model::draw() {
for(auto & mMeshe : mMeshes) {
mMeshe.mTransform = mTransform;
mMeshe.draw();
2021-09-03 16:56:57 +00:00
}
}
2022-11-24 22:26:53 +00:00
void Model::draw(QOpenGLShaderProgram & shader) {
for(auto & mMeshe : mMeshes) {
mMeshe.mTransform = mTransform;
mMeshe.draw(shader);
2021-09-03 16:56:57 +00:00
}
}
2022-11-24 22:26:53 +00:00
void Model::flipTexture(const std::string & fileName, bool flipX, bool flipY) {
2021-09-03 16:56:57 +00:00
bool modified = false;
std::string fullPath = mDirectory + '/' + fileName;
2022-11-24 22:26:53 +00:00
for(auto & texture : mTexturesLoaded) {
if(texture.mPath == fileName) {
2021-09-03 16:56:57 +00:00
texture.mTexture->destroy();
texture.mTexture->create();
texture.mTexture->setData(
2022-11-24 22:26:53 +00:00
*OpenGLTextureFactory::initImage(fullPath.c_str(), flipX, flipY));
2021-09-03 16:56:57 +00:00
modified = true;
}
}
2022-11-24 22:26:53 +00:00
if(!modified) {
2021-09-03 16:56:57 +00:00
qDebug() << "Attempt to flip texture that doesn't exist: "
<< fullPath.c_str() << "\n";
}
}
/*******************************************************************************
* Model Private Member Functions
******************************************************************************/
2022-11-24 22:26:53 +00:00
void Model::loadModel(const std::string & path) {
2021-09-03 16:56:57 +00:00
Assimp::Importer import;
// JIC a relative path was used, get the absolute file path
QFileInfo info(path.c_str());
info.makeAbsolute();
mDirectory = info.absoluteFilePath().toStdString();
2021-09-03 16:56:57 +00:00
// Import the model, converting non-triangular geometry to triangles
// + And flipping texture UVs, etc..
// Assimp options: http://assimp.sourceforge.net/lib_html/postprocess_8h.html
2022-11-24 22:26:53 +00:00
const aiScene * scene = import.ReadFile(
mDirectory, aiProcess_Triangulate | aiProcess_FlipUVs
| aiProcess_GenSmoothNormals | aiProcess_CalcTangentSpace
| aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes);
2021-09-03 16:56:57 +00:00
// If there were errors, print and return
2022-11-24 22:26:53 +00:00
if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
2021-09-03 16:56:57 +00:00
qDebug() << "Error::ASSIMP::" << import.GetErrorString() << "\n";
return;
}
// If there were no errors, find the directory that contains this model
mDirectory = mDirectory.substr(0, mDirectory.find_last_of('/'));
2021-09-03 16:56:57 +00:00
// Pass the pointers to the root node and the scene to recursive function
// + Base case breaks when no nodes left to process on model
processNode(scene->mRootNode, scene);
// Sort models by their distance from the camera
// Optimizes drawing so that overlapping objects are not overwritten
// + Since the topmost object will be drawn first
sortModels();
// Object finished loading, insert it into ModelManager
mManager.insert(getName(), this);
2021-09-03 16:56:57 +00:00
}
2022-11-24 22:26:53 +00:00
void Model::processNode(aiNode * node, const aiScene * scene) {
2021-09-03 16:56:57 +00:00
// Process each mesh that is available for this node
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < node->mNumMeshes; i++) {
2021-09-03 16:56:57 +00:00
aiMesh * mesh = scene->mMeshes[node->mMeshes[i]];
mMeshes.push_back(processMesh(mesh, scene));
}
// Process each child node for this mesh using recursion
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < node->mNumChildren; i++) {
2021-09-03 16:56:57 +00:00
processNode(node->mChildren[i], scene);
}
}
2022-11-24 22:26:53 +00:00
ModelMesh Model::processMesh(aiMesh * mesh, const aiScene * scene) {
2021-09-03 16:56:57 +00:00
ModelMesh::Vertices vertices;
ModelMesh::Indices indices;
ModelMesh::Textures textures;
// For each vertex in the aiMesh
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < mesh->mNumVertices; i++) {
2021-09-03 16:56:57 +00:00
// Create a local vertex object for positions, normals, and texture coords
ModelVertex vertex;
// Reuse this vector to initialize positions and normals
QVector3D vector3D;
// Initialize vertex position
vector3D.setX(mesh->mVertices[i].x);
vector3D.setY(mesh->mVertices[i].y);
vector3D.setZ(mesh->mVertices[i].z);
// Set the position of our local vertex to the local vector object
vertex.mPosition = vector3D;
2022-11-24 22:26:53 +00:00
if(mesh->HasNormals()) {
2021-09-03 16:56:57 +00:00
// Initialize vertex normal
vector3D.setX(mesh->mNormals[i].x);
vector3D.setY(mesh->mNormals[i].y);
vector3D.setZ(mesh->mNormals[i].z);
// Set the normals of our local vertex to the local vector object
vertex.mNormal = vector3D;
}
// Initialize texture coordinates, if any are available
2022-11-24 22:26:53 +00:00
if(mesh->mTextureCoords[0]) {
2021-09-03 16:56:57 +00:00
QVector2D vector2D;
// Texture coordinates
vector2D.setX(mesh->mTextureCoords[0][i].x);
vector2D.setY(mesh->mTextureCoords[0][i].y);
vertex.mTextureCoord = vector2D;
// Tangents
vector3D.setX(mesh->mTangents[i].x);
vector3D.setY(mesh->mTangents[i].y);
vector3D.setZ(mesh->mTangents[i].z);
vertex.mTangent = vector3D;
// Bitangents
vector3D.setX(mesh->mBitangents[i].x);
vector3D.setY(mesh->mBitangents[i].y);
vector3D.setZ(mesh->mBitangents[i].z);
vertex.mBitangent = vector3D;
2022-11-24 22:26:53 +00:00
} else {
2021-09-03 16:56:57 +00:00
vertex.mTextureCoord = {0.0f, 0.0f};
}
// Add the initialized vertex to our container of vertices
vertices.push_back(vertex);
}
// For each face on the mesh, process its indices
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < mesh->mNumFaces; i++) {
2021-09-03 16:56:57 +00:00
aiFace face = mesh->mFaces[i];
2022-11-24 22:26:53 +00:00
for(GLuint j = 0; j < face.mNumIndices; j++) {
2021-09-03 16:56:57 +00:00
// Add the index to out container of indices
indices.push_back(face.mIndices[j]);
}
}
// Process material
2022-11-24 22:26:53 +00:00
if(mesh->mMaterialIndex >= 0) {
2021-09-03 16:56:57 +00:00
// Get the material attached to the model using Assimp
aiMaterial * material = scene->mMaterials[mesh->mMaterialIndex];
// Get all diffuse textures from the material
2022-11-24 22:26:53 +00:00
ModelMesh::Textures diffuseMaps = loadMaterialTextures(
material, aiTextureType_DIFFUSE, "texture_diffuse");
2021-09-03 16:56:57 +00:00
// Insert all diffuse textures found into our textures container
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// Get all specular textures from the material
2022-11-24 22:26:53 +00:00
ModelMesh::Textures specularMaps = loadMaterialTextures(
material, aiTextureType_SPECULAR, "texture_specular");
2021-09-03 16:56:57 +00:00
// Insert all specular textures found into our textures container
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
// Get all normal textures from the material
ModelMesh::Textures normalMaps =
2022-11-24 22:26:53 +00:00
loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");
2021-09-03 16:56:57 +00:00
// Insert all normal maps found into our textures container
textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());
}
2022-11-24 22:26:53 +00:00
return {vertices, indices, textures, mVertexShader, mFragmentShader};
2021-09-03 16:56:57 +00:00
}
ModelMesh::Textures Model::loadMaterialTextures(
2022-11-24 22:26:53 +00:00
aiMaterial * mat, aiTextureType type, const std::string & typeName) {
2021-09-03 16:56:57 +00:00
ModelMesh::Textures textures;
2022-11-24 22:26:53 +00:00
for(GLuint i = 0; i < mat->GetTextureCount(type); i++) {
2021-09-03 16:56:57 +00:00
// Call GetTexture to get the name of the texture file to load
aiString fileName;
mat->GetTexture(type, i, &fileName);
// Check if we have already loaded this texture
bool skip = false;
2022-11-24 22:26:53 +00:00
for(auto & j : mTexturesLoaded) {
2021-09-03 16:56:57 +00:00
// If the path to the texture already exists in m_texturesLoaded, skip it
2022-11-24 22:26:53 +00:00
if(std::strcmp(j.mPath.data(), fileName.C_Str()) == 0) {
textures.push_back(j);
2021-09-03 16:56:57 +00:00
// If we have loaded the texture, do not load it again
skip = true;
break;
}
}
// If the texture has not yet been loaded
2022-11-24 22:26:53 +00:00
if(!skip) {
2021-09-03 16:56:57 +00:00
ModelTexture texture;
2022-11-26 18:24:38 +00:00
texture.mTexture = OpenGLTextureFactory::initTexture(
2022-11-24 22:26:53 +00:00
std::string(mDirectory + '/' + fileName.C_Str()).c_str(), false,
false);
2021-09-03 16:56:57 +00:00
texture.mID = texture.mTexture->textureId();
texture.mType = typeName;
texture.mPath = fileName.C_Str();
// Add the texture to the textures container
textures.push_back(texture);
// Add the texture to the loaded textures to avoid loading it twice
mTexturesLoaded.push_back(texture);
}
}
// Return the resulting textures
return textures;
}
2022-11-24 22:26:53 +00:00
void Model::sortModels() {
2022-11-26 18:24:38 +00:00
auto cameraPos = Scene::getCamera().getTransform();
2022-11-24 22:26:53 +00:00
auto cameraDistance = [&cameraPos](const ModelMesh & a, const ModelMesh & b) {
// Sort by the first vertex position in the model
return (cameraPos.getTranslation().distanceToPoint(
a.mVertices[0].mPosition))
< (cameraPos.getTranslation().distanceToPoint(
b.mVertices[0].mPosition));
2021-09-03 16:56:57 +00:00
};
std::sort(mMeshes.begin(), mMeshes.end(), cameraDistance);
}