using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace StylizedWater2
{
///
/// Attached to every mesh using the Stylized Water 2 shader
/// Provides a generic way of identifying water objects and accessing their properties
///
[ExecuteInEditMode]
[AddComponentMenu("Stylized Water 2/Water Object")]
[DisallowMultipleComponent]
public class WaterObject : MonoBehaviour
{
///
/// Collection of all available WaterObject instances. Instances (un)register themselves in the OnEnable/OnDisable functions.
///
public static readonly List Instances = new List();
public Material material;
public MeshFilter meshFilter;
public MeshRenderer meshRenderer;
private static Vector3 s_PositionOffset;
private static readonly int _WaterPositionOffset = Shader.PropertyToID("_WaterPositionOffset");
///
/// For use with floating-point origin systems. In the shader, the world-position (used for UV coordinates) will be offset by this value.
/// Buoyancy calculations will also be offset to stay in sync.
///
public static Vector3 PositionOffset
{
set
{
s_PositionOffset = value;
Shader.SetGlobalVector(_WaterPositionOffset, s_PositionOffset);
}
internal get => s_PositionOffset;
}
private static float m_customTimeValue = -1f;
private static readonly int CustomTimeID = Shader.PropertyToID("_CustomTime");
///
/// Pass in any time value, any kind of animations will use this as a time index, including wave animations (and thus buoyancy calculations as well).
/// This is typically used for network synchronized waves or cutscenes.
/// To revert to using normal , pass in a value lower than 0.
///
///
public static float CustomTime
{
set
{
m_customTimeValue = value;
Shader.SetGlobalFloat(CustomTimeID, m_customTimeValue);
}
get => m_customTimeValue;
}
private MaterialPropertyBlock _props;
public MaterialPropertyBlock props
{
get
{
//Fetch when required, execution order makes it unreliable otherwise
if (_props == null)
{
CreatePropertyBlock(meshRenderer);
}
return _props;
}
private set => _props = value;
}
private void CreatePropertyBlock(Renderer sourceRenderer)
{
_props = new MaterialPropertyBlock();
sourceRenderer.GetPropertyBlock(_props);
}
private void Reset()
{
meshRenderer = GetComponent();
CreatePropertyBlock(meshRenderer);
meshFilter = GetComponent();
}
private void OnEnable()
{
Instances.Add(this);
}
private void OnDisable()
{
Instances.Remove(this);
}
private void OnValidate()
{
if (!meshRenderer) meshRenderer = GetComponent();
if (!meshFilter) meshFilter = GetComponent();
FetchWaterMaterial();
}
///
/// Grabs the material from the attached Mesh Renderer
///
public Material FetchWaterMaterial()
{
if (meshRenderer)
{
material = meshRenderer.sharedMaterial;
return material;
}
return null;
}
///
/// Applies to changes made to the Material Property Blocks ('props' property)
///
public void ApplyInstancedProperties()
{
if(props != null) meshRenderer.SetPropertyBlock(props);
}
///
/// 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 bool CanTouch(Vector3 position)
{
return Buoyancy.CanTouchWater(position, this);
}
public void AssignMesh(Mesh mesh)
{
if (meshFilter) meshFilter.sharedMesh = mesh;
}
public void AssignMaterial(Material newMaterial)
{
if (meshRenderer) meshRenderer.sharedMaterial = newMaterial;
material = newMaterial;
}
///
/// Creates a new GameObject with a MeshFilter, MeshRenderer and WaterObject component
///
/// If assigned, this material is automatically added to the MeshRenderer
///
public static WaterObject New(Material waterMaterial = null, Mesh mesh = null)
{
GameObject go = new GameObject("Water Object", typeof(MeshFilter), typeof(MeshRenderer), typeof(WaterObject));
go.layer = LayerMask.NameToLayer("Water");
#if UNITY_EDITOR
UnityEditor.Undo.RegisterCreatedObjectUndo(go, "Created Water Object");
#endif
WaterObject waterObject = go.GetComponent();
waterObject.meshRenderer = waterObject.gameObject.GetComponent();
waterObject.meshFilter = waterObject.gameObject.GetComponent();
waterObject.meshFilter.sharedMesh = mesh;
waterObject.meshRenderer.sharedMaterial = waterMaterial;
waterObject.meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
waterObject.material = waterMaterial;
return waterObject;
}
///
/// Attempt to find the WaterObject above or below the position. Checks against the bounds of ALL Water Object meshes by raycasting on the XZ plane
///
/// Position in world-space (height is not relevant)
/// Unless this is true, water rotated on the Y-axis will yield incorrect results (but is faster)
///
public static WaterObject Find(Vector3 position, bool rotationSupport)
{
Ray ray = new Ray(position + (Vector3.up * 1000f), Vector3.down);
foreach (WaterObject obj in Instances)
{
if (rotationSupport)
{
//Local space
ray.origin = obj.transform.InverseTransformPoint(ray.origin);
if (obj.meshFilter.sharedMesh.bounds.IntersectRay(ray)) return obj;
}
else
{
//Axis-aligned bounds
if (obj.meshRenderer.bounds.IntersectRay(ray)) return obj;
}
}
return null;
}
}
}