Browse Source

Generalize octree class into N-dimensional hyperoctree, begin revision of terrain system

master
C. J. Howard 1 week ago
parent
commit
ce645e3108
13 changed files with 819 additions and 856 deletions
  1. +31
    -8
      src/entity/commands.cpp
  2. +1
    -1
      src/entity/commands.hpp
  3. +41
    -0
      src/entity/components/observer.hpp
  4. +4
    -4
      src/entity/components/terrain.hpp
  5. +151
    -244
      src/entity/systems/terrain.cpp
  6. +13
    -21
      src/entity/systems/terrain.hpp
  7. +1
    -91
      src/entity/systems/vegetation.cpp
  8. +2
    -2
      src/game/bootloader.cpp
  9. +36
    -42
      src/game/states/play-state.cpp
  10. +482
    -0
      src/geom/hyperoctree.hpp
  11. +12
    -426
      src/geom/octree.hpp
  12. +45
    -0
      src/geom/quadtree.hpp
  13. +0
    -17
      src/resources/entity-archetype-loader.cpp

+ 31
- 8
src/entity/commands.cpp View File

@ -23,6 +23,8 @@
#include "entity/components/copy-transform.hpp"
#include "entity/components/snap.hpp"
#include "entity/components/parent.hpp"
#include "entity/components/celestial-body.hpp"
#include "entity/components/terrain.hpp"
#include <limits>
namespace entity {
@ -75,15 +77,36 @@ void set_transform(entity::registry& registry, entity::id entity_id, const math:
}
}
void place(entity::registry& registry, entity::id entity_id, const float2& translation)
void place(entity::registry& registry, entity::id entity_id, entity::id celestial_body_id, double altitude, double latitude, double longitude)
{
component::snap component;
component.warp = true;
component.relative = false;
component.autoremove = true;
component.ray.origin = {translation[0], 10000.0f, translation[1]};
component.ray.direction = {0.0f, -1.0f, 0.0f};
registry.assign_or_replace<component::snap>(entity_id, component);
if (registry.has<component::transform>(entity_id))
{
double x = 0.0;
double y = altitude;
double z = 0.0;
if (registry.has<component::celestial_body>(celestial_body_id))
{
const component::celestial_body& celestial_body = registry.get<component::celestial_body>(celestial_body_id);
x = longitude * math::two_pi<double> * celestial_body.radius;
z = -latitude * math::two_pi<double> * celestial_body.radius;
if (registry.has<component::terrain>(celestial_body_id))
{
const component::terrain& terrain = registry.get<component::terrain>(celestial_body_id);
if (terrain.elevation != nullptr)
{
y += terrain.elevation(latitude, longitude);
}
}
}
component::transform& transform = registry.get<component::transform>(entity_id);
transform.local.translation = math::type_cast<float>(double3{x, y, z});
transform.warp = true;
}
}
void assign_render_layers(entity::registry& registry, entity::id entity_id, unsigned int layers)

+ 1
- 1
src/entity/commands.hpp View File

