From 40d0e7085167acf8bd46743b8cb24f5aa8c384fc Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Sun, 30 Mar 2025 10:37:03 +0100 Subject: [PATCH 1/5] draft --- doc/distrib/xml/en-US/DSCoreNodes.xml | 22 ++--- .../CoreNodeModels/CurveMapperNodeModel.cs | 94 ++++++++++++++----- .../CoreNodes/CurveMapper/BezierCurve.cs | 38 ++++++-- .../CoreNodes/CurveMapper/ControlLine.cs | 2 +- .../CoreNodes/CurveMapper/CurveBase.cs | 6 +- .../CurveMapper/CurveMapperGenerator.cs | 29 +++++- .../CoreNodes/CurveMapper/GaussianCurve.cs | 25 ++++- .../CoreNodes/CurveMapper/LinearCurve.cs | 26 ++++- .../CoreNodes/CurveMapper/ParabolicCurve.cs | 43 +++++---- .../CoreNodes/CurveMapper/PerlinNoiseCurve.cs | 23 ++++- .../CoreNodes/CurveMapper/PowerCurve.cs | 24 ++++- .../CoreNodes/CurveMapper/SineWave.cs | 23 ++++- .../CoreNodes/CurveMapper/SquareRootCurve.cs | 21 ++++- 13 files changed, 290 insertions(+), 86 deletions(-) diff --git a/doc/distrib/xml/en-US/DSCoreNodes.xml b/doc/distrib/xml/en-US/DSCoreNodes.xml index 8f9074672cc..a6873ff2236 100644 --- a/doc/distrib/xml/en-US/DSCoreNodes.xml +++ b/doc/distrib/xml/en-US/DSCoreNodes.xml @@ -208,7 +208,7 @@ A Bezier curve is defined by four control points and provides smooth interpolation. - + Gets interpolated Y values based on the assigned parameters and limits. @@ -225,17 +225,17 @@ Provides common functionality for generating and retrieving curve values. - + Abstract method to be implemented by derived classes for generating curve values. - + Common method for retrieving X values. - + Common method for retrieving Y values. @@ -251,7 +251,7 @@ Indicates whether the node is currently being resized, preventing unintended control point updates. - + Returns X and Y values distributed across the curve. @@ -272,7 +272,7 @@ Calculates the X values (canvas coordinates) for min and max Y values - + Returns X and Y values distributed across the curve. @@ -283,7 +283,7 @@ The curve follows a quadratic equation based on two control points. - + Returns X and Y values distributed across the curve. @@ -294,7 +294,7 @@ The curve generates procedural noise based on control points and Perlin noise functions. - + Returns X and Y values distributed across the curve. @@ -305,7 +305,7 @@ The curve is defined by a power equation derived from a control point. - + Returns X and Y values distributed across the curve. @@ -316,7 +316,7 @@ The sine wave is defined by two control points and follows a trigonometric function. - + Returns X and Y values distributed across the curve. @@ -327,7 +327,7 @@ The curve follows a square root function and is influenced by two control points. - + Returns X and Y values distributed across the curve. diff --git a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs index 36128b2f480..66c912e91d6 100644 --- a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs +++ b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs @@ -22,7 +22,7 @@ public class CurveMapperNodeModel : NodeModel private double maxLimitX = 1; private double minLimitY = 0; private double maxLimitY = 1; - private int pointsCount = 10; + private List pointsCount = new List() { 10.0 }; private List outputValuesY; private List outputValuesX; @@ -33,7 +33,11 @@ public class CurveMapperNodeModel : NodeModel private readonly IntNode maxLimitXDefaultValue = new IntNode(1); private readonly IntNode minLimitYDefaultValue = new IntNode(0); private readonly IntNode maxLimitYDefaultValue = new IntNode(1); - private readonly IntNode pointsCountDefaultValue = new IntNode(10); + private readonly AssociativeNode pointsCountDefaultValue = + AstFactory.BuildExprList(new List + { + AstFactory.BuildDoubleNode(10.0) + }); private const string gaussianCurveControlPointData2Tag = "GaussianCurveControlPointData2"; private const string gaussianCurveControlPointData3Tag = "GaussianCurveControlPointData3"; @@ -228,7 +232,7 @@ public double MaxLimitY /// Gets or sets the number of points used to compute the curve. [JsonProperty] - public int PointsCount + public List PointsCount { get => pointsCount; set @@ -678,9 +682,16 @@ public void UpdateGaussianCurveControlPoints(double deltaX, string tag) private bool IsValidInput() { - return PointsCount >= 2 - && MinLimitX != MaxLimitX - && MinLimitY != MaxLimitY; + if (pointsCount == null || pointsCount.Count == 0) + return false; + + if (pointsCount.Count == 1 && pointsCount[0] < 2) + return false; + + if (MinLimitX == MaxLimitX || MinLimitY == MaxLimitY) + return false; + + return true; } private bool IsValidCurve() @@ -787,14 +798,44 @@ private void DataBridgeCallback(object data) var maxValueX = double.TryParse(inputs[1]?.ToString(), out var maxX) ? maxX : MaxLimitX; var minValueY = double.TryParse(inputs[2]?.ToString(), out var minY) ? minY : MinLimitY; var maxValueY = double.TryParse(inputs[3]?.ToString(), out var maxY) ? maxY : MaxLimitY; - var listValue = int.TryParse(inputs[4]?.ToString(), out var parsedCount) ? parsedCount : PointsCount; + List parsedPointsCount; + + if (inputs[4] is IList countList) + { + parsedPointsCount = new List(); + + foreach (var item in countList) + { + if (item is IList nestedList) + { + foreach (var nested in nestedList) + { + if (double.TryParse(nested?.ToString(), out var val)) + parsedPointsCount.Add(val); + } + } + else if (double.TryParse(item?.ToString(), out var val)) + { + parsedPointsCount.Add(val); + } + } + } + else if (double.TryParse(inputs[4]?.ToString(), out var singleVal)) + { + parsedPointsCount = new List { singleVal }; + } + else + { + parsedPointsCount = PointsCount; + } + // Check port connectivity if (InPorts[0].IsConnected) MinLimitX = minValueX; if (InPorts[1].IsConnected) MaxLimitX = maxValueX; if (InPorts[2].IsConnected) MinLimitY = minValueY; if (InPorts[3].IsConnected) MaxLimitY = maxValueY; - if (InPorts[4].IsConnected) PointsCount = listValue; + if (InPorts[4].IsConnected) PointsCount = parsedPointsCount; // Notify property changes to update UI foreach (var propertyName in new[] { nameof(MinLimitX), nameof(MaxLimitX), nameof(MinLimitY), nameof(MaxLimitY), nameof(PointsCount) }) @@ -810,8 +851,8 @@ private void DataBridgeCallback(object data) public override IEnumerable BuildOutputAst(List inputAstNodes) { - // Return null outputs if GraphType is Empty - if (SelectedGraphType == GraphTypes.Empty || !IsValidCurve()) + // If input is missing or invalid, return nulls + if (inputAstNodes == null || inputAstNodes.Count < 5 || SelectedGraphType == GraphTypes.Empty || !IsValidCurve()) { return new[] { @@ -866,16 +907,13 @@ public override IEnumerable BuildOutputAst(List new AssociativeNode[] { AstFactory.BuildDoubleNode(cp.X), AstFactory.BuildDoubleNode(DynamicCanvasSize - cp.Y) - }) - .Cast() - .ToList() + }).ToList() ); // Handle input values with fall-back defaults @@ -892,28 +930,32 @@ public override IEnumerable BuildOutputAst(List, double, double, double, double, double, int, string, List>>( - CurveMapperGenerator.CalculateValues), + new Func, double, double, double, double, double, List, string, List>( + CurveMapperGenerator.CalculateValuesX), curveInputs ); - // DataBridge call - var dataBridgeCall = AstFactory.BuildAssignment( - AstFactory.BuildIdentifier(AstIdentifierBase + "_dataBridge"), - VMDataBridge.DataBridge.GenerateBridgeDataAst(GUID.ToString(), AstFactory.BuildExprList(inputValues)) - ); + AssociativeNode buildResultNodeY = + AstFactory.BuildFunctionCall( + new Func, double, double, double, double, double, List, string, List>( + CurveMapperGenerator.CalculateValuesY), + curveInputs + ); - // Assign outputs var xValuesAssignment = AstFactory.BuildAssignment( GetAstIdentifierForOutputIndex(0), - AstFactory.BuildIndexExpression(buildResultNode, AstFactory.BuildIntNode(0)) - ); + buildResultNodeX); var yValuesAssignment = AstFactory.BuildAssignment( GetAstIdentifierForOutputIndex(1), - AstFactory.BuildIndexExpression(buildResultNode, AstFactory.BuildIntNode(1)) + buildResultNodeY); + + // DataBridge call + var dataBridgeCall = AstFactory.BuildAssignment( + AstFactory.BuildIdentifier(AstIdentifierBase + "_dataBridge"), + VMDataBridge.DataBridge.GenerateBridgeDataAst(GUID.ToString(), AstFactory.BuildExprList(inputValues)) ); return new[] { xValuesAssignment, yValuesAssignment, dataBridgeCall }; diff --git a/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs b/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs index 67a46e1b399..dc9f84252d5 100644 --- a/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs @@ -53,13 +53,15 @@ private void GetValueAtT(double t, out double x, out double y) /// /// Gets interpolated Y values based on the assigned parameters and limits. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender) { var renderValuesX = new List(); var renderValuesY = new List(); var valuesX = new List(); var valuesY = new List(); + var pointsCount = pointsDomain.Count == 1 ? pointsDomain[0] : pointsDomain.Count; + // Generate fine-grained samples to ensure better interpolation int fineSteps = (int)(pointsCount * CanvasSize); @@ -78,15 +80,35 @@ protected override (List XValues, List YValues) GenerateCurve(in return (renderValuesX, renderValuesY); } - // Collect output values - for (int i = 0; i < pointsCount; i++) + if (pointsDomain.Count == 1) + { + for (int i = 0; i < pointsCount; i++) + { + double targetX = (i / (double)(pointsCount - 1) * CanvasSize); + int closestIndex = renderValuesX.IndexOf(renderValuesX.OrderBy(x => Math.Abs(x - targetX)).First()); + double y = renderValuesY[closestIndex]; + + valuesX.Add(targetX); + valuesY.Add(y); + } + } + else { - double targetX = (i / (double)(pointsCount - 1) * CanvasSize); - int closestIndex = renderValuesX.IndexOf(renderValuesX.OrderBy(x => Math.Abs(x - targetX)).First()); - double y = renderValuesY[closestIndex]; + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + // Normalize domain value & map to canvas X coordinate + foreach (double t in pointsDomain) + { + double normalizedT = (t - minInput) / (maxInput - minInput); + double targetX = normalizedT * CanvasSize; + + int closestIndex = renderValuesX.IndexOf(renderValuesX.OrderBy(x => Math.Abs(x - targetX)).First()); + double y = renderValuesY[closestIndex]; - valuesX.Add(targetX); - valuesY.Add(y); + valuesX.Add(targetX); + valuesY.Add(y); + } } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs b/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs index cb60497892d..ab1da877550 100644 --- a/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs +++ b/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs @@ -21,7 +21,7 @@ public ControlLine(double cp1X, double cp1Y, double cp2X, double cp2Y, double ca ControlPoint2X = cp2X; ControlPoint2Y = cp2Y; } - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + protected override (List XValues, List YValues) GenerateCurve(List pointsCount, bool isRender = false) { return (new List { ControlPoint1X, ControlPoint2X }, new List { ControlPoint1Y, ControlPoint2Y }); } diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs index a3ce5c18d52..9d3b7f53c90 100644 --- a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs +++ b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs @@ -19,12 +19,12 @@ protected CurveBase(double canvasSize) /// /// Abstract method to be implemented by derived classes for generating curve values. /// - protected abstract (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender); + protected abstract (List XValues, List YValues) GenerateCurve(List pointsCount, bool isRender); /// /// Common method for retrieving X values. /// - public virtual List GetCurveXValues(int pointsCount, bool isRender = false) + public virtual List GetCurveXValues(List pointsCount, bool isRender = false) { return GenerateCurve(pointsCount, isRender).XValues; } @@ -32,7 +32,7 @@ public virtual List GetCurveXValues(int pointsCount, bool isRender = fal /// /// Common method for retrieving Y values. /// - public virtual List GetCurveYValues(int pointsCount, bool isRender = false) + public virtual List GetCurveYValues(List pointsCount, bool isRender = false) { return GenerateCurve(pointsCount, isRender).YValues; } diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs index aad65891ff9..4653166c91b 100644 --- a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs +++ b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs @@ -10,13 +10,18 @@ public class CurveMapperGenerator public static List> CalculateValues( List controlPoints, double canvasSize, double minX, double maxX, double minY, double maxY, - int pointsCount, string graphType + List pointsCount, string graphType ) { var xValues = new List() { double.NaN }; var yValues = new List() { double.NaN }; - if (minX != maxX && minY != maxY && pointsCount >= 2) + if (pointsCount.Count == 1 && pointsCount[0] < 2) + { + return new List> { yValues, xValues }; + } + + if (minX != maxX && minY != maxY) { // Unpack the control points double cp1x = GetCP(controlPoints, 0), cp1y = GetCP(controlPoints, 1); @@ -69,6 +74,26 @@ public static List> CalculateValues( return new List> { yValues, xValues }; } + public static List CalculateValuesX( + List controlPoints, double canvasSize, + double minX, double maxX, double minY, double maxY, + List pointsCount, string graphType + ) + { + var result = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType)[0]; + return result ; + } + + public static List CalculateValuesY( + List controlPoints, double canvasSize, + double minX, double maxX, double minY, double maxY, + List pointsCount, string graphType + ) + { + var result = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType)[1]; + return result; + } + private static double GetCP(List controlPoints, int index) { return index < controlPoints.Count ? controlPoints[index] : 0; diff --git a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs index ead49914ae9..020c60664da 100644 --- a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -47,7 +48,7 @@ private double ComputeGaussianY(double x, double A, double mu, double sigma) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender) { var valuesX = new List(); var valuesY = new List(); @@ -106,16 +107,32 @@ protected override (List XValues, List YValues) GenerateCurve(in } } } - else + else if (pointsDomain.Count == 1) { + var pointsCount = pointsDomain[0]; var step = CanvasSize / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) { double x = 0 + i * step; - double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); + + valuesX.Add(x); + valuesY.Add(CanvasSize - ComputeGaussianY(x, A, mu, sigma)); + } + } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize domain value & map to canvas X coordinate + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + valuesX.Add(x); - valuesY.Add(y); + valuesY.Add(CanvasSize - ComputeGaussianY(x, A, mu, sigma)); } } diff --git a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs index 52849c32630..e77d7d38855 100644 --- a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -54,7 +55,7 @@ private double SolveForXGivenY(double y) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender = false) { double leftX = SolveForXGivenY(0); double rightX = SolveForXGivenY(CanvasSize); @@ -76,8 +77,10 @@ protected override (List XValues, List YValues) GenerateCurve(in ? new List { System.Math.Clamp(highY, 0, CanvasSize), System.Math.Clamp(lowY, 0, CanvasSize) } : new List { System.Math.Clamp(lowY, 0, CanvasSize), System.Math.Clamp(highY, 0, CanvasSize) }; } - else + else if (pointsDomain.Count == 1) { + var pointsCount = (int)pointsDomain[0]; + // For full point distribution double stepX = CanvasSize / (pointsCount - 1); double stepY = (highY - lowY) / (pointsCount - 1); @@ -88,6 +91,25 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesY.Add((lowY + i * stepY)); } } + else + { + // Make sure X values range from 0 to CanvasSize + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize each value to 0-1 range + double normalizedT = (t - minInput) / (maxInput - minInput); + + // Map it to the canvas X range + double mappedX = normalizedT * CanvasSize; + double y = LineEquation(mappedX); + + valuesX.Add(mappedX); + valuesY.Add(System.Math.Clamp(y, 0, CanvasSize)); + } + } return (valuesX, valuesY); } diff --git a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs index 2418af9358e..36b6b148a99 100644 --- a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -41,14 +42,16 @@ private double SolveParabolaForY(double x) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender) { double leftBoundaryY = (ControlPoint2Y > ControlPoint1Y) ? CanvasSize : 0.0; double rightBoundaryY = (ControlPoint2Y < ControlPoint1Y) ? CanvasSize : 0.0; - double startX = SolveParabolaForX(leftBoundaryY, true); double endX = SolveParabolaForX(leftBoundaryY); + var valuesX = new List(); + var valuesY = new List(); + if (isRender) { double minX = Math.Max(0, Math.Min(startX, endX)); @@ -57,8 +60,8 @@ protected override (List XValues, List YValues) GenerateCurve(in // First point double firstY = SolveParabolaForY(minX); - var valuesX = new List { minX }; - var valuesY = new List { firstY }; + valuesX.Add(minX); + valuesY.Add(firstY); for (double d = minX; d < maxX; d += renderIncrementX) { @@ -73,19 +76,10 @@ protected override (List XValues, List YValues) GenerateCurve(in // Last point valuesX.Add(maxX); valuesY.Add(SolveParabolaForY(maxX)); - - return (valuesX, valuesY); } - else + else if (pointsDomain.Count == 1) { - bool flip = ControlPoint2Y > ControlPoint1Y; - - // First point - double firstY = SolveParabolaForY(leftBoundaryY); - double firstX = leftBoundaryY; - - var valuesX = new List(); - var valuesY = new List(); + var pointsCount = pointsDomain[0]; var step = (rightBoundaryY - leftBoundaryY) / (pointsCount - 1); @@ -99,14 +93,29 @@ protected override (List XValues, List YValues) GenerateCurve(in } // Reverse lists if needed to ensure X values increase from left to right - if (flip) + if (ControlPoint2Y > ControlPoint1Y) { valuesX.Reverse(); valuesY.Reverse(); } + } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); - return (valuesX, valuesY); + foreach (var t in pointsDomain) + { + // Normalize domain value & map to X range on canvas + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + valuesY.Add(SolveParabolaForY(x)); + } } + + return (valuesX, valuesY); } } } diff --git a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs index 48d4773d0ca..40599e2187a 100644 --- a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs @@ -160,7 +160,7 @@ private double ComputePerlinCurveY(double x) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender = false) { var valuesX = new List(); var valuesY = new List(); @@ -194,17 +194,32 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesX = sortedPairs.Select(p => p.X).ToList(); valuesY = sortedPairs.Select(p => p.Y).ToList(); } - else + else if (pointsDomain.Count == 1) { + var pointsCount = pointsDomain[0]; var step = CanvasSize / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) { double x = 0 + step * i; - double y = ComputePerlinCurveY(x); valuesX.Add(x); - valuesY.Add(y); + valuesY.Add(ComputePerlinCurveY(x)); + } + } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize domain value & map to X range on canvas + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + valuesY.Add(ComputePerlinCurveY(x)); } } diff --git a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs index b286ddc02ae..7630f90be2e 100644 --- a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -48,7 +49,7 @@ private double ComputePowerY(double x, double powerFactor) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender) { var valuesX = new List(); var valuesY = new List(); @@ -63,16 +64,31 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesY.Add(y); } } - else + else if (pointsDomain.Count == 1) { + var pointsCount = (int)pointsDomain[0]; double step = CanvasSize / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) { double x = i * step; valuesX.Add(x); - double y = ComputePowerY(x, powerFactor); - valuesY.Add(y); + valuesY.Add(ComputePowerY(x, powerFactor)); + } + } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize domain value & map to canvas X coordinate + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + valuesY.Add(ComputePowerY(x, powerFactor)); } } diff --git a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs index 83ec7bc0029..42132b9dc70 100644 --- a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs +++ b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -46,7 +47,7 @@ private void GetEquationCoefficients() /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender = false) { var valuesX = new List(); var valuesY = new List(); @@ -60,8 +61,10 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesY.Add(ConvertTrigoToY(y)); } } - else + else if (pointsDomain.Count == 1) { + var pointsCount = (int)pointsDomain[0]; + double step = CanvasSize / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) { @@ -71,6 +74,22 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesY.Add(ConvertTrigoToY(y)); } } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize domain value & map to canvas X coordinate + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + double y = CosineEquation(ConvertXToTrigo(x)); + valuesY.Add(ConvertTrigoToY(y)); + } + } return (valuesX, valuesY); } diff --git a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs index ec54d7130ca..9aeea5fc977 100644 --- a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -56,7 +57,7 @@ private double ComputeSquareRootY(double x, double sqrtFactor) /// /// Returns X and Y values distributed across the curve. /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + protected override (List XValues, List YValues) GenerateCurve(List pointsDomain, bool isRender) { var valuesX = new List(); var valuesY = new List(); @@ -90,8 +91,9 @@ protected override (List XValues, List YValues) GenerateCurve(in } } } - else + else if (pointsDomain.Count == 1) { + var pointsCount = (int)pointsDomain[0]; var step = CanvasSize / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) @@ -102,6 +104,21 @@ protected override (List XValues, List YValues) GenerateCurve(in valuesY.Add(ComputeSquareRootY(x, sqrtFactor)); } } + else + { + double minInput = pointsDomain.Min(); + double maxInput = pointsDomain.Max(); + + foreach (var t in pointsDomain) + { + // Normalize domain value & map to canvas X coordinate + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + valuesY.Add(ComputeSquareRootY(x, sqrtFactor)); + } + } return (valuesX, valuesY); } From 3ace3953ba8bf5633d36262c19cfa527911ca6db Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Sun, 30 Mar 2025 11:35:51 +0100 Subject: [PATCH 2/5] some rationalizations --- doc/distrib/xml/en-US/DSCoreNodes.xml | 5 ++++ .../CoreNodes/CurveMapper/CurveBase.cs | 26 +++++++++++++++++++ .../CurveMapper/CurveMapperGenerator.cs | 14 +++++++--- .../CoreNodes/CurveMapper/GaussianCurve.cs | 13 +--------- .../CoreNodes/CurveMapper/LinearCurve.cs | 17 +----------- .../CoreNodes/CurveMapper/ParabolicCurve.cs | 13 +--------- .../CoreNodes/CurveMapper/PerlinNoiseCurve.cs | 13 +--------- .../CoreNodes/CurveMapper/PowerCurve.cs | 13 +--------- .../CoreNodes/CurveMapper/SineWave.cs | 11 ++------ .../CoreNodes/CurveMapper/SquareRootCurve.cs | 13 +--------- 10 files changed, 49 insertions(+), 89 deletions(-) diff --git a/doc/distrib/xml/en-US/DSCoreNodes.xml b/doc/distrib/xml/en-US/DSCoreNodes.xml index a6873ff2236..964204be67a 100644 --- a/doc/distrib/xml/en-US/DSCoreNodes.xml +++ b/doc/distrib/xml/en-US/DSCoreNodes.xml @@ -240,6 +240,11 @@ Common method for retrieving Y values. + + + Generates X and Y values by mapping domain inputs to canvas space and evaluating a curve function. + + Represents a Gaussian curve in the CurveMapper. diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs index 9d3b7f53c90..6a36f0228f0 100644 --- a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs +++ b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Linq; namespace DSCore.CurveMapper { @@ -36,5 +38,29 @@ public virtual List GetCurveYValues(List pointsCount, bool isRen { return GenerateCurve(pointsCount, isRender).YValues; } + + /// + /// Generates X and Y values by mapping domain inputs to canvas space and evaluating a curve function. + /// + protected (List xValues, List yValues) GenerateFromDomain(List domain, Func computeY) + { + var valuesX = new List(); + var valuesY = new List(); + + double minInput = domain.Min(); + double maxInput = domain.Max(); + + foreach (var t in domain) + { + // Normalize domain value & map to canvas X coordinate + double normalizedT = (t - minInput) / (maxInput - minInput); + double x = normalizedT * CanvasSize; + + valuesX.Add(x); + valuesY.Add(computeY(x)); + } + + return (valuesX, valuesY); + } } } diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs index 4653166c91b..44e275167f6 100644 --- a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs +++ b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs @@ -6,6 +6,7 @@ namespace DSCore.CurveMapper public class CurveMapperGenerator { private static int rounding = 10; + private static List> cachedValues = null; public static List> CalculateValues( List controlPoints, double canvasSize, @@ -80,8 +81,10 @@ public static List CalculateValuesX( List pointsCount, string graphType ) { - var result = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType)[0]; - return result ; + // X values must always be calculated first to initialize the cache. + // CalculateValuesY() depends on this to avoid redundant calculation. + cachedValues = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType); + return cachedValues[0]; } public static List CalculateValuesY( @@ -90,8 +93,11 @@ public static List CalculateValuesY( List pointsCount, string graphType ) { - var result = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType)[1]; - return result; + if ( cachedValues == null ) + { + cachedValues = CalculateValues(controlPoints, canvasSize, minX, maxX, minY, maxY, pointsCount, graphType); + } + return cachedValues[1]; } private static double GetCP(List controlPoints, int index) diff --git a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs index 020c60664da..b5f432855cf 100644 --- a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs @@ -122,18 +122,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize domain value & map to canvas X coordinate - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - valuesY.Add(CanvasSize - ComputeGaussianY(x, A, mu, sigma)); - } + return GenerateFromDomain(pointsDomain, x => CanvasSize - ComputeGaussianY(x, A, mu, sigma)); } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs index e77d7d38855..b19de728adf 100644 --- a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs @@ -93,22 +93,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - // Make sure X values range from 0 to CanvasSize - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize each value to 0-1 range - double normalizedT = (t - minInput) / (maxInput - minInput); - - // Map it to the canvas X range - double mappedX = normalizedT * CanvasSize; - double y = LineEquation(mappedX); - - valuesX.Add(mappedX); - valuesY.Add(System.Math.Clamp(y, 0, CanvasSize)); - } + return GenerateFromDomain(pointsDomain, x => LineEquation(x)); } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs index 36b6b148a99..dc99a29418d 100644 --- a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs @@ -101,18 +101,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize domain value & map to X range on canvas - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - valuesY.Add(SolveParabolaForY(x)); - } + return GenerateFromDomain(pointsDomain, x => SolveParabolaForY(x)); } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs index 40599e2187a..bd8d548c3b0 100644 --- a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs @@ -209,18 +209,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize domain value & map to X range on canvas - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - valuesY.Add(ComputePerlinCurveY(x)); - } + return GenerateFromDomain(pointsDomain, x => ComputePerlinCurveY(x)); } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs index 7630f90be2e..34e05dda683 100644 --- a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs @@ -78,18 +78,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize domain value & map to canvas X coordinate - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - valuesY.Add(ComputePowerY(x, powerFactor)); - } + return GenerateFromDomain(pointsDomain, x => ComputePowerY(x, powerFactor)); } return (valuesX, valuesY); diff --git a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs index 42132b9dc70..3aab4796cbd 100644 --- a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs +++ b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs @@ -70,8 +70,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li { double x = i * step; valuesX.Add(x); - double y = CosineEquation(ConvertXToTrigo(x)); - valuesY.Add(ConvertTrigoToY(y)); + valuesY.Add(ConvertTrigoToY(CosineEquation(ConvertXToTrigo(x)))); } } else @@ -81,13 +80,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li foreach (var t in pointsDomain) { - // Normalize domain value & map to canvas X coordinate - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - double y = CosineEquation(ConvertXToTrigo(x)); - valuesY.Add(ConvertTrigoToY(y)); + return GenerateFromDomain(pointsDomain, x => ConvertTrigoToY(CosineEquation(ConvertXToTrigo(x)))); } } diff --git a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs index 9aeea5fc977..9866b20c753 100644 --- a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs +++ b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs @@ -106,18 +106,7 @@ protected override (List XValues, List YValues) GenerateCurve(Li } else { - double minInput = pointsDomain.Min(); - double maxInput = pointsDomain.Max(); - - foreach (var t in pointsDomain) - { - // Normalize domain value & map to canvas X coordinate - double normalizedT = (t - minInput) / (maxInput - minInput); - double x = normalizedT * CanvasSize; - - valuesX.Add(x); - valuesY.Add(ComputeSquareRootY(x, sqrtFactor)); - } + return GenerateFromDomain(pointsDomain, x => ComputeSquareRootY(x, sqrtFactor)); } return (valuesX, valuesY); From 11d713c29169591e95cd0d0b9d12f6f08b41b635 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Mon, 31 Mar 2025 13:30:23 +0100 Subject: [PATCH 3/5] updated resources --- .../CoreNodeModels/Properties/Resources.Designer.cs | 8 ++------ .../CoreNodeModels/Properties/Resources.en-US.resx | 6 ++---- src/Libraries/CoreNodeModels/Properties/Resources.resx | 6 ++---- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs b/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs index f4097a6e406..8e85bc90d04 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs +++ b/src/Libraries/CoreNodeModels/Properties/Resources.Designer.cs @@ -270,9 +270,7 @@ public static string CreateListPortDataResultToolTip { } /// - /// Looks up a localized string similar to count - /// - ///int. + /// Looks up a localized string similar to values. /// public static string CurveMapperCountInputPortName { get { @@ -281,9 +279,7 @@ public static string CurveMapperCountInputPortName { } /// - /// Looks up a localized string similar to Number of values to generate. - /// - ///int. + /// Looks up a localized string similar to Number of values to map or List of values to map.. /// public static string CurveMapperCountInputPortToolTip { get { diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx b/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx index 4bec0592da2..2505b960e1b 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx +++ b/src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx @@ -681,12 +681,10 @@ In Generative Design workflows, this node should be used to control and block th default value: G - count + values - Number of values to generate. - -int + Number of values to map or List of values to map. Redistributes x-coordinates along y-coordinates based on a selected mathematical curve, providing precise control over point distribution. diff --git a/src/Libraries/CoreNodeModels/Properties/Resources.resx b/src/Libraries/CoreNodeModels/Properties/Resources.resx index 08b9744069e..a0db4dfe933 100644 --- a/src/Libraries/CoreNodeModels/Properties/Resources.resx +++ b/src/Libraries/CoreNodeModels/Properties/Resources.resx @@ -681,12 +681,10 @@ In Generative Design workflows, this node should be used to control and block th default value: G - count + values - Number of values to generate. - -int + Number of values to map or List of values to map. Redistributes x-coordinates along y-coordinates based on a selected mathematical curve, providing precise control over point distribution. From 66f2681646e9e40f0acf6aea9e9e76b3d7b3337c Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 1 Apr 2025 16:13:17 +0100 Subject: [PATCH 4/5] Update CurveMapperNodeModel.cs --- src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs index 66c912e91d6..5e7acbd94e5 100644 --- a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs +++ b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs @@ -685,7 +685,7 @@ private bool IsValidInput() if (pointsCount == null || pointsCount.Count == 0) return false; - if (pointsCount.Count == 1 && pointsCount[0] < 2) + if (pointsCount.Count == 1 && pointsCount.FirstOrDefault() < 2) return false; if (MinLimitX == MaxLimitX || MinLimitY == MaxLimitY) From 82d92daf581a505d7e078abead6f859c42b39058 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Wed, 2 Apr 2025 06:36:27 +0100 Subject: [PATCH 5/5] Update CurveMapperNodeModel.cs --- .../CoreNodeModels/CurveMapperNodeModel.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs index a0dbe0dd741..bcac218d948 100644 --- a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs +++ b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs @@ -29,14 +29,20 @@ public class CurveMapperNodeModel : NodeModel private List renderValuesY; private List renderValuesX; - private readonly IntNode minLimitXDefaultValue = new IntNode(0); - private readonly IntNode maxLimitXDefaultValue = new IntNode(1); - private readonly IntNode minLimitYDefaultValue = new IntNode(0); - private readonly IntNode maxLimitYDefaultValue = new IntNode(1); + private static readonly int minXDefaultValue = 0; + private static readonly int maxXDefaultValue = 1; + private static readonly int minYDefaultValue = 0; + private static readonly int maxYDefaultValue = 1; + private static readonly double pointCountDefaultValue = 10.0; + + private readonly IntNode minLimitXDefaultValue = new IntNode(minXDefaultValue); + private readonly IntNode maxLimitXDefaultValue = new IntNode(maxXDefaultValue); + private readonly IntNode minLimitYDefaultValue = new IntNode(minYDefaultValue); + private readonly IntNode maxLimitYDefaultValue = new IntNode(maxYDefaultValue); private readonly AssociativeNode pointsCountDefaultValue = AstFactory.BuildExprList(new List { - AstFactory.BuildDoubleNode(10.0) + AstFactory.BuildDoubleNode(pointCountDefaultValue) }); private const string gaussianCurveControlPointData2Tag = "GaussianCurveControlPointData2";