263 lines
7.1 KiB
GDScript
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()
|