From ce645e3108bf0668f3dc9ace38e50c6f8eb58f04 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 5 Jun 2021 17:06:05 +0800 Subject: [PATCH] Generalize octree class into N-dimensional hyperoctree, begin revision of terrain system --- src/entity/commands.cpp | 39 +- src/entity/commands.hpp | 2 +- src/entity/components/observer.hpp | 41 ++ src/entity/components/terrain.hpp | 8 +- src/entity/systems/terrain.cpp | 395 +++++++----------- src/entity/systems/terrain.hpp | 34 +- src/entity/systems/vegetation.cpp | 92 +---- src/game/bootloader.cpp | 4 +- src/game/states/play-state.cpp | 78 ++-- src/geom/hyperoctree.hpp | 482 ++++++++++++++++++++++ src/geom/octree.hpp | 438 +------------------- src/geom/quadtree.hpp | 45 ++ src/resources/entity-archetype-loader.cpp | 17 - 13 files changed, 819 insertions(+), 856 deletions(-) create mode 100644 src/entity/components/observer.hpp create mode 100644 src/geom/hyperoctree.hpp create mode 100644 src/geom/quadtree.hpp diff --git a/src/entity/commands.cpp b/src/entity/commands.cpp index 6eb10aa..a853976 100644 --- a/src/entity/commands.cpp +++ b/src/entity/commands.cpp @@ -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 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(entity_id, component); + if (registry.has(entity_id)) + { + double x = 0.0; + double y = altitude; + double z = 0.0; + + if (registry.has(celestial_body_id)) + { + const component::celestial_body& celestial_body = registry.get(celestial_body_id); + + x = longitude * math::two_pi * celestial_body.radius; + z = -latitude * math::two_pi * celestial_body.radius; + + if (registry.has(celestial_body_id)) + { + const component::terrain& terrain = registry.get(celestial_body_id); + + if (terrain.elevation != nullptr) + { + y += terrain.elevation(latitude, longitude); + } + } + } + + component::transform& transform = registry.get(entity_id); + transform.local.translation = math::type_cast(double3{x, y, z}); + transform.warp = true; + } } void assign_render_layers(entity::registry& registry, entity::id entity_id, unsigned int layers) diff --git a/src/entity/commands.hpp b/src/entity/commands.hpp index ac33c5c..4d1429b 100644 --- a/src/entity/commands.hpp +++ b/src/entity/commands.hpp @@ -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& 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 get_local_transform(entity::registry& registry, entity::id entity_id); diff --git a/src/entity/components/observer.hpp b/src/entity/components/observer.hpp new file mode 100644 index 0000000..9c860e6 --- /dev/null +++ b/src/entity/components/observer.hpp @@ -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 . + */ + +#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 diff --git a/src/entity/components/terrain.hpp b/src/entity/components/terrain.hpp index be2d00c..4351b93 100644 --- a/src/entity/components/terrain.hpp +++ b/src/entity/components/terrain.hpp @@ -20,18 +20,18 @@ #ifndef ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP #define ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP +#include + 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 elevation; }; } // namespace component } // namespace entity #endif // ANTKEEPER_ENTITY_COMPONENT_TERRAIN_HPP - diff --git a/src/entity/systems/terrain.cpp b/src/entity/systems/terrain.cpp index e547590..00d1434 100644 --- a/src/entity/systems/terrain.cpp +++ b/src/entity/systems/terrain.cpp @@ -17,285 +17,192 @@ * along with Antkeeper source code. If not, see . */ -#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 +#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().connect<&terrain::on_terrain_construct>(this); - registry.on_destroy().connect<&terrain::on_terrain_destroy>(this); - heightmap = resource_manager->load("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().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& 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& 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(morton_x) * length + length * 0.5f) * 2.0f - 1.0f; + center.y = (static_cast(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("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(0, std::min(heightmap->get_width() - 1, pixel_x)); - pixel_y = std::max(0, std::min(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(heightmap->get_pixels()) + pixel_index; - - float elevation = (static_cast(*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& faces = terrain_mesh->get_faces(); - const std::vector& vertices = terrain_mesh->get_vertices(); - - geom::aabb 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().connect<&terrain::on_terrain_construct>(this); + registry.on_destroy().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().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(observer.reference_body_eid) || + !registry.has(observer.reference_body_eid)) + return; + + const auto& celestial_body = registry.get(observer.reference_body_eid); + const auto& terrain = registry.get(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::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(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(entity_id, model); - - // Assign the entity a transform component - component::transform transform; - transform.local = math::identity_transform; - transform.local.translation = float3{(float)component.x * patch_size, 0.0f, (float)component.z * patch_size}; - transform.warp = true; - registry.assign_or_replace(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 diff --git a/src/entity/systems/terrain.hpp b/src/entity/systems/terrain.hpp index 5088003..ae2cc43 100644 --- a/src/entity/systems/terrain.hpp +++ b/src/entity/systems/terrain.hpp @@ -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 - diff --git a/src/entity/systems/vegetation.cpp b/src/entity/systems/vegetation.cpp index a563ab5..cacb96e 100644 --- a/src/entity/systems/vegetation.cpp +++ b/src/entity/systems/vegetation.cpp @@ -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(component.x) * terrain_patch_size - terrain_patch_size * 0.5f; - float terrain_patch_min_z = static_cast(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; - transform.local.translation = float3{vegetation_patch_x, 0.0f, vegetation_patch_z}; - transform.warp = true; - registry.assign_or_replace(vegetation_patch_entity, transform); - - // Assign a model component - component::model model; - model.model = vegetation_model; - model.instance_count = 500; - registry.assign_or_replace(vegetation_patch_entity, model); - */ - - // Find patch translation - float vegetation_patch_x = terrain_patch_min_x + vegetation_patch_size * static_cast(column) + vegetation_patch_size * 0.5f; - float vegetation_patch_z = terrain_patch_min_z + vegetation_patch_size * static_cast(row) + vegetation_patch_size * 0.5f; - float3 translation = {vegetation_patch_x, 0.0f, vegetation_patch_z}; - - // Generate culling mask - geom::aabb* culling_mask = new geom::aabb(vegetation_model->get_bounds()); - culling_mask->min_point.x = std::min(culling_mask->min_point.x, translation.x - vegetation_patch_size * 0.5f); - culling_mask->min_point.z = std::min(culling_mask->min_point.z, translation.z - vegetation_patch_size * 0.5f); - culling_mask->max_point.x = std::max(culling_mask->max_point.x, translation.x + vegetation_patch_size * 0.5f); - culling_mask->max_point.z = std::max(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*>(lod1_material->get_property("instance_multiplier"))->set_value(2); - material* lod2_material = new material(*lod0_material); - static_cast*>(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) {} diff --git a/src/game/bootloader.cpp b/src/game/bootloader.cpp index 3f83f9d..d6bde3a 100644 --- a/src/game/bootloader.cpp +++ b/src/game/bootloader.cpp @@ -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); diff --git a/src/game/states/play-state.cpp b/src/game/states/play-state.cpp index 9af44da..15bd9f9 100644 --- a/src/game/states/play-state.cpp +++ b/src/game/states/play-state.cpp @@ -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 #include #include - 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(earth_entity, body); entity_registry.assign(earth_entity, orbit); entity_registry.assign(earth_entity, atmosphere); + entity_registry.assign(earth_entity, terrain); entity_registry.assign(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(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("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(terrain_entity, terrain); - } - } - - // Create trees - for (int i = 0; i < 0; ++i) - { - auto redwood = redwood_archetype->create(entity_registry); - - auto& transform = entity_registry.get(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(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; diff --git a/src/geom/hyperoctree.hpp b/src/geom/hyperoctree.hpp new file mode 100644 index 0000000..6ffb4f9 --- /dev/null +++ b/src/geom/hyperoctree.hpp @@ -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 . + */ + +#ifndef ANTKEEPER_GEOM_HYPEROCTREE_HPP +#define ANTKEEPER_GEOM_HYPEROCTREE_HPP + +#include +#include +#include +#include +#include + +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 +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::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::const_iterator& it): set_iterator(it) {}; + typename std::unordered_set::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 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 nodes; +}; + +template +typename hyperoctree::iterator& hyperoctree::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::max()); + + return *this; +} + +template +constexpr T hyperoctree::ceil_log2(T n) +{ + return (n <= 1) ? 0 : ceil_log2((n + 1) / 2) + 1; +} + +template +inline T hyperoctree::depth(node_type node) +{ + // Extract depth using a bit mask + constexpr T mask = pow(2, depth_bits) - 1; + return node & mask; +} + +template +inline T hyperoctree::location(node_type node) +{ + return node >> ((node_bits - 1) - depth(node) * N); +} + +template +inline typename hyperoctree::node_type hyperoctree::node(T depth, T location) +{ + return (location << ((node_bits - 1) - depth * N)) | depth; +} + +template +inline typename hyperoctree::node_type hyperoctree::ancestor(node_type node, T depth) +{ + const T mask = std::numeric_limits::max() << ((node_bits - 1) - depth * N); + return (node & mask) | depth; +} + +template +inline typename hyperoctree::node_type hyperoctree::parent(node_type node) +{ + return ancestor(node, depth(node) - 1); +} + +template +inline typename hyperoctree::node_type hyperoctree::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 +inline typename hyperoctree::node_type hyperoctree::child(node_type node, T n) +{ + return sibling(node + 1, n); +} + +template +inline typename hyperoctree::node_type hyperoctree::common_ancestor(node_type a, node_type b) +{ + T bits = std::min(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 +inline hyperoctree::hyperoctree(): + nodes({0}) +{} + +template +void hyperoctree::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 +void hyperoctree::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 +void hyperoctree::clear() +{ + nodes = {0}; +} + +template +inline bool hyperoctree::contains(node_type node) const +{ + return (nodes.find(node) != nodes.end()); +} + +template +inline bool hyperoctree::is_leaf(node_type node) const +{ + return !contains(child(node, 0)); +} + +template +inline std::size_t hyperoctree::size() const +{ + return nodes.size(); +} + +template +typename hyperoctree::iterator hyperoctree::begin() const +{ + return iterator(this, hyperoctree::root); +} + +template +typename hyperoctree::iterator hyperoctree::end() const +{ + return iterator(this, std::numeric_limits::max()); +} + +template +typename hyperoctree::iterator hyperoctree::find(node_type node) const +{ + return contains(node) ? iterator(node) : end(); +} + +template +typename hyperoctree::unordered_iterator hyperoctree::unordered_begin() const +{ + return unordered_iterator(nodes.begin()); +} + +template +typename hyperoctree::unordered_iterator hyperoctree::unordered_end() const +{ + return unordered_iterator(nodes.end()); +} + +template +constexpr T hyperoctree::pow(T x, T exponent) +{ + return (exponent == 0) ? 1 : x * pow(x, exponent - 1); +} + +template +T hyperoctree::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 diff --git a/src/geom/octree.hpp b/src/geom/octree.hpp index 90ffd1e..b9605e1 100644 --- a/src/geom/octree.hpp +++ b/src/geom/octree.hpp @@ -20,440 +20,26 @@ #ifndef ANTKEEPER_GEOM_OCTREE_HPP #define ANTKEEPER_GEOM_OCTREE_HPP -#include -#include -#include -#include -#include +#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 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::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::const_iterator& it): set_iterator(it) {}; - typename std::unordered_set::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 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..7) - * @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 octree with a single root node. - octree(); - - /// 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 octree, 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 exists in the octree, and `false` otherwise. - bool exists(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 octree. - 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 nodes; -}; +/// An octree, or 3-dimensional hyperoctree. +template +using octree = hyperoctree<3, D, T>; -/** - * Octree with a 16-bit node type and a maximum depth of `3`. - */ -typedef octree octree16; - -/** - * Octree with a 32-bit node type and a maximum depth of `8`. - */ -typedef octree octree32; - -/** - * Octree with a 64-bit node type and a maximum depth of `18`. - */ -typedef octree octree64; - -template -typename octree::iterator& octree::iterator::operator++() -{ - // Get next node from top of stack - node_type node = stack.top(); - stack.pop(); - - // If the node has children - if (!octree->is_leaf(node)) - { - // Push first child onto the stack - for (T i = 0; i < 8; ++i) - stack.push(child(node, 7 - i)); - } - - if (stack.empty()) - stack.push(std::numeric_limits::max()); - - return *this; -} - -template -constexpr T octree::ceil_log2(T n) -{ - return (n <= 1) ? 0 : ceil_log2((n + 1) / 2) + 1; -} - -template -inline T octree::depth(node_type node) -{ - // Extract depth using a bit mask - constexpr T mask = pow(2, depth_bits) - 1; - return node & mask; -} - -template -inline T octree::location(node_type node) -{ - return node >> ((node_bits - 1) - depth(node) * 3); -} - -template -inline typename octree::node_type octree::node(T depth, T location) -{ - return (location << ((node_bits - 1) - depth * 3)) | depth; -} - -template -inline typename octree::node_type octree::ancestor(node_type node, T depth) -{ - const T mask = std::numeric_limits::max() << ((node_bits - 1) - depth * 3); - return (node & mask) | depth; -} - -template -inline typename octree::node_type octree::parent(node_type node) -{ - return ancestor(node, depth(node) - 1); -} - -template -inline typename octree::node_type octree::sibling(node_type node, T n) -{ - T depth = octree::depth(node); - T location = node >> ((node_bits - 1) - depth * 3); - return octree::node(depth, (location & (~0b111)) | ((location + n) & 0b111)); -} - -template -inline typename octree::node_type octree::child(node_type node, T n) -{ - return sibling(node + 1, n); -} - -template -inline typename octree::node_type octree::common_ancestor(node_type a, node_type b) -{ - T bits = std::min(depth(a), depth(b)) * 3; - T marker = (T(1) << (node_bits - 1)) >> bits; - T depth = clz((a ^ b) | marker) / 3; - return ancestor(a, depth); -} +/// Octree with an 8-bit node type (2 depth levels). +typedef octree<1, std::uint8_t> octree8; -template -inline octree::octree(): - nodes({0}) -{} +/// Octree with a 16-bit node type (4 depth levels). +typedef octree<3, std::uint16_t> octree16; -template -void octree::insert(node_type node) -{ - if (exists(node)) - return; - - // Insert node - nodes.emplace(node); +/// Octree with a 32-bit node type (9 depth levels). +typedef octree<8, std::uint32_t> octree32; - // Insert siblings - for (T i = 1; i < 8; ++i) - nodes.emplace(sibling(node, i)); - - // Insert parent as necessary - node_type parent = octree::parent(node); - if (!exists(parent)) - insert(parent); -} - -template -void octree::erase(node_type node) -{ - // Don't erase the root! - if (node == root) - return; - - for (T i = 0; i < 8; ++i) - { - // Erase node - nodes.erase(node); - - // Erase descendants - if (!is_leaf(node)) - { - for (T j = 0; j < 8; ++j) - erase(child(node, j)); - } - - // Go to next sibling - if (i < 7) - node = sibling(node, i); - } -} - -template -void octree::clear() -{ - nodes = {0}; -} - -template -inline bool octree::exists(node_type node) const -{ - return (nodes.find(node) != nodes.end()); -} - -template -inline bool octree::is_leaf(node_type node) const -{ - return !exists(child(node, 0)); -} - -template -inline std::size_t octree::size() const -{ - return nodes.size(); -} - -template -typename octree::iterator octree::begin() const -{ - return iterator(this, octree::root); -} - -template -typename octree::iterator octree::end() const -{ - return iterator(this, std::numeric_limits::max()); -} - -template -typename octree::iterator octree::find(node_type node) const -{ - return exists(node) ? iterator(node) : end(); -} - -template -typename octree::unordered_iterator octree::unordered_begin() const -{ - return unordered_iterator(nodes.begin()); -} - -template -typename octree::unordered_iterator octree::unordered_end() const -{ - return unordered_iterator(nodes.end()); -} - -template -constexpr T octree::pow(T x, T exponent) -{ - return (exponent == 0) ? 1 : x * pow(x, exponent - 1); -} - -template -T octree::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 -} +/// Octree with a 64-bit node type (19 depth levels). +typedef octree<18, std::uint64_t> octree64; } // namespace geom #endif // ANTKEEPER_GEOM_OCTREE_HPP - diff --git a/src/geom/quadtree.hpp b/src/geom/quadtree.hpp new file mode 100644 index 0000000..62c5f7c --- /dev/null +++ b/src/geom/quadtree.hpp @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +#ifndef ANTKEEPER_GEOM_QUADTREE_HPP +#define ANTKEEPER_GEOM_QUADTREE_HPP + +#include "geom/hyperoctree.hpp" + +namespace geom { + +/// A quadtree, or 2-dimensional hyperoctree. +template +using quadtree = hyperoctree<2, D, T>; + +/// Quadtree with an 8-bit node type (2 depth levels). +typedef quadtree<1, std::uint8_t> quadtree8; + +/// Quadtree with a 16-bit node type (6 depth levels). +typedef quadtree<5, std::uint16_t> quadtree16; + +/// Quadtree with a 32-bit node type (13 depth levels). +typedef quadtree<12, std::uint32_t> quadtree32; + +/// Quadtree with a 64-bit node type (29 depth levels). +typedef quadtree<28, std::uint64_t> quadtree64; + +} // namespace geom + +#endif // ANTKEEPER_GEOM_QUADTREE_HPP diff --git a/src/resources/entity-archetype-loader.cpp b/src/resources/entity-archetype-loader.cpp index eb1ef0c..09904f3 100644 --- a/src/resources/entity-archetype-loader.cpp +++ b/src/resources/entity-archetype-loader.cpp @@ -136,22 +136,6 @@ static bool load_component_brush(entity::archetype& archetype, const std::vector return true; } -static bool load_component_terrain(entity::archetype& archetype, const std::vector& parameters) -{ - if (parameters.size() != 4) - { - throw std::runtime_error("load_component_terrain(): Invalid parameter count."); - } - - entity::component::terrain component; - component.subdivisions = std::stoi(parameters[1]); - component.x = std::stoi(parameters[2]); - component.z = std::stoi(parameters[3]); - archetype.set(component); - - return true; -} - static bool load_component_tool(entity::archetype& archetype, const std::vector& parameters) { if (parameters.size() != 5) @@ -208,7 +192,6 @@ static bool load_component(entity::archetype& archetype, resource_manager& resou if (parameters[0] == "collision") return load_component_collision(archetype, resource_manager, parameters); if (parameters[0] == "model") return load_component_model(archetype, resource_manager, parameters); if (parameters[0] == "nest") return load_component_nest(archetype, parameters); - if (parameters[0] == "terrain") return load_component_terrain(archetype, parameters); if (parameters[0] == "tool") return load_component_tool(archetype, parameters); if (parameters[0] == "transform") return load_component_transform(archetype, parameters); if (parameters[0] == "marker") return load_component_marker(archetype, parameters);