/* * Copyright (C) 2021 Christopher J. Howard * * This file is part of Antkeeper source code. * * Antkeeper source code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper source code. If not, see . */ #include "game/system/terrain.hpp" #include "game/component/terrain.hpp" #include "game/component/camera.hpp" #include "geom/meshes/grid.hpp" #include "geom/mesh-functions.hpp" #include "geom/morton.hpp" #include "geom/quadtree.hpp" #include "geom/primitive/ray.hpp" #include "gl/vertex-attribute.hpp" #include "math/quaternion.hpp" #include "render/vertex-attribute.hpp" #include "utility/fundamental-types.hpp" #include #include namespace game { namespace system { terrain::terrain(entity::registry& registry): updatable(registry), patch_side_length(0.0f), patch_subdivisions(0), patch_material(nullptr), elevation_function(nullptr), scene_collection(nullptr), patch_base_mesh(nullptr), patch_vertex_size(0), patch_vertex_stride(0), patch_vertex_data(nullptr) { // Specify vertex size and stride // (position + uv + normal + tangent + barycentric + target) patch_vertex_size = 3 + 2 + 3 + 4 + 3 + 3; patch_vertex_stride = patch_vertex_size * sizeof(float); // Init quadtee node sizes at each depth for (std::size_t i = 0; i <= quadtree_type::max_depth; ++i) quadtree_node_size[i] = 0.0f; registry.on_construct().connect<&terrain::on_terrain_construct>(this); registry.on_update().connect<&terrain::on_terrain_update>(this); registry.on_destroy().connect<&terrain::on_terrain_destroy>(this); } terrain::~terrain() { registry.on_construct().disconnect<&terrain::on_terrain_construct>(this); registry.on_update().disconnect<&terrain::on_terrain_update>(this); registry.on_destroy().disconnect<&terrain::on_terrain_destroy>(this); } void terrain::update(double t, double dt) { // Clear quadtree quadtree.clear(); // For each camera this->registry.view().each ( [&](entity::id camera_eid, const auto& camera) { if (!camera.object) return; const scene::camera& cam = *camera.object; // for (int i = 0; i < 8; ++i) // std::cout << "corner " << i << ": " << cam.get_view_frustum().get_corners()[i] << std::endl; geom::primitive::ray rays[8]; rays[0] = cam.pick({-1, -1}); rays[1] = cam.pick({-1, 1}); rays[2] = cam.pick({ 1, 1}); rays[3] = cam.pick({ 1, -1}); float3 ntl = rays[0].origin; float3 nbl = rays[1].origin; float3 nbr = rays[2].origin; float3 ntr = rays[3].origin; float3 ftl = rays[0].origin + rays[0].direction * (cam.get_clip_far() - cam.get_clip_near()); float3 fbl = rays[1].origin + rays[1].direction * (cam.get_clip_far() - cam.get_clip_near()); float3 fbr = rays[2].origin + rays[2].direction * (cam.get_clip_far() - cam.get_clip_near()); float3 ftr = rays[3].origin + rays[3].direction * (cam.get_clip_far() - cam.get_clip_near()); // for (int i = 0; i < 8; ++i) // std::cout << "ray or " << i << ": " << rays[i].origin << std::endl; geom::convex_hull hull(6); hull.planes[0] = geom::plane(ftl, fbl, nbl); hull.planes[1] = geom::plane(ntr, nbr, fbr); hull.planes[2] = geom::plane(fbl, fbr, nbr); hull.planes[3] = geom::plane(ftl, ntl, ntr); hull.planes[4] = geom::plane(ntl, nbl, nbr); hull.planes[5] = geom::plane(ftr, fbr, fbl); geom::sphere sphere; sphere.center = cam.get_translation(); sphere.radius = cam.get_clip_far() * 0.25; //visit_quadtree(cam.get_view_frustum().get_bounds(), quadtree_type::root); visit_quadtree(sphere, quadtree_type::root); } ); //std::cout << "qsize: " << quadtree.size() << std::endl; std::size_t qvis = 0; /// Toggle visibility of terrain scene objects for (auto it = patches.begin(); it != patches.end(); ++it) { bool active = (quadtree.contains(it->first) && quadtree.is_leaf(it->first)); it->second->model_instance->set_active(active); if (active) ++qvis; } //std::cout << "qvis: " << qvis << std::endl; } void terrain::set_patch_side_length(float length) { patch_side_length = length; // Recalculate node sizes at each quadtree depth for (std::size_t i = 0; i <= quadtree_type::max_depth; ++i) { quadtree_node_size[i] = std::exp2(quadtree_type::max_depth - i) * patch_side_length; //std::cout << quadtree_node_size[i] << std::endl; } } void terrain::set_patch_subdivisions(std::size_t n) { patch_subdivisions = n; // Recalculate patch properties patch_cell_count = (patch_subdivisions + 1) * (patch_subdivisions + 1); patch_triangle_count = patch_cell_count * 2; // Resize patch vertex data buffer delete[] patch_vertex_data; patch_vertex_data = new float[patch_triangle_count * 3 * patch_vertex_size]; // Resize patch buffers std::size_t vertex_buffer_row_size = patch_subdivisions + 4; std::size_t vertex_buffer_column_size = vertex_buffer_row_size; patch_vertex_buffer.resize(vertex_buffer_row_size); for (std::size_t i = 0; i < patch_vertex_buffer.size(); ++i) patch_vertex_buffer[i].resize(vertex_buffer_column_size); rebuild_patch_base_mesh(); } void terrain::set_patch_material(::render::material* material) { patch_material = material; } void terrain::set_elevation_function(const std::function& f) { elevation_function = f; } void terrain::set_scene_collection(scene::collection* collection) { scene_collection = collection; } void terrain::on_terrain_construct(entity::registry& registry, entity::id entity_id) { } void terrain::on_terrain_update(entity::registry& registry, entity::id entity_id) { } void terrain::on_terrain_destroy(entity::registry& registry, entity::id entity_id) { } float terrain::get_patch_size(quadtree_node_type node) const { return quadtree_node_size[quadtree_type::depth(node)]; } float3 terrain::get_patch_center(quadtree_node_type node) const { const float node_size = get_patch_size(node); const float node_offset = quadtree_node_size[0] * -0.5f + node_size * 0.5f; // Extract node location from Morton location code quadtree_type::node_type node_location = quadtree_type::location(node); quadtree_type::node_type node_location_x; quadtree_type::node_type node_location_y; geom::morton::decode(node_location, node_location_x, node_location_y); return float3 { node_offset + static_cast(node_location_x) * node_size, 0.0f, node_offset + static_cast(node_location_y) * node_size }; } void terrain::rebuild_patch_base_mesh() { // Rebuild grid delete patch_base_mesh; patch_base_mesh = geom::meshes::grid_xy(1.0f, patch_subdivisions, patch_subdivisions); // Convert quads to triangle fans for (std::size_t i = 0; i < patch_base_mesh->get_faces().size(); ++i) { geom::mesh::face* face = patch_base_mesh->get_faces()[i]; std::size_t edge_count = 1; for (geom::mesh::edge* edge = face->edge->next; edge != face->edge; edge = edge->next) ++edge_count; if (edge_count > 3) { geom::poke_face(*patch_base_mesh, face->index); --i; } } // Transform patch base mesh coordinates from XY plane to XZ plane const math::quaternion xy_to_xz = math::quaternion::rotate_x(math::half_pi); for (geom::mesh::vertex* vertex: patch_base_mesh->get_vertices()) { vertex->position = xy_to_xz * vertex->position; } } void terrain::visit_quadtree(const geom::bounding_volume& volume, quadtree_node_type node) { const float root_offset = quadtree_node_size[0] * -0.5f; // Extract node depth quadtree_type::node_type node_depth = quadtree_type::depth(node); const float node_size = get_patch_size(node); const float3 node_center = get_patch_center(node); // Build node bounds AABB geom::aabb node_bounds; node_bounds.min_point = { node_center.x() - node_size * 0.5f, quadtree_node_size[quadtree_type::max_depth] * -0.5f, node_center.z() - node_size * 0.5f }; node_bounds.max_point = { node_bounds.min_point[0] + node_size, node_bounds.min_point[1] + quadtree_node_size[quadtree_type::max_depth], node_bounds.min_point[2] + node_size }; // If volume intersects node if (volume.intersects(node_bounds)) { // Subdivide leaf nodes if (quadtree.is_leaf(node)) { quadtree.insert(quadtree_type::child(node, 0)); for (quadtree_node_type i = 0; i < quadtree_type::children_per_node; ++i) { quadtree_node_type child = quadtree_type::child(node, i); if (patches.find(child) == patches.end()) { patch* child_patch = generate_patch(child); patches[child] = child_patch; scene_collection->add_object(child_patch->model_instance); } } } // Visit children if (node_depth < quadtree_type::max_depth - 1) { for (quadtree_node_type i = 0; i < quadtree_type::children_per_node; ++i) visit_quadtree(volume, quadtree_type::child(node, i)); } } } geom::mesh* terrain::generate_patch_mesh(quadtree_node_type node) const { // Extract node depth const quadtree_type::node_type node_depth = quadtree_type::depth(node); // Get size of node at depth const float node_size = quadtree_node_size[node_depth]; // Extract node Morton location code and decode location const quadtree_type::node_type node_location = quadtree_type::location(node); quadtree_type::node_type node_location_x; quadtree_type::node_type node_location_y; geom::morton::decode(node_location, node_location_x, node_location_y); // Determine center of node const float node_offset = quadtree_node_size[0] * -0.5f + node_size * 0.5f; const float3 node_center = { node_offset + static_cast(node_location_x) * node_size, 0.0f, node_offset + static_cast(node_location_y) * node_size }; // Copy patch base mesh geom::mesh* patch_mesh = new geom::mesh(*patch_base_mesh); // Modify patch mesh vertex positions for (geom::mesh::vertex* v: patch_mesh->get_vertices()) { v->position.x() = node_center.x() + v->position.x() * node_size; v->position.z() = node_center.z() + v->position.z() * node_size; v->position.y() = elevation_function(v->position.x(), v->position.z()); } return patch_mesh; } ::render::model* terrain::generate_patch_model(quadtree_node_type node) const { // Get size and position of patch const float patch_size = get_patch_size(node); const float3 patch_center = get_patch_center(node); // Calculate size of a patch cell const float cell_size = patch_size / static_cast(patch_subdivisions + 1); const float half_cell_size = cell_size * 0.5f; // Init patch bounds geom::aabb patch_bounds; patch_bounds.min_point.x() = patch_center.x() - patch_size * 0.5f; patch_bounds.min_point.y() = std::numeric_limits::infinity(); patch_bounds.min_point.z() = patch_center.z() - patch_size * 0.5f; patch_bounds.max_point.x() = patch_center.x() + patch_size * 0.5f; patch_bounds.max_point.y() = -std::numeric_limits::infinity(); patch_bounds.max_point.z() = patch_center.z() + patch_size * 0.5f; // Calculate positions and UVs of patch vertices and immediately neighboring vertices float3 first_vertex_position = { patch_bounds.min_point.x() - cell_size, patch_center.y(), patch_bounds.min_point.z() - cell_size }; float3 vertex_position = first_vertex_position; for (std::size_t i = 0; i < patch_vertex_buffer.size(); ++i) { // For each column for (std::size_t j = 0; j < patch_vertex_buffer[i].size(); ++j) { // Calculate vertex elevation vertex_position.y() = elevation_function(vertex_position.x(), vertex_position.z()); // Update patch bounds patch_bounds.min_point.y() = std::min(patch_bounds.min_point.y(), vertex_position.y()); patch_bounds.max_point.y() = std::max(patch_bounds.max_point.y(), vertex_position.y()); // Update patch vertex position patch_vertex_buffer[i][j].position = vertex_position; // Calculate patch vertex UV patch_vertex_buffer[i][j].uv.x() = (vertex_position.x() - patch_bounds.min_point.x()) / patch_size; patch_vertex_buffer[i][j].uv.y() = (vertex_position.z() - patch_bounds.min_point.z()) / patch_size; // Init patch vertex normal, tangent, and bitangent patch_vertex_buffer[i][j].normal = {0, 0, 0}; patch_vertex_buffer[i][j].tangent = {0, 0, 0}; patch_vertex_buffer[i][j].bitangent = {0, 0, 0}; vertex_position.x() += cell_size; } vertex_position.z() += cell_size; vertex_position.x() = first_vertex_position.x(); } // Accumulate normals, tangents, and bitangents for (std::size_t i = 0; i < patch_vertex_buffer.size() - 1; ++i) { for (std::size_t j = 0; j < patch_vertex_buffer[i].size() - 1; ++j) { patch_vertex& a = patch_vertex_buffer[i ][j]; patch_vertex& b = patch_vertex_buffer[i+1][j]; patch_vertex& c = patch_vertex_buffer[i ][j+1]; patch_vertex& d = patch_vertex_buffer[i+1][j+1]; auto add_ntb = [](auto& a, auto& b, auto& c) { const float3 ba = b.position - a.position; const float3 ca = c.position - a.position; const float2 uvba = b.uv - a.uv; const float2 uvca = c.uv - a.uv; const float3 normal = math::normalize(math::cross(ba, ca)); const float f = 1.0f / (uvba.x() * uvca.y() - uvca.x() * uvba.y()); const float3 tangent = (ba * uvca.y() - ca * uvba.y()) * f; const float3 bitangent = (ba * -uvca.x() + ca * uvba.x()) * f; a.normal += normal; a.tangent += tangent; a.bitangent += bitangent; b.normal += normal; b.tangent += tangent; b.bitangent += bitangent; c.normal += normal; c.tangent += tangent; c.bitangent += bitangent; }; if ((j + i) % 2) { add_ntb(a, b, c); add_ntb(c, b, d); } else { add_ntb(a, b, d); add_ntb(a, d, c); } } } // Finalize normals, tangents, and bitangent signs of patch vertices for (std::size_t i = 1; i < patch_vertex_buffer.size() - 1; ++i) { for (std::size_t j = 1; j < patch_vertex_buffer[i].size() - 1; ++j) { auto& vertex = patch_vertex_buffer[i][j]; // Normalize normal vertex.normal = math::normalize(vertex.normal); // Gram-Schmidt orthogonalize tangent vertex.tangent = math::normalize(vertex.tangent - vertex.normal * math::dot(vertex.normal, vertex.tangent)); // Calculate bitangent sign vertex.bitangent_sign = std::copysign(1.0f, math::dot(math::cross(vertex.normal, vertex.tangent), vertex.bitangent)); } } /* 0 subdivisions: +---+---+---+ | | + +---+ + | | | | + +---+ + | | +---+---+---+ 1 subdivision: +---+---+---+---+ | | + +---+---+ + | | | | | + +---+---+ + | | | | | + +---+---+ + | | +---+---+---+---+ 2 subdivisions: +---+---+---+---+---+ | | + +---+---+---+ + | | | | | | + +---+---+---+ + | | | | | | + +---+---+---+ + | | | | | | + +---+---+---+ + | | +---+---+---+---+---+ */ // For each row float* v = patch_vertex_data; for (std::size_t i = 1; i < patch_vertex_buffer.size() - 2; ++i) { // For each column for (std::size_t j = 1; j < patch_vertex_buffer[i].size() - 2; ++j) { // a---c // | | // b---d const patch_vertex& a = patch_vertex_buffer[i ][j]; const patch_vertex& b = patch_vertex_buffer[i+1][j]; const patch_vertex& c = patch_vertex_buffer[i ][j+1]; const patch_vertex& d = patch_vertex_buffer[i+1][j+1]; auto add_triangle = [&v](const patch_vertex& a, const patch_vertex& b, const patch_vertex& c) { auto add_vertex = [&v](const patch_vertex& vertex, const float3& barycentric) { // Position *(v++) = vertex.position[0]; *(v++) = vertex.position[1]; *(v++) = vertex.position[2]; // UV *(v++) = vertex.uv[0]; *(v++) = vertex.uv[1]; // Normal *(v++) = vertex.normal[0]; *(v++) = vertex.normal[1]; *(v++) = vertex.normal[2]; /// Tangent *(v++) = vertex.tangent[0]; *(v++) = vertex.tangent[1]; *(v++) = vertex.tangent[2]; *(v++) = vertex.bitangent_sign; // Barycentric *(v++) = barycentric[0]; *(v++) = barycentric[1]; *(v++) = barycentric[2]; // Morph target (LOD transition) *(v++) = 0.0f; *(v++) = 0.0f; *(v++) = 0.0f; }; add_vertex(a, float3{1, 0, 0}); add_vertex(b, float3{0, 1, 0}); add_vertex(c, float3{0, 0, 1}); }; if ((j + i) % 2) { add_triangle(a, b, c); add_triangle(c, b, d); } else { add_triangle(a, b, d); add_triangle(a, d, c); } } } // Allocate patch model ::render::model* patch_model = new ::render::model(); // Get model VBO and VAO gl::vertex_buffer* vbo = patch_model->get_vertex_buffer(); gl::vertex_array* vao = patch_model->get_vertex_array(); // Resize model VBO and upload vertex data vbo->resize(patch_triangle_count * 3 * patch_vertex_stride, patch_vertex_data); std::size_t attribute_offset = 0; // Define position vertex attribute gl::vertex_attribute position_attribute; position_attribute.buffer = vbo; position_attribute.offset = attribute_offset; position_attribute.stride = patch_vertex_stride; position_attribute.type = gl::vertex_attribute_type::float_32; position_attribute.components = 3; attribute_offset += position_attribute.components * sizeof(float); // Define UV vertex attribute gl::vertex_attribute uv_attribute; uv_attribute.buffer = vbo; uv_attribute.offset = attribute_offset; uv_attribute.stride = patch_vertex_stride; uv_attribute.type = gl::vertex_attribute_type::float_32; uv_attribute.components = 2; attribute_offset += uv_attribute.components * sizeof(float); // Define normal vertex attribute gl::vertex_attribute normal_attribute; normal_attribute.buffer = vbo; normal_attribute.offset = attribute_offset; normal_attribute.stride = patch_vertex_stride; normal_attribute.type = gl::vertex_attribute_type::float_32; normal_attribute.components = 3; attribute_offset += normal_attribute.components * sizeof(float); // Define tangent vertex attribute gl::vertex_attribute tangent_attribute; tangent_attribute.buffer = vbo; tangent_attribute.offset = attribute_offset; tangent_attribute.stride = patch_vertex_stride; tangent_attribute.type = gl::vertex_attribute_type::float_32; tangent_attribute.components = 4; attribute_offset += tangent_attribute.components * sizeof(float); // Define barycentric vertex attribute gl::vertex_attribute barycentric_attribute; barycentric_attribute.buffer = vbo; barycentric_attribute.offset = attribute_offset; barycentric_attribute.stride = patch_vertex_stride; barycentric_attribute.type = gl::vertex_attribute_type::float_32; barycentric_attribute.components = 3; attribute_offset += barycentric_attribute.components * sizeof(float); // Define target vertex attribute gl::vertex_attribute target_attribute; target_attribute.buffer = vbo; target_attribute.offset = attribute_offset; target_attribute.stride = patch_vertex_stride; target_attribute.type = gl::vertex_attribute_type::float_32; target_attribute.components = 3; attribute_offset += target_attribute.components * sizeof(float); // Bind vertex attributes to VAO vao->bind(::render::vertex_attribute::position, position_attribute); vao->bind(::render::vertex_attribute::uv, uv_attribute); vao->bind(::render::vertex_attribute::normal, normal_attribute); vao->bind(::render::vertex_attribute::tangent, tangent_attribute); vao->bind(::render::vertex_attribute::barycentric, barycentric_attribute); vao->bind(::render::vertex_attribute::target, target_attribute); // Create model group ::render::model_group* patch_model_group = patch_model->add_group("terrain"); patch_model_group->set_material(patch_material); patch_model_group->set_drawing_mode(gl::drawing_mode::triangles); patch_model_group->set_start_index(0); patch_model_group->set_index_count(patch_triangle_count * 3); // Set patch model bounds patch_model->set_bounds(patch_bounds); return patch_model; } terrain::patch* terrain::generate_patch(quadtree_node_type node) { patch* node_patch = new patch(); node_patch->mesh = nullptr;//generate_patch_mesh(node); node_patch->model = generate_patch_model(node); node_patch->model_instance = new scene::model_instance(node_patch->model); return node_patch; } } // namespace system } // namespace game