174 lines
5.0 KiB
C++
174 lines
5.0 KiB
C++
#include "icosphere.hpp"
|
|
|
|
#include <iostream>
|
|
#include <array>
|
|
|
|
#include "glm/gtc/matrix_transform.hpp"
|
|
|
|
#include "gfx.hpp"
|
|
|
|
Icosphere::Icosphere(float radius, int subdivisions, GLuint shaderProgram) :
|
|
_shaderProgram(shaderProgram),
|
|
_position({})
|
|
{
|
|
generateVertices(radius, subdivisions);
|
|
|
|
glGenVertexArrays(1, &_vao);
|
|
glGenBuffers(1, &_vbo);
|
|
glGenBuffers(1, &_ebo);
|
|
|
|
glBindVertexArray(_vao);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
|
size_t vboBufferSize = _vertices.size() * sizeof(float);
|
|
glBufferData(GL_ARRAY_BUFFER, vboBufferSize, &_vertices[0], GL_STATIC_DRAW);
|
|
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
|
|
size_t eboBufferSize = _indices.size() * sizeof(unsigned int);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboBufferSize, &_indices[0], GL_STATIC_DRAW);
|
|
|
|
// Vertex position attribute
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
|
|
|
|
// Vertex normal attribute
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
|
|
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
void Icosphere::generateVertices(float radius, int subdivisions)
|
|
{
|
|
VertexList vertices = _isocahedronVertices;
|
|
TriangleList triangles = _isocahedronTriangles;
|
|
for (int i = 0; i < subdivisions; i++)
|
|
{
|
|
triangles = subdivide(vertices, triangles);
|
|
}
|
|
std::cout <<
|
|
"subdivisions: " << subdivisions <<
|
|
" vertices: " << vertices.size() <<
|
|
" triangles: " << triangles.size() << std::endl;
|
|
|
|
// Scale vertices by radius after subdivision as subdivision happens on a
|
|
// unit sphere
|
|
for (int i = 0; i < vertices.size(); i++)
|
|
{
|
|
vertices[i] *= radius;
|
|
}
|
|
|
|
for (const auto& tri : triangles)
|
|
{
|
|
_indices.push_back(tri.vertex[0]);
|
|
_indices.push_back(tri.vertex[1]);
|
|
_indices.push_back(tri.vertex[2]);
|
|
}
|
|
for (const auto& v : vertices)
|
|
{
|
|
// Vertex Data
|
|
_vertices.push_back(v[0]);
|
|
_vertices.push_back(v[1]);
|
|
_vertices.push_back(v[2]);
|
|
|
|
// Normal data
|
|
// The normal at each vertex is actually going to just be the vertex again, but
|
|
// normalised, because this is a sphere.
|
|
glm::vec3 normal = normalize(v);
|
|
_vertices.push_back(normal[0]);
|
|
_vertices.push_back(normal[1]);
|
|
_vertices.push_back(normal[2]);
|
|
}
|
|
}
|
|
|
|
int Icosphere::vertexForEdge(Lookup& lookup, VertexList& vertices, int first, int second)
|
|
{
|
|
Lookup::key_type key(first, second);
|
|
// Normalize edge directions so that we don't store edges twice
|
|
if (key.first > key.second)
|
|
{
|
|
std::swap(key.first, key.second);
|
|
}
|
|
|
|
// .insert returns a pair with a pointer to the newly-inserted (or already
|
|
// existing) element in the lookup as the first element and a boolean
|
|
// indicating a successful new insertion as the second.
|
|
auto inserted = lookup.insert({key, vertices.size()});
|
|
|
|
// If the insertion was successful - i.e this is a newly inserted element
|
|
if (inserted.second)
|
|
{
|
|
// Two vectors determined by two existing points
|
|
auto& edge0 = vertices[first];
|
|
auto& edge1 = vertices[second];
|
|
// Combine and normalize the vectors to get the halfway point on the
|
|
// unit sphere.
|
|
auto point = normalize(edge0 + edge1);
|
|
vertices.push_back(point);
|
|
}
|
|
|
|
return inserted.first->second;
|
|
}
|
|
|
|
Icosphere::TriangleList Icosphere::subdivide(VertexList& vertices, TriangleList triangles)
|
|
{
|
|
Lookup lookup;
|
|
TriangleList result;
|
|
|
|
for (auto&& triangle : triangles)
|
|
{
|
|
std::array<int, 3> mid;
|
|
for (int edge = 0; edge < 3; edge++)
|
|
{
|
|
mid[edge] = vertexForEdge(lookup, vertices,
|
|
triangle.vertex[edge], triangle.vertex[(edge + 1) % 3]);
|
|
}
|
|
|
|
// Create four new triangles from the original triangle
|
|
result.push_back({triangle.vertex[0], mid[0], mid[2]});
|
|
result.push_back({triangle.vertex[1], mid[1], mid[0]});
|
|
result.push_back({triangle.vertex[2], mid[2], mid[1]});
|
|
result.push_back({mid[0], mid[1], mid[2]});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Icosphere::updateModelMatrix()
|
|
{
|
|
// To be able to define a position for a particular instance of a sphere we will
|
|
// need to calculate a model projection matrix per-instance
|
|
glm::mat4 model = glm::translate(glm::mat4(1.0), _position);
|
|
|
|
GLint modelLocation = getShaderUniformLocation(_shaderProgram, "_Model");
|
|
glUniformMatrix4fv(modelLocation, 1, GL_FALSE, &model[0][0]);
|
|
}
|
|
|
|
void Icosphere::render(const float time)
|
|
{
|
|
glUseProgram(_shaderProgram);
|
|
updateModelViewProjectionMatrix(_shaderProgram, time);
|
|
|
|
updateModelMatrix();
|
|
|
|
glBindVertexArray(_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
|
|
|
|
glDrawElements(GL_TRIANGLES, _indices.size(), GL_UNSIGNED_INT, 0);
|
|
}
|
|
|
|
void Icosphere::setPosition(glm::vec3 position)
|
|
{
|
|
_position = position;
|
|
}
|
|
|
|
Icosphere::~Icosphere()
|
|
{
|
|
glDeleteVertexArrays(1, &_vao);
|
|
glDeleteBuffers(1, &_vbo);
|
|
glDeleteBuffers(1, &_ebo);
|
|
}
|