Browse Source

Converted simple nodes prototype to chunkyimage

flabbet 1 year ago
parent
commit
43b5768d1d

+ 9 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/INodeGraph.cs

@@ -0,0 +1,9 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public interface INodeGraph
+{
+    public IReadOnlyCollection<IReadOnlyNode> AllNodes { get; }
+    public IReadOnlyNode OutputNode { get; }
+    public void AddNode(IReadOnlyNode node);
+    public void RemoveNode(IReadOnlyNode node);
+}

+ 32 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/INodeProperty.cs

@@ -0,0 +1,32 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public interface INodeProperty
+{
+    public string Name { get; }
+    public object Value { get; set; }
+    public IReadOnlyNode Node { get; }
+}
+
+public interface INodeProperty<T> : INodeProperty
+{
+    public new T Value { get; set; }
+}
+
+public interface IInputProperty : INodeProperty
+{
+    public IOutputProperty? Connection { get; set; }
+}
+
+public interface IOutputProperty : INodeProperty
+{
+    public void ConnectTo(IInputProperty property);
+    public void DisconnectFrom(IInputProperty property);
+}
+
+public interface IInputProperty<T> : IInputProperty, INodeProperty<T>
+{
+}
+
+public interface IOutputProperty<T> : IOutputProperty, INodeProperty<T>
+{
+}

+ 12 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/IReadOnlyNode.cs

@@ -0,0 +1,12 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public interface IReadOnlyNode
+{
+    public string Name { get; }
+    public IReadOnlyCollection<IInputProperty> InputProperties { get; }
+    public IReadOnlyCollection<IOutputProperty> OutputProperties { get; }
+    public IReadOnlyCollection<IReadOnlyNode> ConnectedNodes { get; }
+
+    public void Execute(int frame);
+    public bool Validate();
+}

+ 35 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/InputProperty.cs

@@ -0,0 +1,35 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public class InputProperty : IInputProperty
+{
+    public string Name { get; }
+    public object Value { get; set; }
+    public Node Node { get; }
+    IReadOnlyNode INodeProperty.Node => Node;
+    
+    public IOutputProperty Connection { get; set; }
+    
+    internal InputProperty(Node node, string name, object defaultValue)
+    {
+        Name = name;
+        Value = defaultValue;
+        Node = node;
+    }
+
+}
+
+
+public class InputProperty<T> : InputProperty, IInputProperty<T>
+{
+    public new T Value
+    {
+        get => (T)base.Value;
+        set => base.Value = value;
+    }
+    
+    internal InputProperty(Node node, string name, T defaultValue) : base(node, name, defaultValue)
+    {
+    }
+}

+ 86 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeGraph.cs

@@ -0,0 +1,86 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public class NodeGraph : INodeGraph
+{
+    private readonly List<Node> _nodes = new();
+    public IReadOnlyCollection<Node> Nodes => _nodes;
+    public OutputNode? OutputNode => Nodes.OfType<OutputNode>().FirstOrDefault();
+    
+    IReadOnlyCollection<IReadOnlyNode> INodeGraph.AllNodes => Nodes;
+    IReadOnlyNode INodeGraph.OutputNode => OutputNode;
+
+    public void AddNode(Node node)
+    {
+        if (Nodes.Contains(node))
+        {
+            return;
+        }
+        
+        _nodes.Add(node);
+    }
+    
+    public void RemoveNode(Node node)
+    {
+        if (!Nodes.Contains(node))
+        {
+            return;
+        }
+        
+        _nodes.Remove(node);
+    }
+    
+    public ChunkyImage Execute()
+    {
+        if(OutputNode == null) return null;
+        
+        var queue = CalculateExecutionQueue(OutputNode);
+        
+        while (queue.Count > 0)
+        {
+            var node = queue.Dequeue();
+            
+            node.Execute(0);
+        }
+        
+        return OutputNode.Input.Value;
+    }
+
+    private Queue<IReadOnlyNode> CalculateExecutionQueue(OutputNode outputNode)
+    {
+        // backwards breadth-first search
+        var visited = new HashSet<IReadOnlyNode>();
+        var queueNodes = new Queue<IReadOnlyNode>();
+        List<IReadOnlyNode> finalQueue = new();
+        queueNodes.Enqueue(outputNode);
+        
+        while (queueNodes.Count > 0)
+        {
+            var node = queueNodes.Dequeue();
+            if (!visited.Add(node))
+            {
+                continue;
+            }
+            
+            finalQueue.Add(node);
+            
+            foreach (var input in node.InputProperties)
+            {
+                if (input.Connection == null)
+                {
+                    continue;
+                }
+                
+                queueNodes.Enqueue(input.Connection.Node);
+            }
+        }
+        
+        finalQueue.Reverse();
+        return new Queue<IReadOnlyNode>(finalQueue);
+    }
+
+    void INodeGraph.AddNode(IReadOnlyNode node) => AddNode((Node)node);
+
+    void INodeGraph.RemoveNode(IReadOnlyNode node) => RemoveNode((Node)node);
+}