@ -35,7 +35,7 @@ void move_to(entity::registry& registry, entity::id entity_id, const float3& pos
void warp_to(entity::registry& registry, entity::id entity_id, const float3& position);
void set_scale(entity::registry& registry, entity::id entity_id, const float3& scale);
void set_transform(entity::registry& registry, entity::id entity_id, const math::transform<float>& transform, bool warp = false);
void place(entity::registry& registry, entity::id entity_id, const float2& translation);
void place(entity::registry& registry, entity::id entity_id, entity::id celestial_body_id, double altitude, double latitude, double longitude);
void assign_render_layers(entity::registry& registry, entity::id entity_id, unsigned int layers);
void bind_transform(entity::registry& registry, entity::id source_eid, entity::id target_eid);
math::transform<float> get_local_transform(entity::registry& registry, entity::id entity_id);

+ 41
- 0
src/entity/components/observer.hpp View File

@ -0,0 +1,41 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_ENTITY_COMPONENT_OBSERVER_HPP
#define ANTKEEPER_ENTITY_COMPONENT_OBSERVER_HPP
#include "entity/id.hpp"
#include "utility/fundamental-types.hpp"
namespace entity {
namespace component {
/// Observer
struct observer
{
entity::id reference_body_eid;
double altitude;
double latitude;
double longitude;
};
} // namespace component
} // namespace entity
#endif // ANTKEEPER_ENTITY_COMPONENT_OBSERVER_HPP

+ 4
- 4
src/entity/components/terrain.hpp View File

@ -20,18 +20,18 @@
#ifndef ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP
#define ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP
#include <functional>
namespace entity {
namespace component {
struct terrain
{
int subdivisions;
int x;
int z;
/// Function object which returns elevation (in meters) given latitude (radians) and longitude (radians).
std::function<double(double, double)> elevation;
};
} // namespace component
} // namespace entity
#endif // ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP

+ 151
- 244
src/entity/systems/terrain.cpp View File

@ -17,285 +17,192 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "terrain.hpp"
#include "entity/components/model.hpp"
#include "entity/components/collision.hpp"
#include "entity/components/transform.hpp"
#include "cart/relief-map.hpp"
#include "renderer/model.hpp"
#include "geom/mesh.hpp"
#include "geom/mesh-functions.hpp"
#include "renderer/vertex-attributes.hpp"
#include "gl/vertex-attribute-type.hpp"
#include "gl/drawing-mode.hpp"
#include "gl/vertex-buffer.hpp"
#include "resources/resource-manager.hpp"
#include "resources/image.hpp"
#include "entity/systems/terrain.hpp"
#include "utility/fundamental-types.hpp"
#include <limits>
#include "entity/components/observer.hpp"
#include "entity/components/terrain.hpp"
#include "entity/components/celestial-body.hpp"
#include "geom/quadtree.hpp"
namespace entity {
namespace system {
terrain::terrain(entity::registry& registry, ::resource_manager* resource_manager):
updatable(registry),
resource_manager(resource_manager)
{
registry.on_construct<component::terrain>().connect<&terrain::on_terrain_construct>(this);
registry.on_destroy<component::terrain>().connect<&terrain::on_terrain_destroy>(this);
heightmap = resource_manager->load<image>("grassland-heightmap.png");
heightmap_size = 2000.0f;
heightmap_scale = 150.0f;
}
terrain::~terrain()
{}
void terrain::update(double t, double dt)
/**
* A cube with six quadtrees as faces.
*/
struct quadtree_cube
{
registry.view<component::terrain, component::transform>().each(
[this](entity::id entity_id, auto& terrain, auto& transform)
{
transform.local.translation = float3{(float)terrain.x * patch_size, 0.0f, (float)terrain.z * patch_size};
transform.warp = true;
});
}
typedef geom::quadtree32 quadtree_type;
typedef quadtree_type::node_type node_type;
void clear();
/**
* Refines the quadtree cube.
*
* @param threshold Function object which, given a quadsphere face index and quadtree node, returns `true` if the node should be subdivided, and `false` otherwise.
*/
void refine(const std::function<bool(std::uint8_t, node_type)>& threshold);
quadtree_type faces[6];
};
void terrain::set_patch_size(float size)
void quadtree_cube::clear()
{
patch_size = size;
for (std::uint8_t i = 0; i < 6; ++i)
faces[i].clear();
}
geom::mesh* terrain::generate_terrain_mesh(float size, int subdivisions)
void quadtree_cube::refine(const std::function<bool(std::uint8_t, node_type)>& threshold)
{
auto elevation = [](float u, float v) -> float
for (std::uint8_t i = 0; i < 6; ++i)
{
return 0.0f;
};
return cart::map_elevation(elevation, size, subdivisions);
for (auto it = faces[i].begin(); it != faces[i].end(); ++it)
{
node_type node = *it;
if (threshold(i, node))
faces[i].insert(quadtree_type::child(node, 0));
}
}
}
model* terrain::generate_terrain_model(geom::mesh* terrain_mesh)
{
// Allocate model
model* terrain_model = new model();
/*
terrain_qtc.refine
(
[observer](std::uint8_t face_index, quadtree_cube_type::node_type node) -> bool
{
// Extract morton location code
quadtree_type::node_type location = quadtree_type::location(node);
quadtree_type::node_type morton_x;
quadtree_type::node_type morton_y;
geom::morton::decode(location, morton_x, morton_y);
// Extract depth
quadtree_type::node_type depth = quadtree_type::depth(node);
// Determine fractional side length at depth
float length = 1.0f / std::exp2(depth);
// Determine fractional center of node
float3 center;
center.x = (static_cast<float>(morton_x) * length + length * 0.5f) * 2.0f - 1.0f;
center.y = (static_cast<float>(morton_y) * length + length * 0.5f) * 2.0f - 1.0f;
center.z = 1.0f;
// Project node center onto unit sphere
center = math::normalize(center);
// Rotate projected center into sphere space
center = face_rotations[face_index] * center;
// Scale center by body radius
center *= body_radius;
// Calculate distance from observer to node center
float distance = math::length(projected_center - observer_location);
if (depth < 4 && distance < ...)
return true;
return false;
}
);
*/
// Get model's VAO and VBO
gl::vertex_buffer* vbo = terrain_model->get_vertex_buffer();
gl::vertex_array* vao = terrain_model->get_vertex_array();
/**
* Queries a quad sphere for a list of leaf nodes. Leaf nodes will be inserted in the set
*
*
* 0. If observer position changed more than x amount:
* 1. Clear quad sphere
* 2. Insert leaves based on observer distance.
* 3. Pass quad sphere to tile generation function.
* 3. Iterate leaves, deriving the face, depth, and morton location from each leaf index.
* 4. Face, depth, and morton location can be used to determine latitude, longitude, and generate tiles.
* 5. Generated tiles cached and added to scene.
* 6. Record position of observer
*/
// Resize VBO
int vertex_size = 3 + 2 + 3 + 4 + 3;
int vertex_stride = vertex_size * sizeof(float);
vbo->resize(terrain_mesh->get_faces().size() * 3 * vertex_stride, nullptr);
/**
* Lat, lon determination:
*
* 1. Use morton location and depth to determine the x-y coordinates on a planar cube face.
* 2. Project x-y coordinates onto sphere.
* 3. Rotate coordinates according to face index.
* 4. Convert cartesian coordinates to spherical coordinates.
*/
// Bind vertex attributes
std::size_t offset = 0;
vao->bind_attribute(VERTEX_POSITION_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, vertex_stride, 0);
offset += 3;
vao->bind_attribute(VERTEX_TEXCOORD_LOCATION, *vbo, 2, gl::vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset);
offset += 2;
vao->bind_attribute(VERTEX_NORMAL_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset);
offset += 3;
vao->bind_attribute(VERTEX_TANGENT_LOCATION, *vbo, 4, gl::vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset);
offset += 4;
vao->bind_attribute(VERTEX_BARYCENTRIC_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, vertex_stride, sizeof(float) * offset);
offset += 3;
// Create model group
model_group* model_group = terrain_model->add_group("terrain");
model_group->set_material(resource_manager->load<material>("forest-terrain.mtl"));
model_group->set_drawing_mode(gl::drawing_mode::triangles);
model_group->set_start_index(0);
model_group->set_index_count(terrain_mesh->get_faces().size() * 3);
return terrain_model;
}
namespace entity {
namespace system {
void terrain::project_terrain_mesh(geom::mesh* terrain_mesh, const component::terrain& component)
terrain::terrain(entity::registry& registry):
updatable(registry),
patch_subdivisions(0),
patch_vertex_size(0),
patch_vertex_count(0),
patch_vertex_data(nullptr)
{
// position + uv + normal + tangent + barycentric
patch_vertex_size = 3 + 2 + 3 + 4 + 3;
float offset_x = (float)component.x * patch_size;
float offset_z = (float)component.z * patch_size;
for (geom::mesh::vertex* vertex: terrain_mesh->get_vertices())
{
int pixel_x = (vertex->position[0] + offset_x + heightmap_size * 0.5f) / heightmap_size * (float)(heightmap->get_width() - 1);
int pixel_y = (vertex->position[2] + offset_z + heightmap_size * 0.5f) / heightmap_size * (float)(heightmap->get_height() - 1);
pixel_x = std::max<int>(0, std::min<int>(heightmap->get_width() - 1, pixel_x));
pixel_y = std::max<int>(0, std::min<int>(heightmap->get_height() - 1, pixel_y));
int pixel_index = (pixel_y * heightmap->get_width() + pixel_x) * heightmap->get_channels();
const unsigned char* pixel = static_cast<const unsigned char*>(heightmap->get_pixels()) + pixel_index;
float elevation = (static_cast<float>(*pixel) / 255.0f - 0.5) * heightmap_scale;
vertex->position[1] = elevation;
}
}
void terrain::update_terrain_model(model* terrain_model, geom::mesh* terrain_mesh)
{
const std::vector<geom::mesh::face*>& faces = terrain_mesh->get_faces();
const std::vector<geom::mesh::vertex*>& vertices = terrain_mesh->get_vertices();
geom::aabb<float> bounds = calculate_bounds(*terrain_mesh);
float bounds_width = bounds.max_point.x - bounds.min_point.x;
float bounds_height = bounds.max_point.y - bounds.min_point.y;
float bounds_depth = bounds.max_point.z - bounds.min_point.z;
set_patch_subdivisions(0);
static const float3 barycentric_coords[3] =
{
float3{1, 0, 0},
float3{0, 1, 0},
float3{0, 0, 1}
};
int triangle_count = faces.size();
int vertex_count = triangle_count * 3;
int vertex_size = 3 + 2 + 3 + 4 + 3;
registry.on_construct<component::terrain>().connect<&terrain::on_terrain_construct>(this);
registry.on_destroy<component::terrain>().connect<&terrain::on_terrain_destroy>(this);
}
// Allocate vertex data
float* vertex_data = new float[vertex_size * vertex_count];
terrain::~terrain()
{}
// Allocate and calculate face normals
float3* face_normals = new float3[faces.size()];
calculate_face_normals(face_normals, *terrain_mesh);
// Allocate and calculate vertex normals
float3* vertex_normals = new float3[vertices.size()];
for (std::size_t i = 0; i < vertices.size(); ++i)
void terrain::update(double t, double dt)
{
// Subdivide or collapse quad sphere
registry.view<component::observer>().each(
[&](entity::id observer_eid, const auto& observer)
{
const geom::mesh::vertex* vertex = vertices[i];
float3 n = {0, 0, 0};
geom::mesh::edge* start = vertex->edge;
geom::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);
// Skip observers with null reference body
if (observer.reference_body_eid == entt::null)
return;
vertex_normals[i] = n;
}
// Allocate and generate vertex texture coordinates
float2* vertex_texcoords = new float2[vertices.size()];
for (std::size_t i = 0; i < vertices.size(); ++i)
{
const geom::mesh::vertex* vertex = vertices[i];
vertex_texcoords[i].x = (vertex->position.x - bounds.min_point.x) / bounds_width;
vertex_texcoords[i].y = (vertex->position.z - bounds.min_point.z) / bounds_depth;
}
// Allocate and calculate vertex tangents
float4* vertex_tangents = new float4[vertices.size()];
calculate_vertex_tangents(vertex_tangents, vertex_texcoords, vertex_normals, *terrain_mesh);
// Generate vertex data
float* v = vertex_data;
for (int i = 0; i < triangle_count; ++i)
{
const geom::mesh::face* triangle = faces[i];
const geom::mesh::vertex* a = triangle->edge->vertex;
const geom::mesh::vertex* b = triangle->edge->next->vertex;
const geom::mesh::vertex* c = triangle->edge->previous->vertex;
const geom::mesh::vertex* abc[] = {a, b, c};
for (int j = 0; j < 3; ++j)
{
const geom::mesh::vertex* vertex = abc[j];
const float3& position = vertex->position;
const float2& texcoord = vertex_texcoords[vertex->index];
const float3& normal = vertex_normals[vertex->index];
const float4& tangent = vertex_tangents[vertex->index];
const float3& barycentric = barycentric_coords[j];
// Skip observers with non-body or non-terrestrial reference bodies
if (!registry.has<component::celestial_body>(observer.reference_body_eid) ||
!registry.has<component::terrain>(observer.reference_body_eid))
return;
const auto& celestial_body = registry.get<component::celestial_body>(observer.reference_body_eid);
const auto& terrain = registry.get<component::terrain>(observer.reference_body_eid);
// Haversine distance to all 6 faces, then recursively to children
});
}
*(v++) = position.x;
*(v++) = position.y;
*(v++) = position.z;
*(v++) = texcoord.x;
*(v++) = texcoord.y;
*(v++) = normal.x;
*(v++) = normal.y;
*(v++) = normal.z;
*(v++) = tangent.x;
*(v++) = tangent.y;
*(v++) = tangent.z;
*(v++) = tangent.w;
*(v++) = barycentric.x;
*(v++) = barycentric.y;
*(v++) = barycentric.z;
}
}
void terrain::set_patch_subdivisions(std::uint8_t n)
{
patch_subdivisions = n;
// Update bounds
terrain_model->set_bounds(bounds);
// Update VBO
terrain_model->get_vertex_buffer()->update(0, vertex_count * vertex_size * sizeof(float), vertex_data);
// Free vertex data
delete[] face_normals;
delete[] vertex_normals;
delete[] vertex_texcoords;
delete[] vertex_tangents;
delete[] vertex_data;
// Recalculate number of vertices per patch
patch_vertex_count = static_cast<std::size_t>(std::pow(std::exp2(patch_subdivisions) + 1, 2));
// Resize patch vertex data buffer
delete[] patch_vertex_data;
patch_vertex_data = new float[patch_vertex_count * patch_vertex_size];
}
void terrain::on_terrain_construct(entity::registry& registry, entity::id entity_id, component::terrain& component)
{
geom::mesh* terrain_mesh = generate_terrain_mesh(patch_size, component.subdivisions);
model* terrain_model = generate_terrain_model(terrain_mesh);
project_terrain_mesh(terrain_mesh, component);
update_terrain_model(terrain_model, terrain_mesh);
// Assign the entity a collision component with the terrain mesh
component::collision collision;
collision.mesh = terrain_mesh;
collision.bounds = calculate_bounds(*terrain_mesh);
collision.mesh_accelerator.build(*collision.mesh);
registry.assign_or_replace<component::collision>(entity_id, collision);
// Assign the entity a model component with the terrain model
component::model model;
model.render_model = terrain_model;
model.instance_count = 0;
model.layers = 1;
registry.assign_or_replace<component::model>(entity_id, model);
// Assign the entity a transform component
component::transform transform;
transform.local = math::identity_transform<float>;
transform.local.translation = float3{(float)component.x * patch_size, 0.0f, (float)component.z * patch_size};
transform.warp = true;
registry.assign_or_replace<component::transform>(entity_id, transform);
// Build quad sphere
}
void terrain::on_terrain_destroy(entity::registry& registry, entity::id entity_id)
{
/*
if (auto it = terrain_map.find(entity_id); it != terrain_map.end())
{
delete std::get<0>(it->second);
delete std::get<1>(it->second);
terrain_map.erase(it);
}
*/
// Destroy quad sphere
}
void terrain::generate_patch()
{
}
} // namespace system

