straywild/world/generation/terrain_chunk_edge_profile.gd

141 lines
3.7 KiB
GDScript

class_name TerrainChunkEdgeProfile
extends RefCounted
var edge := TerrainChunkTopology.Edge.NORTH
var points := PackedVector2Array()
func _init(
profile_edge := TerrainChunkTopology.Edge.NORTH,
profile_points := PackedVector2Array(),
) -> void:
edge = profile_edge
points = profile_points
func matches(other: TerrainChunkEdgeProfile, tolerance: float) -> bool:
if other == null:
return false
var first_surface := _upper_surface_points()
var second_surface := other._upper_surface_points()
return _surfaces_match(first_surface, second_surface, tolerance)
## Compares only the visible raised portion of two edge profiles. Authored
## deep-water cliff pieces continue below ordinary ground level; when their
## lateral end meets an inland cliff, that lower extension is buried by the
## neighboring base terrain and must not create a false seam mismatch.
func matches_above_height(
other: TerrainChunkEdgeProfile,
minimum_height: float,
tolerance: float,
) -> bool:
if other == null:
return false
var first_surface := _points_above_height(
_upper_surface_points(),
minimum_height,
tolerance,
)
var second_surface := _points_above_height(
other._upper_surface_points(),
minimum_height,
tolerance,
)
return _surfaces_match(first_surface, second_surface, tolerance)
func _surfaces_match(
first_surface: PackedVector2Array,
second_surface: PackedVector2Array,
tolerance: float,
) -> bool:
if first_surface.size() < 2 or second_surface.size() < 2:
return false
if (
absf(first_surface[0].x - second_surface[0].x) > tolerance
or absf(
first_surface[first_surface.size() - 1].x
- second_surface[second_surface.size() - 1].x
) > tolerance
):
return false
var sample_tangents := PackedFloat32Array()
for point: Vector2 in first_surface:
_append_unique_tangent(sample_tangents, point.x, tolerance)
for point: Vector2 in second_surface:
_append_unique_tangent(sample_tangents, point.x, tolerance)
sample_tangents.sort()
for tangent: float in sample_tangents:
var first_height := _sample_height(first_surface, tangent)
var second_height := _sample_height(second_surface, tangent)
if absf(first_height - second_height) > tolerance:
return false
return true
func _points_above_height(
surface: PackedVector2Array,
minimum_height: float,
tolerance: float,
) -> PackedVector2Array:
var result := PackedVector2Array()
for point: Vector2 in surface:
if point.y > minimum_height + tolerance:
result.append(point)
return result
func _upper_surface_points() -> PackedVector2Array:
var result := PackedVector2Array()
for point: Vector2 in points:
if (
not result.is_empty()
and is_equal_approx(result[result.size() - 1].x, point.x)
):
var previous := result[result.size() - 1]
previous.y = maxf(previous.y, point.y)
result[result.size() - 1] = previous
continue
result.append(point)
return result
func _append_unique_tangent(
tangents: PackedFloat32Array,
tangent: float,
tolerance: float,
) -> void:
for existing: float in tangents:
if absf(existing - tangent) <= tolerance:
return
tangents.append(tangent)
func _sample_height(surface: PackedVector2Array, tangent: float) -> float:
if tangent <= surface[0].x:
return surface[0].y
for index: int in range(1, surface.size()):
var right := surface[index]
if tangent > right.x:
continue
var left := surface[index - 1]
var span := right.x - left.x
if is_zero_approx(span):
return maxf(left.y, right.y)
return lerpf(left.y, right.y, (tangent - left.x) / span)
return surface[surface.size() - 1].y
func signature(quantization: float) -> String:
var tokens := PackedStringArray()
for point: Vector2 in points:
tokens.append(
"%d:%d"
% [
roundi(point.x / quantization),
roundi(point.y / quantization),
]
)
return ",".join(tokens)