+ 23 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/FolderNode.cs

@@ -0,0 +1,23 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class FolderNode : Node
+{
+    public InputProperty<ChunkyImage> Input { get; }
+    public OutputProperty<ChunkyImage> Output { get; }
+    
+    public FolderNode(string name) : base(name)
+    {
+        Input = CreateInput<ChunkyImage>("Input", null);
+        Output = CreateOutput<ChunkyImage>("Output", null);
+    }    
+    
+    public override bool Validate()
+    {
+        return true;
+    }
+    
+    public override void OnExecute(int frame)
+    {
+        Output.Value = Input.Value;
+    }
+}

+ 35 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/LayerNode.cs

@@ -0,0 +1,35 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class LayerNode : Node
+{
+    public InputProperty<ChunkyImage?> Background { get; }
+    public OutputProperty<ChunkyImage> Output { get; }
+    public ChunkyImage LayerImage { get; set; }
+    
+    public LayerNode(string name, VecI size) : base(name)
+    {
+        Background = CreateInput<ChunkyImage>("Background", null);
+        Output = CreateOutput<ChunkyImage>("Image", new ChunkyImage(size));
+        LayerImage = new ChunkyImage(size);
+    }
+
+    public override bool Validate()
+    {
+        return true;
+    }
+
+    public override void OnExecute(int frame)
+    {
+        if (Background.Value != null)
+        {
+            Output.Value.EnqueueDrawChunkyImage(VecI.Zero, Background.Value);
+        }
+        
+        Output.Value.EnqueueDrawChunkyImage(VecI.Zero, LayerImage);
+        Output.Value.CommitChanges();
+    }
+
+   
+}

+ 41 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/MergeNode.cs

@@ -0,0 +1,41 @@
+using PixiEditor.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class MergeNode : Node
+{
+    public InputProperty<ChunkyImage?> Top { get; }
+    public InputProperty<ChunkyImage?> Bottom { get; }
+    public OutputProperty<ChunkyImage> Output { get; }
+    
+    public MergeNode(string name) : base(name)
+    {
+        Top = CreateInput<ChunkyImage>("Top", null);
+        Bottom = CreateInput<ChunkyImage>("Bottom", null);
+        Output = CreateOutput<ChunkyImage>("Output", null);
+    }
+    
+    public override bool Validate()
+    {
+        return Top.Value != null || Bottom.Value != null;
+    }
+
+    public override void OnExecute(int frame)
+    {
+        VecI size = Top.Value?.CommittedSize ?? Bottom.Value.CommittedSize;
+        
+        Output.Value = new ChunkyImage(size);
+        
+        if (Bottom.Value != null)
+        {
+            Output.Value.EnqueueDrawChunkyImage(VecI.Zero, Bottom.Value);
+        }
+        
+        if (Top.Value != null)
+        {
+            Output.Value.EnqueueDrawChunkyImage(VecI.Zero, Top.Value);
+        }
+        
+        Output.Value.CommitChanges();
+    }
+}

+ 50 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Node.cs

