voxspatium/src/planet/PlanetFace.cpp

503 lines
12 KiB
C++

#include "planet/PlanetFace.h"
#include <time.h>
#include <math.h>
PlanetFaceNode::PlanetFaceNode(
PlanetFace* face,
PlanetFaceNode* parent,
const unsigned int index
) :
m_planetFace(face),
m_quadrant(index),
m_level(parent ? parent->m_level + 1 : 0),
m_generated(false),
m_dirty(true),
m_leaf(true),
m_parent(parent),
m_children(),
m_neighbors(),
neighborDetailDifferences()
{
generate();
}
PlanetFaceNode::~PlanetFaceNode()
{
for (unsigned int i = 0; i < 4; i++)
{
if (m_neighbors[i])
{
m_neighbors[i]->setNeighbor(mirrorSide(i), 0);
}
}
if (!m_leaf)
{
for (int i = 0; i < 4; i++)
{
delete m_children[i];
}
}
}
unsigned int PlanetFaceNode::mirrorSide(const unsigned int side) const
{
// If no neighbor; use default mirroring
if (!m_neighbors[side])
return MIRROR(side);
// Get source and destination faces
const unsigned int f0 = m_planetFace->getFace();
const unsigned int f1 = m_neighbors[side]->m_planetFace->getFace();
// If within the same face or faces with equal properties
if (f0 == f1 || (f0 < 4 && f1 < 4))
return MIRROR(side);
// Source face
switch (f0)
{
// Top face; always end up north
case FACE_TOP:
return TOP;
// Source bottom; always end up south
case FACE_BOTTOM:
return BOTTOM;
}
// Destination face
switch (f1)
{
// Top face; rotate to the source face
case FACE_TOP:
return MIRROR(f0);
// Bottom face; rotate to the source face
case FACE_BOTTOM:
return (4 - f0) % 4;
}
return MIRROR(side);
}
unsigned int PlanetFaceNode::mirrorQuadrant(const unsigned int side, const unsigned int quadrant) const
{
// If mirroring within the parent node
if (!ADJACENT(side, quadrant))
return REFLECT(side, quadrant);
// If no parent or parent neighbor
if (!m_parent || !m_parent->m_neighbors[side])
return REFLECT(side, quadrant);
// Get source and destination faces
const unsigned int f0 = m_planetFace->getFace();
const unsigned int f1 = m_parent->m_neighbors[side]->m_planetFace->getFace();
// If within the same face or faces with equal properties
if (f0 == f1 || (f0 < 4 && f1 < 4))
return REFLECT(side, quadrant);
// Source face
switch (f0)
{
case FACE_FRONT:
return REFLECT(side, quadrant);
case FACE_LEFT:
switch(quadrant)
{
case TOP_RIGHT:
case BOTTOM_LEFT:
return BOTTOM_LEFT;
case TOP_LEFT:
case BOTTOM_RIGHT:
return TOP_LEFT;
}
return REFLECT(side, quadrant);
case FACE_BACK:
switch (quadrant)
{
case TOP_RIGHT:
return TOP_LEFT;
case TOP_LEFT:
return TOP_RIGHT;
case BOTTOM_RIGHT:
return BOTTOM_LEFT;
case BOTTOM_LEFT:
return BOTTOM_RIGHT;
}
return REFLECT(side, quadrant);
case FACE_RIGHT:
switch (quadrant)
{
case TOP_RIGHT:
case BOTTOM_LEFT:
return TOP_RIGHT;
case TOP_LEFT:
case BOTTOM_RIGHT:
return BOTTOM_RIGHT;
}
return REFLECT(side, quadrant);
case FACE_TOP:
switch (quadrant)
{
case TOP_RIGHT:
case BOTTOM_LEFT:
return (side == TOP || side == BOTTOM) ? TOP_LEFT : TOP_RIGHT;
case TOP_LEFT:
case BOTTOM_RIGHT:
return (side == TOP || side == BOTTOM) ? TOP_RIGHT : TOP_LEFT;
}
return REFLECT(side, quadrant);
case FACE_BOTTOM:
switch (quadrant)
{
case TOP_RIGHT:
case BOTTOM_LEFT:
return (side == TOP || side == BOTTOM) ? BOTTOM_RIGHT : BOTTOM_LEFT;
case TOP_LEFT:
case BOTTOM_RIGHT:
return (side == TOP || side == BOTTOM) ? BOTTOM_LEFT : BOTTOM_RIGHT;
}
}
return REFLECT(side, quadrant);
}
void PlanetFaceNode::setNeighbor(const unsigned int side, PlanetFaceNode *neighbor)
{
// Connect the nodes and update neighbor detail differences
m_neighbors[side] = neighbor;
if (neighbor)
{
const unsigned int sideMirrored = mirrorSide(side);
neighbor->m_neighbors[sideMirrored] = this;
neighbor->updateNeighborDetailDifferences(sideMirrored);
}
updateNeighborDetailDifferences(side);
}
// Returns the neighbor of equal depth if it exists; otherwise its youngest ancestor
const PlanetFaceNode* PlanetFaceNode::getEqualOrHigherNeighbor(const unsigned int side) const
{
// Find the youngest ancestor with a neighbor in the given direction
for (const PlanetFaceNode *node = this; node != 0; node = node->m_parent)
{
if (node->m_neighbors[side])
{
return node->m_neighbors[side];
}
}
return 0;
}
void PlanetFaceNode::findNeighbor(const unsigned int side)
{
// If the current node has no neighbor in the given direction, but its parent does
if (!m_neighbors[side] && m_parent && m_parent->m_neighbors[side])
{
// If a valid neighbor is found (child of the parent's neighbor); use it
if (PlanetFaceNode *neighbor = m_parent->m_neighbors[side]->m_children[mirrorQuadrant(side, m_quadrant)])
setNeighbor(side, neighbor);
else
return;
}
// If no leaf node; find child node neighbors
if (!isLeaf())
for (unsigned int i = 0; i < 4; i++)
if (ADJACENT(side, i))
m_children[i]->findNeighbor(side);
}
void PlanetFaceNode::updateNeighborDetailDifferences(const unsigned int side)
{
// Update neighbor detail differences
for (unsigned int i = 0; i < 4; i++)
{
if (const PlanetFaceNode *neighbor = getEqualOrHigherNeighbor(i)) {
neighborDetailDifferences[i] = std::min(m_level - neighbor->m_level, (unsigned int)4); // 4 max difference
}
}
// Force child nodes on the updated side to redraw
if (!isLeaf())
{
for (unsigned int i = 0; i < 4; i++)
{
if (ADJACENT(side, i))
{
m_children[i]->updateNeighborDetailDifferences(side);
}
}
}
}
// Returns the neighbor detail (depth) difference [0,MAX_DETAIL_DIFFERENCE]
unsigned int PlanetFaceNode::getNeighborDetailDifference(const unsigned int side) const
{
return neighborDetailDifferences[side];
}
void PlanetFaceNode::tick(Camera* camera, GLfloat dtime)
{
glm::mat4 transform = m_planetFace->getPlanet()->getTransformation();
glm::transpose(transform);
glm::vec3 campos = glm::vec3(transform * glm::vec4(camera->getPosition(), 1.0f));
float camToOrigin = glm::distance(campos, (m_center));
if (camToOrigin < m_radius * 2.0 && m_leaf) {
this->subdivide();
} else if (camToOrigin > m_radius * 2.0 && !m_leaf) {
this->merge();
return;
}
if (!m_leaf)
{
for (int i = 0; i < 4; i++)
{
m_children[i]->tick(camera, dtime);
}
return;
}
}
void PlanetFaceNode::draw(Camera* camera, Shader* shader)
{
// TODO: occlusion culling
if (!m_leaf)
{
for (int i = 0; i < 4; i++)
{
m_children[i]->draw(camera, shader);
}
return;
}
if (!m_generated)
return;
PlanetIndexBuffer* buffers = m_planetFace->getPlanet()->getIndexBuffer(
getNeighborDetailDifference(TOP),
getNeighborDetailDifference(RIGHT),
getNeighborDetailDifference(BOTTOM),
getNeighborDetailDifference(LEFT)
);
shader->setBuffers(m_vao, m_vbo, buffers->getIbo());
shader->use();
camera->shaderViewProjection(*shader);
shader->setUniform("modelMatrix", m_planetFace->getPlanet()->getTransformation());
glDrawElements(GL_TRIANGLES, buffers->getVertexCount(), GL_UNSIGNED_SHORT, nullptr);
}
void PlanetFaceNode::generate()
{
if (m_generated)
return;
int vertexCount = RESOLUTION * RESOLUTION;
Vertex vertices[vertexCount];
float divisionLevel = 1.0f / (float)pow(2.0, m_level);
float radius = m_planetFace->getPlanet()->getRadius();
float halfRes = (RESOLUTION - 1) / 2.f;
glm::vec2 rpos = getPosition();
for (int i = 0; i < RESOLUTION; i++)
{
for (int j = 0; j < RESOLUTION; j++)
{
// Get vertex position in the quadtree
glm::vec2 treePos = (glm::vec2(i, j) - halfRes) * divisionLevel / halfRes + rpos;
glm::vec3 vertex = glm::vec3(treePos, 1.0f);
// Orient the vertex to the face
vertex = glm::vec3(glm::vec4(vertex, 1.0f) * m_planetFace->getOrientation());
// Normalize and multiply by radius to create a spherical mesh (unit sphere)
float x2 = vertex.x * vertex.x;
float y2 = vertex.y * vertex.y;
float z2 = vertex.z * vertex.z;
glm::vec3 point = glm::vec3(
vertex.x * sqrt(1.0f - ((y2 + z2) / 2.0f) + ((y2 * z2) / 3.0f)),
vertex.y * sqrt(1.0f - ((z2 + x2) / 2.0f) + ((z2 * x2) / 3.0f)),
vertex.z * sqrt(1.0f - ((x2 + y2) / 2.0f) + ((x2 * y2) / 3.0f))
);
// Get noise height and multiply by radius
float height = m_planetFace->getPlanet()->getNoise().fractal(8, point.x, point.y, point.z) * 20.0f;
glm::vec3 pos = (height + radius) * point;
// Texture coordinate
glm::vec2 texCoord = glm::vec2(i, j) / (float)(RESOLUTION - 1);
texCoord = texCoord * (1.0f - (1.0f / 128.f)) + 0.5f * (1.0f / 128.f);
// Add vertex
vertices[INDEX1D(i, j)].position = pos;
vertices[INDEX1D(i, j)].normal = point;
vertices[INDEX1D(i, j)].uv = texCoord;
// Set center
if ((i == RESOLUTION / 2 && j == RESOLUTION / 2))
m_center = pos;
}
}
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertexCount, vertices, GL_STATIC_DRAW);
glm::vec3 boundingSphereCenterF = glm::vec3(0.0, 0.0, 0.0);
float boundingSphereRadiusF = 0.0;
// Calculate mean position and use as bounding sphere center
for (int i = 0; i < vertexCount; i++)
{
boundingSphereCenterF += vertices[i].position;
}
boundingSphereCenterF /= (float)vertexCount;
// Find the largest distance from the center to a vertex
for (int i = 0; i < vertexCount; i++)
{
glm::vec3 length = (vertices[i].position - boundingSphereCenterF);
boundingSphereRadiusF = std::max(boundingSphereRadiusF, glm::dot(length, length));
}
boundingSphereRadiusF = std::sqrt(boundingSphereRadiusF);
m_center = boundingSphereCenterF;
m_radius = boundingSphereRadiusF;
m_generated = true;
}
bool PlanetFaceNode::subdivide()
{
if (m_level == 8 || !m_planetFace->hasSplitsLeft() || !m_leaf)
return false;
m_children[TOP_LEFT] = new PlanetFaceNode(m_planetFace, this, TOP_LEFT);
m_children[TOP_RIGHT] = new PlanetFaceNode(m_planetFace, this, TOP_RIGHT);
m_children[BOTTOM_RIGHT] = new PlanetFaceNode(m_planetFace, this, BOTTOM_RIGHT);
m_children[BOTTOM_LEFT] = new PlanetFaceNode(m_planetFace, this, BOTTOM_LEFT);
// Connect the children
m_children[TOP_LEFT]->setNeighbor(RIGHT, m_children[TOP_RIGHT]);
m_children[TOP_RIGHT]->setNeighbor(BOTTOM, m_children[BOTTOM_RIGHT]);
m_children[BOTTOM_RIGHT]->setNeighbor(LEFT, m_children[BOTTOM_LEFT]);
m_children[BOTTOM_LEFT]->setNeighbor(TOP, m_children[TOP_LEFT]);
// Connect neighbors
for (int i = 0; i < 4; i++)
{
if (m_neighbors[i] && !m_neighbors[i]->isLeaf())
{
m_neighbors[i]->findNeighbor(mirrorSide(i));
}
}
// No longer leaf
m_leaf = false;
this->dispose();
return true;
}
glm::vec2 PlanetFaceNode::getPosition()
{
return m_parent ? m_parent->getPosition() + glm::vec2(ADJACENT(RIGHT, m_quadrant) ? 1.0 : -1.0, ADJACENT(TOP, m_quadrant) ? 1.0 : -1.0) * (1.0f / (1 << m_level)) : glm::vec2(0.0, 0.0);
}
bool PlanetFaceNode::merge()
{
// Leaves don't ever need to be merged
if (m_leaf)
return false;
// Merge and dispose the children
for (int i = 0; i < 4; i++)
{
m_children[i]->dispose();
delete m_children[i];
m_children[i] = 0;
}
// We're a leaf now
m_leaf = true;
generate();
return true;
}
void PlanetFaceNode::dispose()
{
m_generated = false;
}
bool PlanetFaceNode::isLeaf()
{
return m_leaf;
}
glm::vec3 PlanetFaceNode::getAbsolutePosition()
{
return m_planetFace->getPlanet()->getPosition();
}
PlanetFace::PlanetFace(
Planet* planet,
const unsigned int face
) :
m_planet(planet),
m_face(face)
{
if (face < 4)
{
m_orientation = glm::rotate(glm::mat4(1.0f), (float)((double)face * -0.5 * M_PI), glm::vec3(0.0f, -1.0f, 0.0f));
}
else
{
m_orientation = glm::rotate(glm::mat4(1.0f), (float)((face == FACE_BOTTOM ? 0.5 : -0.5) * M_PI), glm::vec3(-1.0f, 0.0f, 0.0f));
}
m_lod = new PlanetFaceNode(this, 0, 0);
}
PlanetFace::~PlanetFace()
{
delete m_lod;
}
void PlanetFace::draw(Camera* camera, Shader* shader)
{
m_lod->draw(camera, shader);
}
void PlanetFace::tick(Camera* camera, GLfloat dtime)
{
m_splits = MAX_SPLITS_PER_UPDATE;
m_lod->tick(camera, dtime);
}
void PlanetFace::connect(const unsigned int side, PlanetFace* face)
{
if (m_lod && face->getLODRoot()) {
m_lod->setNeighbor(side, face->getLODRoot());
}
}
bool PlanetFace::hasSplitsLeft()
{
if (m_splits == 0)
{
return false;
}
m_splits--;
return true;
}