Wednesday, March 29, 2017

Orbital Aero Model: Graphics Code

I'm not qualified in giving OpenGL tutorials, but I've set a precedence of sharing my code as I go and I want to keep that up. The majority of the following OpenGL code is from https://learnopengl.com/, but I've restructured it to match my coding conventions. I also got rid of the global variables. :-)

I'll start off by sharing the code of each of the classes, then go into how they are used in the app. I'm saving the Camera class and control for the next post as it goes into a few more details on user input.

Graphics


Graphics.h

#pragma once

struct GLFWwindow;

class Graphics
{
public:
static const double drawScale;

GLFWwindow* initializeWindow(int windowWidth, int windowHeight);
};

Graphics.cpp

#include <iostream>

#define GLEW_STATIC
#include "..\GLEW\include\glew.h"
#include "..\GLFW\include\glfw3.h"

#include <SFML\OpenGL.hpp>

#include "Graphics.h"

const double Graphics::drawScale = 0.00001;

GLFWwindow* Graphics::initializeWindow(int windowWidth, int windowHeight)
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
glfwWindowHint(GLFW_SAMPLES, 4);

GLFWwindow* window = glfwCreateWindow(800, 600, "Orbital Aero Application", nullptr, nullptr);
if (window == nullptr)
{
printf("Failed to create GLFW window!\n");
glfwTerminate();
//return -1;
}
glfwMakeContextCurrent(window);

glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
printf("Failed to initialize GLEW!\n");
//return -1;
}

int width, height;
glfwGetFramebufferSize(window, &width, &height);

// Define the viewport dimensions
glViewport(0, 0, width, height);

// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);

return window;
}

Mesh


Mesh.h

#pragma once

#include <vector>

#include "Shader.h"

using namespace std;

struct Vertex;
struct Texture;

class Mesh
{
public:
// Mesh Data
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;

// Constructor
Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures);

void draw(Shader shader);

private:
// Render Data
GLuint VAO, VBO, EBO;

// Initializes all the buffer objects/arrays.
void setup();
};

Mesh.cpp

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include "..\GLEW\include\glew.h"
#include "..\GLM\include\glm.hpp"
#include "..\GLM\include\gtc\matrix_transform.hpp"
#include "..\ASSIMP\include\types.h"

#include "Vertex.h"
#include "Texture.h"
#include "Mesh.h"

Mesh::Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;

// Now that we have all the required data, set the vertex buffers and its attribute pointers.
this->setup();

return;
}

// Render the mesh.
void Mesh::draw(Shader shader)
{
// Bind appropriate textures
GLuint diffuseNr = 1;
GLuint specularNr = 1;
for (GLuint i = 0; i < this->textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i); // Active proper texture unit before binding
// Retrieve texture number (the N in diffuse_textureN)
stringstream ss;
string number;
string name = this->textures[i].type;
if (name == "texture_diffuse")
ss << diffuseNr++; // Transfer GLuint to stream
else if (name == "texture_specular")
ss << specularNr++; // Transfer GLuint to stream
number = ss.str();
// Now set the sampler to the correct texture unit
glUniform1i(glGetUniformLocation(shader.program, (name + number).c_str()), i);
// And finally bind the texture
glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
}

// Also set each mesh's shininess property to a default value (if you want you could extend this to another mesh property and possibly change this value)
glUniform1f(glGetUniformLocation(shader.program, "material.shininess"), 16.0f);

// Draw mesh
glBindVertexArray(this->VAO);
glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

// Always good practice to set everything back to defaults once configured.
for (GLuint i = 0; i < this->textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, 0);
}

return;
}

void Mesh::setup()
{
// Create buffers/arrays.
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &this->VBO);
glGenBuffers(1, &this->EBO);

glBindVertexArray(this->VAO);
// Load data into vertex buffers.
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// A great thing about structs is that their memory layout is sequential for all its items.
// The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2 array which
// again translates to 3/2 floats which translates to a byte array.
glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);

// Set the vertex attribute pointers
// Vertex Positions
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
// Vertex Normals
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));
// Vertex Texture Coords
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));

glBindVertexArray(0);

return;
}

Shader


The OpenGL tutorials have code for loading shaders from a file. That seems handy, but for now I've just hard coded the shader programs that I'm actively using. I have two constructors for the class: one that uses default hard coded shaders and one that allows the programmer to pass in a file path.

Shader.h

#pragma once

#include "..\GLEW\include\glew.h"

class Shader
{
public:
GLuint program;

// Constructor generates the default shader.
Shader();

// Constructor generates the shader from files.
Shader(const GLchar* vertexPath, const GLchar* fragmentPath);

// Uses the current shader.
void use();

private:
bool compile(const GLchar* vertexShaderCode, const GLchar* fragmentShaderCode);
};

