#include "planet/PlanetFace.h" #include #include // 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 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; }