|
|
@ -18,119 +18,21 @@ |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "entity/systems/terrain.hpp"
|
|
|
|
#include "utility/fundamental-types.hpp"
|
|
|
|
#include "entity/components/celestial-body.hpp"
|
|
|
|
#include "entity/components/observer.hpp"
|
|
|
|
#include "entity/components/terrain.hpp"
|
|
|
|
#include "entity/components/celestial-body.hpp"
|
|
|
|
#include "geom/meshes/grid.hpp"
|
|
|
|
#include "geom/mesh-functions.hpp"
|
|
|
|
#include "geom/morton.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<bool(std::uint8_t, node_type)>& 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<bool(std::uint8_t, node_type)>& 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<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; |
|
|
|
} |
|
|
|
); |
|
|
|
*/ |
|
|
|
|
|
|
|
/**
|
|
|
|
* 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. |
|
|
|
*/ |
|
|
|
|
|
|
|
#include "geom/spherical.hpp"
|
|
|
|
#include "gl/vertex-attribute-type.hpp"
|
|
|
|
#include "math/constants.hpp"
|
|
|
|
#include "math/quaternion-operators.hpp"
|
|
|
|
#include "renderer/vertex-attributes.hpp"
|
|
|
|
#include "utility/fundamental-types.hpp"
|
|
|
|
#include <functional>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
namespace entity { |
|
|
|
namespace system { |
|
|
@ -138,14 +40,27 @@ namespace system { |
|
|
|
terrain::terrain(entity::registry& registry): |
|
|
|
updatable(registry), |
|
|
|
patch_subdivisions(0), |
|
|
|
patch_base_mesh(nullptr), |
|
|
|
patch_vertex_size(0), |
|
|
|
patch_vertex_count(0), |
|
|
|
patch_vertex_data(nullptr) |
|
|
|
patch_vertex_data(nullptr), |
|
|
|
patch_scene_collection(nullptr), |
|
|
|
max_error(0.0) |
|
|
|
{ |
|
|
|
// position + uv + normal + tangent + barycentric
|
|
|
|
patch_vertex_size = 3 + 2 + 3 + 4 + 3; |
|
|
|
// Build set of quaternions to rotate quadtree cube coordinates into BCBF space according to face index
|
|
|
|
face_rotations[0] = math::quaternion<double>::identity(); // +x
|
|
|
|
face_rotations[1] = math::quaternion<double>::rotate_z(math::pi<double>); // -x
|
|
|
|
face_rotations[2] = math::quaternion<double>::rotate_z( math::half_pi<double>); // +y
|
|
|
|
face_rotations[3] = math::quaternion<double>::rotate_z(-math::half_pi<double>); // -y
|
|
|
|
face_rotations[4] = math::quaternion<double>::rotate_y(-math::half_pi<double>); // +z
|
|
|
|
face_rotations[5] = math::quaternion<double>::rotate_y( math::half_pi<double>); // -z
|
|
|
|
|
|
|
|
// Specify vertex size and stride
|
|
|
|
// (position + uv + normal + tangent + barycentric + target)
|
|
|
|
patch_vertex_size = 3 + 2 + 3 + 4 + 3 + 3; |
|
|
|
patch_vertex_stride = patch_vertex_size * sizeof(float); |
|
|
|
|
|
|
|
// Init patch subdivisions to zero
|
|
|
|
set_patch_subdivisions(0); |
|
|
|
|
|
|
|
registry.on_construct<component::terrain>().connect<&terrain::on_terrain_construct>(this); |
|
|
@ -157,23 +72,171 @@ terrain::~terrain() |
|
|
|
|
|
|
|
void terrain::update(double t, double dt) |
|
|
|
{ |
|
|
|
// Subdivide or collapse quad sphere
|
|
|
|
registry.view<component::observer>().each( |
|
|
|
[&](entity::id observer_eid, const auto& observer) |
|
|
|
// Refine the level of detail of each terrain quadsphere
|
|
|
|
registry.view<component::terrain, component::celestial_body>().each( |
|
|
|
[&](entity::id terrain_eid, const auto& terrain_component, const auto& terrain_body) |
|
|
|
{ |
|
|
|
// 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<component::celestial_body>(observer.reference_body_eid) || |
|
|
|
!registry.has<component::terrain>(observer.reference_body_eid)) |
|
|
|
return; |
|
|
|
// Retrieve terrain quadsphere
|
|
|
|
terrain_quadsphere* quadsphere = terrain_quadspheres[terrain_eid]; |
|
|
|
|
|
|
|
const auto& celestial_body = registry.get<component::celestial_body>(observer.reference_body_eid); |
|
|
|
const auto& terrain = registry.get<component::terrain>(observer.reference_body_eid); |
|
|
|
// For each observer
|
|
|
|
this->registry.view<component::observer>().each( |
|
|
|
[&](entity::id observer_eid, const auto& observer) |
|
|
|
{ |
|
|
|
// Skip observers with invalid reference body
|
|
|
|
if (!this->registry.has<component::celestial_body>(observer.reference_body_eid) || |
|
|
|
!this->registry.has<component::terrain>(observer.reference_body_eid)) |
|
|
|
return; |
|
|
|
|
|
|
|
// Get celestial body and terrain component of observer reference body
|
|
|
|
const auto& reference_celestial_body = this->registry.get<component::celestial_body>(observer.reference_body_eid); |
|
|
|
const auto& reference_terrain = this->registry.get<component::terrain>(observer.reference_body_eid); |
|
|
|
|
|
|
|
// Calculate reference BCBF-space position of observer.
|
|
|
|
//double3 observer_spherical = {reference_celestial_body.radius + observer.elevation, observer.latitude, observer.longitude};
|
|
|
|
double3 observer_spherical = {observer.elevation, observer.latitude, observer.longitude}; |
|
|
|
double3 observer_cartesian = geom::spherical::to_cartesian(observer_spherical); |
|
|
|
|
|
|
|
observer_cartesian = math::type_cast<double>(observer.camera->get_translation()); |
|
|
|
|
|
|
|
/// @TODO Transform observer position into BCBF space of terrain body (use orbit component?)
|
|
|
|
|
|
|
|
// For each terrain quadsphere face
|
|
|
|
for (int i = 0; i < 6; ++i) |
|
|
|
{ |
|
|
|
terrain_quadsphere_face& quadsphere_face = quadsphere->faces[i]; |
|
|
|
|
|
|
|
// Get the quadsphere faces's quadtree
|
|
|
|
quadtree_type& quadtree = quadsphere_face.quadtree; |
|
|
|
|
|
|
|
// Clear quadsphere face quadtree
|
|
|
|
quadtree.clear(); |
|
|
|
|
|
|
|
// For each node in the face quadtree
|
|
|
|
for (auto node_it = quadtree.begin(); node_it != quadtree.end(); ++node_it) |
|
|
|
{ |
|
|
|
quadtree_node_type node = *node_it; |
|
|
|
|
|
|
|
// Skip non leaf nodes
|
|
|
|
if (!quadtree.is_leaf(node)) |
|
|
|
continue; |
|
|
|
|
|
|
|
// Extract node depth
|
|
|
|
quadtree_type::node_type node_depth = quadtree_type::depth(node); |
|
|
|
|
|
|
|
// Skip nodes at max depth level
|
|
|
|
if (node_depth >= terrain_component.max_lod) |
|
|
|
continue; |
|
|
|
|
|
|
|
// Extract node location from Morton location code
|
|
|
|
quadtree_type::node_type node_location = quadtree_type::location(node); |
|
|
|
quadtree_type::node_type node_location_x; |
|
|
|
quadtree_type::node_type node_location_y; |
|
|
|
geom::morton::decode(node_location, node_location_x, node_location_y); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const double nodes_per_axis = std::exp2(node_depth); |
|
|
|
const double node_width = 2.0 / nodes_per_axis; |
|
|
|
|
|
|
|
// Determine node center on front face of unit BCBF cube.
|
|
|
|
double3 center; |
|
|
|
center.y = -(nodes_per_axis * 0.5 * node_width) + node_width * 0.5; |
|
|
|
center.z = center.y; |
|
|
|
center.y += static_cast<double>(node_location_x) * node_width; |
|
|
|
center.z += static_cast<double>(node_location_y) * node_width; |
|
|
|
center.x = 1.0; |
|
|
|
|
|
|
|
// Rotate node center according to cube face
|
|
|
|
/// @TODO Rather than rotating every center, "unrotate" observer position 6 times
|
|
|
|
center = face_rotations[i] * center; |
|
|
|
|
|
|
|
// Project node center onto unit sphere
|
|
|
|
double xx = center.x * center.x; |
|
|
|
double yy = center.y * center.y; |
|
|
|
double zz = center.z * center.z; |
|
|
|
center.x *= std::sqrt(std::max(0.0, 1.0 - yy * 0.5 - zz * 0.5 + yy * zz / 3.0)); |
|
|
|
center.y *= std::sqrt(std::max(0.0, 1.0 - xx * 0.5 - zz * 0.5 + xx * zz / 3.0)); |
|
|
|
center.z *= std::sqrt(std::max(0.0, 1.0 - xx * 0.5 - yy * 0.5 + xx * yy / 3.0)); |
|
|
|
|
|
|
|
// Scale node center by body radius
|
|
|
|
center *= terrain_body.radius; |
|
|
|
center.y -= terrain_body.radius; |
|
|
|
//center *= 50.0;
|
|
|
|
|
|
|
|
const double horizontal_fov = observer.camera->get_fov(); |
|
|
|
const double horizontal_resolution = 1920.0; |
|
|
|
const double distance = math::length(center - observer_cartesian); |
|
|
|
const double geometric_error = static_cast<double>(524288.0 / std::exp2(node_depth)); |
|
|
|
const double screen_error = screen_space_error(horizontal_fov, horizontal_resolution, distance, geometric_error); |
|
|
|
|
|
|
|
if (screen_error > max_error) |
|
|
|
{ |
|
|
|
//std::cout << screen_error << std::endl;
|
|
|
|
quadtree.insert(quadtree_type::child(node, 0)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// Handle meshes and models for each terrain patch
|
|
|
|
registry.view<component::terrain, component::celestial_body>().each( |
|
|
|
[&](entity::id terrain_eid, const auto& terrain_component, const auto& terrain_body) |
|
|
|
{ |
|
|
|
// Retrieve terrain quadsphere
|
|
|
|
terrain_quadsphere* quadsphere = terrain_quadspheres[terrain_eid]; |
|
|
|
|
|
|
|
// Haversine distance to all 6 faces, then recursively to children
|
|
|
|
// For each terrain quadsphere face
|
|
|
|
for (int i = 0; i < 6; ++i) |
|
|
|
{ |
|
|
|
terrain_quadsphere_face& quadsphere_face = quadsphere->faces[i]; |
|
|
|
const quadtree_type& quadtree = quadsphere_face.quadtree; |
|
|
|
|
|
|
|
// For each quadtree node
|
|
|
|
for (auto node_it = quadtree.unordered_begin(); node_it != quadtree.unordered_end(); ++node_it) |
|
|
|
{ |
|
|
|
quadtree_node_type node = *node_it; |
|
|
|
|
|
|
|
// Look up cached patch for this node
|
|
|
|
auto patch_it = quadsphere_face.patches.find(node); |
|
|
|
|
|
|
|
// If there is no cached patch instance for this node
|
|
|
|
if (patch_it == quadsphere_face.patches.end()) |
|
|
|
{ |
|
|
|
// Construct a terrain patch
|
|
|
|
terrain_patch* patch = new terrain_patch(); |
|
|
|
|
|
|
|
// Generate a patch mesh
|
|
|
|
patch->mesh = generate_patch_mesh(i, *node_it, terrain_body.radius, terrain_component.elevation); |
|
|
|
//patch->mesh = generate_patch_mesh(i, *node_it, 50.0, terrain_component.elevation);
|
|
|
|
|
|
|
|
// Generate a patch model
|
|
|
|
patch->model = generate_patch_model(*patch->mesh, terrain_component.patch_material); |
|
|
|
|
|
|
|
// Construct patch model instance
|
|
|
|
patch->model_instance = new scene::model_instance(patch->model); |
|
|
|
|
|
|
|
|
|
|
|
// Cache the terrain patch
|
|
|
|
quadsphere_face.patches[node] = patch; |
|
|
|
|
|
|
|
// Add patch model instance to the patch scene collection
|
|
|
|
if (patch_scene_collection) |
|
|
|
patch_scene_collection->add_object(patch->model_instance); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// For each terrain pach
|
|
|
|
for (auto patch_it = quadsphere_face.patches.begin(); patch_it != quadsphere_face.patches.end(); ++patch_it) |
|
|
|
{ |
|
|
|
quadtree_node_type node = patch_it->first; |
|
|
|
|
|
|
|
// Set patch model instance active if its node is a leaf node, otherwise deactivate it
|
|
|
|
bool active = (quadtree.contains(node) && quadtree.is_leaf(node)); |
|
|
|
patch_it->second->model_instance->set_active(active); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
@ -181,28 +244,274 @@ void terrain::set_patch_subdivisions(std::uint8_t n) |
|
|
|
{ |
|
|
|
patch_subdivisions = n; |
|
|
|
|
|
|
|
// Rebuid patch base mesh
|
|
|
|
{ |
|
|
|
delete patch_base_mesh; |
|
|
|
patch_base_mesh = geom::meshes::grid_xy(2.0f, patch_subdivisions, patch_subdivisions); |
|
|
|
|
|
|
|
// Convert quads to triangle fans
|
|
|
|
for (std::size_t i = 0; i < patch_base_mesh->get_faces().size(); ++i) |
|
|
|
{ |
|
|
|
geom::mesh::face* face = patch_base_mesh->get_faces()[i]; |
|
|
|
|
|
|
|
std::size_t edge_count = 1; |
|
|
|
for (geom::mesh::edge* edge = face->edge->next; edge != face->edge; edge = edge->next) |
|
|
|
++edge_count; |
|
|
|
|
|
|
|
if (edge_count > 3) |
|
|
|
{ |
|
|
|
geom::poke_face(*patch_base_mesh, face->index); |
|
|
|
--i; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Transform patch base mesh coordinates to match the front face of a BCBF cube
|
|
|
|
const math::quaternion<float> xy_to_zy = math::quaternion<float>::rotate_y(-math::half_pi<float>); |
|
|
|
for (geom::mesh::vertex* vertex: patch_base_mesh->get_vertices()) |
|
|
|
{ |
|
|
|
vertex->position = xy_to_zy * vertex->position; |
|
|
|
vertex->position.x = 1.0f; |
|
|
|
} |
|
|
|
|
|
|
|
// Recalculate number of vertices per patch
|
|
|
|
patch_vertex_count = static_cast<std::size_t>(std::pow(std::exp2(patch_subdivisions) + 1, 2)); |
|
|
|
patch_vertex_count = patch_base_mesh->get_faces().size() * 3; |
|
|
|
|
|
|
|
// Resize patch vertex data buffer
|
|
|
|
delete[] patch_vertex_data; |
|
|
|
patch_vertex_data = new float[patch_vertex_count * patch_vertex_size]; |
|
|
|
} |
|
|
|
|
|
|
|
void terrain::set_patch_scene_collection(scene::collection* collection) |
|
|
|
{ |
|
|
|
patch_scene_collection = collection; |
|
|
|
} |
|
|
|
|
|
|
|
void terrain::set_max_error(double error) |
|
|
|
{ |
|
|
|
max_error = error; |
|
|
|
} |
|
|
|
|
|
|
|
void terrain::on_terrain_construct(entity::registry& registry, entity::id entity_id, component::terrain& component) |
|
|
|
{ |
|
|
|
// Build quad sphere
|
|
|
|
terrain_quadsphere* quadsphere = new terrain_quadsphere(); |
|
|
|
terrain_quadspheres[entity_id] = quadsphere; |
|
|
|
} |
|
|
|
|
|
|
|
void terrain::on_terrain_destroy(entity::registry& registry, entity::id entity_id) |
|
|
|
{ |
|
|
|
// Destroy quad sphere
|
|
|
|
// Find terrain quadsphere for the given entity ID
|
|
|
|
auto quadsphere_it = terrain_quadspheres.find(entity_id); |
|
|
|
|
|
|
|
if (quadsphere_it != terrain_quadspheres.end()) |
|
|
|
{ |
|
|
|
terrain_quadsphere* quadsphere = quadsphere_it->second; |
|
|
|
|
|
|
|
// For each terrain quadsphere face
|
|
|
|
for (int i = 0; i < 6; ++i) |
|
|
|
{ |
|
|
|
terrain_quadsphere_face& quadsphere_face = quadsphere->faces[i]; |
|
|
|
|
|
|
|
for (auto patch_it = quadsphere_face.patches.begin(); patch_it != quadsphere_face.patches.end(); ++patch_it) |
|
|
|
{ |
|
|
|
terrain_patch* patch = patch_it->second; |
|
|
|
|
|
|
|
if (patch_scene_collection) |
|
|
|
patch_scene_collection->remove_object(patch->model_instance); |
|
|
|
|
|
|
|
delete patch->model_instance; |
|
|
|
delete patch->model; |
|
|
|
delete patch->mesh; |
|
|
|
|
|
|
|
delete patch; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Free terrain quadsphere
|
|
|
|
delete quadsphere; |
|
|
|
} |
|
|
|
|
|
|
|
// Remove terrain quadsphere from the map
|
|
|
|
terrain_quadspheres.erase(quadsphere_it); |
|
|
|
} |
|
|
|
|
|
|
|
geom::mesh* terrain::generate_patch_mesh(std::uint8_t face_index, quadtree_node_type node, double body_radius, const std::function<double(double, double)>& elevation) const |
|
|
|
{ |
|
|
|
// Extract node depth
|
|
|
|
const quadtree_type::node_type depth = quadtree_type::depth(node); |
|
|
|
|
|
|
|
// Extract node Morton location code and decode location
|
|
|
|
const quadtree_type::node_type location = quadtree_type::location(node); |
|
|
|
quadtree_type::node_type location_x; |
|
|
|
quadtree_type::node_type location_y; |
|
|
|
geom::morton::decode(location, location_x, location_y); |
|
|
|
|
|
|
|
const double nodes_per_axis = std::exp2(depth); |
|
|
|
|
|
|
|
const double scale_yz = 1.0 / nodes_per_axis; |
|
|
|
|
|
|
|
const double node_width = 2.0 / nodes_per_axis; |
|
|
|
|
|
|
|
// Determine vertex offset according to node location
|
|
|
|
double offset_y = -(nodes_per_axis * 0.5 * node_width) + node_width * 0.5; |
|
|
|
double offset_z = offset_y; |
|
|
|
offset_y += static_cast<double>(location_x) * node_width; |
|
|
|
offset_z += static_cast<double>(location_y) * node_width; |
|
|
|
|
|
|
|
// Copy base mesh
|
|
|
|
geom::mesh* patch_mesh = new geom::mesh(*patch_base_mesh); |
|
|
|
|
|
|
|
// Modify patch mesh vertex positions
|
|
|
|
for (geom::mesh::vertex* v: patch_mesh->get_vertices()) |
|
|
|
{ |
|
|
|
double3 position = math::type_cast<double>(v->position); |
|
|
|
|
|
|
|
// Offset and scale vertex position
|
|
|
|
position.y *= scale_yz; |
|
|
|
position.z *= scale_yz; |
|
|
|
position.y += offset_y; |
|
|
|
position.z += offset_z; |
|
|
|
|
|
|
|
// Rotate according to cube face
|
|
|
|
position = face_rotations[face_index] * position; |
|
|
|
|
|
|
|
// Project onto unit sphere
|
|
|
|
//position = math::normalize(position);
|
|
|
|
|
|
|
|
// Cartesian Spherical Cube projection (KSC)
|
|
|
|
/// @see https://catlikecoding.com/unity/tutorials/cube-sphere/
|
|
|
|
/// @see https://core.ac.uk/download/pdf/228552506.pdf
|
|
|
|
double xx = position.x * position.x; |
|
|
|
double yy = position.y * position.y; |
|
|
|
double zz = position.z * position.z; |
|
|
|
position.x *= std::sqrt(std::max(0.0, 1.0 - yy * 0.5 - zz * 0.5 + yy * zz / 3.0)); |
|
|
|
position.y *= std::sqrt(std::max(0.0, 1.0 - xx * 0.5 - zz * 0.5 + xx * zz / 3.0)); |
|
|
|
position.z *= std::sqrt(std::max(0.0, 1.0 - xx * 0.5 - yy * 0.5 + xx * yy / 3.0)); |
|
|
|
|
|
|
|
// Calculate latitude and longitude of vertex position
|
|
|
|
const double latitude = std::atan2(position.z, std::sqrt(position.x * position.x + position.y * position.y)); |
|
|
|
const double longitude = std::atan2(position.y, position.x); |
|
|
|
|
|
|
|
// Look up elevation at latitude and longitude and use to calculate radial distance
|
|
|
|
const double radial_distance = body_radius + elevation(latitude, longitude); |
|
|
|
|
|
|
|
// Scale vertex position by radial distance
|
|
|
|
position *= radial_distance; |
|
|
|
position.y -= body_radius; |
|
|
|
|
|
|
|
v->position = math::type_cast<float>(position); |
|
|
|
} |
|
|
|
|
|
|
|
return patch_mesh; |
|
|
|
} |
|
|
|
|
|
|
|
void terrain::generate_patch() |
|
|
|
model* terrain::generate_patch_model(const geom::mesh& patch_mesh, material* patch_material) const |
|
|
|
{ |
|
|
|
// Barycentric coordinates
|
|
|
|
static const float3 barycentric[3] = |
|
|
|
{ |
|
|
|
{1, 0, 0}, |
|
|
|
{0, 1, 0}, |
|
|
|
{0, 0, 1} |
|
|
|
}; |
|
|
|
|
|
|
|
// Fill vertex data buffer
|
|
|
|
float* v = patch_vertex_data; |
|
|
|
for (const geom::mesh::face* face: patch_mesh.get_faces()) |
|
|
|
{ |
|
|
|
const geom::mesh::vertex* a = face->edge->vertex; |
|
|
|
const geom::mesh::vertex* b = face->edge->next->vertex; |
|
|
|
const geom::mesh::vertex* c = face->edge->previous->vertex; |
|
|
|
const geom::mesh::vertex* face_vertices[] = {a, b, c}; |
|
|
|
|
|
|
|
// Calculate facted normal
|
|
|
|
float3 normal = math::normalize(math::cross(b->position - a->position, c->position - a->position)); |
|
|
|
|
|
|
|
for (std::size_t i = 0; i < 3; ++i) |
|
|
|
{ |
|
|
|
const geom::mesh::vertex* vertex = face_vertices[i]; |
|
|
|
|
|
|
|
// Vertex position
|
|
|
|
const float3& position = vertex->position; |
|
|
|
*(v++) = position.x; |
|
|
|
*(v++) = position.y; |
|
|
|
*(v++) = position.z; |
|
|
|
|
|
|
|
// Vertex UV coordinates (latitude, longitude)
|
|
|
|
const float latitude = std::atan2(position.z, std::sqrt(position.x * position.x + position.y * position.y)); |
|
|
|
const float longitude = std::atan2(position.y, position.x); |
|
|
|
*(v++) = latitude; |
|
|
|
*(v++) = longitude; |
|
|
|
|
|
|
|
// Vertex normal
|
|
|
|
*(v++) = normal.x; |
|
|
|
*(v++) = normal.y; |
|
|
|
*(v++) = normal.z; |
|
|
|
|
|
|
|
/// @TODO Vertex tangent
|
|
|
|
*(v++) = 0.0f; |
|
|
|
*(v++) = 0.0f; |
|
|
|
*(v++) = 0.0f; |
|
|
|
*(v++) = 0.0f; |
|
|
|
|
|
|
|
// Vertex barycentric coordinates
|
|
|
|
*(v++) = barycentric[i].x; |
|
|
|
*(v++) = barycentric[i].y; |
|
|
|
*(v++) = barycentric[i].z; |
|
|
|
|
|
|
|
// Vertex morph target (LOD transition)
|
|
|
|
*(v++) = 0.0f; |
|
|
|
*(v++) = 0.0f; |
|
|
|
*(v++) = 0.0f; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Get triangle count of patch mesh
|
|
|
|
std::size_t patch_triangle_count = patch_mesh.get_faces().size(); |
|
|
|
|
|
|
|
// Allocate patch model
|
|
|
|
model* patch_model = new model(); |
|
|
|
|
|
|
|
// Resize model VBO and upload vertex data
|
|
|
|
gl::vertex_buffer* vbo = patch_model->get_vertex_buffer(); |
|
|
|
vbo->resize(patch_triangle_count * 3 * patch_vertex_stride, patch_vertex_data); |
|
|
|
|
|
|
|
// Bind vertex attributes to model VAO
|
|
|
|
gl::vertex_array* vao = patch_model->get_vertex_array(); |
|
|
|
std::size_t offset = 0; |
|
|
|
vao->bind_attribute(VERTEX_POSITION_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, patch_vertex_stride, 0); |
|
|
|
offset += 3; |
|
|
|
vao->bind_attribute(VERTEX_TEXCOORD_LOCATION, *vbo, 2, gl::vertex_attribute_type::float_32, patch_vertex_stride, sizeof(float) * offset); |
|
|
|
offset += 2; |
|
|
|
vao->bind_attribute(VERTEX_NORMAL_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, patch_vertex_stride, sizeof(float) * offset); |
|
|
|
offset += 3; |
|
|
|
vao->bind_attribute(VERTEX_TANGENT_LOCATION, *vbo, 4, gl::vertex_attribute_type::float_32, patch_vertex_stride, sizeof(float) * offset); |
|
|
|
offset += 4; |
|
|
|
vao->bind_attribute(VERTEX_BARYCENTRIC_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, patch_vertex_stride, sizeof(float) * offset); |
|
|
|
offset += 3; |
|
|
|
vao->bind_attribute(VERTEX_TARGET_LOCATION, *vbo, 3, gl::vertex_attribute_type::float_32, patch_vertex_stride, sizeof(float) * offset); |
|
|
|
offset += 3; |
|
|
|
|
|
|
|
// Create model group
|
|
|
|
model_group* patch_model_group = patch_model->add_group("terrain"); |
|
|
|
patch_model_group->set_material(patch_material); |
|
|
|
patch_model_group->set_drawing_mode(gl::drawing_mode::triangles); |
|
|
|
patch_model_group->set_start_index(0); |
|
|
|
patch_model_group->set_index_count(patch_triangle_count * 3); |
|
|
|
|
|
|
|
// Calculate model bounds
|
|
|
|
geom::aabb<float> bounds = geom::calculate_bounds(patch_mesh); |
|
|
|
patch_model->set_bounds(bounds); |
|
|
|
|
|
|
|
return patch_model; |
|
|
|
} |
|
|
|
|
|
|
|
double terrain::screen_space_error(double horizontal_fov, double horizontal_resolution, double distance, double geometric_error) |
|
|
|
{ |
|
|
|
// Calculate view frustum width at given distance
|
|
|
|
const double frustum_width = 2.0 * distance * std::tan(horizontal_fov * 0.5); |
|
|
|
|
|
|
|
return (geometric_error * horizontal_resolution) / frustum_width; |
|
|
|
} |
|
|
|
|
|
|
|
} // namespace system
|
|
|
|