Shader.cpp

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include "Vertex.h"
#include "Shader.h"

Shader::Shader()
{
const GLchar* vertexShaderCode =
"#version 330 core\n"
"layout(location = 0) in vec3 position;\n"
"layout(location = 2) in vec2 texCoords;\n"
"out vec2 TexCoords;\n"
"uniform mat4 projection;\n"
"uniform mat4 view;\n"
"uniform mat4 model;\n"
"void main()\n"
"{\n"
" gl_Position = projection * view * model * vec4(position, 1.0f);\n"
" TexCoords = texCoords;\n"
"}\n"
"\0";

const GLchar* fragmentShaderCode =
"#version 330 core\n"
"in vec2 TexCoords;\n"
"out vec4 color;\n"
"uniform sampler2D texture_diffuse1;\n"
"void main()\n"
"{\n"
" color = texture(texture_diffuse1, TexCoords);\n"
"}\n"
"\0";

this->compile(vertexShaderCode, fragmentShaderCode);

return;
}

Shader::Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
// Retrieve the vertex/fragment source code from filePath.
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;

// Ensures ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try
{
// Open files.
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// Read file's buffer contents into streams.
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// Close file handlers.
vShaderFile.close();
fShaderFile.close();
// Convert stream into string.
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}

const GLchar* vertexShaderCode = vertexCode.c_str();
const GLchar* fragmentShaderCode = fragmentCode.c_str();

this->compile(vertexShaderCode, fragmentShaderCode);

return;
}

bool Shader::compile(const GLchar* vertexShaderCode, const GLchar* fragmentShaderCode)
{
GLuint vertex, fragment;
GLint success;
GLchar infoLog[512];

// Vertex Shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vertexShaderCode, NULL);
glCompileShader(vertex);
// Print compile errors if any.
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

// Fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fragmentShaderCode, NULL);
glCompileShader(fragment);
// Print compile errors if any.
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}

// Shader Program
this->program = glCreateProgram();
glAttachShader(this->program, vertex);
glAttachShader(this->program, fragment);
glLinkProgram(this->program);
// Print linking errors if any.
glGetProgramiv(this->program, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(this->program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}

// Delete the shaders as they're linked into our program now and no longer necessary.
glDeleteShader(vertex);
glDeleteShader(fragment);

return true;
}

void Shader::use()
{
glUseProgram(this->program);

return;
}

Visual Model


VisualModel.h

#pragma once
#include <string>
#include <vector>

using namespace std;

#include "..\ASSIMP\include\scene.h"

#include "Texture.h"
#include "Vertex.h"

class Mesh;

class VisualModel
{
public:
// Constructor, expects a filepath to a 3D model.
// The path must use /s, not \\s for it to be parsed correctly.
VisualModel(GLchar* path);

void draw(Shader shader);

private:
/*  Model Data  */
vector<Mesh> meshes;
string directory;
vector<Texture> textures_loaded; // Stores all the textures loaded so far, optimization to make sure textures aren't loaded more than once.

void loadModel(string path);

void processNode(aiNode* node, const aiScene* scene);

Mesh processMesh(aiMesh* mesh, const aiScene* scene);

vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName);

GLint textureFromFile(const char* path, string directory);
};

VisualModel.cpp

#include <fstream>
#include <sstream>
#include <iostream>
#include <map>

#include "..\GLEW\include\glew.h"
#include "..\GLM\include\glm.hpp"
#include "..\GLM\include\gtc\matrix_transform.hpp"
#include "..\SOIL\include\SOIL.h"
#include "..\ASSIMP\include\Importer.hpp"
#include "..\ASSIMP\include\postprocess.h"

#include "Mesh.h"

#include "VisualModel.h"

// The path must use /s, not \\s for it to be parsed correctly.
VisualModel::VisualModel(GLchar* path)
{
this->loadModel(path);

return;
}

// Draws the model, and thus all its meshes.
void VisualModel::draw(Shader shader)
{
for (GLuint i = 0; i < this->meshes.size(); i++)
{
this->meshes[i].draw(shader);
}

return;
}

// Loads a model with supported ASSIMP extensions from file and stores the resulting meshes in the meshes vector.
void VisualModel::loadModel(string path)
{
// Read file via ASSIMP
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
// Check for errors
if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
return;
}
// Retrieve the directory path of the filepath
this->directory = path.substr(0, path.find_last_of('/'));

// Process ASSIMP's root node recursively
this->processNode(scene->mRootNode, scene);

return;
}

