2023-11-13 06:48:15 +00:00
//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
2023-12-15 06:15:44 +00:00
#if UNITY_EDITOR
using UnityEditor ;
#endif
2023-11-13 06:48:15 +00:00
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
{
2023-12-15 06:15:44 +00:00
if ( WaterObject . CustomTime > = 0 ) return WaterObject . CustomTime ;
2023-11-13 06:48:15 +00:00
#if UNITY_EDITOR
return Application . isPlaying ? Time . time : Shader . GetGlobalVector ( TimeParametersID ) . x ;
#else
return Time . time ;
#endif
}
}
2023-12-15 06:15:44 +00:00
[Obsolete("Set the static 'WaterObject.CustomTime' parameter instead.", false)]
public static void SetCustomTime ( float value )
2023-11-13 06:48:15 +00:00
{
2023-12-15 06:15:44 +00:00
WaterObject . CustomTime = value ;
2023-11-13 06:48:15 +00:00
}
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 ;
/// <summary>
/// Returns a position in world-space, where a ray cast from the origin in the direction hits the (flat) water level height
/// </summary>
/// <param name="origin"></param>
/// <param name="direction"></param>
/// <param name="waterLevel">Water level height in world-space</param>
/// <returns></returns>
public static Vector3 FindWaterLevelIntersection ( Vector3 origin , Vector3 direction , float waterLevel )
{
2023-12-15 06:15:44 +00:00
#if MATHEMATICS
float upDot = dot ( direction , UnityEngine . Vector3 . up ) ;
2023-11-13 06:48:15 +00:00
float angle = ( Mathf . Acos ( upDot ) * 180f ) / Mathf . PI ;
float depth = waterLevel - origin . y ;
//Distance from origin to water level along direction
2023-12-15 06:15:44 +00:00
float hypotenuse = depth / cos ( Mathf . Deg2Rad * angle ) ;
2023-11-13 06:48:15 +00:00
return origin + ( direction * hypotenuse ) ;
2023-12-15 06:15:44 +00:00
#else
return Vector3 . zero ;
#endif
2023-11-13 06:48:15 +00:00
}
/// <summary>
/// Faux-raycast against the water surface
/// </summary>
/// <param name="waterObject">Water object component, used to get the water material and level (height)</param>
/// <param name="origin">Ray origin</param>
/// <param name="direction">Ray direction</param>
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
/// <param name="hit">Reference to a RaycastHit, hit point and normal will be set</param>
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 ( ) ;
/// <summary>
/// Faux-raycast against the water surface
/// </summary>
/// <param name="waterMat">Material using StylizedWater2 shader</param>
/// <param name="waterLevel">Height of the reference water plane.</param>
/// <param name="origin">Ray origin</param>
/// <param name="direction">Ray direction</param>
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
/// <param name="hit">Reference to a RaycastHit, hit point and normal will be set</param>
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 ;
}
/// <summary>
/// Given a position in world-space, returns the wave height and normal
/// </summary>
/// <param name="position">Sample position in world-space</param>
/// <param name="waterObject">Water object component, used to get the water material and level (height)</param>
/// <param name="rollStrength">Multiplier for the the normal strength</param>
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
/// <param name="normal">Output upwards normal vector, perpendicular to the wave</param>
/// <returns>Wave height, in world-space.</returns>
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 ( )
{
2023-12-15 06:15:44 +00:00
#if MATHEMATICS
direction1 = dir1 * waveParameters . direction ;
direction2 = dir2 * waveParameters . direction ;
2023-11-13 06:48:15 +00:00
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 ;
2023-12-15 06:15:44 +00:00
#endif
2023-11-13 06:48:15 +00:00
}
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" ) ;
2023-12-15 06:15:44 +00:00
#if MATHEMATICS
2023-11-13 06:48:15 +00:00
//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 ;
2023-12-15 06:15:44 +00:00
planarPosition . x = position . x - WaterObject . PositionOffset . x ;
planarPosition . y = position . z - WaterObject . PositionOffset . z ;
2023-11-13 06:48:15 +00:00
for ( int i = 0 ; i < = waveParameters . count ; i + + )
{
var t = 1f + ( ( float ) i / ( float ) waveParameters . count ) ;
frequency * = t ;
#if MATHEMATICS
2023-12-15 06:15:44 +00:00
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 ;
2023-11-13 06:48:15 +00:00
#endif
2023-12-15 06:15:44 +00:00
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 ) ;
2023-11-13 06:48:15 +00:00
2023-12-15 06:15:44 +00:00
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 ) ;
2023-11-13 06:48:15 +00:00
2023-12-15 06:15:44 +00:00
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 ) ) ;
2023-11-13 06:48:15 +00:00
}
rollStrength * = lerp ( 0.001f , 0.1f , waveParameters . steepness ) ;
2023-12-15 06:15:44 +00:00
2023-11-13 06:48:15 +00:00
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 ;
2023-12-15 06:15:44 +00:00
#else
offset = Vector3 . zero ;
normal = Vector3 . zero ;
#endif
2023-11-13 06:48:15 +00:00
Profiler . EndSample ( ) ;
}
private static UnityEngine . Vector3 m_offset ;
/// <summary>
/// Given a position in world-space, returns the wave height and normal
/// </summary>
/// <param name="position">Sample position in world-space</param>
/// <param name="waterMat">Material using StylizedWater2 shader</param>
/// <param name="waterLevel">Height of the reference water plane.</param>
/// <param name="rollStrength">Multiplier for the the normal strength</param>
/// <param name="dynamicMaterial">If true, the material's wave parameters will be re-fetched with every function call</param>
/// <param name="normal">Output upwards normal vector, perpendicular to the wave</param>
/// <returns>Wave height, in world-space.</returns>
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 ;
}
/// <summary>
/// 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
/// </summary>
/// <param name="position"></param>
/// <param name="waterObject"></param>
/// <returns></returns>
public static bool CanTouchWater ( Vector3 position , WaterObject waterObject )
{
if ( ! waterObject ) return false ;
return position . y < ( waterObject . transform . position . y + WaveParameters . GetMaxWaveHeight ( waterObject . material ) ) ;
}
/// <summary>
/// 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
/// </summary>
public static bool CanTouchWater ( Vector3 position , Material waterMaterial , float waterLevel )
{
return position . y < ( waterLevel + WaveParameters . GetMaxWaveHeight ( waterMaterial ) ) ;
}
}
}