skein/src/icosphere.cpp

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);
}