├── Documentation~
├── Example.gif
├── Github Header.jpg
└── Screenshot.PNG
├── Editor.meta
├── Editor
├── Curves.meta
├── Curves
│ ├── AnimationCurveTexturePropertyDrawer.cs
│ └── AnimationCurveTexturePropertyDrawer.cs.meta
├── Extensions.meta
├── Extensions
│ ├── MemberInfoExtensions.cs
│ ├── MemberInfoExtensions.cs.meta
│ ├── PropertyDrawerExtensions.cs
│ ├── PropertyDrawerExtensions.cs.meta
│ ├── RectExtensions.cs
│ ├── RectExtensions.cs.meta
│ ├── SerializedPropertyExtensions.cs
│ ├── SerializedPropertyExtensions.cs.meta
│ ├── TypeExtensions.cs
│ └── TypeExtensions.cs.meta
├── Gradients.meta
├── Gradients
│ ├── GradientAssetEditor.cs
│ ├── GradientAssetEditor.cs.meta
│ ├── GradientTexturePropertyDrawer.cs
│ └── GradientTexturePropertyDrawer.cs.meta
├── RoyTheunissen.CurvesAndGradientsToTexture.Editor.asmdef
└── RoyTheunissen.CurvesAndGradientsToTexture.Editor.asmdef.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── Runtime.meta
├── Runtime
├── Curves.meta
├── Curves
│ ├── AnimationCurveAsset.cs
│ ├── AnimationCurveAsset.cs.meta
│ ├── AnimationCurveTexture.cs
│ └── AnimationCurveTexture.cs.meta
├── Extensions.meta
├── Extensions
│ ├── AnimationCurveExtensions.cs
│ └── AnimationCurveExtensions.cs.meta
├── Gradients.meta
├── Gradients
│ ├── GradientAsset.cs
│ ├── GradientAsset.cs.meta
│ ├── GradientTexture.cs
│ └── GradientTexture.cs.meta
├── RoyTheunissen.CurvesAndGradientsToTexture.asmdef
└── RoyTheunissen.CurvesAndGradientsToTexture.asmdef.meta
├── package.json
└── package.json.meta
/Documentation~/Example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoyTheunissen/Curves-And-Gradients-To-Texture/d6272af6f70991c970522ce37f9e45ad88e52e3c/Documentation~/Example.gif
--------------------------------------------------------------------------------
/Documentation~/Github Header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoyTheunissen/Curves-And-Gradients-To-Texture/d6272af6f70991c970522ce37f9e45ad88e52e3c/Documentation~/Github Header.jpg
--------------------------------------------------------------------------------
/Documentation~/Screenshot.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoyTheunissen/Curves-And-Gradients-To-Texture/d6272af6f70991c970522ce37f9e45ad88e52e3c/Documentation~/Screenshot.PNG
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dc725fc347f585b489e6ba750201ef4c
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Curves.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: eaffc99bd9cd033488fd138ef4daa4a9
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Curves/AnimationCurveTexturePropertyDrawer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using RoyTheunissen.CurvesAndGradientsToTexture.Extensions;
4 | using UnityEditor;
5 | using UnityEngine;
6 |
7 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Curves
8 | {
9 | ///
10 | /// Draws the curve itself but also allows you to unfold the curve and tweak some of the more advanced settings.
11 | ///
12 | /// NOTE: Despite the apparent similarity, this code is not shared with GradientTexturePropertyDrawer because
13 | /// I expect these two utilities to be diverging a lot, so any effort to consolidate the two will likely be undone.
14 | ///
15 | [CustomPropertyDrawer(typeof(AnimationCurveTexture))]
16 | public class AnimationCurveTexturePropertyDrawer : PropertyDrawer
17 | {
18 | private const string CurvePropertyAsset = "animationCurveAsset";
19 | private const string CurvePropertyLocal = "animationCurveLocal";
20 | private const string CurvePropertyTexture = "texture";
21 |
22 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
23 | {
24 | float height = EditorGUI.GetPropertyHeight(property, label);
25 |
26 | if (property.isExpanded)
27 | {
28 | // One line for the preview.
29 | height += EditorGUIUtility.singleLineHeight;
30 |
31 | // And another for the edit or save button.
32 | if (property.FindPropertyRelative("mode").enumValueIndex != (int)AnimationCurveTexture.Modes.Texture)
33 | {
34 | // There's an edit/save button and then also an Export To Texture button.
35 | height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 2;
36 | }
37 | }
38 |
39 | return height;
40 | }
41 |
42 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
43 | {
44 | AnimationCurveTexture animationCurveTexture =
45 | this.GetActualObject(fieldInfo, property);
46 |
47 | EditorGUI.BeginChangeCheck();
48 |
49 | // Figure out what the curve property is.
50 | string curvePropertyName = null;
51 | switch (animationCurveTexture.Mode)
52 | {
53 | case AnimationCurveTexture.Modes.Asset:
54 | curvePropertyName = CurvePropertyAsset;
55 | break;
56 | case AnimationCurveTexture.Modes.Local:
57 | curvePropertyName = CurvePropertyLocal;
58 | break;
59 | case AnimationCurveTexture.Modes.Texture:
60 | curvePropertyName = CurvePropertyTexture;
61 | break;
62 | default:
63 | throw new ArgumentOutOfRangeException();
64 | }
65 | SerializedProperty curveProperty = property.FindPropertyRelative(curvePropertyName);
66 |
67 | // Draw the header.
68 | Rect foldoutRect = position.GetControlFirstRect().GetLabelRect(out Rect curveRect);
69 | EditorGUI.BeginProperty(foldoutRect, label, curveProperty);
70 | property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);
71 | EditorGUI.EndProperty();
72 |
73 | // Draw a field next to the label so you can edit it straight away.
74 | EditorGUI.PropertyField(curveRect, curveProperty, GUIContent.none);
75 |
76 | // Draw the children, too.
77 | if (property.isExpanded)
78 | {
79 | Rect childrenRect = position.GetSubRectFromBottom(position.height - foldoutRect.height);
80 | property.PropertyFieldForChildren(childrenRect);
81 | }
82 |
83 | bool shouldUpdateTexture = EditorGUI.EndChangeCheck();
84 |
85 | bool isTextureMode = animationCurveTexture.Mode == AnimationCurveTexture.Modes.Texture;
86 |
87 | // Draw some of the more internal properties for you to tweak once.
88 | if (property.isExpanded)
89 | {
90 | // Draw the current texture.
91 | Texture2D texture = animationCurveTexture.Texture;
92 | Rect previewRect = position.GetControlLastRect();
93 | if (!isTextureMode)
94 | previewRect = previewRect.GetControlPreviousRect().GetControlPreviousRect();
95 |
96 | Rect labelRect = previewRect.GetLabelRect(out Rect textureRect).Indent(1);
97 | EditorGUI.LabelField(labelRect, new GUIContent("Preview"));
98 |
99 | // Draw an update button.
100 | Rect generateButtonRect = textureRect.GetSubRectFromRight(70, out textureRect);
101 | bool forceUpdate = GUI.Button(generateButtonRect, "Update");
102 |
103 | // Update now so that it's done BEFORE we draw the texture, so you immediately see the result.
104 | if (forceUpdate || shouldUpdateTexture)
105 | {
106 | // NOTE: Need to apply the modified properties otherwise it will generate a texture using old data.
107 | property.serializedObject.ApplyModifiedProperties();
108 | animationCurveTexture.GenerateTextureForCurve();
109 | shouldUpdateTexture = false;
110 | }
111 |
112 | // Draw the texture itself.
113 | if (texture == null)
114 | EditorGUI.DrawRect(textureRect, Color.black);
115 | else
116 | GUI.DrawTexture(textureRect, texture, ScaleMode.StretchToFill);
117 |
118 | AnimationCurveAsset asset =
119 | (AnimationCurveAsset)property.FindPropertyRelative(CurvePropertyAsset).objectReferenceValue;
120 |
121 | Rect buttonRect = previewRect.GetControlNextRect().Indent(1);
122 | if (animationCurveTexture.Mode == AnimationCurveTexture.Modes.Local)
123 | {
124 | // Draw a button to save the local curve to an asset.
125 | string pathToSaveTo = asset == null ? "" : AssetDatabase.GetAssetPath(asset);
126 | bool hasPathToSaveTo = !string.IsNullOrEmpty(pathToSaveTo);
127 | Rect saveRect = buttonRect;
128 | Rect saveAsRect = hasPathToSaveTo
129 | ? buttonRect.GetSubRectFromRight(70, out saveRect)
130 | : buttonRect;
131 |
132 | // Big button to save to the current asset (only shows up if an asset is selected)
133 | if (hasPathToSaveTo)
134 | {
135 | string fileName = Path.GetFileName(pathToSaveTo);
136 | bool save = GUI.Button(saveRect, $"Save ({fileName})");
137 | if (save)
138 | SaveToAsset(curveProperty, property, pathToSaveTo);
139 | }
140 |
141 | // Smaller button to pick a new asset to save to.
142 | bool saveAs = GUI.Button(saveAsRect, "Save As...");
143 | if (saveAs)
144 | SaveToAsset(curveProperty, property, null, pathToSaveTo);
145 | }
146 | else if (animationCurveTexture.Mode == AnimationCurveTexture.Modes.Asset)
147 | {
148 | // Draw a button to start editing a local copy of this asset.
149 | bool wasGuiEnabled = GUI.enabled;
150 | GUI.enabled = asset != null;
151 | bool editLocalCopy = GUI.Button(buttonRect, "Edit Local Copy");
152 | GUI.enabled = wasGuiEnabled;
153 | if (editLocalCopy)
154 | {
155 | // Make a copy of the animation curve.
156 | property.FindPropertyRelative(CurvePropertyLocal).animationCurveValue = asset.AnimationCurve;
157 |
158 | // Switch to local mode.
159 | property.FindPropertyRelative("mode").enumValueIndex =
160 | (int)AnimationCurveTexture.Modes.Local;
161 | }
162 | }
163 |
164 | if (animationCurveTexture.Mode != AnimationCurveTexture.Modes.Texture)
165 | {
166 | buttonRect = buttonRect.GetControlNextRect();
167 | bool wasGuiEnabled = GUI.enabled;
168 | GUI.enabled = asset != null || animationCurveTexture.Mode == AnimationCurveTexture.Modes.Local;
169 | bool exportToTexture = GUI.Button(buttonRect, "Export To Texture");
170 | string pathToSaveTo = asset == null ? "" : AssetDatabase.GetAssetPath(asset);
171 | if (exportToTexture)
172 | ExportToTexture(animationCurveTexture, property, null, pathToSaveTo);
173 | GUI.enabled = wasGuiEnabled;
174 | }
175 | }
176 |
177 | // Re-bake the texture again if needed.
178 | if (shouldUpdateTexture)
179 | {
180 | // NOTE: Need to apply the modified properties otherwise it will generate a texture using old data.
181 | property.serializedObject.ApplyModifiedProperties();
182 | animationCurveTexture.GenerateTextureForCurve();
183 | }
184 | }
185 |
186 | private void SaveToAsset(
187 | SerializedProperty curveProperty, SerializedProperty owner, string path = null, string startingPath = null)
188 | {
189 | if (string.IsNullOrEmpty(path))
190 | {
191 | path = EditorUtility.SaveFilePanelInProject(
192 | "Save Animation Curve Asset", "Animation Curve", "asset",
193 | "Save this local animation curve to a re-usable animation curve asset.", startingPath);
194 | }
195 |
196 | if (string.IsNullOrEmpty(path))
197 | return;
198 |
199 | // First check if that asset existed already.
200 | AnimationCurveAsset asset = AssetDatabase.LoadAssetAtPath(path);
201 | bool existedAlready = asset != null;
202 |
203 | if (!existedAlready)
204 | {
205 | // Create a new animation curve asset for this curve.
206 | asset = ScriptableObject.CreateInstance();
207 | asset.AnimationCurve = curveProperty.animationCurveValue;
208 | AssetDatabase.CreateAsset(asset, path);
209 | }
210 | else
211 | {
212 | // Update the existing asset with this animation curve.
213 | using SerializedObject so = new SerializedObject(asset);
214 | so.Update();
215 | so.FindProperty("animationCurve").animationCurveValue = curveProperty.animationCurveValue;
216 | so.ApplyModifiedProperties();
217 | }
218 |
219 | // Set the animation curve texture to Asset mode and assign the selected asset.
220 | owner.serializedObject.Update();
221 | owner.FindPropertyRelative("mode").enumValueIndex =
222 | (int)AnimationCurveTexture.Modes.Asset;
223 | owner.FindPropertyRelative("animationCurveAsset").objectReferenceValue = asset;
224 | owner.serializedObject.ApplyModifiedProperties();
225 | }
226 |
227 | private void ExportToTexture(
228 | AnimationCurveTexture animationCurveTexture, SerializedProperty owner, string path = null,
229 | string startingPath = null)
230 | {
231 | if (string.IsNullOrEmpty(path))
232 | {
233 | path = EditorUtility.SaveFilePanelInProject(
234 | "Export AnimationCurve To Texture", "AnimationCurve", "png",
235 | "Export this animationCurve as a texture.",
236 | startingPath);
237 | }
238 |
239 | if (string.IsNullOrEmpty(path))
240 | return;
241 |
242 | // Write the texture to a PNG file.
243 | byte[] bytes = animationCurveTexture.Texture.EncodeToPNG();
244 | const string assetsFolder = "Assets";
245 | string absolutePath = path.Substring(assetsFolder.Length + 1);
246 | absolutePath = Application.dataPath + Path.AltDirectorySeparatorChar + absolutePath;
247 | File.WriteAllBytes(absolutePath, bytes);
248 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport);
249 |
250 | // Let's make sure we assign the right settings.
251 | TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(path);
252 | textureImporter.wrapMode = animationCurveTexture.WrapMode;
253 | textureImporter.filterMode = animationCurveTexture.FilterMode;
254 | textureImporter.mipmapEnabled = false;
255 | textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
256 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport);
257 |
258 | // Set the AnimationCurveTexture to Texture mode and assign the created texture.
259 | Texture2D textureAsset = AssetDatabase.LoadAssetAtPath(path);
260 | owner.serializedObject.Update();
261 | owner.FindPropertyRelative("mode").enumValueIndex = (int)AnimationCurveTexture.Modes.Texture;
262 | owner.FindPropertyRelative("texture").objectReferenceValue = textureAsset;
263 | owner.serializedObject.ApplyModifiedProperties();
264 | }
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/Editor/Curves/AnimationCurveTexturePropertyDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bce66af72a795fb4988eb507e2da1fd0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Extensions.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a00cd1cede9202d42afa29d5ee87703b
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Extensions/MemberInfoExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 |
5 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Extensions
6 | {
7 | public static class MemberInfoExtensions
8 | {
9 | public static T GetAttribute(this MemberInfo memberInfo, bool inherit = true)
10 | where T : Attribute
11 | {
12 | object[] attributes = memberInfo.GetCustomAttributes(inherit);
13 | return attributes.OfType().FirstOrDefault();
14 | }
15 |
16 | public static bool HasAttribute(this MemberInfo memberInfo, bool inherit = true)
17 | where T : Attribute
18 | {
19 | object[] attributes = memberInfo.GetCustomAttributes(inherit);
20 | return attributes.OfType().Any();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Editor/Extensions/MemberInfoExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f5d2c569e43c55a41bb55236f3a30113
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Extensions/PropertyDrawerExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Reflection;
4 | using System.Text.RegularExpressions;
5 | using UnityEditor;
6 | using Object = System.Object;
7 |
8 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Extensions
9 | {
10 | public static class PropertyDrawerExtensions
11 | {
12 | public static object GetActualObjectByPath(SerializedObject serializedObject, string path)
13 | {
14 | return GetActualObjectByPath(serializedObject.targetObject, path);
15 | }
16 |
17 | public static object GetActualObjectByPath(Object owner, string path)
18 | {
19 | // Sample paths: connections.Array.data[0].to
20 | // connection.to
21 | // to
22 |
23 | string[] pathSections = path.Split('.');
24 |
25 | object value = owner;
26 | for (int i = 0; i < pathSections.Length; i++)
27 | {
28 | Type valueType = value.GetType();
29 |
30 | if (valueType.IsArray || valueType.IsList())
31 | {
32 | // Parse the next section which contains the index.
33 | string indexPathSection = pathSections[i + 1];
34 | indexPathSection = Regex.Replace(indexPathSection, @"\D", "");
35 | int index = int.Parse(indexPathSection);
36 |
37 | if (valueType.IsArray)
38 | {
39 | // Get the value from the array.
40 | Array array = value as Array;
41 | value = array.GetValue(index);
42 | }
43 | else
44 | {
45 | // Get the value from the list.
46 | IList list = value as IList;
47 | value = list[index];
48 | }
49 |
50 | // We can now skip the next section which is the one with the index.
51 | i++;
52 | continue;
53 | }
54 |
55 | // Go deeper down the hierarchy by searching in the current value for a field with
56 | // the same name as the current path section and then getting that value.
57 | FieldInfo fieldInfo = valueType.GetField(
58 | pathSections[i], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
59 | value = fieldInfo.GetValue(value);
60 | }
61 |
62 | return value;
63 | }
64 |
65 | public static object GetParentObject(
66 | this PropertyDrawer propertyDrawer, SerializedProperty property)
67 | {
68 | string path = property.propertyPath;
69 | int indexOfLastSeparator = path.LastIndexOf(".", StringComparison.Ordinal);
70 |
71 | // No separators means it's a root object and there's no parent.
72 | if (indexOfLastSeparator == -1)
73 | return property.serializedObject.targetObject;
74 |
75 | string pathExcludingLastObject = path.Substring(0, indexOfLastSeparator);
76 | return GetActualObjectByPath(property.serializedObject, pathExcludingLastObject);
77 | }
78 |
79 | public static T GetActualObject(
80 | this PropertyDrawer propertyDrawer, FieldInfo fieldInfo, SerializedProperty property)
81 | where T : class
82 | {
83 | return property.GetValue();
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Editor/Extensions/PropertyDrawerExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e67cbbc3d289af84384590aea64ec8e4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Extensions/RectExtensions.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | #if UNITY_EDITOR
4 | using UnityEditor;
5 | #endif // UNITY_EDITOR
6 |
7 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Extensions
8 | {
9 | public static class RectExtensions
10 | {
11 | public static Vector2 GetPosition(this Rect rect, Vector2 localPosition)
12 | {
13 | return new Vector2(rect.xMin + rect.width * localPosition.x, rect.yMin + rect.height * localPosition.y);
14 | }
15 |
16 | public static Rect Expand(this Rect rect, float xMin, float xMax, float yMin, float yMax)
17 | {
18 | rect.xMin -= xMin;
19 | rect.xMax += xMax;
20 | rect.yMin -= yMin;
21 | rect.yMax += yMax;
22 | return rect;
23 | }
24 |
25 | public static Rect Expand(this Rect rect, float amount)
26 | {
27 | return rect.Expand(amount, amount, amount, amount);
28 | }
29 |
30 | public static Rect Inset(this Rect rect, float xMin, float xMax, float yMin, float yMax)
31 | {
32 | rect.xMin += xMin;
33 | rect.xMax -= xMax;
34 | rect.yMin += yMin;
35 | rect.yMax -= yMax;
36 | return rect;
37 | }
38 |
39 | public static Rect Inset(this Rect rect, float inset)
40 | {
41 | return rect.Inset(inset, inset, inset, inset);
42 | }
43 |
44 | #if UNITY_EDITOR
45 | public static Rect GetControlRect(this Rect rect, int index)
46 | {
47 | return GetControlRect(rect, index, EditorGUIUtility.singleLineHeight);
48 | }
49 |
50 | public static Rect GetControlRect(this Rect rect, int index, float height)
51 | {
52 | return new Rect(
53 | rect.x, rect.yMin + (height + EditorGUIUtility.standardVerticalSpacing) * index, rect.width, height);
54 | }
55 |
56 | public static Rect GetControlFirstRect(this Rect rect)
57 | {
58 | return GetControlFirstRect(rect, EditorGUIUtility.singleLineHeight);
59 | }
60 |
61 | public static Rect GetControlFirstRect(this Rect rect, float height)
62 | {
63 | return new Rect(rect.x, rect.y, rect.width, height);
64 | }
65 |
66 | public static Rect GetControlLastRect(this Rect rect)
67 | {
68 | return GetControlLastRect(rect, EditorGUIUtility.singleLineHeight);
69 | }
70 |
71 | public static Rect GetControlLastRect(this Rect rect, float height)
72 | {
73 | return new Rect(rect.x, rect.yMax - height, rect.width, height);
74 | }
75 |
76 | public static Rect GetControlPreviousRect(this Rect rect)
77 | {
78 | return GetControlPreviousRect(rect, EditorGUIUtility.singleLineHeight);
79 | }
80 |
81 | public static Rect GetControlPreviousRect(this Rect rect, float height)
82 | {
83 | return new Rect(rect.x, rect.yMin - height - EditorGUIUtility.standardVerticalSpacing, rect.width, height);
84 | }
85 |
86 | public static Rect GetControlNextRect(this Rect rect)
87 | {
88 | return GetControlNextRect(rect, EditorGUIUtility.singleLineHeight);
89 | }
90 |
91 | public static Rect GetControlNextRect(this Rect rect, float height)
92 | {
93 | return new Rect(rect.x, rect.yMax + EditorGUIUtility.standardVerticalSpacing, rect.width, height);
94 | }
95 |
96 | public static Rect GetControlRemainderVertical(this Rect rect, Rect occupant)
97 | {
98 | return new Rect(rect.x, occupant.yMax, rect.width, rect.height - occupant.height);
99 | }
100 |
101 | public static Rect GetLabelRect(this Rect rect)
102 | {
103 | return new Rect(
104 | rect.x, rect.y, EditorGUIUtility.labelWidth,
105 | EditorGUIUtility.singleLineHeight);
106 | }
107 |
108 | public static Rect GetLabelRectRemainder(this Rect rect)
109 | {
110 | return new Rect(
111 | rect.x + EditorGUIUtility.labelWidth, rect.y,
112 | rect.width - EditorGUIUtility.labelWidth,
113 | EditorGUIUtility.singleLineHeight);
114 | }
115 |
116 | public static Rect GetLabelRect(this Rect rect, out Rect remainder)
117 | {
118 | remainder = rect.GetLabelRectRemainder();
119 | return rect.GetLabelRect();
120 | }
121 |
122 | public static Rect Indent(this Rect rect)
123 | {
124 | return EditorGUI.IndentedRect(rect);
125 | }
126 |
127 | public static Rect Indent(this Rect rect, int amount)
128 | {
129 | int originalIndentLevel = EditorGUI.indentLevel;
130 | EditorGUI.indentLevel = amount;
131 | Rect result = EditorGUI.IndentedRect(rect);
132 | EditorGUI.indentLevel = originalIndentLevel;
133 |
134 | return result;
135 | }
136 | #endif
137 |
138 | public static Rect GetSubRectFromLeft(this Rect rect, float width)
139 | {
140 | return new Rect(rect.x, rect.y, width, rect.height);
141 | }
142 |
143 | public static Rect GetSubRectFromLeft(this Rect rect, float width, out Rect remainder)
144 | {
145 | remainder = rect.GetSubRectFromRight(rect.width - width);
146 | return rect.GetSubRectFromLeft(width);
147 | }
148 |
149 | public static Rect GetSubRectFromRight(this Rect rect, float width)
150 | {
151 | return new Rect(rect.xMax - width, rect.y, width, rect.height);
152 | }
153 |
154 | public static Rect GetSubRectFromRight(this Rect rect, float width, out Rect remainder)
155 | {
156 | remainder = rect.GetSubRectFromLeft(rect.width - width);
157 | return rect.GetSubRectFromRight(width);
158 | }
159 |
160 | public static Rect GetSubRectFromTop(this Rect rect, float height)
161 | {
162 | return new Rect(rect.x, rect.y, rect.width, height);
163 | }
164 |
165 | public static Rect GetSubRectFromBottom(this Rect rect, float height)
166 | {
167 | return new Rect(rect.x, rect.yMax - height, rect.width, height);
168 | }
169 |
170 | public static Rect SubtractFromLeft(this Rect rect, float width)
171 | {
172 | return new Rect(rect.x + width, rect.y, rect.width - width, rect.height);
173 | }
174 |
175 | public static Rect SubtractFromRight(this Rect rect, float width)
176 | {
177 | return new Rect(rect.x, rect.y, rect.width - width, rect.height);
178 | }
179 |
180 | public static Rect InverseTransform(this Rect rect, Rect child)
181 | {
182 | return new Rect(child.x - rect.x, child.y - rect.y, child.width, child.height);
183 | }
184 |
185 | public static Rect Transform(this Rect rect, Rect child)
186 | {
187 | return new Rect(child.x + rect.x, child.y + rect.y, child.width, child.height);
188 | }
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/Editor/Extensions/RectExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e8354303eab10ea45a28c8ade9cf80d0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Extensions/SerializedPropertyExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Linq;
3 | using System.Reflection;
4 | using System.Text.RegularExpressions;
5 | using UnityEditor;
6 | using UnityEngine;
7 |
8 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Extensions
9 | {
10 | ///
11 | /// Utilities for things like quickly getting a custom value of a serialized property.
12 | ///
13 | public static class SerializedPropertyExtensions
14 | {
15 | ///
16 | /// Courtesy of douduck08. Cheers.
17 | /// https://gist.github.com/douduck08/6d3e323b538a741466de00c30aa4b61f
18 | ///
19 | public static T GetValue(this SerializedProperty property) where T : class
20 | {
21 | object obj = property.serializedObject.targetObject;
22 | string path = property.propertyPath.Replace(".Array.data", "");
23 | string[] fieldStructure = path.Split('.');
24 | Regex rgx = new Regex(@"\[\d+\]");
25 | for (int i = 0; i < fieldStructure.Length; i++)
26 | {
27 | if (TryParseFieldStructure(fieldStructure[i], out string fieldName, out int index))
28 | {
29 | obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
30 | }
31 | else
32 | {
33 | obj = GetFieldValue(fieldStructure[i], obj);
34 | }
35 | }
36 |
37 | return (T)obj;
38 | }
39 |
40 | private static bool TryParseFieldStructure(string input, out string fieldName, out int index)
41 | {
42 | int endIndex = input.LastIndexOf(']');
43 | if (endIndex == -1)
44 | {
45 | fieldName = null;
46 | index = -1;
47 | return false;
48 | }
49 |
50 | int startIndex = input.LastIndexOf('[', endIndex - 1);
51 | index = int.Parse(input.Substring(startIndex + 1, endIndex - startIndex - 1));
52 | fieldName = input.Substring(0, startIndex) + input.Substring(endIndex + 1);
53 | return true;
54 | }
55 |
56 | public static bool SetValue(this SerializedProperty property, T value) where T : class
57 | {
58 | object obj = property.serializedObject.targetObject;
59 | string path = property.propertyPath.Replace(".Array.data", "");
60 | string[] fieldStructure = path.Split('.');
61 | Regex rgx = new Regex(@"\[\d+\]");
62 | for (int i = 0; i < fieldStructure.Length - 1; i++)
63 | {
64 | if (fieldStructure[i].Contains("["))
65 | {
66 | int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(char.IsDigit).ToArray()));
67 | obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
68 | }
69 | else
70 | {
71 | obj = GetFieldValue(fieldStructure[i], obj);
72 | }
73 | }
74 |
75 | string fieldName = fieldStructure.Last();
76 | if (fieldName.Contains("["))
77 | {
78 | int index = System.Convert.ToInt32(new string(fieldName.Where(char.IsDigit).ToArray()));
79 | return SetFieldValueWithIndex(rgx.Replace(fieldName, ""), obj, index, value);
80 | }
81 |
82 | return SetFieldValue(fieldName, obj, value);
83 | }
84 |
85 | private static object GetFieldValue(
86 | string fieldName, object obj,
87 | BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
88 | BindingFlags.NonPublic)
89 | {
90 | FieldInfo field = obj.GetType().GetField(fieldName, bindings);
91 | if (field != null)
92 | return field.GetValue(obj);
93 |
94 | return default(object);
95 | }
96 |
97 | private static object GetFieldValueWithIndex(
98 | string fieldName, object obj, int index,
99 | BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
100 | BindingFlags.NonPublic)
101 | {
102 | if (index < 0)
103 | return null;
104 |
105 | FieldInfo field = obj.GetType().GetField(fieldName, bindings);
106 | if (field != null)
107 | {
108 | object list = field.GetValue(obj);
109 | if (list.GetType().IsArray)
110 | {
111 | object[] array = (object[])list;
112 | if (index >= array.Length)
113 | return null;
114 | return array[index];
115 | }
116 | if (list is IEnumerable)
117 | {
118 | IList iList = (IList)list;
119 | if (index >= iList.Count)
120 | return null;
121 | return iList[index];
122 | }
123 | }
124 |
125 | return null;
126 | }
127 |
128 | public static bool SetFieldValue(
129 | string fieldName, object obj, object value, bool includeAllBases = false,
130 | BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
131 | BindingFlags.NonPublic)
132 | {
133 | FieldInfo field = obj.GetType().GetField(fieldName, bindings);
134 | if (field != null)
135 | {
136 | field.SetValue(obj, value);
137 | return true;
138 | }
139 |
140 | return false;
141 | }
142 |
143 | public static bool SetFieldValueWithIndex(
144 | string fieldName, object obj, int index, object value, bool includeAllBases = false,
145 | BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
146 | BindingFlags.NonPublic)
147 | {
148 | FieldInfo field = obj.GetType().GetField(fieldName, bindings);
149 | if (field != null)
150 | {
151 | object list = field.GetValue(obj);
152 | if (list.GetType().IsArray)
153 | {
154 | ((object[])list)[index] = value;
155 | return true;
156 | }
157 |
158 | if (value is IEnumerable)
159 | {
160 | ((IList)list)[index] = value;
161 | return true;
162 | }
163 | }
164 |
165 | return false;
166 | }
167 |
168 | ///
169 | /// From: https://gist.github.com/monry/9de7009689cbc5050c652bcaaaa11daa
170 | ///
171 | public static SerializedProperty GetParent(this SerializedProperty serializedProperty)
172 | {
173 | string[] propertyPaths = serializedProperty.propertyPath.Split('.');
174 | if (propertyPaths.Length <= 1)
175 | {
176 | return default(SerializedProperty);
177 | }
178 |
179 | SerializedProperty parentSerializedProperty =
180 | serializedProperty.serializedObject.FindProperty(propertyPaths.First());
181 | for (int index = 1; index < propertyPaths.Length - 1; index++)
182 | {
183 | if (propertyPaths[index] == "Array")
184 | {
185 | if (index + 1 == propertyPaths.Length - 1)
186 | {
187 | // reached the end
188 | break;
189 | }
190 | if (propertyPaths.Length > index + 1 && Regex.IsMatch(propertyPaths[index + 1], "^data\\[\\d+\\]$"))
191 | {
192 | Match match = Regex.Match(propertyPaths[index + 1], "^data\\[(\\d+)\\]$");
193 | int arrayIndex = int.Parse(match.Groups[1].Value);
194 | parentSerializedProperty = parentSerializedProperty.GetArrayElementAtIndex(arrayIndex);
195 | index++;
196 | }
197 | }
198 | else
199 | {
200 | parentSerializedProperty = parentSerializedProperty.FindPropertyRelative(propertyPaths[index]);
201 | }
202 | }
203 |
204 | return parentSerializedProperty;
205 | }
206 |
207 | public static SerializedProperty FindPropertyInParent(
208 | this SerializedProperty serializedProperty, string propertyPath)
209 | {
210 | SerializedProperty parentProperty = serializedProperty.GetParent();
211 |
212 | // If this is a top level property already, just find the property in the serialized object itself.
213 | if (parentProperty == default)
214 | return serializedProperty.serializedObject.FindProperty(propertyPath);
215 |
216 | return parentProperty.FindPropertyRelative(propertyPath);
217 | }
218 |
219 | public static SerializedProperty AddArrayElement(this SerializedProperty serializedProperty)
220 | {
221 | serializedProperty.InsertArrayElementAtIndex(serializedProperty.arraySize);
222 | return serializedProperty.GetArrayElementAtIndex(serializedProperty.arraySize - 1);
223 | }
224 |
225 | public static bool IsInArray(this SerializedProperty serializedProperty)
226 | {
227 | SerializedProperty parent = serializedProperty.GetParent();
228 | return parent.isArray;
229 | }
230 |
231 | public static bool IsFirstInArray(this SerializedProperty serializedProperty)
232 | {
233 | SerializedProperty parent = serializedProperty.GetParent();
234 | return parent.isArray && parent.GetArrayElementAtIndex(0).propertyPath == serializedProperty.propertyPath;
235 | }
236 |
237 | public static void PropertyFieldForChildren(this SerializedProperty serializedProperty, Rect position)
238 | {
239 | SerializedProperty iterator = serializedProperty.Copy();
240 | if (!iterator.hasVisibleChildren)
241 | return;
242 |
243 | iterator.NextVisible(true);
244 |
245 | // Make sure the children are indented.
246 | EditorGUI.indentLevel++;
247 |
248 | SerializedProperty nextSibling = serializedProperty.Copy();
249 | bool hasSibling = nextSibling.NextVisible(false);
250 | float y = position.yMin;
251 | do
252 | {
253 | float height = EditorGUI.GetPropertyHeight(iterator);
254 | Rect controlRect = new Rect(position.xMin, y, position.width, height);
255 | EditorGUI.PropertyField(controlRect, iterator, true);
256 | y += height;
257 |
258 | // Spacing, too!
259 | y += EditorGUIUtility.standardVerticalSpacing;
260 | }
261 | while (iterator.NextVisible(false) &&
262 | (!hasSibling || !SerializedProperty.EqualContents(iterator, nextSibling)));
263 |
264 | // Restore indentation.
265 | EditorGUI.indentLevel--;
266 | }
267 |
268 | public static void PropertyFieldForChildren(this SerializedProperty serializedProperty)
269 | {
270 | SerializedProperty iterator = serializedProperty.Copy();
271 | if (!iterator.hasVisibleChildren)
272 | return;
273 |
274 | iterator.NextVisible(true);
275 |
276 | SerializedProperty nextSibling = serializedProperty.Copy();
277 | bool hasSibling = nextSibling.NextVisible(false);
278 |
279 | do
280 | {
281 | EditorGUILayout.PropertyField(iterator, true);
282 | }
283 | while (iterator.NextVisible(false) &&
284 | (!hasSibling || !SerializedProperty.EqualContents(iterator, nextSibling)));
285 | }
286 |
287 | public static Gradient GetGradient(this SerializedProperty gradientProperty)
288 | {
289 | PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty(
290 | "gradientValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
291 |
292 | if (propertyInfo == null)
293 | return null;
294 |
295 | return propertyInfo.GetValue(gradientProperty, null) as Gradient;
296 | }
297 |
298 | public static void SetGradient(this SerializedProperty gradientProperty, Gradient gradient)
299 | {
300 | PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty(
301 | "gradientValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
302 |
303 | if (propertyInfo == null)
304 | return;
305 |
306 | propertyInfo.SetValue(gradientProperty, gradient);
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/Editor/Extensions/SerializedPropertyExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3a5e5ef8abb84b34f951076ff392882d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Extensions/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Reflection;
6 | using UnityEngine;
7 |
8 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Extensions
9 | {
10 | public static class TypeExtensions
11 | {
12 | public static List GetTypesUpUntilBaseClass(
13 | this Type type, bool includeBaseClass = true)
14 | {
15 | List types = new List();
16 | while (typeof(BaseClass).IsAssignableFrom(type))
17 | {
18 | if (type == typeof(BaseClass) && !includeBaseClass)
19 | break;
20 |
21 | types.Add(type);
22 |
23 | type = type.BaseType;
24 |
25 | if (type == typeof(BaseClass) && includeBaseClass)
26 | break;
27 | }
28 | return types;
29 | }
30 |
31 | public static List GetFieldsUpUntilBaseClass(
32 | this Type type, bool includeBaseClass = true)
33 | {
34 | List fields = new List();
35 | while (typeof(BaseClass).IsAssignableFrom(type))
36 | {
37 | if (type == typeof(BaseClass) && !includeBaseClass)
38 | break;
39 |
40 | fields.AddRange(type.GetFields(
41 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
42 |
43 | type = type.BaseType;
44 |
45 | if (type == typeof(BaseClass) && includeBaseClass)
46 | break;
47 | }
48 | return fields;
49 | }
50 |
51 | public static List GetFieldsUpUntilBaseClass(
52 | this Type type, bool includeBaseClass = true)
53 | {
54 | List fields = GetFieldsUpUntilBaseClass(type, includeBaseClass);
55 |
56 | for (int i = fields.Count - 1; i >= 0; i--)
57 | {
58 | if (!typeof(FieldType).IsAssignableFrom(fields[i].FieldType))
59 | fields.RemoveAt(i);
60 | }
61 | return fields;
62 | }
63 |
64 | public static List GetFieldValuesUpUntilBaseClass(
65 | this Type type, object instance, bool includeBaseClass = true)
66 | {
67 | List fields = GetFieldsUpUntilBaseClass(
68 | type, includeBaseClass);
69 |
70 | List values = new List();
71 | for (int i = 0; i < fields.Count; i++)
72 | {
73 | values.Add((FieldType)fields[i].GetValue(instance));
74 | }
75 | return values;
76 | }
77 |
78 | public static FieldInfo GetDeclaringFieldUpUntilBaseClass(
79 | this Type type, object instance, FieldType value, bool includeBaseClass = true)
80 | {
81 | List fields = GetFieldsUpUntilBaseClass(
82 | type, includeBaseClass);
83 |
84 | FieldType fieldValue;
85 | for (int i = 0; i < fields.Count; i++)
86 | {
87 | fieldValue = (FieldType)fields[i].GetValue(instance);
88 | if (Equals(fieldValue, value))
89 | return fields[i];
90 | }
91 |
92 | return null;
93 | }
94 |
95 | public static string GetNameOfDeclaringField(
96 | this Type type, object instance, FieldType value, bool capitalize = false)
97 | {
98 | FieldInfo declaringField = type
99 | .GetDeclaringFieldUpUntilBaseClass(instance, value);
100 |
101 | if (declaringField == null)
102 | return null;
103 |
104 | return GetFieldName(type, declaringField, capitalize);
105 | }
106 |
107 | public static string GetFieldName(this Type type, FieldInfo fieldInfo, bool capitalize = false)
108 | {
109 | string name = fieldInfo.Name;
110 |
111 | if (!capitalize)
112 | return name;
113 |
114 | if (name.Length <= 1)
115 | return name.ToUpper();
116 |
117 | return char.ToUpper(name[0]) + name.Substring(1);
118 | }
119 |
120 | public static List GetAllAssignableFields(this Type type, BindingFlags bindingFlags)
121 | {
122 | List result = new List();
123 | FieldInfo[] fieldCandidates = type.GetFields(bindingFlags);
124 | for (int i = 0; i < fieldCandidates.Length; i++)
125 | {
126 | if (typeof(FieldType).IsAssignableFrom(fieldCandidates[i].FieldType))
127 | result.Add(fieldCandidates[i]);
128 | }
129 | return result;
130 | }
131 |
132 | public static Type[] GetAllAssignableClasses(
133 | this Type type, bool includeAbstract = true, bool includeItself = false)
134 | {
135 | return AppDomain.CurrentDomain.GetAssemblies()
136 | .SelectMany(a => a.GetTypes())
137 | .Where(t => type.IsAssignableFrom(t) && (t != type || includeItself) && (includeAbstract || !t.IsAbstract))
138 | .ToArray();
139 | }
140 |
141 | public static Type[] GetAllClassesWithAttribute(bool includeAbstract = true)
142 | where T : Attribute
143 | {
144 | return AppDomain.CurrentDomain.GetAssemblies()
145 | .SelectMany(a => a.GetTypes())
146 | .Where(t => t.HasAttribute() && (includeAbstract || !t.IsAbstract)).ToArray();
147 | }
148 |
149 | public static MethodInfo[] GetAllMethodsWithAttribute(
150 | this Type type, bool includeAbstract = true,
151 | BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static)
152 | where T : Attribute
153 | {
154 | return type.GetMethods(bindingFlags)
155 | .Where(t => t.HasAttribute() && (includeAbstract || !t.IsAbstract)).ToArray();
156 | }
157 |
158 | public static void ExecuteStaticMethod(this Type type, string name, params object[] arguments)
159 | {
160 | MethodInfo method = type.GetMethod(name);
161 | if (method == null || !method.IsStatic)
162 | return;
163 |
164 | method.Invoke(null, arguments);
165 | }
166 |
167 | public static MethodInfo GetMethodIncludingFromBaseClasses(this Type type, string name)
168 | {
169 | MethodInfo methodInfo = null;
170 | Type baseType = type;
171 | while (methodInfo == null)
172 | {
173 | methodInfo = baseType.GetMethod(
174 | name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
175 |
176 | if (methodInfo != null)
177 | return methodInfo;
178 |
179 | baseType = baseType.BaseType;
180 | if (baseType == null)
181 | break;
182 | }
183 |
184 | return null;
185 | }
186 |
187 | ///
188 | /// Courtesy of codeproject.com:
189 | /// https://www.codeproject.com/Tips/5267157/How-to-Get-a-Collection-Element-Type-Using-Reflect
190 | ///
191 | public static bool IsList(this Type type)
192 | {
193 | if (null == type)
194 | throw new ArgumentNullException(nameof(type));
195 |
196 | if (typeof(IList).IsAssignableFrom(type))
197 | return true;
198 |
199 | foreach (Type it in type.GetInterfaces())
200 | {
201 | if (it.IsGenericType && typeof(IList<>) == it.GetGenericTypeDefinition())
202 | return true;
203 | }
204 | return false;
205 | }
206 |
207 | public static List GetSerializableFields(this Type type, bool includeParents = true)
208 | {
209 | BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
210 | if (includeParents)
211 | flags |= BindingFlags.FlattenHierarchy;
212 |
213 | FieldInfo[] allFields = type.GetFields(flags);
214 | List serializableFields = new List();
215 | foreach (FieldInfo fieldInfo in allFields)
216 | {
217 | // Public fields get serialized regardless.
218 | if (fieldInfo.IsPublic)
219 | {
220 | serializableFields.Add(fieldInfo);
221 | continue;
222 | }
223 |
224 | // Private fields only get serialized if they have a certain attribute.
225 | if (fieldInfo.HasAttribute() || fieldInfo.HasAttribute())
226 | serializableFields.Add(fieldInfo);
227 | }
228 |
229 | return serializableFields;
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/Editor/Extensions/TypeExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7102467552b3821479d156083f0d5f3d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Gradients.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c7f0cee69d8d9514fbb2bda16282a905
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/Gradients/GradientAssetEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Gradients
5 | {
6 | ///
7 | /// Draws the gradient of a gradient asset a little nicer.
8 | ///
9 | [CustomEditor(typeof(GradientAsset))]
10 | public class GradientAssetEditor : Editor
11 | {
12 | private SerializedProperty gradientProperty;
13 |
14 | private void OnEnable()
15 | {
16 | gradientProperty = serializedObject.FindProperty("gradient");
17 | }
18 |
19 | public override void OnInspectorGUI()
20 | {
21 | serializedObject.Update();
22 | EditorGUILayout.PropertyField(gradientProperty, GUIContent.none);
23 | serializedObject.ApplyModifiedProperties();
24 | }
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Editor/Gradients/GradientAssetEditor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 742bbd291db4dcc478c155ba50296937
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Gradients/GradientTexturePropertyDrawer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using RoyTheunissen.CurvesAndGradientsToTexture.Extensions;
4 | using UnityEditor;
5 | using UnityEngine;
6 |
7 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Gradients
8 | {
9 | ///
10 | /// Draws the gradient itself but also allows you to unfold it and tweak some of the more advanced settings.
11 | ///
12 | /// NOTE: Despite the apparent similarity, this code is not shared with AnimationCurveTexturePropertyDrawer because
13 | /// I expect these two utilities to be diverging a lot, so any effort to consolidate the two will likely be undone.
14 | ///
15 | [CustomPropertyDrawer(typeof(GradientTexture))]
16 | public class GradientTexturePropertyDrawer : PropertyDrawer
17 | {
18 | private const string GradientPropertyAsset = "gradientAsset";
19 | private const string GradientPropertyLocal = "gradientLocal";
20 | private const string GradientPropertyTexture = "texture";
21 |
22 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
23 | {
24 | float height = EditorGUI.GetPropertyHeight(property, label);
25 |
26 | if (property.isExpanded)
27 | {
28 | // One line for the preview.
29 | height += EditorGUIUtility.singleLineHeight;
30 |
31 | // And another for the edit or save button.
32 | if (property.FindPropertyRelative("mode").enumValueIndex != (int)GradientTexture.Modes.Texture)
33 | {
34 | // There's an edit/save button and then also an Export To Texture button.
35 | height += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 2;
36 | }
37 | }
38 |
39 | return height;
40 | }
41 |
42 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
43 | {
44 | GradientTexture gradientTexture = this.GetActualObject(fieldInfo, property);
45 |
46 | EditorGUI.BeginChangeCheck();
47 |
48 | // Draw the header.
49 | Rect foldoutRect = position.GetControlFirstRect().GetLabelRect(out Rect gradientRect);
50 | property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);
51 |
52 | // Draw a field next to the label so you can edit it straight away.
53 | string gradientPropertyName = null;
54 | switch (gradientTexture.Mode)
55 | {
56 | case GradientTexture.Modes.Asset:
57 | gradientPropertyName = GradientPropertyAsset;
58 | break;
59 | case GradientTexture.Modes.Local:
60 | gradientPropertyName = GradientPropertyLocal;
61 | break;
62 | case GradientTexture.Modes.Texture:
63 | gradientPropertyName = GradientPropertyTexture;
64 | break;
65 | default:
66 | throw new ArgumentOutOfRangeException();
67 | }
68 | SerializedProperty gradientProperty = property.FindPropertyRelative(gradientPropertyName);
69 | EditorGUI.PropertyField(gradientRect, gradientProperty, GUIContent.none);
70 |
71 | // Draw the children, too.
72 | if (property.isExpanded)
73 | {
74 | Rect childrenRect = position.GetSubRectFromBottom(position.height - foldoutRect.height);
75 | property.PropertyFieldForChildren(childrenRect);
76 | }
77 |
78 | bool shouldUpdateTexture = EditorGUI.EndChangeCheck();
79 |
80 | bool isTextureMode = gradientTexture.Mode == GradientTexture.Modes.Texture;
81 |
82 | // Draw some of the more internal properties for you to tweak once.
83 | if (property.isExpanded)
84 | {
85 | // Draw the current texture.
86 | Texture2D texture = gradientTexture.Texture;
87 | Rect previewRect = position.GetControlLastRect();
88 | if (!isTextureMode)
89 | previewRect = previewRect.GetControlPreviousRect().GetControlPreviousRect();
90 |
91 | Rect labelRect = previewRect.GetLabelRect(out Rect textureRect).Indent(1);
92 | EditorGUI.LabelField(labelRect, new GUIContent("Preview"));
93 |
94 | // Draw an update button.
95 | Rect generateButtonRect = textureRect.GetSubRectFromRight(70, out textureRect);
96 | bool forceUpdate = GUI.Button(generateButtonRect, "Update");
97 |
98 | // Update now so that it's done BEFORE we draw the texture, so you immediately see the result.
99 | if (forceUpdate || shouldUpdateTexture)
100 | {
101 | // NOTE: Need to apply the modified properties otherwise it will generate a texture using old data.
102 | property.serializedObject.ApplyModifiedProperties();
103 | gradientTexture.GenerateTexture();
104 | shouldUpdateTexture = false;
105 | }
106 |
107 | // Draw the texture itself.
108 | if (texture == null)
109 | EditorGUI.DrawRect(textureRect, Color.black);
110 | else
111 | GUI.DrawTexture(textureRect, texture, ScaleMode.StretchToFill);
112 |
113 | GradientAsset asset =
114 | (GradientAsset)property.FindPropertyRelative(GradientPropertyAsset).objectReferenceValue;
115 |
116 | Rect buttonRect = previewRect.GetControlNextRect().Indent(1);
117 | if (gradientTexture.Mode == GradientTexture.Modes.Local)
118 | {
119 | // Draw a button to save the local gradient to an asset.
120 | string pathToSaveTo = asset == null ? "" : AssetDatabase.GetAssetPath(asset);
121 | bool hasPathToSaveTo = !string.IsNullOrEmpty(pathToSaveTo);
122 | Rect saveRect = buttonRect;
123 | Rect saveAsRect = hasPathToSaveTo
124 | ? buttonRect.GetSubRectFromRight(70, out saveRect)
125 | : buttonRect;
126 |
127 | // Big button to save to the current asset (only shows up if an asset is selected)
128 | if (hasPathToSaveTo)
129 | {
130 | string fileName = Path.GetFileName(pathToSaveTo);
131 | bool save = GUI.Button(saveRect, $"Save ({fileName})");
132 | if (save)
133 | SaveToAsset(gradientProperty, property, pathToSaveTo);
134 | }
135 |
136 | // Smaller button to pick a new asset to save to.
137 | bool saveAs = GUI.Button(saveAsRect, "Save As...");
138 | if (saveAs)
139 | SaveToAsset(gradientProperty, property, null, pathToSaveTo);
140 | }
141 | else if (gradientTexture.Mode == GradientTexture.Modes.Asset)
142 | {
143 | // Draw a button to start editing a local copy of this asset.
144 | bool wasGuiEnabled = GUI.enabled;
145 | GUI.enabled = asset != null;
146 | bool editLocalCopy = GUI.Button(buttonRect, "Edit Local Copy");
147 | GUI.enabled = wasGuiEnabled;
148 | if (editLocalCopy)
149 | {
150 | // Make a copy of the asset.
151 | property.FindPropertyRelative(GradientPropertyLocal).SetGradient(asset.Gradient);
152 |
153 | // Switch to local mode.
154 | property.FindPropertyRelative("mode").enumValueIndex = (int)GradientTexture.Modes.Local;
155 | }
156 | }
157 |
158 | if (gradientTexture.Mode != GradientTexture.Modes.Texture)
159 | {
160 | buttonRect = buttonRect.GetControlNextRect();
161 | bool wasGuiEnabled = GUI.enabled;
162 | GUI.enabled = asset != null || gradientTexture.Mode == GradientTexture.Modes.Local;
163 | bool exportToTexture = GUI.Button(buttonRect, "Export To Texture");
164 | string pathToSaveTo = asset == null ? "" : AssetDatabase.GetAssetPath(asset);
165 | if (exportToTexture)
166 | ExportToTexture(gradientTexture, property, null, pathToSaveTo);
167 | GUI.enabled = wasGuiEnabled;
168 | }
169 | }
170 |
171 | // Re-bake the texture again if needed.
172 | if (shouldUpdateTexture)
173 | {
174 | // NOTE: Need to apply the modified properties otherwise it will generate a texture using old data.
175 | property.serializedObject.ApplyModifiedProperties();
176 | gradientTexture.GenerateTexture();
177 | }
178 | }
179 |
180 | private void SaveToAsset(
181 | SerializedProperty gradientProperty, SerializedProperty owner, string path = null, string startingPath = null)
182 | {
183 | if (string.IsNullOrEmpty(path))
184 | {
185 | path = EditorUtility.SaveFilePanelInProject(
186 | "Save Gradient Asset", "Gradient", "asset",
187 | "Save this local gradient to a re-usable gradient asset.", startingPath);
188 | }
189 |
190 | if (string.IsNullOrEmpty(path))
191 | return;
192 |
193 | // First check if that asset existed already.
194 | GradientAsset asset = AssetDatabase.LoadAssetAtPath(path);
195 | bool existedAlready = asset != null;
196 |
197 | if (!existedAlready)
198 | {
199 | // Create a new asset for this gradient.
200 | asset = ScriptableObject.CreateInstance();
201 | asset.Gradient = gradientProperty.GetGradient();
202 | AssetDatabase.CreateAsset(asset, path);
203 | }
204 | else
205 | {
206 | // Update the existing asset with this gradient.
207 | using SerializedObject so = new SerializedObject(asset);
208 | so.Update();
209 | so.FindProperty("gradient").SetGradient(gradientProperty.GetGradient());
210 | so.ApplyModifiedProperties();
211 | }
212 |
213 | // Set the gradient texture to Asset mode and assign the selected asset.
214 | owner.serializedObject.Update();
215 | owner.FindPropertyRelative("mode").enumValueIndex = (int)GradientTexture.Modes.Asset;
216 | owner.FindPropertyRelative("gradientAsset").objectReferenceValue = asset;
217 | owner.serializedObject.ApplyModifiedProperties();
218 | }
219 |
220 | private void ExportToTexture(
221 | GradientTexture gradientTexture, SerializedProperty owner, string path = null, string startingPath = null)
222 | {
223 | if (string.IsNullOrEmpty(path))
224 | {
225 | path = EditorUtility.SaveFilePanelInProject(
226 | "Export Gradient To Texture", "Gradient", "png", "Export this gradient as a texture.",
227 | startingPath);
228 | }
229 |
230 | if (string.IsNullOrEmpty(path))
231 | return;
232 |
233 | // Write the texture to a PNG file.
234 | byte[] bytes = gradientTexture.Texture.EncodeToPNG();
235 | const string assetsFolder = "Assets";
236 | string absolutePath = path.Substring(assetsFolder.Length + 1);
237 | absolutePath = Application.dataPath + Path.AltDirectorySeparatorChar + absolutePath;
238 | File.WriteAllBytes(absolutePath, bytes);
239 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport);
240 |
241 | // Let's make sure we assign the right settings.
242 | TextureImporter textureImporter = (TextureImporter)AssetImporter.GetAtPath(path);
243 | textureImporter.wrapMode = gradientTexture.WrapMode;
244 | textureImporter.filterMode = gradientTexture.FilterMode;
245 | textureImporter.mipmapEnabled = false;
246 | textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
247 | AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport);
248 |
249 | // Set the GradientTexture to Texture mode and assign the created texture.
250 | Texture2D textureAsset = AssetDatabase.LoadAssetAtPath(path);
251 | owner.serializedObject.Update();
252 | owner.FindPropertyRelative("mode").enumValueIndex = (int)GradientTexture.Modes.Texture;
253 | owner.FindPropertyRelative("texture").objectReferenceValue = textureAsset;
254 | owner.serializedObject.ApplyModifiedProperties();
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/Editor/Gradients/GradientTexturePropertyDrawer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 397204bc001711544aade2ec562c5dbe
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/RoyTheunissen.CurvesAndGradientsToTexture.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RoyTheunissen.CurvesAndGradientsToTexture.Editor",
3 | "rootNamespace": "RoyTheunissen.CurvesAndGradientsToTexture",
4 | "references": [
5 | "GUID:0043130421c927846854eb1182e09ddb"
6 | ],
7 | "includePlatforms": [
8 | "Editor"
9 | ],
10 | "excludePlatforms": [],
11 | "allowUnsafeCode": false,
12 | "overrideReferences": false,
13 | "precompiledReferences": [],
14 | "autoReferenced": true,
15 | "defineConstraints": [],
16 | "versionDefines": [],
17 | "noEngineReferences": false
18 | }
--------------------------------------------------------------------------------
/Editor/RoyTheunissen.CurvesAndGradientsToTexture.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1e9b27ce124c48e468484bf0a848ea06
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Roy Theunissen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: cccbac261abd49b4b92f20810bdb1392
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://roytheunissen.com)
2 | [](LICENSE.md)
3 | 
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | _Contains utilities for defining a curve or gradient in the inspector and automatically generating a texture for that to pass on to shaders._
26 |
27 | ## About the Project
28 |
29 | Sometimes you're working on a shader and you really need to finesse the curve of certain values, or perhaps some kind of colour gradient. Very quickly you'll grab your image processing application of choice and whip up a nice texture file. This workflow is tedious though. Make a new texture, tweak the values, press CTRL+S, switch back to Unity, wait for it to import, and _then_ you see your results. There is an inherent separation between making the texture and seeing the end result.
30 |
31 | To cut down on iteration time you would ideally just tweak a curve or gradient in the Inspector and see it apply to your shader in real-time.
32 |
33 | But surely it would be very tedious to make such a utility? Yes it was. I hope it can be of some use to you.
34 |
35 | [Article](https://blog.roytheunissen.com/2022/09/02/curves-gradients-to-texture-in-unitys-built-in-render-pipeline/)
36 |
37 | 
38 |
39 | ## Getting Started
40 |
41 | - Add an `AnimationCurveTexture` or `GradientTexture` field to your script
42 | - Send it to a shader via a `MaterialPropertyBlock` or Global Shader Property by accessing its `Texture` property.
43 | - Tweak the values by generating a new texture for this field (Local Mode) or from a re-usable Animation Curve Asset.
44 | - I recommend just working in Local mode, and once you're happy with the values, saving it to an Asset or a Texture.
45 | - See the result of your changes in real-time and approach the optimal values faster.
46 |
47 | 
48 |
49 | ## Compatibility
50 |
51 | It has been tested in 2021.3.2f1, but as far as I know it's not dependent on any 2021-specific features and will work in older versions, too.
52 |
53 | ## Installation
54 |
55 | ### Package Manager
56 |
57 | Go to `Edit > Project Settings > Package Manager`. Under 'Scoped Registries' make sure there is an OpenUPM entry.
58 |
59 | If you don't have one: click the `+` button and enter the following values:
60 |
61 | - Name: `OpenUPM`
62 | - URL: `https://package.openupm.com`
63 |
64 | Then under 'Scope(s)' press the `+` button and add `com.roytheunissen`.
65 |
66 | It should look something like this:
67 | 
68 |
69 |
70 | All of my packages will now be available to you in the Package Manager in the 'My Registries' section and can be installed from there.
71 |
72 |
73 |
74 | ### Git Submodule
75 |
76 | You can check out this repository as a submodule into your project's Assets folder. This is recommended if you intend to contribute to the repository yourself.
77 |
78 | ### OpenUPM
79 | The package is available on the [openupm registry](https://openupm.com). It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli).
80 |
81 | ```
82 | openupm add com.roytheunissen.curvesandgradientstotexture
83 | ```
84 |
85 | ### Manifest
86 | You can also install via git URL by adding this entry in your **manifest.json**
87 |
88 | ```
89 | "com.roytheunissen.curvesandgradientstotexture": "https://github.com/RoyTheunissen/Curves-And-Gradients-To-Texture.git"
90 | ```
91 |
92 | ### Unity Package Manager
93 | ```
94 | from Window->Package Manager, click on the + sign and Add from git: https://github.com/RoyTheunissen/Curves-And-Gradients-To-Texture.git
95 | ```
96 |
97 |
98 | ## Contact
99 | [Roy Theunissen](https://roytheunissen.com)
100 |
101 | [roy.theunissen@live.nl](mailto:roy.theunissen@live.nl)
102 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6d635b713edd4b44a8120c63cb913b37
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 36d493421677ec14ead1a86dbca87ec0
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Curves.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fd476182e9d45cf4fad97efa53175304
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Curves/AnimationCurveAsset.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Curves
4 | {
5 | ///
6 | /// An asset that wraps an animation curve so that the same one can be re-used across multiple scripts.
7 | ///
8 | [CreateAssetMenu(fileName = "AnimationCurveAsset", menuName = "ScriptableObject/AnimationCurveAsset")]
9 | public class AnimationCurveAsset : ScriptableObject
10 | {
11 | [SerializeField]
12 | private AnimationCurve animationCurve = AnimationCurve.Linear(0.0f, 0.0f, 1.0f, 1.0f);
13 | public AnimationCurve AnimationCurve
14 | {
15 | get => animationCurve;
16 | set
17 | {
18 | // Create a new animation curve but with copies of its keys.
19 | animationCurve = new AnimationCurve();
20 | foreach (Keyframe inputKeyframe in value.keys)
21 | {
22 | animationCurve.AddKey(inputKeyframe);
23 | }
24 | }
25 | }
26 |
27 | public float Evaluate(float time)
28 | {
29 | return animationCurve.Evaluate(time);
30 | }
31 |
32 | public Keyframe[] keys
33 | {
34 | get => animationCurve.keys;
35 | set => animationCurve.keys = value;
36 | }
37 |
38 | public Keyframe this[int index] => animationCurve[index];
39 |
40 | public int length => animationCurve.length;
41 |
42 | public WrapMode preWrapMode
43 | {
44 | get => animationCurve.preWrapMode;
45 | set => animationCurve.preWrapMode = value;
46 | }
47 |
48 | public WrapMode postWrapMode
49 | {
50 | get => animationCurve.postWrapMode;
51 | set => animationCurve.postWrapMode = value;
52 | }
53 |
54 | public float Duration => animationCurve.GetDuration();
55 |
56 | public static implicit operator AnimationCurve(AnimationCurveAsset animationCurveAsset)
57 | {
58 | return animationCurveAsset == null ? null : animationCurveAsset.AnimationCurve;
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Runtime/Curves/AnimationCurveAsset.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4c0dc0b09d1dd994ea460393a1d346b1
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Curves/AnimationCurveTexture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using RoyTheunissen.CurvesAndGradientsToTexture.Curves;
3 | using UnityEngine;
4 | using UnityEngine.Serialization;
5 |
6 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Curves
7 | {
8 | ///
9 | /// Caches a texture for an Animation Curve. Helps pass easily tweakable curve data on to a shader.
10 | ///
11 | /// NOTE: Despite the apparent similarity, this code is not shared with GradientTexture because
12 | /// I expect these two utilities to be diverging a lot, so any effort to consolidate the two will likely be undone.
13 | ///
14 | [Serializable]
15 | public class AnimationCurveTexture
16 | {
17 | public enum Modes
18 | {
19 | Asset,
20 | Local,
21 | Texture,
22 | }
23 |
24 | private const float DefaultValueMultiplier = 1.0f;
25 | private const int DefaultResolution = 512;
26 | private const TextureWrapMode DefaultWrapMode = TextureWrapMode.Clamp;
27 | private const FilterMode DefaultFilterMode = FilterMode.Bilinear;
28 |
29 | [FormerlySerializedAs("curveMode")]
30 | [SerializeField] private Modes mode = Modes.Local;
31 | public Modes Mode => mode;
32 |
33 | [SerializeField, HideInInspector] private AnimationCurveAsset animationCurveAsset;
34 |
35 | [SerializeField, HideInInspector]
36 | private AnimationCurve animationCurveLocal = AnimationCurve.Linear(0, 0, 1, 1);
37 |
38 | [SerializeField, HideInInspector] private Texture2D texture;
39 |
40 | [SerializeField] private float valueMultiplier = DefaultValueMultiplier;
41 | [SerializeField] private int resolution = DefaultResolution;
42 | [SerializeField] private TextureWrapMode wrapMode = DefaultWrapMode;
43 | public TextureWrapMode WrapMode => wrapMode;
44 |
45 | [SerializeField] private FilterMode filterMode = DefaultFilterMode;
46 | public FilterMode FilterMode => filterMode;
47 |
48 | [NonSerialized] private Texture2D cachedTexture;
49 |
50 | private AnimationCurve Curve => mode == Modes.Asset ? animationCurveAsset : animationCurveLocal;
51 |
52 | public AnimationCurveTexture()
53 | {
54 | resolution = DefaultResolution;
55 | wrapMode = DefaultWrapMode;
56 | filterMode = DefaultFilterMode;
57 | }
58 |
59 | public AnimationCurveTexture(AnimationCurveAsset animationCurveAsset) : this()
60 | {
61 | mode = Modes.Asset;
62 | this.animationCurveAsset = animationCurveAsset;
63 | }
64 |
65 | public AnimationCurveTexture(AnimationCurve animationCurve) : this()
66 | {
67 | mode = Modes.Local;
68 | animationCurveLocal = animationCurve;
69 | }
70 |
71 | public Texture2D Texture
72 | {
73 | get
74 | {
75 | if (mode == Modes.Texture)
76 | return texture == null ? DefaultTexture : texture;
77 |
78 | if (cachedTexture == null)
79 | GenerateTextureForCurve();
80 |
81 | return cachedTexture;
82 | }
83 | }
84 |
85 | private static Texture2D cachedDefaultTexture;
86 | private static bool didCacheDefaultTexture;
87 | private static Texture2D DefaultTexture
88 | {
89 | get
90 | {
91 | if (!didCacheDefaultTexture)
92 | {
93 | didCacheDefaultTexture = true;
94 | cachedDefaultTexture = new Texture2D(1, 1);
95 | cachedDefaultTexture.SetPixels(new[] { Color.white });
96 | }
97 | return cachedDefaultTexture;
98 | }
99 | }
100 |
101 | public void GenerateTextureForCurve()
102 | {
103 | if (cachedTexture == null)
104 | cachedTexture = new Texture2D(resolution, 1, TextureFormat.RFloat, false);
105 |
106 | if (cachedTexture.width != resolution)
107 | cachedTexture.Reinitialize(resolution, 1);
108 |
109 | cachedTexture.wrapMode = wrapMode;
110 | cachedTexture.filterMode = filterMode;
111 |
112 | Color[] colors = new Color[resolution];
113 | bool hasCurve = Curve != null;
114 | float duration = hasCurve ? Curve.GetDuration() : 0;
115 | for(int i = 0; i < resolution; ++i)
116 | {
117 | if (!hasCurve)
118 | {
119 | colors[i] = new Color(0, 0, 0, 1);
120 | continue;
121 | }
122 |
123 | float t = (float)i / resolution;
124 |
125 | float x = t * duration;
126 |
127 | float value = Curve == null ? 0 : Curve.Evaluate(x) * valueMultiplier;
128 |
129 | colors[i] = new Color(value, value, value, 1);
130 | }
131 |
132 | cachedTexture.SetPixels(colors);
133 | cachedTexture.Apply(false);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Runtime/Curves/AnimationCurveTexture.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3a4c4cb9a0578f941944821718185662
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Extensions.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2b02a38d3b45f1c4c911918890553f2d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Extensions/AnimationCurveExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace UnityEngine
2 | {
3 | public static class AnimationCurveExtensions
4 | {
5 | public static void Copy(this AnimationCurve animationCurve, AnimationCurve from)
6 | {
7 | Keyframe[] keyframes = new Keyframe[from.length];
8 |
9 | for (int i = 0; i < keyframes.Length; i++)
10 | {
11 | Keyframe k = new Keyframe
12 | {
13 | time = from[i].time,
14 | value = from[i].value,
15 | inTangent = from[i].inTangent,
16 | outTangent = from[i].outTangent,
17 | };
18 |
19 | keyframes[i] = k;
20 | }
21 |
22 | animationCurve.keys = keyframes;
23 | }
24 |
25 | public static void Lerp(
26 | this AnimationCurve animationCurve, AnimationCurve from, AnimationCurve to, float fraction)
27 | {
28 | if (from.length != to.length)
29 | return;
30 |
31 | Keyframe[] keyframes = new Keyframe[from.length];
32 |
33 | for (int i = 0; i < keyframes.Length; i++)
34 | {
35 | Keyframe k = new Keyframe
36 | {
37 | time = Mathf.Lerp(from[i].time, to[i].time, fraction),
38 | value = Mathf.Lerp(from[i].value, to[i].value, fraction),
39 | inTangent = Mathf.Lerp(from[i].inTangent, to[i].inTangent, fraction),
40 | outTangent = Mathf.Lerp(from[i].outTangent, to[i].outTangent, fraction),
41 | };
42 |
43 | keyframes[i] = k;
44 | }
45 |
46 | animationCurve.keys = keyframes;
47 | }
48 |
49 | public static float GetDuration(this AnimationCurve animationCurve)
50 | {
51 | if (animationCurve.length < 2)
52 | return 0.0f;
53 |
54 | return animationCurve[animationCurve.length - 1].time;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Runtime/Extensions/AnimationCurveExtensions.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e9260ed14f7be7148ab24330728eba83
3 | timeCreated: 1469373664
4 | licenseType: Pro
5 | MonoImporter:
6 | serializedVersion: 2
7 | defaultReferences: []
8 | executionOrder: 0
9 | icon: {instanceID: 0}
10 | userData:
11 | assetBundleName:
12 | assetBundleVariant:
13 |
--------------------------------------------------------------------------------
/Runtime/Gradients.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a9c0a85d2404a614399bf4db8b6c1025
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Gradients/GradientAsset.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 |
4 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Gradients
5 | {
6 | ///
7 | /// Re-usable gradient.
8 | ///
9 | [CreateAssetMenu(fileName = "GradientAsset", menuName = "ScriptableObject/GradientAsset")]
10 | public class GradientAsset : ScriptableObject
11 | {
12 | [SerializeField, GradientUsage(true)] private Gradient gradient = new Gradient
13 | {
14 | alphaKeys = new[] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) },
15 | colorKeys = new[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) },
16 | };
17 | public Gradient Gradient
18 | {
19 | get => gradient;
20 | set
21 | {
22 | gradient = new Gradient();
23 | GradientColorKey[] colorKeys = new GradientColorKey[value.colorKeys.Length];
24 | Array.Copy(value.colorKeys, colorKeys, colorKeys.Length);
25 |
26 | GradientAlphaKey[] alphaKeys = new GradientAlphaKey[value.alphaKeys.Length];
27 | Array.Copy(value.alphaKeys, alphaKeys, alphaKeys.Length);
28 |
29 | gradient.SetKeys(colorKeys, alphaKeys);
30 | }
31 | }
32 |
33 | public Color Evaluate(float time)
34 | {
35 | return gradient.Evaluate(time);
36 | }
37 |
38 | public void SetKeys(GradientColorKey[] colorKeys, GradientAlphaKey[] alphaKeys)
39 | {
40 | gradient.SetKeys(colorKeys, alphaKeys);
41 | }
42 |
43 | public bool Equals(Gradient other)
44 | {
45 | return gradient.Equals(other);
46 | }
47 |
48 | public GradientColorKey[] colorKeys
49 | {
50 | get => gradient.colorKeys;
51 | set => gradient.colorKeys = value;
52 | }
53 |
54 | public GradientAlphaKey[] alphaKeys
55 | {
56 | get => gradient.alphaKeys;
57 | set => gradient.alphaKeys = value;
58 | }
59 |
60 | public GradientMode mode
61 | {
62 | get => gradient.mode;
63 | set => gradient.mode = value;
64 | }
65 |
66 | public static implicit operator Gradient(GradientAsset gradientAsset)
67 | {
68 | return gradientAsset == null ? null : gradientAsset.gradient;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Runtime/Gradients/GradientAsset.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 54ee6ffe951d5b843ac5119edc8d6495
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {fileID: 2800000, guid: 9aedd38d716f1d14d9bb4ef3e1ea858b, type: 3}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Gradients/GradientTexture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 |
4 | namespace RoyTheunissen.CurvesAndGradientsToTexture.Gradients
5 | {
6 | ///
7 | /// Caches a texture for a Gradient. Helps pass easily tweakable gradient data on to a shader.
8 | ///
9 | /// NOTE: Despite the apparent similarity, this code is not shared with AnimationCurveTexture because
10 | /// I expect these two utilities to be diverging a lot, so any effort to consolidate the two will likely be undone.
11 | ///
12 | [Serializable]
13 | public class GradientTexture
14 | {
15 | public enum Modes
16 | {
17 | Asset,
18 | Local,
19 | Texture,
20 | }
21 |
22 | private const int DefaultResolution = 512;
23 | private const TextureWrapMode DefaultWrapMode = TextureWrapMode.Clamp;
24 | private const FilterMode DefaultFilterMode = FilterMode.Bilinear;
25 |
26 | [SerializeField] private Modes mode = Modes.Local;
27 | public Modes Mode => mode;
28 |
29 | [SerializeField, HideInInspector] private GradientAsset gradientAsset;
30 |
31 | [SerializeField, HideInInspector] private Texture2D texture;
32 |
33 | [SerializeField, HideInInspector, GradientUsage(true)]
34 | private Gradient gradientLocal = new Gradient
35 | {
36 | alphaKeys = new[] { new GradientAlphaKey(1, 0), new GradientAlphaKey(1, 1) },
37 | colorKeys = new[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) },
38 | };
39 |
40 | public Gradient GradientLocal => gradientLocal;
41 |
42 | [SerializeField] private int resolution = DefaultResolution;
43 | [SerializeField] private TextureWrapMode wrapMode = DefaultWrapMode;
44 | public TextureWrapMode WrapMode => wrapMode;
45 |
46 | [SerializeField] private FilterMode filterMode = DefaultFilterMode;
47 | public FilterMode FilterMode => filterMode;
48 |
49 | [NonSerialized] private Texture2D cachedTexture;
50 |
51 | private Gradient Gradient => mode == Modes.Asset ? gradientAsset : gradientLocal;
52 |
53 | public GradientTexture()
54 | {
55 | resolution = DefaultResolution;
56 | wrapMode = DefaultWrapMode;
57 | filterMode = DefaultFilterMode;
58 | }
59 |
60 | public GradientTexture(GradientAsset gradientAsset) : this()
61 | {
62 | mode = Modes.Asset;
63 | this.gradientAsset = gradientAsset;
64 | }
65 |
66 | public GradientTexture(Gradient gradient) : this()
67 | {
68 | mode = Modes.Local;
69 | gradientLocal = gradient;
70 | }
71 |
72 | public Texture2D Texture
73 | {
74 | get
75 | {
76 | if (mode == Modes.Texture)
77 | return texture == null ? DefaultTexture : texture;
78 |
79 | if (cachedTexture == null)
80 | GenerateTexture();
81 |
82 | return cachedTexture;
83 | }
84 | }
85 |
86 | private static Texture2D cachedDefaultTexture;
87 | private static bool didCacheDefaultTexture;
88 | private static Texture2D DefaultTexture
89 | {
90 | get
91 | {
92 | if (!didCacheDefaultTexture)
93 | {
94 | didCacheDefaultTexture = true;
95 | cachedDefaultTexture = new Texture2D(1, 1);
96 | cachedDefaultTexture.SetPixels(new[] { Color.white });
97 | }
98 | return cachedDefaultTexture;
99 | }
100 | }
101 |
102 | public void GenerateTexture()
103 | {
104 | if (cachedTexture == null)
105 | cachedTexture = new Texture2D(resolution, 1, TextureFormat.RGBA32, false);
106 |
107 | if (cachedTexture.width != resolution)
108 | cachedTexture.Reinitialize(resolution, 1);
109 |
110 | cachedTexture.wrapMode = wrapMode;
111 | cachedTexture.filterMode = filterMode;
112 |
113 | Color[] colors = new Color[resolution];
114 | bool hasGradient = Gradient != null;
115 | for(int i = 0; i < resolution; ++i)
116 | {
117 | if (!hasGradient)
118 | {
119 | colors[i] = new Color(0, 0, 0, 1);
120 | continue;
121 | }
122 |
123 | float t = (float)i / resolution;
124 |
125 | Color color = Gradient == null ? Color.black : Gradient.Evaluate(t);
126 |
127 | colors[i] = color;
128 | }
129 |
130 | cachedTexture.SetPixels(colors);
131 | cachedTexture.Apply(false);
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Runtime/Gradients/GradientTexture.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: acda4fd7a98fef64ca297a4a2882d747
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/RoyTheunissen.CurvesAndGradientsToTexture.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RoyTheunissen.CurvesAndGradientsToTexture",
3 | "rootNamespace": "RoyTheunissen.CurvesAndGradientsToTexture",
4 | "references": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": [],
12 | "versionDefines": [],
13 | "noEngineReferences": false
14 | }
--------------------------------------------------------------------------------
/Runtime/RoyTheunissen.CurvesAndGradientsToTexture.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0043130421c927846854eb1182e09ddb
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.roytheunissen.curvesandgradientstotexture",
3 | "displayName": "Curves & Gradients To Texture",
4 | "version": "0.0.4",
5 | "unity": "2021.3",
6 | "description": "Contains utilities for defining a curve or gradient in the inspector and automatically generating a texture for that to pass on to shaders.",
7 | "keywords": [
8 | "curve",
9 | "curves",
10 | "animationcurve",
11 | "animationcurves",
12 | "gradient",
13 | "gradients",
14 | "texture",
15 | "shaders",
16 | "effects",
17 | "brp"
18 | ],
19 | "author": {
20 | "name": "Roy Theunissen",
21 | "url": "https://www.roytheunissen.com"
22 | },
23 | "category": "editor extensions",
24 | "homepage": "https://github.com/RoyTheunissen/Curves-And-Gradients-To-Texture",
25 | "repository": {
26 | "type": "git",
27 | "url": "https://github.com/RoyTheunissen/Curves-And-Gradients-To-Texture"
28 | },
29 | "bugs": "https://github.com/RoyTheunissen/Curves-And-Gradients-To-Texture/issues"
30 | }
31 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d19754296101a2140909fd33db5270c3
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------