diff options
| author | yum <yum.food.vr@gmail.com> | 2026-01-12 19:31:54 -0800 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2026-01-12 19:31:54 -0800 |
| commit | 6d86c9663bab3ec1ef95ba455dfa7281415b7f44 (patch) | |
| tree | cda255c2dfd0ef2c96334fb7ecf2182d4950086e /Scripts | |
| parent | cbc299f489d6bdc38cce30d74040c54197efe125 (diff) | |
Fold: move from visual programming to fluent builder
Diffstat (limited to 'Scripts')
| -rw-r--r-- | Scripts/Fold/Editor/FoldGraph.cs | 146 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/FoldGraphView.cs | 623 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/FoldNodeSerialized.cs | 19 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/FoldWindow.cs | 85 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/README.md | 131 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/VertexDeformationBuilder.cs | 339 | ||||
| -rw-r--r-- | Scripts/Fold/Editor/VertexDeformationExamples.cs | 114 |
7 files changed, 584 insertions, 873 deletions
diff --git a/Scripts/Fold/Editor/FoldGraph.cs b/Scripts/Fold/Editor/FoldGraph.cs deleted file mode 100644 index c0719d9..0000000 --- a/Scripts/Fold/Editor/FoldGraph.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -[Serializable] -public class FoldEdge -{ - public string outputNodeGuid, outputPortName, inputNodeGuid, inputPortName; -} - -[Serializable] -public readonly struct FoldOperationDefinition -{ - public readonly string title; - public readonly int opcode; - public readonly (string name, Type type)[] inputs; - - public FoldOperationDefinition(string title, int opcode, params (string name, Type type)[] inputs) - { - this.title = title; - this.opcode = opcode; - this.inputs = inputs; - } - - public FoldNodeSerialized Serialize(FoldGraphView graphView, string guid) - { - var serialized = new FoldNodeSerialized { opcode = opcode }; - int floatIndex = 0, vectorIndex = 0; - - foreach (var (name, type) in inputs) - { - if (type == typeof(float)) - SetFloat(ref serialized, floatIndex++, graphView.GetInputValue(guid, name, 0f)); - else if (type == typeof(Vector4)) - SetVector(ref serialized, vectorIndex++, graphView.GetInputValue(guid, name, Vector4.zero)); - } - - return serialized; - } - - static void SetFloat(ref FoldNodeSerialized node, int index, float value) - { - switch (index) - { - case 0: node.float0 = value; break; - case 1: node.float1 = value; break; - case 2: node.float2 = value; break; - case 3: node.float3 = value; break; - } - } - - static void SetVector(ref FoldNodeSerialized node, int index, Vector4 value) - { - switch (index) - { - case 0: node.vec0 = value; break; - case 1: node.vec1 = value; break; - case 2: node.vec2 = value; break; - case 3: node.vec3 = value; break; - } - } -} - -[Serializable] -public abstract class FoldNodeData -{ - public string guid = Guid.NewGuid().ToString(); - public Vector2 position; - public virtual string Title => GetType().Name; -} - -[Serializable] -public abstract class FoldOperationData : FoldNodeData -{ - protected abstract FoldOperationDefinition Definition { get; } - public override string Title => Definition.title; - public virtual IEnumerable<(string name, Type type)> GetInputPorts() => Definition.inputs; - public virtual FoldNodeSerialized Serialize(FoldGraphView graphView) => Definition.Serialize(graphView, guid); -} - -[Serializable] public class FloatValueNodeData : FoldNodeData { public float value; public override string Title => "Float"; } -[Serializable] public class VectorValueNodeData : FoldNodeData { public Vector4 value; public override string Title => "Vector"; } -[Serializable] public class GameObjectNodeData : FoldNodeData { public GameObject output; public override string Title => "GameObject"; } -[Serializable] public class KeyframeNodeData : FoldNodeData { public override string Title => "Keyframe"; } - -[Serializable] -public class AxisAlignNodeData : FoldOperationData -{ - static readonly FoldOperationDefinition DefinitionInfo = new( - "Axis Align", - 4, - ("po", typeof(Vector4)), - ("pp", typeof(Vector4)), - ("r", typeof(Vector4)), - ("t", typeof(float))); - - protected override FoldOperationDefinition Definition => DefinitionInfo; -} - -[Serializable] -public class PlaneToTubeNodeData : FoldOperationData -{ - static readonly FoldOperationDefinition DefinitionInfo = new( - "Plane to Tube", - 2, - ("p", typeof(Vector4)), - ("r", typeof(Vector4)), - ("s", typeof(Vector4)), - ("t", typeof(float))); - - protected override FoldOperationDefinition Definition => DefinitionInfo; -} - -[Serializable] -public class PointAlignNodeData : FoldOperationData -{ - static readonly FoldOperationDefinition DefinitionInfo = new( - "Point Align", - 3, - ("po", typeof(Vector4)), - ("pp", typeof(Vector4)), - ("r", typeof(Vector4)), - ("t", typeof(float))); - - protected override FoldOperationDefinition Definition => DefinitionInfo; -} - -[Serializable] -public class TubeToPlaneNodeData : FoldOperationData -{ - static readonly FoldOperationDefinition DefinitionInfo = new( - "Tube to Plane", - 1, - ("p", typeof(Vector4)), - ("r", typeof(Vector4)), - ("s", typeof(Vector4)), - ("t", typeof(float))); - - protected override FoldOperationDefinition Definition => DefinitionInfo; -} - -public class FoldGraph : ScriptableObject -{ - [SerializeReference] public List<FoldNodeData> nodes = new(); - public List<FoldEdge> edges = new(); -} diff --git a/Scripts/Fold/Editor/FoldGraphView.cs b/Scripts/Fold/Editor/FoldGraphView.cs deleted file mode 100644 index e01bf4f..0000000 --- a/Scripts/Fold/Editor/FoldGraphView.cs +++ /dev/null @@ -1,623 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEditor; -using UnityEditor.Experimental.GraphView; -using UnityEditor.UIElements; -using UnityEngine; -using UnityEngine.UIElements; -using SearchWindow = UnityEditor.Experimental.GraphView.SearchWindow; - -public interface IFoldNode -{ - FoldNodeSerialized Serialize(); - IFoldNode GetPreviousFold(); -} - -public abstract class FoldNodeView : Node -{ - public FoldNodeData Data { get; private set; } - protected FoldGraphView graphView; - readonly Dictionary<string, Port> ports = new(); - - public virtual void Initialize(FoldGraphView view, FoldNodeData data) - { - graphView = view; - Data = data; - title = data.Title; - viewDataKey = data.guid; - SetPosition(new Rect(data.position, Vector2.zero)); - - RegisterCallback<MouseDownEvent>(evt => - { - if (!evt.shiftKey) return; - if (!graphView.selection.Contains(this)) - graphView.AddToSelection(this); - evt.StopImmediatePropagation(); - evt.PreventDefault(); - }, TrickleDown.TrickleDown); - } - - public override void SetPosition(Rect newPos) - { - base.SetPosition(newPos); - if (Data != null) - { - graphView.RecordUndo("Move Node"); - Data.position = newPos.position; - graphView.MarkDirty(); - } - } - - protected Port AddPort(Direction direction, Port.Capacity capacity, string name, Type type) - { - var port = InstantiatePort(Orientation.Horizontal, direction, capacity, type); - port.portName = name; - ports[name] = port; - graphView.BindEdgeConnector(port); - (direction == Direction.Input ? inputContainer : outputContainer).Add(port); - return port; - } - - public Port GetPort(string name) => ports.TryGetValue(name, out var p) ? p : null; -} - -public class ValueNodeView : FoldNodeView -{ - object GetValue() => Data switch - { - FloatValueNodeData f => f.value, - VectorValueNodeData v => v.value, - GameObjectNodeData g => g.output, - _ => null - }; - - public T GetTypedValue<T>() => GetValue() is T v ? v : default; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - var portType = data switch - { - FloatValueNodeData => typeof(float), - VectorValueNodeData => typeof(Vector4), - GameObjectNodeData => typeof(GameObject), - _ => typeof(object) - }; - AddPort(Direction.Output, Port.Capacity.Multi, "Out", portType); - - VisualElement field = data switch - { - FloatValueNodeData f => CreateFloatField(f), - VectorValueNodeData v => CreateVectorField(v), - GameObjectNodeData g => CreateObjectField(g), - _ => null - }; - if (field != null) extensionContainer.Add(field); - RefreshExpandedState(); - } - - FloatField CreateFloatField(FloatValueNodeData d) - { - var f = new FloatField("Value") { value = d.value }; - f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit Float"); d.value = e.newValue; graphView.MarkDirty(); }); - return f; - } - - Vector4Field CreateVectorField(VectorValueNodeData d) - { - var f = new Vector4Field("Value") { value = d.value }; - f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit Vector"); d.value = e.newValue; graphView.MarkDirty(); }); - return f; - } - - ObjectField CreateObjectField(GameObjectNodeData d) - { - var f = new ObjectField("Object") { allowSceneObjects = true, objectType = typeof(GameObject), value = d.output }; - f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit GameObject"); d.output = e.newValue as GameObject; graphView.MarkDirty(); }); - return f; - } -} - -public class OperationNodeView : FoldNodeView, IFoldNode -{ - Port foldInputPort; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - foldInputPort = AddPort(Direction.Input, Port.Capacity.Single, "Input", typeof(IFoldNode)); - AddPort(Direction.Output, Port.Capacity.Single, "Out", typeof(IFoldNode)); - - foreach (var (name, type) in ((FoldOperationData)data).GetInputPorts()) - AddPort(Direction.Input, Port.Capacity.Single, name, type); - } - - public IFoldNode GetPreviousFold() => foldInputPort.connections.FirstOrDefault()?.output?.node as IFoldNode; - public FoldNodeSerialized Serialize() => ((FoldOperationData)Data).Serialize(graphView); -} - -public class KeyframeNodeView : FoldNodeView -{ - Port gameObjectPort, foldPort; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - gameObjectPort = AddPort(Direction.Input, Port.Capacity.Single, "GameObject", typeof(GameObject)); - foldPort = AddPort(Direction.Input, Port.Capacity.Single, "Fold Data", typeof(IFoldNode)); - extensionContainer.Add(new Button(OnGenerateClick) { text = "Generate Keyframe" }); - RefreshExpandedState(); - } - - void OnGenerateClick() - { - var goEdge = gameObjectPort.connections.FirstOrDefault(); - var go = (goEdge?.output?.node as ValueNodeView)?.GetTypedValue<GameObject>(); - if (go == null) { graphView.ShowNotification("GameObject input missing or empty."); return; } - - var renderer = go.GetComponent<MeshRenderer>(); - if (renderer == null) { graphView.ShowNotification("GameObject must have a MeshRenderer component."); return; } - - var foldNode = foldPort.connections.FirstOrDefault()?.output?.node as IFoldNode; - if (foldNode == null) { graphView.ShowNotification("Fold input not connected."); return; } - - var stack = new Stack<IFoldNode>(); - for (var cur = foldNode; cur != null; cur = cur.GetPreviousFold()) - stack.Push(cur); - - Undo.RecordObject(renderer, "Generate Keyframe"); - var mat = renderer.material; - - for (int i = 0; i < 16; i++) - { - var prefix = $"_Vertex_Deformation_Slot_{i}_"; - var node = stack.Count > 0 ? stack.Pop() : null; - var s = node?.Serialize(); - bool on = s != null; - - mat.SetFloat(prefix + "Enabled", on ? 1f : 0f); - mat.SetInt(prefix + "Opcode", on ? s.opcode : 0); - mat.SetFloat(prefix + "Float_0", on ? s.float0 : 0f); - mat.SetFloat(prefix + "Float_1", on ? s.float1 : 0f); - mat.SetFloat(prefix + "Float_2", on ? s.float2 : 0f); - mat.SetFloat(prefix + "Float_3", on ? s.float3 : 0f); - mat.SetVector(prefix + "Vector_0", on ? s.vec0 : Vector4.zero); - mat.SetVector(prefix + "Vector_1", on ? s.vec1 : Vector4.zero); - mat.SetVector(prefix + "Vector_2", on ? s.vec2 : Vector4.zero); - mat.SetVector(prefix + "Vector_3", on ? s.vec3 : Vector4.zero); - } - EditorUtility.SetDirty(renderer); - } -} - -public class FoldGraphView : GraphView -{ - static readonly (string label, Type dataType)[] AllNodeOptions = - { - ("Fold/Axis Align", typeof(AxisAlignNodeData)), - ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)), - ("Fold/Point Align", typeof(PointAlignNodeData)), - ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)), - ("Value/Float", typeof(FloatValueNodeData)), - ("Value/Vector", typeof(VectorValueNodeData)), - ("Value/GameObject", typeof(GameObjectNodeData)), - ("Output/Keyframe", typeof(KeyframeNodeData)), - }; - - readonly FoldWindow window; - public readonly FoldGraph graphAsset; - readonly Dictionary<string, FoldNodeView> nodeLookup = new(); - readonly FoldEdgeConnectorListener edgeConnectorListener; - bool suppressGraphChanges; - readonly HashSet<string> pendingAutoComplete = new(); - - void WithGraphChangesSuppressed(Action action) - { - var previous = suppressGraphChanges; - suppressGraphChanges = true; - try { action(); } - finally { suppressGraphChanges = previous; } - } - - public FoldGraphView(FoldWindow owner, FoldGraph graph) - { - window = owner; - graphAsset = graph; - edgeConnectorListener = new FoldEdgeConnectorListener(this); - - style.flexGrow = 1; - this.AddManipulator(new ContentZoomer()); - this.AddManipulator(new ContentDragger()); - this.AddManipulator(new SelectionDragger()); - this.AddManipulator(new RectangleSelector()); - - var grid = new GridBackground(); - Insert(0, grid); - grid.StretchToParentSize(); - - graphViewChanged = OnGraphViewChanged; - nodeCreationRequest = ctx => ShowSearchWindow(null, ctx.screenMousePosition); - RegisterCallback<KeyDownEvent>(OnKeyDown); - RegisterCallback<ContextualMenuPopulateEvent>(evt => { evt.menu.ClearItems(); evt.StopImmediatePropagation(); evt.PreventDefault(); ShowSearchWindow(null, evt.mousePosition); }); - Undo.undoRedoPerformed += OnUndoRedo; - - Reload(); - } - - public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) => - ports.Where(p => p.node != startPort.node && p.direction != startPort.direction && - (startPort.portType == null || p.portType == null || startPort.portType.IsAssignableFrom(p.portType) || p.portType.IsAssignableFrom(startPort.portType))).ToList(); - - internal void BindEdgeConnector(Port port) => port.AddManipulator(new EdgeConnector<Edge>(edgeConnectorListener)); - - GraphViewChange OnGraphViewChanged(GraphViewChange change) - { - if (suppressGraphChanges) return change; - change.elementsToRemove?.ForEach(e => { if (e is Edge edge) RemoveEdgeData(edge); else if (e is FoldNodeView node) RemoveNode(node); }); - change.edgesToCreate?.ForEach(AddEdgeData); - return change; - } - - void RemoveNode(FoldNodeView node) - { - RecordUndo("Delete Node"); - graphAsset.nodes.Remove(node.Data); - graphAsset.edges.RemoveAll(e => e.inputNodeGuid == node.Data.guid || e.outputNodeGuid == node.Data.guid); - nodeLookup.Remove(node.Data.guid); - MarkDirty(); - } - - internal void AddEdgeData(Edge edge) - { - if (edge.input?.node is not FoldNodeView inp || edge.output?.node is not FoldNodeView outp) return; - RecordUndo("Add Edge"); - graphAsset.edges.Add(new FoldEdge { inputNodeGuid = inp.Data.guid, inputPortName = edge.input.portName, outputNodeGuid = outp.Data.guid, outputPortName = edge.output.portName }); - MarkDirty(); - } - - void RemoveEdgeData(Edge edge) - { - if (edge.input?.node is not FoldNodeView inp || edge.output?.node is not FoldNodeView outp) return; - RecordUndo("Remove Edge"); - graphAsset.edges.RemoveAll(e => e.inputNodeGuid == inp.Data.guid && e.outputNodeGuid == outp.Data.guid && e.inputPortName == edge.input.portName && e.outputPortName == edge.output.portName); - MarkDirty(); - } - - public void Reload() => WithGraphChangesSuppressed(() => - { - nodeLookup.Clear(); - DeleteElements(graphElements.Where(e => e is Node or Edge).ToList()); - - foreach (var d in graphAsset.nodes) { var n = CreateNode(d); nodeLookup[d.guid] = n; AddElement(n); } - foreach (var e in graphAsset.edges) - { - if (nodeLookup.TryGetValue(e.outputNodeGuid, out var o) && nodeLookup.TryGetValue(e.inputNodeGuid, out var i)) - if (o.GetPort(e.outputPortName) is { } op && i.GetPort(e.inputPortName) is { } ip) - AddElement(op.ConnectTo(ip)); - } - }); - - void OnKeyDown(KeyDownEvent evt) - { - if (evt.keyCode == KeyCode.D && (evt.commandKey || evt.ctrlKey)) { evt.StopImmediatePropagation(); DuplicateSelectedNodes(); } - } - - void DuplicateSelectedNodes() - { - var sel = selection.OfType<FoldNodeView>().ToList(); - if (sel.Count == 0) return; - - RecordUndo("Duplicate Nodes"); - - var map = new Dictionary<string, FoldNodeView>(); - WithGraphChangesSuppressed(() => - { - foreach (var orig in sel) - { - var clone = (FoldNodeData)Activator.CreateInstance(orig.Data.GetType()); - JsonUtility.FromJsonOverwrite(JsonUtility.ToJson(orig.Data), clone); - clone.guid = Guid.NewGuid().ToString(); - clone.position += new Vector2(30f, 30f); - graphAsset.nodes.Add(clone); - var v = CreateNode(clone); - map[orig.Data.guid] = v; - nodeLookup[clone.guid] = v; - AddElement(v); - } - - foreach (var e in graphAsset.edges.Where(e => map.ContainsKey(e.outputNodeGuid) && map.ContainsKey(e.inputNodeGuid))) - if (map[e.outputNodeGuid].GetPort(e.outputPortName) is { } op && map[e.inputNodeGuid].GetPort(e.inputPortName) is { } ip) - { var edge = op.ConnectTo(ip); AddElement(edge); AddEdgeData(edge); } - }); - - ClearSelection(); - foreach (var n in map.Values) AddToSelection(n); - MarkDirty(); - } - - FoldNodeView CreateNode(FoldNodeData data) - { - FoldNodeView view = data switch - { - FloatValueNodeData or VectorValueNodeData or GameObjectNodeData => new ValueNodeView(), - FoldOperationData => new OperationNodeView(), - KeyframeNodeData => new KeyframeNodeView(), - _ => throw new ArgumentOutOfRangeException(nameof(data)) - }; - view.Initialize(this, data); - return view; - } - - internal FoldNodeView CreateNode(Type dataType, Vector2 position, bool autoComplete = false) - { - if (!suppressGraphChanges) RecordUndo("Create Node"); - var data = (FoldNodeData)Activator.CreateInstance(dataType); - data.position = position; - graphAsset.nodes.Add(data); - var view = CreateNode(data); - nodeLookup[data.guid] = view; - AddElement(view); - view.RefreshExpandedState(); - view.RefreshPorts(); - if (autoComplete) RegisterGeometryOnce(view, () => AutoCreateSingleOptionNodes(view)); - MarkDirty(); - return view; - } - - public void MarkDirty() => EditorUtility.SetDirty(graphAsset); - public void ShowNotification(string msg) => window.ShowNotification(msg); - public void ShowNodeCreationMenuFromPort(Port port, Vector2 pos) => ShowSearchWindow(port, pos); - public void Dispose() => Undo.undoRedoPerformed -= OnUndoRedo; - void OnUndoRedo() => Reload(); - internal void RecordUndo(string label) { if (!suppressGraphChanges) Undo.RegisterCompleteObjectUndo(graphAsset, label); } - - public FoldNodeView CreateNodeAndConnect(Port startPort, Type dataType, Vector2 pos) - { - RecordUndo("Create Node"); - FoldNodeView view = null; - WithGraphChangesSuppressed(() => - { - view = CreateNode(dataType, pos); - - // If dragging from input port, offset so the new node's right-center is at cursor once layout is computed - if (startPort.direction == Direction.Input) - RegisterGeometryOnce(view, () => - { - var size = view.layout.size; - view.SetPosition(new Rect(pos - new Vector2(size.x, size.y / 2), size)); - }); - - var compat = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); - if (compat != null) - { - var (o, i) = startPort.direction == Direction.Output ? (startPort, compat) : (compat, startPort); - var edge = o.ConnectTo(i); - AddElement(edge); - AddEdgeData(edge); - } - }); - RegisterGeometryOnce(view, () => AutoCreateSingleOptionNodes(view)); - MarkDirty(); - return view; - } - - internal IEnumerable<(string label, Type dataType)> GetNodeOptions(Port port) - { - if (port == null) return AllNodeOptions; - var t = port.portType; - bool @out = port.direction == Direction.Output; - return AllNodeOptions.Where(o => - (t == typeof(float) || t == typeof(Vector4)) ? (@out ? typeof(FoldOperationData).IsAssignableFrom(o.dataType) : (t == typeof(float) ? o.dataType == typeof(FloatValueNodeData) : o.dataType == typeof(VectorValueNodeData))) : - t == typeof(GameObject) ? (@out ? o.dataType == typeof(KeyframeNodeData) : o.dataType == typeof(GameObjectNodeData)) : - typeof(IFoldNode).IsAssignableFrom(t) && (typeof(FoldOperationData).IsAssignableFrom(o.dataType) || (@out && o.dataType == typeof(KeyframeNodeData)))); - } - - public T GetInputValue<T>(string guid, string port, T fallback) => - nodeLookup.TryGetValue(guid, out var n) && n.GetPort(port)?.connections.FirstOrDefault()?.output?.node is ValueNodeView v ? v.GetTypedValue<T>() : fallback; - - void ShowSearchWindow(Port port, Vector2 pos) - { - var p = ScriptableObject.CreateInstance<FoldSearchProvider>(); - p.Initialize(this, port, pos); - if (p.HasEntries()) SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(pos)), p); - } - - void AutoCreateSingleOptionNodes(FoldNodeView node) - { - if (node == null) return; - pendingAutoComplete.Remove(node.Data.guid); - var debug = new List<string>(); - var targetRect = EstimateNodeRect(node); - if (targetRect.size == Vector2.zero) - { - if (pendingAutoComplete.Add(node.Data.guid)) - RegisterGeometryOnce(node, () => AutoCreateSingleOptionNodes(node)); - Debug.Log("Fold AutoComplete: defer (no size); layout=" + node.layout.size + " resolved=(" + node.resolvedStyle.width + "," + node.resolvedStyle.height + ")"); - return; - } - - var ports = node.inputContainer.Children().OfType<Port>() - .Where(p => !p.connections.Any()) - .Select((p, i) => (port: p, index: i)) - .ToList(); - - var candidates = ports - .Select(p => (p.port, p.index, options: GetNodeOptions(p.port).ToList())) - .Where(p => p.options.Count == 1) - .ToList(); - - if (candidates.Count == 0) return; - - var created = new List<FoldNodeView>(); - var horizontalSpacing = targetRect.width + GetHorizontalMargins(node); - var verticalSpacing = targetRect.height; - var occupied = new List<Rect> { targetRect }; - - RecordUndo("Auto-complete Ports"); - WithGraphChangesSuppressed(() => - { - foreach (var (port, index, options) in candidates) - { - var dataType = options[0].dataType; - var targetPos = targetRect.position; - var desired = new Vector2( - targetPos.x - horizontalSpacing, - targetPos.y + (index - (candidates.Count - 1) * 0.5f) * verticalSpacing); - - var rect = FindFreePosition(new Rect(desired, targetRect.size), occupied, debug); - if (rect == Rect.zero) - { - debug.Add($"fail place idx={index} type={dataType.Name} desired={desired}"); - continue; - } - - var newNode = CreateNode(dataType, rect.position); - created.Add(newNode); - - var compat = GetCompatiblePorts(port, null).FirstOrDefault(p => p.node == newNode); - if (compat != null) - { - var (o, i) = port.direction == Direction.Output ? (port, compat) : (compat, port); - var edge = o.ConnectTo(i); - AddElement(edge); - AddEdgeData(edge); - } - } - }); - - foreach (var child in created) - AutoCreateSingleOptionNodes(child); - - Debug.Log($"Fold AutoComplete: node='{node.title}' size={targetRect.size} spacing=({horizontalSpacing},{verticalSpacing}) candidates={candidates.Count} created={created.Count} notes=[{string.Join("; ", debug)}]"); - MarkDirty(); - } - - Rect FindFreePosition(Rect desired, List<Rect> occupied, List<string> debug) - { - if (desired.size == Vector2.zero) return Rect.zero; - - Rect rect = desired; - int safety = 0; - while ((occupied?.Any(o => RectsOverlap(o, rect)) == true || OverlapsExisting(rect)) && safety++ < 50) - rect.position += new Vector2(0f, rect.height); - if (safety >= 50) - { - debug?.Add($"exceeded iterations({safety}) starting={desired.position}"); - return Rect.zero; - } - occupied?.Add(rect); - return rect; - } - - bool OverlapsExisting(Rect rect) - { - return nodeLookup.Values.Any(n => - { - var existing = EstimateNodeRect(n); - return existing.size != Vector2.zero && RectsOverlap(existing, rect); - }); - } - - static Rect EstimateNodeRect(FoldNodeView node) - { - var size = node.layout.size; - if (float.IsNaN(size.x) || float.IsNaN(size.y)) - size = Vector2.zero; - if (size == Vector2.zero) - { - var rs = node.resolvedStyle; - size = new Vector2(float.IsNaN(rs.width) ? 0f : rs.width, float.IsNaN(rs.height) ? 0f : rs.height); - } - if (float.IsNaN(size.x) || float.IsNaN(size.y)) - size = Vector2.zero; - return size == Vector2.zero ? Rect.zero : new Rect(node.GetPosition().position, size); - } - - static float GetHorizontalMargins(FoldNodeView node) - { - var rs = node.resolvedStyle; - float left = float.IsNaN(rs.marginLeft) ? 0f : rs.marginLeft; - float right = float.IsNaN(rs.marginRight) ? 0f : rs.marginRight; - return left + right; - } - - static bool RectsOverlap(Rect a, Rect b) => - a.xMin < b.xMax && a.xMax > b.xMin && a.yMin < b.yMax && a.yMax > b.yMin; - - static void RegisterGeometryOnce(VisualElement element, Action action) - { - bool TryInvoke() - { - var size = element.layout.size; - if (float.IsNaN(size.x) || float.IsNaN(size.y) || size == Vector2.zero) - { - var rs = element.resolvedStyle; - size = new Vector2(float.IsNaN(rs.width) ? 0f : rs.width, float.IsNaN(rs.height) ? 0f : rs.height); - } - - if (size == Vector2.zero) return false; - action?.Invoke(); - return true; - }; - - if (TryInvoke()) return; - - EventCallback<GeometryChangedEvent> handler = null; - handler = evt => - { - if (TryInvoke()) - element.UnregisterCallback<GeometryChangedEvent>(handler); - }; - element.RegisterCallback<GeometryChangedEvent>(handler); - } -} - -class FoldEdgeConnectorListener : IEdgeConnectorListener -{ - readonly FoldGraphView graphView; - public FoldEdgeConnectorListener(FoldGraphView gv) => graphView = gv; - - public void OnDropOutsidePort(Edge edge, Vector2 pos) => graphView.ShowNodeCreationMenuFromPort(edge.output ?? edge.input, pos); - - public void OnDrop(GraphView gv, Edge edge) - { - if (edge.input == null || edge.output == null) return; - edge.input.Connect(edge); - edge.output.Connect(edge); - gv.AddElement(edge); - graphView?.AddEdgeData(edge); - } -} - -class FoldSearchProvider : ScriptableObject, ISearchWindowProvider -{ - FoldGraphView graphView; - Port startPort; - Vector2 worldPosition; - List<(string label, Type dataType)> options; - - public void Initialize(FoldGraphView gv, Port port, Vector2 pos) { graphView = gv; startPort = port; worldPosition = pos; options = gv.GetNodeOptions(port).ToList(); } - public bool HasEntries() => options?.Count > 0; - - public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext ctx) - { - var entries = new List<SearchTreeEntry> { new SearchTreeGroupEntry(new GUIContent("Create Node"), 0) }; - foreach (var grp in options.GroupBy(o => o.label.Split('/')[0]).OrderBy(g => g.Key)) - { - entries.Add(new SearchTreeGroupEntry(new GUIContent(grp.Key), 1)); - foreach (var opt in grp) - entries.Add(new SearchTreeEntry(new GUIContent(opt.label.Split('/')[1])) { level = 2, userData = opt.dataType }); - } - return entries; - } - - public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext ctx) - { - if (entry.userData is not Type dataType) return false; - var pos = graphView.contentViewContainer.WorldToLocal(worldPosition); - if (startPort != null) graphView.CreateNodeAndConnect(startPort, dataType, pos); - else graphView.CreateNode(dataType, pos, true); - return true; - } -} diff --git a/Scripts/Fold/Editor/FoldNodeSerialized.cs b/Scripts/Fold/Editor/FoldNodeSerialized.cs deleted file mode 100644 index 62431ae..0000000 --- a/Scripts/Fold/Editor/FoldNodeSerialized.cs +++ /dev/null @@ -1,19 +0,0 @@ -using UnityEngine; - -public class FoldNodeSerialized -{ - public int opcode; - - // Correspond to _Vertex_Deformation_Float_0, etc. - public float float0; - public float float1; - public float float2; - public float float3; - - // _Vertex_Deformation_Vector_0, etc. - public Vector4 vec0; - public Vector4 vec1; - public Vector4 vec2; - public Vector4 vec3; -}; - diff --git a/Scripts/Fold/Editor/FoldWindow.cs b/Scripts/Fold/Editor/FoldWindow.cs deleted file mode 100644 index f0f9b42..0000000 --- a/Scripts/Fold/Editor/FoldWindow.cs +++ /dev/null @@ -1,85 +0,0 @@ -using UnityEditor; -using UnityEngine; -using UnityEngine.UIElements; - -public class FoldWindow : EditorWindow -{ - const string GraphPath = "Assets/FoldGraph.asset"; - - FoldGraphView graphView; - FoldGraph graphAsset; - - [MenuItem("Tools/yum_food/Fold")] - public static void Open() - { - var window = GetWindow<FoldWindow>(); - window.titleContent = new GUIContent("Fold"); - window.Show(); - } - - void OnEnable() - { - graphAsset = LoadOrCreateGraph(); - ConstructGraphView(); - } - - void OnDisable() - { - if (graphView != null) - { - rootVisualElement.Remove(graphView); - graphView.Dispose(); - graphView = null; - } - - AssetDatabase.SaveAssets(); - } - - void OnFocus() - { - // If we lost the graph view for any reason, rebuild it. Otherwise, reload to match the asset on disk. - if (graphView == null) - { - graphAsset = LoadOrCreateGraph(); - ConstructGraphView(); - } - else - { - graphView.Reload(); - } - } - - FoldGraph LoadOrCreateGraph() - { - var asset = AssetDatabase.LoadAssetAtPath<FoldGraph>(GraphPath); - if (asset == null) - { - asset = CreateInstance<FoldGraph>(); - AssetDatabase.CreateAsset(asset, GraphPath); - AssetDatabase.SaveAssets(); - } - - return asset; - } - - void ConstructGraphView() - { - graphView = new FoldGraphView(this, graphAsset) - { - name = "Fold Graph" - }; - - graphView.style.flexGrow = 1f; - graphView.style.width = Length.Percent(100); - graphView.style.height = Length.Percent(100); - rootVisualElement.Add(graphView); - } - - public void ShowNotification(string message) - { - if (string.IsNullOrEmpty(message)) - return; - - ShowNotification(new GUIContent(message)); - } -} diff --git a/Scripts/Fold/Editor/README.md b/Scripts/Fold/Editor/README.md new file mode 100644 index 0000000..8305abf --- /dev/null +++ b/Scripts/Fold/Editor/README.md @@ -0,0 +1,131 @@ +# Vertex Deformation Fluent Builder + +A simple, code-first API for building vertex deformation pipelines without visual node editors. + +## Quick Start + +```csharp +using UnityEngine; + +// Apply to a material +VertexDeformationBuilder.Create() + .For(material) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(2f, 1f, 1f) + .Apply(); +``` + +## Available Operations + +### TubeToPlane(p, r, s, t) +Unfolds a tube into a plane. +- `p`: Origin point (Vector3) +- `r`: R axis direction (Vector3) +- `s`: S axis direction (Vector3) +- `t`: Interpolation factor (float, 0-1) + +### PlaneToTube(p, r, s, t) +Folds a plane into a tube. +- Same parameters as TubeToPlane + +### PointAlign(po, pp, r, t) +Aligns geometry to a point. +- `po`: Original point (Vector3) +- `pp`: Target point (Vector3) +- `r`: Rotation axis (Vector3) +- `t`: Interpolation factor (float, 0-1) + +### AxisAlign(po, pp, r, t) +Aligns geometry along an axis. +- Same parameters as PointAlign + +### NormConversion(inputK, outputK, t) +Converts between different norms (L1, L2, L∞). +- `inputK`: Input norm (float, e.g., 1 for L1, 2 for L2, ∞ for L∞) +- `outputK`: Output norm (float) +- `t`: Interpolation factor (float, 0-1) + +Common conversions: +- Sphere to box: `NormConversion(2f, float.PositiveInfinity, 1f)` +- Box to sphere: `NormConversion(float.PositiveInfinity, 2f, 1f)` +- Diamond to sphere: `NormConversion(1f, 2f, 1f)` + +### Seal(A, k, st, t) +Sealing operation for closing geometry. +- `A`: Amplitude (float) +- `k`: Smoothness factor (float) +- `st`: Scale factor (float) +- `t`: Interpolation factor (float, 0-1) + +### SineWaves() +Applies sine wave deformation. + +### FBM() +Applies fractal Brownian motion deformation. + +### Custom(opcode, f0-f3, v0-v3) +For advanced use cases or custom opcodes. + +## Presets + +Use built-in presets from anywhere: + +```csharp +VertexDeformationPresets.TubeToPlaneFull(material); +VertexDeformationPresets.NormConvL2ToL1(material); +VertexDeformationPresets.NormConvL2ToLinf(material); +``` + +## GUI Window + +Open `Window > Vertex Deformation Presets` for a GUI with buttons for common operations. + +## Adding Custom Presets + +1. Edit `VertexDeformationPresets` class: + +```csharp +public static void MyCustomEffect(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(2f, 1f, 0.5f) + .Seal(0.1f, 2f, 0.8f, 1f) + .Apply(); +``` + +2. Add button in `VertexDeformationEditorWindow`: + +```csharp +if (GUILayout.Button("My Custom Effect")) + VertexDeformationPresets.MyCustomEffect(targetMaterial); +``` + +## Pipeline Chaining + +Operations are applied in the order they're chained: + +```csharp +VertexDeformationBuilder.Create() + .For(material) + .TubeToPlane(...) // Step 1 + .NormConversion(...) // Step 2 + .PointAlign(...) // Step 3 + .Seal(...) // Step 4 + .Apply(); // Execute +``` + +The shader supports up to 16 operations per material. + +## Clearing Deformations + +```csharp +// Via GUI: Use "Clear All Deformations" button + +// Via code: +targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f); +``` + +## Examples + +See `VertexDeformationExamples.cs` for menu items under `Tools > Vertex Deformation > Example: ...` diff --git a/Scripts/Fold/Editor/VertexDeformationBuilder.cs b/Scripts/Fold/Editor/VertexDeformationBuilder.cs new file mode 100644 index 0000000..c04a66a --- /dev/null +++ b/Scripts/Fold/Editor/VertexDeformationBuilder.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +public class VertexDeformationSlot +{ + public int opcode; + public float float0, float1, float2, float3; + public Vector4 vec0, vec1, vec2, vec3; + + public void ApplyToMaterial(Material mat, int slotIndex) + { + var prefix = $"_Vertex_Deformation_Slot_{slotIndex}_"; + mat.SetFloat(prefix + "Enabled", 1f); + mat.SetInt(prefix + "Opcode", opcode); + mat.SetFloat(prefix + "Float_0", float0); + mat.SetFloat(prefix + "Float_1", float1); + mat.SetFloat(prefix + "Float_2", float2); + mat.SetFloat(prefix + "Float_3", float3); + mat.SetVector(prefix + "Vector_0", vec0); + mat.SetVector(prefix + "Vector_1", vec1); + mat.SetVector(prefix + "Vector_2", vec2); + mat.SetVector(prefix + "Vector_3", vec3); + } +} + +public class VertexDeformationBuilder +{ + readonly List<VertexDeformationSlot> slots = new(); + Material targetMaterial; + + public static class Opcodes + { + public const int None = 0; + public const int TubeToPlane = 1; + public const int PlaneToTube = 2; + public const int PointAlign = 3; + public const int AxisAlign = 4; + public const int NormConversion = 5; + public const int Seal = 6; + public const int SineWaves = 7; + public const int FBM = 8; + } + + VertexDeformationBuilder() { } + + public static VertexDeformationBuilder Create() => new(); + + public VertexDeformationBuilder For(Material material) + { + targetMaterial = material; + return this; + } + + public VertexDeformationBuilder TubeToPlane(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.TubeToPlane, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public VertexDeformationBuilder PlaneToTube(Vector3 p, Vector3 r, Vector3 s, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.PlaneToTube, + vec0 = p, + vec1 = r, + vec2 = s, + float0 = t + }); + return this; + } + + public VertexDeformationBuilder PointAlign(Vector3 po, Vector3 pp, Vector3 r, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.PointAlign, + vec0 = po, + vec1 = pp, + vec2 = r, + float0 = t + }); + return this; + } + + public VertexDeformationBuilder AxisAlign(Vector3 po, Vector3 pp, Vector3 r, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.AxisAlign, + vec0 = po, + vec1 = pp, + vec2 = r, + float0 = t + }); + return this; + } + + public VertexDeformationBuilder NormConversion(float inputK, float outputK, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.NormConversion, + float0 = inputK, + float1 = outputK, + float2 = t + }); + return this; + } + + public VertexDeformationBuilder Seal(float A, float k, float st, float t) + { + slots.Add(new VertexDeformationSlot + { + opcode = Opcodes.Seal, + float0 = A, + float1 = k, + float2 = st, + float3 = t + }); + return this; + } + + public VertexDeformationBuilder SineWaves() + { + slots.Add(new VertexDeformationSlot { opcode = Opcodes.SineWaves }); + return this; + } + + public VertexDeformationBuilder FBM() + { + slots.Add(new VertexDeformationSlot { opcode = Opcodes.FBM }); + return this; + } + + public VertexDeformationBuilder Custom(int opcode, + float f0 = 0, float f1 = 0, float f2 = 0, float f3 = 0, + Vector4? v0 = null, Vector4? v1 = null, Vector4? v2 = null, Vector4? v3 = null) + { + slots.Add(new VertexDeformationSlot + { + opcode = opcode, + float0 = f0, float1 = f1, float2 = f2, float3 = f3, + vec0 = v0 ?? Vector4.zero, + vec1 = v1 ?? Vector4.zero, + vec2 = v2 ?? Vector4.zero, + vec3 = v3 ?? Vector4.zero + }); + return this; + } + + public void Apply() + { + if (targetMaterial == null) + { + Debug.LogError("No target material set. Use .For(material) before .Apply()"); + return; + } + + if (slots.Count > 16) + { + Debug.LogWarning($"Too many operations ({slots.Count}). Only the first 16 will be applied."); + } + + Undo.RecordObject(targetMaterial, "Apply Vertex Deformation"); + + targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 1f); + + for (int i = 0; i < 16; i++) + { + if (i < slots.Count) + { + slots[i].ApplyToMaterial(targetMaterial, i); + } + else + { + var prefix = $"_Vertex_Deformation_Slot_{i}_"; + targetMaterial.SetFloat(prefix + "Enabled", 0f); + } + } + + EditorUtility.SetDirty(targetMaterial); + } + + public void Clear() + { + slots.Clear(); + } + + public int Count => slots.Count; +} + +public static class VertexDeformationPresets +{ + public static void TubeToPlaneFull(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .Apply(); + + public static void PlaneToTubeFull(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .Apply(); + + public static void TubeToPlaneThenNormConv(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(2f, 1f, 1f) + .Apply(); + + public static void NormConvL1ToL2(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .NormConversion(1f, 2f, 1f) + .Apply(); + + public static void NormConvL2ToL1(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .NormConversion(2f, 1f, 1f) + .Apply(); + + public static void NormConvL2ToLinf(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .NormConversion(2f, float.PositiveInfinity, 1f) + .Apply(); +} + +public class VertexDeformationEditorWindow : EditorWindow +{ + Material targetMaterial; + Vector2 scrollPos; + + [MenuItem("Window/Vertex Deformation Presets")] + static void ShowWindow() + { + GetWindow<VertexDeformationEditorWindow>("Vertex Deformation"); + } + + void OnGUI() + { + GUILayout.Label("Vertex Deformation Builder", EditorStyles.boldLabel); + + targetMaterial = EditorGUILayout.ObjectField("Target Material", targetMaterial, typeof(Material), false) as Material; + + if (targetMaterial == null) + { + EditorGUILayout.HelpBox("Select a material to apply vertex deformations", MessageType.Info); + return; + } + + scrollPos = EditorGUILayout.BeginScrollView(scrollPos); + + DrawSection("Basic Operations", () => + { + if (GUILayout.Button("Tube to Plane")) + VertexDeformationPresets.TubeToPlaneFull(targetMaterial); + + if (GUILayout.Button("Plane to Tube")) + VertexDeformationPresets.PlaneToTubeFull(targetMaterial); + }); + + DrawSection("Norm Conversions", () => + { + if (GUILayout.Button("L1 to L2")) + VertexDeformationPresets.NormConvL1ToL2(targetMaterial); + + if (GUILayout.Button("L2 to L1")) + VertexDeformationPresets.NormConvL2ToL1(targetMaterial); + + if (GUILayout.Button("L2 to L∞")) + VertexDeformationPresets.NormConvL2ToLinf(targetMaterial); + }); + + DrawSection("Complex Pipelines", () => + { + if (GUILayout.Button("Tube to Plane + Norm Conv (L2→L1)")) + VertexDeformationPresets.TubeToPlaneThenNormConv(targetMaterial); + }); + + DrawSection("Utilities", () => + { + if (GUILayout.Button("Clear All Deformations")) + { + Undo.RecordObject(targetMaterial, "Clear Vertex Deformations"); + targetMaterial.SetFloat("_Vertex_Deformation_Enabled", 0f); + for (int i = 0; i < 16; i++) + { + var prefix = $"_Vertex_Deformation_Slot_{i}_"; + targetMaterial.SetFloat(prefix + "Enabled", 0f); + } + EditorUtility.SetDirty(targetMaterial); + } + + if (GUILayout.Button("Show Code Example")) + { + ShowCodeExample(); + } + }); + + EditorGUILayout.EndScrollView(); + } + + void DrawSection(string title, Action content) + { + GUILayout.Space(10); + GUILayout.Label(title, EditorStyles.boldLabel); + content(); + } + + void ShowCodeExample() + { + EditorUtility.DisplayDialog("Code Example", + "// Quick preset:\n" + + "VertexDeformationPresets.TubeToPlaneFull(material);\n\n" + + "// Custom pipeline:\n" + + "VertexDeformationBuilder.Create()\n" + + " .For(material)\n" + + " .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" + + " .NormConversion(2f, 1f, 1f)\n" + + " .PlaneToTube(Vector3.zero, Vector3.right, Vector3.forward, 1f)\n" + + " .Apply();\n\n" + + "// Add your own presets in VertexDeformationPresets class!", + "OK"); + } +} diff --git a/Scripts/Fold/Editor/VertexDeformationExamples.cs b/Scripts/Fold/Editor/VertexDeformationExamples.cs new file mode 100644 index 0000000..705cea9 --- /dev/null +++ b/Scripts/Fold/Editor/VertexDeformationExamples.cs @@ -0,0 +1,114 @@ +using UnityEditor; +using UnityEngine; + +public static class VertexDeformationExamples +{ + [MenuItem("Tools/Vertex Deformation/Example: Tube to Plane")] + static void Example_TubeToPlane() + { + var mat = Selection.activeObject as Material; + if (mat == null) + { + EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK"); + return; + } + + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane( + p: Vector3.zero, + r: Vector3.right, + s: Vector3.forward, + t: 1f) + .Apply(); + } + + [MenuItem("Tools/Vertex Deformation/Example: Complex Pipeline")] + static void Example_ComplexPipeline() + { + var mat = Selection.activeObject as Material; + if (mat == null) + { + EditorUtility.DisplayDialog("Error", "Select a material in the Project window", "OK"); + return; + } + + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(inputK: 2f, outputK: 1f, t: 1f) + .PointAlign( + po: new Vector3(0, 0, 0), + pp: new Vector3(0, 1, 0), + r: Vector3.right, + t: 0.5f) + .Apply(); + + Debug.Log("Applied 3-step deformation pipeline to " + mat.name); + } + + [MenuItem("Tools/Vertex Deformation/Example: Add Custom Preset")] + static void Example_ShowHowToAddPreset() + { + EditorUtility.DisplayDialog("How to Add Presets", + "1. Open VertexDeformationPresets class\n\n" + + "2. Add a new static method:\n\n" + + "public static void MyCustomPreset(Material mat) =>\n" + + " VertexDeformationBuilder.Create()\n" + + " .For(mat)\n" + + " .TubeToPlane(...)\n" + + " .NormConversion(...)\n" + + " .Apply();\n\n" + + "3. Add a button in VertexDeformationEditorWindow:\n\n" + + "if (GUILayout.Button(\"My Custom Preset\"))\n" + + " VertexDeformationPresets.MyCustomPreset(targetMaterial);", + "OK"); + } + + [MenuItem("Tools/Vertex Deformation/Example: All Opcodes")] + static void Example_AllOpcodes() + { + EditorUtility.DisplayDialog("All Available Operations", + "TubeToPlane(p, r, s, t)\n" + + "PlaneToTube(p, r, s, t)\n" + + "PointAlign(po, pp, r, t)\n" + + "AxisAlign(po, pp, r, t)\n" + + "NormConversion(inputK, outputK, t)\n" + + "Seal(A, k, st, t)\n" + + "SineWaves()\n" + + "FBM()\n\n" + + "Custom(opcode, f0, f1, f2, f3, v0, v1, v2, v3)\n\n" + + "Chain them with method calls:\n" + + ".TubeToPlane(...).NormConversion(...).Apply()", + "OK"); + } + + public static class CustomPresetIdeas + { + public static void CylinderToPlane(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.up, Vector3.right, 1f) + .Apply(); + + public static void SphereToBox(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .NormConversion(inputK: 2f, outputK: float.PositiveInfinity, t: 1f) + .Apply(); + + public static void BoxToSphere(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .NormConversion(inputK: float.PositiveInfinity, outputK: 2f, t: 1f) + .Apply(); + + public static void TubeUnfoldAndFlatten(Material mat) => + VertexDeformationBuilder.Create() + .For(mat) + .TubeToPlane(Vector3.zero, Vector3.right, Vector3.forward, 1f) + .NormConversion(inputK: 2f, outputK: 1f, t: 0.5f) + .Seal(A: 0.1f, k: 2f, st: 0.8f, t: 1f) + .Apply(); + } +} |
