修改水

This commit is contained in:
2026-01-01 22:00:33 +08:00
parent 040a222bd6
commit 9ceffccd39
1800 changed files with 103929 additions and 139495 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9705fd80c227847b1b2c21279a353316
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d75412fa9c7c743ddb0bc94b3a5c7622
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5be9f1155ece744ac9b4e52aa05d7ef9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WaveHarmonic.Crest")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8af62055026f246e7a9d675f69b994d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"reference": "GUID:457756d89b35d2941b3e7b37b4ece6f1"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c358b569712814645b33be93fdc16c4d
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b91042832cea4543b4c617e5ee9501b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f9a650914f9e44519ab747c4366060de
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WaveHarmonic.Crest")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46afa615b9e4b409481f591af6c8d671
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
{
"reference": "GUID:15fc0a57446b3144c949da3e2b9737a9"
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6c91763ce81814a8cb7f57f7e18525bb
AssemblyDefinitionReferenceImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using WaveHarmonic.Crest.Utility;
namespace WaveHarmonic.Crest
{
/// <summary>
/// Overrides global quality settings.
/// </summary>
[System.Serializable]
public sealed partial class QualitySettingsOverride
{
[Tooltip("Whether to override the LOD bias.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _OverrideLodBias;
[Tooltip("Overrides the LOD bias for meshes.\n\nHighest quality is infinity.")]
[@Predicated(nameof(_OverrideLodBias))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal float _LodBias;
[Tooltip("Whether to override the maximum LOD level.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _OverrideMaximumLodLevel;
[Tooltip("Overrides the maximum LOD level.\n\nHighest quality is zero.")]
[@Predicated(nameof(_OverrideMaximumLodLevel))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal int _MaximumLodLevel;
[Tooltip("Whether to override the terrain pixel error.")]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal bool _OverrideTerrainPixelError;
[Tooltip("Overrides the pixel error value for terrains.\n\nHighest quality is zero.")]
[@Predicated(nameof(_OverrideTerrainPixelError))]
[@GenerateAPI]
[@DecoratedField, SerializeField]
internal float _TerrainPixelError;
float _OldLodBias;
int _OldMaximumLodLevelOverride;
float _OldTerrainPixelError;
TerrainQualityOverrides _OldTerrainOverrides;
internal void Override()
{
if (_OverrideLodBias)
{
_OldLodBias = QualitySettings.lodBias;
QualitySettings.lodBias = _LodBias;
}
if (_OverrideMaximumLodLevel)
{
_OldMaximumLodLevelOverride = QualitySettings.maximumLODLevel;
QualitySettings.maximumLODLevel = _MaximumLodLevel;
}
if (_OverrideTerrainPixelError)
{
_OldTerrainOverrides = QualitySettings.terrainQualityOverrides;
_OldTerrainPixelError = QualitySettings.terrainPixelError;
QualitySettings.terrainQualityOverrides = TerrainQualityOverrides.PixelError;
QualitySettings.terrainPixelError = _TerrainPixelError;
}
}
internal void Restore()
{
if (_OverrideLodBias)
{
QualitySettings.lodBias = _OldLodBias;
}
if (_OverrideMaximumLodLevel)
{
QualitySettings.maximumLODLevel = _OldMaximumLodLevelOverride;
}
if (_OverrideTerrainPixelError)
{
QualitySettings.terrainQualityOverrides = _OldTerrainOverrides;
QualitySettings.terrainPixelError = _OldTerrainPixelError;
}
}
/// <inheritdoc/>
public override int GetHashCode()
{
var hash = Hash.CreateHash();
Hash.AddBool(_OverrideLodBias, ref hash);
Hash.AddFloat(_LodBias, ref hash);
Hash.AddBool(_OverrideMaximumLodLevel, ref hash);
Hash.AddInt(_MaximumLodLevel, ref hash);
Hash.AddBool(_OverrideTerrainPixelError, ref hash);
Hash.AddFloat(_TerrainPixelError, ref hash);
return hash;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c534680c2c3c4912bfabb90bfa03574
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 37d8168c36cf74fdea3eef047313d378
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,32 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("WaveHarmonic.Crest")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Shared.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Examples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Ripples")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Submarine")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.CPUQueries.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Paint.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShallowWater.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShiftingOrigin")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.ShiftingOrigin.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Splines.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Watercraft.Editor")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool")]
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Whirlpool.Editor")]
// Define empty namespaces for when assemblies are not present.
namespace UnityEditor.SceneManagement { }
namespace UnityEngine.Rendering.HighDefinition { }
namespace UnityEngine.Rendering.Universal { }

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4f89cf7fe52246cb883953865c0ae55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c01e8ab7829a9475d86ca9975ce640aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System;
using UnityEngine;
namespace WaveHarmonic.Crest.Utility
{
/// <summary>
/// Circular buffer to store a multiple sets of data.
/// </summary>
sealed class BufferedData<T>
{
readonly T[] _Buffers;
int _CurrentFrameIndex;
public T Current { get => _Buffers[_CurrentFrameIndex]; set => _Buffers[_CurrentFrameIndex] = value; }
public int Size => _Buffers.Length;
public BufferedData(int size, Func<T> initialize)
{
_Buffers = new T[size];
for (var i = 0; i < size; i++)
{
_Buffers[i] = initialize();
}
}
public T Previous(int framesBack)
{
Debug.Assert(framesBack >= 0 && framesBack < _Buffers.Length);
return _Buffers[(_CurrentFrameIndex - framesBack + _Buffers.Length) % _Buffers.Length];
}
public void Flip()
{
_CurrentFrameIndex = (_CurrentFrameIndex + 1) % _Buffers.Length;
}
public void RunLambda(Action<T> lambda)
{
foreach (var buffer in _Buffers)
{
lambda(buffer);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c5666bcd450914e3e980a32dd866957e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections;
using System.Collections.Generic;
namespace WaveHarmonic.Crest.Utility
{
/// <summary>
/// This is a list this is meant to be similar in behaviour to the C#
/// SortedList, but without allocations when used directly in a foreach loop.
///
/// It works by using a regular list as as backing and ensuring that it is
/// sorted when the enumerator is accessed and used. This is a simple approach
/// that means we avoid sorting each time an element is added, and helps us
/// avoid having to develop our own more complex data structure.
/// </summary>
sealed class SortedList<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
{
public int Count => _BackingList.Count;
readonly List<KeyValuePair<TKey, TValue>> _BackingList = new();
readonly System.Comparison<TKey> _Comparison;
bool _NeedsSorting = false;
int Comparison(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return _Comparison(x.Key, y.Key);
}
public SortedList(System.Comparison<TKey> comparison)
{
// We provide the only constructors that SortedList provides that
// we need. We wrap the input IComparer to ensure that our backing list
// is sorted in the same way a SortedList would be with the same one.
_Comparison = comparison;
}
public void Add(TKey key, TValue value)
{
_BackingList.Add(new(key, value));
_NeedsSorting = true;
}
public bool Remove(TValue value)
{
// This remove function has a fairly high complexity, as we need to search
// the list for a matching Key-Value pair, and then remove it. However,
// for the small lists we work with this is fine, as we don't use this
// function more often. But it's worth bearing in mind if we decide to
// expand where we use this list. At that point we might need to take a
// different approach.
var removeIndex = -1;
var index = 0;
foreach (var item in _BackingList)
{
if (item.Value.Equals(value))
{
removeIndex = index;
}
index++;
}
if (removeIndex > -1)
{
// Remove method produces garbage.
_BackingList.RemoveAt(removeIndex);
}
return removeIndex > -1;
}
public void Clear()
{
_BackingList.Clear();
_NeedsSorting = false;
}
#region GetEnumerator
public List<KeyValuePair<TKey, TValue>>.Enumerator GetEnumerator()
{
ResortArrays();
return _BackingList.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
void ResortArrays()
{
if (_NeedsSorting)
{
// @GC: Allocates 112B.
_BackingList.Sort(Comparison);
}
_NeedsSorting = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f796ed8d9971b4795b28bcb1c4dc3a01
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System.Collections.Generic;
using UnityEngine;
namespace WaveHarmonic.Crest.Utility.Internal
{
/// <summary>
/// A less rigid stack implementation which is easier to use. Prevents duplicates.
/// </summary>
/// <typeparam name="T">Type to store.</typeparam>
public sealed class Stack<T>
{
readonly List<T> _Items = new();
internal Stack() { }
/// <summary>
/// Add item to the end of the stack.
/// </summary>
/// <param name="item">Item to add.</param>
public void Push(T item)
{
Debug.Assert(item != null, "Null item pushed");
// Remove any instances of item already in the stack.
Pop(item);
// Add it to the top.
_Items.Add(item);
}
/// <summary>
/// Removes all instances of item.
/// </summary>
/// <param name="item">The item to be removed.</param>
public void Pop(T item)
{
Debug.Assert(item != null, "Null item popped");
_Items.RemoveAll(candidate => candidate.Equals(item));
}
/// <summary>
/// Returns the object at the top of the Stack without removing it.
/// </summary>
/// <returns>Object at the top of the Stack.</returns>
public T Peek() => _Items[^1];
/// <summary>
/// Number of items.
/// </summary>
public int Count => _Items.Count;
internal void Clear()
{
_Items.Clear();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0c68f77a1b8b43359f34cb46ce19801
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b9f06a06d4570449fbc0c55e262f5ea9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,238 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#pragma warning disable IDE0005 // Using directive is unnecessary.
using System;
using System.Diagnostics;
using UnityEngine;
namespace WaveHarmonic.Crest
{
static class Symbols
{
public const string k_UnityEditor = "UNITY_EDITOR";
}
[Conditional(Symbols.k_UnityEditor)]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
sealed class ExecuteDuringEditMode : Attribute
{
[Flags]
public enum Include
{
None,
PrefabStage,
BuildPipeline,
All = PrefabStage | BuildPipeline,
}
[Flags]
public enum Options
{
None,
Singleton,
}
public Include _Including;
public Options _Options;
public ExecuteDuringEditMode(Include including = Include.PrefabStage, Options options = Options.None)
{
_Including = including;
_Options = options;
}
}
enum Getter
{
Default,
Custom,
}
enum Setter
{
Default,
Custom,
Internal,
Dirty,
None,
}
[Conditional(Symbols.k_UnityEditor)]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
sealed class GenerateAPI : Attribute
{
public readonly Getter _Getter;
public readonly Setter _Setter;
public readonly string _Name;
public readonly string _ScriptingSymbol;
public GenerateAPI(Getter getter = Getter.Default, Setter setter = Setter.Default, string name = null, string symbol = null)
{
_Getter = getter;
_Setter = setter;
_Name = name;
_ScriptingSymbol = symbol;
}
public GenerateAPI(Setter setter, string name = null, string symbol = null)
{
_Setter = setter;
_Name = name;
_ScriptingSymbol = symbol;
}
}
#if !UNITY_EDITOR
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
abstract class Decorator : PropertyAttribute { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Layer : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Stripped : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Delayed : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Disabled : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class AttachMaterialEditor : Decorator { }
[Conditional(Symbols.k_UnityEditor)]
sealed class Embedded : Decorator
{
public Embedded(int margin = 0) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class DecoratedField : Decorator
{
public DecoratedField(bool isCustomFoldout = false) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Group : Decorator
{
public enum Style { None, Foldout, Accordian, }
public Group(string title = null, Style style = Style.Foldout, bool isCustomFoldout = false) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Stepped : Decorator
{
public Stepped(int minimum, int maximum, int step = 1, bool power = false) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Range : Decorator
{
[Flags]
public enum Clamp { None = 0, Minimum = 1, Maximum = 2, Both = Minimum | Maximum }
public Range(float minimum, float maximum, Clamp clamp = Clamp.Both, float scale = 1f, bool delayed = false, int step = 0, bool power = false) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Minimum : Decorator
{
public Minimum(float minimum) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Maximum : Decorator
{
public Maximum(float maximum) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class WarnIfAbove : Decorator
{
public WarnIfAbove(float maximum) {}
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Filtered : Decorator
{
public enum Mode { Include, Exclude, }
public Filtered(int unset = 0) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class FilterEnum : Decorator
{
public FilterEnum(string property, Filtered.Mode mode, params int[] values) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Label : Decorator
{
public Label(string label) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Heading : Decorator
{
public enum Style { Normal, Settings, }
public Heading(string heading, Style style = Style.Normal, bool alwaysVisible = false, bool alwaysEnabled = false, string helpLink = null) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Space : Decorator
{
public Space(float height, bool isAlwaysVisible = false) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class Predicated : Decorator
{
public Predicated(Type type, string member, bool inverted = false, bool hide = false) { }
public Predicated(Type type, bool inverted = false, bool hide = false) { }
public Predicated(string property, bool inverted = false, object disableValue = null, bool hide = false) { }
public Predicated(RenderPipeline rp, bool inverted = false, bool hide = false) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class OnChange : Decorator
{
public OnChange(bool skipIfInactive = true) { }
public OnChange(Type type, bool skipIfInactive = true) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class HelpURL : Decorator
{
public HelpURL(string path = "") { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class HelpBox : Decorator
{
public enum MessageType { Info, Warning, Error, }
public enum Visibility { Always, PropertyEnabled, PropertyDisabled, }
public HelpBox(string message, MessageType messageType = MessageType.Info, Visibility visibility = Visibility.Always) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class MaterialField : Decorator
{
public MaterialField(string shader, string title = "", string name = "", string parent = null) { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class PrefabField : Decorator
{
public PrefabField(string title = "", string name = "") { }
}
[Conditional(Symbols.k_UnityEditor)]
sealed class ShowComputedProperty : Decorator
{
public ShowComputedProperty(string name) { }
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10b67a4d95adf4592abe527745be027b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using MonoBehaviour = WaveHarmonic.Crest.Internal.EditorBehaviour;
#else
using MonoBehaviour = UnityEngine.MonoBehaviour;
#endif
namespace WaveHarmonic.Crest.Internal
{
/// <summary>
/// Implements logic to smooth out Unity's wrinkles.
/// </summary>
public abstract class CustomBehaviour : MonoBehaviour
{
bool _AfterStart;
#pragma warning disable 114
private protected virtual void Awake()
{
#if UNITY_EDITOR
base.Awake();
#endif
}
/// <summary>
/// Unity's Start method. Make sure to call base if overriden.
/// </summary>
protected void Start()
{
_AfterStart = true;
#if UNITY_EDITOR
base.Start();
if (!enabled) return;
#endif
OnStart();
}
#pragma warning restore 114
/// <summary>
/// Called in OnEnable only after Start has ran.
/// </summary>
private protected virtual void Initialize()
{
}
/// <summary>
/// Replaces Start. Only called in the editor if passes validation.
/// </summary>
private protected virtual void OnStart()
{
Initialize();
}
/// <summary>
/// Unity's OnEnable method. Make sure to call base if overriden.
/// </summary>
private protected virtual void OnEnable()
{
if (!_AfterStart) return;
Initialize();
}
#if UNITY_EDITOR
[InitializeOnEnterPlayMode]
static void OnEnterPlayModeInEditor(EnterPlayModeOptions options)
{
foreach (var @object in FindObjectsByType<CustomBehaviour>(FindObjectsInactive.Include, FindObjectsSortMode.None))
{
@object._AfterStart = false;
}
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 636b808a07ad9446c84091c05f393663
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,184 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
#if UNITY_EDITOR
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
namespace WaveHarmonic.Crest.Internal
{
using Include = ExecuteDuringEditMode.Include;
/// <summary>
/// Implements custom behaviours common to all components.
/// </summary>
public abstract partial class EditorBehaviour : MonoBehaviour
{
bool _IsFirstOnValidate = true;
internal bool _IsPrefabStageInstance;
private protected virtual void Awake()
{
// When copy and pasting from one scene to another, destroy instance objects as
// they will have bad state.
foreach (var generated in transform.GetComponentsInChildren<ManagedGameObject>(includeInactive: true))
{
if (generated.Owner == this)
{
Helpers.Destroy(generated.gameObject);
}
}
}
/// <summary>
/// Start method. Must be called if overriden.
/// </summary>
protected virtual void Start()
{
if (Application.isPlaying && !(bool)s_ExecuteValidators.Invoke(null, new object[] { this }))
{
enabled = false;
}
}
// Unity does not call OnDisable/OnEnable on Reset.
private protected virtual void Reset()
{
if (!enabled) return;
enabled = false;
enabled = true;
}
/// <summary>
/// OnValidate method. Must be called if overriden.
/// </summary>
private protected virtual void OnValidate()
{
if (Application.isPlaying)
{
return;
}
if (_IsFirstOnValidate)
{
var attribute = Helpers.GetCustomAttribute<ExecuteDuringEditMode>(GetType());
var enableInEditMode = attribute != null;
if (enableInEditMode && !attribute._Including.HasFlag(Include.BuildPipeline))
{
// Do not execute when building the player.
enableInEditMode = !BuildPipeline.isBuildingPlayer;
}
// Components that use the singleton pattern are candidates for not executing in the prefab stage
// as a new instance will be created which could interfere with the scene stage instance.
if (enableInEditMode && !attribute._Including.HasFlag(Include.PrefabStage))
{
var stage = PrefabStageUtility.GetCurrentPrefabStage();
_IsPrefabStageInstance = stage != null && gameObject.scene == stage.scene;
// Do not execute in prefab stage.
enableInEditMode = !_IsPrefabStageInstance;
}
// When singleton, destroy instance objects.
if (enableInEditMode && attribute._Options.HasFlag(ExecuteDuringEditMode.Options.Singleton) &&
FindObjectsByType(GetType(), FindObjectsSortMode.None).Length > 1)
{
enableInEditMode = false;
EditorApplication.update -= InternalDestroyNonSaveables;
EditorApplication.update += InternalDestroyNonSaveables;
}
// runInEditMode will immediately call Awake and OnEnable so we must not do this in OnValidate as there
// are many restrictions which Unity will produce warnings for:
// https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnValidate.html
if (enableInEditMode)
{
if (BuildPipeline.isBuildingPlayer)
{
// EditorApplication.update and Invoke are not called when building.
InternalEnableEditMode();
}
else
{
// Called between OnAwake/OnEnable and Start which makes it seamless.
EditorApplication.update -= InternalEnableEditMode;
EditorApplication.update += InternalEnableEditMode;
}
}
}
_IsFirstOnValidate = false;
}
void InternalDestroyNonSaveables()
{
EditorApplication.update -= InternalDestroyNonSaveables;
// See comment below.
if (this == null) return;
foreach (Transform transform in transform.GetComponentInChildren<Transform>(includeInactive: true))
{
if (transform.gameObject.hideFlags.HasFlag(HideFlags.DontSaveInEditor))
{
Helpers.Destroy(transform.gameObject);
}
}
}
void InternalEnableEditMode()
{
EditorApplication.update -= InternalEnableEditMode;
// If the scene that is being built is already opened then, there can be a rogue instance which registers
// an event but is destroyed by the time it gets here. It has something to do with OnValidate being called
// after the object is destroyed with _isFirstOnValidate being true.
if (this == null) return;
// Workaround to ExecuteAlways also executing during building which is often not what we want.
runInEditMode = true;
}
static MethodInfo s_ExecuteValidators;
[InitializeOnLoadMethod]
static void Load()
{
var type = System.Type.GetType("WaveHarmonic.Crest.Editor.ValidatedHelper, WaveHarmonic.Crest.Shared.Editor");
s_ExecuteValidators = type.GetMethod
(
"ExecuteValidators",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(Object) },
null
);
}
}
}
#endif
namespace WaveHarmonic.Crest
{
// Stores a reference to the owner so the GO can be deleted safely when duplicated/pasted.
sealed class ManagedGameObject : MonoBehaviour
{
[field: SerializeField]
public Component Owner { get; set; }
}
static class Extentions
{
[System.Diagnostics.Conditional("UNITY_EDITOR")]
public static void Manage(this Component owner, GameObject @object)
{
@object.AddComponent<ManagedGameObject>().Owner = owner;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f9b0b7f0a05449e4952db2c0e3a5516
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,177 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using System;
using System.Collections.Generic;
namespace WaveHarmonic.Crest.Internal
{
/// <summary>
/// Manages ManagedBehaviours. Replaces Unity's event system.
/// </summary>
/// <typeparam name="T">The manager type.</typeparam>
public abstract class ManagerBehaviour<T> : CustomBehaviour where T : ManagerBehaviour<T>
{
internal static readonly List<Action<T>> s_OnUpdate = new();
internal static readonly List<Action<T>> s_OnLateUpdate = new();
internal static readonly List<Action<T>> s_OnFixedUpdate = new();
internal static readonly List<Action<T>> s_OnEnable = new();
internal static readonly List<Action<T>> s_OnDisable = new();
/// <summary>
/// The singleton instance.
/// </summary>
public static T Instance { get; private set; }
void Broadcast(List<Action<T>> listeners, T instance)
{
for (var i = listeners.Count - 1; i >= 0; --i)
{
listeners[i].Invoke(instance);
}
}
void Broadcast(List<Action<T>> listeners)
{
Broadcast(listeners, Instance);
}
private protected virtual void Enable()
{
// Setting up instance should be last.
Instance = (T)this;
Broadcast(s_OnEnable);
}
private protected virtual void Disable()
{
Broadcast(s_OnDisable);
Instance = null;
}
private protected virtual void FixedUpdate() => Broadcast(s_OnFixedUpdate);
private protected void BroadcastUpdate() => Broadcast(s_OnUpdate);
private protected virtual void LateUpdate() => Broadcast(s_OnLateUpdate);
// OnLoad etc cannot be used on open generic types.
internal static void AfterRuntimeLoad()
{
Instance = null;
}
internal static void AfterScriptReload()
{
Instance = FindFirstObjectByType<T>();
}
}
/// <summary>
/// A behaviour which is driven by a ManagerBehaviour instead of Unity's event system.
/// </summary>
/// <typeparam name="T">The manager type.</typeparam>
public abstract class ManagedBehaviour<T> : CustomBehaviour where T : ManagerBehaviour<T>
{
readonly Action<T> _OnUpdate;
readonly Action<T> _OnLateUpdate;
readonly Action<T> _OnFixedUpdate;
readonly Action<T> _OnEnable;
readonly Action<T> _OnDisable;
/// <summary>
/// The Update method called by the manager class.
/// </summary>
private protected virtual Action<T> OnUpdateMethod => null;
/// <summary>
/// The LateUpdate method called by the manager class.
/// </summary>
private protected virtual Action<T> OnLateUpdateMethod => null;
/// <summary>
/// The FixedUpdated method called by the manager class.
/// </summary>
private protected virtual Action<T> OnFixedUpdateMethod => null;
/// <summary>
/// The OnEnable method called by the manager class.
/// </summary>
private protected virtual Action<T> OnEnableMethod => null;
/// <summary>
/// The OnDisable method called by the manager class.
/// </summary>
private protected virtual Action<T> OnDisableMethod => null;
/// <summary>
/// Constructor which caches Actions to avoid allocations.
/// </summary>
public ManagedBehaviour()
{
if (OnUpdateMethod != null) _OnUpdate = new(OnUpdateMethod);
if (OnLateUpdateMethod != null) _OnLateUpdate = new(OnLateUpdateMethod);
if (OnFixedUpdateMethod != null) _OnFixedUpdate = new(OnFixedUpdateMethod);
if (OnEnableMethod != null) _OnEnable = new(OnEnableMethod);
if (OnDisableMethod != null) _OnDisable = new(OnDisableMethod);
}
/// <inheritdoc/>
private protected override void OnEnable()
{
base.OnEnable();
UpdateSubscription(listen: true);
// Trigger OnEnable as it has already passed.
if (_OnEnable != null && ManagerBehaviour<T>.Instance != null)
{
_OnEnable(ManagerBehaviour<T>.Instance);
}
}
/// <summary>
/// Unity's OnDisable method. Make sure to call base if overriden.
/// </summary>
private protected virtual void OnDisable()
{
UpdateSubscription(listen: false);
if (_OnDisable != null && ManagerBehaviour<T>.Instance != null)
{
_OnDisable(ManagerBehaviour<T>.Instance);
}
}
void UpdateSubscription(bool listen)
{
if (_OnUpdate != null)
{
ManagerBehaviour<T>.s_OnUpdate.Remove(_OnUpdate);
if (listen) ManagerBehaviour<T>.s_OnUpdate.Add(_OnUpdate);
}
if (_OnLateUpdate != null)
{
ManagerBehaviour<T>.s_OnLateUpdate.Remove(_OnLateUpdate);
if (listen) ManagerBehaviour<T>.s_OnLateUpdate.Add(_OnLateUpdate);
}
if (_OnFixedUpdate != null)
{
ManagerBehaviour<T>.s_OnFixedUpdate.Remove(_OnFixedUpdate);
if (listen) ManagerBehaviour<T>.s_OnFixedUpdate.Add(_OnFixedUpdate);
}
if (_OnEnable != null)
{
ManagerBehaviour<T>.s_OnEnable.Remove(_OnEnable);
if (listen) ManagerBehaviour<T>.s_OnEnable.Add(_OnEnable);
}
if (_OnDisable != null)
{
ManagerBehaviour<T>.s_OnDisable.Remove(_OnDisable);
if (listen) ManagerBehaviour<T>.s_OnDisable.Add(_OnDisable);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b697363737b2425180c036dc593512e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2119ba437f41246979c9c6b448977c10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a8125db46507e461ea5334eddf20dc1d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,107 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityHDRP
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.HighDefinition;
namespace WaveHarmonic.Crest
{
static class CustomPassHelpers
{
internal static List<CustomPassVolume> s_Volumes = new();
// Create or update Game Object.
public static GameObject CreateOrUpdate
(
Transform parent,
string name,
bool hide = true
)
{
GameObject gameObject = null;
// Find the existing custom pass volume.
// During recompiles, the reference will be lost so we need to find the game object. It could be limited to
// the editor if it is safe to do so, but there is a potential for leaking game objects.
if (gameObject == null)
{
var transform = parent.Find(name);
if (transform != null)
{
gameObject = transform.gameObject;
}
}
// Create or update the custom pass volume.
if (gameObject == null)
{
gameObject = new()
{
name = name,
hideFlags = hide ? HideFlags.HideAndDontSave : HideFlags.DontSave,
};
// Place the custom pass under the water renderer since it is easier to find later. Transform.Find can
// find inactive game objects unlike GameObject.Find.
gameObject.transform.parent = parent;
}
else
{
gameObject.hideFlags = hide ? HideFlags.HideAndDontSave : HideFlags.DontSave;
gameObject.SetActive(true);
}
return gameObject;
}
// Create or update Custom Pass Volume.
public static void CreateOrUpdate<T>
(
GameObject gameObject,
ref T pass,
string name,
CustomPassInjectionPoint injectionPoint
)
where T : CustomPass, new()
{
CustomPassVolume volume = null;
gameObject.GetComponents(s_Volumes);
foreach (var v in s_Volumes)
{
if (v.injectionPoint == injectionPoint)
{
volume = v;
break;
}
}
// Create the custom pass volume if it does not exist.
if (volume == null)
{
// It appears that this is currently the only way to add a custom pass.
volume = gameObject.AddComponent<CustomPassVolume>();
volume.injectionPoint = injectionPoint;
volume.isGlobal = true;
}
// Create custom pass.
pass ??= new()
{
name = $"Crest {name}",
targetColorBuffer = CustomPass.TargetBuffer.None,
targetDepthBuffer = CustomPass.TargetBuffer.None,
};
// Add custom pass.
if (!volume.customPasses.Contains(pass))
{
volume.customPasses.Add(pass);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03b1e50fbb96a49f1ac7de50cc1f30db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,189 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
/// <summary>
/// Unified interface for setting properties on both materials and material property blocks
/// </summary>
interface IPropertyWrapper
{
void SetFloat(int param, float value);
void SetFloatArray(int param, float[] value);
void SetVector(int param, Vector4 value);
void SetVectorArray(int param, Vector4[] value);
void SetTexture(int param, Texture value);
void SetMatrix(int param, Matrix4x4 matrix);
void SetInteger(int param, int value);
void SetBoolean(int param, bool value);
void GetBlock();
void SetBlock();
}
static class PropertyWrapperConstants
{
internal const string k_NoShaderMessage = "Cannot create required material because shader <i>{0}</i> could not be found or loaded."
+ " Try right clicking the Crest folder in the Project view and selecting Reimport, and checking for errors.";
}
readonly struct PropertyWrapperBuffer : IPropertyWrapper
{
public CommandBuffer Buffer { get; }
public PropertyWrapperBuffer(CommandBuffer mpb) => Buffer = mpb;
public void SetFloat(int param, float value) => Buffer.SetGlobalFloat(param, value);
public void SetFloatArray(int param, float[] value) => Buffer.SetGlobalFloatArray(param, value);
public void SetTexture(int param, Texture value) => Buffer.SetGlobalTexture(param, value);
public void SetVector(int param, Vector4 value) => Buffer.SetGlobalVector(param, value);
public void SetVectorArray(int param, Vector4[] value) => Buffer.SetGlobalVectorArray(param, value);
public void SetMatrix(int param, Matrix4x4 value) => Buffer.SetGlobalMatrix(param, value);
public void SetInteger(int param, int value) => Buffer.SetGlobalInteger(param, value);
public void SetBoolean(int param, bool value) => Buffer.SetGlobalInteger(param, value ? 1 : 0);
public void GetBlock() { }
public void SetBlock() { }
}
readonly struct PropertyWrapperRenderer : IPropertyWrapper
{
public MaterialPropertyBlock PropertyBlock { get; }
public Renderer Renderer { get; }
public PropertyWrapperRenderer(Renderer renderer, MaterialPropertyBlock block)
{
Renderer = renderer;
PropertyBlock = block;
}
public void SetFloat(int param, float value) => PropertyBlock.SetFloat(param, value);
public void SetFloatArray(int param, float[] value) => PropertyBlock.SetFloatArray(param, value);
public void SetTexture(int param, Texture value) => PropertyBlock.SetTexture(param, value);
public void SetBuffer(int param, ComputeBuffer value) => PropertyBlock.SetBuffer(param, value);
public void SetVector(int param, Vector4 value) => PropertyBlock.SetVector(param, value);
public void SetVectorArray(int param, Vector4[] value) => PropertyBlock.SetVectorArray(param, value);
public void SetMatrix(int param, Matrix4x4 value) => PropertyBlock.SetMatrix(param, value);
public void SetInteger(int param, int value) => PropertyBlock.SetInteger(param, value);
public void SetBoolean(int param, bool value) => PropertyBlock.SetInteger(param, value ? 1 : 0);
public void GetBlock() => Renderer.GetPropertyBlock(PropertyBlock);
public void SetBlock() => Renderer.SetPropertyBlock(PropertyBlock);
}
[System.Serializable]
readonly struct PropertyWrapperMaterial : IPropertyWrapper
{
public Material Material { get; }
public PropertyWrapperMaterial(Material material) => Material = material;
public PropertyWrapperMaterial(Shader shader)
{
Debug.Assert(shader != null, "Crest: PropertyWrapperMaterial: Cannot create required material because shader is null");
Material = new(shader);
}
public PropertyWrapperMaterial(string shaderPath)
{
var shader = Shader.Find(shaderPath);
Debug.AssertFormat(shader != null, $"Crest.PropertyWrapperMaterial: {PropertyWrapperConstants.k_NoShaderMessage}", shaderPath);
Material = new(shader);
}
public void SetFloat(int param, float value) => Material.SetFloat(param, value);
public void SetFloatArray(int param, float[] value) => Material.SetFloatArray(param, value);
public void SetTexture(int param, Texture value) => Material.SetTexture(param, value);
public void SetBuffer(int param, ComputeBuffer value) => Material.SetBuffer(param, value);
public void SetVector(int param, Vector4 value) => Material.SetVector(param, value);
public void SetVectorArray(int param, Vector4[] value) => Material.SetVectorArray(param, value);
public void SetMatrix(int param, Matrix4x4 value) => Material.SetMatrix(param, value);
public void SetInteger(int param, int value) => Material.SetInteger(param, value);
public void SetBoolean(int param, bool value) => Material.SetInteger(param, value ? 1 : 0);
public void GetBlock() { }
public void SetBlock() { }
// Non-Interface Methods
public void SetKeyword(in LocalKeyword keyword, bool value) => Material.SetKeyword(keyword, value);
}
readonly struct PropertyWrapperMPB : IPropertyWrapper
{
public MaterialPropertyBlock MaterialPropertyBlock { get; }
public PropertyWrapperMPB(MaterialPropertyBlock mpb) => MaterialPropertyBlock = mpb;
public void SetFloat(int param, float value) => MaterialPropertyBlock.SetFloat(param, value);
public void SetFloatArray(int param, float[] value) => MaterialPropertyBlock.SetFloatArray(param, value);
public void SetTexture(int param, Texture value) => MaterialPropertyBlock.SetTexture(param, value);
public void SetVector(int param, Vector4 value) => MaterialPropertyBlock.SetVector(param, value);
public void SetVectorArray(int param, Vector4[] value) => MaterialPropertyBlock.SetVectorArray(param, value);
public void SetMatrix(int param, Matrix4x4 value) => MaterialPropertyBlock.SetMatrix(param, value);
public void SetInteger(int param, int value) => MaterialPropertyBlock.SetInteger(param, value);
public void SetBoolean(int param, bool value) => MaterialPropertyBlock.SetInteger(param, value ? 1 : 0);
public void GetBlock() { }
public void SetBlock() { }
}
[System.Serializable]
readonly struct PropertyWrapperCompute : IPropertyWrapper
{
readonly CommandBuffer _Buffer;
readonly ComputeShader _Shader;
readonly int _Kernel;
public PropertyWrapperCompute(CommandBuffer buffer, ComputeShader shader, int kernel)
{
_Buffer = buffer;
_Shader = shader;
_Kernel = kernel;
}
public void SetFloat(int param, float value) => _Buffer.SetComputeFloatParam(_Shader, param, value);
public void SetFloatArray(int param, float[] value) => _Buffer.SetGlobalFloatArray(param, value);
public void SetInteger(int param, int value) => _Buffer.SetComputeIntParam(_Shader, param, value);
public void SetBoolean(int param, bool value) => _Buffer.SetComputeIntParam(_Shader, param, value ? 1 : 0);
public void SetTexture(int param, Texture value) => _Buffer.SetComputeTextureParam(_Shader, _Kernel, param, value);
public void SetTexture(int param, RenderTargetIdentifier value) => _Buffer.SetComputeTextureParam(_Shader, _Kernel, param, value);
public void SetBuffer(int param, ComputeBuffer value) => _Buffer.SetComputeBufferParam(_Shader, _Kernel, param, value);
public void SetVector(int param, Vector4 value) => _Buffer.SetComputeVectorParam(_Shader, param, value);
public void SetVectorArray(int param, Vector4[] value) => _Buffer.SetComputeVectorArrayParam(_Shader, param, value);
public void SetMatrix(int param, Matrix4x4 value) => _Buffer.SetComputeMatrixParam(_Shader, param, value);
public void GetBlock() { }
public void SetBlock() { }
// Non-Interface Methods
public void SetKeyword(in LocalKeyword keyword, bool value) => _Buffer.SetKeyword(_Shader, keyword, value);
public void Dispatch(int x, int y, int z) => _Buffer.DispatchCompute(_Shader, _Kernel, x, y, z);
}
[System.Serializable]
readonly struct PropertyWrapperComputeStandalone : IPropertyWrapper
{
readonly ComputeShader _Shader;
readonly int _Kernel;
public PropertyWrapperComputeStandalone(ComputeShader shader, int kernel)
{
_Shader = shader;
_Kernel = kernel;
}
public void SetFloat(int param, float value) => _Shader.SetFloat(param, value);
public void SetFloatArray(int param, float[] value) => _Shader.SetFloats(param, value);
public void SetInteger(int param, int value) => _Shader.SetInt(param, value);
public void SetBoolean(int param, bool value) => _Shader.SetBool(param, value);
public void SetTexture(int param, Texture value) => _Shader.SetTexture(_Kernel, param, value);
public void SetBuffer(int param, ComputeBuffer value) => _Shader.SetBuffer(_Kernel, param, value);
public void SetConstantBuffer(int param, ComputeBuffer value) => _Shader.SetConstantBuffer(param, value, 0, value.stride);
public void SetVector(int param, Vector4 value) => _Shader.SetVector(param, value);
public void SetVectorArray(int param, Vector4[] value) => _Shader.SetVectorArray(param, value);
public void SetMatrix(int param, Matrix4x4 value) => _Shader.SetMatrix(param, value);
public void GetBlock() { }
public void SetBlock() { }
// Non-Interface Methods
public void SetKeyword(in LocalKeyword keyword, bool value) => _Shader.SetKeyword(keyword, value);
public void Dispatch(int x, int y, int z) => _Shader.Dispatch(_Kernel, x, y, z);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2659ec9b44b34272a21ea388cd354d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
#if d_UnityURP
#if UNITY_6000_0_OR_NEWER
using System.Reflection;
using System.Runtime.CompilerServices;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
static class RenderGraphHelper
{
public struct Handle
{
RTHandle _RTHandle;
TextureHandle _TextureHandle;
public readonly RTHandle Texture { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _RTHandle ?? _TextureHandle; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Handle(RTHandle handle) => new() { _RTHandle = handle };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Handle(TextureHandle handle) => new() { _TextureHandle = handle };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator RTHandle(Handle texture) => texture.Texture;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator TextureHandle(Handle texture) => texture._TextureHandle;
}
static readonly FieldInfo s_RenderContext = typeof(InternalRenderGraphContext).GetField("renderContext", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo s_WrappedContext = typeof(UnsafeGraphContext).GetField("wrappedContext", BindingFlags.NonPublic | BindingFlags.Instance);
static readonly FieldInfo s_FrameData = typeof(RenderingData).GetField("frameData", BindingFlags.NonPublic | BindingFlags.Instance);
public static ScriptableRenderContext GetRenderContext(this UnsafeGraphContext unsafeContext)
{
return (ScriptableRenderContext)s_RenderContext.GetValue((InternalRenderGraphContext)s_WrappedContext.GetValue(unsafeContext));
}
public static ContextContainer GetFrameData(this ref RenderingData renderingData)
{
return (ContextContainer)s_FrameData.GetValue(renderingData);
}
}
}
#endif // UNITY_6000_0_OR_NEWER
#endif // d_UnityURP

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed778baa1b8804c5ca0074af8b68f7e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,169 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
using UnityEngine.Rendering;
namespace WaveHarmonic.Crest
{
static class RenderPipelineCompatibilityHelper
{
// Taken from:
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L588-L634
/// <summary>
/// Return true if handle does not match descriptor
/// </summary>
/// <param name="handle">RTHandle to check (can be null)</param>
/// <param name="descriptor">Descriptor for the RTHandle to match</param>
/// <param name="filterMode">Filtering mode of the RTHandle.</param>
/// <param name="wrapMode">Addressing mode of the RTHandle.</param>
/// <param name="isShadowMap">Set to true if the depth buffer should be used as a shadow map.</param>
/// <param name="anisoLevel">Anisotropic filtering level.</param>
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <param name="scaled">Check if the RTHandle has auto scaling enabled if not, check the widths and heights</param>
/// <returns></returns>
internal static bool RTHandleNeedsReAlloc(
RTHandle handle,
in RenderTextureDescriptor descriptor,
FilterMode filterMode,
TextureWrapMode wrapMode,
bool isShadowMap,
int anisoLevel,
float mipMapBias,
string name,
bool scaled)
{
if (handle == null || handle.rt == null)
return true;
if (handle.useScaling != scaled)
return true;
if (!scaled && (handle.rt.width != descriptor.width || handle.rt.height != descriptor.height))
return true;
return
handle.rt.descriptor.depthBufferBits != descriptor.depthBufferBits ||
(handle.rt.descriptor.depthBufferBits == (int)DepthBits.None && !isShadowMap && handle.rt.descriptor.graphicsFormat != descriptor.graphicsFormat) ||
handle.rt.descriptor.dimension != descriptor.dimension ||
handle.rt.descriptor.enableRandomWrite != descriptor.enableRandomWrite ||
handle.rt.descriptor.useMipMap != descriptor.useMipMap ||
handle.rt.descriptor.autoGenerateMips != descriptor.autoGenerateMips ||
handle.rt.descriptor.msaaSamples != descriptor.msaaSamples ||
handle.rt.descriptor.bindMS != descriptor.bindMS ||
handle.rt.descriptor.useDynamicScale != descriptor.useDynamicScale ||
handle.rt.descriptor.memoryless != descriptor.memoryless ||
handle.rt.filterMode != filterMode ||
handle.rt.wrapMode != wrapMode ||
handle.rt.anisoLevel != anisoLevel ||
handle.rt.mipMapBias != mipMapBias ||
handle.name != name;
}
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L666-L695
/// <summary>
/// Re-allocate fixed-size RTHandle if it is not allocated or doesn't match the descriptor
/// </summary>
/// <param name="handle">RTHandle to check (can be null)</param>
/// <param name="descriptor">Descriptor for the RTHandle to match</param>
/// <param name="filterMode">Filtering mode of the RTHandle.</param>
/// <param name="wrapMode">Addressing mode of the RTHandle.</param>
/// <param name="isShadowMap">Set to true if the depth buffer should be used as a shadow map.</param>
/// <param name="anisoLevel">Anisotropic filtering level.</param>
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <returns></returns>
public static bool ReAllocateIfNeeded(
ref RTHandle handle,
in RenderTextureDescriptor descriptor,
FilterMode filterMode = FilterMode.Point,
TextureWrapMode wrapMode = TextureWrapMode.Repeat,
bool isShadowMap = false,
int anisoLevel = 1,
float mipMapBias = 0,
string name = "")
{
if (RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, false))
{
handle?.Release();
handle = RTHandles.Alloc(descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
return false;
}
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L697-L729
/// <summary>
/// Re-allocate dynamically resized RTHandle if it is not allocated or doesn't match the descriptor
/// </summary>
/// <param name="handle">RTHandle to check (can be null)</param>
/// <param name="scaleFactor">Constant scale for the RTHandle size computation.</param>
/// <param name="descriptor">Descriptor for the RTHandle to match</param>
/// <param name="filterMode">Filtering mode of the RTHandle.</param>
/// <param name="wrapMode">Addressing mode of the RTHandle.</param>
/// <param name="isShadowMap">Set to true if the depth buffer should be used as a shadow map.</param>
/// <param name="anisoLevel">Anisotropic filtering level.</param>
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <returns>If the RTHandle should be re-allocated</returns>
public static bool ReAllocateIfNeeded(
ref RTHandle handle,
Vector2 scaleFactor,
in RenderTextureDescriptor descriptor,
FilterMode filterMode = FilterMode.Point,
TextureWrapMode wrapMode = TextureWrapMode.Repeat,
bool isShadowMap = false,
int anisoLevel = 1,
float mipMapBias = 0,
string name = "")
{
var usingConstantScale = handle != null && handle.useScaling && handle.scaleFactor == scaleFactor;
if (!usingConstantScale || RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, true))
{
handle?.Release();
handle = RTHandles.Alloc(scaleFactor, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
return false;
}
// https://github.com/Unity-Technologies/Graphics/blob/19ec161f3f752db865597374b3ad1b3eaf110097/Packages/com.unity.render-pipelines.universal/Runtime/RenderingUtils.cs#L731-L764
/// <summary>
/// Re-allocate dynamically resized RTHandle if it is not allocated or doesn't match the descriptor
/// </summary>
/// <param name="handle">RTHandle to check (can be null)</param>
/// <param name="scaleFunc">Function used for the RTHandle size computation.</param>
/// <param name="descriptor">Descriptor for the RTHandle to match</param>
/// <param name="filterMode">Filtering mode of the RTHandle.</param>
/// <param name="wrapMode">Addressing mode of the RTHandle.</param>
/// <param name="isShadowMap">Set to true if the depth buffer should be used as a shadow map.</param>
/// <param name="anisoLevel">Anisotropic filtering level.</param>
/// <param name="mipMapBias">Bias applied to mipmaps during filtering.</param>
/// <param name="name">Name of the RTHandle.</param>
/// <returns>If an allocation was done</returns>
public static bool ReAllocateIfNeeded(
ref RTHandle handle,
ScaleFunc scaleFunc,
in RenderTextureDescriptor descriptor,
FilterMode filterMode = FilterMode.Point,
TextureWrapMode wrapMode = TextureWrapMode.Repeat,
bool isShadowMap = false,
int anisoLevel = 1,
float mipMapBias = 0,
string name = "")
{
var usingScaleFunction = handle != null && handle.useScaling && handle.scaleFactor == Vector2.zero;
if (!usingScaleFunction || RTHandleNeedsReAlloc(handle, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name, true))
{
handle?.Release();
handle = RTHandles.Alloc(scaleFunc, descriptor, filterMode, wrapMode, isShadowMap, anisoLevel, mipMapBias, name);
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39dff4a7749404569b14b7395adc361a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.Rendering.Universal;
namespace WaveHarmonic.Crest
{
enum RenderPipeline
{
Legacy,
HighDefinition,
Universal,
}
sealed class RenderPipelineHelper
{
public static RenderPipeline RenderPipeline => GraphicsSettings.currentRenderPipeline switch
{
#if d_UnityHDRP
HDRenderPipelineAsset => RenderPipeline.HighDefinition,
#endif
#if d_UnityURP
UniversalRenderPipelineAsset => RenderPipeline.Universal,
#endif
_ => RenderPipeline.Legacy,
};
// GraphicsSettings.currentRenderPipeline could be from the graphics setting or current quality level.
public static bool IsLegacy => GraphicsSettings.currentRenderPipeline == null;
public static bool IsUniversal
{
get
{
#if d_UnityURP
return GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset;
#else
return false;
#endif
}
}
public static bool IsHighDefinition
{
get
{
#if d_UnityHDRP
return GraphicsSettings.currentRenderPipeline is HDRenderPipelineAsset;
#else
return false;
#endif
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00e1b88667d3544fd875d2f9184fdd78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
using UnityEngine;
namespace WaveHarmonic.Crest
{
static class TextureArrayHelpers
{
const int k_SmallTextureSize = 4;
public static Texture2D CreateTexture2D(Color color, TextureFormat format)
{
var texture = new Texture2D(k_SmallTextureSize, k_SmallTextureSize, format, false, false);
var pixels = new Color[texture.height * texture.width];
for (var i = 0; i < pixels.Length; i++)
{
pixels[i] = color;
}
texture.SetPixels(pixels);
texture.Apply();
return texture;
}
public static Texture2DArray CreateTexture2DArray(Texture2D texture, int depth)
{
var array = new Texture2DArray(
k_SmallTextureSize, k_SmallTextureSize,
depth,
texture.format,
false,
false
);
for (var textureArrayIndex = 0; textureArrayIndex < array.depth; textureArrayIndex++)
{
Graphics.CopyTexture(texture, 0, 0, array, textureArrayIndex, 0);
}
array.Apply();
return array;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18d910d25ea794953b63417c1a72d587
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
// Based on Unity's ScriptableSingleton but works with Assets and Packages
// directory. Works in builds except loading from file so it will need a
// reference in the scene for it to instantiate.
using System;
using UnityEditor;
using UnityEngine;
namespace WaveHarmonic.Crest.Utility
{
[AttributeUsage(AttributeTargets.Class)]
sealed class FilePath : Attribute
{
public readonly string _Path;
public FilePath(string path)
{
_Path = path;
}
}
abstract class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject
{
public static T Instance { get; private set; }
public ScriptableSingleton()
{
// Constructor will be called during run-time if there is a asset reference saved
// in a scene or preloaded assets.
// BUG: There appears to be a Unity bug where this will not happen which required
// recreating the asset. Perhaps after renaming the script.
Instance = this as T;
}
#if UNITY_EDITOR
static string GetFilePath()
{
foreach (var attribute in typeof(T).GetCustomAttributes(inherit: true))
{
if (attribute is FilePath f)
{
return f._Path;
}
}
return string.Empty;
}
internal static void LoadFromAsset()
{
if (Instance != null)
{
return;
}
// This will trigger the constructor and set Instance. But setting it here first
// prevents exceptions when data changes.
Instance = AssetDatabase.LoadAssetAtPath<T>(GetFilePath());
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b7f4d7de990a4a049d9aa9a442fa866
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
{
"name": "WaveHarmonic.Crest.Shared",
"rootNamespace": "",
"references": [
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
"GUID:457756d89b35d2941b3e7b37b4ece6f1",
"GUID:15fc0a57446b3144c949da3e2b9737a9"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [
"UNITY_2022_3_OR_NEWER"
],
"versionDefines": [
{
"name": "com.unity.modules.xr",
"expression": "",
"define": "d_UnityModuleVR"
},
{
"name": "com.unity.render-pipelines.high-definition",
"expression": "",
"define": "d_UnityHDRP"
},
{
"name": "com.unity.render-pipelines.universal",
"expression": "",
"define": "d_UnityURP"
}
],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 056ff2a5b2f124d468c6655552acdca5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9a1193a5fe7614861bf6312bb57afe80
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,183 @@
// Crest Water System
// Copyright © 2024 Wave Harmonic. All rights reserved.
// Adaptor layer for XR module. Could be replaced with the following one day:
// com.unity.render-pipelines.core/Runtime/Common/XRGraphics.cs
// Currently, only the horizon line uses it.
// ENABLE_VR is defined if platform support XR.
// d_UnityModuleVR is defined if VR module is installed.
// VR module depends on XR module so we only need to check the VR module.
#if ENABLE_VR && d_UnityModuleVR
#define _XR_ENABLED
#endif
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
namespace WaveHarmonic.Crest
{
static class XRHelpers
{
// NOTE: This is the same value as Unity, but in the future it could be higher.
const int k_MaximumViews = 2;
#if _XR_ENABLED
static readonly List<XRDisplaySubsystem> s_DisplayList = new();
// Unity only supports one display right now.
static XRDisplaySubsystem Display => IsRunning ? s_DisplayList[0] : null;
#endif
static Matrix4x4 LeftEyeProjectionMatrix { get; set; }
static Matrix4x4 RightEyeProjectionMatrix { get; set; }
static Matrix4x4 LeftEyeViewMatrix { get; set; }
static Matrix4x4 RightEyeViewMatrix { get; set; }
static Matrix4x4 LeftInverseViewProjectionMatrixGPU { get; set; }
static Matrix4x4 RightInverseViewProjectionMatrixGPU { get; set; }
static class ShaderIDs
{
public static readonly int s_InverseViewProjection = Shader.PropertyToID("_Crest_InverseViewProjection");
public static readonly int s_InverseViewProjectionRight = Shader.PropertyToID("_Crest_InverseViewProjectionRight");
}
internal static bool IsRunning
{
get
{
#if _XR_ENABLED
return XRSettings.enabled;
#else
return false;
#endif
}
}
public static bool IsSinglePass
{
get
{
#if _XR_ENABLED
return IsRunning && (XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.SinglePassInstanced ||
XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.SinglePassMultiview);
#else
return false;
#endif
}
}
static Texture2DArray s_WhiteTexture = null;
public static Texture2DArray WhiteTexture
{
get
{
if (s_WhiteTexture == null)
{
s_WhiteTexture = TextureArrayHelpers.CreateTexture2DArray(Texture2D.whiteTexture, k_MaximumViews);
s_WhiteTexture.name = "Crest White Texture XR";
}
return s_WhiteTexture;
}
}
public static RenderTextureDescriptor GetRenderTextureDescriptor(Camera camera)
{
#if _XR_ENABLED
if (camera.stereoEnabled)
{
return XRSettings.eyeTextureDesc;
}
else
#endif
{
// As recommended by Unity, in 2021.2 using SystemInfo.GetGraphicsFormat with DefaultFormat.LDR is
// necessary or gamma color space texture is returned:
// https://docs.unity3d.com/ScriptReference/Experimental.Rendering.DefaultFormat.html
return new(camera.pixelWidth, camera.pixelHeight, SystemInfo.GetGraphicsFormat(UnityEngine.Experimental.Rendering.DefaultFormat.LDR), 0);
}
}
static void SetViewProjectionMatrices(Camera camera, int passIndex)
{
#if _XR_ENABLED
if (!XRSettings.enabled || IsSinglePass)
{
return;
}
// Not going to use cached values here just in case.
Display.GetRenderPass(passIndex, out var xrPass);
xrPass.GetRenderParameter(camera, renderParameterIndex: 0, out var xrEye);
camera.projectionMatrix = xrEye.projection;
#endif
}
public static void UpdatePassIndex(ref int passIndex)
{
if (IsRunning)
{
#if _XR_ENABLED
if (XRSettings.stereoRenderingMode == XRSettings.StereoRenderingMode.MultiPass)
{
// Alternate between left and right eye.
passIndex += 1;
passIndex %= 2;
}
else
{
passIndex = 0;
}
#endif
}
else
{
passIndex = -1;
}
}
public static void SetInverseViewProjectionMatrix(Camera camera)
{
// Have to set these explicitly as the built-in transforms aren't in world-space for the blit function.
if (camera.stereoEnabled && IsSinglePass)
{
Shader.SetGlobalMatrix(ShaderIDs.s_InverseViewProjection, LeftInverseViewProjectionMatrixGPU);
Shader.SetGlobalMatrix(ShaderIDs.s_InverseViewProjectionRight, RightInverseViewProjectionMatrixGPU);
}
else
{
Shader.SetGlobalMatrix(ShaderIDs.s_InverseViewProjection, LeftInverseViewProjectionMatrixGPU);
}
}
public static void Update(Camera camera)
{
#if _XR_ENABLED
SubsystemManager.GetSubsystems(s_DisplayList);
#endif
if (!camera.stereoEnabled || !IsSinglePass)
{
// Built-in renderer does not provide these matrices.
LeftInverseViewProjectionMatrixGPU = (GL.GetGPUProjectionMatrix(camera.projectionMatrix, false) * camera.worldToCameraMatrix).inverse;
return;
}
#if _XR_ENABLED
// XR SPI only has one pass by definition.
Display.GetRenderPass(renderPassIndex: 0, out var xrPass);
// Grab left and right eye.
xrPass.GetRenderParameter(camera, renderParameterIndex: 0, out var xrLeftEye);
xrPass.GetRenderParameter(camera, renderParameterIndex: 1, out var xrRightEye);
// Store all the matrices.
LeftEyeViewMatrix = xrLeftEye.view;
RightEyeViewMatrix = xrRightEye.view;
LeftEyeProjectionMatrix = xrLeftEye.projection;
RightEyeProjectionMatrix = xrRightEye.projection;
LeftInverseViewProjectionMatrixGPU = (GL.GetGPUProjectionMatrix(LeftEyeProjectionMatrix, false) * LeftEyeViewMatrix).inverse;
RightInverseViewProjectionMatrixGPU = (GL.GetGPUProjectionMatrix(RightEyeProjectionMatrix, false) * RightEyeViewMatrix).inverse;
#endif
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3893707ad62c4444985948429b69651
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: