492 lines
13 KiB
C++
492 lines
13 KiB
C++
#include "planet/PlanetFace.h"
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
// Correlates directly to Face enum
|
|
const glm::vec3 FACE_NORMALS[6] = {
|
|
// FACE_FRONT
|
|
glm::vec3(0.0f, 0.0f, -1.0f),
|
|
// FACE_LEFT
|
|
glm::vec3(-1.0f, 0.0f, 0.0f),
|
|
// FACE_BACK
|
|
glm::vec3(0.0f, 0.0f, 1.0f),
|
|
// FACE_RIGHT
|
|
glm::vec3(1.0f, 0.0f, 0.0f),
|
|
// FACE_TOP
|
|
glm::vec3(0.0f, 1.0f, 0.0f),
|
|
// FACE_BOTTOM
|
|
glm::vec3(0.0f, -1.0f, 0.0f)
|
|
};
|
|
|
|
PlanetFaceNode::PlanetFaceNode(PlanetFace* face, PlanetFaceNode* parent, glm::vec3 position, const unsigned int index) :
|
|
m_planetFace(face), m_pos(position), m_index(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()
|
|
{
|
|
glm::vec3 normal = face->getNormal();
|
|
|
|
// normal 0 1 0 left 1 0 0 forward 0 0 -1
|
|
// normal 0 0 -1 left 0 -1 0 forward -1 0 0
|
|
m_left = glm::vec3(normal.y, normal.z, normal.x);
|
|
m_forward = glm::cross(normal, m_left);
|
|
|
|
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_index)])
|
|
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)
|
|
{
|
|
// TODO: based on planet transform
|
|
float camToOrigin = glm::distance(camera->getPosition(), (m_planetFace->getPlanet()->getPosition() + m_center));
|
|
|
|
if (camToOrigin < m_radius * 2.0 && m_leaf) {
|
|
this->subdivide();
|
|
return;
|
|
} 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;
|
|
|
|
std::vector<Vertex> vertices;
|
|
|
|
float divisionLevel = (float)pow(2.0, m_level);
|
|
float radius = m_planetFace->getPlanet()->getRadius();
|
|
for (int i = 0; i < RESOLUTION; i++)
|
|
{
|
|
for (int j = 0; j < RESOLUTION; j++)
|
|
{
|
|
// Get the 2D index of the vertex on the plane from zero to one (1 = RESOLUTION - 1)
|
|
glm::vec2 index = glm::vec2(i, j) / (RESOLUTION - 1.0f);
|
|
|
|
// Generate the vertices on the plane using left and forward vectors.
|
|
// here 2 * index - 1 is used to convert 0 - 1 to -1 - 1, that is to
|
|
// generate the vertices starting from the center point of the unit plane.
|
|
glm::vec3 iv = (m_forward * (2.0f * index.x - 1.0f)) / divisionLevel;
|
|
glm::vec3 jv = (m_left * (2.0f * index.y - 1.0f)) / divisionLevel;
|
|
|
|
// Add the unit left and forward vectors to the origin, m_pos here
|
|
// being the center point, which in division level zero is the offset
|
|
// normal from the center of the cube.
|
|
glm::vec3 vertex = m_pos + jv + iv;
|
|
|
|
// 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;
|
|
|
|
// Add vertex
|
|
vertices.push_back({ pos, point, glm::vec2(j * (1.0 / RESOLUTION), i * (1.0 / RESOLUTION)) });
|
|
|
|
// 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) * vertices.size(), &(vertices[0]), GL_STATIC_DRAW);
|
|
|
|
glm::vec3 boundingSphereCenterF = glm::vec3(0.0, 0.0, 0.0);
|
|
float boundingSphereRadiusF = 0.0;
|
|
int patchVerticesTotal = RESOLUTION * RESOLUTION;
|
|
|
|
// Calculate mean position and use as bounding sphere center
|
|
for (int i = 0; i < patchVerticesTotal; i++)
|
|
{
|
|
boundingSphereCenterF += vertices[i].position;
|
|
}
|
|
boundingSphereCenterF /= (float)patchVerticesTotal;
|
|
|
|
// Find the largest distance from the center to a vertex
|
|
for (int i = 0; i < patchVerticesTotal; 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 == 10 || !m_planetFace->hasSplitsLeft() || !m_leaf)
|
|
return false;
|
|
|
|
int lv = m_level + 1;
|
|
|
|
// Calculate distance to move the vertices on the unit plane based on division level
|
|
glm::vec3 stepLeft = m_left * (1.0f / (float)pow(2, lv));
|
|
glm::vec3 stepForward = m_forward * (1.0f / (float)pow(2, lv));
|
|
|
|
m_children[TOP_LEFT] = new PlanetFaceNode(m_planetFace, this, m_pos + stepForward - stepLeft, TOP_LEFT);
|
|
m_children[TOP_RIGHT] = new PlanetFaceNode(m_planetFace, this, m_pos - stepForward - stepLeft, TOP_RIGHT);
|
|
m_children[BOTTOM_RIGHT] = new PlanetFaceNode(m_planetFace, this, m_pos - stepForward + stepLeft, BOTTOM_RIGHT);
|
|
m_children[BOTTOM_LEFT] = new PlanetFaceNode(m_planetFace, this, m_pos + stepForward + stepLeft, 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;
|
|
}
|
|
|
|
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() + m_pos;
|
|
}
|
|
|
|
PlanetFace::PlanetFace(Planet* planet, const unsigned int face) :
|
|
m_planet(planet), m_face(face)
|
|
{
|
|
m_normal = FACE_NORMALS[m_face];
|
|
m_lod = new PlanetFaceNode(this, 0, m_normal, 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;
|
|
}
|