CapersProject/Assets/StylizedWater2/Runtime/Buoyancy.cs
2024-08-06 16:37:03 +09:00

297 lines
13 KiB
C#

//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;
/// <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)
{
#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
}
/// <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()
{
#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;
/// <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));
}
}
}