/* * Copyright (C) 2021 Christopher J. Howard * * This file is part of Antkeeper source code. * * Antkeeper source code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper source code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper source code. If not, see . */ #include "entity/systems/terrain.hpp" #include "utility/fundamental-types.hpp" #include "entity/components/observer.hpp" #include "entity/components/terrain.hpp" #include "entity/components/celestial-body.hpp" #include "geom/quadtree.hpp" /** * A cube with six quadtrees as faces. */ struct quadtree_cube { 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 quadtree_cube::clear() { for (std::uint8_t i = 0; i < 6; ++i) faces[i].clear(); } void quadtree_cube::refine(const std::function& threshold) { for (std::uint8_t i = 0; i < 6; ++i) { 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)); } } } /* 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; } ); */ /** * 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 */ /** * 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. */ namespace entity { namespace system { 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; set_patch_subdivisions(0); registry.on_construct().connect<&terrain::on_terrain_construct>(this); registry.on_destroy().connect<&terrain::on_terrain_destroy>(this); } terrain::~terrain() {} void terrain::update(double t, double dt) { // Subdivide or collapse quad sphere registry.view().each( [&](entity::id observer_eid, const auto& observer) { // Skip observers with null reference body if (observer.reference_body_eid == entt::null) return; // 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 }); } void terrain::set_patch_subdivisions(std::uint8_t n) { patch_subdivisions = n; // 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) { // Build quad sphere } void terrain::on_terrain_destroy(entity::registry& registry, entity::id entity_id) { // Destroy quad sphere } void terrain::generate_patch() { } } // namespace system } // namespace entity