port from perforce
This commit is contained in:
14
intromat/Intromat/Nodes/Code/BooleanLiteralModel.cs
Normal file
14
intromat/Intromat/Nodes/Code/BooleanLiteralModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("BooleanLiteral", Namespace = _namespace)]
|
||||
public sealed class BooleanLiteralModel : LiteralModelBase<bool>
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new BooleanLiteralNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/Nodes/Code/BooleanLiteralNode.cs
Normal file
66
intromat/Intromat/Nodes/Code/BooleanLiteralNode.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class BooleanLiteralNode : CodeGenNodeViewModel
|
||||
{
|
||||
static BooleanLiteralNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<BooleanLiteralNode>));
|
||||
}
|
||||
|
||||
public BooleanLiteralNode() : base(NodeType.Literal)
|
||||
{
|
||||
Name = "Boolean";
|
||||
|
||||
Output = new CodeGenOutputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
|
||||
{
|
||||
Name = "Value",
|
||||
Editor = ValueEditor,
|
||||
Value = ValueEditor.ValueChanged.Select(v => new BooleanLiteralValue { Value = v })
|
||||
};
|
||||
Outputs.Add(Output);
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Output.CurrentValue)
|
||||
.Where(v => v != null)
|
||||
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "False");
|
||||
}
|
||||
|
||||
public BooleanValueEditorViewModel ValueEditor { get; } = new();
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<bool>> Output { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new BooleanLiteralModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var booleanLiteral = (BooleanLiteralModel)model;
|
||||
ValueEditor.Value = booleanLiteral.Value;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var booleanLiteral = (BooleanLiteralModel)model;
|
||||
booleanLiteral.Value = ValueEditor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
intromat/Intromat/Nodes/Code/BooleanLiteralValue.cs
Normal file
6
intromat/Intromat/Nodes/Code/BooleanLiteralValue.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class BooleanLiteralValue : LiteralValueBase<bool>
|
||||
{
|
||||
}
|
||||
}
|
||||
14
intromat/Intromat/Nodes/Code/FloatLiteralModel.cs
Normal file
14
intromat/Intromat/Nodes/Code/FloatLiteralModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("FloatLiteral", Namespace = _namespace)]
|
||||
public sealed class FloatLiteralModel : LiteralModelBase<float>
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new FloatLiteralNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/Nodes/Code/FloatLiteralNode.cs
Normal file
66
intromat/Intromat/Nodes/Code/FloatLiteralNode.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class FloatLiteralNode : CodeGenNodeViewModel
|
||||
{
|
||||
static FloatLiteralNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<FloatLiteralNode>));
|
||||
}
|
||||
|
||||
public FloatLiteralNode() : base(NodeType.Literal)
|
||||
{
|
||||
Name = "Float";
|
||||
|
||||
Output = new CodeGenOutputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Value",
|
||||
Editor = ValueEditor,
|
||||
Value = ValueEditor.ValueChanged.Select(v => new FloatLiteralValue { Value = v })
|
||||
};
|
||||
Outputs.Add(Output);
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Output.CurrentValue)
|
||||
.Where(v => v != null)
|
||||
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "0.0");
|
||||
}
|
||||
|
||||
public FloatValueEditorViewModel ValueEditor { get; } = new();
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<float>> Output { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new FloatLiteralModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var floatLiteral = (FloatLiteralModel)model;
|
||||
ValueEditor.Value = floatLiteral.Value;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var floatLiteral = (FloatLiteralModel)model;
|
||||
floatLiteral.Value = ValueEditor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
intromat/Intromat/Nodes/Code/FloatLiteralValue.cs
Normal file
9
intromat/Intromat/Nodes/Code/FloatLiteralValue.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class FloatLiteralValue : LiteralValueBase<float>
|
||||
{
|
||||
public override string? PreviewValue => Value.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
19
intromat/Intromat/Nodes/Code/ForLoopModel.cs
Normal file
19
intromat/Intromat/Nodes/Code/ForLoopModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("ForLoop", Namespace = _namespace)]
|
||||
public sealed class ForLoopModel : NodeModelBase
|
||||
{
|
||||
public IntLiteralModel FirstIndex { get; set; } = null!;
|
||||
|
||||
public IntLiteralModel LastIndex { get; set; } = null!;
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new ForLoopNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
114
intromat/Intromat/Nodes/Code/ForLoopNode.cs
Normal file
114
intromat/Intromat/Nodes/Code/ForLoopNode.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class ForLoopNode : ExecutionNodeBase
|
||||
{
|
||||
static ForLoopNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<ForLoopNode>));
|
||||
}
|
||||
|
||||
public ForLoopNode() : base(NodeType.Code)
|
||||
{
|
||||
var boundsGroup = new EndpointGroup("Bounds");
|
||||
|
||||
Name = "For Loop";
|
||||
|
||||
LoopBodyFlow = new CodeGenInputViewModel<IStatement>(EPortType.Execution)
|
||||
{
|
||||
Name = "Loop Body",
|
||||
Group = _executionFlowGroup
|
||||
};
|
||||
Inputs.Add(LoopBodyFlow);
|
||||
|
||||
FirstIndex = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "First Index",
|
||||
Group = boundsGroup,
|
||||
Editor = FirstIndexEditor
|
||||
};
|
||||
Inputs.Add(FirstIndex);
|
||||
|
||||
LastIndex = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Last Index",
|
||||
Group = boundsGroup,
|
||||
Editor = LastIndexEditor
|
||||
};
|
||||
|
||||
Inputs.Add(LastIndex);
|
||||
|
||||
var loopBodyChanged = LoopBodyFlow.ValueChanged.Select(_ => Unit.Default).StartWith(Unit.Default);
|
||||
FlowIn.Value = loopBodyChanged
|
||||
.CombineLatest(FlowOutChanged, FirstIndex.ValueChanged, LastIndex.ValueChanged, (bodyChange, endChange, firstI, lastI) => (BodyChange: bodyChange, EndChange: endChange, FirstI: firstI, LastI: lastI))
|
||||
.Select(v => new ForLoopValue
|
||||
{
|
||||
LoopBody = LoopBodyFlow.Value,
|
||||
FlowOut = FlowOut.Value,
|
||||
LowerBound = v.FirstI,
|
||||
UpperBound = v.LastI
|
||||
});
|
||||
|
||||
CurrentIndex = new CodeGenOutputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Current Index",
|
||||
Value = FlowIn.Value.Select(v => new VariableReference<int> { LocalVariable = ((ForLoopValue)v).CurrentIndex })
|
||||
};
|
||||
Outputs.Add(CurrentIndex);
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
FlowIn.Value.Subscribe(v =>
|
||||
{
|
||||
var forLoop = (ForLoopValue)v;
|
||||
stringPreview.Value = $"[{forLoop.LowerBound?.PreviewValue ?? "0"}..{forLoop.UpperBound?.PreviewValue ?? "0"}]";
|
||||
});
|
||||
}
|
||||
|
||||
public ValueNodeInputViewModel<IStatement> LoopBodyFlow { get; }
|
||||
|
||||
public IntegerExpressionEditorViewModel FirstIndexEditor { get; } = new();
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> FirstIndex { get; }
|
||||
|
||||
public IntegerExpressionEditorViewModel LastIndexEditor { get; } = new();
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> LastIndex { get; }
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<int>> CurrentIndex { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new ForLoopModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var forLoop = (ForLoopModel)model;
|
||||
forLoop.FirstIndex = FirstIndexEditor.CreateModel();
|
||||
forLoop.LastIndex = LastIndexEditor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var forLoop = (ForLoopModel)model;
|
||||
FirstIndexEditor.LoadModel(forLoop.FirstIndex);
|
||||
LastIndexEditor.LoadModel(forLoop.LastIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
intromat/Intromat/Nodes/Code/ForLoopValue.cs
Normal file
48
intromat/Intromat/Nodes/Code/ForLoopValue.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Intromat.Model;
|
||||
using Intromat.Model.Compiler;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class ForLoopValue : ExecutionValueBase
|
||||
{
|
||||
public IStatement? LoopBody { get; set; }
|
||||
|
||||
public ITypedExpression<int>? LowerBound { get; set; }
|
||||
public ITypedExpression<int>? UpperBound { get; set; }
|
||||
|
||||
public InlineVariableDefinition<int> CurrentIndex { get; } = new();
|
||||
|
||||
public override void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
Debug.Assert(UpperBound != null, nameof(UpperBound) + " != null");
|
||||
|
||||
context.EnterNewScope("For loop");
|
||||
|
||||
CurrentIndex.Value = LowerBound;
|
||||
sb.Append("for (");
|
||||
CurrentIndex.Compile(context, sb);
|
||||
sb.Append("; ");
|
||||
sb.Append(CurrentIndex.VariableName);
|
||||
sb.Append(" <= ");
|
||||
UpperBound.Compile(context, sb);
|
||||
sb.Append("; ++");
|
||||
sb.Append(CurrentIndex.VariableName);
|
||||
sb.Append(")\n{\n");
|
||||
LoopBody?.Compile(context, sb);
|
||||
sb.Append("\n}\n");
|
||||
|
||||
context.LeaveScope();
|
||||
|
||||
base.Compile(context, sb);
|
||||
}
|
||||
|
||||
public override void CompileHeader(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
LoopBody?.CompileHeader(context, sb);
|
||||
|
||||
base.CompileHeader(context, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
intromat/Intromat/Nodes/Code/FunctionCall.cs
Normal file
31
intromat/Intromat/Nodes/Code/FunctionCall.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Intromat.Model.Compiler;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class FunctionCall : ExecutionValueBase
|
||||
{
|
||||
public string? FunctionName { get; set; }
|
||||
public List<IExpression> Parameters { get; } = new();
|
||||
|
||||
public override void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
Debug.Assert(FunctionName != null, nameof(FunctionName) + " != null");
|
||||
|
||||
sb.Append($"{FunctionName}(");
|
||||
for (int i = 0, n = Parameters.Count; i < n; ++i)
|
||||
{
|
||||
var p = Parameters[i];
|
||||
p.Compile(context, sb);
|
||||
if (i != n - 1)
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.Append(")\n");
|
||||
|
||||
base.Compile(context, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
intromat/Intromat/Nodes/Code/IntLiteralModel.cs
Normal file
14
intromat/Intromat/Nodes/Code/IntLiteralModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("IntLiteral", Namespace = _namespace)]
|
||||
public sealed class IntLiteralModel : LiteralModelBase<int>
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new IntLiteralNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/Nodes/Code/IntLiteralNode.cs
Normal file
66
intromat/Intromat/Nodes/Code/IntLiteralNode.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class IntLiteralNode : CodeGenNodeViewModel
|
||||
{
|
||||
static IntLiteralNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<IntLiteralNode>));
|
||||
}
|
||||
|
||||
public IntLiteralNode() : base(NodeType.Literal)
|
||||
{
|
||||
Name = "Integer";
|
||||
|
||||
Output = new CodeGenOutputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Value",
|
||||
Editor = ValueEditor,
|
||||
Value = ValueEditor.ValueChanged.Select(v => new IntLiteralValue { Value = v })
|
||||
};
|
||||
Outputs.Add(Output);
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Output.CurrentValue)
|
||||
.Where(v => v != null)
|
||||
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "0");
|
||||
}
|
||||
|
||||
public IntegerValueEditorViewModel ValueEditor { get; } = new();
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<int>> Output { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new IntLiteralModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var intLiteral = (IntLiteralModel)model;
|
||||
ValueEditor.Value = intLiteral.Value;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var intLiteral = (IntLiteralModel)model;
|
||||
intLiteral.Value = ValueEditor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
intromat/Intromat/Nodes/Code/IntLiteralValue.cs
Normal file
25
intromat/Intromat/Nodes/Code/IntLiteralValue.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class IntLiteralValue : LiteralValueBase<int>
|
||||
{
|
||||
public int EvaluateDimension()
|
||||
{
|
||||
var relativeDimension = RelativeSource switch
|
||||
{
|
||||
ERelativeSource.Parent => 1 << ParentValue,
|
||||
ERelativeSource.Input => InputValue,
|
||||
_ => 1
|
||||
};
|
||||
|
||||
var result = Value switch
|
||||
{
|
||||
< 0 => relativeDimension >> -Value,
|
||||
> 0 => relativeDimension << Value,
|
||||
_ => relativeDimension
|
||||
};
|
||||
return Math.Max(1, Math.Min(8192, result));
|
||||
}
|
||||
}
|
||||
}
|
||||
34
intromat/Intromat/Nodes/Code/LiteralModelBase.cs
Normal file
34
intromat/Intromat/Nodes/Code/LiteralModelBase.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Textures;
|
||||
using Intromat.PersistentModel;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public enum ERelativeSource
|
||||
{
|
||||
Custom,
|
||||
Parent,
|
||||
Input
|
||||
}
|
||||
|
||||
[XmlInclude(typeof(IntLiteralModel))]
|
||||
[XmlInclude(typeof(StringLiteralModel))]
|
||||
[XmlInclude(typeof(FloatLiteralModel))]
|
||||
[XmlInclude(typeof(BooleanLiteralModel))]
|
||||
[XmlInclude(typeof(SamplerLiteralModel))]
|
||||
public abstract class LiteralModelBase : NodeModelBase
|
||||
{
|
||||
public ERelativeSource RelativeSource { get; set; }
|
||||
}
|
||||
|
||||
public class LiteralModelEntry
|
||||
{
|
||||
public string Key { get; set; } = null!;
|
||||
public LiteralModelBase Literal { get; set; } = null!;
|
||||
}
|
||||
|
||||
public abstract class LiteralModelBase<T> : LiteralModelBase
|
||||
{
|
||||
public T Value { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
28
intromat/Intromat/Nodes/Code/LiteralValueBase.cs
Normal file
28
intromat/Intromat/Nodes/Code/LiteralValueBase.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Text;
|
||||
using Intromat.Model.Compiler;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public abstract class LiteralValueBase<T> : ITypedExpression<T>
|
||||
{
|
||||
public virtual string? PreviewValue => Value?.ToString();
|
||||
|
||||
public T ParentValue { get; set; } = default!;
|
||||
|
||||
public T InputValue { get; set; } = default!;
|
||||
|
||||
public T Value { get; set; } = default!;
|
||||
|
||||
public ERelativeSource RelativeSource { get; set; }
|
||||
|
||||
public virtual void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
sb.Append(Value);
|
||||
}
|
||||
|
||||
public virtual T Evaluate()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
intromat/Intromat/Nodes/Code/PrintModel.cs
Normal file
17
intromat/Intromat/Nodes/Code/PrintModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("Print", Namespace = _namespace)]
|
||||
public sealed class PrintModel : NodeModelBase
|
||||
{
|
||||
public string? Text { get; set; }
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new PrintNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
75
intromat/Intromat/Nodes/Code/PrintNode.cs
Normal file
75
intromat/Intromat/Nodes/Code/PrintNode.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class PrintNode : ExecutionNodeBase
|
||||
{
|
||||
static PrintNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<PrintNode>));
|
||||
}
|
||||
|
||||
public PrintNode() : base(NodeType.Code)
|
||||
{
|
||||
Name = "Print";
|
||||
|
||||
Text = new CodeGenInputViewModel<ITypedExpression<string>>(EPortType.String)
|
||||
{
|
||||
Name = "Text",
|
||||
Editor = TextEditor
|
||||
};
|
||||
Inputs.Add(Text);
|
||||
|
||||
FlowIn.Value = FlowOutChanged
|
||||
.CombineLatest(Text.ValueChanged, (flowOutChanged, textValueChanged) => (FlowOutChanged: flowOutChanged, StringExpression: textValueChanged))
|
||||
.Select(v => new FunctionCall
|
||||
{
|
||||
FunctionName = "print",
|
||||
FlowOut = FlowOut.Value,
|
||||
Parameters = { v.StringExpression ?? new StringLiteralValue { Value = "" } }
|
||||
});
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Text.Value)
|
||||
.Where(v => v != null)
|
||||
.Subscribe(_ => stringPreview.Value = Text.Value.PreviewValue ?? string.Empty);
|
||||
}
|
||||
|
||||
public StringExpressionEditorViewModel TextEditor { get; } = new();
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<string>> Text { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new PrintModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var print = (PrintModel)model;
|
||||
print.Text = TextEditor.Value is StringLiteralValue text ? text.Value : null;
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var print = (PrintModel)model;
|
||||
if (print.Text != null)
|
||||
TextEditor.Value = new StringLiteralValue { Value = print.Text };
|
||||
}
|
||||
}
|
||||
}
|
||||
14
intromat/Intromat/Nodes/Code/StringFileModel.cs
Normal file
14
intromat/Intromat/Nodes/Code/StringFileModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("StringFile", Namespace = _namespace)]
|
||||
public sealed class StringFileModel : LiteralModelBase<string?>
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new StringFileNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
121
intromat/Intromat/Nodes/Code/StringFileNode.cs
Normal file
121
intromat/Intromat/Nodes/Code/StringFileNode.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.Utilities;
|
||||
using ReactiveUI;
|
||||
using RxFileSystemWatcher;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class StringFileNode : CodeGenNodeViewModel
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<ObservableFileSystemWatcher> _fileWatcher;
|
||||
private readonly ObservableAsPropertyHelper<string> _fullPath;
|
||||
|
||||
static StringFileNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<StringFileNode>));
|
||||
}
|
||||
|
||||
public StringFileNode()
|
||||
: base(NodeType.Literal)
|
||||
{
|
||||
Name = "String (file)";
|
||||
|
||||
Path = new CodeGenInputViewModel<string?>(EPortType.String)
|
||||
{
|
||||
Name = "Path",
|
||||
Editor = PathEditor,
|
||||
};
|
||||
Inputs.Add(Path);
|
||||
|
||||
Contents = new CodeGenOutputViewModel<ITypedExpression<string?>>(EPortType.String)
|
||||
{
|
||||
Name = "Contents",
|
||||
};
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Parent, vm => vm.Path.Value)
|
||||
.Where(pair => pair.Item1 != null && pair.Item2 != null)
|
||||
.Select(pair => System.IO.Path.Combine(((CodeGenNetworkViewModel)pair.Item1).Document.Parent.FullPath, pair.Item2!))
|
||||
.StartWith(string.Empty)
|
||||
.ToProperty(this, vm => vm.FullPath, out _fullPath);
|
||||
|
||||
this.WhenAnyValue(vm => vm.FullPath)
|
||||
.Where(File.Exists)
|
||||
.Select(path => new ObservableFileSystemWatcher(c =>
|
||||
{
|
||||
c.Path = System.IO.Path.GetDirectoryName(path)!;
|
||||
}))
|
||||
.ToProperty(this, vm => vm.FileWatcher, out _fileWatcher);
|
||||
|
||||
Contents.Value = this.WhenAnyValue(vm => vm.FileWatcher)
|
||||
.PairWithPreviousValue()
|
||||
.Select(pair =>
|
||||
{
|
||||
var oldWatcher = pair.OldValue;
|
||||
var newWatcher = pair.NewValue;
|
||||
|
||||
oldWatcher?.Dispose();
|
||||
if (newWatcher != null)
|
||||
{
|
||||
stringPreview.Value = Path.Value!;
|
||||
var result =
|
||||
newWatcher.Renamed.Where(e => e.FullPath == FullPath)
|
||||
.Merge(newWatcher.Changed.Where(e => e.FullPath == FullPath))
|
||||
.Throttle(TimeSpan.FromMilliseconds(100))
|
||||
.Select(_ => new StringLiteralValue() { Value = File.ReadAllText(FullPath) })
|
||||
.StartWith(new StringLiteralValue() { Value = File.ReadAllText(FullPath) });
|
||||
newWatcher.Start();
|
||||
return result;
|
||||
}
|
||||
|
||||
stringPreview.Value = string.Empty;
|
||||
return Observable.Empty<StringLiteralValue>();
|
||||
})
|
||||
.Switch();
|
||||
|
||||
Outputs.Add(Contents);
|
||||
}
|
||||
|
||||
public ObservableFileSystemWatcher FileWatcher => _fileWatcher.Value;
|
||||
|
||||
public string FullPath => _fullPath.Value;
|
||||
|
||||
public StringValueEditorViewModel PathEditor { get; } = new();
|
||||
|
||||
public ValueNodeInputViewModel<string?> Path { get; }
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<string?>> Contents { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new StringFileModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var fileModel = (StringFileModel)model;
|
||||
PathEditor.Value = fileModel.Value;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var fileModel = (StringFileModel)model;
|
||||
fileModel.Value = PathEditor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
intromat/Intromat/Nodes/Code/StringLiteralModel.cs
Normal file
14
intromat/Intromat/Nodes/Code/StringLiteralModel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
[XmlRoot("StringLiteral", Namespace = _namespace)]
|
||||
public sealed class StringLiteralModel : LiteralModelBase<string?>
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new StringLiteralNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
intromat/Intromat/Nodes/Code/StringLiteralNode.cs
Normal file
66
intromat/Intromat/Nodes/Code/StringLiteralNode.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class StringLiteralNode : CodeGenNodeViewModel
|
||||
{
|
||||
static StringLiteralNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<StringLiteralNode>));
|
||||
}
|
||||
|
||||
public StringLiteralNode() : base(NodeType.Literal)
|
||||
{
|
||||
Name = "String";
|
||||
|
||||
Output = new CodeGenOutputViewModel<ITypedExpression<string?>>(EPortType.String)
|
||||
{
|
||||
Name = "Value",
|
||||
Editor = ValueEditor,
|
||||
Value = ValueEditor.ValueChanged.Select(v => new StringLiteralValue { Value = v })
|
||||
};
|
||||
Outputs.Add(Output);
|
||||
|
||||
var stringPreview = new StringPreviewViewModel();
|
||||
Preview = stringPreview;
|
||||
|
||||
this.WhenAnyValue(vm => vm.Output.CurrentValue)
|
||||
.Where(v => v != null)
|
||||
.Subscribe(_ => stringPreview.Value = ((StringLiteralValue)Output.CurrentValue).Value ?? string.Empty);
|
||||
}
|
||||
|
||||
public StringValueEditorViewModel ValueEditor { get; } = new();
|
||||
|
||||
public ValueNodeOutputViewModel<ITypedExpression<string?>> Output { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new StringLiteralModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var textLiteral = (StringLiteralModel)model;
|
||||
ValueEditor.Value = textLiteral.Value;
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var textLiteral = (StringLiteralModel)model;
|
||||
textLiteral.Value = ValueEditor.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
intromat/Intromat/Nodes/Code/StringLiteralValue.cs
Normal file
21
intromat/Intromat/Nodes/Code/StringLiteralValue.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Intromat.Model.Compiler;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class StringLiteralValue : LiteralValueBase<string?>
|
||||
{
|
||||
public override string? PreviewValue => Value;
|
||||
|
||||
public override void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
sb.Append($"\"{Value}\"");
|
||||
}
|
||||
|
||||
public override string Evaluate()
|
||||
{
|
||||
return Value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
intromat/Intromat/Nodes/Code/VariableReference.cs
Normal file
32
intromat/Intromat/Nodes/Code/VariableReference.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Intromat.Model;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Model.Compiler.Error;
|
||||
|
||||
namespace Intromat.Nodes.Code
|
||||
{
|
||||
public class VariableReference<T> : ITypedExpression<T>
|
||||
{
|
||||
public string? PreviewValue => LocalVariable?.VariableName;
|
||||
|
||||
public ITypedVariableDefinition<T>? LocalVariable { get; set; }
|
||||
|
||||
public void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
var localVariable = LocalVariable;
|
||||
Debug.Assert(localVariable != null, nameof(localVariable) + " != null");
|
||||
Debug.Assert(localVariable.VariableName != null, $"{nameof(localVariable)}.{nameof(localVariable.VariableName)} != null");
|
||||
|
||||
if (!context.IsInScope(localVariable))
|
||||
throw new VariableOutOfScopeException(localVariable.VariableName);
|
||||
|
||||
sb.Append(localVariable.VariableName);
|
||||
}
|
||||
public T Evaluate()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
intromat/Intromat/Nodes/DxNodeBase.cs
Normal file
28
intromat/Intromat/Nodes/DxNodeBase.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.ViewModels;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes
|
||||
{
|
||||
public abstract class DxNodeBase : CodeGenNodeViewModel
|
||||
{
|
||||
protected DxNodeBase()
|
||||
: base(NodeType.Literal)
|
||||
{
|
||||
AnyInputChanged = Inputs
|
||||
.Connect()
|
||||
.Filter(input => input is ValueNodeInputViewModelBase)
|
||||
.WhenValueChanged(input => ((ValueNodeInputViewModelBase)input).UnitValueChanged)
|
||||
.SelectMany(i => i!);
|
||||
|
||||
AnyInputChanged.Subscribe(_ => { });
|
||||
Resizable = ResizeOrientation.HorizontalAndVertical;
|
||||
}
|
||||
|
||||
public IObservable<Unit> AnyInputChanged { get; }
|
||||
}
|
||||
}
|
||||
44
intromat/Intromat/Nodes/ExecutionNodeBase.cs
Normal file
44
intromat/Intromat/Nodes/ExecutionNodeBase.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.ViewModels;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes
|
||||
{
|
||||
public abstract class ExecutionNodeBase : CodeGenNodeViewModel
|
||||
{
|
||||
protected readonly EndpointGroup _executionFlowGroup;
|
||||
|
||||
protected ExecutionNodeBase(NodeType type)
|
||||
: base(type)
|
||||
{
|
||||
_executionFlowGroup = new EndpointGroup("Execution Flow");
|
||||
|
||||
FlowOut = new CodeGenInputViewModel<IStatement>(EPortType.Execution)
|
||||
{
|
||||
Name = "Flow",
|
||||
Group = _executionFlowGroup
|
||||
};
|
||||
Inputs.Add(FlowOut);
|
||||
|
||||
FlowOutChanged = FlowOut.ValueChanged.Select(_ => Unit.Default).StartWith(Unit.Default);
|
||||
|
||||
FlowIn = new CodeGenOutputViewModel<IStatement>(EPortType.Execution)
|
||||
{
|
||||
Name = "Flow",
|
||||
Group = _executionFlowGroup
|
||||
};
|
||||
Outputs.Add(FlowIn);
|
||||
}
|
||||
|
||||
public ValueNodeOutputViewModel<IStatement> FlowIn { get; }
|
||||
|
||||
public ValueNodeInputViewModel<IStatement> FlowOut { get; }
|
||||
|
||||
public IObservable<Unit> FlowOutChanged { get; }
|
||||
}
|
||||
}
|
||||
24
intromat/Intromat/Nodes/ExecutionValueBase.cs
Normal file
24
intromat/Intromat/Nodes/ExecutionValueBase.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text;
|
||||
using Intromat.Model.Compiler;
|
||||
|
||||
namespace Intromat.Nodes
|
||||
{
|
||||
public abstract class ExecutionValueBase : IStatement
|
||||
{
|
||||
public IStatement? FlowOut { get; set; }
|
||||
|
||||
public virtual void Compile(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
if (FlowOut != null)
|
||||
{
|
||||
FlowOut.Compile(context, sb);
|
||||
sb.Append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void CompileHeader(CompilerContext context, StringBuilder sb)
|
||||
{
|
||||
FlowOut?.CompileHeader(context, sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
intromat/Intromat/Nodes/MainEntryPointModel.cs
Normal file
15
intromat/Intromat/Nodes/MainEntryPointModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes
|
||||
{
|
||||
[XmlRoot("MainEntryPoint", Namespace = _namespace)]
|
||||
public sealed class MainEntryPointModel : NodeModelBase
|
||||
{
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new MainEntryPointNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
intromat/Intromat/Nodes/MainEntryPointNode.cs
Normal file
36
intromat/Intromat/Nodes/MainEntryPointNode.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using Splat;
|
||||
|
||||
namespace Intromat.Nodes
|
||||
{
|
||||
public class MainEntryPointNode : CodeGenNodeViewModel
|
||||
{
|
||||
static MainEntryPointNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<MainEntryPointNode>));
|
||||
}
|
||||
|
||||
public MainEntryPointNode()
|
||||
: base(NodeType.Special)
|
||||
{
|
||||
Name = "Main";
|
||||
|
||||
OnClickFlow = new CodeGenInputViewModel<IStatement>(EPortType.Execution) { Name = "Entry point" };
|
||||
|
||||
Inputs.Add(OnClickFlow);
|
||||
}
|
||||
|
||||
public ValueNodeInputViewModel<IStatement> OnClickFlow { get; }
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new MainEntryPointModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
intromat/Intromat/Nodes/Meshes/DxMeshModelBase.cs
Normal file
8
intromat/Intromat/Nodes/Meshes/DxMeshModelBase.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Intromat.PersistentModel;
|
||||
|
||||
namespace Intromat.Nodes.Meshes
|
||||
{
|
||||
public abstract class DxMeshModelBase : NodeModelBase
|
||||
{
|
||||
}
|
||||
}
|
||||
97
intromat/Intromat/Nodes/Meshes/DxMeshNodeBase.cs
Normal file
97
intromat/Intromat/Nodes/Meshes/DxMeshNodeBase.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Graphics;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.Nodes.Meshes;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
using Splat;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Device = SharpDX.Direct3D11.Device;
|
||||
|
||||
namespace Intromat.Nodes.Meshs
|
||||
{
|
||||
[CategoryOrder("Mesh", 0)]
|
||||
public abstract class DxMeshNodeBase : DxNodeBase
|
||||
{
|
||||
protected MeshValue? _output;
|
||||
protected bool _resourcesCreated;
|
||||
|
||||
protected DxMeshNodeBase()
|
||||
{
|
||||
var meshGroup = new EndpointGroup("Mesh");
|
||||
Outputs.Add(Output = new CodeGenOutputViewModel<MeshValue?>(EPortType.Mesh)
|
||||
{
|
||||
Name = "Output",
|
||||
Group = meshGroup
|
||||
});
|
||||
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
Output.Value = this
|
||||
.WhenAnyObservable(vm => vm.ParentChanged, vm => vm.AnyInputChanged, (parent, _) => parent)
|
||||
.Where(parent => parent != null)
|
||||
.Throttle(TimeSpan.FromMilliseconds(1))
|
||||
.ObserveOn(dxHost.RenderDispatcher)
|
||||
.Do(_ => { UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext); })
|
||||
.Select(_ => new MeshValue(_output!));
|
||||
|
||||
var dxMeshPreviewViewModel = new DxMeshPreviewViewModel(this, Output.Value);
|
||||
Preview = dxMeshPreviewViewModel;
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_output))]
|
||||
protected bool EnsureBuffers(Device device)
|
||||
{
|
||||
return EnsureMesh(device, ref _output);
|
||||
}
|
||||
|
||||
protected bool EnsureMesh(Device device, [NotNull] ref MeshValue? meshValue)
|
||||
{
|
||||
meshValue = null!;
|
||||
//if (false) // TODO: changed
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void UpdateFrame(Device device, DeviceContext deviceContext)
|
||||
{
|
||||
if (!_resourcesCreated)
|
||||
{
|
||||
_resourcesCreated = true;
|
||||
CreateDeviceResources(device);
|
||||
}
|
||||
EnsureBuffers(device);
|
||||
}
|
||||
|
||||
protected virtual void CreateDeviceResources(Device device)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var meshModel = (DxMeshModelBase)model;
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var meshModel = (DxMeshModelBase)model;
|
||||
}
|
||||
|
||||
public ValueNodeOutputViewModel<MeshValue?> Output { get; }
|
||||
}
|
||||
}
|
||||
22
intromat/Intromat/Nodes/Textures/Blend2DColorModel.cs
Normal file
22
intromat/Intromat/Nodes/Textures/Blend2DColorModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("Blend2DColor", Namespace = _namespace)]
|
||||
public class Blend2DColorModel : DxTextureModelBase
|
||||
{
|
||||
public enum EBlendMode
|
||||
{
|
||||
Multiply
|
||||
}
|
||||
|
||||
public EBlendMode BlendMode { get; set; }
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new Blend2DColorNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
129
intromat/Intromat/Nodes/Textures/Blend2DColorNode.cs
Normal file
129
intromat/Intromat/Nodes/Textures/Blend2DColorNode.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using ReactiveUI;
|
||||
using SharpDX.D3DCompiler;
|
||||
using SharpDX.Direct3D11;
|
||||
using Splat;
|
||||
using Buffer = SharpDX.Direct3D11.Buffer;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public class Blend2DColorNode : DxTextureFilterNodeBase
|
||||
{
|
||||
private Buffer? _constantBuffer;
|
||||
private ComputeShader? _cs;
|
||||
private const string _computeShader = @"cbuffer cb
|
||||
{
|
||||
int m;
|
||||
}
|
||||
|
||||
Texture2D tSource;
|
||||
Texture2D tTarget;
|
||||
SamplerState ss;
|
||||
RWTexture2D<float4> o;
|
||||
[numthreads(16,16,1)]
|
||||
void main(in uint3 i : SV_DispatchThreadID)
|
||||
{
|
||||
float2 dim;
|
||||
o.GetDimensions(dim.x, dim.y);
|
||||
float2 uv = i.xy / dim;
|
||||
float4 src = tSource.SampleLevel(ss, uv, 0);
|
||||
float4 dst = tTarget.SampleLevel(ss, uv, 0);
|
||||
switch (m)
|
||||
{
|
||||
case 0: o[i.xy] = src * dst; break;
|
||||
}
|
||||
}";
|
||||
|
||||
static Blend2DColorNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Blend2DColorNode>));
|
||||
}
|
||||
|
||||
public Blend2DColorNode()
|
||||
{
|
||||
Name = "Blend";
|
||||
|
||||
Inputs.Add(Target = new CodeGenInputViewModel<TextureValue?>(EPortType.Texture)
|
||||
{
|
||||
Name = "Target",
|
||||
Group = Width.Group
|
||||
});
|
||||
|
||||
Inputs.Add(BlendMode = new CodeGenInputViewModel<int>(EPortType.None)
|
||||
{
|
||||
Name = "Blend mode",
|
||||
Group = Width.Group,
|
||||
Editor = BlendModeEditor
|
||||
});
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new Blend2DColorModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var blend2D = (Blend2DColorModel)model;
|
||||
blend2D.BlendMode = (Blend2DColorModel.EBlendMode)BlendModeEditor.Value;
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var blend2D = (Blend2DColorModel)model;
|
||||
BlendModeEditor.Value = (int)blend2D.BlendMode;
|
||||
}
|
||||
|
||||
protected override void CreateDeviceResources(Device device)
|
||||
{
|
||||
base.CreateDeviceResources(device);
|
||||
|
||||
var bufferDesc = new BufferDescription(16, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
|
||||
_constantBuffer = new Buffer(device, bufferDesc);
|
||||
var flags = ShaderFlags.None;
|
||||
#if DEBUG
|
||||
flags |= ShaderFlags.Debug;
|
||||
#endif
|
||||
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
|
||||
}
|
||||
|
||||
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
|
||||
{
|
||||
base.UpdateFrame(device, context);
|
||||
var srvSource = Input.Value?.ShaderResourceView;
|
||||
var srvTarget = Target.Value?.ShaderResourceView;
|
||||
if (srvSource == null || srvTarget == null)
|
||||
return;
|
||||
|
||||
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
|
||||
var ints = new Span<int>((void*)stream.DataPointer, 4);
|
||||
ints[0] = BlendMode.Value;
|
||||
context.UnmapSubresource(_constantBuffer, 0);
|
||||
|
||||
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
|
||||
context.ComputeShader.SetShaderResource(0, srvSource);
|
||||
context.ComputeShader.SetShaderResource(1, srvTarget);
|
||||
context.ComputeShader.SetSampler(0, _samplerState);
|
||||
context.ComputeShader.SetShader(_cs, null, 0);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
|
||||
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
|
||||
context.ComputeShader.SetShaderResource(0, null);
|
||||
context.ComputeShader.SetShaderResource(1, null);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, null);
|
||||
}
|
||||
|
||||
public ValueNodeInputViewModel<TextureValue?> Target { get; }
|
||||
|
||||
public EnumEditorViewModel BlendModeEditor { get; } = new(typeof(Blend2DColorModel.EBlendMode));
|
||||
public ValueNodeInputViewModel<int> BlendMode { get; }
|
||||
}
|
||||
}
|
||||
39
intromat/Intromat/Nodes/Textures/DxTextureFilterNodeBase.cs
Normal file
39
intromat/Intromat/Nodes/Textures/DxTextureFilterNodeBase.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.ViewModels;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using SharpDX.Direct3D11;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public abstract class DxTextureFilterNodeBase : DxTextureNodeBase
|
||||
{
|
||||
protected SamplerState? _samplerState;
|
||||
|
||||
protected DxTextureFilterNodeBase()
|
||||
{
|
||||
Inputs.Add(Input = new CodeGenInputViewModel<TextureValue?>(EPortType.Texture)
|
||||
{
|
||||
Name = "Input",
|
||||
Group = Width.Group
|
||||
});
|
||||
var inputObservable = Input.ValueChanged;
|
||||
WidthEditor.InputValueProvider = inputObservable.Select(input => input?.Width ?? 0);
|
||||
HeightEditor.InputValueProvider = inputObservable.Select(input => input?.Height ?? 0);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_samplerState))]
|
||||
protected override void CreateDeviceResources(Device device)
|
||||
{
|
||||
base.CreateDeviceResources(device);
|
||||
|
||||
var samplerDesc = SamplerStateDescription.Default();
|
||||
samplerDesc.Filter = Filter.MinMagMipLinear;
|
||||
_samplerState = new SamplerState(device, samplerDesc);
|
||||
}
|
||||
|
||||
public ValueNodeInputViewModel<TextureValue?> Input { get; }
|
||||
}
|
||||
}
|
||||
12
intromat/Intromat/Nodes/Textures/DxTextureModelBase.cs
Normal file
12
intromat/Intromat/Nodes/Textures/DxTextureModelBase.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.PersistentModel;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public abstract class DxTextureModelBase : NodeModelBase
|
||||
{
|
||||
public IntLiteralModel Width { get; set; } = null!;
|
||||
|
||||
public IntLiteralModel Height { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
157
intromat/Intromat/Nodes/Textures/DxTextureNodeBase.cs
Normal file
157
intromat/Intromat/Nodes/Textures/DxTextureNodeBase.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Graphics;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.ViewModels.Previews;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
using Splat;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Device = SharpDX.Direct3D11.Device;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[CategoryOrder("Texture", 0)]
|
||||
public abstract class DxTextureNodeBase : DxNodeBase
|
||||
{
|
||||
protected TextureValue? _output;
|
||||
protected Texture2DDescription _texDesc;
|
||||
protected bool _resourcesCreated;
|
||||
|
||||
protected DxTextureNodeBase()
|
||||
{
|
||||
var textureGroup = new EndpointGroup("Texture");
|
||||
var parentObservable = this.WhenAnyObservable(vm => vm.ParentChanged).Select(parent => (CodeGenNetworkViewModel?)parent);
|
||||
Inputs.Add(Width = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.None)
|
||||
{
|
||||
Name = "Width",
|
||||
Group = textureGroup,
|
||||
Editor = WidthEditor = new()
|
||||
{
|
||||
ParentValueProvider = parentObservable.Select(parent => parent?.DefaultWidth ?? -1),
|
||||
MinValue = -16,
|
||||
MaxValue = 16,
|
||||
CustomValue = 0,
|
||||
RelativeSource = ERelativeSource.Parent
|
||||
}
|
||||
});
|
||||
Inputs.Add(Height = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.None)
|
||||
{
|
||||
Name = "Height",
|
||||
Group = textureGroup,
|
||||
Editor = HeightEditor = new()
|
||||
{
|
||||
ParentValueProvider = parentObservable.Select(parent => parent?.DefaultHeight ?? -1),
|
||||
MinValue = -16,
|
||||
MaxValue = 16,
|
||||
CustomValue = 0,
|
||||
RelativeSource = ERelativeSource.Parent
|
||||
}
|
||||
});
|
||||
Outputs.Add(Output = new CodeGenOutputViewModel<TextureValue?>(EPortType.Texture)
|
||||
{
|
||||
Name = "Output",
|
||||
Group = textureGroup
|
||||
});
|
||||
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
Output.Value = this
|
||||
.WhenAnyObservable(vm => vm.ParentChanged, vm => vm.AnyInputChanged, (parent, _) => parent)
|
||||
.Where(parent => parent != null)
|
||||
.Throttle(TimeSpan.FromMilliseconds(1))
|
||||
.ObserveOn(dxHost.RenderDispatcher)
|
||||
.Do(_ => { UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext); })
|
||||
.Select(_ => new TextureValue(_output!));
|
||||
|
||||
var dxTexturePreviewViewModel = new DxTexturePreviewViewModel(this, Output.Value);
|
||||
Preview = dxTexturePreviewViewModel;
|
||||
|
||||
_texDesc = new Texture2DDescription
|
||||
{
|
||||
ArraySize = 1,
|
||||
Format = Format.R16G16B16A16_Float,
|
||||
BindFlags = BindFlags.ShaderResource | BindFlags.UnorderedAccess,
|
||||
Usage = ResourceUsage.Default,
|
||||
SampleDescription = new(1, 0)
|
||||
};
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_output))]
|
||||
protected bool EnsureBuffers(Device device)
|
||||
{
|
||||
return EnsureTexture(device, ref _output);
|
||||
}
|
||||
|
||||
protected bool EnsureTexture(Device device, [NotNull] ref TextureValue? textureValue)
|
||||
{
|
||||
var width = ((IntLiteralValue)Width.Value).EvaluateDimension();
|
||||
var height = ((IntLiteralValue)Height.Value).EvaluateDimension();
|
||||
if (_texDesc.Width != width || _texDesc.Height != height || textureValue == null)
|
||||
{
|
||||
textureValue?.ShaderResourceView?.Resource?.Dispose();
|
||||
textureValue?.ShaderResourceView?.Dispose();
|
||||
textureValue?.UnorderedAccessView?.Dispose();
|
||||
|
||||
_texDesc.Width = width;
|
||||
_texDesc.Height = height;
|
||||
var texture = new Texture2D(device, _texDesc);
|
||||
|
||||
textureValue ??= new TextureValue();
|
||||
textureValue.Width = width;
|
||||
textureValue.Height = height;
|
||||
textureValue.ShaderResourceView = new ShaderResourceView(device, texture);
|
||||
textureValue.UnorderedAccessView = new UnorderedAccessView(device, texture);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual void UpdateFrame(Device device, DeviceContext deviceContext)
|
||||
{
|
||||
if (!_resourcesCreated)
|
||||
{
|
||||
_resourcesCreated = true;
|
||||
CreateDeviceResources(device);
|
||||
}
|
||||
EnsureBuffers(device);
|
||||
}
|
||||
|
||||
protected virtual void CreateDeviceResources(Device device)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var textureModel = (DxTextureModelBase)model;
|
||||
textureModel.Width = WidthEditor.CreateModel();
|
||||
textureModel.Height = HeightEditor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var textureModel = (DxTextureModelBase)model;
|
||||
WidthEditor.LoadModel(textureModel.Width);
|
||||
HeightEditor.LoadModel(textureModel.Height);
|
||||
}
|
||||
|
||||
public DimensionEditorViewModel WidthEditor { get; }
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Width { get; }
|
||||
|
||||
public DimensionEditorViewModel HeightEditor { get; }
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Height { get; }
|
||||
|
||||
public ValueNodeOutputViewModel<TextureValue?> Output { get; }
|
||||
}
|
||||
}
|
||||
23
intromat/Intromat/Nodes/Textures/SamplerDesc.cs
Normal file
23
intromat/Intromat/Nodes/Textures/SamplerDesc.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using ReactiveUI;
|
||||
using SharpDX.Direct3D11;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public class SamplerDesc : ReactiveObject
|
||||
{
|
||||
private Filter _filter = SamplerStateDescription.Default().Filter;
|
||||
private TextureAddressMode _address = SamplerStateDescription.Default().AddressU;
|
||||
|
||||
public Filter Filter
|
||||
{
|
||||
get => _filter;
|
||||
set => this.RaiseAndSetIfChanged(ref _filter, value);
|
||||
}
|
||||
|
||||
public TextureAddressMode Address
|
||||
{
|
||||
get => _address;
|
||||
set => this.RaiseAndSetIfChanged(ref _address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
intromat/Intromat/Nodes/Textures/SamplerLiteralModel.cs
Normal file
16
intromat/Intromat/Nodes/Textures/SamplerLiteralModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.ViewModels;
|
||||
using SharpDX.Direct3D11;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("SamplerLiteral", Namespace = _namespace)]
|
||||
public sealed class SamplerLiteralModel : LiteralModelBase<SamplerDesc>
|
||||
{
|
||||
public override CodeGenNodeViewModel? CreateViewModel()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
intromat/Intromat/Nodes/Textures/SamplerLiteralValue.cs
Normal file
10
intromat/Intromat/Nodes/Textures/SamplerLiteralValue.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using Intromat.Nodes.Code;
|
||||
using SharpDX.Direct3D11;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public class SamplerLiteralValue : LiteralValueBase<SamplerDesc>
|
||||
{
|
||||
public SamplerState? SamplerState { get; set; }
|
||||
}
|
||||
}
|
||||
18
intromat/Intromat/Nodes/Textures/ShaderTextureModel.cs
Normal file
18
intromat/Intromat/Nodes/Textures/ShaderTextureModel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("ShaderTexture", Namespace = _namespace)]
|
||||
public sealed class ShaderTextureModel : DxTextureModelBase
|
||||
{
|
||||
public StringLiteralModel Source { get; set; } = null!;
|
||||
public LiteralModelEntry[] InputValues { get; set; } = null!;
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new ShaderTextureNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
477
intromat/Intromat/Nodes/Textures/ShaderTextureNode.cs
Normal file
477
intromat/Intromat/Nodes/Textures/ShaderTextureNode.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using DynamicData;
|
||||
using Intromat.Graphics;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX;
|
||||
using SharpDX.D3DCompiler;
|
||||
using SharpDX.Direct3D;
|
||||
using SharpDX.Direct3D11;
|
||||
using Splat;
|
||||
using Buffer = SharpDX.Direct3D11.Buffer;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
public class ShaderTextureNode : DxTextureNodeBase
|
||||
{
|
||||
private readonly List<Buffer> _constantBuffers = new();
|
||||
private ComputeShader? _cs;
|
||||
private byte[]? _byteCode;
|
||||
private TextureValue[]? _customOutputs;
|
||||
private readonly List<(NodeInputViewModel, InputDesc)> _customInputs = new();
|
||||
private readonly EndpointGroup _group;
|
||||
private readonly Dictionary<string, object> _inputValues = new();
|
||||
|
||||
public record InputDesc(int ConstantBufferIndex, int Offset, Type VariableType, string Name);
|
||||
|
||||
static ShaderTextureNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<ShaderTextureNode>));
|
||||
}
|
||||
|
||||
public ShaderTextureNode()
|
||||
{
|
||||
Name = "Shader Texture";
|
||||
_group = new EndpointGroup("Shader Texture");
|
||||
Inputs.Add(Source = new CodeGenInputViewModel<ITypedExpression<string>>(EPortType.String)
|
||||
{
|
||||
Name = "Source",
|
||||
Group = _group,
|
||||
Editor = SourceEditor
|
||||
});
|
||||
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
Source.ValueChanged
|
||||
.ObserveOn(dxHost.RenderDispatcher)
|
||||
.Subscribe(_ =>
|
||||
{
|
||||
_resourcesCreated = false;
|
||||
UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext);
|
||||
});
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new ShaderTextureModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var shaderTexture = (ShaderTextureModel)model;
|
||||
shaderTexture.Source = SourceEditor.CreateModel();
|
||||
shaderTexture.InputValues = _customInputs
|
||||
.Where(i => i.Item1.Editor != null)
|
||||
.Select(i => new LiteralModelEntry()
|
||||
{
|
||||
Key = i.Item1.Name,
|
||||
Literal = CreateLiteralModel((dynamic)i.Item1.Editor)
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private TModel CreateLiteralModel<T, TValue, TModel>(ExpressionEditorBaseViewModel<T, TValue, TModel> editor) where TValue : LiteralValueBase<T>, new() where TModel : LiteralModelBase<T>, new()
|
||||
{
|
||||
return editor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var shaderTexture = (ShaderTextureModel)model;
|
||||
SourceEditor.LoadModel(shaderTexture.Source);
|
||||
foreach (var inputValue in shaderTexture.InputValues)
|
||||
{
|
||||
_inputValues[inputValue.Key] = inputValue.Literal;
|
||||
}
|
||||
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
_resourcesCreated = true;
|
||||
CreateDeviceResources(dxHost.Device);
|
||||
}
|
||||
|
||||
protected override void CreateDeviceResources(Device device)
|
||||
{
|
||||
foreach (var cb in _constantBuffers)
|
||||
cb.Dispose();
|
||||
|
||||
_constantBuffers.Clear();
|
||||
|
||||
var flags = ShaderFlags.None;
|
||||
#if DEBUG
|
||||
flags |= ShaderFlags.Debug;
|
||||
#endif
|
||||
var errors = new List<string>();
|
||||
var shader = Source.Value.Evaluate();
|
||||
if (string.IsNullOrEmpty(shader))
|
||||
{
|
||||
errors.Add("Shader is empty");
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var compilationResult = ShaderBytecode.Compile(shader, "main", "cs_5_0", flags);
|
||||
if (compilationResult.HasErrors)
|
||||
{
|
||||
errors.Add(compilationResult.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
_byteCode = compilationResult.Bytecode.Data;
|
||||
var reflection = new ShaderReflection(_byteCode);
|
||||
var newInputs = new List<InputDesc>();
|
||||
var newOutputs = new List<InputDesc>();
|
||||
for (int i = 0; i < reflection.Description.BoundResources; ++i)
|
||||
{
|
||||
var resBindingDesc = reflection.GetResourceBindingDescription(i);
|
||||
switch (resBindingDesc.Type)
|
||||
{
|
||||
case ShaderInputType.ConstantBuffer:
|
||||
{
|
||||
var cb = reflection.GetConstantBuffer(resBindingDesc.Name);
|
||||
var cbIndex = resBindingDesc.BindPoint;
|
||||
var cbSize = cb.Description.Size;
|
||||
var bufferDesc = new BufferDescription(cbSize, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
|
||||
_constantBuffers.Add(new Buffer(device, bufferDesc));
|
||||
for (int j = 0; j < cb.Description.VariableCount; ++j)
|
||||
{
|
||||
var variable = cb.GetVariable(j);
|
||||
var varTypeDesc = variable.GetVariableType()
|
||||
.Description;
|
||||
var type = varTypeDesc.Type switch
|
||||
{
|
||||
ShaderVariableType.Float => typeof(float),
|
||||
ShaderVariableType.Int => typeof(int),
|
||||
ShaderVariableType.Bool => typeof(bool),
|
||||
_ => null
|
||||
};
|
||||
if (type == null)
|
||||
{
|
||||
errors.Add($"Constant buffer {resBindingDesc.Name} at slot {cbIndex} has a variable {variable.Description.Name} with unsupported type {varTypeDesc.Type}");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int k = 0; k < varTypeDesc.ColumnCount; ++k)
|
||||
{
|
||||
var suffix = "" + 'X' + k;
|
||||
var inputName = varTypeDesc.ColumnCount == 1 ? variable.Description.Name : $"{variable.Description.Name}.{suffix}";
|
||||
newInputs.Add(new(cbIndex, variable.Description.StartOffset + k * variable.Description.Size, type, inputName));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ShaderInputType.Texture:
|
||||
{
|
||||
var texName = resBindingDesc.Name;
|
||||
newInputs.Add(new(-1, resBindingDesc.BindPoint, typeof(TextureValue), texName));
|
||||
break;
|
||||
}
|
||||
case ShaderInputType.Sampler:
|
||||
{
|
||||
var texName = resBindingDesc.Name;
|
||||
newInputs.Add(new(-1, resBindingDesc.BindPoint, typeof(SamplerLiteralValue), texName));
|
||||
break;
|
||||
}
|
||||
case ShaderInputType.UnorderedAccessViewRWTyped:
|
||||
{
|
||||
var texName = resBindingDesc.Name;
|
||||
if (resBindingDesc.Dimension != ShaderResourceViewDimension.Texture2D)
|
||||
{
|
||||
errors.Add($"UAV {texName} at slot {resBindingDesc.BindPoint} has unsupported dimension {resBindingDesc.Dimension}");
|
||||
break;
|
||||
}
|
||||
|
||||
newOutputs.Add(new(-1, resBindingDesc.BindPoint, typeof(TextureValue), texName));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
errors.Add($"Resource {resBindingDesc.Name} at slot {resBindingDesc.BindPoint} has unsupported type {resBindingDesc.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newOutputs.Count == 0)
|
||||
{
|
||||
errors.Add("The shader doesn't contain any UAV suitable for the texture output");
|
||||
}
|
||||
|
||||
Inputs.Edit(inputs =>
|
||||
{
|
||||
var maxSamplerStateIndex = 0;
|
||||
for (var i = _customInputs.Count - 1; i >= 0; --i)
|
||||
{
|
||||
var existingInput = _customInputs[i];
|
||||
if (newInputs.All(inputDesc => inputDesc.Name != existingInput.Item1.Name))
|
||||
{
|
||||
inputs.Remove(existingInput.Item1);
|
||||
_customInputs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var newInputDesc in newInputs)
|
||||
{
|
||||
if (_customInputs.Any(i => i.Item2 == newInputDesc))
|
||||
continue;
|
||||
|
||||
NodeInputViewModel newInput;
|
||||
if (newInputDesc.VariableType == typeof(float))
|
||||
{
|
||||
newInput = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = newInputDesc.Name,
|
||||
Group = _group,
|
||||
Editor = new FloatExpressionEditorViewModel { MaxValue = 1 }
|
||||
};
|
||||
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is FloatLiteralModel model)
|
||||
((FloatExpressionEditorViewModel)newInput.Editor).LoadModel(model);
|
||||
}
|
||||
else if (newInputDesc.VariableType == typeof(int))
|
||||
{
|
||||
newInput = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = newInputDesc.Name,
|
||||
Group = _group,
|
||||
Editor = new IntegerExpressionEditorViewModel { MaxValue = 100 }
|
||||
};
|
||||
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is IntLiteralModel model)
|
||||
((IntegerExpressionEditorViewModel)newInput.Editor).LoadModel(model);
|
||||
}
|
||||
else if (newInputDesc.VariableType == typeof(bool))
|
||||
{
|
||||
newInput = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
|
||||
{
|
||||
Name = newInputDesc.Name,
|
||||
Group = _group,
|
||||
Editor = new BooleanExpressionEditorViewModel()
|
||||
};
|
||||
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is BooleanLiteralModel model)
|
||||
((BooleanExpressionEditorViewModel)newInput.Editor).LoadModel(model);
|
||||
}
|
||||
else if (newInputDesc.VariableType == typeof(TextureValue))
|
||||
{
|
||||
newInput = new CodeGenInputViewModel<TextureValue>(EPortType.Texture) { Name = newInputDesc.Name };
|
||||
if (newInputDesc.Offset == 0)
|
||||
{
|
||||
var inputObservable = ((CodeGenInputViewModel<TextureValue>)newInput).ValueChanged;
|
||||
WidthEditor.InputValueProvider = inputObservable.Select(input => input?.Width ?? 0);
|
||||
HeightEditor.InputValueProvider = inputObservable.Select(input => input?.Height ?? 0);
|
||||
}
|
||||
}
|
||||
else if (newInputDesc.VariableType == typeof(SamplerLiteralValue))
|
||||
{
|
||||
maxSamplerStateIndex = Math.Max(maxSamplerStateIndex, newInputDesc.Offset);
|
||||
newInput = new CodeGenInputViewModel<ITypedExpression<SamplerDesc>>(EPortType.None)
|
||||
{
|
||||
Name = newInputDesc.Name,
|
||||
Group = _group,
|
||||
Editor = new SamplerEditorViewModel { CustomValue = new SamplerDesc() }
|
||||
};
|
||||
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is SamplerLiteralModel model)
|
||||
((SamplerEditorViewModel)newInput.Editor).LoadModel(model);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_customInputs.Add((newInput, newInputDesc));
|
||||
inputs.Add(newInput);
|
||||
}
|
||||
});
|
||||
|
||||
Outputs.Edit(outputs =>
|
||||
{
|
||||
if (outputs.Count > newOutputs.Count)
|
||||
{
|
||||
if (newOutputs.Count == 0)
|
||||
{
|
||||
if (outputs.Count > 1)
|
||||
outputs.RemoveRange(1, outputs.Count - 1);
|
||||
outputs[0]
|
||||
.Name = "Output";
|
||||
}
|
||||
else
|
||||
{
|
||||
outputs.RemoveRange(newOutputs.Count, outputs.Count - newOutputs.Count);
|
||||
}
|
||||
}
|
||||
|
||||
var outputIndex = 0;
|
||||
if (outputIndex < newOutputs.Count)
|
||||
for (; outputIndex < outputs.Count; ++outputIndex)
|
||||
outputs[outputIndex]
|
||||
.Name = newOutputs[outputIndex]
|
||||
.Name;
|
||||
|
||||
for (; outputIndex < newOutputs.Count; ++outputIndex)
|
||||
{
|
||||
var newOutputDesc = newOutputs[outputIndex];
|
||||
NodeOutputViewModel newOutput;
|
||||
if (newOutputDesc.VariableType == typeof(TextureValue))
|
||||
newOutput = new CodeGenOutputViewModel<TextureValue>(EPortType.Texture) { Name = newOutputDesc.Name };
|
||||
else
|
||||
continue;
|
||||
|
||||
outputs.Add(newOutput);
|
||||
}
|
||||
|
||||
_customOutputs = new TextureValue[outputs.Count];
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (CompilationException e)
|
||||
{
|
||||
errors.Add(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_cs?.Dispose();
|
||||
_cs = errors.Count == 0 ? new ComputeShader(device, _byteCode) : null;
|
||||
|
||||
Errors.Edit(e =>
|
||||
{
|
||||
e.Clear();
|
||||
e.AddRange(errors);
|
||||
});
|
||||
}
|
||||
|
||||
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
|
||||
{
|
||||
if (!_resourcesCreated)
|
||||
{
|
||||
_resourcesCreated = true;
|
||||
var dxHost = Locator.Current.GetService<DxHost>()!;
|
||||
dxHost.MainDispatcher!.Invoke(() => CreateDeviceResources(device));
|
||||
}
|
||||
|
||||
EnsureBuffers(device);
|
||||
if (_customOutputs == null || _cs == null)
|
||||
return;
|
||||
|
||||
_customOutputs[0] = _output;
|
||||
for (int i = 1; i < Outputs.Count; ++i)
|
||||
{
|
||||
EnsureTexture(device, ref _customOutputs![i]);
|
||||
}
|
||||
|
||||
var cbData = _constantBuffers.Select(cb =>
|
||||
{
|
||||
context.MapSubresource(cb, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
|
||||
return stream.DataPointer;
|
||||
}).ToArray();
|
||||
|
||||
int maxSrvIndex = 0;
|
||||
foreach (var input in _customInputs)
|
||||
{
|
||||
var desc = input.Item2;
|
||||
if (desc.ConstantBufferIndex >= 0)
|
||||
{
|
||||
var dataPtr = cbData[desc.ConstantBufferIndex] + desc.Offset;
|
||||
if (desc.VariableType == typeof(float))
|
||||
{
|
||||
_inputValues[input.Item1.Name] = ((FloatExpressionEditorViewModel)input.Item1.Editor).CreateModel();
|
||||
*(float*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<float>>)input.Item1).Value.Evaluate();
|
||||
}
|
||||
else if (desc.VariableType == typeof(int))
|
||||
{
|
||||
_inputValues[input.Item1.Name] = ((IntegerExpressionEditorViewModel)input.Item1.Editor).CreateModel();
|
||||
*(int*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<int>>)input.Item1).Value.Evaluate();
|
||||
}
|
||||
else if (desc.VariableType == typeof(bool))
|
||||
{
|
||||
_inputValues[input.Item1.Name] = ((BooleanExpressionEditorViewModel)input.Item1.Editor).CreateModel();
|
||||
*(bool*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<bool>>)input.Item1).Value.Evaluate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (desc.VariableType == typeof(TextureValue))
|
||||
{
|
||||
maxSrvIndex = Math.Max(maxSrvIndex, desc.Offset);
|
||||
context.ComputeShader.SetShaderResource(desc.Offset, ((CodeGenInputViewModel<TextureValue>)input.Item1).Value?.ShaderResourceView);
|
||||
}
|
||||
else if (desc.VariableType == typeof(SamplerLiteralValue))
|
||||
{
|
||||
_inputValues[input.Item1.Name] = ((SamplerEditorViewModel)input.Item1.Editor).CreateModel();
|
||||
var samplerValue = (SamplerLiteralValue)((CodeGenInputViewModel<ITypedExpression<SamplerDesc>>)input.Item1).Value;
|
||||
EnsureSamplerState(device, ref samplerValue);
|
||||
context.ComputeShader.SetSampler(desc.Offset, samplerValue.SamplerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _customOutputs!.Length; i++)
|
||||
{
|
||||
var output = _customOutputs![i];
|
||||
context.ComputeShader.SetUnorderedAccessView(i, output.UnorderedAccessView);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _constantBuffers.Count; ++i)
|
||||
{
|
||||
context.UnmapSubresource(_constantBuffers[i], 0);
|
||||
context.ComputeShader.SetConstantBuffer(i, _constantBuffers[i]);
|
||||
}
|
||||
|
||||
context.ComputeShader.SetShader(_cs, null, 0);
|
||||
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
|
||||
|
||||
for (var i = 0; i < _customOutputs!.Length; i++)
|
||||
{
|
||||
context.ComputeShader.SetUnorderedAccessView(i, null);
|
||||
}
|
||||
|
||||
for (var i = 0; i <= maxSrvIndex; i++)
|
||||
{
|
||||
context.ComputeShader.SetShaderResource(i, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSamplerState(Device device, ref SamplerLiteralValue samplerLiteralValue)
|
||||
{
|
||||
if (samplerLiteralValue.SamplerState == null || samplerLiteralValue.SamplerState.Description.AddressU != samplerLiteralValue.Value.Address || samplerLiteralValue.SamplerState.Description.Filter != samplerLiteralValue.Value.Filter)
|
||||
{
|
||||
var desc = SamplerStateDescription.Default();
|
||||
desc.AddressU = desc.AddressV = desc.AddressW = samplerLiteralValue.Value.Address;
|
||||
desc.Filter = samplerLiteralValue.Value.Filter;
|
||||
samplerLiteralValue.SamplerState = new SamplerState(device, desc);
|
||||
}
|
||||
}
|
||||
|
||||
public ISourceList<string> Errors { get; } = new SourceList<string>();
|
||||
public CodeGenInputViewModel<ITypedExpression<string>> Source { get; }
|
||||
|
||||
public StringExpressionEditorViewModel SourceEditor { get; } = new()
|
||||
{
|
||||
CustomValue = @"
|
||||
cbuffer cb
|
||||
{
|
||||
float f;
|
||||
}
|
||||
Texture2D t0;
|
||||
SamplerState ss;
|
||||
RWTexture2D<float4> o;
|
||||
[numthreads(16,16,1)]
|
||||
void main(uint3 i : SV_DispatchThreadID)
|
||||
{
|
||||
float2 s;
|
||||
o.GetDimensions(s.x, s.y);
|
||||
float2 uv = i.xy/s;
|
||||
float4 c = t0.SampleLevel(ss, uv, 0);
|
||||
o[i.xy] = float4(c.xyz, 1);
|
||||
}"
|
||||
};
|
||||
}
|
||||
}
|
||||
34
intromat/Intromat/Nodes/Textures/Shape2DGrayscaleModel.cs
Normal file
34
intromat/Intromat/Nodes/Textures/Shape2DGrayscaleModel.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("Shape2DGrayscale", Namespace = _namespace)]
|
||||
public class Shape2DGrayscaleModel : DxTextureModelBase
|
||||
{
|
||||
public enum EPattern
|
||||
{
|
||||
Square
|
||||
}
|
||||
|
||||
public IntLiteralModel Tiling { get; set; } = default!;
|
||||
|
||||
public EPattern Pattern { get; set; }
|
||||
|
||||
public FloatLiteralModel Scale { get; set; } = default!;
|
||||
|
||||
public FloatLiteralModel SizeX { get; set; } = default!;
|
||||
|
||||
public FloatLiteralModel SizeY { get; set; } = default!;
|
||||
|
||||
public FloatLiteralModel Angle { get; set; } = default!;
|
||||
|
||||
public BooleanLiteralModel Rotate45 { get; set; } = default!;
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new Shape2DGrayscaleNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
213
intromat/Intromat/Nodes/Textures/Shape2DGrayscaleNode.cs
Normal file
213
intromat/Intromat/Nodes/Textures/Shape2DGrayscaleNode.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX.D3DCompiler;
|
||||
using SharpDX.Direct3D11;
|
||||
using Splat;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Buffer = SharpDX.Direct3D11.Buffer;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[CategoryOrder("Shape 2D", 1)]
|
||||
public class Shape2DGrayscaleNode : DxTextureNodeBase
|
||||
{
|
||||
private Buffer? _constantBuffer;
|
||||
private ComputeShader? _cs;
|
||||
private const string _computeShader = @"cbuffer cb
|
||||
{
|
||||
int t;
|
||||
int p;
|
||||
float s;
|
||||
float sx;
|
||||
float sy;
|
||||
float a;
|
||||
int r;
|
||||
}
|
||||
|
||||
float Square(float2 uv)
|
||||
{
|
||||
return step(uv.x, sx) * step(-sx, uv.x) * step(uv.y, sy) * step(-sy, uv.y);
|
||||
}
|
||||
|
||||
RWTexture2D<float> o;
|
||||
[numthreads(16,16,1)]
|
||||
void main(in uint3 i : SV_DispatchThreadID)
|
||||
{
|
||||
float sa, ca;
|
||||
sincos(a, sa, ca);
|
||||
float2 dim;
|
||||
o.GetDimensions(dim.x, dim.y);
|
||||
float2 uv = i.xy / dim;
|
||||
if (r > 0)
|
||||
{
|
||||
float f = sqrt(2)/2;
|
||||
uv = uv * 2 - 1;
|
||||
uv = mul(uv, float2x2(f, -f, f, f));
|
||||
uv = uv * .5 + .5;
|
||||
}
|
||||
uv *= t;
|
||||
uv = frac(uv);
|
||||
uv = uv * 2 - 1;
|
||||
uv /= s;
|
||||
uv = mul(uv, float2x2(ca, -sa, sa, ca));
|
||||
float z = 0;
|
||||
switch (p)
|
||||
{
|
||||
case 0: z = Square(uv); break;
|
||||
}
|
||||
o[i.xy] = z;
|
||||
}";
|
||||
|
||||
static Shape2DGrayscaleNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Shape2DGrayscaleNode>));
|
||||
}
|
||||
|
||||
public Shape2DGrayscaleNode()
|
||||
{
|
||||
Name = "Shape 2D";
|
||||
var group = new EndpointGroup("Shape 2D");
|
||||
Inputs.Add(Tiling = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Tiling",
|
||||
Group = group,
|
||||
Editor = TilingEditor
|
||||
});
|
||||
Inputs.Add(Pattern = new CodeGenInputViewModel<int>(EPortType.None)
|
||||
{
|
||||
Name = "Pattern",
|
||||
Group = group,
|
||||
Editor = PatternEditor
|
||||
});
|
||||
Inputs.Add(Scale = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Scale",
|
||||
Group = group,
|
||||
Editor = ScaleEditor
|
||||
});
|
||||
Inputs.Add(SizeX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Size X",
|
||||
Group = group,
|
||||
Editor = SizeXEditor
|
||||
});
|
||||
Inputs.Add(SizeY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Size Y",
|
||||
Group = group,
|
||||
Editor = SizeYEditor
|
||||
});
|
||||
Inputs.Add(Angle = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Angle",
|
||||
Group = group,
|
||||
Editor = AngleEditor
|
||||
});
|
||||
Inputs.Add(Rotate45 = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Float)
|
||||
{
|
||||
Name = "Rotate 45°",
|
||||
Group = group,
|
||||
Editor = Rotate45Editor
|
||||
});
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new Shape2DGrayscaleModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var shape2D = (Shape2DGrayscaleModel)model;
|
||||
shape2D.Tiling = TilingEditor.CreateModel();
|
||||
shape2D.Pattern = (Shape2DGrayscaleModel.EPattern)PatternEditor.Value;
|
||||
shape2D.Scale = ScaleEditor.CreateModel();
|
||||
shape2D.SizeX = SizeXEditor.CreateModel();
|
||||
shape2D.SizeY = SizeYEditor.CreateModel();
|
||||
shape2D.Angle = AngleEditor.CreateModel();
|
||||
shape2D.Rotate45 = Rotate45Editor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var shape2D = (Shape2DGrayscaleModel)model;
|
||||
TilingEditor.LoadModel(shape2D.Tiling);
|
||||
PatternEditor.Value = (int)shape2D.Pattern;
|
||||
ScaleEditor.LoadModel(shape2D.Scale);
|
||||
SizeXEditor.LoadModel(shape2D.SizeX);
|
||||
SizeYEditor.LoadModel(shape2D.SizeY);
|
||||
AngleEditor.LoadModel(shape2D.Angle);
|
||||
Rotate45Editor.LoadModel(shape2D.Rotate45);
|
||||
}
|
||||
|
||||
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
|
||||
{
|
||||
base.UpdateFrame(device, context);
|
||||
|
||||
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
|
||||
var floats = new Span<float>((void*)stream.DataPointer, 8);
|
||||
var ints = new Span<int>((void*)stream.DataPointer, 8);
|
||||
ints[0] = Tiling.Value.Evaluate();
|
||||
ints[1] = Pattern.Value;
|
||||
floats[2] = Scale.Value.Evaluate();
|
||||
floats[3] = SizeX.Value.Evaluate();
|
||||
floats[4] = SizeY.Value.Evaluate();
|
||||
floats[5] = Angle.Value.Evaluate();
|
||||
ints[6] = Rotate45.Value.Evaluate() ? 1 : 0;
|
||||
context.UnmapSubresource(_constantBuffer, 0);
|
||||
|
||||
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
|
||||
context.ComputeShader.SetShader(_cs, null, 0);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
|
||||
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, null);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_constantBuffer))]
|
||||
[MemberNotNull(nameof(_cs))]
|
||||
protected override void CreateDeviceResources(Device device)
|
||||
{
|
||||
base.CreateDeviceResources(device);
|
||||
|
||||
var bufferDesc = new BufferDescription(32, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
|
||||
_constantBuffer = new Buffer(device, bufferDesc);
|
||||
var flags = ShaderFlags.None;
|
||||
#if DEBUG
|
||||
flags |= ShaderFlags.Debug;
|
||||
#endif
|
||||
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
|
||||
}
|
||||
|
||||
public IntegerExpressionEditorViewModel TilingEditor { get; } = new() { CustomValue = 1, MinValue = 1, MaxValue = 16 };
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Tiling { get; }
|
||||
|
||||
public EnumEditorViewModel PatternEditor { get; } = new(typeof(Shape2DGrayscaleModel.EPattern));
|
||||
public ValueNodeInputViewModel<int> Pattern { get; }
|
||||
|
||||
public FloatExpressionEditorViewModel ScaleEditor { get; } = new() { CustomValue = 1, MaxValue = 1 };
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> Scale { get; }
|
||||
|
||||
public FloatExpressionEditorViewModel SizeXEditor { get; } = new() { CustomValue = .5f, MaxValue = 1 };
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> SizeX { get; }
|
||||
|
||||
public FloatExpressionEditorViewModel SizeYEditor { get; } = new() { CustomValue = .5f, MaxValue = 1 };
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> SizeY { get; }
|
||||
|
||||
public FloatExpressionEditorViewModel AngleEditor { get; } = new() { MaxValue = (float)(2 * Math.PI) };
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> Angle { get; }
|
||||
|
||||
public BooleanExpressionEditorViewModel Rotate45Editor { get; } = new();
|
||||
public ValueNodeInputViewModel<ITypedExpression<bool>> Rotate45 { get; }
|
||||
}
|
||||
}
|
||||
19
intromat/Intromat/Nodes/Textures/SolidColorModel.cs
Normal file
19
intromat/Intromat/Nodes/Textures/SolidColorModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("SolidColor", Namespace = _namespace)]
|
||||
public sealed class SolidColorModel : DxTextureModelBase
|
||||
{
|
||||
public IntLiteralModel Red { get; set; } = null!;
|
||||
public IntLiteralModel Green { get; set; } = null!;
|
||||
public IntLiteralModel Blue { get; set; } = null!;
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new SolidColorNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
intromat/Intromat/Nodes/Textures/SolidColorNode.cs
Normal file
90
intromat/Intromat/Nodes/Textures/SolidColorNode.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.Mathematics.Interop;
|
||||
using Splat;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[CategoryOrder("Solid Color", 1)]
|
||||
public class SolidColorNode : DxTextureNodeBase
|
||||
{
|
||||
static SolidColorNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<SolidColorNode>));
|
||||
}
|
||||
|
||||
public SolidColorNode()
|
||||
{
|
||||
Name = "Solid Color";
|
||||
var group = new EndpointGroup("SolidColor");
|
||||
Inputs.Add(Red = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Input",
|
||||
Group = group,
|
||||
Editor = RedEditor
|
||||
});
|
||||
Inputs.Add(Green = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Green",
|
||||
Group = group,
|
||||
Editor = GreenEditor
|
||||
});
|
||||
Inputs.Add(Blue = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
|
||||
{
|
||||
Name = "Blue",
|
||||
Group = group,
|
||||
Editor = BlueEditor
|
||||
});
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new SolidColorModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var solidColor = (SolidColorModel)model;
|
||||
solidColor.Red = RedEditor.CreateModel();
|
||||
solidColor.Green = GreenEditor.CreateModel();
|
||||
solidColor.Blue = BlueEditor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var solidColor = (SolidColorModel)model;
|
||||
RedEditor.LoadModel(solidColor.Red);
|
||||
GreenEditor.LoadModel(solidColor.Green);
|
||||
BlueEditor.LoadModel(solidColor.Blue);
|
||||
}
|
||||
|
||||
protected override void UpdateFrame(Device device, DeviceContext context)
|
||||
{
|
||||
base.UpdateFrame(device, context);
|
||||
float r = Red.Value.Evaluate() / 255.0f;
|
||||
float g = Green.Value.Evaluate() / 255.0f;
|
||||
float b = Blue.Value.Evaluate() / 255.0f;
|
||||
context.ClearUnorderedAccessView(_output!.UnorderedAccessView, new RawVector4(r, g, b, 1.0f));
|
||||
}
|
||||
|
||||
public IntegerExpressionEditorViewModel RedEditor { get; } = new() { MinValue = 0, MaxValue = 255};
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Red { get; }
|
||||
|
||||
public IntegerExpressionEditorViewModel GreenEditor { get; } = new() { MinValue = 0, MaxValue = 255};
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Green { get; }
|
||||
|
||||
public IntegerExpressionEditorViewModel BlueEditor { get; } = new() { MinValue = 0, MaxValue = 255};
|
||||
public ValueNodeInputViewModel<ITypedExpression<int>> Blue { get; }
|
||||
}
|
||||
}
|
||||
22
intromat/Intromat/Nodes/Textures/Transform2DColorModel.cs
Normal file
22
intromat/Intromat/Nodes/Textures/Transform2DColorModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Xml.Serialization;
|
||||
using Intromat.Nodes.Code;
|
||||
using Intromat.ViewModels;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[XmlRoot("Transform2DColor", Namespace = _namespace)]
|
||||
public sealed class Transform2DColorModel : DxTextureModelBase
|
||||
{
|
||||
public BooleanLiteralModel Clamp { get; set; } = default!;
|
||||
public FloatLiteralModel ScaleX { get; set; } = default!;
|
||||
public FloatLiteralModel ScaleY { get; set; } = default!;
|
||||
public FloatLiteralModel OffsetX { get; set; } = default!;
|
||||
public FloatLiteralModel OffsetY { get; set; } = default!;
|
||||
public FloatLiteralModel Rotate { get; set; } = default!;
|
||||
|
||||
public override CodeGenNodeViewModel CreateViewModel()
|
||||
{
|
||||
return new Transform2DColorNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
190
intromat/Intromat/Nodes/Textures/Transform2DColorNode.cs
Normal file
190
intromat/Intromat/Nodes/Textures/Transform2DColorNode.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using DynamicData;
|
||||
using Intromat.Model.Compiler;
|
||||
using Intromat.PersistentModel;
|
||||
using Intromat.ViewModels;
|
||||
using Intromat.ViewModels.Editors;
|
||||
using Intromat.Views;
|
||||
using NodeNetwork.Toolkit.ValueNode;
|
||||
using NodeNetwork.ViewModels;
|
||||
using ReactiveUI;
|
||||
using SharpDX.D3DCompiler;
|
||||
using SharpDX.Direct3D11;
|
||||
using Splat;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
using Buffer = SharpDX.Direct3D11.Buffer;
|
||||
|
||||
namespace Intromat.Nodes.Textures
|
||||
{
|
||||
[CategoryOrder("Transform 2D", 1)]
|
||||
public class Transform2DColorNode : DxTextureFilterNodeBase
|
||||
{
|
||||
private Buffer? _constantBuffer;
|
||||
private ComputeShader? _cs;
|
||||
private const string _computeShader = @"cbuffer cb
|
||||
{
|
||||
float2 s;
|
||||
float2 p;
|
||||
float r;
|
||||
int c;
|
||||
}
|
||||
|
||||
Texture2D t;
|
||||
SamplerState ss;
|
||||
RWTexture2D<float4> o;
|
||||
[numthreads(16,16,1)]
|
||||
void main(in uint3 i : SV_DispatchThreadID)
|
||||
{
|
||||
float sa, ca;
|
||||
sincos(r, sa, ca);
|
||||
float2 dim;
|
||||
o.GetDimensions(dim.x, dim.y);
|
||||
float2 uv = i.xy / dim;
|
||||
uv -= p;
|
||||
uv = uv * 2 - 1;
|
||||
uv = mul(uv, float2x2(ca, -sa, sa, ca));
|
||||
uv /= s;
|
||||
uv = uv * .5 + .5;
|
||||
if (c == 0) uv = frac(uv);
|
||||
o[i.xy] = t.SampleLevel(ss, uv, 0);
|
||||
}";
|
||||
|
||||
static Transform2DColorNode()
|
||||
{
|
||||
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Transform2DColorNode>));
|
||||
}
|
||||
|
||||
public Transform2DColorNode()
|
||||
{
|
||||
Name = "Transform 2D";
|
||||
var group = new EndpointGroup("Transform 2D");
|
||||
Inputs.Add(Clamp = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
|
||||
{
|
||||
Name = "Clamp",
|
||||
Group = group,
|
||||
Editor = ClampEditor
|
||||
});
|
||||
Inputs.Add(ScaleX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Scale X",
|
||||
Group = group,
|
||||
Editor = ScaleXEditor
|
||||
});
|
||||
Inputs.Add(ScaleY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Scale Y",
|
||||
Group = group,
|
||||
Editor = ScaleYEditor
|
||||
});
|
||||
Inputs.Add(OffsetX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Offset X",
|
||||
Group = group,
|
||||
Editor = OffsetXEditor
|
||||
});
|
||||
Inputs.Add(OffsetY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Offset Y",
|
||||
Group = group,
|
||||
Editor = OffsetYEditor
|
||||
});
|
||||
Inputs.Add(Rotate = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
|
||||
{
|
||||
Name = "Rotate",
|
||||
Group = group,
|
||||
Editor = RotateEditor
|
||||
});
|
||||
}
|
||||
|
||||
public override NodeModelBase CreateModel()
|
||||
{
|
||||
return new Transform2DColorModel();
|
||||
}
|
||||
|
||||
public override void SaveModel(NodeModelBase model)
|
||||
{
|
||||
base.SaveModel(model);
|
||||
var transform2DColor = (Transform2DColorModel)model;
|
||||
transform2DColor.ScaleX = ScaleXEditor.CreateModel();
|
||||
transform2DColor.ScaleY = ScaleYEditor.CreateModel();
|
||||
transform2DColor.OffsetX = OffsetXEditor.CreateModel();
|
||||
transform2DColor.OffsetY = OffsetYEditor.CreateModel();
|
||||
transform2DColor.Rotate = RotateEditor.CreateModel();
|
||||
transform2DColor.Clamp = ClampEditor.CreateModel();
|
||||
}
|
||||
|
||||
public override void LoadModel(NodeModelBase model)
|
||||
{
|
||||
base.LoadModel(model);
|
||||
var transform2DColor = (Transform2DColorModel)model;
|
||||
ScaleXEditor.LoadModel(transform2DColor.ScaleX);
|
||||
ScaleYEditor.LoadModel(transform2DColor.ScaleY);
|
||||
OffsetXEditor.LoadModel(transform2DColor.OffsetX);
|
||||
OffsetYEditor.LoadModel(transform2DColor.OffsetY);
|
||||
RotateEditor.LoadModel(transform2DColor.Rotate);
|
||||
ClampEditor.LoadModel(transform2DColor.Clamp);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_constantBuffer))]
|
||||
[MemberNotNull(nameof(_cs))]
|
||||
protected override void CreateDeviceResources(Device device)
|
||||
{
|
||||
base.CreateDeviceResources(device);
|
||||
|
||||
var bufferDesc = new BufferDescription(32, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
|
||||
_constantBuffer = new Buffer(device, bufferDesc);
|
||||
var flags = ShaderFlags.None;
|
||||
#if DEBUG
|
||||
flags |= ShaderFlags.Debug;
|
||||
#endif
|
||||
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
|
||||
}
|
||||
|
||||
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
|
||||
{
|
||||
base.UpdateFrame(device, context);
|
||||
var srv = Input.Value?.ShaderResourceView;
|
||||
if (srv == null)
|
||||
return;
|
||||
|
||||
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
|
||||
var floats = new Span<float>((void*)stream.DataPointer, 8);
|
||||
var ints = new Span<int>((void*)stream.DataPointer, 8);
|
||||
floats[0] = ScaleX.Value.Evaluate();
|
||||
floats[1] = ScaleY.Value.Evaluate();
|
||||
floats[2] = OffsetX.Value.Evaluate();
|
||||
floats[3] = OffsetY.Value.Evaluate();
|
||||
floats[4] = Rotate.Value.Evaluate();
|
||||
ints[5] = Clamp.Value.Evaluate() ? 1 : 0;
|
||||
context.UnmapSubresource(_constantBuffer, 0);
|
||||
|
||||
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
|
||||
context.ComputeShader.SetShaderResource(0, srv);
|
||||
context.ComputeShader.SetSampler(0, _samplerState);
|
||||
context.ComputeShader.SetShader(_cs, null, 0);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
|
||||
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
|
||||
context.ComputeShader.SetShaderResource(0, null);
|
||||
context.ComputeShader.SetUnorderedAccessView(0, null);
|
||||
}
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<bool>> Clamp { get; }
|
||||
public BooleanExpressionEditorViewModel ClampEditor { get; } = new();
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> ScaleX { get; }
|
||||
public FloatExpressionEditorViewModel ScaleXEditor { get; } = new() { CustomValue = 1, MaxValue = 4 };
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> ScaleY { get; }
|
||||
public FloatExpressionEditorViewModel ScaleYEditor { get; } = new() { CustomValue = 1, MaxValue = 4 };
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> OffsetX { get; }
|
||||
public FloatExpressionEditorViewModel OffsetXEditor { get; } = new() { MinValue = -1, MaxValue = 1 };
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> OffsetY { get; }
|
||||
public FloatExpressionEditorViewModel OffsetYEditor { get; } = new() { MinValue = -1, MaxValue = 1 };
|
||||
|
||||
public ValueNodeInputViewModel<ITypedExpression<float>> Rotate { get; }
|
||||
public FloatExpressionEditorViewModel RotateEditor { get; } = new() { MaxValue = (float)(2 * Math.PI) };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user