// Crest Water System // Copyright © 2024 Wave Harmonic. All rights reserved. using UnityEngine; namespace WaveHarmonic.Crest { /// /// Helper to trace a ray against the water surface. /// /// /// Works by sampling at a set of points along the ray and interpolating the /// intersection location. /// public sealed class RayCastHelper : Internal.SampleHelper { readonly float _RayStepSize; readonly float _MinimumLength; /// /// The length of the ray and the step size must be given here. The smaller the step size, the greater the accuracy. /// /// Length of the ray. /// Size of the step. With length the number of steps is computed. public RayCastHelper(float rayLength, float rayStepSize = 2f) : base(ComputeQueryCount(rayLength, ref rayStepSize)) { _RayStepSize = rayStepSize; // Waves go max double along min length. Thats too much - only allow half of a wave per step. _MinimumLength = _RayStepSize * 4f; } static int ComputeQueryCount(float rayLength, ref float rayStepSize) { Debug.Assert(rayLength > 0f); Debug.Assert(rayStepSize > 0f); var stepCount = Mathf.CeilToInt(rayLength / rayStepSize) + 1; var maxStepCount = 128; if (stepCount > maxStepCount) { stepCount = maxStepCount; rayStepSize = rayLength / (stepCount - 1f); Debug.LogWarning($"Crest: RayTraceHelper: ray steps exceed maximum ({maxStepCount}), step size increased to {rayStepSize} to reduce step count."); } return stepCount; } /// /// Call this once each frame to do the query. /// /// World space position of ray origin /// World space ray direction /// The distance along the ray to the first intersection with the water surface. /// The layer this ray targets. /// True if the results have come back from the GPU, and if the ray intersects the water surface. public bool RayCast(Vector3 origin, Vector3 direction, out float distance, CollisionLayer layer = CollisionLayer.Everything) { distance = -1f; Validate(allowMultipleCallsPerFrame: false); var water = WaterRenderer.Instance; var provider = water == null ? null : water.AnimatedWavesLod.Provider; if (provider == null) return false; for (var i = 0; i < _QueryPosition.Length; i++) { _QueryPosition[i] = origin + i * _RayStepSize * direction; } var status = provider.Query(GetHashCode(), _MinimumLength, _QueryPosition, _QueryResult, null, null, layer); if (!provider.RetrieveSucceeded(status)) { return false; } // Now that data is available, compare the height of the water to the height of each point of the ray. If // the ray crosses the surface, the distance to the intersection is interpolated from the heights. for (var i = 1; i < _QueryPosition.Length; i++) { var height0 = _QueryResult[i - 1].y + water.SeaLevel - _QueryPosition[i - 1].y; var height1 = _QueryResult[i].y + water.SeaLevel - _QueryPosition[i].y; if (Mathf.Sign(height0) != Mathf.Sign(height1)) { var prop = Mathf.Abs(height0) / (Mathf.Abs(height0) + Mathf.Abs(height1)); distance = (i - 1 + prop) * _RayStepSize; break; } } return distance >= 0f; } } }