@@ -0,0 +1,50 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public abstract class Node(string name) : IReadOnlyNode
+{
+    private List<InputProperty> inputs = new();
+    private List<OutputProperty> outputs = new();
+    
+    private List<IReadOnlyNode> _connectedNodes = new();
+    
+    public string Name { get; } = name;
+    
+    public IReadOnlyCollection<InputProperty> InputProperties => inputs;
+    public IReadOnlyCollection<OutputProperty> OutputProperties => outputs;
+    public IReadOnlyCollection<IReadOnlyNode> ConnectedNodes => _connectedNodes;
+
+    IReadOnlyCollection<IInputProperty> IReadOnlyNode.InputProperties => inputs;
+    IReadOnlyCollection<IOutputProperty> IReadOnlyNode.OutputProperties => outputs;
+
+    public void Execute(int frame)
+    {
+        foreach (var output in outputs)
+        {
+            foreach (var connection in output.Connections)
+            {
+                connection.Value = output.Value;
+            }
+        }
+        
+        OnExecute(frame);
+    }
+
+    public abstract void OnExecute(int frame);
+    public abstract bool Validate();
+    
+    protected InputProperty<T> CreateInput<T>(string name, T defaultValue)
+    {
+        var property = new InputProperty<T>(this, name, defaultValue);
+        inputs.Add(property);
+        return property;
+    }
+    
+    protected OutputProperty<T> CreateOutput<T>(string name, T defaultValue)
+    {
+        var property = new OutputProperty<T>(this, name, defaultValue);
+        outputs.Add(property);
+        property.Connected += (input, _) => _connectedNodes.Add(input.Node);
+        property.Disconnected += (input, _) => _connectedNodes.Remove(input.Node);
+        return property;
+    }
+}

+ 20 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/OutputNode.cs

@@ -0,0 +1,20 @@
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+public class OutputNode : Node
+{
+    public InputProperty<ChunkyImage?> Input { get; } 
+    public OutputNode(string name) : base(name)
+    {
+        Input = CreateInput<ChunkyImage>("Input", null);
+    }
+    
+    public override bool Validate()
+    {
+        return Input.Value != null;
+    }
+    
+    public override void OnExecute(int frame)
+    {
+        
+    }
+}

+ 75 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/OutputProperty.cs

@@ -0,0 +1,75 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public delegate void InputConnectedEvent(IInputProperty input, IOutputProperty output);
+public class OutputProperty : IOutputProperty
+{
+    private List<IInputProperty> _connections = new();
+    private object _value;
+    public string Name { get; }
+    
+    public Node Node { get; }
+    IReadOnlyNode INodeProperty.Node => Node;
+
+    public object Value
+    {
+        get => _value;
+        set
+        {
+            _value = value;
+            foreach (var connection in Connections)
+            {
+                connection.Value = value;
+            }
+        }    
+    }
+
+    public IReadOnlyCollection<IInputProperty> Connections => _connections;
+    
+    public event InputConnectedEvent Connected;
+    public event InputConnectedEvent Disconnected;
+    
+    internal OutputProperty(Node node, string name, object defaultValue)
+    {
+        Name = name;
+        Value = defaultValue;
+        _connections = new List<IInputProperty>();
+        Node = node;
+    }
+    
+    public void ConnectTo(IInputProperty property)
+    {
+        if(Connections.Contains(property)) return;
+        
+        _connections.Add(property);
+        property.Connection = this;
+        Connected?.Invoke(property, this);
+    }
+    
+    public void DisconnectFrom(IInputProperty property)
+    {
+        if(!Connections.Contains(property)) return;
+        
+        _connections.Remove(property);
+        if(property.Connection == this)
+        {
+            property.Connection = null;
+        }
+        
+        Disconnected?.Invoke(property, this);
+    }
+}
+
+public class OutputProperty<T> : OutputProperty, INodeProperty<T>
+{
+    public new T Value
+    {
+        get => (T)base.Value;
+        set => base.Value = value;
+    }
+    
+    internal OutputProperty(Node node ,string name, T defaultValue) : base(node, name, defaultValue)
+    {
+    }
+}