//Stylized Water 2 //Staggart Creations (http://staggart.xyz) //Copyright protected under Unity Asset Store EULA //#undef MATHEMATICS using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; #if MATHEMATICS using Unity.Mathematics; using static Unity.Mathematics.math; using Vector4 = Unity.Mathematics.float4; using Vector3 = Unity.Mathematics.float3; using Vector2 = Unity.Mathematics.float2; #endif #if UNITY_EDITOR using UnityEditor; #endif namespace StylizedWater2 { public static partial class Buoyancy { private static WaveParameters waveParameters = new WaveParameters(); private static Material lastMaterial; private static readonly int TimeParametersID = Shader.PropertyToID("_TimeParameters"); private static void GetMaterialParameters(Material mat) { waveParameters.Update(mat); } //Returns the same value as _TimeParameters.x private static float _TimeParameters { get { if (WaterObject.CustomTime > 0) return WaterObject.CustomTime; #if UNITY_EDITOR return Application.isPlaying ? Time.time : Shader.GetGlobalVector(TimeParametersID).x; #else return Time.time; #endif } } [Obsolete("Set the static 'WaterObject.CustomTime' parameter instead.", false)] public static void SetCustomTime(float value) { WaterObject.CustomTime = value; } private static Vector4 sine; private static Vector4 cosine; private static Vector4 dotABCD; private static Vector4 AB; private static Vector4 CD; private static Vector4 direction1; private static Vector4 direction2; private static Vector4 TIME; private static Vector2 planarPosition; private static Vector4 amp = new Vector4(0.3f, 0.35f, 0.25f, 0.25f); private static Vector4 freq = new Vector4(1.3f, 1.35f, 1.25f, 1.25f); private static Vector4 speed = new Vector4(1.2f, 1.375f, 1.1f, 1); private static Vector4 dir1 = new Vector4(0.3f, 0.85f, 0.85f, 0.25f); private static Vector4 dir2 = new Vector4(0.1f, 0.9f, -0.5f, -0.5f); private static Vector4 steepness = new Vector4(12f,12f,12f,12f); //Real frequency value per wave layer private static Vector4 frequency; //Output private static Vector3 offsets; /// /// Returns a position in world-space, where a ray cast from the origin in the direction hits the (flat) water level height /// /// /// /// Water level height in world-space /// public static Vector3 FindWaterLevelIntersection(Vector3 origin, Vector3 direction, float waterLevel) { #if MATHEMATICS float upDot = dot(direction, UnityEngine.Vector3.up); float angle = (Mathf.Acos(upDot) * 180f) / Mathf.PI; float depth = waterLevel - origin.y; //Distance from origin to water level along direction float hypotenuse = depth / cos(Mathf.Deg2Rad * angle); return origin + (direction * hypotenuse); #else return Vector3.zero; #endif } /// /// Faux-raycast against the water surface /// /// Water object component, used to get the water material and level (height) /// Ray origin /// Ray direction /// If true, the material's wave parameters will be re-fetched with every function call /// Reference to a RaycastHit, hit point and normal will be set public static void Raycast(WaterObject waterObject, Vector3 origin, Vector3 direction, bool dynamicMaterial, out RaycastHit hit) { Raycast(waterObject.material, waterObject.transform.position.y, origin, direction, dynamicMaterial, out hit); } private static RaycastHit hit = new RaycastHit(); /// /// Faux-raycast against the water surface /// /// Material using StylizedWater2 shader /// Height of the reference water plane. /// Ray origin /// Ray direction /// If true, the material's wave parameters will be re-fetched with every function call /// Reference to a RaycastHit, hit point and normal will be set public static void Raycast(Material waterMat, float waterLevel, Vector3 origin, Vector3 direction, bool dynamicMaterial, out RaycastHit hit) { Vector3 samplePos = FindWaterLevelIntersection(origin, direction, waterLevel); float waveHeight = SampleWaves(samplePos, waterMat, waterLevel, 1f, dynamicMaterial, out var normal); samplePos.y = waveHeight; hit = Buoyancy.hit; hit.normal = normal; hit.point = samplePos; } /// /// Given a position in world-space, returns the wave height and normal /// /// Sample position in world-space /// Water object component, used to get the water material and level (height) /// Multiplier for the the normal strength /// If true, the material's wave parameters will be re-fetched with every function call /// Output upwards normal vector, perpendicular to the wave /// Wave height, in world-space. public static float SampleWaves(UnityEngine.Vector3 position, WaterObject waterObject, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 normal) { return SampleWaves(position, waterObject.material, waterObject.transform.position.y, rollStrength, dynamicMaterial, out normal); } private static void RecalculateParameters() { #if MATHEMATICS direction1 = dir1 * waveParameters.direction; direction2 = dir2 * waveParameters.direction; frequency = freq * (1-waveParameters.distance) * 3f; AB.x = steepness.x * waveParameters.steepness * direction1.x * amp.x; AB.y = steepness.x * waveParameters.steepness * direction1.y * amp.x; AB.z = steepness.x * waveParameters.steepness * direction1.z * amp.y; AB.w = steepness.x * waveParameters.steepness * direction1.w * amp.y; CD.x = steepness.z * waveParameters.steepness * direction2.x * amp.z; CD.y = steepness.z * waveParameters.steepness * direction2.y * amp.z; CD.z = steepness.w * waveParameters.steepness * direction2.z * amp.w; CD.w = steepness.w * waveParameters.steepness * direction2.w * amp.w; #endif } private static void SampleWaves(UnityEngine.Vector3 position, Material waterMat, float waterLevel, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 offset, out UnityEngine.Vector3 normal) { Profiler.BeginSample("Buoyancy sampling"); #if MATHEMATICS //If not desired to re-fetch the material properties every call, at least fetch them if the input material changed (since this is a static function) //In edit-mode, always do this as materials are most likely modified then if(!dynamicMaterial && Application.isPlaying) { //Fetch the material's wave parameters, so the exact calculations can be mirrored if (lastMaterial == null || lastMaterial.Equals(waterMat) == false) { #if SWS_DEV Debug.Log("SampleWaves: water material changed, re-fetching parameters"); #endif GetMaterialParameters(waterMat); lastMaterial = waterMat; } } else { GetMaterialParameters(waterMat); } TIME = (_TimeParameters * -waveParameters.animationSpeed * waveParameters.speed * speed); RecalculateParameters(); offsets = Vector3.zero; planarPosition.x = position.x - WaterObject.PositionOffset.x; planarPosition.y = position.z - WaterObject.PositionOffset.z; for (int i = 0; i <= waveParameters.count; i++) { var t = 1f+((float)i / (float)waveParameters.count); frequency *= t; #if MATHEMATICS dotABCD.x = dot(direction1.xy, planarPosition) * frequency.x; dotABCD.y = dot(direction1.zw, planarPosition) * frequency.y; dotABCD.z = dot(direction2.xy, planarPosition) * frequency.z; dotABCD.w = dot(direction2.zw, planarPosition) * frequency.w; #endif sine.x = sin(dotABCD.x + TIME.x); sine.y = sin(dotABCD.y + TIME.y); sine.z = sin(dotABCD.z + TIME.z); sine.w = sin(dotABCD.w + TIME.w); cosine.x = cos(dotABCD.x + TIME.x); cosine.y = cos(dotABCD.y + TIME.y); cosine.z = cos(dotABCD.z + TIME.z); cosine.w = cos(dotABCD.w + TIME.w); offsets.x += dot(cosine, new Vector4(AB.x, AB.z, CD.x, CD.z)); offsets.y += dot(sine, amp); offsets.z += dot(cosine, new Vector4(AB.y, AB.w, CD.y, CD.w)); } rollStrength *= lerp(0.001f, 0.1f, waveParameters.steepness); normal.x = -offsets.x * rollStrength * waveParameters.height; normal.y = 2f; normal.z = -offsets.z * rollStrength * waveParameters.height; normal = normalize(normal); //Average height offsets.y /= waveParameters.count; offsets.y = (offsets.y* waveParameters.height) + waterLevel; offset = offsets; #else offset = Vector3.zero; normal = Vector3.zero; #endif Profiler.EndSample(); } private static UnityEngine.Vector3 m_offset; /// /// Given a position in world-space, returns the wave height and normal /// /// Sample position in world-space /// Material using StylizedWater2 shader /// Height of the reference water plane. /// Multiplier for the the normal strength /// If true, the material's wave parameters will be re-fetched with every function call /// Output upwards normal vector, perpendicular to the wave /// Wave height, in world-space. public static float SampleWaves(UnityEngine.Vector3 position, Material waterMat, float waterLevel, float rollStrength, bool dynamicMaterial, out UnityEngine.Vector3 normal) { SampleWaves(position, waterMat, waterLevel, rollStrength, dynamicMaterial, out m_offset, out normal); return m_offset.y; } /// /// Checks if the position is below the maximum possible wave height. Can be used as a fast broad-phase check, before actually using the more expensive SampleWaves function /// /// /// /// public static bool CanTouchWater(Vector3 position, WaterObject waterObject) { if (!waterObject) return false; return position.y < (waterObject.transform.position.y + WaveParameters.GetMaxWaveHeight(waterObject.material)); } /// /// Checks if the position is below the maximum possible wave height. Can be used as a fast broad-phase check, before actually using the more expensive SampleWaves function /// public static bool CanTouchWater(Vector3 position, Material waterMaterial, float waterLevel) { return position.y < (waterLevel + WaveParameters.GetMaxWaveHeight(waterMaterial)); } } }