+ 13
- 21
src/entity/systems/terrain.hpp View File

@ -23,12 +23,6 @@
#include "entity/systems/updatable.hpp"
#include "entity/components/terrain.hpp"
#include "entity/id.hpp"
#include "geom/mesh.hpp"
class terrain;
class resource_manager;
class model;
class image;
namespace entity {
namespace system {
@ -36,33 +30,31 @@ namespace system {
class terrain: public updatable
{
public:
terrain(entity::registry& registry, ::resource_manager* resource_manager);
terrain(entity::registry& registry);
~terrain();
virtual void update(double t, double dt);
/**
* Sets the size of a single terrain patch.
* Sets the number of subdivisions for a patch.
*
* @param n Number of subdivisions.
*/
void set_patch_size(float size);
void set_patch_subdivisions(std::uint8_t n);
private:
geom::mesh* generate_terrain_mesh(float size, int subdivisions);
model* generate_terrain_model(geom::mesh* terrain_mesh);
void project_terrain_mesh(geom::mesh* terrain_mesh, const entity::component::terrain& component);
void update_terrain_model(model* terrain_model, geom::mesh* terrain_mesh);
void on_terrain_construct(entity::registry& registry, entity::id entity_id, entity::component::terrain& component);
void on_terrain_destroy(entity::registry& registry, entity::id entity_id);
resource_manager* resource_manager;
float patch_size;
float heightmap_size;
float heightmap_scale;
image* heightmap;
void generate_patch();
std::uint8_t patch_subdivisions;
std::size_t patch_vertex_size;
std::size_t patch_vertex_count;
float* patch_vertex_data;
};
} // namespace system
} // namespace entity
#endif // ANTKEEPER_ENTITY_SYSTEM_TERRAIN_HPP

+ 1
- 91
src/entity/systems/vegetation.cpp View File

@ -80,97 +80,7 @@ void vegetation::set_scene(scene::collection* collection)
}
void vegetation::on_terrain_construct(entity::registry& registry, entity::id entity_id, component::terrain& component)
{
// Find corner of terrain patch
float terrain_patch_min_x = static_cast<float>(component.x) * terrain_patch_size - terrain_patch_size * 0.5f;
float terrain_patch_min_z = static_cast<float>(component.z) * terrain_patch_size - terrain_patch_size * 0.5f;
// Create vegetation patches
for (int column = 0; column < vegetation_patch_columns; ++column)
{
for (int row = 0; row < vegetation_patch_rows; ++row)
{
/*
// Create vegetation patch entity
auto vegetation_patch_entity = registry.create();
// Assign a transform component
component::transform transform;
transform.local = math::identity_transform<float>;
transform.local.translation = float3{vegetation_patch_x, 0.0f, vegetation_patch_z};
transform.warp = true;
registry.assign_or_replace<component::transform>(vegetation_patch_entity, transform);
// Assign a model component
component::model model;
model.model = vegetation_model;
model.instance_count = 500;
registry.assign_or_replace<component::model>(vegetation_patch_entity, model);
*/
// Find patch translation
float vegetation_patch_x = terrain_patch_min_x + vegetation_patch_size * static_cast<float>(column) + vegetation_patch_size * 0.5f;
float vegetation_patch_z = terrain_patch_min_z + vegetation_patch_size * static_cast<float>(row) + vegetation_patch_size * 0.5f;
float3 translation = {vegetation_patch_x, 0.0f, vegetation_patch_z};
// Generate culling mask
geom::aabb<float>* culling_mask = new geom::aabb<float>(vegetation_model->get_bounds());
culling_mask->min_point.x = std::min<float>(culling_mask->min_point.x, translation.x - vegetation_patch_size * 0.5f);
culling_mask->min_point.z = std::min<float>(culling_mask->min_point.z, translation.z - vegetation_patch_size * 0.5f);
culling_mask->max_point.x = std::max<float>(culling_mask->max_point.x, translation.x + vegetation_patch_size * 0.5f);
culling_mask->max_point.z = std::max<float>(culling_mask->max_point.z, translation.z + vegetation_patch_size * 0.5f);
std::size_t lod_count = 4;
std::size_t instance_count_lod0 = 500;
std::size_t instance_count_lod1 = instance_count_lod0 / 2;
std::size_t instance_count_lod2 = instance_count_lod1 / 2;
// Generate LOD materials
const material* lod0_material = (*vegetation_model->get_groups())[0]->get_material();
material* lod1_material = new material(*lod0_material);
static_cast<material_property<int>*>(lod1_material->get_property("instance_multiplier"))->set_value(2);
material* lod2_material = new material(*lod0_material);
static_cast<material_property<int>*>(lod2_material->get_property("instance_multiplier"))->set_value(4);
// Create LOD 0
scene::model_instance* patch_lod0 = new scene::model_instance();
patch_lod0->set_model(vegetation_model);
patch_lod0->set_translation(translation);
patch_lod0->set_instanced(true, instance_count_lod0);
patch_lod0->set_culling_mask(culling_mask);
patch_lod0->update_tweens();
// Create LOD 1
scene::model_instance* patch_lod1 = new scene::model_instance();
patch_lod1->set_model(vegetation_model);
patch_lod1->set_material(0, lod1_material);
patch_lod1->set_translation(translation);
patch_lod1->set_instanced(true, instance_count_lod1);
patch_lod1->set_culling_mask(culling_mask);
patch_lod1->update_tweens();
// Create LOD 2
scene::model_instance* patch_lod2 = new scene::model_instance();
patch_lod2->set_model(vegetation_model);
patch_lod2->set_material(0, lod2_material);
patch_lod2->set_translation(translation);
patch_lod2->set_instanced(true, instance_count_lod2);
patch_lod2->set_culling_mask(culling_mask);
patch_lod2->update_tweens();
// Create LOD group
scene::lod_group* lod_group = new scene::lod_group(lod_count);
lod_group->add_object(0, patch_lod0);
lod_group->add_object(1, patch_lod1);
lod_group->add_object(2, patch_lod2);
lod_group->set_translation(translation);
lod_group->update_tweens();
// Add LOD group to scene
scene_collection->add_object(lod_group);
}
}
}
{}
void vegetation::on_terrain_destroy(entity::registry& registry, entity::id entity_id)
{}

