/*
|
|
* Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "subterrain-system.hpp"
|
|
#include "game/components/model-component.hpp"
|
|
#include "game/components/cavity-component.hpp"
|
|
#include "renderer/model.hpp"
|
|
#include "renderer/material.hpp"
|
|
#include "geometry/mesh-functions.hpp"
|
|
#include "renderer/vertex-attributes.hpp"
|
|
#include "rasterizer/vertex-attribute-type.hpp"
|
|
#include "rasterizer/drawing-mode.hpp"
|
|
#include "rasterizer/vertex-buffer.hpp"
|
|
#include "resources/resource-manager.hpp"
|
|
#include "geometry/marching-cubes.hpp"
|
|
#include "geometry/intersection.hpp"
|
|
#include "scene/scene.hpp"
|
|
#include "scene/model-instance.hpp"
|
|
#include "utility/fundamental-types.hpp"
|
|
#include <array>
|
|
#include <limits>
|
|
|
|
using namespace ecs;
|
|
|
|
/**
|
|
* An octree containing cubes for the marching cubes algorithm.
|
|
*/
|
|
struct cube_tree
|
|
{
|
|
public:
|
|
cube_tree(const aabb<float>& bounds, int max_depth);
|
|
~cube_tree();
|
|
|
|
const bool is_leaf() const;
|
|
const aabb<float>& get_bounds() const;
|
|
|
|
/// Subdivides all nodes intersecting with a region to the max depth.
|
|
void subdivide_max(const aabb<float>& region);
|
|
|
|
/// Fills a list with all leaf nodes that intersect with a region.
|
|
void query_leaves(std::list<cube_tree*>& nodes, const aabb<float>& region);
|
|
void visit_leaves(const aabb<float>& region, const std::function<void(cube_tree&)>& f);
|
|
|
|
/// Counts then number of nodes in the octree.
|
|
std::size_t size() const;
|
|
|
|
cube_tree* children[8];
|
|
float3 corners[8];
|
|
float distances[8];
|
|
const int max_depth;
|
|
const int depth;
|
|
const aabb<float> bounds;
|
|
|
|
private:
|
|
cube_tree(const aabb<float>& bounds, int max_depth, int depth);
|
|
void subdivide();
|
|
};
|
|
|
|
cube_tree::cube_tree(const aabb<float>& bounds, int max_depth):
|
|
cube_tree(bounds, max_depth, 0)
|
|
{}
|
|
|
|
cube_tree::cube_tree(const aabb<float>& bounds, int max_depth, int depth):
|
|
bounds(bounds),
|
|
max_depth(max_depth),
|
|
depth(depth)
|
|
{
|
|
corners[0] = {bounds.min_point.x, bounds.min_point.y, bounds.min_point.z};
|
|
corners[1] = {bounds.max_point.x, bounds.min_point.y, bounds.min_point.z};
|
|
corners[2] = {bounds.max_point.x, bounds.max_point.y, bounds.min_point.z};
|
|
corners[3] = {bounds.min_point.x, bounds.max_point.y, bounds.min_point.z};
|
|
corners[4] = {bounds.min_point.x, bounds.min_point.y, bounds.max_point.z};
|
|
corners[5] = {bounds.max_point.x, bounds.min_point.y, bounds.max_point.z};
|
|
corners[6] = {bounds.max_point.x, bounds.max_point.y, bounds.max_point.z};
|
|
corners[7] = {bounds.min_point.x, bounds.max_point.y, bounds.max_point.z};
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
children[i] = nullptr;
|
|
distances[i] = -std::numeric_limits<float>::infinity();
|
|
|
|
// For outside normals
|
|
//distances[i] = std::numeric_limits<float>::infinity();
|
|
}
|
|
}
|
|
|
|
cube_tree::~cube_tree()
|
|
{
|
|
for (cube_tree* child: children)
|
|
delete child;
|
|
}
|
|
|
|
void cube_tree::subdivide_max(const aabb<float>& region)
|
|
{
|
|
if (depth != max_depth && aabb_aabb_intersection(bounds, region))
|
|
{
|
|
if (is_leaf())
|
|
subdivide();
|
|
|
|
for (cube_tree* child: children)
|
|
child->subdivide_max(region);
|
|
}
|
|
}
|
|
|
|
void cube_tree::query_leaves(std::list<cube_tree*>& nodes, const aabb<float>& region)
|
|
{
|
|
if (aabb_aabb_intersection(bounds, region))
|
|
{
|
|
if (is_leaf())
|
|
{
|
|
nodes.push_back(this);
|
|
}
|
|
else
|
|
{
|
|
for (cube_tree* child: children)
|
|
child->query_leaves(nodes, region);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cube_tree::visit_leaves(const aabb<float>& region, const std::function<void(cube_tree&)>& f)
|
|
{
|
|
if (aabb_aabb_intersection(bounds, region))
|
|
{
|
|
if (is_leaf())
|
|
{
|
|
f(*this);
|
|
}
|
|
else
|
|
{
|
|
for (cube_tree* child: children)
|
|
child->visit_leaves(region, f);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::size_t cube_tree::size() const
|
|
{
|
|
std::size_t node_count = 1;
|
|
if (!is_leaf())
|
|
{
|
|
for (cube_tree* child: children)
|
|
node_count += child->size();
|
|
}
|
|
|
|
return node_count;
|
|
}
|
|
|
|
inline const bool cube_tree::is_leaf() const
|
|
{
|
|
return (children[0] == nullptr);
|
|
}
|
|
|
|
inline const aabb<float>& cube_tree::get_bounds() const
|
|
{
|
|
return bounds;
|
|
}
|
|
|
|
void cube_tree::subdivide()
|
|
{
|
|
const float3 center = (bounds.min_point + bounds.max_point) * 0.5f;
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
aabb<float> child_bounds;
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
child_bounds.min_point[j] = std::min<float>(corners[i][j], center[j]);
|
|
child_bounds.max_point[j] = std::max<float>(corners[i][j], center[j]);
|
|
}
|
|
|
|
children[i] = new cube_tree(child_bounds, max_depth, depth + 1);
|
|
}
|
|
}
|
|
|
|
subterrain_system::subterrain_system(entt::registry& registry, ::resource_manager* resource_manager):
|
|
entity_system(registry),
|
|
resource_manager(resource_manager)
|
|
{
|
|
|
|
// Load subterrain materials
|
|
subterrain_inside_material = resource_manager->load<material>("subterrain-inside.mtl");
|
|
subterrain_inside_material = resource_manager->load<material>("subterrain-outside.mtl");
|
|
|
|
// Allocate subterrain model
|
|
subterrain_model = new model();
|
|
|
|
// Create inside model group
|
|
subterrain_inside_group = subterrain_model->add_group("inside");
|
|
subterrain_inside_group->set_material(resource_manager->load<material>("subterrain-inside.mtl"));
|
|
subterrain_inside_group->set_drawing_mode(drawing_mode::triangles);
|
|
subterrain_inside_group->set_start_index(0);
|
|
subterrain_inside_group->set_index_count(0);
|
|
|
|
// Create outside model group
|
|
subterrain_outside_group = subterrain_model->add_group("outside");
|
|
subterrain_outside_group->set_material(resource_manager->load<material>("subterrain-outside.mtl"));
|
|
subterrain_outside_group->set_drawing_mode(drawing_mode::triangles);
|
|
subterrain_outside_group->set_start_index(0);
|
|
subterrain_outside_group->set_index_count(0);
|
|
|
|
// Determine vertex size (position, normal, barycentric)
|
|
subterrain_model_vertex_size = 3 + 3 + 3;
|
|
subterrain_model_vertex_stride = subterrain_model_vertex_size * sizeof(float);
|
|
|
|
// Bind vertex attributes
|
|
vertex_buffer* vbo = subterrain_model->get_vertex_buffer();
|
|
vertex_array* vao = subterrain_model->get_vertex_array();
|
|
std::size_t offset = 0;
|
|
vao->bind_attribute(VERTEX_POSITION_LOCATION, *vbo, 3, vertex_attribute_type::float_32, subterrain_model_vertex_stride, 0);
|
|
offset += 3;
|
|
vao->bind_attribute(VERTEX_NORMAL_LOCATION, *vbo, 3, vertex_attribute_type::float_32, subterrain_model_vertex_stride, sizeof(float) * offset);
|
|
offset += 3;
|
|
vao->bind_attribute(VERTEX_BARYCENTRIC_LOCATION, *vbo, 3, vertex_attribute_type::float_32, subterrain_model_vertex_stride, sizeof(float) * offset);
|
|
offset += 3;
|
|
|
|
// Calculate adjusted bounds to fit isosurface resolution
|
|
//isosurface_resolution = 0.325f;
|
|
isosurface_resolution = 0.5f;
|
|
float ideal_volume_size = 200.0f;
|
|
int octree_depth = std::floor(std::log(ideal_volume_size / isosurface_resolution) / std::log(2)) + 1;
|
|
float adjusted_volume_size = std::pow(2.0f, octree_depth) * isosurface_resolution;
|
|
|
|
// Set subterrain bounds
|
|
subterrain_bounds.min_point = float3{-0.5f, -1.0f, -0.5f} * adjusted_volume_size;
|
|
subterrain_bounds.max_point = float3{ 0.5f, 0.0f, 0.5f} * adjusted_volume_size;
|
|
|
|
// Set subterrain model bounds
|
|
subterrain_model->set_bounds(subterrain_bounds);
|
|
|
|
// Allocate cube tree
|
|
cube_tree = new ::cube_tree(subterrain_bounds, octree_depth);
|
|
|
|
// Allocate mesh
|
|
subterrain_mesh = new mesh();
|
|
|
|
first_run = true;
|
|
}
|
|
|
|
subterrain_system::~subterrain_system()
|
|
{
|
|
delete subterrain_model;
|
|
delete subterrain_mesh;
|
|
}
|
|
|
|
void subterrain_system::update(double t, double dt)
|
|
{
|
|
if (first_run)
|
|
{
|
|
first_run = false;
|
|
//auto subterrain_entity = registry.create();
|
|
//registry.assign<model_component>(subterrain_entity, subterrain_model);
|
|
|
|
subterrain_model_instance = new model_instance(subterrain_model);
|
|
scene->add_object(subterrain_model_instance);
|
|
}
|
|
|
|
bool digging = false;
|
|
|
|
registry.view<cavity_component>().each(
|
|
[this, &digging](auto entity, auto& cavity)
|
|
{
|
|
this->dig(cavity.position, cavity.radius);
|
|
registry.destroy(entity);
|
|
|
|
digging = true;
|
|
});
|
|
|
|
if (digging)
|
|
{
|
|
|
|
//std::cout << "regenerating subterrain mesh...\n";
|
|
regenerate_subterrain_mesh();
|
|
//std::cout << "regenerating subterrain mesh... done\n";
|
|
|
|
//std::cout << "regenerating subterrain model...\n";
|
|
regenerate_subterrain_model();
|
|
//std::cout << "regenerating subterrain model... done\n";
|
|
}
|
|
}
|
|
|
|
void subterrain_system::set_scene(::scene* scene)
|
|
{
|
|
this->scene = scene;
|
|
}
|
|
|
|
void subterrain_system::regenerate_subterrain_mesh()
|
|
{
|
|
delete subterrain_mesh;
|
|
subterrain_mesh = new mesh();
|
|
subterrain_vertices.clear();
|
|
subterrain_triangles.clear();
|
|
subterrain_vertex_map.clear();
|
|
|
|
//std::cout << "marching...\n";
|
|
merged = 0;
|
|
march(cube_tree);
|
|
//std::cout << "merged " << merged << " vertices\n";
|
|
//std::cout << "marching...done\n";
|
|
|
|
//std::cout << "vertex count: " << subterrain_vertices.size() << std::endl;
|
|
//std::cout << "triangle count: " << subterrain_triangles.size() << std::endl;
|
|
|
|
//std::cout << "creating mesh...\n";
|
|
create_triangle_mesh(*subterrain_mesh, subterrain_vertices, subterrain_triangles);
|
|
//std::cout << "creating mesh... done\n";
|
|
}
|
|
|
|
void subterrain_system::march(::cube_tree* node)
|
|
{
|
|
if (!node->is_leaf())
|
|
{
|
|
for (::cube_tree* child: node->children)
|
|
march(child);
|
|
return;
|
|
}
|
|
else if (node->depth != node->max_depth)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get node bounds
|
|
const aabb<float>& bounds = node->get_bounds();
|
|
|
|
// Polygonize cube
|
|
float vertex_buffer[12 * 3];
|
|
std::uint_fast8_t vertex_count;
|
|
std::int_fast8_t triangle_buffer[5 * 3];
|
|
std::uint_fast8_t triangle_count;
|
|
const float* corners = &node->corners[0][0];
|
|
const float* distances = &node->distances[0];
|
|
mc::polygonize(vertex_buffer, &vertex_count, triangle_buffer, &triangle_count, corners, distances);
|
|
|
|
// Remap local vertex buffer indices (0-11) to mesh vertex indices
|
|
std::uint_fast32_t vertex_remap[12];
|
|
for (int i = 0; i < vertex_count; ++i)
|
|
{
|
|
const float3& vertex = reinterpret_cast<const float3&>(vertex_buffer[i * 3]);
|
|
|
|
if (auto it = subterrain_vertex_map.find(vertex); it != subterrain_vertex_map.end())
|
|
{
|
|
vertex_remap[i] = it->second;
|
|
++merged;
|
|
}
|
|
else
|
|
{
|
|
vertex_remap[i] = subterrain_vertices.size();
|
|
subterrain_vertex_map[vertex] = subterrain_vertices.size();
|
|
subterrain_vertices.push_back(vertex);
|
|
}
|
|
}
|
|
|
|
// Add triangles
|
|
for (std::uint_fast32_t i = 0; i < triangle_count; ++i)
|
|
{
|
|
subterrain_triangles.push_back(
|
|
{
|
|
vertex_remap[triangle_buffer[i * 3]],
|
|
vertex_remap[triangle_buffer[i * 3 + 1]],
|
|
vertex_remap[triangle_buffer[i * 3 + 2]]
|
|
});
|
|
}
|
|
}
|
|
|
|
void subterrain_system::regenerate_subterrain_model()
|
|
{
|
|
float3* face_normals = new float3[subterrain_mesh->get_faces().size()];
|
|
calculate_face_normals(face_normals, *subterrain_mesh);
|
|
|
|
static const float3 barycentric_coords[3] =
|
|
{
|
|
float3{1, 0, 0},
|
|
float3{0, 1, 0},
|
|
float3{0, 0, 1}
|
|
};
|
|
|
|
float* vertex_data = new float[subterrain_model_vertex_size * subterrain_mesh->get_faces().size() * 3];
|
|
float* v = vertex_data;
|
|
for (std::size_t i = 0; i < subterrain_mesh->get_faces().size(); ++i)
|
|
{
|
|
mesh::face* face = subterrain_mesh->get_faces()[i];
|
|
mesh::edge* ab = face->edge;
|
|
mesh::edge* bc = face->edge->next;
|
|
mesh::edge* ca = face->edge->previous;
|
|
mesh::vertex* a = ab->vertex;
|
|
mesh::vertex* b = bc->vertex;
|
|
mesh::vertex* c = ca->vertex;
|
|
mesh::vertex* vertices[3] = {a, b, c};
|
|
|
|
for (std::size_t j = 0; j < 3; ++j)
|
|
{
|
|
mesh::vertex* vertex = vertices[j];
|
|
|
|
float3 n = {0, 0, 0};
|
|
mesh::edge* start = vertex->edge;
|
|
mesh::edge* edge = start;
|
|
do
|
|
{
|
|
if (edge->face)
|
|
{
|
|
n += face_normals[edge->face->index];
|
|
}
|
|
|
|
edge = edge->previous->symmetric;
|
|
}
|
|
while (edge != start);
|
|
n = math::normalize(n);
|
|
|
|
//float3 n = reinterpret_cast<const float3&>(face_normals[i * 3]);
|
|
|
|
*(v++) = vertex->position[0];
|
|
*(v++) = vertex->position[1];
|
|
*(v++) = vertex->position[2];
|
|
|
|
*(v++) = n[0];
|
|
*(v++) = n[1];
|
|
*(v++) = n[2];
|
|
|
|
*(v++) = barycentric_coords[j][0];
|
|
*(v++) = barycentric_coords[j][1];
|
|
*(v++) = barycentric_coords[j][2];
|
|
}
|
|
}
|
|
|
|
// Resized VBO and upload vertex data
|
|
vertex_buffer* vbo = subterrain_model->get_vertex_buffer();
|
|
vbo->resize(subterrain_mesh->get_faces().size() * 3 * subterrain_model_vertex_stride, vertex_data);
|
|
|
|
// Deallocate vertex data
|
|
delete[] face_normals;
|
|
delete[] vertex_data;
|
|
|
|
// Update model groups
|
|
subterrain_inside_group->set_index_count(subterrain_mesh->get_faces().size() * 3);
|
|
subterrain_outside_group->set_index_count(subterrain_mesh->get_faces().size() * 3);
|
|
}
|
|
|
|
void subterrain_system::dig(const float3& position, float radius)
|
|
{
|
|
// Construct region containing the cavity sphere
|
|
aabb<float> region = {position, position};
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
region.min_point[i] -= radius + isosurface_resolution;
|
|
region.max_point[i] += radius + isosurface_resolution;
|
|
}
|
|
|
|
// Subdivide the octree to the maximum depth within the region
|
|
cube_tree->subdivide_max(region);
|
|
|
|
// Query all octree leaf nodes within the region
|
|
std::list<::cube_tree*> nodes;
|
|
cube_tree->visit_leaves(region,
|
|
[&position, radius](::cube_tree& node)
|
|
{
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
// For outside normals (also set node initial distance to +infinity)
|
|
//float distance = math::length(node->corners[i] - position) - radius;
|
|
// if (distance < node->distances[i])
|
|
|
|
float distance = radius - math::length(node.corners[i] - position);
|
|
if (distance > node.distances[i])
|
|
node.distances[i] = distance;
|
|
}
|
|
});
|
|
}
|
|
|