Files
frog/scripts/water_surface_generator.gd
2026-09-03 09:43:13 +02:00

263 lines
7.1 KiB
GDScript

@tool
extends Node
class_name WaterSurfaceGenerator
@export var river_material: ShaderMaterial
@export var path: Path3D:
set(value):
_path = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _path
@export var width: float = 5.0:
set(value):
_width = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _width
@export var steps: int = 30:
set(value):
_steps = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _steps
@export var smoothness: float = 0.5:
set(value):
_smoothness = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _smoothness
@export var path_rotation_accurate: bool = true:
set(value):
_path_rotation_accurate = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _path_rotation_accurate
@export var double_sided: bool = false:
set(value):
_double_sided = value
if Engine.is_editor_hint():
_update_mesh()
get:
return _double_sided
@export var width_subdivisions: int = 0:
set(value):
_width_subdivisions = max(0, value)
if Engine.is_editor_hint():
_update_mesh()
get:
return _width_subdivisions
var _path: Path3D
var _width: float = 5.0
var _steps: int = 30
var _smoothness: float = 0.5
var _path_rotation_accurate: bool = true
var _double_sided: bool = true
var _width_subdivisions: int = 0
var _mesh_instance: MeshInstance3D
var _curve: Curve3D
func _ready():
_mesh_instance = MeshInstance3D.new()
_mesh_instance.visible = true
add_child(_mesh_instance)
_mesh_instance.owner = owner if owner else self
call_deferred("_update_mesh")
func _process(_delta):
if Engine.is_editor_hint():
_check_curve_changes()
func _check_curve_changes():
if not _path:
return
var new_curve = _path.curve
if new_curve != _curve:
_curve = new_curve
_update_mesh()
func _update_mesh():
if not _mesh_instance:
return
if not _path:
return
if not _path.curve:
return
if _path.curve.point_count < 2:
return
var mesh = _generate_river_mesh(_path.curve, _width, _steps, _smoothness, _path_rotation_accurate, _width_subdivisions)
if mesh:
_post_process_tangents(mesh, _path.curve, _path_rotation_accurate, _width_subdivisions)
_mesh_instance.mesh = mesh
if river_material:
river_material.set_shader_parameter("double_sided", _double_sided)
_mesh_instance.material_override = river_material
else:
var material = StandardMaterial3D.new()
material.cull_mode = BaseMaterial3D.CULL_DISABLED if _double_sided else BaseMaterial3D.CULL_BACK
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
material.albedo_color = Color(0.5, 0.7, 1.0, 0.5)
_mesh_instance.material_override = material
else:
_mesh_instance.mesh = null
func _post_process_tangents(mesh: ArrayMesh, curve: Curve3D, path_rotation_accurate: bool, width_subdivisions: int):
if not mesh or mesh.get_surface_count() == 0:
return
var arrays = mesh.surface_get_arrays(0)
var vertices = arrays[ArrayMesh.ARRAY_VERTEX]
var tangents = arrays[ArrayMesh.ARRAY_TANGENT]
var curve_length = curve.get_baked_length()
var closed_loop = curve.closed
var vertices_per_segment = width_subdivisions + 2
var vertex_count = vertices.size() / vertices_per_segment
var actual_steps = vertex_count - 1 if not closed_loop else vertex_count
var curve_tangents = []
for i in range(vertex_count):
var t = float(i) / float(actual_steps)
var offset = t * curve_length
var forward_vector: Vector3
if path_rotation_accurate:
if curve.has_method("sample_baked_tangent"):
forward_vector = curve.sample_baked_tangent(offset).normalized()
else:
var t_forward = min(t + 0.01, 1.0)
var t_backward = max(t - 0.01, 0.0)
var pos_forward = curve.sample_baked(t_forward * curve_length)
var pos_backward = curve.sample_baked(t_backward * curve_length)
forward_vector = (pos_forward - pos_backward).normalized()
else:
var t_forward = min(t + 0.01, 1.0)
var t_backward = max(t - 0.01, 0.0)
var pos_forward = curve.sample_baked(t_forward * curve_length)
var pos_backward = curve.sample_baked(t_backward * curve_length)
forward_vector = (pos_forward - pos_backward).normalized()
curve_tangents.append(forward_vector)
for i in range(vertex_count):
var tangent = curve_tangents[i]
for j in range(vertices_per_segment):
var vertex_idx = i * vertices_per_segment + j
var t_idx = vertex_idx * 4
tangents[t_idx] = tangent.x
tangents[t_idx + 1] = tangent.y
tangents[t_idx + 2] = tangent.z
tangents[t_idx + 3] = 1.0
arrays[ArrayMesh.ARRAY_TANGENT] = tangents
mesh.clear_surfaces()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
static func _generate_river_mesh(curve: Curve3D, width: float, steps: int, smoothness: float, path_rotation_accurate: bool, width_subdivisions: int = 0) -> ArrayMesh:
if not curve or curve.point_count < 2:
return null
if curve.bake_interval == 0:
curve.bake_interval = 0.1
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
var curve_length = curve.get_baked_length()
var subdivisions = int(smoothness * 4.0)
var actual_steps = steps * (subdivisions + 1)
var vertex_count = actual_steps + 1
var first_tangent: Vector3
var closed_loop = curve.closed
for i in range(vertex_count):
var t = float(i) / float(actual_steps)
var offset = t * curve_length
var position = curve.sample_baked(offset)
var forward_vector: Vector3
if path_rotation_accurate:
if curve.has_method("sample_baked_tangent"):
forward_vector = curve.sample_baked_tangent(offset).normalized()
else:
var t_forward = min(t + 0.01, 1.0)
var t_backward = max(t - 0.01, 0.0)
var pos_forward = curve.sample_baked(t_forward * curve_length)
var pos_backward = curve.sample_baked(t_backward * curve_length)
forward_vector = (pos_forward - pos_backward).normalized()
else:
var t_forward = min(t + 0.01, 1.0)
var t_backward = max(t - 0.01, 0.0)
var pos_forward = curve.sample_baked(t_forward * curve_length)
var pos_backward = curve.sample_baked(t_backward * curve_length)
forward_vector = (pos_forward - pos_backward).normalized()
if i == 0:
first_tangent = forward_vector
elif closed_loop and i == vertex_count - 1:
forward_vector = first_tangent
var right_vector = forward_vector.cross(Vector3.UP).normalized()
var uv_t = offset / curve_length
var vertices_per_segment = width_subdivisions + 2
for j in range(vertices_per_segment):
var width_t = float(j) / float(width_subdivisions + 1)
var vertex_pos = position - right_vector * width * 0.5 + right_vector * width * width_t
st.set_normal(Vector3.UP)
st.set_uv(Vector2(uv_t, width_t))
st.add_vertex(vertex_pos)
var triangle_count = vertex_count - 1
var vertices_per_segment = width_subdivisions + 2
for i in range(triangle_count):
var base_idx = i * vertices_per_segment
for j in range(width_subdivisions + 1):
var v0 = base_idx + j
var v1 = base_idx + j + 1
var v2 = base_idx + vertices_per_segment + j
var v3 = base_idx + vertices_per_segment + j + 1
st.add_index(v0)
st.add_index(v2)
st.add_index(v1)
st.add_index(v1)
st.add_index(v2)
st.add_index(v3)
st.generate_tangents()
return st.commit()