+ 2
- 2
src/game/bootloader.cpp View File

@ -793,8 +793,8 @@ void setup_systems(game_context* ctx)
const double3 rgb_wavelengths_nm = {602.224, 541.069, 448.143};
// Setup terrain system
ctx->terrain_system = new entity::system::terrain(*ctx->entity_registry, ctx->resource_manager);
ctx->terrain_system->set_patch_size(TERRAIN_PATCH_SIZE);
ctx->terrain_system = new entity::system::terrain(*ctx->entity_registry);
ctx->terrain_system->set_patch_subdivisions(4);
// Setup vegetation system
//ctx->vegetation_system = new entity::system::vegetation(*ctx->entity_registry);

+ 36
- 42
src/game/states/play-state.cpp View File

@ -36,6 +36,7 @@
#include "entity/components/celestial-body.hpp"
#include "entity/components/atmosphere.hpp"
#include "entity/components/light.hpp"
#include "entity/components/observer.hpp"
#include "entity/commands.hpp"
#include "game/game-context.hpp"
#include "game/states/game-states.hpp"
@ -62,14 +63,12 @@
#include "entity/systems/astronomy.hpp"
#include "game/biome.hpp"
#include "utility/fundamental-types.hpp"
#include "utility/bit-math.hpp"
#include "genetics/genetics.hpp"
#include <iostream>
#include <bitset>
#include <ctime>
void play_state_enter(game_context* ctx)
{
debug::logger* logger = ctx->logger;
@ -142,6 +141,12 @@ void play_state_enter(game_context* ctx)
orbit.elements.w = longitude_periapsis - orbit.elements.raan;
orbit.elements.ta = math::radians(100.46457166) - longitude_periapsis;
entity::component::terrain terrain;
terrain.elevation = [](double, double) -> double
{
return 0.0;
};
entity::component::atmosphere atmosphere;
atmosphere.exosphere_altitude = 65e3;
atmosphere.index_of_refraction = 1.000293;
@ -158,9 +163,22 @@ void play_state_enter(game_context* ctx)
entity_registry.assign<entity::component::celestial_body>(earth_entity, body);
entity_registry.assign<entity::component::orbit>(earth_entity, orbit);
entity_registry.assign<entity::component::atmosphere>(earth_entity, atmosphere);
entity_registry.assign<entity::component::terrain>(earth_entity, terrain);
entity_registry.assign<entity::component::transform>(earth_entity, transform);
}
// Create observer
auto observer_eid = entity_registry.create();
{
entity::component::observer observer;
observer.reference_body_eid = earth_entity;
observer.altitude = 0.0;
observer.latitude = 0.0;
observer.longitude = 0.0;
entity_registry.assign<entity::component::observer>(observer_eid, observer);
}
scene::ambient_light* ambient = new scene::ambient_light();
ambient->set_color({1, 1, 1});
ambient->set_intensity(0.0f);
@ -206,85 +224,61 @@ void play_state_enter(game_context* ctx)
entity::archetype* color_checker_archetype = resource_manager->load<entity::archetype>("color-checker.ent");
// Create tools
/*
forceps_archetype->assign(entity_registry, ctx->forceps_entity);
lens_archetype->assign(entity_registry, ctx->lens_entity);
brush_archetype->assign(entity_registry, ctx->brush_entity);
marker_archetype->assign(entity_registry, ctx->marker_entity);
container_archetype->assign(entity_registry, ctx->container_entity);
twig_archetype->assign(entity_registry, ctx->twig_entity);
*/
// Create flashlight and light cone, set light cone parent to flashlight, and move both to underworld scene
/*
flashlight_archetype->assign(entity_registry, ctx->flashlight_entity);
auto flashlight_light_cone = flashlight_light_cone_archetype->create(entity_registry);
entity::command::parent(entity_registry, flashlight_light_cone, ctx->flashlight_entity);
entity::command::assign_render_layers(entity_registry, ctx->flashlight_entity, 2);
entity::command::assign_render_layers(entity_registry, flashlight_light_cone, 2);
*/
// Make lens tool's model instance unculled, so its shadow is always visible.
scene::model_instance* lens_model_instance = ctx->render_system->get_model_instance(ctx->lens_entity);
if (lens_model_instance)
{
//scene::model_instance* lens_model_instance = ctx->render_system->get_model_instance(ctx->lens_entity);
//if (lens_model_instance)
//{
//lens_model_instance->set_culling_mask(&ctx->no_cull);
}
//}
// Create lens light cone and set its parent to lens
auto lens_light_cone = lens_light_cone_archetype->create(entity_registry);
entity::command::bind_transform(entity_registry, lens_light_cone, ctx->lens_entity);
entity::command::parent(entity_registry, lens_light_cone, ctx->lens_entity);
//auto lens_light_cone = lens_light_cone_archetype->create(entity_registry);
//entity::command::bind_transform(entity_registry, lens_light_cone, ctx->lens_entity);
//entity::command::parent(entity_registry, lens_light_cone, ctx->lens_entity);
// Hide inactive tools
/*
entity::command::assign_render_layers(entity_registry, ctx->forceps_entity, 0);
entity::command::assign_render_layers(entity_registry, ctx->brush_entity, 0);
entity::command::assign_render_layers(entity_registry, ctx->lens_entity, 0);
entity::command::assign_render_layers(entity_registry, ctx->marker_entity, 0);
entity::command::assign_render_layers(entity_registry, ctx->container_entity, 0);
entity::command::assign_render_layers(entity_registry, ctx->twig_entity, 0);
*/
// Activate brush tool
//ctx->tool_system->set_active_tool(ctx->brush_entity);
// Create ant-hill
auto ant_hill_entity = ant_hill_archetype->create(entity_registry);
entity::command::place(entity_registry, ant_hill_entity, {0, -40});
// Creat nest
auto nest_entity = nest_archetype->create(entity_registry);
// Create terrain
int terrain_radius = 0;//6;
for (int x = -terrain_radius; x <= terrain_radius; ++x)
{
for (int z = -terrain_radius; z <= terrain_radius; ++z)
{
entity::component::terrain terrain;
terrain.subdivisions = TERRAIN_PATCH_RESOLUTION;
terrain.x = x;
terrain.z = z;
auto terrain_entity = entity_registry.create();
entity_registry.assign<entity::component::terrain>(terrain_entity, terrain);
}
}
// Create trees
for (int i = 0; i < 0; ++i)
{
auto redwood = redwood_archetype->create(entity_registry);
auto& transform = entity_registry.get<entity::component::transform>(redwood);
float zone = 500.0f;
entity::command::place(entity_registry, redwood, {math::random(-zone, zone), math::random(-zone, zone)});
}
// Create unit cube
auto cube = cube_archetype->create(entity_registry);
entity::command::place(entity_registry, cube, {10, 10});
entity::command::place(entity_registry, ant_hill_entity, earth_entity, 0.0, 0.0, 0.0);
// Create color checker
/*
auto color_checker = color_checker_archetype->create(entity_registry);
entity::command::place(entity_registry, color_checker, {-10, -10});
auto& cc_transform = entity_registry.get<entity::component::transform>(color_checker);
cc_transform.local.scale *= 10.0f;
cc_transform.local.rotation = math::angle_axis(math::radians(-90.0f), {1, 0, 0});
*/
// Setup camera focal point
entity::component::transform focal_point_transform;

