summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-02-17 19:24:26 -0800
committeryum <yum.food.vr@gmail.com>2026-02-17 19:24:31 -0800
commitec6c0dc6ed672129e5a773b2a3158443951ed101 (patch)
tree5d772633c25b123338b70e55de39b7b4ce46149f
parent00553b3f305d0e2217659993f237ff3da604ef85 (diff)
Fold: support animation window
-rwxr-xr-xScripts/Fold/Editor/FoldEditorWindow.cs266
-rwxr-xr-xScripts/Fold/Editor/FoldPipelineBuilder.cs2
2 files changed, 261 insertions, 7 deletions
diff --git a/Scripts/Fold/Editor/FoldEditorWindow.cs b/Scripts/Fold/Editor/FoldEditorWindow.cs
index e8faf1f..31d47fd 100755
--- a/Scripts/Fold/Editor/FoldEditorWindow.cs
+++ b/Scripts/Fold/Editor/FoldEditorWindow.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using UnityEditor;
using UnityEngine;
@@ -10,6 +11,11 @@ public class FoldEditorWindow : EditorWindow
[SerializeReference] List<DeformOperation> operations = new();
[SerializeField] List<int> expandedOps = new();
[SerializeField] bool liveUpdate = true;
+ [SerializeField] GameObject targetObject;
+ int frameStep;
+ bool wasInAnimationMode;
+
+ const string FrameStepPrefKey = "Fold_FrameStep";
[MenuItem("Tools/yum_food/Fold")]
static void ShowWindow()
@@ -18,8 +24,21 @@ public class FoldEditorWindow : EditorWindow
window.minSize = new Vector2(400, 300);
}
+ void OnEnable() => frameStep = EditorPrefs.GetInt(FrameStepPrefKey, 1);
+
+ void OnDisable()
+ {
+ EditorPrefs.SetInt(FrameStepPrefKey, frameStep);
+ ClearPropertyBlock();
+ }
+
void OnGUI()
{
+ bool inAnimMode = AnimationMode.InAnimationMode();
+ if (wasInAnimationMode && !inAnimMode)
+ ClearPropertyBlock();
+ wasInAnimationMode = inAnimMode;
+
EditorGUILayout.Space(5);
DrawHeader();
@@ -61,6 +80,11 @@ public class FoldEditorWindow : EditorWindow
}
EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField("Target Object", EditorStyles.boldLabel, GUILayout.Width(100));
+ targetObject = EditorGUILayout.ObjectField(targetObject, typeof(GameObject), true) as GameObject;
+ EditorGUILayout.EndHorizontal();
}
void DrawToolbar()
@@ -182,19 +206,36 @@ public class FoldEditorWindow : EditorWindow
{
EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("Apply to Material", GUILayout.Height(30)))
- ApplyToMaterial();
+ if (GUILayout.Button("Create Keyframe", GUILayout.Height(30)))
+ ApplyToMaterial(recordKeyframes: true);
- if (GUILayout.Button("Clear All", GUILayout.Height(30), GUILayout.Width(100)))
+ if (GUILayout.Button("Delete Keyframe", GUILayout.Height(30), GUILayout.Width(120)))
+ DeleteKeyframeAtCurrentTime();
+
+ if (GUILayout.Button("Clear All", GUILayout.Height(30), GUILayout.Width(80)))
{
- operations.Clear();
- expandedOps.Clear();
+ if (EditorUtility.DisplayDialog("Clear All Operations",
+ "Remove all operations from the pipeline?", "Clear", "Cancel"))
+ {
+ operations.Clear();
+ expandedOps.Clear();
+ }
}
EditorGUILayout.EndHorizontal();
- EditorGUILayout.Space(5);
+ EditorGUILayout.Space(3);
+
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("<", GUILayout.Width(30), GUILayout.Height(20)))
+ AdvancePlayhead(-frameStep);
+ frameStep = EditorGUILayout.IntField(frameStep, GUILayout.Width(40));
+ if (frameStep < 1) frameStep = 1;
+ if (GUILayout.Button(">", GUILayout.Width(30), GUILayout.Height(20)))
+ AdvancePlayhead(frameStep);
+ GUILayout.FlexibleSpace();
EditorGUILayout.LabelField($"Operations: {operations.Count}/16", EditorStyles.miniLabel);
+ EditorGUILayout.EndHorizontal();
}
void ShowAddOperationMenu()
@@ -231,7 +272,7 @@ public class FoldEditorWindow : EditorWindow
expandedOps.Add(operations.Count - 1);
}
- void ApplyToMaterial()
+ void ApplyToMaterial(bool recordKeyframes = false)
{
var builder = FoldPipelineBuilder.Create().For(targetMaterial);
@@ -239,8 +280,219 @@ public class FoldEditorWindow : EditorWindow
op.ApplyTo(builder);
builder.Apply();
+
+ if (AnimationMode.InAnimationMode())
+ ApplyPropertyBlock(builder);
+
+ if (recordKeyframes)
+ RecordAnimationKeyframes(builder);
+ }
+
+ void ApplyPropertyBlock(FoldPipelineBuilder builder)
+ {
+ if (targetObject == null) return;
+ var renderer = targetObject.GetComponent<Renderer>();
+ if (renderer == null) return;
+
+ var mpb = new MaterialPropertyBlock();
+ renderer.GetPropertyBlock(mpb);
+
+ mpb.SetFloat("_Vertex_Deformation_Enabled", 1f);
+
+ for (int i = 0; i < 16; i++)
+ {
+ var slot = builder.GetSlot(i);
+ var prefix = $"_Vertex_Deformation_Slot_{i}_";
+
+ mpb.SetFloat(prefix + "Enabled", slot != null ? 1f : 0f);
+ mpb.SetInteger(prefix + "Opcode", slot?.opcode ?? 0);
+ mpb.SetFloat(prefix + "Float_0", slot?.float0 ?? 0f);
+ mpb.SetFloat(prefix + "Float_1", slot?.float1 ?? 0f);
+ mpb.SetFloat(prefix + "Float_2", slot?.float2 ?? 0f);
+ mpb.SetFloat(prefix + "Float_3", slot?.float3 ?? 0f);
+ mpb.SetVector(prefix + "Vector_0", slot?.vec0 ?? Vector4.zero);
+ mpb.SetVector(prefix + "Vector_1", slot?.vec1 ?? Vector4.zero);
+ mpb.SetVector(prefix + "Vector_2", slot?.vec2 ?? Vector4.zero);
+ mpb.SetVector(prefix + "Vector_3", slot?.vec3 ?? Vector4.zero);
+ }
+
+ renderer.SetPropertyBlock(mpb);
+ }
+
+ void ClearPropertyBlock()
+ {
+ if (targetObject == null) return;
+ var renderer = targetObject.GetComponent<Renderer>();
+ if (renderer != null)
+ renderer.SetPropertyBlock(null);
+ }
+
+ #region Animation Recording
+
+ void RecordAnimationKeyframes(FoldPipelineBuilder builder)
+ {
+ if (targetObject == null || !AnimationMode.InAnimationMode())
+ return;
+
+ var renderer = targetObject.GetComponent<Renderer>();
+ if (renderer == null) return;
+
+ if (!TryGetAnimationWindowState(out var clip, out float time))
+ return;
+
+ var animator = targetObject.GetComponentInParent<Animator>();
+ string path = animator != null
+ ? AnimationUtility.CalculateTransformPath(renderer.transform, animator.transform)
+ : "";
+
+ SetFloatKey(clip, path, "material._Vertex_Deformation_Enabled", 1f, time);
+
+ for (int i = 0; i < 16; i++)
+ {
+ var slot = builder.GetSlot(i);
+ var prefix = $"material._Vertex_Deformation_Slot_{i}_";
+
+ SetFloatKey(clip, path, prefix + "Enabled", slot != null ? 1f : 0f, time);
+ SetDiscreteKey(clip, path, prefix + "Opcode", slot?.opcode ?? 0, time);
+ SetFloatKey(clip, path, prefix + "Float_0", slot?.float0 ?? 0f, time);
+ SetFloatKey(clip, path, prefix + "Float_1", slot?.float1 ?? 0f, time);
+ SetFloatKey(clip, path, prefix + "Float_2", slot?.float2 ?? 0f, time);
+ SetFloatKey(clip, path, prefix + "Float_3", slot?.float3 ?? 0f, time);
+ SetVectorKey(clip, path, prefix + "Vector_0", slot?.vec0 ?? Vector4.zero, time);
+ SetVectorKey(clip, path, prefix + "Vector_1", slot?.vec1 ?? Vector4.zero, time);
+ SetVectorKey(clip, path, prefix + "Vector_2", slot?.vec2 ?? Vector4.zero, time);
+ SetVectorKey(clip, path, prefix + "Vector_3", slot?.vec3 ?? Vector4.zero, time);
+ }
+ }
+
+ void DeleteKeyframeAtCurrentTime()
+ {
+ if (targetObject == null || !AnimationMode.InAnimationMode())
+ {
+ Debug.LogWarning("Fold: No target object set or Animation window is not recording.");
+ return;
+ }
+
+ var renderer = targetObject.GetComponent<Renderer>();
+ if (renderer == null) return;
+
+ if (!TryGetAnimationWindowState(out var clip, out float time))
+ return;
+
+ var animator = targetObject.GetComponentInParent<Animator>();
+ string path = animator != null
+ ? AnimationUtility.CalculateTransformPath(renderer.transform, animator.transform)
+ : "";
+
+ Undo.RecordObject(clip, "Delete Fold Keyframe");
+
+ foreach (var binding in AnimationUtility.GetCurveBindings(clip))
+ {
+ if (binding.type != typeof(Renderer)) continue;
+ if (binding.path != path) continue;
+ if (!binding.propertyName.StartsWith("material._Vertex_Deformation_")) continue;
+
+ var curve = AnimationUtility.GetEditorCurve(clip, binding);
+ if (curve == null) continue;
+
+ bool removed = false;
+ for (int i = curve.length - 1; i >= 0; i--)
+ {
+ if (Mathf.Approximately(curve.keys[i].time, time))
+ {
+ curve.RemoveKey(i);
+ removed = true;
+ }
+ }
+
+ if (removed)
+ {
+ if (curve.length == 0)
+ AnimationUtility.SetEditorCurve(clip, binding, null);
+ else
+ AnimationUtility.SetEditorCurve(clip, binding, curve);
+ }
+ }
+ }
+
+ static void SetFloatKey(AnimationClip clip, string path, string prop, float value, float time)
+ {
+ var binding = EditorCurveBinding.FloatCurve(path, typeof(Renderer), prop);
+ var curve = AnimationUtility.GetEditorCurve(clip, binding) ?? new AnimationCurve();
+ AddOrReplaceKey(curve, time, value);
+ AnimationUtility.SetEditorCurve(clip, binding, curve);
+ }
+
+ static void SetDiscreteKey(AnimationClip clip, string path, string prop, int value, float time)
+ {
+ var binding = EditorCurveBinding.DiscreteCurve(path, typeof(Renderer), prop);
+ var curve = AnimationUtility.GetEditorCurve(clip, binding) ?? new AnimationCurve();
+ AddOrReplaceKey(curve, time, value);
+ AnimationUtility.SetEditorCurve(clip, binding, curve);
+ }
+
+ static void SetVectorKey(AnimationClip clip, string path, string prop, Vector4 v, float time)
+ {
+ SetFloatKey(clip, path, prop + ".x", v.x, time);
+ SetFloatKey(clip, path, prop + ".y", v.y, time);
+ SetFloatKey(clip, path, prop + ".z", v.z, time);
+ SetFloatKey(clip, path, prop + ".w", v.w, time);
+ }
+
+ static void AddOrReplaceKey(AnimationCurve curve, float time, float value)
+ {
+ for (int i = curve.length - 1; i >= 0; i--)
+ if (Mathf.Approximately(curve.keys[i].time, time))
+ curve.RemoveKey(i);
+ curve.AddKey(new Keyframe(time, value));
}
+ void AdvancePlayhead(int frames)
+ {
+ if (!TryGetAnimationWindowState(out var clip, out float time))
+ return;
+ float frameDuration = 1f / clip.frameRate;
+ SetAnimationWindowTime(time + frames * frameDuration);
+ }
+
+ static void SetAnimationWindowTime(float time)
+ {
+ var windowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.AnimationWindow");
+ if (windowType == null) return;
+
+ var windows = Resources.FindObjectsOfTypeAll(windowType);
+ if (windows.Length == 0) return;
+
+ var timeProp = windowType.GetProperty("time",
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
+ timeProp?.SetValue(windows[0], time);
+ }
+
+ static bool TryGetAnimationWindowState(out AnimationClip clip, out float time)
+ {
+ clip = null;
+ time = 0f;
+
+ var windowType = typeof(EditorWindow).Assembly.GetType("UnityEditor.AnimationWindow");
+ if (windowType == null) return false;
+
+ var windows = Resources.FindObjectsOfTypeAll(windowType);
+ if (windows.Length == 0) return false;
+
+ var window = windows[0];
+ const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+
+ var clipProp = windowType.GetProperty("animationClip", flags);
+ var timeProp = windowType.GetProperty("time", flags);
+ if (clipProp == null || timeProp == null) return false;
+
+ clip = clipProp.GetValue(window) as AnimationClip;
+ time = (float)timeProp.GetValue(window);
+ return clip != null;
+ }
+
+ #endregion
+
void LoadFromMaterial()
{
operations.Clear();
diff --git a/Scripts/Fold/Editor/FoldPipelineBuilder.cs b/Scripts/Fold/Editor/FoldPipelineBuilder.cs
index 731ceeb..b610aca 100755
--- a/Scripts/Fold/Editor/FoldPipelineBuilder.cs
+++ b/Scripts/Fold/Editor/FoldPipelineBuilder.cs
@@ -301,4 +301,6 @@ public class FoldPipelineBuilder
}
public int Count => slots.Count;
+
+ public FoldSlot GetSlot(int index) => index < slots.Count ? slots[index] : null;
}