// 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).
void VisualModel::processNode(aiNode* node, const aiScene* scene)
{
// Process each mesh located at the current node
for (GLuint i = 0; i < node->mNumMeshes; i++)
{
// The node object only contains indices to index the actual objects in the scene. 
// The scene contains all the data, node is just to keep stuff organized (like relations between nodes).
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
this->meshes.push_back(this->processMesh(mesh, scene));
}

// After we've processed all of the meshes (if any) we then recursively process each of the children nodes
for (GLuint i = 0; i < node->mNumChildren; i++)
{
this->processNode(node->mChildren[i], scene);
}

return;
}

Mesh VisualModel::processMesh(aiMesh* mesh, const aiScene* scene)
{
// Data to fill
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;

// Walk through each of the mesh's vertices
for (GLuint i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
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.

// Positions
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
vertex.Position = vector;

// Normals
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z;
vertex.Normal = vector;

// Texture Coordinates
if (mesh->mTextureCoords[0]) // Does the mesh contain texture coordinates?
{
glm::vec2 vec;
// A vertex can contain up to 8 different texture coordinates. We thus make the assumption that we won't 
// use models where a vertex can have multiple texture coordinates so we always take the first set (0).
vec.x = mesh->mTextureCoords[0][i].x;
vec.y = mesh->mTextureCoords[0][i].y;
vertex.TexCoords = vec;
}
else
{
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
}
vertices.push_back(vertex);
}

// Now wak through each of the mesh's faces (a face is a mesh its triangle) and retrieve the corresponding vertex indices.
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
aiFace face = mesh->mFaces[i];

// Retrieve all indices of the face and store them in the indices vector.
for (GLuint j = 0; j < face.mNumIndices; j++)
{
indices.push_back(face.mIndices[j]);
}
}

// Process materials
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// We assume a convention for sampler names in the shaders. Each diffuse texture should be named
// as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER. 
// Same applies to other texture as the following list summarizes:
// Diffuse: texture_diffuseN
// Specular: texture_specularN
// Normal: texture_normalN

// 1. Diffuse maps
vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// 2. Specular maps
vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
}

// Return a mesh object created from the extracted mesh data.
return Mesh(vertices, indices, textures);
}

// Checks all material textures of a given type and loads the textures if they're not loaded yet.
// The required info is returned as a Texture struct.
vector<Texture> VisualModel::loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName)
{
vector<Texture> textures;
for (GLuint i = 0; i < mat->GetTextureCount(type); i++)
{
aiString str;
mat->GetTexture(type, i, &str);
// Check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
GLboolean skip = false;
for (GLuint j = 0; j < textures_loaded.size(); j++)
{
if (textures_loaded[j].path == str)
{
textures.push_back(textures_loaded[j]);
skip = true; // A texture with the same filepath has already been loaded, continue to next one. (optimization)
break;
}
}
if (!skip)
{   // If texture hasn't been loaded already, load it
Texture texture;
texture.id = textureFromFile(str.C_Str(), this->directory);
texture.type = typeName;
texture.path = str;
textures.push_back(texture);
this->textures_loaded.push_back(texture);  // Store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
}
}

return textures;
}

GLint VisualModel::textureFromFile(const char* path, string directory)
{
// Generate texture ID and load texture data 
string filename = string(path);
filename = directory + '/' + filename;
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);

// Parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);

return textureID;
}

Texture


Texture.h

#pragma once

#include <vector>

#include "..\GLEW\include\glew.h"
#include "..\ASSIMP\include\types.h"

using namespace std;

struct Texture
{
GLuint id;
string type;
aiString path;
};

Vertex


Vertex.h

#pragma once

#include "..\GLM\include\glm.hpp"

struct Vertex
{
// Position
glm::vec3 Position;

// Normal
glm::vec3 Normal;

// TexCoords
glm::vec2 TexCoords;
};

Application


The drawing code begins with creating a graphics object and initializing the window.


int _tmain(int argc, _TCHAR* argv[])

{
// Window dimensions
int windowWidth = 800;
int windowHeight = 600;

Graphics graphics;
GLFWwindow* window = graphics.initializeWindow(windowWidth, windowHeight);


The camera and control inputs will be discussed in the next post, but they are initialized after the graphics window:


Camera camera(glm::vec3(0.0, 0.0, 6367913 * 5 * Graphics::drawScale));

ControlsInput *controlsInput = ControlsInput::instance();
controlsInput->setCallbacks(window, &camera);


Next up is creating the shader object. I'm just using the default hard coded shader for now.


Shader shaderTextured;


Then I load the Visual Models using the ASSIMP library.


VisualModel planet("./OrbitalAeroApp/Models/Moon_3D_Model/moon.fbx");


And define the projection with the near and far clippings plane. I'm still battling with the near/far clip to get good results. (I'd like to be able to fly close to a planet without clipping inside, but I still want to see distance objects.) There are some tricks to solve this that I haven't gotten to yet. In the short term, I've gotten acceptable results with:
- 45 degree Field of View
- Near clip of 1
- Far clip of 5000
- Draw scale of 0.00001. (Every actual location and size is multiplied by my draw scale when sent to be rendered.)

