ADD: planet quadtree stabilized

This commit is contained in:
2025-12-29 17:33:11 +09:00
parent dd97019264
commit 56e10d9983
3 changed files with 341 additions and 63 deletions

View File

@@ -16,11 +16,54 @@ namespace planet
PatchKey key{};
};
void compute_patch_visibility_terms(const PatchKey &key,
const glm::dvec3 &patch_center_dir,
double radius_m,
double &out_cos_patch_radius,
double &out_sin_patch_radius,
double &out_bound_radius_m)
{
glm::dvec3 c = patch_center_dir;
const double c_len2 = glm::dot(c, c);
if (!(c_len2 > 0.0))
{
c = glm::dvec3(0.0, 0.0, 1.0);
}
else
{
c *= (1.0 / std::sqrt(c_len2));
}
double u0 = 0.0, u1 = 0.0, v0 = 0.0, v1 = 0.0;
cubesphere_tile_uv_bounds(key.level, key.x, key.y, u0, u1, v0, v1);
// Conservative angular radius: max angle from patch center direction to any corner direction.
double min_dot = 1.0;
min_dot = std::min(min_dot, glm::dot(c, cubesphere_unit_direction(key.face, u0, v0)));
min_dot = std::min(min_dot, glm::dot(c, cubesphere_unit_direction(key.face, u1, v0)));
min_dot = std::min(min_dot, glm::dot(c, cubesphere_unit_direction(key.face, u0, v1)));
min_dot = std::min(min_dot, glm::dot(c, cubesphere_unit_direction(key.face, u1, v1)));
const double cos_a = glm::clamp(min_dot, -1.0, 1.0);
const double sin_a = std::sqrt(glm::max(0.0, 1.0 - cos_a * cos_a));
// Vertex positions are built as (unit_dir - patch_center_dir) * radius (chord length).
const double chord_r = radius_m * std::sqrt(glm::max(0.0, 2.0 - 2.0 * cos_a));
// Skirts extend inward; add a small safety margin so CPU culling stays conservative.
const double skirt_depth = cubesphere_skirt_depth_m(radius_m, key.level);
out_cos_patch_radius = cos_a;
out_sin_patch_radius = sin_a;
out_bound_radius_m = glm::max(1.0, chord_r + skirt_depth);
}
bool is_patch_visible_horizon(const WorldVec3 &body_center_world,
double radius_m,
const WorldVec3 &camera_world,
const glm::dvec3 &patch_center_dir,
double patch_edge_m)
double cos_patch_radius,
double sin_patch_radius)
{
const glm::dvec3 w = camera_world - body_center_world;
const double d = glm::length(w);
@@ -36,15 +79,13 @@ namespace planet
const double cos_h = glm::clamp(radius_m / d, 0.0, 1.0);
const double sin_h = std::sqrt(glm::max(0.0, 1.0 - cos_h * cos_h));
// Expand horizon by patch angular radius to avoid culling near silhouettes.
const double half_diag_m = patch_edge_m * 0.7071067811865476; // sqrt(2)/2
const double ang = glm::clamp(half_diag_m / radius_m, 0.0, glm::pi<double>());
const double cos_a = std::cos(ang);
const double sin_a = std::sin(ang);
// Visible if theta <= theta_h + ang:
// cos(theta) >= cos(theta_h + ang)
const double cos_limit = cos_h * cos_a - sin_h * sin_a;
const double cos_limit = cos_h * cos_patch_radius - sin_h * sin_patch_radius;
if (!std::isfinite(cos_theta) || !std::isfinite(cos_limit))
{
return true; // fail-safe: avoid catastrophic full culls
}
return cos_theta >= cos_limit;
}
@@ -161,9 +202,22 @@ namespace planet
const double patch_edge_m = cubesphere_patch_edge_m(radius_m, k.level);
const glm::dvec3 patch_dir = cubesphere_patch_center_direction(k.face, k.level, k.x, k.y);
double cos_patch_radius = 1.0;
double sin_patch_radius = 0.0;
double patch_bound_r_m = 1.0;
if (_settings.horizon_cull || _settings.frustum_cull)
{
compute_patch_visibility_terms(k, patch_dir, radius_m, cos_patch_radius, sin_patch_radius, patch_bound_r_m);
}
if (_settings.horizon_cull)
{
if (!is_patch_visible_horizon(body_center_world, radius_m, camera_world, patch_dir, patch_edge_m))
if (!is_patch_visible_horizon(body_center_world,
radius_m,
camera_world,
patch_dir,
cos_patch_radius,
sin_patch_radius))
{
_stats.nodes_culled++;
continue;
@@ -176,7 +230,7 @@ namespace planet
if (_settings.frustum_cull)
{
const glm::vec3 patch_center_local = world_to_local(patch_center_world, origin_world);
const float bound_r = static_cast<float>(patch_edge_m * 0.7071067811865476);
const float bound_r = static_cast<float>(patch_bound_r_m);
if (!is_patch_visible_frustum(patch_center_local, bound_r, scene_data.viewproj))
{
_stats.nodes_culled++;