Polish world, shop, and artwork presentation

This commit is contained in:
Alexander Sellite 2026-08-13 14:53:39 -04:00
parent 7d38bc962b
commit 117fdbfdaa
94 changed files with 1771 additions and 579 deletions

View file

@ -4,6 +4,12 @@
[sub_resource type="ShaderMaterial" id="ShaderMaterial_netfishing"]
shader = ExtResource("1_sky_shader")
shader_parameter/cloud_coverage = 0.0
shader_parameter/cloud_opacity = 0.0
shader_parameter/cloud_light_color = Color(0.7, 0.76, 0.77, 1)
shader_parameter/cloud_shadow_color = Color(0.31, 0.38, 0.4, 1)
shader_parameter/cloud_wind = Vector2(0.01, -0.006)
shader_parameter/cloud_scale = 0.72
shader_parameter/sky_top_color = Color(0.204, 0.498, 0.643, 1)
shader_parameter/sky_horizon_color = Color(0.663, 0.843, 0.847, 1)
shader_parameter/ground_bottom_color = Color(0.157, 0.361, 0.439, 1)

File diff suppressed because one or more lines are too long

View file

@ -49,9 +49,6 @@ uniform float shoreline_base_fill : hint_range(0.0, 1.0, 0.01) = 0.32;
uniform float shoreline_opacity_boost : hint_range(0.0, 0.5, 0.01) = 0.26;
uniform float surface_highlight_night_floor : hint_range(0.0, 1.0, 0.01) = 0.72;
uniform float shoreline_minimum_reach : hint_range(0.1, 1.0, 0.01) = 0.48;
uniform float shoreline_crest_width : hint_range(0.02, 0.5, 0.01) = 0.13;
uniform float shoreline_trail_spacing : hint_range(0.05, 0.8, 0.01) = 0.28;
uniform float shoreline_trail_strength : hint_range(0.0, 1.0, 0.01) = 0.55;
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
@ -486,25 +483,6 @@ void fragment() {
water_depth
);
shoreline_mask *= shoreline_depth_visibility;
float shoreline_lead_depth = animated_shoreline_depth * 0.82;
float shoreline_lead_crest = 1.0 - smoothstep(
shoreline_crest_width * 0.25,
shoreline_crest_width,
abs(contour_depth - shoreline_lead_depth)
);
float shoreline_trail_depth = max(
shoreline_lead_depth - shoreline_trail_spacing,
shoreline_crest_width
);
float shoreline_trail_crest = 1.0 - smoothstep(
shoreline_crest_width * 0.40,
shoreline_crest_width * 1.45,
abs(contour_depth - shoreline_trail_depth)
);
shoreline_lead_crest *= shoreline_depth_visibility;
shoreline_trail_crest *= (
shoreline_depth_visibility * shoreline_trail_strength
);
float shoreline_marks = max(
blocky_mark_layer(
world_position.xz * shoreline_mark_density
@ -524,17 +502,14 @@ void fragment() {
1.0,
shoreline_marks
);
float moving_crests = max(
shoreline_lead_crest,
shoreline_trail_crest
) * mix(0.68, 1.0, shoreline_marks);
shoreline_foam = max(shoreline_foam, moving_crests);
shoreline_foam *= shoreline_proximity;
water_alpha = min(
water_alpha + shoreline_foam * shoreline_opacity_boost,
0.98
);
water_alpha *= contact_stability;
// Preserve most of the water layer at exact terrain contact. Fully fading
// it exposed the sand texture as a broken tan seam along the water plane.
water_alpha *= mix(0.82, 1.0, contact_stability);
float surface_mottle = value_noise(
world_position.xz * surface_mottle_scale

View file

@ -47,9 +47,6 @@ shader_parameter/shoreline_base_fill = 0.58
shader_parameter/shoreline_opacity_boost = 0.38
shader_parameter/surface_highlight_night_floor = 0.72
shader_parameter/shoreline_minimum_reach = 0.22
shader_parameter/shoreline_crest_width = 0.08
shader_parameter/shoreline_trail_spacing = 0.13
shader_parameter/shoreline_trail_strength = 0.55
shader_parameter/distant_alpha_start = 260.0
shader_parameter/distant_alpha_end = 1400.0
shader_parameter/environment_tint = Color(1, 1, 1, 1)

View file

@ -47,9 +47,6 @@ shader_parameter/shoreline_base_fill = 0.58
shader_parameter/shoreline_opacity_boost = 0.38
shader_parameter/surface_highlight_night_floor = 0.72
shader_parameter/shoreline_minimum_reach = 0.22
shader_parameter/shoreline_crest_width = 0.08
shader_parameter/shoreline_trail_spacing = 0.13
shader_parameter/shoreline_trail_strength = 0.55
shader_parameter/distant_alpha_start = 260.0
shader_parameter/distant_alpha_end = 1400.0
shader_parameter/environment_tint = Color(1, 1, 1, 1)

File diff suppressed because one or more lines are too long

View file

@ -6,13 +6,13 @@
[ext_resource type="Environment" path="res://world/environment/netfishing_environment.tres" id="4_environment"]
[sub_resource type="BoxShape3D" id="NorthSouthBoundsShape"]
size = Vector3(120, 14, 2)
size = Vector3(168.8772, 14, 2)
[sub_resource type="BoxShape3D" id="EastWestBoundsShape"]
size = Vector3(2, 14, 90)
size = Vector3(2, 14, 126.45079)
[sub_resource type="BoxShape3D" id="FailsafeShape"]
size = Vector3(118, 4, 88)
size = Vector3(166.8772, 4, 124.45079)
[node name="TestWorld" type="Node3D"]
script = ExtResource("1_world")
@ -49,25 +49,25 @@ shape = SubResource("FailsafeShape")
[node name="WorldBounds" type="Node3D" parent="."]
[node name="North" type="StaticBody3D" parent="WorldBounds"]
position = Vector3(12, 5, 45)
position = Vector3(12, 5, 63.225395)
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/North"]
shape = SubResource("NorthSouthBoundsShape")
[node name="South" type="StaticBody3D" parent="WorldBounds"]
position = Vector3(12, 5, -45)
position = Vector3(12, 5, -63.225395)
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/South"]
shape = SubResource("NorthSouthBoundsShape")
[node name="West" type="StaticBody3D" parent="WorldBounds"]
position = Vector3(-48, 5, 0)
position = Vector3(-72.4386, 5, 0)
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/West"]
shape = SubResource("EastWestBoundsShape")
[node name="East" type="StaticBody3D" parent="WorldBounds"]
position = Vector3(72, 5, 0)
position = Vector3(96.4386, 5, 0)
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/East"]
shape = SubResource("EastWestBoundsShape")

View file

@ -51,7 +51,8 @@ func _rebuild(configuration: ShorelineRibbonConfig) -> Dictionary:
configuration.water_is_inside,
configuration.simplification_tolerance,
configuration.smoothing_iterations,
configuration.resample_spacing
configuration.resample_spacing,
configuration.corner_rounding_distance,
)
var output_directory := configuration.output_resource_path.get_base_dir()
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_directory))