+ 482
- 0
src/geom/hyperoctree.hpp View File

@ -0,0 +1,482 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GEOM_HYPEROCTREE_HPP
#define ANTKEEPER_GEOM_HYPEROCTREE_HPP
#include <cstdint>
#include <limits>
#include <type_traits>
#include <unordered_set>
#include <stack>
namespace geom {
/**
* Hashed linear hyperoctree.
*
* @see http://codervil.blogspot.com/2015/10/octree-node-identifiers.html
* @see https://geidav.wordpress.com/2014/08/18/advanced-octrees-2-node-representations/
*
* @tparam N Number of dimensions.
* @tparam D Max depth.
*
* Max depth can likely be determined by a generalized formula. 2D and 3D cases are given below:
*
* 2D:
* 8 bit ( 1 byte) = max depth 1 ( 4 loc bits, 1 depth bits, 1 divider bit) = 6 bits
* 16 bit ( 2 byte) = max depth 5 ( 12 loc bits, 3 depth bits, 1 divider bit) = 16 bits
* 32 bit ( 4 byte) = max depth 12 ( 26 loc bits, 4 depth bits, 1 divider bit) = 31 bits
* 64 bit ( 8 byte) = max depth 28 ( 58 loc bits, 5 depth bits, 1 divider bit) = 64 bits
* 128 bit (16 byte) = max depth 59 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 123 (248 loc bits, 7 depth bits, 1 divider bit) = 256 bits
*
* @see https://oeis.org/A173009
*
* 3D:
* 8 bit ( 1 byte) = max depth 1 ( 6 loc bits, 1 depth bits, 1 divider bit) = 8 bits
* 16 bit ( 2 byte) = max depth 3 ( 12 loc bits, 2 depth bits, 1 divider bit) = 15 bits
* 32 bit ( 4 byte) = max depth 8 ( 27 loc bits, 4 depth bits, 1 divider bit) = 32 bits
* 64 bit ( 8 byte) = max depth 18 ( 57 loc bits, 5 depth bits, 1 divider bit) = 63 bits
* 128 bit (16 byte) = max depth 39 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 81 (243 loc bits, 7 depth bits, 1 divider bit) = 251 bits
*
* @see https://oeis.org/A178420
*
* @tparam T Integer node type.
*/
template <std::size_t N, std::size_t D, class T>
class hyperoctree
{
private:
/// Compile-time calculation of the minimum bits required to represent `n` state changes.
static constexpr T ceil_log2(T n);
public:
/// Integral node type.
typedef T node_type;
/// Ensure the node type is integral
static_assert(std::is_integral<T>::value, "Node type must be integral.");
/// Maximum node depth.
static constexpr std::size_t max_depth = D;
/// Number of bits required to encode the depth of a node.
static constexpr T depth_bits = ceil_log2(max_depth + 1);
/// Number of bits required to encode the location of a node.
static constexpr T location_bits = (max_depth + 1) * N;
/// Number of bits in the node type.
static constexpr T node_bits = sizeof(node_type) * 8;
// Ensure the node type has enough bits
static_assert(depth_bits + location_bits + 1 <= node_bits, "Size of hyperoctree node type is insufficient to encode the maximum depth");
/// Number of children per node.
static constexpr T children_per_node = (N) ? (2 << (N - 1)) : 1;
/// Number of siblings per node.
static constexpr T siblings_per_node = children_per_node - 1;
/// Root node which is always guaranteed to exist.
static constexpr node_type root = 0;
/**
* Accesses nodes in their internal hashmap order.
*/
struct unordered_iterator
{
inline unordered_iterator(const unordered_iterator& other): set_iterator(other.set_iterator) {};
inline unordered_iterator& operator=(const unordered_iterator& other) { this->set_iterator = other.set_iterator; return *this; };
inline unordered_iterator& operator++() { ++(this->set_iterator); return *this; };
inline unordered_iterator& operator--() { --(this->set_iterator); return *this; };
inline bool operator==(const unordered_iterator& other) const { return this->set_iterator == other.set_iterator; };
inline bool operator!=(const unordered_iterator& other) const { return this->set_iterator != other.set_iterator; };
inline node_type operator*() const { return *this->set_iterator; };
private:
friend class hyperoctree;
inline explicit unordered_iterator(const typename std::unordered_set<node_type>::const_iterator& it): set_iterator(it) {};
typename std::unordered_set<node_type>::const_iterator set_iterator;
};
/**
* Accesses nodes in z-order.
*
* @TODO Can this be implemented without a stack?
*/
struct iterator
{
inline iterator(const iterator& other): hyperoctree(other.hyperoctree), stack(other.stack) {};
inline iterator& operator=(const iterator& other) { this->hyperoctree = other.hyperoctree; this->stack = other.stack; return *this; };
iterator& operator++();
inline bool operator==(const iterator& other) const { return **this == *other; };
inline bool operator!=(const iterator& other) const { return **this != *other; };
inline node_type operator*() const { return stack.top(); };
private:
friend class hyperoctree;
inline explicit iterator(const hyperoctree* hyperoctree, node_type node): hyperoctree(hyperoctree), stack({node}) {};
const hyperoctree* hyperoctree;
std::stack<node_type> stack;
};
/**
* Returns the depth of a node.
*
* @param node Node.
* @return Depth of the node.
*/
static T depth(node_type node);
/**
* Returns the Morton code location of a node.
*
* @param node Node.
* @return Morton code location of the node.
*/
static T location(node_type node);
/**
* Returns the node at the given depth and location.
*
* @param depth Node depth.
* @param location Node Morton code location.
*/
static node_type node(T depth, T location);
/**
* Returns the ancestor of a node at the specified depth.
*
* @param node Node whose ancestor will be located.
* @param depth Absolute depth of the ancestors.
* @return Ancestral node.
*/
static node_type ancestor(node_type node, T depth);
/**
* Returns the parent of a node.
*
* @param node Node.
* @return Parent node.
*/
static node_type parent(node_type node);
/**
* Returns the nth sibling of a node.
*
* @param node Node.
* @param n Offset to next sibling. (Automatically wraps to `[0, siblings_per_node]`)
* @return Next sibling node.
*/
static node_type sibling(node_type node, T n);
/**
* Returns the nth child of a node.
*
* @param node Parent node.
* @param n Offset to the nth sibling of the first child node. (Automatically wraps to 0..7)
* @return nth child node.
*/
static node_type child(node_type node, T n);
/**
* Calculates the first common ancestor of two nodes.
*
* @param a First node.
* @param b Second node.
* @return First common ancestor of the two nodes.
*/
static node_type common_ancestor(node_type a, node_type b);
/// Creates an hyperoctree with a single root node.
hyperoctree();
/// Returns a z-order iterator to the root node.
iterator begin() const;
/// Returns a z-order iterator indicating the end of a traversal.
iterator end() const;
/// Returns an iterator to the specified node.
iterator find(node_type node) const;
/// Returns an unordered iterator indicating the beginning of a traversal.
unordered_iterator unordered_begin() const;
/// Returns an unordered iterator indicating the end of a traversal.
unordered_iterator unordered_end() const;
/**
* Inserts a node and its siblings into the hyperoctree, creating its ancestors as necessary. Note: The root node is persistent and cannot be inserted.
*
* @param node Node to insert.
*/
void insert(node_type node);
/**
* Erases a node along with its siblings and descendants. Note: The root node is persistent and cannot be erased.
*
* @param node Node to erase.
*/
void erase(node_type node);
/**
* Erases all nodes except the root.
*/
void clear();
/// Returns `true` if the node is contained within the hyperoctree, and `false` otherwise.
bool contains(node_type node) const;
/// Returns `true` if the node has no children, and `false` otherwise.
bool is_leaf(node_type node) const;
/// Returns the number of nodes in the hyperoctree.
std::size_t size() const;
private:
/// Compile-time pow()
static constexpr T pow(T x, T exponent);
/// Count leading zeros
static T clz(T x);
std::unordered_set<node_type> nodes;
};
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::iterator& hyperoctree<N, D, T>::iterator::operator++()
{
// Get next node from top of stack
node_type node = stack.top();
stack.pop();
// If the node has children
if (!hyperoctree->is_leaf(node))
{
// Push first child onto the stack
for (T i = 0; i < children_per_node; ++i)
stack.push(child(node, siblings_per_node - i));
}
if (stack.empty())
stack.push(std::numeric_limits<T>::max());
return *this;
}
template <std::size_t N, std::size_t D, class T>
constexpr T hyperoctree<N, D, T>::ceil_log2(T n)
{
return (n <= 1) ? 0 : ceil_log2((n + 1) / 2) + 1;
}
template <std::size_t N, std::size_t D, class T>
inline T hyperoctree<N, D, T>::depth(node_type node)
{
// Extract depth using a bit mask
constexpr T mask = pow(2, depth_bits) - 1;
return node & mask;
}
template <std::size_t N, std::size_t D, class T>
inline T hyperoctree<N, D, T>::location(node_type node)
{
return node >> ((node_bits - 1) - depth(node) * N);
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::node(T depth, T location)
{
return (location << ((node_bits - 1) - depth * N)) | depth;
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::ancestor(node_type node, T depth)
{
const T mask = std::numeric_limits<T>::max() << ((node_bits - 1) - depth * N);
return (node & mask) | depth;
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::parent(node_type node)
{
return ancestor(node, depth(node) - 1);
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::sibling(node_type node, T n)
{
constexpr T mask = (1 << N) - 1;
T depth = hyperoctree::depth(node);
T location = node >> ((node_bits - 1) - depth * N);
return hyperoctree::node(depth, (location & (~mask)) | ((location + n) & mask));
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::child(node_type node, T n)
{
return sibling(node + 1, n);
}
template <std::size_t N, std::size_t D, class T>
inline typename hyperoctree<N, D, T>::node_type hyperoctree<N, D, T>::common_ancestor(node_type a, node_type b)
{
T bits = std::min<T>(depth(a), depth(b)) * N;
T marker = (T(1) << (node_bits - 1)) >> bits;
T depth = clz((a ^ b) | marker) / N;
return ancestor(a, depth);
}
template <std::size_t N, std::size_t D, class T>
inline hyperoctree<N, D, T>::hyperoctree():
nodes({0})
{}
template <std::size_t N, std::size_t D, class T>
void hyperoctree<N, D, T>::insert(node_type node)
{
if (contains(node))
return;
// Insert node
nodes.emplace(node);
// Insert siblings
for (T i = 1; i < children_per_node; ++i)
nodes.emplace(sibling(node, i));
// Insert parent as necessary
node_type parent = hyperoctree::parent(node);
if (!contains(parent))
insert(parent);
}
template <std::size_t N, std::size_t D, class T>
void hyperoctree<N, D, T>::erase(node_type node)
{
// Don't erase the root!
if (node == root)
return;
for (T i = 0; i < children_per_node; ++i)
{
// Erase node
nodes.erase(node);
// Erase descendants
if (!is_leaf(node))
{
for (T j = 0; j < children_per_node; ++j)
erase(child(node, j));
}
// Go to next sibling
if (i < siblings_per_node)
node = sibling(node, i);
}
}
template <std::size_t N, std::size_t D, class T>
void hyperoctree<N, D, T>::clear()
{
nodes = {0};
}
template <std::size_t N, std::size_t D, class T>
inline bool hyperoctree<N, D, T>::contains(node_type node) const
{
return (nodes.find(node) != nodes.end());
}
template <std::size_t N, std::size_t D, class T>
inline bool hyperoctree<N, D, T>::is_leaf(node_type node) const
{
return !contains(child(node, 0));
}
template <std::size_t N, std::size_t D, class T>
inline std::size_t hyperoctree<N, D, T>::size() const
{
return nodes.size();
}
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::iterator hyperoctree<N, D, T>::begin() const
{
return iterator(this, hyperoctree::root);
}
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::iterator hyperoctree<N, D, T>::end() const
{
return iterator(this, std::numeric_limits<T>::max());
}
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::iterator hyperoctree<N, D, T>::find(node_type node) const
{
return contains(node) ? iterator(node) : end();
}
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::unordered_iterator hyperoctree<N, D, T>::unordered_begin() const
{
return unordered_iterator(nodes.begin());
}
template <std::size_t N, std::size_t D, class T>
typename hyperoctree<N, D, T>::unordered_iterator hyperoctree<N, D, T>::unordered_end() const
{
return unordered_iterator(nodes.end());
}
template <std::size_t N, std::size_t D, class T>
constexpr T hyperoctree<N, D, T>::pow(T x, T exponent)
{
return (exponent == 0) ? 1 : x * pow(x, exponent - 1);
}
template <std::size_t N, std::size_t D, class T>
T hyperoctree<N, D, T>::clz(T x)
{
if (!x)
return sizeof(T) * 8;
#if defined(__GNU__)
return __builtin_clz(x);
#else
T n = 0;
while ((x & (T(1) << (8 * sizeof(x) - 1))) == 0)
{
x <<= 1;
++n;
}
return n;
#endif
}
} // namespace geom
#endif // ANTKEEPER_GEOM_HYPEROCTREE_HPP

+ 12
- 426
src/geom/octree.hpp View File

@ -20,440 +20,26 @@
#ifndef ANTKEEPER_GEOM_OCTREE_HPP
#define ANTKEEPER_GEOM_OCTREE_HPP
#include <cstdint>
#include <limits>
#include <type_traits>
#include <unordered_set>
#include <stack>
#include "geom/hyperoctree.hpp"
namespace geom {
/**
* A general purpose (hashed) linear octree. Nodes are integer identifiers and no other data is stored in the octree.
*
* @tparam T Integer node type. Must be 16-bit, 32-bit, or 64-bit.
*
* @see http://codervil.blogspot.com/2015/10/octree-node-identifiers.html
* @see https://geidav.wordpress.com/2014/08/18/advanced-octrees-2-node-representations/
*/
template <class T>
class octree
{
private:
/// Compile-time calculation of the minimum bits required to represent `n` state changes.
static constexpr T ceil_log2(T n);
public:
static_assert(std::is_integral<T>::value, "Node type must be integral.");
static_assert(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8, "Node type must be 16-bit, 32-bit, or 64-bit.");
/// Maximum octree depth
static constexpr T max_depth = (sizeof(T) == 2) ? 3 : (sizeof(T) == 4) ? 8 : 18;
/// Number of bits in the node type
static constexpr T node_bits = sizeof(T) * 8;
/// Number of bits used to encode the depth of a node.
static constexpr T depth_bits = ceil_log2(max_depth + 1);
/// Number of bits used to encode the Morton code location a node.
static constexpr T location_bits = (max_depth + 1) * 3;
/// Integer node type.
typedef T node_type;
/// Root node which is always guaranteed to exist.
static constexpr node_type root = 0;
/**
* Accesses nodes in their internal hashmap order.
*/
struct unordered_iterator
{
inline unordered_iterator(const unordered_iterator& other): set_iterator(other.set_iterator) {};
inline unordered_iterator& operator=(const unordered_iterator& other) { this->set_iterator = other.set_iterator; return *this; };
inline unordered_iterator& operator++() { ++(this->set_iterator); return *this; };
inline unordered_iterator& operator--() { --(this->set_iterator); return *this; };
inline bool operator==(const unordered_iterator& other) const { return this->set_iterator == other.set_iterator; };
inline bool operator!=(const unordered_iterator& other) const { return this->set_iterator != other.set_iterator; };
inline const node_type& operator*() const { return *this->set_iterator; };
private:
friend class octree;
inline explicit unordered_iterator(const typename std::unordered_set<node_type>::const_iterator& it): set_iterator(it) {};
typename std::unordered_set<node_type>::const_iterator set_iterator;
};
/**
* Accesses nodes in z-order. TODO: I think this can be done without a stack.
*/
struct iterator
{
inline iterator(const iterator& other): octree(other.octree), stack(other.stack) {};
inline iterator& operator=(const iterator& other) { this->octree = other.octree; this->stack = other.stack; return *this; };
iterator& operator++();
inline bool operator==(const iterator& other) const { return **this == *other; };
inline bool operator!=(const iterator& other) const { return **this != *other; };
inline const node_type& operator*() const { return stack.top(); };
private:
friend class octree;
inline explicit iterator(const octree* octree, node_type node): octree(octree), stack({node}) {};
const octree* octree;
std::stack<node_type> stack;
};