'push'
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1db68a6c1808a4d4d9099ac4c9f9812c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abe8bdec24030408b8a545ae03e751f6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8b444bc1fb43438aadcfff26a9342e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Shared.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebc89e55d7e224f58b73430f7f836895
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:3eae0364be2026648bf74846acb8a731"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5373b95f6a6234bd9acf598c2856b1c9
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9efee046059b1440a932e76cb96a30d0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 645345341492f4e7daaf6a332bf6d944
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89ed50bd0e0cc4e0593b1eb86b22e426
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:c579267770062bf448e75eb160330b7f"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4e50428db6234431b8ab6d9fe9af9ad
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab8bbb10bd06a476aa3c33abddbdd42d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abd60842ea94549cbbde56358927ae18
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Shared.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88af6858f7fbe4851ae792f703289573
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reference": "GUID:be0903cd8e1546f498710afdc59db5eb"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1d2e8dc2707d4ea38f4e65cafa7ab83
|
||||
AssemblyDefinitionReferenceImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2df0bac542844fa0a8e4ad57f8e16a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.Samples")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Editor")]
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Samples.Examples")]
|
||||
[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")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 959b22cf6debd4b5398062eb810543b2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70a73c2fbd8a142b3956c49e19e17c31
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d4feed62e65f34662aea25208d1ce0bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Embedded : DecoratedProperty
|
||||
{
|
||||
internal EmbeddedAssetEditor _Editor;
|
||||
public int BottomMargin { get; private set; }
|
||||
public string DefaultPropertyName { get; private set; }
|
||||
|
||||
public Embedded(int margin = 0, string defaultPropertyName = null)
|
||||
{
|
||||
_Editor = new();
|
||||
BottomMargin = margin;
|
||||
DefaultPropertyName = defaultPropertyName;
|
||||
}
|
||||
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
_Editor.DrawEditorCombo(this, label, drawer, property, "asset");
|
||||
}
|
||||
|
||||
internal override bool NeedsControlRectangle(SerializedProperty property) => false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c024c6def66044f1cb4783f23a050bec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,72 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class HelpBox : Decorator
|
||||
{
|
||||
// Define our own as Unity's won't be available in builds.
|
||||
public enum MessageType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
public string _Message;
|
||||
public MessageType _MessageType;
|
||||
public Visibility _Visibility;
|
||||
|
||||
public enum Visibility
|
||||
{
|
||||
Always,
|
||||
PropertyEnabled,
|
||||
PropertyDisabled,
|
||||
}
|
||||
|
||||
readonly GUIContent _GuiContent;
|
||||
|
||||
public override bool AlwaysVisible => false;
|
||||
|
||||
public HelpBox(string message, MessageType messageType = MessageType.Info, Visibility visibility = Visibility.Always)
|
||||
{
|
||||
_Message = message;
|
||||
_MessageType = messageType;
|
||||
_Visibility = visibility;
|
||||
_GuiContent = new(message);
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
if (_Visibility == Visibility.PropertyEnabled && !GUI.enabled || _Visibility == Visibility.PropertyDisabled && GUI.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable rich text in help boxes. Store original so we can revert since this might be a "hack".
|
||||
var style = GUI.skin.GetStyle("HelpBox");
|
||||
var styleRichText = style.richText;
|
||||
style.richText = true;
|
||||
|
||||
var height = style.CalcHeight(_GuiContent, EditorGUIUtility.currentViewWidth);
|
||||
if (height <= EditorGUIUtility.singleLineHeight)
|
||||
{
|
||||
// This gets internal layout of the help box right but breaks down if multiline.
|
||||
height += style.padding.horizontal + style.lineHeight;
|
||||
}
|
||||
|
||||
// Always get a new control rect so we don't have to deal with positions and offsets.
|
||||
position = EditorGUILayout.GetControlRect(true, height, style);
|
||||
// + 1 maps our MessageType to Unity's.
|
||||
EditorGUI.HelpBox(position, _Message, (UnityEditor.MessageType)_MessageType + 1);
|
||||
|
||||
// Revert skin since it persists.
|
||||
style.richText = styleRichText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49c68d73fb5ce4690a444ab84a0abc8e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a custom link to Crest's documentation for the help URL button.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Enum, AllowMultiple = false)]
|
||||
sealed class HelpURL : HelpURLAttribute
|
||||
{
|
||||
public HelpURL(string path = "") : base(GetPageLink(path))
|
||||
{
|
||||
// Blank.
|
||||
}
|
||||
|
||||
public static string GetPageLink(string path)
|
||||
{
|
||||
return "https://docs.crest.waveharmonic.com/" + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f113b211b4a014cf19cf7828018f49d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Layer : DecoratedProperty
|
||||
{
|
||||
internal override void OnGUI(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
property.intValue = EditorGUI.LayerField(position, label, property.intValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a25b1272591fc4fd6a34265f20064746
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
sealed class OnChange : Attribute
|
||||
{
|
||||
public Type Type { get; }
|
||||
public bool SkipIfInactive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Register an instance method as an OnChange handler.
|
||||
/// </summary>
|
||||
public OnChange(bool skipIfInactive = true)
|
||||
{
|
||||
SkipIfInactive = skipIfInactive;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a static method as an OnChange handler.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to target.</param>
|
||||
/// <param name="skipIfInactive">Skip this handler if component is inactive.</param>
|
||||
public OnChange(Type type, bool skipIfInactive = true)
|
||||
{
|
||||
Type = type;
|
||||
SkipIfInactive = skipIfInactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d2e7be9abf446048de686f69cb485c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,219 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
using WaveHarmonic.Crest.Editor;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class Predicated : Decorator
|
||||
{
|
||||
enum Mode
|
||||
{
|
||||
Property,
|
||||
Member,
|
||||
Type,
|
||||
RenderPipeline,
|
||||
}
|
||||
|
||||
readonly Mode _Mode;
|
||||
|
||||
readonly bool _Inverted;
|
||||
readonly bool _Hide;
|
||||
|
||||
readonly string _PropertyName;
|
||||
readonly object _DisableValue;
|
||||
|
||||
readonly Type _Type;
|
||||
readonly MemberInfo _Member;
|
||||
|
||||
readonly RenderPipeline _RenderPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// The field with this attribute will be drawn enabled/disabled based on return of method.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to call the method on. Must be either a static type or the type the field is defined on.</param>
|
||||
/// <param name="member">Member name. Method must match signature: bool MethodName(Component component). Can be any visibility and static or instance.</param>
|
||||
/// <param name="disableValue"></param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(Type type, string member, object disableValue, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Member;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
_DisableValue = disableValue;
|
||||
_Member = _Type.GetMember(member, Helpers.s_AnyMethod)[0];
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Predicated(Type, string, object, bool, bool)"/>
|
||||
public Predicated(Type type, string member, bool inverted = false, bool hide = false) : this(type, member, true, inverted, hide)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable/Disable field depending on the current type of the component.
|
||||
/// </summary>
|
||||
/// <param name="type">If a component of this type is not attached to this GameObject, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(Type type, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Type;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The field with this attribute will be drawn enabled/disabled based on another field. For example can be used
|
||||
/// to disable a field if a toggle is false.
|
||||
/// </summary>
|
||||
/// <param name="property">The name of the other property whose value dictates whether this field is enabled or not.</param>
|
||||
/// <param name="inverted">Flip behaviour - for example disable if a bool field is set to true (instead of false).</param>
|
||||
/// <param name="disableValue">If the field has this value, disable the GUI (or enable if inverted is true).</param>
|
||||
/// <param name="hide">Hide this component in the inspector.</param>
|
||||
public Predicated(string property, bool inverted = false, object disableValue = null, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.Property;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_PropertyName = property;
|
||||
_DisableValue = disableValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Field is predicated (enabled/shown) on which render pipeline is active.
|
||||
/// </summary>
|
||||
/// <param name="rp">Enable if this render pipeline is active.</param>
|
||||
/// <param name="inverted">Invert behaviour.</param>
|
||||
/// <param name="hide">Hide instead of disable.</param>
|
||||
public Predicated(RenderPipeline rp, bool inverted = false, bool hide = false)
|
||||
{
|
||||
_Mode = Mode.RenderPipeline;
|
||||
_Inverted = inverted;
|
||||
_Hide = hide;
|
||||
_RenderPipeline = rp;
|
||||
}
|
||||
|
||||
public override bool AlwaysVisible => true;
|
||||
|
||||
public bool GUIEnabled(SerializedProperty property)
|
||||
{
|
||||
return _Inverted != property.propertyType switch
|
||||
{
|
||||
// Enable GUI if int value of field is not equal to 0, or whatever the disable-value is set to
|
||||
SerializedPropertyType.Integer => property.intValue != ((int?)_DisableValue ?? 0),
|
||||
// Enable GUI if disable-value is 0 and field is true, or disable-value is not 0 and field is false
|
||||
SerializedPropertyType.Boolean => property.boolValue ^ (((int?)_DisableValue ?? 0) != 0),
|
||||
SerializedPropertyType.Float => property.floatValue != ((float?)_DisableValue ?? 0),
|
||||
SerializedPropertyType.String => property.stringValue != ((string)_DisableValue ?? ""),
|
||||
// Must pass nameof enum entry as we are comparing names because index != value.
|
||||
SerializedPropertyType.Enum => property.enumNames[property.enumValueIndex] != ((string)_DisableValue ?? ""),
|
||||
SerializedPropertyType.ObjectReference => property.objectReferenceValue != null,
|
||||
SerializedPropertyType.ManagedReference => property.managedReferenceValue != null,
|
||||
_ => throw new ArgumentException($"Crest.Predicated: property type on <i>{property.serializedObject.targetObject}</i> not implemented yet: {property.propertyType}."),
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Decorate(Rect position, SerializedProperty property, GUIContent label, DecoratedDrawer drawer)
|
||||
{
|
||||
var enabled = true;
|
||||
|
||||
if (_Mode == Mode.Property)
|
||||
{
|
||||
var propertyPath = _PropertyName;
|
||||
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
propertyPath = $"{string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1])}.{propertyPath}";
|
||||
}
|
||||
|
||||
// Get the other property to be the predicate for the enabled/disabled state of this property.
|
||||
// Do not use property.FindPropertyRelative as it does not work with nested properties.
|
||||
// Try and find the nested property first and then fallback to the root object.
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath) ?? property.serializedObject.FindProperty(_PropertyName);
|
||||
Debug.AssertFormat(otherProperty != null, "Crest.Predicated: {0} or {1} property on {2} ({3}) could not be found!", propertyPath, _PropertyName, property.serializedObject.targetObject.GetType(), property.name);
|
||||
|
||||
if (otherProperty != null)
|
||||
{
|
||||
enabled = GUIEnabled(otherProperty);
|
||||
}
|
||||
}
|
||||
else if (_Mode == Mode.Member)
|
||||
{
|
||||
// Static is both abstract and sealed: https://stackoverflow.com/a/1175950
|
||||
object @object = _Type.IsAbstract && _Type.IsSealed ? null : property.serializedObject.targetObject;
|
||||
|
||||
// If this is a nested type, grab that type. This may not be suitable in all cases.
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var propertyPath = string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1]);
|
||||
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath);
|
||||
|
||||
@object = otherProperty.propertyType switch
|
||||
{
|
||||
SerializedPropertyType.ManagedReference => otherProperty.managedReferenceValue,
|
||||
SerializedPropertyType.Generic => otherProperty.boxedValue,
|
||||
_ => @object,
|
||||
};
|
||||
}
|
||||
|
||||
if (_Member is PropertyInfo autoProperty)
|
||||
{
|
||||
// == operator does not work.
|
||||
enabled = !autoProperty.GetValue(@object).Equals(_DisableValue);
|
||||
}
|
||||
else if (_Member is MethodInfo method)
|
||||
{
|
||||
enabled = !method.Invoke(@object, new object[] { }).Equals(_DisableValue);
|
||||
}
|
||||
|
||||
if (_Inverted) enabled = !enabled;
|
||||
}
|
||||
else if (_Mode == Mode.Type)
|
||||
{
|
||||
var type = property.serializedObject.targetObject.GetType();
|
||||
|
||||
// If this is a nested type, grab that type. This may not be suitable in all cases.
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var propertyPath = string.Join(".", property.propertyPath.Split(".", StringSplitOptions.None)[0..^1]);
|
||||
|
||||
var otherProperty = property.serializedObject.FindProperty(propertyPath);
|
||||
|
||||
type = otherProperty.propertyType switch
|
||||
{
|
||||
SerializedPropertyType.ManagedReference => otherProperty.managedReferenceValue.GetType(),
|
||||
SerializedPropertyType.Generic => otherProperty.boxedValue.GetType(),
|
||||
_ => type,
|
||||
};
|
||||
}
|
||||
|
||||
var enabledByTypeCheck = _Type.IsAssignableFrom(type);
|
||||
if (_Inverted) enabledByTypeCheck = !enabledByTypeCheck;
|
||||
enabled = enabledByTypeCheck && enabled;
|
||||
}
|
||||
else if (_Mode == Mode.RenderPipeline)
|
||||
{
|
||||
enabled = RenderPipelineHelper.RenderPipeline == _RenderPipeline != _Inverted;
|
||||
}
|
||||
|
||||
// Keep current disabled state.
|
||||
GUI.enabled &= enabled;
|
||||
|
||||
// Keep previous hidden state.
|
||||
DecoratedDrawer.s_HideInInspector = DecoratedDrawer.s_HideInInspector || (_Hide && !enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a36938141700f4f47b31f7565311ace0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,229 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Adapted from:
|
||||
// https://forum.unity.com/threads/drawing-a-field-using-multiple-property-drawers.479377/
|
||||
|
||||
// This class draws all the attributes which inherit from DecoratedProperty. This class may need to be
|
||||
// extended to handle reseting GUI states as we need them.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using WaveHarmonic.Crest.Attributes;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(DecoratedProperty), true)]
|
||||
sealed class DecoratedDrawer : PropertyDrawer
|
||||
{
|
||||
internal static bool s_HideInInspector = false;
|
||||
public static bool s_IsFoldout = false;
|
||||
public static bool s_IsFoldoutOpen = false;
|
||||
|
||||
public static bool s_TemporaryColor;
|
||||
public static Color s_PreviousColor;
|
||||
|
||||
public float Space { get; private set; }
|
||||
|
||||
List<object> _Decorators = null;
|
||||
List<object> Decorators
|
||||
{
|
||||
get
|
||||
{
|
||||
// Populate list with decorators.
|
||||
_Decorators ??= fieldInfo
|
||||
.GetCustomAttributes(typeof(Decorator), false)
|
||||
.ToList();
|
||||
|
||||
return _Decorators;
|
||||
}
|
||||
}
|
||||
|
||||
List<Attributes.Validator> _Validators = null;
|
||||
List<Attributes.Validator> Validators => _Validators ??= fieldInfo
|
||||
.GetCustomAttributes(typeof(Attributes.Validator), false)
|
||||
.Cast<Attributes.Validator>()
|
||||
.ToList();
|
||||
|
||||
static List<(MethodInfo, OnChange)> s_OnChangeHandlers;
|
||||
static List<(MethodInfo, OnChange)> OnChangeHandlers => s_OnChangeHandlers ??= TypeCache
|
||||
.GetMethodsWithAttribute<OnChange>()
|
||||
.Select(x => (x, x.GetCustomAttribute<OnChange>()))
|
||||
.ToList();
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnDomainReload()
|
||||
{
|
||||
s_OnChangeHandlers = null;
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Make original control rectangle be invisible because we always create our own. Zero still adds a little
|
||||
// height which becomes noticeable once multiple properties are hidden. This could be some GUI style
|
||||
// property but could not find which one.
|
||||
return -2f;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
// Store the original GUI state so it can be reset later.
|
||||
var originalColor = GUI.color;
|
||||
var originalEnabled = GUI.enabled;
|
||||
|
||||
if (s_TemporaryColor) GUI.color = s_PreviousColor;
|
||||
|
||||
// Execute all non visual attributes like Predicated. If these change any IMGUI
|
||||
// properties they might be overriden with EditorGUI.GetPropertyHeight call.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (!attribute.AlwaysVisible) continue;
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
Space = 0;
|
||||
|
||||
// Execute all labels
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute is Space space) Space = space._Height;
|
||||
label = attribute.BuildLabel(label);
|
||||
}
|
||||
|
||||
if (!s_HideInInspector && (!s_IsFoldout || s_IsFoldoutOpen))
|
||||
{
|
||||
// Execute all visual attributes.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
attribute.Decorate(position, property, label, this);
|
||||
}
|
||||
|
||||
var a = (DecoratedProperty)attribute;
|
||||
position = a.NeedsControlRectangle(property)
|
||||
? EditorGUILayout.GetControlRect(true, EditorGUI.GetPropertyHeight(property, label, true))
|
||||
: position;
|
||||
|
||||
// Call for labels again as EditorGUI.GetPropertyHeight will revert them.
|
||||
// Specifically for nested classes where the label will revert once opened.
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
label = attribute.BuildLabel(label);
|
||||
}
|
||||
|
||||
var skipChange = fieldInfo.FieldType == typeof(UnityEvent);
|
||||
|
||||
if (!skipChange)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
}
|
||||
|
||||
var oldValue = skipChange ? null : property.boxedValue;
|
||||
a.OnGUI(position, property, label, this);
|
||||
|
||||
for (var index = 0; index < Validators.Count; index++)
|
||||
{
|
||||
Validators[index].Validate(position, property, label, this, oldValue);
|
||||
}
|
||||
|
||||
// Guard against foldouts triggering change check due to changing isExpanded.
|
||||
if (!skipChange && EditorGUI.EndChangeCheck() && oldValue != property.boxedValue)
|
||||
{
|
||||
// Apply any changes.
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
OnChange(property, oldValue);
|
||||
}
|
||||
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (attribute.AlwaysVisible) continue;
|
||||
attribute.DecorateAfter(position, property, label, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s_IsFoldout || s_IsFoldoutOpen)
|
||||
{
|
||||
for (var index = 0; index < Decorators.Count; index++)
|
||||
{
|
||||
var attribute = (Decorator)Decorators[index];
|
||||
if (!attribute.AlwaysVisible) continue;
|
||||
attribute.DecorateAfter(position, property, label, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resetting the GUI state.
|
||||
s_HideInInspector = false;
|
||||
GUI.color = originalColor;
|
||||
GUI.enabled = originalEnabled;
|
||||
}
|
||||
|
||||
public static void OnChange(SerializedProperty property, object oldValue)
|
||||
{
|
||||
var target = property.serializedObject.targetObject;
|
||||
// This is the type the field is declared on so it will work for nested objects.
|
||||
var targetType = target.GetType();
|
||||
|
||||
var isActive = true;
|
||||
if (property.serializedObject.targetObject is Crest.Internal.EditorBehaviour c && !c.isActiveAndEnabled)
|
||||
{
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
// Send event to target.
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
{
|
||||
if (attribute.SkipIfInactive && !isActive) continue;
|
||||
var type = attribute.Type ?? method.DeclaringType;
|
||||
if (!type.IsAssignableFrom(targetType)) continue;
|
||||
|
||||
if (attribute.Type == null)
|
||||
{
|
||||
method.Invoke(target, new object[] { property.propertyPath, oldValue });
|
||||
}
|
||||
else
|
||||
{
|
||||
method.Invoke(null, new object[] { target, property.propertyPath, oldValue, property });
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate event to nested classes.
|
||||
for (var i = 0; i < property.depth; i++)
|
||||
{
|
||||
var chunks = property.propertyPath.Split(".");
|
||||
var nestedProperty = property.serializedObject.FindProperty(string.Join(".", chunks[..(i + 1)]));
|
||||
if (nestedProperty.propertyType != SerializedPropertyType.ManagedReference) continue;
|
||||
var relativePath = string.Join(".", chunks[(i + 1)..]);
|
||||
|
||||
var nestedTarget = nestedProperty.managedReferenceValue;
|
||||
var nestedTargetType = nestedTarget.GetType();
|
||||
|
||||
foreach (var (method, attribute) in OnChangeHandlers)
|
||||
{
|
||||
if (attribute.SkipIfInactive && !isActive) continue;
|
||||
var type = attribute.Type ?? method.DeclaringType;
|
||||
if (!type.IsAssignableFrom(nestedTargetType)) continue;
|
||||
|
||||
if (attribute.Type == null)
|
||||
{
|
||||
method.Invoke(nestedTarget, new object[] { relativePath, oldValue });
|
||||
}
|
||||
else
|
||||
{
|
||||
method.Invoke(null, new object[] { nestedTarget, relativePath, oldValue, property });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb1d9ebb9335a411586a683ac6f78acd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,265 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// This file is subject to the Unity Companion License:
|
||||
// https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/LICENSE.md
|
||||
|
||||
// Lovingly adapted from Cinemachine:
|
||||
// https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/Editor/Utility/EmbeddedAssetHelpers.cs
|
||||
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.VersionControl;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for editors that receive an argument
|
||||
/// </summary>
|
||||
interface IEmbeddableEditor
|
||||
{
|
||||
void SetTypeOfHostComponent(System.Type hostType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper for drawing embedded asset editors
|
||||
/// </summary>
|
||||
sealed class EmbeddedAssetEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Create in OnEnable()
|
||||
/// </summary>
|
||||
public EmbeddedAssetEditor()
|
||||
{
|
||||
_CreateButtonGUIContent = new("Create Asset", "Create a new shared settings asset");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the asset editor is created, in case it needs
|
||||
/// to be customized
|
||||
/// </summary>
|
||||
public OnCreateEditorDelegate _OnCreateEditor;
|
||||
public delegate void OnCreateEditorDelegate(UnityEditor.Editor editor);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the asset being edited was changed by the user.
|
||||
/// </summary>
|
||||
public OnChangedDelegate _OnChanged;
|
||||
public delegate void OnChangedDelegate(System.Type type, Object obj);
|
||||
|
||||
/// <summary>
|
||||
/// Free the resources in OnDisable()
|
||||
/// </summary>
|
||||
public void OnDisable()
|
||||
{
|
||||
DestroyEditor();
|
||||
Helpers.Destroy(_DefaultTarget);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Customize this after creation if you want
|
||||
/// </summary>
|
||||
public GUIContent _CreateButtonGUIContent;
|
||||
|
||||
UnityEditor.Editor _Editor = null;
|
||||
|
||||
System.Type _Type;
|
||||
|
||||
Object _DefaultTarget;
|
||||
FieldInfo _DefaultTargetField;
|
||||
|
||||
const int k_IndentOffset = 3;
|
||||
|
||||
public void DrawEditorCombo(Embedded embedded, GUIContent label, PropertyDrawer drawer, SerializedProperty property, string extension)
|
||||
{
|
||||
_Type = drawer.fieldInfo.FieldType;
|
||||
|
||||
DrawEditorCombo
|
||||
(
|
||||
embedded,
|
||||
label,
|
||||
$"Create {property.displayName} Asset",
|
||||
$"{property.displayName.Replace(' ', '_')}",
|
||||
extension,
|
||||
string.Empty,
|
||||
false,
|
||||
property
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this from OnInspectorGUI. Will draw the asset reference field, and
|
||||
/// the embedded editor, or a Create Asset button, if no asset is set.
|
||||
/// </summary>
|
||||
public void DrawEditorCombo
|
||||
(
|
||||
Embedded embedded,
|
||||
GUIContent label,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message,
|
||||
bool indent,
|
||||
SerializedProperty property
|
||||
)
|
||||
{
|
||||
UpdateEditor(property, embedded);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var rect = AssetField(label, property, title, defaultName, extension, message);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
UpdateEditor(property, embedded);
|
||||
}
|
||||
|
||||
// Display embedded editor.
|
||||
if (_Editor != null)
|
||||
{
|
||||
var foldoutRect = new Rect(rect.x - k_IndentOffset, rect.y, rect.width + k_IndentOffset, EditorGUIUtility.singleLineHeight);
|
||||
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true);
|
||||
|
||||
var canEditAsset = AssetDatabase.IsOpenForEdit(_Editor.target, StatusQueryOptions.UseCachedIfPossible);
|
||||
|
||||
// We take the current GUI state into account to support attribute stacking.
|
||||
var guiEnabled = GUI.enabled;
|
||||
GUI.enabled = guiEnabled && canEditAsset;
|
||||
|
||||
if (property.isExpanded)
|
||||
{
|
||||
var level = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
// NOTE: Tweaked for current usage but probably will not work everywhere.
|
||||
if (level > 0) GUILayout.Space(8 * (level + 2));
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
|
||||
if ((_Editor.target.hideFlags & HideFlags.NotEditable) == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("This is a shared asset. Changes made here will apply to all users of this asset.", MessageType.Info);
|
||||
}
|
||||
EditorGUI.BeginChangeCheck();
|
||||
_Editor.OnInspectorGUI();
|
||||
if (EditorGUI.EndChangeCheck() && (_OnChanged != null))
|
||||
_OnChanged(_Type, property.objectReferenceValue);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (embedded.BottomMargin > 0)
|
||||
{
|
||||
EditorGUILayout.Space(embedded.BottomMargin);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable GUI so the checkout button works.
|
||||
GUI.enabled = true;
|
||||
|
||||
if (_Editor.target != null)
|
||||
{
|
||||
if (!canEditAsset && GUILayout.Button("Check out"))
|
||||
{
|
||||
var task = Provider.Checkout(AssetDatabase.GetAssetPath(_Editor.target), CheckoutMode.Asset);
|
||||
task.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Restore stacked GUI enabled state.
|
||||
GUI.enabled = guiEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
Rect AssetField
|
||||
(
|
||||
GUIContent label,
|
||||
SerializedProperty property,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message
|
||||
)
|
||||
{
|
||||
return EditorHelpers.AssetField
|
||||
(
|
||||
_Type,
|
||||
label,
|
||||
property,
|
||||
EditorGUILayout.GetControlRect(true),
|
||||
title,
|
||||
defaultName,
|
||||
extension,
|
||||
message,
|
||||
x => ScriptableObject.CreateInstance(_Type)
|
||||
);
|
||||
}
|
||||
|
||||
public void DestroyEditor()
|
||||
{
|
||||
if (_Editor != null)
|
||||
{
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateEditor(SerializedProperty property, Embedded embedded)
|
||||
{
|
||||
var target = property.objectReferenceValue;
|
||||
var hasDefaultField = !string.IsNullOrEmpty(embedded.DefaultPropertyName);
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
if (!hasDefaultField)
|
||||
{
|
||||
if (_DefaultTarget == null)
|
||||
{
|
||||
_DefaultTarget = ScriptableObject.CreateInstance(_Type);
|
||||
_DefaultTarget.hideFlags = HideFlags.DontSave | HideFlags.NotEditable;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_DefaultTargetField == null)
|
||||
{
|
||||
_DefaultTargetField = property.serializedObject.targetObject.GetType().GetField(embedded.DefaultPropertyName, Helpers.s_AnyMethod);
|
||||
}
|
||||
|
||||
// Always call, as it is dynamic.
|
||||
_DefaultTarget = (Object)_DefaultTargetField.GetValue(property.serializedObject.targetObject);
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
target = _DefaultTarget;
|
||||
}
|
||||
|
||||
// Destroy the editor if target has changed.
|
||||
if (_Editor != null && _Editor.target != target)
|
||||
{
|
||||
DestroyEditor();
|
||||
}
|
||||
|
||||
if (_Editor != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: This is triggered twice on asset switch for some reason.
|
||||
// Create editor if need one.
|
||||
if (target != null)
|
||||
{
|
||||
_Editor = UnityEditor.Editor.CreateEditor(target);
|
||||
|
||||
// Pass through argument for editors that receive it
|
||||
if (property.serializedObject.targetObject != null)
|
||||
{
|
||||
(_Editor as IEmbeddableEditor)?.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType());
|
||||
}
|
||||
|
||||
_OnCreateEditor?.Invoke(_Editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13b989c062ddf461a9afd71ab6da8921
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,488 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides general helper functions for the editor.
|
||||
/// </summary>
|
||||
static partial class EditorHelpers
|
||||
{
|
||||
internal static ComputeShader s_VisualizeNegativeValuesShader;
|
||||
internal static ComputeShader VisualizeNegativeValuesShader
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_VisualizeNegativeValuesShader == null)
|
||||
{
|
||||
s_VisualizeNegativeValuesShader = AssetDatabase.LoadAssetAtPath<ComputeShader>("Packages/com.waveharmonic.crest/Editor/Shaders/VisualizeNegativeValues.compute");
|
||||
}
|
||||
|
||||
return s_VisualizeNegativeValuesShader;
|
||||
}
|
||||
}
|
||||
|
||||
public static LayerMask LayerMaskField(string label, LayerMask layerMask)
|
||||
{
|
||||
// Adapted from: http://answers.unity.com/answers/1387522/view.html
|
||||
var temporary = EditorGUILayout.MaskField(
|
||||
label,
|
||||
UnityEditorInternal.InternalEditorUtility.LayerMaskToConcatenatedLayersMask(layerMask),
|
||||
UnityEditorInternal.InternalEditorUtility.layers);
|
||||
return UnityEditorInternal.InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(temporary);
|
||||
}
|
||||
|
||||
/// <summary>Attempts to get the scene view this camera is rendering.</summary>
|
||||
/// <returns>The scene view or null if not found.</returns>
|
||||
public static SceneView GetSceneViewFromSceneCamera(Camera camera)
|
||||
{
|
||||
foreach (SceneView sceneView in SceneView.sceneViews)
|
||||
{
|
||||
if (sceneView.camera == camera)
|
||||
{
|
||||
return sceneView;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Get time passed to animated materials.</summary>
|
||||
public static float GetShaderTime()
|
||||
{
|
||||
// When "Always Refresh" is disabled, Unity passes zero. Also uses realtimeSinceStartup:
|
||||
// https://github.com/Unity-Technologies/Graphics/blob/5743e39cdf0795cf7cbeb7ba8ffbbcc7ca200709/Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs#L116
|
||||
return !Application.isPlaying && SceneView.lastActiveSceneView != null &&
|
||||
!SceneView.lastActiveSceneView.sceneViewState.alwaysRefresh ? 0f : Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public static GameObject GetGameObject(SerializedObject serializedObject)
|
||||
{
|
||||
// We will either get the component or the GameObject it is attached to.
|
||||
return serializedObject.targetObject is GameObject
|
||||
? serializedObject.targetObject as GameObject
|
||||
: (serializedObject.targetObject as Component).gameObject;
|
||||
}
|
||||
|
||||
public static Material CreateSerializedMaterial(string shaderPath, string message)
|
||||
{
|
||||
var shader = Shader.Find(shaderPath);
|
||||
Debug.Assert(shader != null, "Crest: Cannot create required material because shader is null");
|
||||
|
||||
var material = new Material(shader);
|
||||
|
||||
// Record the material and any subsequent changes.
|
||||
Undo.RegisterCreatedObjectUndo(material, message);
|
||||
Undo.RegisterCompleteObjectUndo(material, message);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
public static Material CreateSerializedMaterial(string shaderPath)
|
||||
{
|
||||
return CreateSerializedMaterial(shaderPath, Undo.GetCurrentGroupName());
|
||||
}
|
||||
|
||||
public static Object GetDefaultReference(this SerializedObject self, string property)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(MonoScript.FromMonoBehaviour(self.targetObject as MonoBehaviour));
|
||||
var importer = AssetImporter.GetAtPath(path) as MonoImporter;
|
||||
return importer.GetDefaultReference(property);
|
||||
}
|
||||
|
||||
public static object GetDefiningBoxedObject(this SerializedProperty property)
|
||||
{
|
||||
object target = property.serializedObject.targetObject;
|
||||
|
||||
if (property.depth > 0)
|
||||
{
|
||||
// Get the property path so we can find it from the serialized object.
|
||||
var path = string.Join(".", property.propertyPath.Split(".", System.StringSplitOptions.None)[0..^1]);
|
||||
var other = property.serializedObject.FindProperty(path);
|
||||
// Boxed value can handle both managed and generic with caveats:
|
||||
// https://docs.unity3d.com/ScriptReference/SerializedProperty-boxedValue.html
|
||||
// Not sure if it will be a new or same instance as in the scene.
|
||||
target = other.boxedValue;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
internal delegate Object CreateInstance(SerializedProperty property);
|
||||
|
||||
internal static Rect AssetField
|
||||
(
|
||||
System.Type type,
|
||||
GUIContent label,
|
||||
SerializedProperty property,
|
||||
Rect rect,
|
||||
string title,
|
||||
string defaultName,
|
||||
string extension,
|
||||
string message,
|
||||
CreateInstance create
|
||||
)
|
||||
{
|
||||
var hSpace = 5;
|
||||
var buttonWidth = 45;
|
||||
var buttonCount = 2;
|
||||
|
||||
rect.width -= buttonWidth * buttonCount + hSpace;
|
||||
EditorGUI.PropertyField(rect, property, label);
|
||||
|
||||
var r = new Rect(rect);
|
||||
|
||||
r.x += r.width + hSpace;
|
||||
r.width = buttonWidth;
|
||||
if (GUI.Button(r, "New", EditorStyles.miniButtonLeft))
|
||||
{
|
||||
var path = EditorUtility.SaveFilePanelInProject(title, defaultName, extension, message);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var asset = create(property);
|
||||
if (asset != null)
|
||||
{
|
||||
if (extension == "prefab")
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(asset as GameObject, path);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
}
|
||||
|
||||
property.objectReferenceValue = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Crest: Could not create file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only allow cloning if extensions match. Guards against cloning Shader Graph if
|
||||
// using its embedded material.
|
||||
var cloneable = property.objectReferenceValue != null;
|
||||
cloneable = cloneable && Path.GetExtension(AssetDatabase.GetAssetPath(property.objectReferenceValue)) == $".{extension}";
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!cloneable);
|
||||
r.x += r.width;
|
||||
if (GUI.Button(r, "Clone", EditorStyles.miniButtonRight))
|
||||
{
|
||||
var oldPath = AssetDatabase.GetAssetPath(property.objectReferenceValue);
|
||||
var newPath = oldPath;
|
||||
if (!newPath.StartsWithNoAlloc("Assets")) newPath = Path.Join("Assets", Path.GetFileName(newPath));
|
||||
newPath = AssetDatabase.GenerateUniqueAssetPath(newPath);
|
||||
AssetDatabase.CopyAsset(oldPath, newPath);
|
||||
property.objectReferenceValue = AssetDatabase.LoadAssetAtPath<Object>(newPath);
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
internal static void RichTextHelpBox(string message, MessageType type)
|
||||
{
|
||||
var styleRichText = GUI.skin.GetStyle("HelpBox").richText;
|
||||
GUI.skin.GetStyle("HelpBox").richText = true;
|
||||
|
||||
EditorGUILayout.HelpBox(message, type);
|
||||
|
||||
// Revert skin since it persists.
|
||||
GUI.skin.GetStyle("HelpBox").richText = styleRichText;
|
||||
}
|
||||
|
||||
// Prettify nameof.
|
||||
internal static string Pretty(this string text)
|
||||
{
|
||||
// Regular expression to split on transitions from lower to upper case and keep acronyms together
|
||||
return Regex.Replace(text, @"([a-z])([A-Z])|([A-Z])([A-Z][a-z])", "$1$3 $2$4").Replace("_", "");
|
||||
}
|
||||
|
||||
internal static string Italic(this string text)
|
||||
{
|
||||
return $"<i>{text}</i>";
|
||||
}
|
||||
|
||||
public static void MarkCurrentStageAsDirty()
|
||||
{
|
||||
var stage = UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
|
||||
|
||||
if (stage != null)
|
||||
{
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(stage.scene);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
{
|
||||
internal static string GetSubShaderTag([DisallowNull] this Shader shader, ShaderSnippetData snippet, ShaderTagId id)
|
||||
{
|
||||
var data = ShaderUtil.GetShaderData(shader);
|
||||
if (data == null) return null;
|
||||
|
||||
var index = (int)snippet.pass.SubshaderIndex;
|
||||
if (index < 0 || index >= shader.subshaderCount) return null;
|
||||
|
||||
var subShader = data.GetSerializedSubshader(index);
|
||||
if (subShader == null) return null;
|
||||
|
||||
var tag = subShader.FindTagValue(id);
|
||||
if (string.IsNullOrEmpty(tag.name)) return null;
|
||||
|
||||
return tag.name;
|
||||
}
|
||||
}
|
||||
|
||||
static partial class EditorHelpers
|
||||
{
|
||||
const int k_ButtonDropDownWidth = 15;
|
||||
|
||||
static readonly GUIContent s_ButtonDropDownIcon = new(EditorGUIUtility.FindTexture("icon dropdown@2x"));
|
||||
static readonly PropertyInfo s_TopLevel = typeof(GUILayoutUtility).GetProperty("topLevel", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
static readonly MethodInfo s_GetLast = typeof(GUILayoutUtility).Assembly.GetType("UnityEngine.GUILayoutGroup").GetMethod("GetLast", BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
// Only way to identify the caller is its rect.
|
||||
static Rect s_ButtonChooser;
|
||||
static int s_ButtonChoice = -2;
|
||||
|
||||
// Normal button or split button with dropdown.
|
||||
public static bool Button
|
||||
(
|
||||
GUIContent label,
|
||||
out int choice,
|
||||
string[] labels,
|
||||
bool disableMain = false,
|
||||
bool disableDropDown = false,
|
||||
bool centerLabel = false,
|
||||
bool expandWidth = true,
|
||||
int minimumWidth = 0
|
||||
)
|
||||
{
|
||||
choice = -2;
|
||||
var chosen = false;
|
||||
|
||||
var hasDropDown = labels?.Length > 0;
|
||||
var skin = GUI.skin.button;
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(disableMain))
|
||||
{
|
||||
var style = new GUIStyle(hasDropDown ? EditorStyles.miniButtonLeft : EditorStyles.miniButton)
|
||||
{
|
||||
padding = skin.padding,
|
||||
stretchHeight = skin.stretchHeight,
|
||||
fixedHeight = skin.fixedHeight
|
||||
};
|
||||
|
||||
var width = style.CalcSize(label).x + style.padding.left +
|
||||
style.padding.right + style.border.left + style.border.right;
|
||||
width = Mathf.Max(width, minimumWidth);
|
||||
// TODO: Add option to disable this (consistent width).
|
||||
if (!hasDropDown && minimumWidth > 0) width += k_ButtonDropDownWidth;
|
||||
if (centerLabel && hasDropDown) style.padding.left += k_ButtonDropDownWidth;
|
||||
|
||||
if (GUILayout.Button(label, style, expandWidth ? GUILayout.ExpandWidth(true) : GUILayout.Width(width)))
|
||||
{
|
||||
choice = -1;
|
||||
chosen = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDropDown)
|
||||
{
|
||||
using (new EditorGUI.DisabledGroupScope(disableDropDown))
|
||||
{
|
||||
// TODO: color interior border same as exterior (lighten).
|
||||
var style = new GUIStyle(EditorStyles.miniButtonRight)
|
||||
{
|
||||
padding = new(1, 1, 3, 3),
|
||||
stretchHeight = skin.stretchHeight,
|
||||
fixedHeight = skin.fixedHeight
|
||||
};
|
||||
|
||||
var rect = (Rect)s_GetLast.Invoke(s_TopLevel.GetValue(null), null);
|
||||
rect.width += k_ButtonDropDownWidth;
|
||||
|
||||
if (s_ButtonChoice > -1 && s_ButtonChooser == rect)
|
||||
{
|
||||
choice = s_ButtonChoice;
|
||||
chosen = true;
|
||||
s_ButtonChoice = -2;
|
||||
s_ButtonChooser = Rect.zero;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(s_ButtonDropDownIcon, style, GUILayout.Width(k_ButtonDropDownWidth), GUILayout.ExpandHeight(true)))
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
|
||||
for (var i = 0; i < labels.Length; i++)
|
||||
{
|
||||
menu.AddItem(new(labels[i]), false, x => { s_ButtonChoice = (int)x; s_ButtonChooser = rect; }, i);
|
||||
}
|
||||
|
||||
menu.DropDown(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
static partial class EditorHelpers
|
||||
{
|
||||
// Adapted from (public API may support this in future):
|
||||
// com.unity.splines@2.7.2/Editor/Components/SplineContainerEditor.cs
|
||||
static GUIStyle s_HelpLabelStyle;
|
||||
static GUIStyle HelpLabelStyle => s_HelpLabelStyle ??= new(EditorStyles.label)
|
||||
{
|
||||
wordWrap = EditorStyles.helpBox.wordWrap,
|
||||
fontSize = EditorStyles.helpBox.fontSize,
|
||||
padding = new(-2, 0, 0, 0),
|
||||
richText = true,
|
||||
};
|
||||
|
||||
static readonly MethodInfo s_GetHelpIcon = typeof(EditorGUIUtility).GetMethod("GetHelpIcon", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
internal static int? HelpBox
|
||||
(
|
||||
GUIContent message,
|
||||
MessageType type,
|
||||
GUIContent button = null,
|
||||
string[] buttons = null,
|
||||
bool buttonCenterLabel = false,
|
||||
int buttonMinimumWidth = 0
|
||||
)
|
||||
{
|
||||
return HelpBox
|
||||
(
|
||||
message,
|
||||
new GUIContent((Texture2D)s_GetHelpIcon.Invoke(null, new object[] { type })),
|
||||
button,
|
||||
buttons,
|
||||
buttonCenterLabel,
|
||||
buttonMinimumWidth
|
||||
);
|
||||
}
|
||||
|
||||
internal static int? HelpBox
|
||||
(
|
||||
GUIContent message,
|
||||
GUIContent icon,
|
||||
GUIContent button = null,
|
||||
string[] buttons = null,
|
||||
bool buttonCenterLabel = false,
|
||||
int buttonMinimumWidth = 0
|
||||
)
|
||||
{
|
||||
int? result = null;
|
||||
|
||||
// Box
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.helpBox);
|
||||
|
||||
// Icon
|
||||
EditorGUIUtility.SetIconSize(new(32f, 32f));
|
||||
EditorGUILayout.LabelField(icon, GUILayout.Width(34), GUILayout.MinHeight(34), GUILayout.ExpandHeight(true));
|
||||
EditorGUIUtility.SetIconSize(Vector2.zero);
|
||||
|
||||
// Text
|
||||
EditorGUILayout.LabelField(message, HelpLabelStyle, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
|
||||
// Button
|
||||
if (button != null)
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (Button(button, out var choice, buttons, centerLabel: buttonCenterLabel, minimumWidth: buttonMinimumWidth, expandWidth: false))
|
||||
{
|
||||
result = choice;
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
static class Extensions
|
||||
{
|
||||
// Recursively find the field owner (instance).
|
||||
public static bool FindOwner(this FieldInfo field, ref object target)
|
||||
{
|
||||
if (field.DeclaringType.IsAssignableFrom(target.GetType()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return field.FindOwnerInFields(ref target);
|
||||
}
|
||||
|
||||
public static bool FindOwnerInFields(this FieldInfo targetField, ref object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var fields = target.GetType()
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (field.GetCustomAttribute<SerializeReference>() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = field.GetValue(target);
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetField.DeclaringType.IsAssignableFrom(value.GetType()))
|
||||
{
|
||||
target = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (FindOwnerInFields(targetField, ref value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19e5156bd1ea44af484895930b902df7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
partial class Inspector
|
||||
{
|
||||
static readonly string[] s_FixButtonDropDown = { "Inspect" };
|
||||
static readonly GUIContent s_FixButtonContent = new("Fix");
|
||||
static readonly GUIContent s_InspectButtonContent = new("Inspect", "Jump to object to resolve issue.");
|
||||
|
||||
protected virtual void RenderValidationMessages()
|
||||
{
|
||||
// This is a static list so we need to clear it before use. Not sure if this will ever be a threaded
|
||||
// operation which would be an issue.
|
||||
foreach (var messages in ValidatedHelper.s_Messages)
|
||||
{
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
ValidatedHelper.ExecuteValidators(target, ValidatedHelper.HelpBox);
|
||||
|
||||
// We only want space before and after the list of help boxes. We don't want space between.
|
||||
var needsSpaceAbove = true;
|
||||
|
||||
// Work out if button label needs aligning.
|
||||
var needsAlignment = false;
|
||||
var hasBoth = false;
|
||||
var hasEither = false;
|
||||
for (var messageTypeIndex = 0; messageTypeIndex < ValidatedHelper.s_Messages.Length; messageTypeIndex++)
|
||||
{
|
||||
var messages = ValidatedHelper.s_Messages[messageTypeIndex];
|
||||
|
||||
if (messages.Count > 0)
|
||||
{
|
||||
var messageType = (MessageType)ValidatedHelper.s_Messages.Length - messageTypeIndex;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var hasFix = message._Action != null;
|
||||
var hasInspect = false;
|
||||
|
||||
if (message._Object != null)
|
||||
{
|
||||
var casted = message._Object as MonoBehaviour;
|
||||
|
||||
if (Selection.activeObject != message._Object && (casted == null || casted.gameObject != Selection.activeObject))
|
||||
{
|
||||
hasInspect = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFix && hasInspect) hasBoth = true;
|
||||
if (hasInspect != hasFix) hasEither = true;
|
||||
if (hasBoth && hasEither) goto exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
needsAlignment = hasBoth && hasEither;
|
||||
|
||||
// We loop through in reverse order so errors appears at the top.
|
||||
for (var messageTypeIndex = 0; messageTypeIndex < ValidatedHelper.s_Messages.Length; messageTypeIndex++)
|
||||
{
|
||||
var messages = ValidatedHelper.s_Messages[messageTypeIndex];
|
||||
|
||||
if (messages.Count > 0)
|
||||
{
|
||||
if (needsSpaceAbove)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
needsSpaceAbove = false;
|
||||
}
|
||||
|
||||
// Map Validated.MessageType to HelpBox.MessageType.
|
||||
var messageType = (MessageType)ValidatedHelper.s_Messages.Length - messageTypeIndex;
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
var originalGUIEnabled = GUI.enabled;
|
||||
|
||||
if ((message._Action == ValidatedHelper.FixAddMissingMathPackage || message._Action == ValidatedHelper.FixAddMissingBurstPackage) && PackageManagerHelpers.IsBusy)
|
||||
{
|
||||
GUI.enabled = false;
|
||||
}
|
||||
|
||||
if (message._FixDescription != null)
|
||||
{
|
||||
var sanitized = Regex.Replace(message._FixDescription, @"<[^<>]*>", "'");
|
||||
s_FixButtonContent.tooltip = $"Fix: {sanitized}";
|
||||
}
|
||||
else
|
||||
{
|
||||
s_FixButtonContent.tooltip = "Fix issue";
|
||||
}
|
||||
|
||||
var canFix = message._Action != null;
|
||||
var canInspect = false;
|
||||
|
||||
// Jump to object button.
|
||||
if (message._Object != null)
|
||||
{
|
||||
// Selection.activeObject can be message._object.gameObject instead of the component
|
||||
// itself. We soft cast to MonoBehaviour to get the gameObject for comparison.
|
||||
// Alternatively, we could always pass gameObject instead of "this".
|
||||
var casted = message._Object as MonoBehaviour;
|
||||
|
||||
if (Selection.activeObject != message._Object && (casted == null || casted.gameObject != Selection.activeObject))
|
||||
{
|
||||
canInspect = true;
|
||||
}
|
||||
}
|
||||
|
||||
var result = EditorHelpers.HelpBox
|
||||
(
|
||||
new($"{message._Message} {message._FixDescription}"),
|
||||
messageType,
|
||||
canFix ? s_FixButtonContent : canInspect ? s_InspectButtonContent : null,
|
||||
buttons: canInspect && canFix ? s_FixButtonDropDown : null,
|
||||
buttonCenterLabel: needsAlignment,
|
||||
buttonMinimumWidth: 72
|
||||
);
|
||||
|
||||
if (canFix && result == -1)
|
||||
{
|
||||
// Run fix function.
|
||||
var serializedObject = new SerializedObject(message._Object);
|
||||
// Property is optional.
|
||||
var property = message._PropertyPath != null ? serializedObject?.FindProperty(message._PropertyPath) : null;
|
||||
var oldValue = property?.boxedValue;
|
||||
message._Action.Invoke(serializedObject, property);
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
// SerializedObject does this for us, but gives the history item a nicer label.
|
||||
Undo.RecordObject(message._Object, s_FixButtonContent.tooltip);
|
||||
DecoratedDrawer.OnChange(property, oldValue);
|
||||
}
|
||||
}
|
||||
else if (canInspect && result != null)
|
||||
{
|
||||
Selection.activeObject = message._Object;
|
||||
}
|
||||
|
||||
GUI.enabled = originalGUIEnabled;
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2125841df4154bd89cf5af6847a0d40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,238 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Editor.Internal;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Base editor. Needed as custom drawers require a custom editor to work.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(EditorBehaviour), editorForChildClasses: true)]
|
||||
partial class Inspector : UnityEditor.Editor
|
||||
{
|
||||
public const int k_FieldGroupOrder = 1000;
|
||||
const string k_NoPrefabModeSupportWarning = "Prefab mode is not supported. Changes made in prefab mode will not be reflected in the scene view. Save this prefab to see changes.";
|
||||
|
||||
internal static Inspector Current { get; private set; }
|
||||
|
||||
readonly Dictionary<FieldInfo, object> _MaterialOwners = new();
|
||||
readonly Dictionary<Material, MaterialEditor> _MaterialEditors = new();
|
||||
|
||||
public override bool RequiresConstantRepaint() => TexturePreview.s_ActiveInstance?.Open == true;
|
||||
|
||||
static readonly IOrderedEnumerable<FieldInfo> s_AttachMaterialEditors = TypeCache
|
||||
.GetFieldsWithAttribute<AttachMaterialEditor>()
|
||||
.OrderBy(x => x.GetCustomAttribute<AttachMaterialEditor>().Order);
|
||||
|
||||
readonly Utility.SortedList<int, SerializedProperty> _Properties = new(Helpers.DuplicateComparison);
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_MaterialOwners.Clear();
|
||||
|
||||
foreach (var field in s_AttachMaterialEditors)
|
||||
{
|
||||
var target = (object)this.target;
|
||||
|
||||
if (field.FindOwner(ref target))
|
||||
{
|
||||
_MaterialOwners.Add(field, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// Reset foldout values.
|
||||
DecoratedDrawer.s_IsFoldout = false;
|
||||
DecoratedDrawer.s_IsFoldoutOpen = false;
|
||||
|
||||
// Make sure we adhere to flags.
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = (target.hideFlags & HideFlags.NotEditable) == 0;
|
||||
|
||||
RenderBeforeInspectorGUI();
|
||||
RenderInspectorGUI();
|
||||
RenderValidationMessages();
|
||||
|
||||
EditorGUI.BeginDisabledGroup(target is Behaviour component && !component.isActiveAndEnabled);
|
||||
RenderBottomButtons();
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
RenderAfterInspectorGUI();
|
||||
|
||||
GUI.enabled = enabled;
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
foreach (var (_, editor) in _MaterialEditors)
|
||||
{
|
||||
Helpers.Destroy(editor);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RenderBeforeInspectorGUI()
|
||||
{
|
||||
if (this.target is EditorBehaviour target && target._IsPrefabStageInstance)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox(k_NoPrefabModeSupportWarning, MessageType.Warning);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RenderInspectorGUI()
|
||||
{
|
||||
var previous = Current;
|
||||
Current = this;
|
||||
|
||||
_Properties.Clear();
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
using var iterator = serializedObject.GetIterator();
|
||||
if (iterator.NextVisible(true))
|
||||
{
|
||||
var index = 0;
|
||||
var group = 0;
|
||||
Type type = null;
|
||||
|
||||
do
|
||||
{
|
||||
var property = serializedObject.FindProperty(iterator.name);
|
||||
if (iterator.name == "m_Script")
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
_Properties.Add(index++, property);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
var field = property.GetFieldInfo(out _);
|
||||
|
||||
// If field is on a new type (inheritance) then reset the group to prevent group leakage.
|
||||
if (field.DeclaringType != type)
|
||||
{
|
||||
group = 0;
|
||||
type = field.DeclaringType;
|
||||
}
|
||||
|
||||
// Null checking but there should always be one DecoratedProperty.
|
||||
var order = field.GetCustomAttribute<Attributes.DecoratedProperty>()?.order ?? 0;
|
||||
group = field.GetCustomAttribute<Group>()?.order * k_FieldGroupOrder ?? group;
|
||||
_Properties.Add(order + group + index++, property);
|
||||
}
|
||||
while (iterator.NextVisible(false));
|
||||
}
|
||||
|
||||
foreach (var (_, property) in _Properties)
|
||||
{
|
||||
#if CREST_DEBUG
|
||||
using (new EditorGUI.DisabledGroupScope(property.name == "m_Script"))
|
||||
#endif
|
||||
{
|
||||
// Only support top level ordering for now.
|
||||
EditorGUILayout.PropertyField(property, includeChildren: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to call just in case there is no decorated property.
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
// Restore previous in case this is a nested editor.
|
||||
Current = previous;
|
||||
|
||||
// Fixes indented validation etc.
|
||||
EditorGUI.indentLevel = 0;
|
||||
}
|
||||
|
||||
protected virtual void RenderBottomButtons()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void RenderAfterInspectorGUI()
|
||||
{
|
||||
foreach (var mapping in _MaterialOwners)
|
||||
{
|
||||
var material = (Material)mapping.Key.GetValue(mapping.Value);
|
||||
if (material != null)
|
||||
{
|
||||
DrawMaterialEditor(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from: http://answers.unity.com/answers/975894/view.html
|
||||
void DrawMaterialEditor(Material material)
|
||||
{
|
||||
MaterialEditor editor;
|
||||
if (!_MaterialEditors.ContainsKey(material))
|
||||
{
|
||||
editor = (MaterialEditor)CreateEditor(material);
|
||||
_MaterialEditors[material] = editor;
|
||||
}
|
||||
else
|
||||
{
|
||||
editor = _MaterialEditors[material];
|
||||
}
|
||||
|
||||
// Check material again as sometimes null.
|
||||
if (editor != null && material != null)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Draw the material's foldout and the material shader field. Required to call OnInspectorGUI.
|
||||
editor.DrawHeader();
|
||||
|
||||
var path = AssetDatabase.GetAssetPath(material);
|
||||
|
||||
// We need to prevent the user from editing Unity's default materials.
|
||||
// Check Editor.IsEnabled in Editor.cs for further filtering.
|
||||
var isEditable = (material.hideFlags & HideFlags.NotEditable) == 0;
|
||||
|
||||
#if !CREST_DEBUG
|
||||
// Do not allow editing of our assets.
|
||||
isEditable &= !path.StartsWithNoAlloc("Packages/com.waveharmonic");
|
||||
#endif
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(!isEditable))
|
||||
{
|
||||
// Draw the material properties. Works only if the foldout of DrawHeader is open.
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from:
|
||||
// https://gist.github.com/thebeardphantom/1ad9aee0ef8de6271fff39f1a6a3d66d
|
||||
static partial class Extensions
|
||||
{
|
||||
static readonly MethodInfo s_GetFieldInfoFromProperty;
|
||||
|
||||
static Extensions()
|
||||
{
|
||||
var utility = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility");
|
||||
Debug.Assert(utility != null, "Crest: ScriptAttributeUtility not found!");
|
||||
|
||||
s_GetFieldInfoFromProperty = utility.GetMethod("GetFieldInfoFromProperty", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
Debug.Assert(s_GetFieldInfoFromProperty != null, "Crest: GetFieldInfoFromProperty not found!");
|
||||
}
|
||||
|
||||
public static FieldInfo GetFieldInfo(this SerializedProperty property, out Type type)
|
||||
{
|
||||
type = null;
|
||||
return (FieldInfo)s_GetFieldInfoFromProperty.Invoke(null, new object[] { property, type });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db8aed30f95c447c3b9491865a600372
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a deprecated message to shaders.
|
||||
///
|
||||
/// USAGE
|
||||
/// Add to bottom of Shader block:
|
||||
/// CustomEditor "Crest.ObsoleteShaderGUI"
|
||||
/// Optionally add to Properties block:
|
||||
/// [HideInInspector] _ObsoleteMessage("The additional message.", Float) = 0
|
||||
/// </summary>
|
||||
sealed class ObsoleteShaderGUI : ShaderGUI
|
||||
{
|
||||
public override void OnGUI(MaterialEditor editor, MaterialProperty[] properties)
|
||||
{
|
||||
// Enable rich text in help boxes. Store original so we can revert since this might be a "hack".
|
||||
var styleRichText = GUI.skin.GetStyle("HelpBox").richText;
|
||||
GUI.skin.GetStyle("HelpBox").richText = true;
|
||||
|
||||
var message = "";
|
||||
|
||||
{
|
||||
var property = FindProperty("_Message", properties, propertyIsMandatory: false);
|
||||
if (property != null)
|
||||
{
|
||||
message += property.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var property = FindProperty("_ObsoleteMessage", properties, propertyIsMandatory: false);
|
||||
if (property != null)
|
||||
{
|
||||
message += "This shader is deprecated and will be removed in a future version. " + property.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(message, MessageType.Warning);
|
||||
EditorGUILayout.Space(3f);
|
||||
|
||||
// Revert skin since it persists.
|
||||
GUI.skin.GetStyle("HelpBox").richText = styleRichText;
|
||||
|
||||
// Render the default GUI.
|
||||
base.OnGUI(editor, properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a7b7bf49879b47f5b5e4fe887760848
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Helpers for using the Unity Package Manager.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.PackageManager;
|
||||
using UnityEditor.PackageManager.Requests;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
static class PackageManagerHelpers
|
||||
{
|
||||
static AddRequest s_Request;
|
||||
public static bool IsBusy => s_Request?.IsCompleted == false;
|
||||
|
||||
public static void AddMissingPackage(string packageName)
|
||||
{
|
||||
s_Request = Client.Add(packageName);
|
||||
EditorApplication.update += AddMissingPackageProgress;
|
||||
}
|
||||
|
||||
static void AddMissingPackageProgress()
|
||||
{
|
||||
if (s_Request.IsCompleted)
|
||||
{
|
||||
if (s_Request.Status == StatusCode.Success)
|
||||
{
|
||||
Debug.Log("Installed: " + s_Request.Result.packageId);
|
||||
}
|
||||
else if (s_Request.Status >= StatusCode.Failure)
|
||||
{
|
||||
Debug.Log(s_Request.Error.message);
|
||||
}
|
||||
|
||||
EditorApplication.update -= AddMissingPackageProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5138ba66b6604da09f58ac99ee035f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.Settings
|
||||
{
|
||||
static class ScriptingSymbols
|
||||
{
|
||||
static NamedBuildTarget CurrentNamedBuildTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
return NamedBuildTarget.Server;
|
||||
#else
|
||||
return NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] Symbols => PlayerSettings.GetScriptingDefineSymbols(CurrentNamedBuildTarget).Split(';');
|
||||
|
||||
public static void Add(string[] symbols)
|
||||
{
|
||||
// We remove our symbols from the list first to prevent duplicates - just to be safe.
|
||||
SetScriptingDefineSymbols(Symbols.Except(symbols).Concat(symbols).ToArray());
|
||||
}
|
||||
|
||||
public static void Add(string symbol)
|
||||
{
|
||||
Add(new string[] { symbol });
|
||||
}
|
||||
|
||||
public static void Remove(string[] symbols)
|
||||
{
|
||||
SetScriptingDefineSymbols(Symbols.Except(symbols).ToArray());
|
||||
}
|
||||
|
||||
public static void Remove(string symbol)
|
||||
{
|
||||
Remove(new string[] { symbol });
|
||||
}
|
||||
|
||||
public static void Set(string[] symbols, bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
Add(symbols);
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(symbols);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Set(string symbol, bool enable)
|
||||
{
|
||||
Set(new string[] { symbol }, enable);
|
||||
}
|
||||
|
||||
static void SetScriptingDefineSymbols(string[] symbols)
|
||||
{
|
||||
SetScriptingDefineSymbols(string.Join(";", symbols));
|
||||
}
|
||||
|
||||
static void SetScriptingDefineSymbols(string symbols)
|
||||
{
|
||||
PlayerSettings.SetScriptingDefineSymbols(CurrentNamedBuildTarget, symbols);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c03a1671f6a84ed4ac42813e95a07d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Exposes a customized version of Unity's shader generator task to only generate
|
||||
// our source files.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
static class ShaderGeneratorUtility
|
||||
{
|
||||
static MethodInfo s_GenerateAsync;
|
||||
static MethodInfo GenerateAsync => s_GenerateAsync ??= typeof(CSharpToHLSL)
|
||||
.GetMethod("GenerateAsync", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
|
||||
static async Task InvokeAsync(this MethodInfo methodInfo, object obj, params object[] parameters)
|
||||
{
|
||||
dynamic awaitable = methodInfo.Invoke(obj, parameters);
|
||||
await awaitable;
|
||||
}
|
||||
|
||||
// Adapted from:
|
||||
// https://github.com/Unity-Technologies/Graphics/blob/96ba978a240e96adcb2abceb21e90b24caa484a3/Packages/com.unity.render-pipelines.core/Editor/ShaderGenerator/CSharpToHLSL.cs#L18L53
|
||||
internal static async Task GenerateAll()
|
||||
{
|
||||
Dictionary<string, List<ShaderTypeGenerator>> sourceGenerators = null;
|
||||
try
|
||||
{
|
||||
// Store per source file path the generator definitions
|
||||
sourceGenerators = DictionaryPool<string, List<ShaderTypeGenerator>>.Get();
|
||||
|
||||
// Extract all types with the GenerateHLSL tag
|
||||
foreach (var type in TypeCache.GetTypesWithAttribute<GenerateHLSL>())
|
||||
{
|
||||
// Only generate our sources as Unity's will trigger a package refresh.
|
||||
if (!type.FullName.StartsWith("WaveHarmonic.Crest")) continue;
|
||||
|
||||
var attr = type.GetCustomAttributes(typeof(GenerateHLSL), false).First() as GenerateHLSL;
|
||||
if (!sourceGenerators.TryGetValue(attr.sourcePath, out var generators))
|
||||
{
|
||||
generators = ListPool<ShaderTypeGenerator>.Get();
|
||||
sourceGenerators.Add(attr.sourcePath, generators);
|
||||
}
|
||||
|
||||
generators.Add(new(type, attr));
|
||||
}
|
||||
|
||||
// We need to force the culture to invariant, otherwise generated code can replace characters.
|
||||
// For example, Turkish will replace "I" with "İ". This is a bug with GenerateAsync.
|
||||
var culture = System.Globalization.CultureInfo.CurrentCulture;
|
||||
System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
|
||||
|
||||
// Generate all files
|
||||
await Task.WhenAll(sourceGenerators.Select(async it => await GenerateAsync
|
||||
.InvokeAsync(null, new object[] { $"{it.Key}.hlsl", $"{Path.ChangeExtension(it.Key, "custom")}.hlsl", it.Value })));
|
||||
|
||||
System.Globalization.CultureInfo.CurrentCulture = culture;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Make sure we always release pooled resources
|
||||
if (sourceGenerators != null)
|
||||
{
|
||||
foreach (var pair in sourceGenerators)
|
||||
ListPool<ShaderTypeGenerator>.Release(pair.Value);
|
||||
DictionaryPool<string, List<ShaderTypeGenerator>>.Release(sourceGenerators);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bf974bd66ba04a1aa65908c4308b1b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,739 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Rendering.BuiltIn;
|
||||
using UnityEditor.Rendering.BuiltIn.ShaderGraph;
|
||||
using UnityEditor.ShaderGraph;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
using UnityEngine.UIElements;
|
||||
#else
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
#endif
|
||||
|
||||
using UnityBuiltInLitSubTarget = UnityEditor.Rendering.BuiltIn.ShaderGraph.BuiltInLitSubTarget;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor.ShaderGraph
|
||||
{
|
||||
sealed class MaterialModificationProcessor : AssetModificationProcessor
|
||||
{
|
||||
static void OnWillCreateAsset(string asset)
|
||||
{
|
||||
if (!asset.ToLowerInvariant().EndsWith(".mat"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialPostProcessor.s_CreatedAssets.Add(asset);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MaterialPostProcessor : AssetPostprocessor
|
||||
{
|
||||
public override int GetPostprocessOrder()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
internal static readonly List<string> s_CreatedAssets = new();
|
||||
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
foreach (var asset in importedAssets)
|
||||
{
|
||||
// We only care about materials
|
||||
if (!asset.EndsWith(".mat", System.StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Load the material and look for it's BuiltIn ShaderID.
|
||||
// We only care about versioning materials using a known BuiltIn ShaderID.
|
||||
// This skips any materials that only target other render pipelines, are user shaders,
|
||||
// or are shaders we don't care to version
|
||||
var material = (Material)AssetDatabase.LoadAssetAtPath(asset, typeof(Material));
|
||||
var shaderID = ShaderUtils.GetShaderID(material.shader);
|
||||
if (shaderID == ShaderUtils.ShaderID.Unknown)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (material.shader == null || material.shader.name != "Crest/Water")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for the BuiltIn AssetVersion
|
||||
AssetVersion assetVersion = null;
|
||||
var allAssets = AssetDatabase.LoadAllAssetsAtPath(asset);
|
||||
foreach (var subAsset in allAssets)
|
||||
{
|
||||
if (subAsset is AssetVersion sub)
|
||||
{
|
||||
assetVersion = sub;
|
||||
}
|
||||
}
|
||||
|
||||
if (!assetVersion)
|
||||
{
|
||||
if (s_CreatedAssets.Contains(asset))
|
||||
{
|
||||
s_CreatedAssets.Remove(asset);
|
||||
CustomBuiltInLitGUI.UpdateMaterial(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomBuiltInLitGUI : BuiltInLitGUI
|
||||
{
|
||||
MaterialEditor _MaterialEditor;
|
||||
MaterialProperty[] _Properties;
|
||||
|
||||
static readonly GUIContent s_WorkflowModeText = EditorGUIUtility.TrTextContent
|
||||
(
|
||||
"Workflow Mode",
|
||||
"Select a workflow that fits your textures. Choose between Metallic or Specular."
|
||||
);
|
||||
|
||||
static readonly GUIContent s_TransparentReceiveShadowsText = EditorGUIUtility.TrTextContent
|
||||
(
|
||||
"Receives Shadows",
|
||||
"When enabled, other GameObjects can cast shadows onto this GameObject."
|
||||
);
|
||||
|
||||
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
|
||||
{
|
||||
_MaterialEditor = materialEditor;
|
||||
_Properties = properties;
|
||||
|
||||
base.OnGUI(materialEditor, properties);
|
||||
}
|
||||
|
||||
public override void ValidateMaterial(Material material)
|
||||
{
|
||||
base.ValidateMaterial(material);
|
||||
UpdateMaterial(material);
|
||||
}
|
||||
|
||||
public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
|
||||
{
|
||||
base.AssignNewShaderToMaterial(material, oldShader, newShader);
|
||||
UpdateMaterial(material);
|
||||
}
|
||||
|
||||
protected override void DrawSurfaceOptions(Material material)
|
||||
{
|
||||
var materialEditor = _MaterialEditor;
|
||||
var properties = _Properties;
|
||||
|
||||
var workflowProperty = FindProperty(Property.SpecularWorkflowMode(), properties, false);
|
||||
if (workflowProperty != null)
|
||||
{
|
||||
DoPopup(s_WorkflowModeText, materialEditor, workflowProperty, System.Enum.GetNames(typeof(WorkflowMode)));
|
||||
}
|
||||
|
||||
base.DrawSurfaceOptions(material);
|
||||
|
||||
var surfaceTypeProp = FindProperty(Property.Surface(), properties, false);
|
||||
if (surfaceTypeProp != null && (SurfaceType)surfaceTypeProp.floatValue == SurfaceType.Transparent)
|
||||
{
|
||||
var trsProperty = FindProperty(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty, properties, false);
|
||||
DrawFloatToggleProperty(s_TransparentReceiveShadowsText, trsProperty);
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called by ShaderGraphMaterialsUpdater, but we will never upgrade.
|
||||
public static new void UpdateMaterial(Material material)
|
||||
{
|
||||
if (material.HasProperty(Property.SpecularWorkflowMode()))
|
||||
{
|
||||
var workflow = (WorkflowMode)material.GetFloat(Property.SpecularWorkflowMode());
|
||||
CoreUtils.SetKeyword(material, BuiltInLitSubTarget.LitDefines.s_SpecularSetup.referenceName, workflow == WorkflowMode.Specular);
|
||||
}
|
||||
|
||||
if (material.HasProperty(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty))
|
||||
{
|
||||
var receive = material.GetFloat(BuiltInLitSubTarget.s_TransparentReceiveShadowsProperty) == 1f;
|
||||
CoreUtils.SetKeyword(material, BuiltInLitSubTarget.LitDefines.s_TransparentReceivesShadows.referenceName, receive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class BuiltInLitSubTarget : BuiltInSubTarget
|
||||
{
|
||||
const string k_ShaderPath = "Packages/com.waveharmonic.crest/Runtime/Shaders/Library/Utility/Legacy";
|
||||
const string k_TemplatePath = "Packages/com.waveharmonic.crest/Editor/Shaders/Templates";
|
||||
|
||||
readonly UnityBuiltInLitSubTarget _BuiltInLitSubTarget;
|
||||
|
||||
#pragma warning disable IDE0032, IDE1006
|
||||
[SerializeField]
|
||||
WorkflowMode m_WorkflowMode = WorkflowMode.Metallic;
|
||||
|
||||
[SerializeField]
|
||||
NormalDropOffSpace m_NormalDropOffSpace = NormalDropOffSpace.Tangent;
|
||||
|
||||
[SerializeField]
|
||||
bool m_TransparentReceiveShadows = true;
|
||||
#pragma warning restore IDE0032, IDE1006
|
||||
|
||||
public static readonly string s_TransparentReceiveShadowsProperty = "_BUILTIN_TransparentReceiveShadows";
|
||||
|
||||
public BuiltInLitSubTarget()
|
||||
{
|
||||
_BuiltInLitSubTarget = new();
|
||||
displayName = _BuiltInLitSubTarget.displayName;
|
||||
}
|
||||
|
||||
protected override ShaderUtils.ShaderID shaderID => ShaderUtils.ShaderID.SG_Lit;
|
||||
public override bool IsActive() => true;
|
||||
|
||||
WorkflowMode WorkflowMode
|
||||
{
|
||||
get => m_WorkflowMode;
|
||||
set => m_WorkflowMode = value;
|
||||
}
|
||||
|
||||
NormalDropOffSpace NormalDropOffSpace
|
||||
{
|
||||
get => m_NormalDropOffSpace;
|
||||
set => m_NormalDropOffSpace = value;
|
||||
}
|
||||
|
||||
bool TransparentReceiveShadows
|
||||
{
|
||||
get => m_TransparentReceiveShadows;
|
||||
set => m_TransparentReceiveShadows = value;
|
||||
}
|
||||
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
static FieldInfo s_CustomEditorForRenderPipelines;
|
||||
static FieldInfo CustomEditorForRenderPipelines => s_CustomEditorForRenderPipelines ??= typeof(TargetSetupContext).GetField("customEditorForRenderPipelines", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
#endif
|
||||
|
||||
public override void Setup(ref TargetSetupContext context)
|
||||
{
|
||||
_BuiltInLitSubTarget.target = target;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
_BuiltInLitSubTarget.Setup(ref context);
|
||||
|
||||
// Caused a crash: !context.HasCustomEditorForRenderPipeline(null)
|
||||
if (string.IsNullOrEmpty(target.customEditorGUI))
|
||||
{
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
var editors = (List<ShaderCustomEditor>)CustomEditorForRenderPipelines.GetValue(context);
|
||||
if (editors.Count > 0)
|
||||
{
|
||||
editors.RemoveAt(editors.Count - 1);
|
||||
}
|
||||
|
||||
context.AddCustomEditorForRenderPipeline(typeof(CustomBuiltInLitGUI).FullName, "");
|
||||
#else
|
||||
if (context.customEditorForRenderPipelines.Count > 0)
|
||||
{
|
||||
context.customEditorForRenderPipelines.RemoveAt(context.customEditorForRenderPipelines.Count - 1);
|
||||
}
|
||||
|
||||
context.customEditorForRenderPipelines.Add((typeof(CustomBuiltInLitGUI).FullName, ""));
|
||||
#endif
|
||||
}
|
||||
|
||||
context.subShaders.RemoveAt(0);
|
||||
context.AddSubShader(SubShaders.Lit(this));
|
||||
}
|
||||
|
||||
public override void ProcessPreviewMaterial(Material material)
|
||||
{
|
||||
_BuiltInLitSubTarget.target = target;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
_BuiltInLitSubTarget.ProcessPreviewMaterial(material);
|
||||
CustomBuiltInLitGUI.UpdateMaterial(material);
|
||||
}
|
||||
|
||||
public override void GetFields(ref TargetFieldContext context)
|
||||
{
|
||||
_BuiltInLitSubTarget.target = target;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
_BuiltInLitSubTarget.GetFields(ref context);
|
||||
// Do not use this, as we handle this properly.
|
||||
context.AddField(BuiltInFields.SpecularSetup, false);
|
||||
}
|
||||
|
||||
public override void GetActiveBlocks(ref TargetActiveBlockContext context)
|
||||
{
|
||||
_BuiltInLitSubTarget.target = target;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
_BuiltInLitSubTarget.GetActiveBlocks(ref context);
|
||||
|
||||
context.activeBlocks.Remove(BlockFields.SurfaceDescription.Metallic);
|
||||
var insertion = context.activeBlocks.FindIndex(x => x == BlockFields.SurfaceDescription.Occlusion) + 1;
|
||||
|
||||
if ((WorkflowMode == WorkflowMode.Specular) || target.allowMaterialOverride)
|
||||
{
|
||||
context.activeBlocks.Insert(insertion, BlockFields.SurfaceDescription.Specular);
|
||||
}
|
||||
|
||||
if ((WorkflowMode == WorkflowMode.Metallic) || target.allowMaterialOverride)
|
||||
{
|
||||
context.activeBlocks.Insert(insertion, BlockFields.SurfaceDescription.Metallic);
|
||||
}
|
||||
}
|
||||
|
||||
public override void CollectShaderProperties(PropertyCollector collector, GenerationMode generationMode)
|
||||
{
|
||||
if (target.allowMaterialOverride)
|
||||
{
|
||||
collector.AddFloatProperty(Property.SpecularWorkflowMode(), (float)WorkflowMode);
|
||||
}
|
||||
|
||||
_BuiltInLitSubTarget.target = target;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
_BuiltInLitSubTarget.CollectShaderProperties(collector, generationMode);
|
||||
|
||||
if (target.allowMaterialOverride)
|
||||
{
|
||||
collector.AddFloatProperty(s_TransparentReceiveShadowsProperty, TransparentReceiveShadows ? 1f : 0f);
|
||||
}
|
||||
|
||||
// LEqual
|
||||
collector.AddFloatProperty(SubShaders.k_ShadowCasterZTest, 4, UnityEditor.ShaderGraph.Internal.HLSLDeclaration.UnityPerMaterial);
|
||||
}
|
||||
|
||||
public override void GetPropertiesGUI(ref TargetPropertyGUIContext context, System.Action onChange, System.Action<string> registerUndo)
|
||||
{
|
||||
target.AddDefaultMaterialOverrideGUI(ref context, onChange, registerUndo);
|
||||
|
||||
context.AddProperty("Workflow", new EnumField(WorkflowMode.Metallic) { value = WorkflowMode }, (evt) =>
|
||||
{
|
||||
if (Equals(WorkflowMode, evt.newValue))
|
||||
return;
|
||||
|
||||
registerUndo("Change Workflow");
|
||||
WorkflowMode = (WorkflowMode)evt.newValue;
|
||||
onChange();
|
||||
});
|
||||
|
||||
target.GetDefaultSurfacePropertiesGUI(ref context, onChange, registerUndo);
|
||||
|
||||
context.AddProperty("Transparent Receives Shadows", new Toggle() { value = TransparentReceiveShadows }, (evt) =>
|
||||
{
|
||||
if (Equals(TransparentReceiveShadows, evt.newValue))
|
||||
return;
|
||||
|
||||
registerUndo("Change Transparent Receives Shadows");
|
||||
TransparentReceiveShadows = evt.newValue;
|
||||
onChange();
|
||||
});
|
||||
|
||||
context.AddProperty("Fragment Normal Space", new EnumField(NormalDropOffSpace.Tangent) { value = NormalDropOffSpace }, (evt) =>
|
||||
{
|
||||
if (Equals(NormalDropOffSpace, evt.newValue))
|
||||
return;
|
||||
|
||||
registerUndo("Change Fragment Normal Space");
|
||||
NormalDropOffSpace = (NormalDropOffSpace)evt.newValue;
|
||||
_BuiltInLitSubTarget.normalDropOffSpace = NormalDropOffSpace;
|
||||
onChange();
|
||||
});
|
||||
}
|
||||
|
||||
static class SubShaders
|
||||
{
|
||||
static readonly string s_ShaderPathDefines = $"{k_ShaderPath}/Defines.hlsl";
|
||||
static readonly string s_ShaderPathBuilding = $"{k_ShaderPath}/LegacyBuilding.hlsl";
|
||||
|
||||
// SetShaderPassEnabled on ShadowCaster pass does not work for BIRP. We set ZTest
|
||||
// to Never which is the best we can do. We are still incurring the draw call cost.
|
||||
// This is an issue because of the way we trigger motion vectors, but is a bug with
|
||||
// Unity and should be reported.
|
||||
internal const string k_ShadowCasterZTest = "_Crest_BUILTIN_ShadowCasterZTest";
|
||||
|
||||
internal static System.Type s_SubShadersType;
|
||||
internal static System.Type SubShadersType => s_SubShadersType ??= typeof(UnityBuiltInLitSubTarget).GetNestedType("SubShaders", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
internal static MethodInfo s_LitMethod;
|
||||
internal static MethodInfo LitMethod => s_LitMethod ??= SubShadersType.GetMethod("Lit", BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
static void PatchIncludes(ref PassDescriptor result)
|
||||
{
|
||||
var includes = new IncludeCollection();
|
||||
|
||||
includes.Add(s_ShaderPathDefines, IncludeLocation.Pregraph);
|
||||
includes.Add("Packages/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/Editor/ShaderGraph/Includes/ShaderPass.hlsl", IncludeLocation.Pregraph);
|
||||
|
||||
foreach (var include in result.includes)
|
||||
{
|
||||
includes.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
|
||||
}
|
||||
|
||||
result.includes = includes;
|
||||
}
|
||||
|
||||
static void PatchSpecularIncludes(ref PassDescriptor result, string file)
|
||||
{
|
||||
var ic = new IncludeCollection();
|
||||
foreach (var include in result.includes)
|
||||
{
|
||||
if (include.path.EndsWith(file))
|
||||
{
|
||||
ic.Add(s_ShaderPathBuilding, include.location);
|
||||
ic.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
|
||||
}
|
||||
else
|
||||
{
|
||||
ic.AddInternal(include.guid, include.path, include.location, include.fieldConditions);
|
||||
}
|
||||
}
|
||||
|
||||
result.includes = ic;
|
||||
}
|
||||
|
||||
static readonly Dictionary<string, string> s_Mappings = new()
|
||||
{
|
||||
{ "SHADERPASS_FORWARD", "PBRForwardPass.hlsl" },
|
||||
{ "SHADERPASS_FORWARD_ADD", "PBRForwardAddPass.hlsl" },
|
||||
{ "SHADERPASS_DEFERRED", "PBRDeferredPass.hlsl" },
|
||||
};
|
||||
|
||||
static readonly string[] s_SkipVariants = new string[]
|
||||
{
|
||||
"LIGHTMAP_ON",
|
||||
"LIGHTMAP_SHADOW_MIXING",
|
||||
"DIRLIGHTMAP_COMBINED",
|
||||
"DYNAMICLIGHTMAP_ON",
|
||||
"SHADOWS_SHADOWMASK",
|
||||
};
|
||||
|
||||
public static SubShaderDescriptor Lit(BuiltInLitSubTarget subtarget)
|
||||
{
|
||||
var target = subtarget.target;
|
||||
var ssd = (SubShaderDescriptor)LitMethod.Invoke(null, new object[] { target, target.renderType, target.renderQueue });
|
||||
|
||||
PassCollection passes = new();
|
||||
|
||||
foreach (var item in ssd.passes)
|
||||
{
|
||||
// Many artifacts in U6 if our Write Depth enabled.
|
||||
// Caused by _SURFACE_TYPE_TRANSPARENT in m_ValidKeywords.
|
||||
if (item.descriptor.referenceName == "SceneSelectionPass")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = item.descriptor;
|
||||
|
||||
var keywords = new KeywordCollection();
|
||||
|
||||
foreach (var keyword in result.keywords)
|
||||
{
|
||||
// All others are either duplicate or unused.
|
||||
if (!keyword.descriptor.referenceName.StartsWith("_BUILTIN_"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
keywords.Add(keyword.descriptor, keyword.fieldConditions);
|
||||
}
|
||||
|
||||
result.keywords = keywords;
|
||||
|
||||
switch (item.descriptor.referenceName)
|
||||
{
|
||||
case "SHADERPASS_FORWARD":
|
||||
case "SHADERPASS_FORWARD_ADD":
|
||||
case "SHADERPASS_DEFERRED":
|
||||
AddWorkflowModeControlToPass(ref result, target, subtarget.WorkflowMode);
|
||||
PatchSpecularIncludes(ref result, s_Mappings[item.descriptor.referenceName]);
|
||||
|
||||
var pragmas = new PragmaCollection();
|
||||
foreach (var pragma in result.pragmas)
|
||||
{
|
||||
// For UAVs (RWStructuredBuffer).
|
||||
if (pragma.descriptor.value.StartsWithNoAlloc("target"))
|
||||
{
|
||||
pragmas.Add(Pragma.Target(ShaderModel.Target45));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pragma.descriptor.value.StartsWithNoAlloc("vertex"))
|
||||
{
|
||||
pragmas.Add(Pragma.SkipVariants(s_SkipVariants));
|
||||
}
|
||||
|
||||
pragmas.Add(pragma.descriptor, pragma.fieldConditions);
|
||||
}
|
||||
result.pragmas = pragmas;
|
||||
|
||||
goto default;
|
||||
default:
|
||||
PatchIncludes(ref result);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (item.descriptor.referenceName)
|
||||
{
|
||||
case "SHADERPASS_FORWARD":
|
||||
case "SHADERPASS_FORWARD_ADD":
|
||||
AddReceivesShadowsControlToPass(ref result, target, subtarget.TransparentReceiveShadows);
|
||||
break;
|
||||
case "SHADERPASS_SHADOWCASTER":
|
||||
var states = new RenderStateCollection();
|
||||
foreach (var state in result.renderStates)
|
||||
{
|
||||
if (state.descriptor.type == RenderStateType.ZTest)
|
||||
{
|
||||
states.Add(RenderState.ZTest($"[{k_ShadowCasterZTest}]"));
|
||||
continue;
|
||||
}
|
||||
|
||||
states.Add(state.descriptor, state.fieldConditions);
|
||||
}
|
||||
|
||||
result.renderStates = states;
|
||||
break;
|
||||
}
|
||||
|
||||
// Add missing cull render state.
|
||||
if (item.descriptor.referenceName == "SHADERPASS_FORWARD_ADD")
|
||||
{
|
||||
CoreRenderStates.AddUberSwitchedCull(target, result.renderStates);
|
||||
}
|
||||
|
||||
// Inject MV before DO pass.
|
||||
if (item.descriptor.referenceName == "SHADERPASS_DEPTHONLY")
|
||||
{
|
||||
var mv = LitPasses.MotionVectors(target);
|
||||
PatchIncludes(ref mv);
|
||||
passes.Add(mv);
|
||||
}
|
||||
|
||||
// Fix XR SPI.
|
||||
if (result.requiredFields != null)
|
||||
{
|
||||
var found = false;
|
||||
|
||||
foreach (var collection in result.requiredFields)
|
||||
{
|
||||
if (collection.field == StructFields.Attributes.instanceID)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
result.requiredFields.Add(StructFields.Attributes.instanceID);
|
||||
}
|
||||
}
|
||||
|
||||
passes.Add(result);
|
||||
}
|
||||
|
||||
ssd.passes = passes;
|
||||
|
||||
return ssd;
|
||||
}
|
||||
|
||||
static void AddWorkflowModeControlToPass(ref PassDescriptor pass, BuiltInTarget target, WorkflowMode workflowMode)
|
||||
{
|
||||
if (target.allowMaterialOverride)
|
||||
{
|
||||
pass.keywords.Add(LitDefines.s_SpecularSetup);
|
||||
}
|
||||
else if (workflowMode == WorkflowMode.Specular)
|
||||
{
|
||||
pass.defines.Add(LitDefines.s_SpecularSetup, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddReceivesShadowsControlToPass(ref PassDescriptor pass, BuiltInTarget target, bool receives)
|
||||
{
|
||||
if (target.allowMaterialOverride)
|
||||
{
|
||||
pass.keywords.Add(LitDefines.s_TransparentReceivesShadows);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSingleCascade);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSplitSpheres);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSoft);
|
||||
}
|
||||
else if (receives)
|
||||
{
|
||||
pass.defines.Add(LitDefines.s_TransparentReceivesShadows, 1);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSingleCascade);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSplitSpheres);
|
||||
pass.keywords.Add(LitDefines.s_ShadowsSoft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class LitPasses
|
||||
{
|
||||
static readonly string s_ShaderPathMotionVectorCommon = $"{k_ShaderPath}/MotionVectorCommon.hlsl";
|
||||
static readonly string s_ShaderPathMotionVectorPass = $"{k_ShaderPath}/MotionVectorPass.hlsl";
|
||||
|
||||
public static RenderStateDescriptor UberSwitchedCullRenderState(BuiltInTarget target)
|
||||
{
|
||||
if (target.allowMaterialOverride)
|
||||
{
|
||||
return RenderState.Cull(CoreRenderStates.Uniforms.cullMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RenderState.Cull(CoreRenderStates.RenderFaceToCull(target.renderFace));
|
||||
}
|
||||
}
|
||||
|
||||
public static PassDescriptor MotionVectors(BuiltInTarget target)
|
||||
{
|
||||
var result = new PassDescriptor()
|
||||
{
|
||||
// Definition
|
||||
displayName = "BuiltIn MotionVectors",
|
||||
referenceName = "SHADERPASS_MOTION_VECTORS",
|
||||
lightMode = "MotionVectors",
|
||||
useInPreview = false,
|
||||
|
||||
// Template
|
||||
passTemplatePath = BuiltInTarget.kTemplatePath,
|
||||
sharedTemplateDirectories = BuiltInTarget.kSharedTemplateDirectories.Union
|
||||
(
|
||||
new string[]
|
||||
{
|
||||
k_TemplatePath,
|
||||
"Packages/com.unity.shadergraph/Editor/Generation/Targets/BuiltIn/Editor/ShaderGraph"
|
||||
}
|
||||
).ToArray(),
|
||||
|
||||
// Port Mask
|
||||
validVertexBlocks = new BlockFieldDescriptor[]
|
||||
{
|
||||
BlockFields.VertexDescription.Position,
|
||||
},
|
||||
validPixelBlocks = CoreBlockMasks.FragmentAlphaOnly,
|
||||
|
||||
// Fields
|
||||
structs = CoreStructCollections.Default,
|
||||
requiredFields = new()
|
||||
{
|
||||
// Needed for XR, but not sure if correct.
|
||||
StructFields.Attributes.instanceID,
|
||||
},
|
||||
fieldDependencies = CoreFieldDependencies.Default,
|
||||
|
||||
// Conditional State
|
||||
renderStates = new()
|
||||
{
|
||||
{ RenderState.ZTest(ZTest.LEqual) },
|
||||
{ RenderState.ZWrite(ZWrite.On) },
|
||||
{ UberSwitchedCullRenderState(target) },
|
||||
// MVs write to the depth buffer causing z-fighting. Luckily, the depth texture has
|
||||
// already been updated, and will not be updated before water renders.
|
||||
{ RenderState.ColorMask("ColorMask RG\nOffset 1, 1") },
|
||||
},
|
||||
|
||||
pragmas = new()
|
||||
{
|
||||
{ Pragma.Target(ShaderModel.Target35) }, // NOTE: SM 2.0 only GL
|
||||
{ Pragma.MultiCompileInstancing },
|
||||
{ Pragma.Vertex("vert") },
|
||||
{ Pragma.Fragment("frag") },
|
||||
},
|
||||
|
||||
defines = new() { CoreDefines.BuiltInTargetAPI },
|
||||
keywords = new(),
|
||||
includes = new()
|
||||
{
|
||||
// Pre-graph
|
||||
{ CoreIncludes.CorePregraph },
|
||||
{ CoreIncludes.ShaderGraphPregraph },
|
||||
|
||||
// Post-graph
|
||||
{ s_ShaderPathMotionVectorCommon, IncludeLocation.Postgraph },
|
||||
{ CoreIncludes.CorePostgraph },
|
||||
{ s_ShaderPathMotionVectorPass, IncludeLocation.Postgraph },
|
||||
},
|
||||
|
||||
// Custom Interpolator Support
|
||||
customInterpolators = CoreCustomInterpDescriptors.Common,
|
||||
};
|
||||
|
||||
// Only support time for now.
|
||||
result.defines.Add(LitDefines.s_AutomaticTimeBasedMotionVectors, 1);
|
||||
|
||||
CorePasses.AddAlphaClipControlToPass(ref result, target);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LitDefines
|
||||
{
|
||||
public static readonly KeywordDescriptor s_AutomaticTimeBasedMotionVectors = new()
|
||||
{
|
||||
displayName = "Automatic Time-Based Motion Vectors",
|
||||
referenceName = "AUTOMATIC_TIME_BASED_MOTION_VECTORS",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.Predefined,
|
||||
scope = KeywordScope.Local,
|
||||
stages = KeywordShaderStage.Vertex,
|
||||
};
|
||||
|
||||
public static readonly KeywordDescriptor s_SpecularSetup = new()
|
||||
{
|
||||
displayName = "Specular Setup",
|
||||
referenceName = "_BUILTIN_SPECULAR_SETUP",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.ShaderFeature,
|
||||
scope = KeywordScope.Local,
|
||||
stages = KeywordShaderStage.Fragment
|
||||
};
|
||||
|
||||
public static readonly KeywordDescriptor s_TransparentReceivesShadows = new()
|
||||
{
|
||||
displayName = "Transparent Receives Shadows",
|
||||
referenceName = "_BUILTIN_TRANSPARENT_RECEIVES_SHADOWS",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.ShaderFeature,
|
||||
scope = KeywordScope.Local,
|
||||
stages = KeywordShaderStage.Fragment
|
||||
};
|
||||
|
||||
public static readonly KeywordDescriptor s_ShadowsSingleCascade = new()
|
||||
{
|
||||
displayName = "Single Cascade Shadows",
|
||||
referenceName = "SHADOWS_SINGLE_CASCADE",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.MultiCompile,
|
||||
scope = KeywordScope.Global,
|
||||
stages = KeywordShaderStage.All,
|
||||
};
|
||||
|
||||
public static readonly KeywordDescriptor s_ShadowsSoft = new()
|
||||
{
|
||||
displayName = "Soft Shadows",
|
||||
referenceName = "SHADOWS_SOFT",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.MultiCompile,
|
||||
scope = KeywordScope.Global,
|
||||
stages = KeywordShaderStage.All,
|
||||
};
|
||||
|
||||
public static readonly KeywordDescriptor s_ShadowsSplitSpheres = new()
|
||||
{
|
||||
displayName = "Stable Fit Shadows",
|
||||
referenceName = "SHADOWS_SPLIT_SPHERES",
|
||||
type = KeywordType.Boolean,
|
||||
definition = KeywordDefinition.MultiCompile,
|
||||
scope = KeywordScope.Global,
|
||||
stages = KeywordShaderStage.All,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4c99fd6915934b21a43efb6ca9915f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor;
|
||||
using UnityEditor.ShaderGraph;
|
||||
using UnityEditor.ShaderGraph.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
[assembly: InternalsVisibleTo("WaveHarmonic.Crest.Editor")]
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
internal static class ShaderGraphPropertyDrawers
|
||||
{
|
||||
static Dictionary<GraphInputData, bool> s_CompoundPropertyFoldoutStates = new();
|
||||
static Dictionary<string, string> s_Tooltips;
|
||||
static readonly GUIContent s_Label = new();
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, Dictionary<string, string> tooltips)
|
||||
{
|
||||
s_Tooltips = tooltips;
|
||||
Material m = materialEditor.target as Material;
|
||||
Shader s = m.shader;
|
||||
string path = AssetDatabase.GetAssetPath(s);
|
||||
ShaderGraphMetadata metadata = null;
|
||||
foreach (var obj in AssetDatabase.LoadAllAssetsAtPath(path))
|
||||
{
|
||||
if (obj is ShaderGraphMetadata meta)
|
||||
{
|
||||
metadata = meta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata != null)
|
||||
DrawShaderGraphGUI(materialEditor, properties, metadata.categoryDatas);
|
||||
else
|
||||
PropertiesDefaultGUI(materialEditor, properties);
|
||||
}
|
||||
|
||||
static void PropertiesDefaultGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties)
|
||||
{
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if ((property.flags & (MaterialProperty.PropFlags.HideInInspector | MaterialProperty.PropFlags.PerRendererData)) != 0)
|
||||
continue;
|
||||
|
||||
float h = materialEditor.GetPropertyHeight(property, property.displayName);
|
||||
Rect r = EditorGUILayout.GetControlRect(true, h, EditorStyles.layerMaskField);
|
||||
|
||||
s_Label.text = property.displayName;
|
||||
s_Label.tooltip = s_Tooltips.GetValueOrDefault(property.name, null);
|
||||
materialEditor.ShaderProperty(r, property, s_Label);
|
||||
}
|
||||
}
|
||||
|
||||
static Rect GetRect(MaterialProperty prop)
|
||||
{
|
||||
return EditorGUILayout.GetControlRect(true, MaterialEditor.GetDefaultPropertyHeight(prop));
|
||||
}
|
||||
|
||||
static MaterialProperty FindProperty(string propertyName, IEnumerable<MaterialProperty> properties)
|
||||
{
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
if (prop.name == propertyName)
|
||||
{
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DrawShaderGraphGUI(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, IEnumerable<MinimalCategoryData> categoryDatas)
|
||||
{
|
||||
foreach (MinimalCategoryData mcd in categoryDatas)
|
||||
{
|
||||
DrawCategory(materialEditor, properties, mcd);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawCategory(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, MinimalCategoryData minimalCategoryData)
|
||||
{
|
||||
if (minimalCategoryData.categoryName.Length > 0)
|
||||
{
|
||||
minimalCategoryData.expanded = EditorGUILayout.BeginFoldoutHeaderGroup(minimalCategoryData.expanded, minimalCategoryData.categoryName);
|
||||
}
|
||||
else
|
||||
{
|
||||
// force draw if no category name to do foldout on
|
||||
minimalCategoryData.expanded = true;
|
||||
}
|
||||
|
||||
if (minimalCategoryData.expanded)
|
||||
{
|
||||
foreach (var propData in minimalCategoryData.propertyDatas)
|
||||
{
|
||||
if (propData.isCompoundProperty == false)
|
||||
{
|
||||
MaterialProperty prop = FindProperty(propData.referenceName, properties);
|
||||
if (prop == null) continue;
|
||||
DrawMaterialProperty(materialEditor, prop, propData.propertyType, propData.isKeyword, propData.keywordType);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawCompoundProperty(materialEditor, properties, propData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndFoldoutHeaderGroup();
|
||||
}
|
||||
|
||||
static void DrawCompoundProperty(MaterialEditor materialEditor, IEnumerable<MaterialProperty> properties, GraphInputData compoundPropertyData)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
bool foldoutState = true;
|
||||
var exists = s_CompoundPropertyFoldoutStates.ContainsKey(compoundPropertyData);
|
||||
if (!exists)
|
||||
s_CompoundPropertyFoldoutStates.Add(compoundPropertyData, true);
|
||||
else
|
||||
foldoutState = s_CompoundPropertyFoldoutStates[compoundPropertyData];
|
||||
|
||||
foldoutState = EditorGUILayout.Foldout(foldoutState, compoundPropertyData.referenceName);
|
||||
if (foldoutState)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
foreach (var subProperty in compoundPropertyData.subProperties)
|
||||
{
|
||||
var property = FindProperty(subProperty.referenceName, properties);
|
||||
if (property == null) continue;
|
||||
DrawMaterialProperty(materialEditor, property, subProperty.propertyType);
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
if (exists)
|
||||
s_CompoundPropertyFoldoutStates[compoundPropertyData] = foldoutState;
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
static void DrawMaterialProperty(MaterialEditor materialEditor, MaterialProperty property, PropertyType propertyType, bool isKeyword = false, KeywordType keywordType = KeywordType.Boolean)
|
||||
{
|
||||
if (!isKeyword)
|
||||
{
|
||||
switch (propertyType)
|
||||
{
|
||||
case PropertyType.SamplerState:
|
||||
case PropertyType.Matrix4:
|
||||
case PropertyType.Matrix3:
|
||||
case PropertyType.Matrix2:
|
||||
case PropertyType.VirtualTexture:
|
||||
case PropertyType.Gradient:
|
||||
return;
|
||||
case PropertyType.Vector3:
|
||||
DrawVector3Property(materialEditor, property);
|
||||
return;
|
||||
case PropertyType.Vector2:
|
||||
DrawVector2Property(materialEditor, property);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s_Label.text = property.displayName;
|
||||
s_Label.tooltip = s_Tooltips.GetValueOrDefault(property.name, null);
|
||||
materialEditor.ShaderProperty(property, s_Label);
|
||||
}
|
||||
|
||||
static void DrawVector2Property(MaterialEditor materialEditor, MaterialProperty property)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
Vector2 newValue = EditorGUI.Vector2Field(GetRect(property), property.displayName, new Vector2(property.vectorValue.x, property.vectorValue.y));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.vectorValue = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawVector3Property(MaterialEditor materialEditor, MaterialProperty property)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = property.hasMixedValue;
|
||||
Vector3 newValue = EditorGUI.Vector3Field(GetRect(property), property.displayName, new Vector3(property.vectorValue.x, property.vectorValue.y, property.vectorValue.z));
|
||||
EditorGUI.showMixedValue = false;
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.vectorValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ee5c95ce93114c1c909f5b1346d8bb0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
abstract class TexturePreview : ObjectPreview
|
||||
{
|
||||
public static TexturePreview s_ActiveInstance;
|
||||
public bool Open { get; private set; }
|
||||
|
||||
UnityEditor.Editor _Editor;
|
||||
RenderTexture _RenderTexture;
|
||||
RenderTextureDescriptor _OriginalDescriptor = new();
|
||||
Texture _Current;
|
||||
protected int _Slice;
|
||||
bool _First = true;
|
||||
|
||||
protected abstract Texture OriginalTexture { get; }
|
||||
protected virtual Texture ModifiedTexture { get; }
|
||||
|
||||
Texture Texture => ModifiedTexture != null ? ModifiedTexture : OriginalTexture;
|
||||
|
||||
protected virtual bool Flipped => false;
|
||||
|
||||
// Preview complains if not a certain set of formats.
|
||||
bool Incompatible => !(GraphicsFormatUtility.IsIEEE754Format(Texture.graphicsFormat)
|
||||
|| GraphicsFormatUtility.IsNormFormat(Texture.graphicsFormat));
|
||||
|
||||
public TexturePreview() { }
|
||||
|
||||
public override bool HasPreviewGUI()
|
||||
{
|
||||
if (Event.current != null && Event.current.type == EventType.Layout)
|
||||
{
|
||||
Open = false;
|
||||
}
|
||||
|
||||
return OriginalTexture;
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
base.Cleanup();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
if (_RenderTexture != null) _RenderTexture.Release();
|
||||
Object.DestroyImmediate(_RenderTexture);
|
||||
}
|
||||
|
||||
public override void OnPreviewSettings()
|
||||
{
|
||||
if (_First && Event.current.type == EventType.Repaint && !Application.isPlaying)
|
||||
{
|
||||
// Solves on enter edit mode:
|
||||
// ArgumentException: Getting control 2's position in a group with only 2 controls when doing repaint
|
||||
_First = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_Editor != null) _Editor.OnPreviewSettings();
|
||||
}
|
||||
|
||||
public override void OnPreviewGUI(Rect rect, GUIStyle background)
|
||||
{
|
||||
s_ActiveInstance = this;
|
||||
Open = true;
|
||||
|
||||
// This check is in original.
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
background.Draw(rect, false, false, false, false);
|
||||
}
|
||||
|
||||
if (Texture is Cubemap)
|
||||
{
|
||||
if (_Editor == null || _Editor.target != Texture)
|
||||
{
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = UnityEditor.Editor.CreateEditor(Texture);
|
||||
}
|
||||
|
||||
_Editor.DrawPreview(rect);
|
||||
return;
|
||||
}
|
||||
|
||||
var descriptor = Texture.GetDescriptor();
|
||||
|
||||
if (Helpers.RenderTextureNeedsUpdating(descriptor, _OriginalDescriptor))
|
||||
{
|
||||
_OriginalDescriptor = descriptor;
|
||||
|
||||
if (Incompatible)
|
||||
{
|
||||
descriptor.graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
|
||||
}
|
||||
|
||||
Helpers.SafeCreateRenderTexture(ref _RenderTexture, descriptor);
|
||||
_RenderTexture.Create();
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = UnityEditor.Editor.CreateEditor(_RenderTexture);
|
||||
// Reset for incompatible copy.
|
||||
descriptor = _OriginalDescriptor;
|
||||
}
|
||||
|
||||
// Name may change without texture changing (see SWS).
|
||||
_RenderTexture.name = Texture.name + " (Preview)";
|
||||
|
||||
if (Incompatible)
|
||||
{
|
||||
var temporary = RenderTexture.GetTemporary(descriptor);
|
||||
Graphics.CopyTexture(Texture, temporary);
|
||||
Helpers.Blit(temporary, _RenderTexture);
|
||||
RenderTexture.ReleaseTemporary(temporary);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.CopyTexture(Texture, _RenderTexture);
|
||||
}
|
||||
|
||||
_Editor.DrawPreview(rect);
|
||||
}
|
||||
|
||||
#if CREST_DEBUG
|
||||
public override void OnInteractivePreviewGUI(Rect rect, GUIStyle background)
|
||||
{
|
||||
OnPreviewGUI(rect, background);
|
||||
|
||||
if (Texture is Cubemap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show pixel value in preview.
|
||||
_Slice = Development.Utility.GetPreviewSlice(_Editor, Texture);
|
||||
var color = Development.Utility.InspectPixel(rect, OriginalTexture, Flipped, _Slice);
|
||||
var text = Development.Utility.GetInspectPixelString(color, OriginalTexture);
|
||||
EditorGUI.DropShadowLabel(new Rect(rect.x, rect.y, rect.width, 40), text);
|
||||
}
|
||||
#endif
|
||||
|
||||
void Allocate(Texture texture)
|
||||
{
|
||||
// LOD with buffered data like foam will recreate every frame freezing controls.
|
||||
if (_Editor != null && _Current == Texture) return;
|
||||
_Current = texture;
|
||||
Object.DestroyImmediate(_Editor);
|
||||
_Editor = UnityEditor.Editor.CreateEditor(texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c817389e2ce240f69487a0e78fc7091
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,302 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// How to use:
|
||||
// Use or inherit from Crest.Editor.Inspector to support validation messages.
|
||||
// Then create a static method with Validator attribute.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest.Editor
|
||||
{
|
||||
using FixValidation = System.Action<SerializedObject, SerializedProperty>;
|
||||
|
||||
[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
sealed class Validator : System.Attribute
|
||||
{
|
||||
public readonly System.Type _Type;
|
||||
|
||||
public Validator(System.Type type)
|
||||
{
|
||||
_Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
// Holds the shared list for messages
|
||||
static class ValidatedHelper
|
||||
{
|
||||
public enum MessageType
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
}
|
||||
|
||||
public struct HelpBoxMessage
|
||||
{
|
||||
public string _Message;
|
||||
public string _FixDescription;
|
||||
public Object _Object;
|
||||
public FixValidation _Action;
|
||||
public string _PropertyPath;
|
||||
}
|
||||
|
||||
// This is a shared resource. It will be cleared before use. It is only used by the HelpBox delegate since we
|
||||
// want to group them by severity (MessageType). Make sure length matches MessageType length.
|
||||
public static readonly List<HelpBoxMessage>[] s_Messages = new List<HelpBoxMessage>[]
|
||||
{
|
||||
new(),
|
||||
new(),
|
||||
new(),
|
||||
};
|
||||
|
||||
public delegate void ShowMessage(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null);
|
||||
|
||||
public static void DebugLog(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
{
|
||||
// Never log info validation to console.
|
||||
if (type == MessageType.Info)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
message = $"Crest Validation: {message} {fixDescription} Click this message to highlight the problem object.";
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Error: Debug.LogError(message, @object); break;
|
||||
case MessageType.Warning: Debug.LogWarning(message, @object); break;
|
||||
default: Debug.Log(message, @object); break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HelpBox(string message, string fixDescription, MessageType type, Object @object = null, FixValidation action = null, string property = null)
|
||||
{
|
||||
s_Messages[(int)type].Add(new() { _Message = message, _FixDescription = fixDescription, _Object = @object, _Action = action, _PropertyPath = property });
|
||||
}
|
||||
|
||||
public static void Suppressed(string _0, string _1, MessageType _2, Object _3 = null, FixValidation _4 = null, string _5 = null)
|
||||
{
|
||||
}
|
||||
|
||||
public static T FixAttachComponent<T>(SerializedObject componentOrGameObject)
|
||||
where T : Component
|
||||
{
|
||||
return Undo.AddComponent<T>(EditorHelpers.GetGameObject(componentOrGameObject));
|
||||
}
|
||||
|
||||
internal static void FixSetMaterialOptionEnabled(SerializedObject material, string keyword, string floatParam, bool enabled)
|
||||
{
|
||||
var mat = material.targetObject as Material;
|
||||
Undo.RecordObject(mat, $"Enable keyword {keyword}");
|
||||
mat.SetBoolean(Shader.PropertyToID(floatParam), enabled);
|
||||
if (ArrayUtility.Contains(mat.shader.keywordSpace.keywordNames, keyword))
|
||||
{
|
||||
mat.SetKeyword(keyword, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FixSetMaterialIntProperty(SerializedObject material, string label, string intParam, int value)
|
||||
{
|
||||
var mat = material.targetObject as Material;
|
||||
Undo.RecordObject(mat, $"change {label}");
|
||||
mat.SetInteger(intParam, value);
|
||||
}
|
||||
|
||||
public static void FixAddMissingMathPackage(SerializedObject _0, SerializedProperty _1)
|
||||
{
|
||||
PackageManagerHelpers.AddMissingPackage("com.unity.mathematics");
|
||||
}
|
||||
|
||||
public static void FixAddMissingBurstPackage(SerializedObject _0, SerializedProperty _1)
|
||||
{
|
||||
PackageManagerHelpers.AddMissingPackage("com.unity.burst");
|
||||
}
|
||||
|
||||
public static bool ValidateNoScale(Object @object, Transform transform, ShowMessage showMessage)
|
||||
{
|
||||
if (transform.lossyScale != Vector3.one)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"There must be no scale on the <i>{@object.GetType().Name}</i> Transform or any of its parents." +
|
||||
$"The current scale is <i>{transform.lossyScale}</i>.",
|
||||
"Reset the scale on this Transform and all parents to one.",
|
||||
MessageType.Error, @object
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateNoRotation(Object @object, Transform transform, ShowMessage showMessage)
|
||||
{
|
||||
if (transform.eulerAngles.magnitude > 0.0001f)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"There must be no rotation on the <i>{@object.GetType().Name}</i> Transform or any of its parents." +
|
||||
$"The current rotation is <i>{transform.eulerAngles}.</i>",
|
||||
"Reset the rotation on this Transform and all parents to zero.",
|
||||
MessageType.Error, @object
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool ValidateRenderer<T>
|
||||
(
|
||||
Component component,
|
||||
Renderer renderer,
|
||||
ShowMessage showMessage,
|
||||
bool checkShaderPasses,
|
||||
string shaderPrefix = null
|
||||
)
|
||||
where T : Renderer
|
||||
{
|
||||
if (renderer == null)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var name = type.Name;
|
||||
|
||||
// Give users a hint as to what "Renderer" really means.
|
||||
if (type == typeof(Renderer))
|
||||
{
|
||||
name += " (Mesh, Trail etc)";
|
||||
}
|
||||
|
||||
showMessage
|
||||
(
|
||||
$"A <i>{name}</i> component is required but none is assigned.",
|
||||
"Provide a renderer.",
|
||||
MessageType.Error, component
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var materials = renderer.sharedMaterials;
|
||||
for (var i = 0; i < materials.Length; i++)
|
||||
{
|
||||
// Empty material slots is a user error. Unity complains about it so we should too.
|
||||
if (materials[i] == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"<i>{renderer.GetType().Name}</i> used by this input (<i>{component.GetType().Name}</i>) has empty material slots.",
|
||||
"Remove these slots or fill them with a material.",
|
||||
MessageType.Error, renderer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer is MeshRenderer)
|
||||
{
|
||||
renderer.gameObject.TryGetComponent<MeshFilter>(out var mf);
|
||||
if (mf == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"A <i>MeshRenderer</i> component is being used by this input but no <i>MeshFilter</i> component was found so there may not be any valid geometry to render.",
|
||||
"Attach a <i>MeshFilter</i> component.",
|
||||
MessageType.Error, renderer.gameObject,
|
||||
(_, _) => Undo.AddComponent<MeshFilter>(renderer.gameObject)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (mf.sharedMesh == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"A <i>MeshRenderer</i> component is being used by this input but no mesh is assigned to the <i>MeshFilter</i> component.",
|
||||
"Assign the geometry to be rendered to the <i>MeshFilter</i> component.",
|
||||
MessageType.Error, renderer.gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ValidateMaterial(renderer.gameObject, showMessage, renderer.sharedMaterial, shaderPrefix, checkShaderPasses))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ValidateMaterial(GameObject gameObject, ShowMessage showMessage, Material material, string shaderPrefix, bool checkShaderPasses)
|
||||
{
|
||||
if (shaderPrefix == null && material == null)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"<i>Mesh Renderer</i> requires a material.",
|
||||
"Assign a material.",
|
||||
MessageType.Error, gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!material || material.shader && (!material.shader.name.StartsWithNoAlloc(shaderPrefix) && !material.shader.name.StartsWithNoAlloc($"Hidden/{shaderPrefix}") && !material.shader.name.Contains("/All/")))
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"Shader assigned to water input expected to be of type <i>{shaderPrefix}</i>.",
|
||||
"Assign a material that uses a shader of this type.",
|
||||
MessageType.Error, gameObject
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkShaderPasses && material.passCount > 1)
|
||||
{
|
||||
showMessage
|
||||
(
|
||||
$"The shader <i>{material.shader.name}</i> for material <i>{material.name}</i> has multiple passes which might not work as expected as only the first pass is executed. " +
|
||||
"This can be ignored in most cases, like Shader Graph, as only one pass is often required.",
|
||||
"To have all passes execute then set <i>Shader Pass Index</i> to <i>-1</i>.",
|
||||
MessageType.Info, gameObject
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ExecuteValidators(object target, ShowMessage messenger)
|
||||
{
|
||||
var isValid = true;
|
||||
var type = target.GetType();
|
||||
var validators = TypeCache.GetMethodsWithAttribute<Validator>();
|
||||
foreach (var validator in validators)
|
||||
{
|
||||
var attribute = validator.GetCustomAttribute<Validator>();
|
||||
if (attribute._Type.IsAssignableFrom(type))
|
||||
{
|
||||
isValid = (bool)validator.Invoke(null, new object[] { target, messenger }) && isValid;
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool ExecuteValidators(Object target)
|
||||
{
|
||||
return ExecuteValidators(target, DebugLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9a7f4ffb1bb41999902cd4310bf2d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "WaveHarmonic.Crest.Shared.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:056ff2a5b2f124d468c6655552acdca5",
|
||||
"GUID:3eae0364be2026648bf74846acb8a731",
|
||||
"GUID:df380645f10b7bc4b97d4f5eb6303d95",
|
||||
"GUID:be0903cd8e1546f498710afdc59db5eb"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_2022_3_OR_NEWER"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ab2a6c2a51cd4b43867788dbaee1a55
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user