392 lines
18 KiB
HLSL
392 lines
18 KiB
HLSL
#ifndef CULTMATH_PLANETARY_HLSL
|
|
#define CULTMATH_PLANETARY_HLSL
|
|
|
|
static const float CULTMATH_PLANETARY_QUARTER_PI = 0.78539816339744830962;
|
|
|
|
float3 cultmath_planetary_face_direction(int face, float2 coordinate)
|
|
{
|
|
float2 tangent = tan(coordinate * CULTMATH_PLANETARY_QUARTER_PI);
|
|
float3 cube = face == 0 ? float3(1.0, tangent.y, -tangent.x)
|
|
: face == 1 ? float3(-1.0, tangent.y, tangent.x)
|
|
: face == 2 ? float3(tangent.x, 1.0, -tangent.y)
|
|
: face == 3 ? float3(tangent.x, -1.0, tangent.y)
|
|
: face == 4 ? float3(tangent.x, tangent.y, 1.0)
|
|
: float3(-tangent.x, tangent.y, -1.0);
|
|
return cultmath_normalize(cube);
|
|
}
|
|
|
|
float2 cultmath_planetary_face_coordinate(float3 direction, out int face)
|
|
{
|
|
float3 d = cultmath_normalize(direction);
|
|
float3 a = abs(d);
|
|
float2 cube_uv;
|
|
if (a.x >= a.y && a.x >= a.z)
|
|
{
|
|
if (d.x >= 0.0) { face = 0; cube_uv = float2(-d.z, d.y) / a.x; }
|
|
else { face = 1; cube_uv = float2(d.z, d.y) / a.x; }
|
|
}
|
|
else if (a.y >= a.z)
|
|
{
|
|
if (d.y >= 0.0) { face = 2; cube_uv = float2(d.x, -d.z) / a.y; }
|
|
else { face = 3; cube_uv = float2(d.x, d.z) / a.y; }
|
|
}
|
|
else
|
|
{
|
|
if (d.z >= 0.0) { face = 4; cube_uv = d.xy / a.z; }
|
|
else { face = 5; cube_uv = float2(-d.x, d.y) / a.z; }
|
|
}
|
|
return atan(cube_uv) / CULTMATH_PLANETARY_QUARTER_PI;
|
|
}
|
|
|
|
bool cultmath_planetary_page_local(float3 direction, float4 tile_address, float active, out float2 local)
|
|
{
|
|
int face;
|
|
float2 uv = cultmath_planetary_face_coordinate(direction, face);
|
|
local = 0.0;
|
|
if (active < 0.5 || abs((float)face - tile_address.x) > 0.25) return false;
|
|
float axis_tiles = exp2(tile_address.y);
|
|
float2 scaled = (uv * 0.5 + 0.5) * axis_tiles;
|
|
float2 canonical = floor(min(scaled, axis_tiles - 1.0e-5));
|
|
if (any(abs(canonical - tile_address.zw) > 0.25)) return false;
|
|
local = scaled - tile_address.zw;
|
|
return all(local >= 0.0) && all(local <= 1.0);
|
|
}
|
|
|
|
float3 cultmath_planetary_surface_normal(float3 direction, float3 world_distance_tangent_gradient)
|
|
{
|
|
direction = cultmath_normalize(direction);
|
|
float3 tangent_gradient = world_distance_tangent_gradient - direction * dot(world_distance_tangent_gradient, direction);
|
|
return cultmath_normalize(direction - tangent_gradient);
|
|
}
|
|
|
|
float2 cultmath_planetary_equirectangular_forward(float3 direction)
|
|
{
|
|
direction = cultmath_normalize(direction);
|
|
return float2(atan2(direction.y, direction.x) / CULTMATH_PI, asin(clamp(direction.z, -1.0, 1.0)) / CULTMATH_HALF_PI);
|
|
}
|
|
|
|
float3 cultmath_planetary_equirectangular_inverse(float2 coordinate)
|
|
{
|
|
float longitude = coordinate.x * CULTMATH_PI;
|
|
float latitude = coordinate.y * CULTMATH_HALF_PI;
|
|
float cos_latitude = cos(latitude);
|
|
return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
|
|
}
|
|
|
|
static const float CULTMATH_EQUAL_EARTH_A1 = 1.340264;
|
|
static const float CULTMATH_EQUAL_EARTH_A2 = -0.081106;
|
|
static const float CULTMATH_EQUAL_EARTH_A3 = 0.000893;
|
|
static const float CULTMATH_EQUAL_EARTH_A4 = 0.003796;
|
|
static const float CULTMATH_EQUAL_EARTH_M = 0.8660254037844386;
|
|
static const float CULTMATH_EQUAL_EARTH_X_MAX = 2.706629;
|
|
static const float CULTMATH_EQUAL_EARTH_Y_MAX = 1.3173628;
|
|
|
|
float cultmath_equal_earth_y(float theta)
|
|
{
|
|
float theta2 = theta * theta;
|
|
float theta6 = theta2 * theta2 * theta2;
|
|
return theta * (CULTMATH_EQUAL_EARTH_A1 + CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (CULTMATH_EQUAL_EARTH_A3 + CULTMATH_EQUAL_EARTH_A4 * theta2));
|
|
}
|
|
|
|
float2 cultmath_planetary_equal_earth_forward(float3 direction)
|
|
{
|
|
direction = cultmath_normalize(direction);
|
|
float longitude = atan2(direction.y, direction.x);
|
|
float latitude = asin(clamp(direction.z, -1.0, 1.0));
|
|
float theta = asin(CULTMATH_EQUAL_EARTH_M * sin(latitude));
|
|
float theta2 = theta * theta;
|
|
float theta6 = theta2 * theta2 * theta2;
|
|
float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2));
|
|
return float2(longitude * cos(theta) / denominator / CULTMATH_EQUAL_EARTH_X_MAX, cultmath_equal_earth_y(theta) / CULTMATH_EQUAL_EARTH_Y_MAX);
|
|
}
|
|
|
|
float3 cultmath_planetary_equal_earth_inverse(float2 coordinate)
|
|
{
|
|
float target_y = coordinate.y * CULTMATH_EQUAL_EARTH_Y_MAX;
|
|
float theta = target_y / CULTMATH_EQUAL_EARTH_A1;
|
|
[unroll] for (int iteration = 0; iteration < 8; iteration++)
|
|
{
|
|
float theta2 = theta * theta;
|
|
float theta6 = theta2 * theta2 * theta2;
|
|
float derivative = CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2);
|
|
theta -= (cultmath_equal_earth_y(theta) - target_y) / derivative;
|
|
}
|
|
float latitude = asin(clamp(sin(theta) / CULTMATH_EQUAL_EARTH_M, -1.0, 1.0));
|
|
float theta2 = theta * theta;
|
|
float theta6 = theta2 * theta2 * theta2;
|
|
float denominator = CULTMATH_EQUAL_EARTH_M * (CULTMATH_EQUAL_EARTH_A1 + 3.0 * CULTMATH_EQUAL_EARTH_A2 * theta2 + theta6 * (7.0 * CULTMATH_EQUAL_EARTH_A3 + 9.0 * CULTMATH_EQUAL_EARTH_A4 * theta2));
|
|
float longitude = coordinate.x * CULTMATH_EQUAL_EARTH_X_MAX * denominator / cos(theta);
|
|
float cos_latitude = cos(latitude);
|
|
return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
|
|
}
|
|
|
|
float cultmath_planetary_wrap_longitude(float longitude)
|
|
{
|
|
longitude = fmod(longitude, CULTMATH_TAU);
|
|
if (longitude > CULTMATH_PI) longitude -= CULTMATH_TAU;
|
|
if (longitude < -CULTMATH_PI) longitude += CULTMATH_TAU;
|
|
return longitude;
|
|
}
|
|
|
|
float3 cultmath_planetary_lon_lat_direction(float longitude, float latitude)
|
|
{
|
|
float cos_latitude = cos(latitude);
|
|
return cultmath_normalize(float3(cos_latitude * cos(longitude), cos_latitude * sin(longitude), sin(latitude)));
|
|
}
|
|
|
|
float cultmath_planetary_asinh(float value) { return log(value + sqrt(value * value + 1.0)); }
|
|
float cultmath_planetary_sinh(float value) { return 0.5 * (exp(value) - exp(-value)); }
|
|
|
|
bool cultmath_planetary_azimuthal_forward(float3 direction, float2 center_lon_lat, int kind, out float2 coordinate)
|
|
{
|
|
direction = cultmath_normalize(direction);
|
|
float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat.x);
|
|
float latitude = asin(clamp(direction.z, -1.0, 1.0));
|
|
float sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y);
|
|
float sin_latitude = sin(latitude), cos_latitude = cos(latitude), cos_longitude = cos(longitude);
|
|
float cos_c = clamp(sin0 * sin_latitude + cos0 * cos_latitude * cos_longitude, -1.0, 1.0);
|
|
float2 raw = float2(cos_latitude * sin(longitude), cos0 * sin_latitude - sin0 * cos_latitude * cos_longitude);
|
|
float factor = 1.0;
|
|
if (kind == 3)
|
|
{
|
|
if (cos_c < 0.0) { coordinate = 0.0; return false; }
|
|
}
|
|
else if (kind == 4)
|
|
{
|
|
float c = acos(cos_c); factor = (c < 1.0e-6 ? 1.0 : c / sin(c)) / CULTMATH_PI;
|
|
}
|
|
else if (kind == 5) factor = sqrt(2.0 / max(1.0 + cos_c, 1.0e-20)) * 0.5;
|
|
else
|
|
{
|
|
if (cos_c <= 0.0) { coordinate = 0.0; return false; }
|
|
factor = 1.0 / cos_c;
|
|
}
|
|
coordinate = raw * factor;
|
|
return true;
|
|
}
|
|
|
|
bool cultmath_planetary_azimuthal_inverse(float2 coordinate, float2 center_lon_lat, int kind, out float3 direction)
|
|
{
|
|
float rho = length(coordinate);
|
|
float c;
|
|
if (kind == 3) { if (rho > 1.0) { direction = 0.0; return false; } c = asin(clamp(rho, 0.0, 1.0)); }
|
|
else if (kind == 4) { if (rho > 1.0) { direction = 0.0; return false; } c = rho * CULTMATH_PI; }
|
|
else if (kind == 5) { if (rho > 1.0) { direction = 0.0; return false; } c = 2.0 * asin(clamp(rho, 0.0, 1.0)); }
|
|
else c = atan(rho);
|
|
if (rho < 1.0e-6) { direction = cultmath_planetary_lon_lat_direction(center_lon_lat.x, center_lon_lat.y); return true; }
|
|
float sin_c = sin(c), cos_c = cos(c), sin0 = sin(center_lon_lat.y), cos0 = cos(center_lon_lat.y);
|
|
float latitude = asin(clamp(cos_c * sin0 + coordinate.y * sin_c * cos0 / rho, -1.0, 1.0));
|
|
float delta_longitude = atan2(coordinate.x * sin_c, rho * cos0 * cos_c - coordinate.y * sin0 * sin_c);
|
|
direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(delta_longitude + center_lon_lat.x), latitude);
|
|
return true;
|
|
}
|
|
|
|
bool cultmath_planetary_cube_atlas_forward(float3 direction, out float2 coordinate)
|
|
{
|
|
int face;
|
|
float2 face_coordinate = cultmath_planetary_face_coordinate(direction, face);
|
|
int column = face % 3;
|
|
int row = face / 3;
|
|
coordinate = float2(-1.0 + (column + (face_coordinate.x + 1.0) * 0.5) * (2.0 / 3.0), 1.0 - (row + (face_coordinate.y + 1.0) * 0.5));
|
|
return true;
|
|
}
|
|
|
|
bool cultmath_planetary_cube_atlas_inverse(float2 coordinate, out float3 direction)
|
|
{
|
|
if (any(coordinate < -1.0) || any(coordinate > 1.0)) { direction = 0.0; return false; }
|
|
int column = min((int)((coordinate.x + 1.0) * 1.5), 2);
|
|
int row = min((int)(1.0 - coordinate.y), 1);
|
|
float u = ((coordinate.x + 1.0) * 1.5 - column) * 2.0 - 1.0;
|
|
float v = ((1.0 - coordinate.y) - row) * 2.0 - 1.0;
|
|
direction = cultmath_planetary_face_direction(row * 3 + column, float2(u, v));
|
|
return true;
|
|
}
|
|
|
|
bool cultmath_planetary_projection_forward(float3 direction, int kind, float3 center_lon_lat_scale, out float2 coordinate)
|
|
{
|
|
float scale = max(center_lon_lat_scale.z, 1.0e-20);
|
|
if (kind == 6)
|
|
{
|
|
bool valid = cultmath_planetary_cube_atlas_forward(direction, coordinate);
|
|
coordinate /= scale; return valid;
|
|
}
|
|
if (kind >= 3)
|
|
{
|
|
bool valid = cultmath_planetary_azimuthal_forward(direction, center_lon_lat_scale.xy, kind, coordinate);
|
|
coordinate /= scale; return valid;
|
|
}
|
|
direction = cultmath_normalize(direction);
|
|
float longitude = cultmath_planetary_wrap_longitude(atan2(direction.y, direction.x) - center_lon_lat_scale.x);
|
|
float latitude = asin(clamp(direction.z, -1.0, 1.0));
|
|
if (kind == 0) coordinate = float2(longitude / CULTMATH_PI, latitude / CULTMATH_HALF_PI);
|
|
else if (kind == 1)
|
|
{
|
|
const float maximum_latitude = 1.4844222297453324;
|
|
if (abs(latitude) > maximum_latitude) { coordinate = 0.0; return false; }
|
|
coordinate = float2(longitude / CULTMATH_PI, cultmath_planetary_asinh(tan(latitude)) / CULTMATH_PI);
|
|
}
|
|
else
|
|
{
|
|
float3 centered = cultmath_planetary_lon_lat_direction(longitude, latitude);
|
|
coordinate = cultmath_planetary_equal_earth_forward(centered);
|
|
}
|
|
coordinate /= scale;
|
|
return true;
|
|
}
|
|
|
|
bool cultmath_planetary_projection_inverse(float2 coordinate, int kind, float3 center_lon_lat_scale, out float3 direction)
|
|
{
|
|
coordinate *= max(center_lon_lat_scale.z, 1.0e-20);
|
|
if (kind == 6) return cultmath_planetary_cube_atlas_inverse(coordinate, direction);
|
|
if (kind >= 3) return cultmath_planetary_azimuthal_inverse(coordinate, center_lon_lat_scale.xy, kind, direction);
|
|
if (any(abs(coordinate) > 1.0)) { direction = 0.0; return false; }
|
|
float longitude, latitude;
|
|
if (kind == 0) { longitude = coordinate.x * CULTMATH_PI; latitude = coordinate.y * CULTMATH_HALF_PI; }
|
|
else if (kind == 1) { longitude = coordinate.x * CULTMATH_PI; latitude = atan(cultmath_planetary_sinh(coordinate.y * CULTMATH_PI)); }
|
|
else
|
|
{
|
|
float3 centered = cultmath_planetary_equal_earth_inverse(coordinate);
|
|
longitude = atan2(centered.y, centered.x); latitude = asin(clamp(centered.z, -1.0, 1.0));
|
|
}
|
|
direction = cultmath_planetary_lon_lat_direction(cultmath_planetary_wrap_longitude(longitude + center_lon_lat_scale.x), latitude);
|
|
return true;
|
|
}
|
|
|
|
struct CultMathPlanetaryFieldDefinition
|
|
{
|
|
float radius;
|
|
int seed;
|
|
CultMathAdvancedErosionParameters erosion;
|
|
};
|
|
|
|
struct CultMathPlanetaryBaseFieldSample
|
|
{
|
|
float radial_displacement;
|
|
float3 radial_gradient;
|
|
float field_value;
|
|
float3 field_gradient;
|
|
float fade_target;
|
|
};
|
|
|
|
struct CultMathPlanetarySurfaceSample
|
|
{
|
|
float radial_displacement;
|
|
float3 tangent_gradient;
|
|
float3 surface_normal;
|
|
float slope;
|
|
float ridge;
|
|
float gully;
|
|
float finest_resolved_wavelength;
|
|
float unresolved_height_bound;
|
|
};
|
|
|
|
struct CultMathPlanetaryPageSample
|
|
{
|
|
float4 height_gradient;
|
|
float2 masks;
|
|
};
|
|
|
|
CultMathPlanetaryPageSample cultmath_planetary_page_lerp(
|
|
CultMathPlanetaryPageSample a,
|
|
CultMathPlanetaryPageSample b,
|
|
CultMathPlanetaryPageSample c,
|
|
CultMathPlanetaryPageSample d,
|
|
float2 fraction)
|
|
{
|
|
CultMathPlanetaryPageSample result;
|
|
result.height_gradient = lerp(lerp(a.height_gradient, b.height_gradient, fraction.x), lerp(c.height_gradient, d.height_gradient, fraction.x), fraction.y);
|
|
result.masks = lerp(lerp(a.masks, b.masks, fraction.x), lerp(c.masks, d.masks, fraction.x), fraction.y);
|
|
return result;
|
|
}
|
|
|
|
CultMathPlanetaryPageSample cultmath_planetary_residual_sample(
|
|
CultMathPlanetarySurfaceSample child,
|
|
CultMathPlanetarySurfaceSample parent,
|
|
bool has_parent)
|
|
{
|
|
CultMathPlanetaryPageSample result;
|
|
result.height_gradient = float4(child.radial_displacement, child.tangent_gradient);
|
|
if (has_parent) result.height_gradient -= float4(parent.radial_displacement, parent.tangent_gradient);
|
|
result.masks = float2(child.ridge, child.gully);
|
|
return result;
|
|
}
|
|
|
|
float4 cultmath_planetary_summary_empty()
|
|
{
|
|
return float4(3.402823466e+38, -3.402823466e+38, 0.0, 0.0);
|
|
}
|
|
|
|
float4 cultmath_planetary_summary_accumulate(float4 summary, float4 height_gradient, float unresolved_height)
|
|
{
|
|
summary.x = min(summary.x, height_gradient.x);
|
|
summary.y = max(summary.y, height_gradient.x);
|
|
summary.z = max(summary.z, length(height_gradient.yzw));
|
|
summary.w = max(summary.w, max(unresolved_height, 0.0));
|
|
return summary;
|
|
}
|
|
|
|
float cultmath_planetary_pow4(float value) { float square = value * value; return square * square; }
|
|
|
|
CultMathPlanetarySurfaceSample cultmath_planetary_field_sample(
|
|
CultMathPlanetaryFieldDefinition definition,
|
|
float3 unit_direction,
|
|
CultMathPlanetaryBaseFieldSample base_sample,
|
|
float footprint_meters)
|
|
{
|
|
float3 direction = cultmath_normalize(unit_direction);
|
|
float3 radial_gradient = base_sample.radial_gradient - direction * dot(base_sample.radial_gradient, direction);
|
|
float3 field_gradient = base_sample.field_gradient - direction * dot(base_sample.field_gradient, direction);
|
|
CultMathAdvancedErosionParameters p = definition.erosion;
|
|
CultMathErosionBandSelection band = cultmath_select_erosion_bands(
|
|
p.scale * p.cell_scale, footprint_meters, p.octaves, p.lacunarity,
|
|
p.strength * p.scale, p.gain, 2.0);
|
|
float3 powers = float3(
|
|
cultmath_planetary_pow4(abs(direction.x)),
|
|
cultmath_planetary_pow4(abs(direction.y)),
|
|
cultmath_planetary_pow4(abs(direction.z)));
|
|
float power_sum = max(powers.x + powers.y + powers.z, 1.0e-6);
|
|
float3 weights = powers / power_sum;
|
|
float3 world = direction * definition.radius;
|
|
float seed = (float)definition.seed;
|
|
CultMathAdvancedErosionResult xy = cultmath_advanced_erosion_filter_banded(
|
|
world.xy + float2(713.0 + seed * 0.754877666, -291.0 + seed * 0.569840296),
|
|
float3(base_sample.field_value, field_gradient.x, field_gradient.y), base_sample.fade_target, p, band);
|
|
CultMathAdvancedErosionResult yz = cultmath_advanced_erosion_filter_banded(
|
|
world.yz + float2(-431.0 + seed * 0.438289027, 887.0 + seed * 0.328438163),
|
|
float3(base_sample.field_value, field_gradient.y, field_gradient.z), base_sample.fade_target, p, band);
|
|
CultMathAdvancedErosionResult zx = cultmath_advanced_erosion_filter_banded(
|
|
world.zx + float2(197.0 + seed * 0.219783071, 557.0 + seed * 0.145898034),
|
|
float3(base_sample.field_value, field_gradient.z, field_gradient.x), base_sample.fade_target, p, band);
|
|
float erosion_height = xy.delta.x * weights.z + yz.delta.x * weights.x + zx.delta.x * weights.y;
|
|
float3 erosion_gradient = float3(
|
|
xy.delta.y * weights.z + zx.delta.z * weights.y,
|
|
xy.delta.z * weights.z + yz.delta.y * weights.x,
|
|
yz.delta.z * weights.x + zx.delta.y * weights.y);
|
|
float3 power_gradient_x = float3(4.0 * direction.x * direction.x * direction.x / definition.radius, 0.0, 0.0);
|
|
float3 power_gradient_y = float3(0.0, 4.0 * direction.y * direction.y * direction.y / definition.radius, 0.0);
|
|
float3 power_gradient_z = float3(0.0, 0.0, 4.0 * direction.z * direction.z * direction.z / definition.radius);
|
|
power_gradient_x -= direction * dot(power_gradient_x, direction);
|
|
power_gradient_y -= direction * dot(power_gradient_y, direction);
|
|
power_gradient_z -= direction * dot(power_gradient_z, direction);
|
|
float3 power_sum_gradient = power_gradient_x + power_gradient_y + power_gradient_z;
|
|
float inverse_power_sum_squared = 1.0 / (power_sum * power_sum);
|
|
float3 weight_gradient_x = (power_gradient_x * power_sum - power_sum_gradient * powers.x) * inverse_power_sum_squared;
|
|
float3 weight_gradient_y = (power_gradient_y * power_sum - power_sum_gradient * powers.y) * inverse_power_sum_squared;
|
|
float3 weight_gradient_z = (power_gradient_z * power_sum - power_sum_gradient * powers.z) * inverse_power_sum_squared;
|
|
erosion_gradient += yz.delta.x * weight_gradient_x + zx.delta.x * weight_gradient_y + xy.delta.x * weight_gradient_z;
|
|
float3 gradient = radial_gradient + erosion_gradient;
|
|
gradient -= direction * dot(gradient, direction);
|
|
float ridge_evidence = xy.ridge_map * weights.z + yz.ridge_map * weights.x + zx.ridge_map * weights.y;
|
|
CultMathPlanetarySurfaceSample result;
|
|
result.radial_displacement = base_sample.radial_displacement + erosion_height;
|
|
result.tangent_gradient = gradient;
|
|
result.surface_normal = cultmath_planetary_surface_normal(direction, gradient);
|
|
result.slope = length(gradient);
|
|
result.ridge = saturate(ridge_evidence * 0.5 + 0.5);
|
|
result.gully = saturate(0.5 - ridge_evidence * 0.5);
|
|
result.finest_resolved_wavelength = band.finest_included_wavelength;
|
|
result.unresolved_height_bound = band.unresolved_height_bound;
|
|
return result;
|
|
}
|
|
|
|
#endif
|