So essentially I'm clipping into anything closer than 100 km. No low level flight for now...


// Horizontal Field of View, Aspect Ratio, Near Clipping Plane (As Large As Possible), Far Clipping Plane (As Small As Possible)
glm::mat4 projection = glm::perspective(45.0f, (GLfloat)windowWidth / (GLfloat)windowHeight, 1.0f, 5000.0f);
shaderTextured.use();
glUniformMatrix4fv(glGetUniformLocation(shaderTextured.program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));


After the models are loaded, I create the entities in the simulation. At entity creation I pass in the model I'm using so it can get saved in the hostData.


addBodyEarth(orbitalAero, &planet);
addBodyMoon(orbitalAero, &planet);

void addBodyEarth(OrbitalAeroModel &orbitalAero, VisualModel *newVisualModel)
{
uint64 newBodyIndex = orbitalAero.createBody();
BodyModel *newBody = orbitalAero.getBody(newBodyIndex);
newBody->location_meters = 0.0;
newBody->massBase_kilograms = earthMass_kilograms;
newBody->radius_meters = earthRadius_meters;
newBody->linearVelocity_metersPerSecond = 0.0;
newBody->angularVelocity_radiansPerSecond.psi = 0.0000727220509259;

Entity *newHostBody = new Entity(newBody);
newBody->hostData = newHostBody;
newHostBody->visualModelObject = newVisualModel;

return;
}


Now onto the main program loop!


while (isRunning)
{
if (glfwWindowShouldClose(window))
{
isRunning = false;
}

orbitalAero.update(0.2); // Real time


The camera will be discussed in the next post, but it is updated at this time.


// Set frame time
double currentFrameTimestamp_seconds = glfwGetTime();
deltaTime_seconds = currentFrameTimestamp_seconds - lastFrameTimestamp_seconds;
lastFrameTimestamp_seconds = currentFrameTimestamp_seconds;

glfwPollEvents();
camera.update(deltaTime_seconds);


Clear out the buffer.


// Clear the colorbuffer
glClearColor(0.03f, 0.03f, 0.03f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUniformMatrix4fv(glGetUniformLocation(shaderTextured.program, "view"), 1, GL_FALSE, glm::value_ptr(camera.getViewMatrix()));


Loop through all the bodies to draw them. You can see I'm applying the Graphics::drawScale to the size and location so things that are 10,000 km in size 100,000 km away are drawn as 0.1 km in size 10 km away.


map <uint64, BodyModel*>::iterator iteratorBody = orbitalAero.tableBodies.begin();
while (iteratorBody != orbitalAero.tableBodies.end())
{
BodyModel *loopBody = iteratorBody->second;
if (loopBody->isActive)
{
Entity *loopEntity = (Entity*)loopBody->hostData;

if (loopEntity->isVisible)
{
glm::mat4 visualModelMatrix;
visualModelMatrix = glm::translate(visualModelMatrix, glm::vec3(loopBody->location_meters.x * Graphics::drawScale, loopBody->location_meters.y * Graphics::drawScale, loopBody->location_meters.z * Graphics::drawScale));
visualModelMatrix = glm::scale(visualModelMatrix, glm::vec3(loopBody->radius_meters * Graphics::drawScale, loopBody->radius_meters * Graphics::drawScale, loopBody->radius_meters * Graphics::drawScale));
Euler earthRotation = loopBody->orientation_quaternions.toEulerAngles();
visualModelMatrix = glm::rotate(visualModelMatrix, (GLfloat)earthRotation.phi, glm::vec3(1.0f, 0.0f, 0.0f));
visualModelMatrix = glm::rotate(visualModelMatrix, (GLfloat)earthRotation.theta, glm::vec3(0.0f, 1.0f, 0.0f));
visualModelMatrix = glm::rotate(visualModelMatrix, (GLfloat)earthRotation.psi, glm::vec3(0.0f, 0.0f, 1.0f));
glUniformMatrix4fv(glGetUniformLocation(shaderTextured.program, "model"), 1, GL_FALSE, glm::value_ptr(visualModelMatrix));
loopEntity->visualModelObject->draw(shaderTextured);
} // loopEntity->isVisible
} // (loopBody.isActive)
iteratorBody++;
} // (iteratorBody != tableBodies.end())


And complete the main program loop.


glfwSwapBuffers(window);

Sleep(20);
}


And cleanup when the program ends.

glfwTerminate();


I'm publishing the above code under the same license as the original learnopengl.com: "licensed under the public domain depicted by the terms of the CC0 1.0 Universal license as published by Creative Commons, either version 1.0 of the License, or (at your option) any later version." Link to license.




No comments:

Post a Comment