Last active
October 3, 2022 07:42
-
-
Save Seneral/31161381f993a4a06c59bf12576cabd8 to your computer and use it in GitHub Desktop.
Extended editor GUI controls for runtime usage in Unity. Includes runtime-compatible editor control ports, new controls and fields, advanced texture drawing utilities, general texture utilities and efficient clipped bezier drawing. Bezier drawing needs line texture www.dropbox.com/s/2qn3q3nwhny17ok and (optional) GUIScaleUtility gist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "Hidden/GUITextureClip_ChannelControl" | |
{ | |
Properties | |
{ | |
_MainTex ("Texture", Any) = "white" {} | |
} | |
SubShader | |
{ | |
Tags { "ForceSupported" = "True" } | |
Lighting Off | |
Blend SrcAlpha OneMinusSrcAlpha | |
Cull Off | |
ZWrite Off | |
ZTest Always | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#pragma shader_feature SCALE_CHANNEL | |
#include "UnityCG.cginc" | |
struct appdata_t { | |
float4 vertex : POSITION; | |
half4 color : COLOR; | |
float2 texcoord : TEXCOORD0; | |
}; | |
struct v2f { | |
float4 vertex : SV_POSITION; | |
half4 color : COLOR; | |
float2 texcoord : TEXCOORD0; | |
float2 clipUV : TEXCOORD1; | |
}; | |
sampler2D _MainTex; | |
sampler2D _GUIClipTexture; | |
uniform float4 _MainTex_ST; | |
uniform fixed4 _Color; | |
uniform float4x4 unity_GUIClipTextureMatrix; | |
// Ints pointing to the channel to represent: 0-black - 1-red - 2-green - 3-blue - 4-alpha - 5-white | |
uniform int shuffleRed = 1, shuffleGreen = 2, shuffleBlue = 3, shuffleAlpha = 4; | |
// Scale values for the individual channels | |
#if SCALE_CHANNEL | |
uniform float scaleRed = 1, scaleGreen = 1, scaleBlue = 1, scaleAlpha = 1; | |
#else | |
uniform float scaleColor = 1; | |
#endif | |
v2f vert (appdata_t v) | |
{ | |
v2f o; | |
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex); | |
float4 eyePos = mul(UNITY_MATRIX_MV, v.vertex); | |
o.clipUV = mul(unity_GUIClipTextureMatrix, eyePos); | |
o.color = v.color; | |
o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex); | |
return o; | |
} | |
half shuffleChannel (half4 srcCol, int shuffle) | |
{ | |
if (shuffle <= 0) | |
return 0; | |
if (shuffle == 1) | |
return srcCol.r; | |
if (shuffle == 2) | |
return srcCol.g; | |
if (shuffle == 3) | |
return srcCol.b; | |
if (shuffle == 4) | |
return srcCol.a; | |
return 1; | |
} | |
half4 frag (v2f i) : SV_Target | |
{ | |
half4 rawCol = tex2D(_MainTex, i.texcoord); | |
#if SCALE_CHANNEL | |
half4 shuffledCol = half4 ( shuffleChannel (rawCol, shuffleRed) * scaleRed, | |
shuffleChannel (rawCol, shuffleGreen) * scaleGreen, | |
shuffleChannel (rawCol, shuffleBlue) * scaleBlue, | |
shuffleChannel (rawCol, shuffleAlpha) * scaleAlpha); | |
#else | |
half4 shuffledCol = half4 ( shuffleChannel (rawCol, shuffleRed) * scaleColor, | |
shuffleChannel (rawCol, shuffleGreen) * scaleColor, | |
shuffleChannel (rawCol, shuffleBlue) * scaleColor, | |
shuffleChannel (rawCol, shuffleAlpha) * scaleColor); | |
#endif | |
half4 col = shuffledCol * i.color; | |
col.a *= 2.0f * tex2D(_GUIClipTexture, i.clipUV).a; | |
return col; | |
} | |
ENDCG | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "Hidden/LineShader" | |
{ | |
SubShader | |
{ | |
Blend SrcAlpha OneMinusSrcAlpha | |
ZWrite Off | |
Cull Off | |
Fog { Mode Off } | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
uniform float4 _LineColor; | |
uniform sampler2D _LineTexture; | |
struct v2f | |
{ | |
float2 texcoord : TEXCOORD0; | |
float4 vertex : POSITION; | |
}; | |
v2f vert (float2 texcoord : TEXCOORD0, float4 vertex : POSITION) | |
{ | |
v2f o; | |
o.vertex = mul(UNITY_MATRIX_MVP, vertex); | |
o.texcoord = texcoord; | |
return o; | |
} | |
float4 frag (v2f i) : COLOR | |
{ | |
return _LineColor * tex2D (_LineTexture, i.texcoord); | |
} | |
ENDCG | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using UnityEngine; | |
using System; | |
using System.Globalization; | |
using System.Linq; | |
using System.Collections.Generic; | |
using Object = UnityEngine.Object; | |
namespace NodeEditorFramework.Utilities | |
{ | |
public static class RTEditorGUI | |
{ | |
#region GUI Proportioning Utilities | |
public static float labelWidth = 150; | |
public static float fieldWidth = 50; | |
public static float indent = 0; | |
private static float textFieldHeight { get { return GUI.skin.textField.CalcHeight (new GUIContent ("i"), 10); } } | |
public static Rect PrefixLabel (Rect totalPos, GUIContent label, GUIStyle style) | |
{ | |
if (label == GUIContent.none) | |
return totalPos;//IndentedRect (totalPos); | |
Rect labelPos = new Rect (totalPos.x + indent, totalPos.y, Mathf.Min (getLabelWidth () - indent, totalPos.width/2), totalPos.height); | |
GUI.Label (labelPos, label, style); | |
return new Rect (totalPos.x + getLabelWidth (), totalPos.y, totalPos.width - getLabelWidth (), totalPos.height); | |
} | |
public static Rect PrefixLabel (Rect totalPos, float percentage, GUIContent label, GUIStyle style) | |
{ | |
if (label == GUIContent.none) | |
return totalPos; | |
Rect labelPos = new Rect (totalPos.x + indent, totalPos.y, totalPos.width*percentage, totalPos.height); | |
GUI.Label (labelPos, label, style); | |
return new Rect (totalPos.x + totalPos.width*percentage, totalPos.y, totalPos.width*(1-percentage), totalPos.height); | |
} | |
private static Rect IndentedRect (Rect source) | |
{ | |
return new Rect (source.x + indent, source.y, source.width - indent, source.height); | |
} | |
private static float getLabelWidth () | |
{ | |
#if UNITY_EDITOR | |
return UnityEditor.EditorGUIUtility.labelWidth; | |
#else | |
if (labelWidth == 0) | |
return 150; | |
return labelWidth; | |
#endif | |
} | |
private static float getFieldWidth () | |
{ | |
#if UNITY_EDITOR | |
return UnityEditor.EditorGUIUtility.fieldWidth; | |
#else | |
if (fieldWidth == 0) | |
return 50; | |
return fieldWidth; | |
#endif | |
} | |
private static Rect GetFieldRect (GUIContent label, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
float minLabelW = 0, maxLabelW = 0; | |
if (label != GUIContent.none) | |
style.CalcMinMaxWidth (label, out minLabelW, out maxLabelW); | |
return GUILayoutUtility.GetRect (getFieldWidth() + minLabelW + 5, getFieldWidth() + maxLabelW + 5, textFieldHeight, textFieldHeight, options); | |
} | |
private static Rect GetSliderRect (GUIContent label, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
float minLabelW = 0, maxLabelW = 0; | |
if (label != GUIContent.none) | |
style.CalcMinMaxWidth (label, out minLabelW, out maxLabelW); | |
return GUILayoutUtility.GetRect (getFieldWidth() + minLabelW + 5, getFieldWidth() + maxLabelW + 5 + 100, textFieldHeight, textFieldHeight, options); | |
} | |
private static Rect GetSliderRect (Rect sliderRect) | |
{ | |
return new Rect (sliderRect.x, sliderRect.y, sliderRect.width - getFieldWidth() - 5, sliderRect.height); | |
} | |
private static Rect GetSliderFieldRect (Rect sliderRect) | |
{ | |
return new Rect (sliderRect.x + sliderRect.width - getFieldWidth(), sliderRect.y, getFieldWidth(), sliderRect.height); | |
} | |
#endregion | |
#region Seperator | |
/// <summary> | |
/// Efficient space like EditorGUILayout.Space | |
/// </summary> | |
public static void Space () | |
{ | |
Space (6); | |
} | |
/// <summary> | |
/// Space like GUILayout.Space but more efficient | |
/// </summary> | |
public static void Space (float pixels) | |
{ | |
GUILayoutUtility.GetRect (pixels, pixels); | |
} | |
/// <summary> | |
/// A GUI Function which simulates the default seperator | |
/// </summary> | |
public static void Seperator () | |
{ | |
setupSeperator (); | |
GUILayout.Box (GUIContent.none, seperator, new GUILayoutOption[] { GUILayout.Height (1) }); | |
} | |
/// <summary> | |
/// A GUI Function which simulates the default seperator | |
/// </summary> | |
public static void Seperator (Rect rect) | |
{ | |
setupSeperator (); | |
GUI.Box (new Rect (rect.x, rect.y, rect.width, 1), GUIContent.none, seperator); | |
} | |
private static GUIStyle seperator; | |
private static void setupSeperator () | |
{ | |
if (seperator == null) | |
{ | |
seperator = new GUIStyle(); | |
seperator.normal.background = ColorToTex (1, new Color (0.6f, 0.6f, 0.6f)); | |
seperator.stretchWidth = true; | |
seperator.margin = new RectOffset(0, 0, 7, 7); | |
} | |
} | |
#endregion | |
#region Change Check | |
private static Stack<bool> changeStack = new Stack<bool> (); | |
public static void BeginChangeCheck () | |
{ | |
changeStack.Push (GUI.changed); | |
GUI.changed = false; | |
} | |
public static bool EndChangeCheck () | |
{ | |
bool changed = GUI.changed; | |
if (changeStack.Count > 0) | |
{ | |
GUI.changed = changeStack.Pop (); | |
if (changed && changeStack.Count > 0 && !changeStack.Peek ()) | |
{ // Update parent change check | |
changeStack.Pop (); | |
changeStack.Push (changed); | |
} | |
} | |
else | |
Debug.LogWarning ("Requesting more EndChangeChecks than issuing BeginChangeChecks!"); | |
return changed; | |
} | |
#endregion | |
#region Foldout and Toggle Wrappers | |
public static bool Foldout (bool foldout, string content, params GUILayoutOption[] options) | |
{ | |
return Foldout (foldout, new GUIContent (content), options); | |
} | |
public static bool Foldout (bool foldout, string content, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
return Foldout (foldout, new GUIContent (content), style, options); | |
} | |
public static bool Foldout (bool foldout, GUIContent content, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.Foldout (foldout, content); | |
#endif | |
return Foldout (foldout, content, GUI.skin.toggle, options); | |
} | |
public static bool Foldout (bool foldout, GUIContent content, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.Foldout (foldout, content, style); | |
#endif | |
return GUILayout.Toggle (foldout, content, style, options); | |
} | |
public static bool Toggle (bool toggle, string content, params GUILayoutOption[] options) | |
{ | |
return Toggle (toggle, new GUIContent (content), options); | |
} | |
public static bool Toggle (bool toggle, string content, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
return Toggle (toggle, new GUIContent (content), style, options); | |
} | |
public static bool Toggle (bool toggle, GUIContent content, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.ToggleLeft (content, toggle, options); | |
#endif | |
return Toggle (toggle, content, GUI.skin.toggle, options); | |
} | |
public static bool Toggle (bool toggle, GUIContent content, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.ToggleLeft (content, toggle, style, options); | |
#endif | |
return GUILayout.Toggle (toggle, content, style, options); | |
} | |
#endregion | |
#region Fields and Sliders | |
#region Extra | |
/// <summary> | |
/// Text Field with label for ingame purposes. Behaves like UnityEditor.EditorGUILayout.TextField | |
/// </summary> | |
public static string TextField (GUIContent label, string text, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.TextField (label, text); | |
#endif | |
if (style == null) style = GUI.skin.textField; | |
Rect totalPos = GetFieldRect (label, style, options); | |
Rect fieldPos = PrefixLabel (totalPos, 0.5f, label, style); | |
text = GUI.TextField (fieldPos, text); | |
return text; | |
} | |
/// <summary> | |
/// Slider to select between the given options | |
/// </summary> | |
public static int OptionSlider (GUIContent label, int selected, string[] selectableOptions, params GUILayoutOption[] options) | |
{ | |
return OptionSlider (label, selected, selectableOptions, GUI.skin.label, options); | |
} | |
/// <summary> | |
/// Slider to select between the given options | |
/// </summary> | |
public static int OptionSlider (GUIContent label, int selected, string[] selectableOptions, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
if (style == null) style = GUI.skin.textField; | |
Rect totalPos = GetSliderRect (label, style, options); | |
Rect sliderFieldPos = PrefixLabel (totalPos, 0.5f, label, style); | |
selected = Mathf.RoundToInt (GUI.HorizontalSlider (GetSliderRect (sliderFieldPos), selected, 0, selectableOptions.Length-1)); | |
GUI.Label (GetSliderFieldRect (sliderFieldPos), selectableOptions[selected]); | |
return selected; | |
} | |
/// <summary> | |
/// Slider to select from a set range of powers for a given base value. | |
/// Operates on the final value, rounds it to the next power and displays it. | |
/// </summary> | |
public static int MathPowerSlider (GUIContent label, int baseValue, int value, int minPow, int maxPow, params GUILayoutOption[] options) | |
{ | |
int power = (int)Math.Floor (Math.Log (value) / Math.Log (baseValue)); | |
power = MathPowerSliderRaw (label, baseValue, power, minPow, maxPow, options); | |
return (int)Math.Pow (baseValue, power); | |
} | |
/// <summary> | |
/// Slider to select from a set range of powers for a given base value. | |
/// Operates on the raw power but displays the final calculated value. | |
/// </summary> | |
public static int MathPowerSliderRaw (GUIContent label, int baseValue, int power, int minPow, int maxPow, params GUILayoutOption[] options) | |
{ | |
Rect totalPos = GetSliderRect (label, GUI.skin.label, options); | |
Rect sliderFieldPos = PrefixLabel (totalPos, 0.5f, label, GUI.skin.label); | |
power = Mathf.RoundToInt (GUI.HorizontalSlider (GetSliderRect (sliderFieldPos), power, minPow, maxPow)); | |
GUI.Label (GetSliderFieldRect (sliderFieldPos), Mathf.Pow (baseValue, power).ToString ()); | |
return power; | |
} | |
#endregion | |
#region Int Fields and Slider Wrappers | |
/// <summary> | |
/// An integer slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with a label prefixed and an additional int field thereafter if desired. | |
/// </summary> | |
public static int IntSlider (string label, int value, int minValue, int maxValue, params GUILayoutOption[] options) | |
{ | |
return (int)Slider (new GUIContent (label), value, minValue, maxValue, options); | |
} | |
/// <summary> | |
/// An integer slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with a label prefixed and an additional int field thereafter if desired. | |
/// </summary> | |
public static int IntSlider (GUIContent label, int value, int minValue, int maxValue, params GUILayoutOption[] options) | |
{ | |
return (int)Slider (label, value, minValue, maxValue, options); | |
} | |
/// <summary> | |
/// An integer slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with a label prefixed and an additional int field thereafter if desired. | |
/// </summary> | |
public static int IntSlider (int value, int minValue, int maxValue, params GUILayoutOption[] options) | |
{ | |
return (int)Slider (GUIContent.none, value, minValue, maxValue, options); | |
} | |
/// <summary> | |
/// Int Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.IntField, besides the label slide field | |
/// </summary> | |
public static int IntField (string label, int value, params GUILayoutOption[] options) | |
{ | |
return (int)FloatField (new GUIContent (label), value, options); | |
} | |
/// <summary> | |
/// Int Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.IntField, besides the label slide field | |
/// </summary> | |
public static int IntField (GUIContent label, int value, params GUILayoutOption[] options) | |
{ | |
return (int)FloatField (label, value, options); | |
} | |
/// <summary> | |
/// Int Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.IntField | |
/// </summary> | |
public static int IntField (int value, params GUILayoutOption[] options) | |
{ | |
return (int)FloatField (value, options); | |
} | |
#endregion | |
#region Float Slider | |
/// <summary> | |
/// A slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with an additional float field thereafter. | |
/// </summary> | |
public static float Slider (float value, float minValue, float maxValue, params GUILayoutOption[] options) | |
{ | |
return Slider (GUIContent.none, value, minValue, maxValue, options); | |
} | |
/// <summary> | |
/// A slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with a label prefixed and an additional float field thereafter if desired. | |
/// </summary> | |
public static float Slider (string label, float value, float minValue, float maxValue, params GUILayoutOption[] options) | |
{ | |
return Slider (new GUIContent (label), value, minValue, maxValue, options); | |
} | |
/// <summary> | |
/// A slider that emulates the EditorGUILayout version. | |
/// HorizontalSlider with a label prefixed and an additional float field thereafter if desired. | |
/// </summary> | |
public static float Slider (GUIContent label, float value, float minValue, float maxValue, params GUILayoutOption[] options) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.Slider (label, value, minValue, maxValue, options); | |
#endif | |
Rect totalPos = GetSliderRect (label, GUI.skin.label, options); | |
Rect sliderFieldPos = PrefixLabel (totalPos, 0.5f, label, GUI.skin.label); | |
value = GUI.HorizontalSlider (GetSliderRect (sliderFieldPos), value, minValue, maxValue); | |
value = Mathf.Min (maxValue, Mathf.Max (minValue, FloatField (GetSliderFieldRect (sliderFieldPos), value, GUILayout.Width (60)))); | |
return value; | |
} | |
#endregion | |
#region Float Field | |
private static int activeFloatField = -1; | |
private static float activeFloatFieldLastValue = 0; | |
private static string activeFloatFieldString = ""; | |
/// <summary> | |
/// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField, besides the label slide field | |
/// </summary> | |
public static float FloatField (string label, float value, params GUILayoutOption[] fieldOptions) | |
{ | |
return FloatField (new GUIContent (label), value, fieldOptions); | |
} | |
/// <summary> | |
/// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField, besides the label slide field | |
/// </summary> | |
public static float FloatField (GUIContent label, float value, params GUILayoutOption[] options) | |
{ | |
Rect totalPos = GetFieldRect (label, GUI.skin.label, options); | |
Rect fieldPos = PrefixLabel (totalPos, 0.5f, label, GUI.skin.label); | |
return FloatField (fieldPos, value, options); | |
} | |
/// <summary> | |
/// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField | |
/// </summary> | |
public static float FloatField (float value, params GUILayoutOption[] options) | |
{ | |
Rect pos = GetFieldRect (GUIContent.none, null, options); | |
return FloatField (pos, value, options); | |
} | |
/// <summary> | |
/// Float Field for ingame purposes. Behaves exactly like UnityEditor.EditorGUILayout.FloatField | |
/// </summary> | |
public static float FloatField (Rect pos, float value, params GUILayoutOption[] options) | |
{ | |
int floatFieldID = GUIUtility.GetControlID ("FloatField".GetHashCode (), FocusType.Keyboard, pos) + 1; | |
if (floatFieldID == 0) | |
return value; | |
bool recorded = activeFloatField == floatFieldID; | |
bool active = floatFieldID == GUIUtility.keyboardControl; | |
if (active && recorded && activeFloatFieldLastValue != value) | |
{ // Value has been modified externally | |
activeFloatFieldLastValue = value; | |
activeFloatFieldString = value.ToString (); | |
} | |
// Get stored string for the text field if this one is recorded | |
string str = recorded? activeFloatFieldString : value.ToString (); | |
string strValue = GUI.TextField (pos, str); | |
if (recorded) | |
activeFloatFieldString = strValue; | |
// Try Parse if value got changed. If the string could not be parsed, ignore it and keep last value | |
bool parsed = true; | |
if (strValue == "") | |
value = activeFloatFieldLastValue = 0; | |
else if (strValue != value.ToString ()) | |
{ | |
float newValue; | |
parsed = float.TryParse (strValue, out newValue); | |
if (parsed) | |
value = activeFloatFieldLastValue = newValue; | |
} | |
if (active && !recorded) | |
{ // Gained focus this frame | |
activeFloatField = floatFieldID; | |
activeFloatFieldString = strValue; | |
activeFloatFieldLastValue = value; | |
} | |
else if (!active && recorded) | |
{ // Lost focus this frame | |
activeFloatField = -1; | |
if (!parsed) | |
value = strValue.ForceParse (); | |
} | |
return value; | |
} | |
/// <summary> | |
/// Forces to parse to float by cleaning string if necessary | |
/// </summary> | |
public static float ForceParse (this string str) | |
{ | |
// try parse | |
float value; | |
if (float.TryParse (str, out value)) | |
return value; | |
// Clean string if it could not be parsed | |
bool recordedDecimalPoint = false; | |
List<char> strVal = new List<char> (str); | |
for (int cnt = 0; cnt < strVal.Count; cnt++) | |
{ | |
UnicodeCategory type = CharUnicodeInfo.GetUnicodeCategory (str[cnt]); | |
if (type != UnicodeCategory.DecimalDigitNumber) | |
{ | |
strVal.RemoveRange (cnt, strVal.Count-cnt); | |
break; | |
} | |
else if (str[cnt] == '.') | |
{ | |
if (recordedDecimalPoint) | |
{ | |
strVal.RemoveRange (cnt, strVal.Count-cnt); | |
break; | |
} | |
recordedDecimalPoint = true; | |
} | |
} | |
// Parse again | |
if (strVal.Count == 0) | |
return 0; | |
str = new string (strVal.ToArray ()); | |
if (!float.TryParse (str, out value)) | |
Debug.LogError ("Could not parse " + str); | |
return value; | |
} | |
#endregion | |
#endregion | |
#region Object Field | |
/// <summary> | |
/// Provides an object field both for editor (using default) and for runtime (not yet implemented other that displaying object) | |
/// </summary> | |
public static T ObjectField<T> (T obj, bool allowSceneObjects) where T : Object | |
{ | |
return ObjectField<T> (GUIContent.none, obj, allowSceneObjects); | |
} | |
/// <summary> | |
/// Provides an object field both for editor (using default) and for runtime (not yet implemented other that displaying object) | |
/// </summary> | |
public static T ObjectField<T> (string label, T obj, bool allowSceneObjects) where T : Object | |
{ | |
return ObjectField<T> (new GUIContent (label), obj, allowSceneObjects); | |
} | |
/// <summary> | |
/// Provides an object field both for editor (using default) and for runtime (not yet implemented other that displaying object) | |
/// </summary> | |
public static T ObjectField<T> (GUIContent label, T obj, bool allowSceneObjects, params GUILayoutOption[] options) where T : Object | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.ObjectField (label, obj, typeof (T), allowSceneObjects) as T; | |
#endif | |
bool open = false; | |
if (obj.GetType () == typeof(Texture2D)) | |
{ | |
GUILayout.BeginHorizontal (); | |
GUILayout.Label (label); | |
open = GUILayout.Button (obj as Texture2D, new GUILayoutOption[] { GUILayout.MaxWidth (64), GUILayout.MaxHeight (64) }); | |
GUILayout.EndHorizontal (); | |
} | |
else | |
{ | |
GUIStyle style = new GUIStyle (GUI.skin.box); | |
open = GUILayout.Button (label, style); | |
} | |
if (open) | |
{ | |
//Debug.Log ("Selecting Object!"); | |
} | |
return obj; | |
} | |
#endregion | |
#region Popups | |
// TODO: Implement RT Popup | |
public static System.Enum EnumPopup (System.Enum selected) | |
{ | |
return EnumPopup (GUIContent.none, selected); | |
} | |
public static System.Enum EnumPopup (string label, System.Enum selected) | |
{ | |
return EnumPopup (new GUIContent (label), selected); | |
} | |
public static System.Enum EnumPopup (GUIContent label, System.Enum selected) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.EnumPopup (label, selected); | |
#endif | |
label.text += ": " + selected.ToString (); | |
GUILayout.Label (label); | |
return selected; | |
} | |
public static int Popup (GUIContent label, int selected, string[] displayedOptions) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
{ | |
GUILayout.BeginHorizontal (); | |
GUILayout.Label (label); | |
selected = UnityEditor.EditorGUILayout.Popup (selected, displayedOptions); | |
GUILayout.EndHorizontal (); | |
return selected; | |
} | |
#endif | |
GUILayout.BeginHorizontal (); | |
label.text += ": " + selected.ToString (); | |
GUILayout.Label (label); | |
GUILayout.EndHorizontal (); | |
return selected; | |
} | |
public static int Popup (string label, int selected, string[] displayedOptions) | |
{ | |
#if UNITY_EDITOR | |
if (!Application.isPlaying) | |
return UnityEditor.EditorGUILayout.Popup (label, selected, displayedOptions); | |
#endif | |
GUILayout.Label (label + ": " + selected.ToString ()); | |
return selected; | |
} | |
public static int Popup (int selected, string[] displayedOptions) | |
{ | |
return Popup ("", selected, displayedOptions); | |
} | |
#endregion | |
#region Extended GUI Texture Drawing | |
private static Material texVizMat; | |
public static void DrawTexture (Texture texture, int texSize, GUIStyle style, params GUILayoutOption[] options) | |
{ | |
DrawTexture (texture, texSize, style, 1, 2, 3, 4, options); | |
} | |
public static void DrawTexture (Texture texture, int texSize, GUIStyle style, int shuffleRed, int shuffleGreen, int shuffleBlue, int shuffleAlpha, params GUILayoutOption[] options) | |
{ | |
if (texVizMat == null) | |
texVizMat = new Material (Shader.Find ("Hidden/GUITextureClip_ChannelControl")); | |
texVizMat.SetInt ("shuffleRed", shuffleRed); | |
texVizMat.SetInt ("shuffleGreen", shuffleGreen); | |
texVizMat.SetInt ("shuffleBlue", shuffleBlue); | |
texVizMat.SetInt ("shuffleAlpha", shuffleAlpha); | |
if (options == null || options.Length == 0) | |
options = new GUILayoutOption[] { GUILayout.ExpandWidth (false) }; | |
Rect rect = style == null? GUILayoutUtility.GetRect (texSize, texSize, options) : GUILayoutUtility.GetRect (texSize, texSize, style, options); | |
if (Event.current.type == EventType.Repaint) | |
Graphics.DrawTexture (rect, texture, texVizMat); | |
} | |
#endregion | |
#region Low-Level Drawing | |
private static Material lineMaterial; | |
private static Texture2D lineTexture; | |
private static void SetupLineMat (Texture tex, Color col) | |
{ | |
if (lineMaterial == null) | |
lineMaterial = new Material (Shader.Find ("Hidden/LineShader")); | |
if (tex == null) | |
tex = lineTexture != null? lineTexture : lineTexture = NodeEditorFramework.Utilities.ResourceManager.LoadTexture ("Textures/AALine.png"); | |
lineMaterial.SetTexture ("_LineTexture", tex); | |
lineMaterial.SetColor ("_LineColor", col); | |
lineMaterial.SetPass (0); | |
} | |
/// <summary> | |
/// Draws a Bezier curve just as UnityEditor.Handles.DrawBezier, non-clipped. If width is 1, tex is ignored; Else if tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. | |
/// </summary> | |
public static void DrawBezier (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan, Color col, Texture2D tex, float width = 1) | |
{ | |
if (Event.current.type != EventType.Repaint) | |
return; | |
// Own Bezier Formula | |
// Slower than handles because of the horrendous amount of calls into the native api | |
// Calculate optimal segment count | |
int segmentCount = CalculateBezierSegmentCount (startPos, endPos, startTan, endTan); | |
// Draw bezier with calculated segment count | |
DrawBezier (startPos, endPos, startTan, endTan, col, tex, segmentCount, width); | |
} | |
/// <summary> | |
/// Draws a clipped Bezier curve just as UnityEditor.Handles.DrawBezier. | |
/// If width is 1, tex is ignored; Else if tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. | |
/// </summary> | |
public static void DrawBezier (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan, Color col, Texture2D tex, int segmentCount, float width) | |
{ | |
if (Event.current.type != EventType.Repaint && Event.current.type != EventType.KeyDown) | |
return; | |
// Own Bezier Formula | |
// Slower than handles because of the horrendous amount of calls into the native api | |
// Calculate bezier points | |
Vector2[] bezierPoints = new Vector2[segmentCount+1]; | |
for (int pointCnt = 0; pointCnt <= segmentCount; pointCnt++) | |
bezierPoints[pointCnt] = GetBezierPoint ((float)pointCnt/segmentCount, startPos, endPos, startTan, endTan); | |
// Draw polygon line from the bezier points | |
DrawPolygonLine (bezierPoints, col, tex, width); | |
} | |
/// <summary> | |
/// Draws a clipped polygon line from the given points. | |
/// If width is 1, tex is ignored; Else if tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. | |
/// </summary> | |
public static void DrawPolygonLine (Vector2[] points, Color col, Texture2D tex, float width = 1) | |
{ | |
if (Event.current.type != EventType.Repaint && Event.current.type != EventType.KeyDown) | |
return; | |
// Simplify basic cases | |
if (points.Length == 1) | |
return; | |
else if (points.Length == 2) | |
DrawLine (points[0], points[1], col, tex, width); | |
// Setup for drawing | |
SetupLineMat (tex, col); | |
GL.Begin (GL.TRIANGLE_STRIP); | |
GL.Color (Color.white); | |
// Fetch clipping rect | |
Rect clippingRect = GUIScaleUtility.getTopRect; // Replace with other rect if not using GUIScaleUtility | |
clippingRect.x = clippingRect.y = 0; | |
Vector2 curPoint = points[0], nextPoint, perpendicular; | |
bool clippedP0, clippedP1; | |
for (int pointCnt = 1; pointCnt < points.Length; pointCnt++) | |
{ | |
nextPoint = points[pointCnt]; | |
// Clipping test | |
Vector2 curPointOriginal = curPoint, nextPointOriginal = nextPoint; | |
if (SegmentRectIntersection (clippingRect, ref curPoint, ref nextPoint, out clippedP0, out clippedP1)) | |
{ // (partially) visible | |
// Calculate apropriate perpendicular | |
if (pointCnt < points.Length-1) // Interpolate perpendicular inbetween the point chain | |
perpendicular = CalculatePointPerpendicular (curPointOriginal, nextPointOriginal, points[pointCnt+1]); | |
else // At the end, there's no further point to interpolate the perpendicular from | |
perpendicular = CalculateLinePerpendicular (curPointOriginal, nextPointOriginal); | |
if (clippedP0) | |
{ // Just became visible, so enable GL again and draw the clipped line start point | |
GL.End (); | |
GL.Begin (GL.TRIANGLE_STRIP); | |
DrawLineSegment (curPoint, perpendicular * width/2); | |
} | |
// Draw first point before starting with the point chain. Placed here instead of before because of clipping | |
if (pointCnt == 1) | |
DrawLineSegment (curPoint, CalculateLinePerpendicular (curPoint, nextPoint) * width/2); | |
// Draw the actual point | |
DrawLineSegment (nextPoint, perpendicular * width/2); | |
} | |
else if (clippedP1) | |
{ // Just became invisible, so disable GL | |
GL.End (); | |
GL.Begin (GL.TRIANGLE_STRIP); | |
} | |
// Update state variable | |
curPoint = nextPointOriginal; | |
} | |
// Finalize drawing | |
GL.End (); | |
} | |
/// <summary> | |
/// Calculates the optimal bezier segment count for the given bezier | |
/// </summary> | |
private static int CalculateBezierSegmentCount (Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan) | |
{ | |
float straightFactor = Vector2.Angle (startTan-startPos, endPos-startPos) * Vector2.Angle (endTan-endPos, startPos-endPos) * (endTan.magnitude+startTan.magnitude); | |
straightFactor = 2 + Mathf.Pow (straightFactor / 400, 0.125f); // 1/8 | |
float distanceFactor = 1 + (startPos-endPos).magnitude; | |
distanceFactor = Mathf.Pow (distanceFactor, 0.25f); // 1/4 | |
return 4 + (int)(straightFactor * distanceFactor); | |
} | |
/// <summary> | |
/// Calculates the normalized perpendicular vector of the give line | |
/// </summary> | |
private static Vector2 CalculateLinePerpendicular (Vector2 startPos, Vector2 endPos) | |
{ | |
return new Vector2 (endPos.y-startPos.y, startPos.x-endPos.x).normalized; | |
} | |
/// <summary> | |
/// Calculates the normalized perpendicular vector for the pointPos interpolated with its two neighbours prevPos and nextPos | |
/// </summary> | |
private static Vector2 CalculatePointPerpendicular (Vector2 prevPos, Vector2 pointPos, Vector2 nextPos) | |
{ | |
return CalculateLinePerpendicular (pointPos, pointPos + (nextPos-prevPos)); | |
} | |
/// <summary> | |
/// Gets the point of the bezier at t | |
/// </summary> | |
private static Vector2 GetBezierPoint (float t, Vector2 startPos, Vector2 endPos, Vector2 startTan, Vector2 endTan) | |
{ | |
float rt = 1 - t; | |
float rtt = rt * t; | |
return startPos * rt*rt*rt + | |
startTan * 3 * rt * rtt + | |
endTan * 3 * rtt * t + | |
endPos * t*t*t; | |
} | |
/// <summary> | |
/// Adds a line sgement to the GL buffer. Useed in a row to create a line | |
/// </summary> | |
private static void DrawLineSegment (Vector2 point, Vector2 perpendicular) | |
{ | |
GL.TexCoord2 (0, 0); | |
GL.Vertex (point - perpendicular); | |
GL.TexCoord2 (0, 1); | |
GL.Vertex (point + perpendicular); | |
} | |
/// <summary> | |
/// Draws a non-clipped line. If tex is null, a anti-aliased texture tinted with col will be used; else, col is ignored and tex is used. | |
/// </summary> | |
public static void DrawLine (Vector2 startPos, Vector2 endPos, Color col, Texture2D tex, float width = 1) | |
{ | |
if (Event.current.type != EventType.Repaint) | |
return; | |
// Setup | |
SetupLineMat (tex, col); | |
GL.Begin (GL.TRIANGLE_STRIP); | |
GL.Color (Color.white); | |
// Fetch clipping rect | |
Rect clippingRect = GUIScaleUtility.getTopRect; // Replace with other rect if not using GUIScaleUtility | |
clippingRect.x = clippingRect.y = 0; | |
// Clip to rect | |
if (SegmentRectIntersection (clippingRect, ref startPos, ref endPos)) | |
{ // Draw with clipped line if it is visible | |
Vector2 perpWidthOffset = CalculateLinePerpendicular (startPos, endPos) * width / 2; | |
DrawLineSegment (startPos, perpWidthOffset); | |
DrawLineSegment (endPos, perpWidthOffset); | |
} | |
// Finalize drawing | |
GL.End (); | |
} | |
/// <summary> | |
/// Clips the line between the points p1 and p2 to the bounds rect. | |
/// Uses Liang-Barsky Line Clipping Algorithm. | |
/// </summary> | |
private static bool SegmentRectIntersection(Rect bounds, ref Vector2 p0, ref Vector2 p1) | |
{ | |
bool cP0, cP1; | |
return SegmentRectIntersection (bounds, ref p0, ref p1, out cP0, out cP1); | |
} | |
/// <summary> | |
/// Clips the line between the points p1 and p2 to the bounds rect. | |
/// Uses Liang-Barsky Line Clipping Algorithm. | |
/// </summary> | |
private static bool SegmentRectIntersection (Rect bounds, ref Vector2 p0, ref Vector2 p1, out bool clippedP0, out bool clippedP1) | |
{ | |
float t0 = 0.0f; | |
float t1 = 1.0f; | |
float dx = p1.x - p0.x; | |
float dy = p1.y - p0.y; | |
if (ClipTest (-dx, p0.x - bounds.xMin, ref t0, ref t1)) // Left | |
{ | |
if (ClipTest (dx, bounds.xMax - p0.x, ref t0, ref t1)) // Right | |
{ | |
if (ClipTest (-dy, p0.y - bounds.yMin, ref t0, ref t1)) // Bottom | |
{ | |
if (ClipTest (dy, bounds.yMax - p0.y, ref t0, ref t1)) // Top | |
{ | |
clippedP0 = t0 > 0; | |
clippedP1 = t1 < 1; | |
if (clippedP1) | |
{ | |
p1.x = p0.x + t1 * dx; | |
p1.y = p0.y + t1 * dy; | |
} | |
if (clippedP0) | |
{ | |
p0.x = p0.x + t0 * dx; | |
p0.y = p0.y + t0 * dy; | |
} | |
return true; | |
} | |
} | |
} | |
} | |
clippedP1 = clippedP0 = true; | |
return false; | |
} | |
/// <summary> | |
/// Liang-Barsky Line Clipping Test | |
/// </summary> | |
private static bool ClipTest(float p, float q, ref float t0, ref float t1) | |
{ | |
float u = q / p; | |
if (p < 0.0f) | |
{ | |
if (u > t1) | |
return false; | |
if (u > t0) | |
t0 = u; | |
} | |
else if (p > 0.0f) | |
{ | |
if (u < t0) | |
return false; | |
if (u < t1) | |
t1 = u; | |
} | |
else if (q < 0.0f) | |
return false; | |
return true; | |
} | |
#endregion | |
#region Texture Utilities | |
/// <summary> | |
/// Create a 1x1 tex with color col | |
/// </summary> | |
public static Texture2D ColorToTex (int pxSize, Color col) | |
{ | |
Color[] texCols = new Color[pxSize*pxSize]; | |
for (int px = 0; px < pxSize*pxSize; px++) | |
texCols[px] = col; | |
Texture2D tex = new Texture2D (pxSize, pxSize); | |
tex.SetPixels (texCols); | |
tex.Apply (); | |
return tex; | |
} | |
/// <summary> | |
/// Tint the texture with the color. It's advised to use ResourceManager.GetTintedTexture to account for doubles. | |
/// </summary> | |
public static Texture2D Tint (Texture2D tex, Color color) | |
{ | |
Texture2D tintedTex = UnityEngine.Object.Instantiate (tex); | |
for (int x = 0; x < tex.width; x++) | |
for (int y = 0; y < tex.height; y++) | |
tintedTex.SetPixel (x, y, tex.GetPixel (x, y) * color); | |
tintedTex.Apply (); | |
return tintedTex; | |
} | |
/// <summary> | |
/// Rotates the texture Counter-Clockwise, 'quarterSteps' specifying the times | |
/// </summary> | |
public static Texture2D RotateTextureCCW (Texture2D tex, int quarterSteps) | |
{ | |
if (tex == null) | |
return null; | |
// Copy and setup working arrays | |
tex = UnityEngine.Object.Instantiate (tex); | |
int width = tex.width, height = tex.height; | |
Color[] col = tex.GetPixels (); | |
Color[] rotatedCol = new Color[width*height]; | |
for (int itCnt = 0; itCnt < quarterSteps; itCnt++) | |
{ // For each iteration, perform rotation of 90 degrees | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
rotatedCol[x*width + y] = col[(width-y-1) * width + x]; | |
rotatedCol.CopyTo (col, 0); // Push rotation for next iteration | |
} | |
// Apply rotated working arrays | |
tex.SetPixels (col); | |
tex.Apply (); | |
return tex; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment