// ╔════════════════════════════════════════════════════════════════╗ // ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║ // ║ Licensed under Unity Asset Store Terms of Service: ║ // ║ https://unity.com/legal/as-terms ║ // ║ Use permitted only in compliance with the License. ║ // ║ Distributed "AS IS", without warranty of any kind. ║ // ╚════════════════════════════════════════════════════════════════╝ #region using System.Collections; using UnityEngine; using Random = UnityEngine.Random; #if UNITY_EDITOR using NWH.NUI; using UnityEditor; #endif #endregion namespace NWH.DWP2.SailController { /// /// Generates procedural wind with randomized gusts for sail simulation. /// Creates realistic wind conditions by varying speed and direction over time. /// Provides a singleton instance accessible to all sail and wind-related components. /// Wind direction uses world coordinates where 0 degrees points along the positive Z-axis. /// Only one WindGenerator should exist per scene. /// public class WindGenerator : MonoBehaviour { /// /// Singleton instance accessible globally. /// Used by SailController and HullWindApplicator to get current wind conditions. /// public static WindGenerator Instance; /// /// Primary wind direction in degrees on the horizontal plane. /// 0 degrees corresponds to positive Z-axis (north in Unity coordinates). /// Rotation follows the right-hand rule around the Y-axis. /// Wind will vary around this base direction within the limits of maxDirectionVariation. /// [Tooltip("Base wind direction in degrees with 0 degrees indicating Z-forward ('north').")] public float baseDirection; /// /// Average wind speed in meters per second. /// Wind will vary around this base speed within the limits of maxSpeedVariation. /// Typical sailing winds range from 5 m/s (light breeze) to 20 m/s (strong wind). /// [Tooltip("Base wind speed in m/s.")] public float baseSpeed = 10.0f; /// /// Maximum angular deviation from baseDirection during wind gusts. /// Measured in degrees. /// Higher values create more unpredictable wind shifts. /// Realistic values range from 15 to 45 degrees. /// [Tooltip("Maximum possible variation of the direction in degrees from the\r\nbaseDirection.")] public float maxDirectionVariation = 30f; /// /// Maximum speed deviation from baseSpeed during wind gusts. /// Measured in meters per second. /// Higher values create stronger variations between lulls and gusts. /// Can be positive or negative relative to base speed. /// [Tooltip("Maximum possible variation/deviation of the wind from the baseSpeed.")] public float maxSpeedVariation = 5f; /// /// Longest possible duration between wind condition changes. /// Measured in seconds. /// New wind conditions are selected randomly between minVariationInterval and this value. /// [Tooltip("Maximum interval between the wind variations / changes.")] public float maxVariationInterval = 6.0f; /// /// Shortest possible duration between wind condition changes. /// Measured in seconds. /// New wind conditions are selected randomly between this value and maxVariationInterval. /// [Tooltip("Minimum interval between the wind variations / changes.")] public float minVariationInterval = 2.0f; private float _currentInterval = 1f; private float _smoothingDirectionVelocity; private float _smoothingSpeedVelocity; private float _targetDirection; private float _targetSpeed; /// /// Current wind vector in world space. /// Combines CurrentDirection and CurrentSpeed into a directional velocity vector. /// Updated every FixedUpdate with smooth damping between gust transitions. /// Measured in meters per second. /// public Vector3 CurrentWind { get; private set; } /// /// Current wind direction in degrees on the horizontal plane. /// Smoothly interpolates between randomized target directions. /// 0 degrees corresponds to positive Z-axis. /// public float CurrentDirection { get; private set; } /// /// Current wind speed magnitude in meters per second. /// Smoothly interpolates between randomized target speeds. /// public float CurrentSpeed { get; private set; } private void Awake() { if (Instance != null) { Debug.LogWarning("The scene has more than one WindGenerator. The previous one(s) will be ignored."); } Instance = this; } private void Start() { StartCoroutine(GustCoroutine()); } private void FixedUpdate() { CurrentDirection = Mathf.SmoothDamp(CurrentDirection, _targetDirection, ref _smoothingDirectionVelocity, _currentInterval); CurrentSpeed = Mathf.SmoothDamp(CurrentSpeed, _targetSpeed, ref _smoothingSpeedVelocity, _currentInterval); CurrentWind = Quaternion.AngleAxis(CurrentDirection, Vector3.up) * Vector3.forward * CurrentSpeed; } private IEnumerator GustCoroutine() { while (true) { _targetSpeed = baseSpeed + Random.Range(-maxSpeedVariation, maxSpeedVariation); _targetDirection = baseDirection + Random.Range(-maxDirectionVariation, maxDirectionVariation); _currentInterval = Random.Range(minVariationInterval, maxVariationInterval); yield return new WaitForSeconds(_currentInterval); } } } } #if UNITY_EDITOR namespace NWH.DWP2.SailController { [CustomEditor(typeof(WindGenerator))] [CanEditMultipleObjects] public class WindGeneratorEditor : DWP2NUIEditor { public override bool OnInspectorNUI() { if (!base.OnInspectorNUI()) { return false; } WindGenerator windGenerator = (WindGenerator)target; if (Application.isPlaying) { drawer.Label($"Current Wind: {windGenerator.CurrentSpeed:0.0} from {windGenerator.CurrentDirection:0}"); } drawer.BeginSubsection("Base"); drawer.Field("baseSpeed"); drawer.Field("baseDirection"); drawer.EndSubsection(); drawer.BeginSubsection("Variation"); drawer.Field("maxSpeedVariation"); drawer.Field("maxDirectionVariation"); drawer.Field("minVariationInterval"); drawer.Field("maxVariationInterval"); drawer.EndSubsection(); drawer.EndEditor(this); return true; } } } #endif