// 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
{
///
/// Provides general helper functions for the editor.
///
static partial class EditorHelpers
{
internal static ComputeShader s_VisualizeNegativeValuesShader;
internal static ComputeShader VisualizeNegativeValuesShader
{
get
{
if (s_VisualizeNegativeValuesShader == null)
{
s_VisualizeNegativeValuesShader = AssetDatabase.LoadAssetAtPath("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);
}
/// Attempts to get the scene view this camera is rendering.
/// The scene view or null if not found.
public static SceneView GetSceneViewFromSceneCamera(Camera camera)
{
foreach (SceneView sceneView in SceneView.sceneViews)
{
if (sceneView.camera == camera)
{
return sceneView;
}
}
return null;
}
/// Get time passed to animated materials.
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