// ╔════════════════════════════════════════════════════════════════╗ // ║ 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 NWH.DWP2.WaterObjects; using UnityEngine; #endregion namespace NWH.DWP2.WaterData { /// /// Base class for providing water surface data to WaterObjects. /// Implementations provide water height, flow velocity, and surface normal information. /// Uses a trigger collider to automatically detect and register WaterObjects that enter the water. /// Inherit from this class to create adapters for different water systems. /// public abstract class WaterDataProvider : MonoBehaviour { protected float[] _singleHeightArray; protected Vector3[] _singlePointArray; protected Collider _triggerCollider; public virtual void Awake() { _singleHeightArray = new float[1]; _singlePointArray = new Vector3[1]; _triggerCollider = GetComponent(); if (_triggerCollider == null) { // Debug.LogWarning("WaterDataProvider requires a Collider with 'Is Trigger' ticked to be present " + // "on the same GameObject to act as a trigger volume. Creating one."); _triggerCollider = gameObject.AddComponent(); _triggerCollider.isTrigger = true; ((SphereCollider)_triggerCollider).radius = 1000000f; } if (!_triggerCollider.isTrigger) { Debug.LogWarning("WaterDataProvider Collider has to have 'Is Trigger' ticked. Fixing."); _triggerCollider.isTrigger = true; } } private void OnDrawGizmos() { Gizmos.color = Color.green; Gizmos.DrawSphere(transform.position, 0.1f); } private void OnTriggerEnter(Collider other) { // Assign the current water data provider Rigidbody targetRigidbody = other.attachedRigidbody; if (targetRigidbody != null) { WaterObject[] targetWaterObjects = targetRigidbody.GetComponentsInChildren(); for (int i = 0; i < targetWaterObjects.Length; i++) { targetWaterObjects[i].OnEnterWaterDataProvider(this); } } } private void OnTriggerExit(Collider other) { // Assign the current water data provider Rigidbody targetRigidbody = other.attachedRigidbody; if (targetRigidbody != null) { WaterObject[] targetWaterObjects = targetRigidbody.GetComponentsInChildren(); for (int i = 0; i < targetWaterObjects.Length; i++) { targetWaterObjects[i].OnExitWaterDataProvider(this); } } } /// /// Does this water system support water height queries? /// /// True if it does, false if it does not. public abstract bool SupportsWaterHeightQueries(); /// /// Does this water system support water normal queries? /// /// True if it does, false if it does not. public abstract bool SupportsWaterNormalQueries(); /// /// Does this water system support water velocity queries? /// /// True if it does, false if it does not. public abstract bool SupportsWaterFlowQueries(); /// /// Returns water height at each given point. /// Override this method to provide water height data from your water system. /// /// WaterObject requesting the data. /// Position array in world coordinates. /// Water height array in world coordinates. Corresponds to positions. public virtual void GetWaterHeights(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights) { // Do nothing. This will use the initial values of water heights (0). } /// /// Returns water flow velocity at each given point. /// Override this method to provide water flow data from your water system. /// Flow should be in world space and relative to the world, not the WaterObject. /// /// WaterObject requesting the data. /// Position array in world coordinates. /// Water flow velocity array in world coordinates. Corresponds to positions. public virtual void GetWaterFlows(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterFlows) { // Do nothing. This will use the initial values of water velocities (0,0,0). } /// /// Returns water surface normals at each given point. /// Override this method to provide water normal data from your water system. /// /// WaterObject requesting the data. /// Position array in world coordinates. /// Water surface normal array in world coordinates. Corresponds to positions. public virtual void GetWaterNormals(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterNormals) { // Do nothing. This will use the initial values of water normals (0,0,0). } /// /// Queries water data based on enabled flags. /// Calls the appropriate getter methods based on which data types are requested. /// /// WaterObject requesting the data. /// Position array in world coordinates. /// Output water heights array. /// Output water flows array. /// Output water normals array. /// Should water heights be queried. /// Should water normals be queried. /// Should water flows be queried. public void GetWaterHeightsFlowsNormals(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights, ref Vector3[] waterFlows, ref Vector3[] waterNormals, bool useWaterHeight, bool useWaterNormals, bool useWaterFlow) { if (useWaterHeight) { GetWaterHeights(waterObject, ref points, ref waterHeights); } if (useWaterFlow && SupportsWaterFlowQueries()) { GetWaterFlows(waterObject, ref points, ref waterFlows); } if (useWaterNormals && SupportsWaterNormalQueries()) { GetWaterNormals(waterObject, ref points, ref waterNormals); } } /// /// Returns water height at a single point. /// Less efficient than batch queries but useful for one-off checks. /// /// WaterObject requesting the data. /// Position in world coordinates. /// Water height at the given point. public virtual float GetWaterHeightSingle(WaterObject waterObject, Vector3 point) { _singlePointArray[0] = point; GetWaterHeights(waterObject, ref _singlePointArray, ref _singleHeightArray); return _singleHeightArray[0]; } /// /// Checks if a point is underwater. /// Accounts for wave height when water normals are supported. /// /// WaterObject making the query. /// Position to check in world coordinates. /// True if the point is below the water surface. public bool PointInWater(WaterObject waterObject, Vector3 worldPoint) { return GetWaterHeight(waterObject, worldPoint) > worldPoint.y; } /// /// Returns water height at the given world position. /// /// WaterObject making the query. /// Position in world coordinates. /// Water surface height at the given point. public float GetWaterHeight(WaterObject waterObject, Vector3 worldPoint) { return GetWaterHeightSingle(waterObject, worldPoint); } } } #if UNITY_EDITOR namespace NWH.DWP2.WaterData { using NWH.NUI; using UnityEditor; /// /// Base editor for WaterDataProvider implementations. /// Draws common UI elements (capabilities, trigger volume) and provides virtual methods for derived editors. /// public class WaterDataProviderEditor : DWP2NUIEditor { public override bool OnInspectorNUI() { if (!base.OnInspectorNUI()) return false; var provider = (WaterDataProvider)target; DrawStatus(provider); DrawTriggerVolume(provider); DrawCapabilities(provider); DrawSettings(provider); drawer.EndEditor(this); return true; } /// /// Override to draw provider-specific status information. /// protected virtual void DrawStatus(WaterDataProvider provider) { } /// /// Draws the trigger volume information. /// protected virtual void DrawTriggerVolume(WaterDataProvider provider) { drawer.BeginSubsection("Trigger Volume"); var collider = provider.GetComponent(); if (collider != null) { string colliderType = collider.GetType().Name; string size = collider switch { UnityEngine.BoxCollider box => $"{box.size.x:F1} x {box.size.y:F1} x {box.size.z:F1}", UnityEngine.SphereCollider sphere => $"Radius: {sphere.radius:F1}", UnityEngine.CapsuleCollider capsule => $"Radius: {capsule.radius:F1}, Height: {capsule.height:F1}", UnityEngine.MeshCollider mesh => mesh.sharedMesh != null ? mesh.sharedMesh.name : "No mesh", _ => "" }; drawer.Info($"{colliderType}{(size.Length > 0 ? $" ({size})" : "")}"); if (!collider.isTrigger) { drawer.Info("Collider is not set as trigger!", MessageType.Warning); } } else { drawer.Info("No collider found. A large sphere trigger will be auto-created at runtime.", MessageType.Warning); } drawer.EndSubsection(); } /// /// Draws the capabilities section showing supported query types. /// protected virtual void DrawCapabilities(WaterDataProvider provider) { drawer.BeginSubsection("Capabilities"); drawer.Label($"Water Height: {(provider.SupportsWaterHeightQueries() ? "Yes" : "No")}"); drawer.Label($"Water Normal: {(provider.SupportsWaterNormalQueries() ? "Yes" : "No")}"); drawer.Label($"Water Flow: {(provider.SupportsWaterFlowQueries() ? "Yes" : "No")}"); drawer.EndSubsection(); } /// /// Override to draw provider-specific settings. /// protected virtual void DrawSettings(WaterDataProvider provider) { } public override bool UseDefaultMargins() => false; } } #endif