View file

@ -12,3 +12,4 @@ extends Resource
@export var simplification_tolerance := -1.0
@export_range(-1, 4, 1) var smoothing_iterations := -1
@export var resample_spacing := -1.0
@export var corner_rounding_distance := -1.0

View file

@ -5,9 +5,14 @@ const WATER_PLANE_EPSILON := 0.001
const ENDPOINT_MERGE_TOLERANCE := 0.03
const LOOP_CLOSURE_TOLERANCE := 0.06
const MINIMUM_FRAGMENT_LENGTH := 2.0
const SIMPLIFICATION_TOLERANCE := 0.025
const SMOOTHING_ITERATIONS := 0
const RESAMPLE_SPACING := 0.10
const SIMPLIFICATION_TOLERANCE := 0.55
const SMOOTHING_ITERATIONS := 3
const RESAMPLE_SPACING := 0.16
const CORNER_ROUNDING_DISTANCE := 2.4
const CORNER_ROUNDING_START_DEGREES := 24.0
const CORNER_ROUNDING_FULL_DEGREES := 78.0
const MAXIMUM_JOIN_SCALE := 1.0
const RIBBON_REACH_GROWTH_PER_METER := 0.52
const RIBBON_WIDTH := 1.35
const LAND_INSET := 0.12
const SURFACE_OFFSET := 0.018
@ -21,7 +26,8 @@ static func generate(
water_is_inside: bool,
simplification_override := -1.0,
smoothing_iterations_override := -1,
resample_spacing_override := -1.0
resample_spacing_override := -1.0,
corner_rounding_override := -1.0
) -> Dictionary:
var simplification := (
SIMPLIFICATION_TOLERANCE
@ -38,6 +44,11 @@ static func generate(
if resample_spacing_override < 0.0
else resample_spacing_override
)
var corner_rounding := (
CORNER_ROUNDING_DISTANCE
if corner_rounding_override < 0.0
else corner_rounding_override
)
var segments := _extract_segments(faces, water_height, bounds)
var raw_paths := _stitch_segments(segments)
var processed_paths: Array[Dictionary] = []
@ -52,7 +63,12 @@ static func generate(
continue
var simplified := _simplify(raw_points, closed, simplification)
simplified_paths.append({"points": simplified, "closed": closed})
var smoothed := _chaikin(simplified, closed, smoothing_iterations)
var rounded := _round_sharp_corners(
simplified,
closed,
corner_rounding,
)
var smoothed := _chaikin(rounded, closed, smoothing_iterations)
var resampled := _resample(smoothed, closed, resample_spacing)
if resampled.size() < (3 if closed else 2):
continue
@ -296,6 +312,65 @@ static func _chaikin(
return result
static func _round_sharp_corners(
points: PackedVector2Array,
closed: bool,
rounding_distance: float,
) -> PackedVector2Array:
if rounding_distance <= WATER_PLANE_EPSILON or points.size() < 3:
return points
var result := PackedVector2Array()
for index: int in points.size():
if not closed and (index == 0 or index == points.size() - 1):
result.append(points[index])
continue
var previous := points[(index - 1 + points.size()) % points.size()]
var point := points[index]
var following := points[(index + 1) % points.size()]
var incoming_vector := point - previous
var outgoing_vector := following - point
var incoming_length := incoming_vector.length()
var outgoing_length := outgoing_vector.length()
if (
incoming_length <= WATER_PLANE_EPSILON
or outgoing_length <= WATER_PLANE_EPSILON
):
result.append(point)
continue
var incoming := incoming_vector / incoming_length
var outgoing := outgoing_vector / outgoing_length
var turn_degrees := rad_to_deg(
acos(clampf(incoming.dot(outgoing), -1.0, 1.0))
)
var corner_weight := smoothstep(
CORNER_ROUNDING_START_DEGREES,
CORNER_ROUNDING_FULL_DEGREES,
turn_degrees,
)
var cut_distance := minf(
rounding_distance * corner_weight,
minf(incoming_length, outgoing_length) * 0.44,
)
if cut_distance <= WATER_PLANE_EPSILON:
result.append(point)
continue
var entry := point - incoming * cut_distance
var exit := point + outgoing * cut_distance
var curve_steps := maxi(3, ceili(cut_distance / 0.22))
for step: int in curve_steps + 1:
var amount := float(step) / float(curve_steps)
var first := entry.lerp(point, amount)
var second := point.lerp(exit, amount)
var rounded_point := first.lerp(second, amount)
if (
result.is_empty()
or result[result.size() - 1].distance_to(rounded_point)
> WATER_PLANE_EPSILON
):
result.append(rounded_point)
return result
static func _resample(
points: PackedVector2Array,
closed: bool,
@ -348,10 +423,10 @@ static func _build_mesh(
else 0.0
)
var base_index := vertices.size()
var path_distance := 0.0
var water_normals := PackedVector2Array()
var join_scales := PackedFloat32Array()
var water_reaches := PackedFloat32Array()
for index: int in points.size():
if index > 0:
path_distance += points[index - 1].distance_to(points[index])
var previous := points[(index - 1 + points.size()) % points.size()]
var following := points[(index + 1) % points.size()]
if not closed:
@ -381,14 +456,37 @@ static func _build_mesh(
water_normal = -water_normal
var join_scale := minf(
1.0 / maxf(absf(water_normal.dot(outgoing_normal)), 0.55),
1.65,
MAXIMUM_JOIN_SCALE,
)
var water_reach := _safe_water_reach(
incoming,
outgoing,
water_normal,
previous.distance_to(point),
point.distance_to(following),
(RIBBON_WIDTH - LAND_INSET) * join_scale,
)
water_normals.append(water_normal)
join_scales.append(join_scale)
water_reaches.append(water_reach)
water_reaches = _smooth_water_reaches(
points,
water_reaches,
closed,
)
var path_distance := 0.0
for index: int in points.size():
if index > 0:
path_distance += points[index - 1].distance_to(points[index])
var point := points[index]
var water_normal := water_normals[index]
var join_scale := join_scales[index]
var land_point := (
point - water_normal * LAND_INSET * join_scale
)
var water_point := (
point
+ water_normal * (RIBBON_WIDTH - LAND_INSET) * join_scale
+ water_normal * water_reaches[index]
)
vertices.append(Vector3(land_point.x, water_height + SURFACE_OFFSET, land_point.y))
vertices.append(Vector3(water_point.x, water_height + SURFACE_OFFSET, water_point.y))
@ -416,6 +514,58 @@ static func _build_mesh(
return mesh
static func _safe_water_reach(
incoming: Vector2,
outgoing: Vector2,
water_normal: Vector2,
incoming_length: float,
outgoing_length: float,
desired_reach: float,
) -> float:
var turn_cross := incoming.cross(outgoing)
var water_side := incoming.cross(water_normal)
if turn_cross * water_side <= 0.0:
return desired_reach
var turn_angle := acos(clampf(incoming.dot(outgoing), -1.0, 1.0))
if turn_angle <= WATER_PLANE_EPSILON:
return desired_reach
var radius := minf(incoming_length, outgoing_length) / maxf(
2.0 * sin(turn_angle * 0.5),
WATER_PLANE_EPSILON,
)
return minf(desired_reach, maxf(radius * 0.58, 0.24))
static func _smooth_water_reaches(
points: PackedVector2Array,
reaches: PackedFloat32Array,
closed: bool,
) -> PackedFloat32Array:
var result := reaches.duplicate()
if result.size() < 2:
return result
for _pass: int in 3:
var forward_start := 0 if closed else 1
for index: int in range(forward_start, result.size()):
var previous := (index - 1 + result.size()) % result.size()
var allowed := (
result[previous]
+ points[previous].distance_to(points[index])
* RIBBON_REACH_GROWTH_PER_METER
)
result[index] = minf(result[index], allowed)
var backward_start := result.size() - 1 if closed else result.size() - 2
for index: int in range(backward_start, -1, -1):
var following := (index + 1) % result.size()
var allowed := (
result[following]
+ points[index].distance_to(points[following])
* RIBBON_REACH_GROWTH_PER_METER
)
result[index] = minf(result[index], allowed)
return result
static func _closed_path_water_side(
points: PackedVector2Array,
water_is_inside: bool,