114 lines
3.1 KiB
GDScript
114 lines
3.1 KiB
GDScript
class_name WaterSurfaceMotion
|
|
extends MeshInstance3D
|
|
|
|
const DEFAULT_AMPLITUDE: float = 0.055
|
|
const DEFAULT_CYCLE_SECONDS: float = 7.0
|
|
const DEFAULT_SECONDARY_SWELL: float = 0.18
|
|
const DEFAULT_PHASE_OFFSET: float = 0.0
|
|
const STABLE_SURFACE_HEIGHT_OFFSET_PARAMETER: StringName = (
|
|
&"stable_surface_height_offset"
|
|
)
|
|
|
|
@export_range(0.0, 0.2, 0.005, "suffix:m") var amplitude: float = (
|
|
DEFAULT_AMPLITUDE
|
|
)
|
|
@export_range(1.0, 30.0, 0.1, "suffix:s") var cycle_seconds: float = (
|
|
DEFAULT_CYCLE_SECONDS
|
|
)
|
|
@export_range(0.0, 0.5, 0.01) var secondary_swell: float = (
|
|
DEFAULT_SECONDARY_SWELL
|
|
)
|
|
@export_range(0.0, TAU, 0.01, "radians") var phase_offset: float = 0.0
|
|
|
|
var _base_height: float
|
|
var _motion_strength_multiplier: float = 1.0
|
|
|
|
|
|
func _ready() -> void:
|
|
_base_height = position.y
|
|
_apply_height_offset(0.0)
|
|
set_process(amplitude > 0.0)
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
_apply_height_offset(
|
|
calculate_height_offset(
|
|
_current_time_seconds(),
|
|
get_effective_amplitude(),
|
|
cycle_seconds,
|
|
secondary_swell,
|
|
phase_offset,
|
|
)
|
|
)
|
|
|
|
|
|
func set_motion_enabled(enabled: bool) -> void:
|
|
set_process(enabled and amplitude > 0.0)
|
|
if not enabled:
|
|
_apply_height_offset(0.0)
|
|
|
|
|
|
func set_motion_strength_multiplier(multiplier: float) -> void:
|
|
_motion_strength_multiplier = maxf(multiplier, 0.0)
|
|
if _motion_strength_multiplier <= 0.0:
|
|
_apply_height_offset(0.0)
|
|
|
|
|
|
func get_motion_strength_multiplier() -> float:
|
|
return _motion_strength_multiplier
|
|
|
|
|
|
func get_effective_amplitude() -> float:
|
|
return amplitude * _motion_strength_multiplier
|
|
|
|
|
|
func _apply_height_offset(height_offset: float) -> void:
|
|
position.y = _base_height + height_offset
|
|
# Headless worlds have no visible shoreline to correct. Skip the visual
|
|
# material update there and keep dedicated/headless worlds presentation-free.
|
|
if DisplayServer.get_name() == "headless":
|
|
return
|
|
# The surface can still bob visually, but all shoreline shader thresholds
|
|
# should remain at the authored waterline. Only the active ocean uses this
|
|
# moving material, so publishing the offset on that material is sufficient.
|
|
var shader_material := material_override as ShaderMaterial
|
|
if shader_material == null:
|
|
shader_material = get_active_material(0) as ShaderMaterial
|
|
if shader_material != null:
|
|
shader_material.set_shader_parameter(
|
|
STABLE_SURFACE_HEIGHT_OFFSET_PARAMETER,
|
|
-height_offset
|
|
)
|
|
|
|
|
|
static func get_default_height_offset() -> float:
|
|
return calculate_height_offset(
|
|
_current_time_seconds(),
|
|
DEFAULT_AMPLITUDE,
|
|
DEFAULT_CYCLE_SECONDS,
|
|
DEFAULT_SECONDARY_SWELL,
|
|
DEFAULT_PHASE_OFFSET,
|
|
)
|
|
|
|
|
|
static func calculate_height_offset(
|
|
time_seconds: float,
|
|
motion_amplitude: float,
|
|
motion_cycle_seconds: float,
|
|
motion_secondary_swell: float,
|
|
motion_phase_offset: float,
|
|
) -> float:
|
|
var safe_cycle := maxf(motion_cycle_seconds, 0.001)
|
|
var phase := (
|
|
fposmod(time_seconds, safe_cycle) / safe_cycle * TAU
|
|
+ motion_phase_offset
|
|
)
|
|
var swell := (
|
|
sin(phase)
|
|
+ sin(phase * 2.0 + 1.1) * motion_secondary_swell
|
|
) / (1.0 + motion_secondary_swell)
|
|
return swell * motion_amplitude
|
|
|
|
|
|
static func _current_time_seconds() -> float:
|
|
return float(Time.get_ticks_msec()) * 0.001
|