diff --git a/GRBL-Plotter/GCodeCreation/GraphicClasses.cs b/GRBL-Plotter/GCodeCreation/GraphicClasses.cs index a0ed3302..c9e00425 100644 --- a/GRBL-Plotter/GCodeCreation/GraphicClasses.cs +++ b/GRBL-Plotter/GCodeCreation/GraphicClasses.cs @@ -1,7 +1,7 @@ /* GRBL-Plotter. Another GCode sender for GRBL. This file is part of the GRBL-Plotter application. - Copyright (C) 2019-2023 Sven Hasemann contact: svenhb@web.de + Copyright (C) 2019-2024 Sven Hasemann contact: svenhb@web.de This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,12 +29,14 @@ You should have received a copy of the GNU General Public License * 2023-05-31 add OptionSFromWidth * 2023-08-16 l:388 f:IsSameAs pull request Speed up merge and sort #348 * 2023-08-31 l:686 f:AddArc limit stepwidth - issue #353 + * 2024-02-04 l:720 f:AddArc add noise to line */ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Windows; using static System.Net.Mime.MediaTypeNames; @@ -94,6 +96,7 @@ internal class GraphicInformationClass public bool OptionDragTool { get; set; } // Path modifications public bool OptionTangentialAxis { get; set; } public bool OptionHatchFill { get; set; } + public bool OptionNoise { get; set; } public bool OptionExtendPath { get; set; } public bool OptionClipCode { get; set; } // Clipping @@ -136,6 +139,7 @@ public GraphicInformationClass() OptionZFromRadius = Properties.Settings.Default.importSVGCircleToDotZ; OptionRepeatCode = Properties.Settings.Default.importRepeatEnable; OptionHatchFill = Properties.Settings.Default.importGraphicHatchFillEnable; + OptionNoise = Properties.Settings.Default.importGraphicNoiseEnable; OptionClipCode = Properties.Settings.Default.importGraphicClipEnable; OptionNodesOnly = Properties.Settings.Default.importSVGNodesOnly; OptionTangentialAxis = Properties.Settings.Default.importGCTangentialEnable; @@ -149,7 +153,7 @@ public GraphicInformationClass() OptionCodeSortDimension = Properties.Settings.Default.importGraphicSortDimension; OptionFeedFromToolTable = Properties.Settings.Default.importGCToolTableUse; - ConvertArcToLine = Properties.Settings.Default.importGCNoArcs || OptionClipCode || OptionDragTool || OptionHatchFill;// only for SVG: || ApplyHatchFill; + ConvertArcToLine = Properties.Settings.Default.importGCNoArcs || OptionClipCode || OptionDragTool || OptionHatchFill || OptionNoise;// only for SVG: || ApplyHatchFill; ConvertArcToLine = ConvertArcToLine || OptionSpecialWireBend || OptionSpecialDevelop || OptionRampOnPenDown || OptionDashPattern; } public void ResetOptions(bool enableFigures) @@ -167,6 +171,7 @@ public void ResetOptions(bool enableFigures) OptionCodeOffset = false; ApplyHatchFill = false; OptionHatchFill = false; + OptionNoise = false; OptionClipCode = false; OptionNodesOnly = false; OptionTangentialAxis = false; @@ -215,6 +220,7 @@ public string ListOptions() if (Properties.Settings.Default.importGraphicMultiplyGraphicsEnable) importOptions += " "; if (OptionCodeSortDistance) importOptions += " "; if (ApplyHatchFill) importOptions += " "; + if (OptionNoise) importOptions += " "; if (OptionHatchFill) importOptions += " "; if (OptionRepeatCode && !Properties.Settings.Default.importRepeatComplete) importOptions += " "; if (OptionRepeatCode && Properties.Settings.Default.importRepeatComplete) importOptions += " "; @@ -671,29 +677,28 @@ public void AddArc(Point tmp, Point centerIJ, bool isCW, double dz, double angSt End = tmp; } - public void AddArc(Point tmp, Point centerIJ, double dz, bool isCW, bool convertToLine) + public void AddArc(Point tmp, Point centerIJ, double dz, bool isCW, bool convertToLine, bool addNoise) { GCodeMotion motion; + + ArcProperties arcMove; + Point p1 = Round(End); + Point p2 = Round(tmp); + arcMove = GcodeMath.GetArcMoveProperties(p1, p2, centerIJ, isCW); + PathLength += Math.Abs(arcMove.radius * arcMove.angleDiff); // distance from last to current point + Dimension.SetDimensionArc(new XyPoint(End), new XyPoint(tmp), centerIJ.X, centerIJ.Y, isCW); + if (!convertToLine) { motion = new GCodeArc(tmp, centerIJ, isCW, dz); - Dimension.SetDimensionArc(new XyPoint(End), new XyPoint(tmp), centerIJ.X, centerIJ.Y, isCW); Path.Add(motion); - - ArcProperties arcMove; - Point p1 = Round(End); - Point p2 = Round(tmp); - arcMove = GcodeMath.GetArcMoveProperties(p1, p2, centerIJ, isCW); - PathLength += Math.Abs(arcMove.radius * arcMove.angleDiff); // distance from last to current point - End = tmp; } else { - ArcProperties arcMove; - Point p1 = Round(End); - Point p2 = Round(tmp); + double radius; + double noiseAmplitude = (double)Properties.Settings.Default.importGraphicNoiseAmplitude/2; + double x, y; - arcMove = GcodeMath.GetArcMoveProperties(p1, p2, centerIJ, isCW); double stepwidth = (double)Properties.Settings.Default.importGCSegment; if (Properties.Settings.Default.importRemoveShortMovesEnable) // 2023-08-31 issue #353 @@ -702,42 +707,41 @@ public void AddArc(Point tmp, Point centerIJ, double dz, bool isCW, bool convert if (stepwidth > arcMove.radius / 2) { stepwidth = arcMove.radius / 5; } double step = Math.Asin(stepwidth / arcMove.radius); // in RAD - // double step = Math.Asin((double)Properties.Settings.Default.importGCSegment / arcMove.radius); // in RAD if (step > Math.Abs(arcMove.angleDiff)) step = Math.Abs(arcMove.angleDiff / 2); - if (arcMove.angleDiff > 0) //(da > 0) // if delta >0 go counter clock wise + if (arcMove.angleDiff > 0) // counter clock wise { for (double angle = (arcMove.angleStart + step); angle < (arcMove.angleStart + arcMove.angleDiff); angle += step) { - x = arcMove.center.X + arcMove.radius * Math.Cos(angle); - y = arcMove.center.Y + arcMove.radius * Math.Sin(angle); + if (addNoise) + radius = arcMove.radius + (Noise.CalcPixel2D(Path.Count, (int)angle, 1)* noiseAmplitude); + else + radius = arcMove.radius; + x = arcMove.center.X + radius * Math.Cos(angle); + y = arcMove.center.Y + radius * Math.Sin(angle); motion = new GCodeLine(new Point(x, y), dz); - Dimension.SetDimensionXY(x, y); Path.Add(motion); - PathLength += PointDistance(End, tmp); // distance from last to current point - End = tmp; } } - else // else go clock wise + else // else go clock wise { for (double angle = (arcMove.angleStart - step); angle > (arcMove.angleStart + arcMove.angleDiff); angle -= step) { - x = arcMove.center.X + arcMove.radius * Math.Cos(angle); - y = arcMove.center.Y + arcMove.radius * Math.Sin(angle); + if (addNoise) + radius = arcMove.radius + (Noise.CalcPixel2D(Path.Count, (int)angle, 1) * noiseAmplitude); + else + radius = arcMove.radius; + x = arcMove.center.X + radius * Math.Cos(angle); + y = arcMove.center.Y + radius * Math.Sin(angle); motion = new GCodeLine(new Point(x, y), dz); - Dimension.SetDimensionXY(x, y); Path.Add(motion); - PathLength += PointDistance(End, tmp); // distance from last to current point - End = tmp; } } motion = new GCodeLine(new Point(tmp.X, tmp.Y), dz); - Dimension.SetDimensionXY(tmp.X, tmp.Y); Path.Add(motion); - PathLength += PointDistance(End, tmp); // distance from last to current point - End = tmp; } + End = tmp; if (Start == End) IsClosed = true; } diff --git a/GRBL-Plotter/GCodeCreation/GraphicCollectData.cs b/GRBL-Plotter/GCodeCreation/GraphicCollectData.cs index 0263ec00..f9a84a8e 100644 --- a/GRBL-Plotter/GCodeCreation/GraphicCollectData.cs +++ b/GRBL-Plotter/GCodeCreation/GraphicCollectData.cs @@ -55,6 +55,7 @@ You should have received a copy of the GNU General Public License * 2023-08-16 l:271 f:StartPath f:UpdateGUI pull request Speed up merge and sort #348 * 2023-09-16 l:774 f:CreateGCode wrong call to RemoveOffset(minx,minx) -> miny * 2024-01-25 l:1100 f:CreateGCode export graphics dimension, to be able to calculate point-marker-size in 2Dview + * 2024-02-04 l:426 f:AddLine add noise to line */ using System; @@ -92,6 +93,7 @@ public static partial class Graphic internal static Dimensions actualDimension = new Dimensions(); private static Point lastPoint = new Point(); // System.Windows + private static bool lastPointIsStart = true; private static PathObject lastPath = new ItemPath(); private static CreationOption lastOption = CreationOption.none; @@ -113,6 +115,10 @@ public static partial class Graphic private static int countWarnDimNotSet = 0; + // private static bool noiseAdd = false; + private static double noiseAmplitude = 1; + private static double noiseDensity = 1; + private static int pathAddOnCount = 0; //private static int pathAddOnPosition = 0; //private static double pathAddOnScale = 1; @@ -242,6 +248,10 @@ public static void Init(SourceType type, string filePath, BackgroundWorker worke pathBackground = new GraphicsPath(); + //noiseAdd = Properties.Settings.Default.importGraphicNoiseEnable; + noiseAmplitude = (double)Properties.Settings.Default.importGraphicNoiseAmplitude; + noiseDensity = (double)Properties.Settings.Default.importGraphicNoiseDensity; + completeGraphic = new List(); finalPathList = new List(); tileGraphicAll = new List(); @@ -256,6 +266,7 @@ public static void Init(SourceType type, string filePath, BackgroundWorker worke lastPoint = new Point(); lastPath = new ItemPath(); lastOption = CreationOption.none; + lastPointIsStart = true; for (int i = 0; i < groupPropertiesCount.Length; i++) groupPropertiesCount[i] = new Dictionary(); @@ -321,10 +332,6 @@ public static bool StartPath(Point xy, double? useZ = null)//, CreationOptions a if (setNewId) objectCount++; - // double z = GetActualZ(); // apply penWidth if enabled - // if (useZ != null) - // z = (double)useZ; - if (useZ != null) actualPath = new ItemPath(xy, (double)useZ); else @@ -350,8 +357,7 @@ public static bool StartPath(Point xy, double? useZ = null)//, CreationOptions a lastPoint = xy; setNewId = false; lastOption = CreationOption.none; - - // actualPathInfo.SetGroupAttribute((int)GroupOption.ByFill, ""); // reset fill + lastPointIsStart = true; return success; } @@ -372,6 +378,9 @@ public static void StopPath(string cmt) { if (actualPath.Dimension.IsXYSet()) { + if (graphicInformation.OptionNoise) + actualPath.Add(lastPoint, GetActualZ(), 0); + completeGraphic.Add(actualPath); if (logCoordinates) { Logger.Trace("▲ StopPath completeGraphic.Add {0}", completeGraphic.Count); } } @@ -392,7 +401,7 @@ public static void StopPath(string cmt) actualPath.Dimension.ResetDimension(); // 2020-10-31 } - public static bool AddLine(float x, float y) + public static bool AddLine(double x, double y) { return AddLine(new Point(x, y)); } public static bool AddLine(Point xy, double? useZ = null)//, string cmt = "") { @@ -412,13 +421,79 @@ public static bool AddLine(Point xy, double? useZ = null)//, string cmt = "") { if (logCoordinates) Logger.Trace("⚠ AddLine SKIP, same coordinates! X:{0:0.00} Y:{1:0.00}", xy.X, xy.Y); } else { - actualPath.Add(xy, z, 0); + if (graphicInformation.OptionNoise) + { + AddNoiseToPath(xy, z, actualPath.Path.Count, noiseAmplitude, noiseDensity); + } + else + { + actualPath.Add(xy, z, 0); + } if (logCoordinates) Logger.Trace("● AddLine to X:{0:0.00} Y:{1:0.00} Z:{2:0.00} new dist {3:0.00} start.X:{4:0.00} start.Y:{5:0.00}", xy.X, xy.Y, z, actualPath.PathLength, actualPath.Start.X, actualPath.Start.Y); actualDimension.SetDimensionXY(xy.X, xy.Y); lastPoint = xy; } + lastPointIsStart = false; return success; } + private static Point AddNoiseToPath(Point end, double z, int index, double amplitude, double density) + { + double x = lastPoint.X; + double y = lastPoint.Y; + double dx = end.X - lastPoint.X; + double dy = end.Y - lastPoint.Y; + double lineLength = Math.Sqrt(dx * dx + dy * dy); + double stepWidth = density; + int step = (int)Math.Ceiling(lineLength / stepWidth); + + if (step == 0) + { + actualPath.Add(end, z, 0); + return end; + } + double dix = dx / step; + double diy = dy / step; + + double fx, fy; + if (dx == 0) + { fx = 1; fy = 0; } + else if (dy == 0) + { fx = 0; fy = 1; } + else + { + fx = dy / lineLength; fy = dx / lineLength; + } + fx *= (amplitude / 2); ; + fy *= (-amplitude / 2); ; + + float scale, n, nx = 0, ny = 0; + scale = 1;// (float)stepWidth / 2000; + + Logger.Trace("AddNoiseToPath step:{0}",step); + + if (step <= 1) + { + n = Noise.CalcPixel2D(index, 1, scale); + nx = (float)fx * n; + ny = (float)fy * n; + actualPath.Add(new Point(x + nx, y + ny), z, 0); + //actualPath.Add(end, z, 0); + } + else + { + for (int i = 1; i < step; i++) + { + n = Noise.CalcPixel2D(index, i, scale); + nx = (float)fx * n; + ny = (float)fy * n; + x += dix; + y += diy; + actualPath.Add(new Point(x + nx, y + ny), z, 0); + } + actualPath.Add(end, z, 0); + } + return new Point(x + nx, y + ny); + } public static bool AddDot(Point xy)//, string cmt = "") { return AddDot(xy.X, xy.Y); }//, cmt); @@ -469,7 +544,7 @@ public static bool AddCircle(double centerX, double centerY, double radius) { Logger.Error("AddCircle NaN skip the circle X:{0:0.00} Y:{1:0.00} r:{2:0.00} ", centerX, centerY, radius); success = false; } else { - actualPath.AddArc(new Point(centerX + radius, centerY), new Point(-radius, 0), GetActualZ(), true, graphicInformation.ConvertArcToLine);// convertArcToLine); + actualPath.AddArc(new Point(centerX + radius, centerY), new Point(-radius, 0), GetActualZ(), true, graphicInformation.ConvertArcToLine, graphicInformation.OptionNoise);// convertArcToLine); actualPath.Info.CopyData(actualPathInfo); // preset global info for GROUP if (logCoordinates) Logger.Trace(" AddCircle to X:{0:0.00} Y:{1:0.00} r:{2:0.00} angleStep:{3}", centerX, centerY, radius, Properties.Settings.Default.importGCSegment); } @@ -488,7 +563,7 @@ public static bool AddArc(bool isg2, double ax, double ay, double ai, double aj) else { lastPoint = new Point(ax, ay); - actualPath.AddArc(new Point(ax, ay), new Point(ai, aj), GetActualZ(), isg2, graphicInformation.ConvertArcToLine); + actualPath.AddArc(new Point(ax, ay), new Point(ai, aj), GetActualZ(), isg2, graphicInformation.ConvertArcToLine, graphicInformation.OptionNoise); actualPath.Info.CopyData(actualPathInfo); // preset global info for GROUP if (logCoordinates) Logger.Trace(" AddArc to X:{0:0.00} Y:{1:0.00} i:{2:0.00} j:{3:0.00} angleStep:{4} isG2:{5}", ax, ay, ai, aj, Properties.Settings.Default.importGCSegment, isg2); } @@ -975,7 +1050,7 @@ public static bool CreateGCode()//Final(BackgroundWorker backgroundWorker, DoWor if (!cancelByWorker && (graphicInformation.ApplyHatchFill || graphicInformation.OptionHatchFill)) { backgroundWorker?.ReportProgress(0, new MyUserState { Value = (actOpt++ * 100 / maxOpt), Content = "Generate hatch fill..." }); - Logger.Info("{0} Hatch fill", loggerTag); + Logger.Info("{0} Hatch fill distance:{1:0.00} angle:{2:0.00}", loggerTag, Properties.Settings.Default.importGraphicHatchFillDistance, Properties.Settings.Default.importGraphicHatchFillAngle); HatchFill(completeGraphic); SetHeaderInfo(string.Format(" Option: Hatch fill distance:{0:0.00} angle:{1:0.00}", Properties.Settings.Default.importGraphicHatchFillDistance, Properties.Settings.Default.importGraphicHatchFillAngle)); } diff --git a/GRBL-Plotter/GCodeCreation/GraphicGenerateHatchFill.cs b/GRBL-Plotter/GCodeCreation/GraphicGenerateHatchFill.cs index 7569b85e..1f63b5f2 100644 --- a/GRBL-Plotter/GCodeCreation/GraphicGenerateHatchFill.cs +++ b/GRBL-Plotter/GCodeCreation/GraphicGenerateHatchFill.cs @@ -29,7 +29,7 @@ You should have received a copy of the GNU General Public License using System; using System.Collections.Generic; -using System.Linq; +using System.Web.Hosting; using System.Windows; namespace GrblPlotter @@ -109,7 +109,7 @@ internal static void HatchFill(List graphicToFill) if (inset2) { - if (!ShrinkPaths(tmpPath, insetVal)) + if (!ShrinkPaths(tmpPath, insetVal)) { ShrinkPaths(tmpPath2, -insetVal); tmpPath.Clear(); @@ -209,6 +209,11 @@ private static void AddLinesToGraphic(List HatchLines, ItemPath PathDat if (logModification) Logger.Trace(" AddLinesToGraphic"); Point start, end; bool switchColor = graphicInformation.ApplyHatchFill; + + bool noiseAdd = !Properties.Settings.Default.importGraphicNoiseEnable && Properties.Settings.Default.importGraphicHatchFillNoise; + double noiseAmplitude = (double)Properties.Settings.Default.importGraphicNoiseAmplitude; + double noiseDensity = (double)Properties.Settings.Default.importGraphicNoiseDensity; + for (int i = 0; i < HatchLines.Count; i++) { if ((i % 2) > 0) @@ -225,11 +230,70 @@ private static void AddLinesToGraphic(List HatchLines, ItemPath PathDat // actualPath.Info.PathGeometry += "_hatch"; actualPath.Info.PathGeometry = "hatch_fill_" + actualPath.Info.PathGeometry; + if (noiseAdd) + AddNoiseToLine(start, end, i, noiseAmplitude, noiseDensity); + AddLine(end); StopPath(); } } + private static void AddNoiseToLine(Point start, Point end, int index, double amplitude, double density) + { + double x = start.X; + double y = start.Y; + double dx = end.X - start.X; + double dy = end.Y - start.Y; + double lineLength = Math.Sqrt(dx * dx + dy * dy); + double stepWidth = density; + int step = (int)Math.Ceiling(lineLength / stepWidth); + + if (step == 0) + { + return; + } + double dix = dx / step; + double diy = dy / step; + + double fx, fy; + if (dx == 0) + { fx = 1; fy = 0; } + else if (dy == 0) + { fx = 0; fy = 1; } + else + { + fx = dy / lineLength; fy = dx / lineLength; + } + fx *= (amplitude / 2); ; + fy *= (-amplitude / 2); ; + + float scale, n, nx = 0, ny = 0; + scale = 1;// (float)stepWidth / 2000; + + Logger.Trace("AddNoiseToPath step:{0}", step); + + if (step <= 1) + { + n = Noise.CalcPixel2D(index, 1, scale); + nx = (float)fx * n; + ny = (float)fy * n; + AddLine(x + nx, y + ny); + } + else + { + for (int i = 1; i < step; i++) + { + n = Noise.CalcPixel2D(index, i, scale); + nx = (float)fx * n; + ny = (float)fy * n; + x += dix; + y += diy; + AddLine(x + nx, y + ny); + } + AddLine(x + nx, y + ny); + } + } + private static double CalculateIntersection(Point p1, Point p2, Point p3, Point p4) { double d21x = p2.X - p1.X; @@ -369,18 +433,18 @@ private static void ClipLineByPolygone(Point p1, Point p2, List path // Remove duplicate intersections int i_last = 1; IntersectionInfo last = d_and_a[0]; - - for (int i=1; i < d_and_a.Count; i++) + + for (int i = 1; i < d_and_a.Count; i++) { //Logger.Trace(" s1:{0:0.000} slast:{1:0.000} diff:{2:0.000}", d_and_a[i].s , last.s, Math.Abs(d_and_a[i].s - last.s)); - if ((Math.Abs(d_and_a[i].s - last.s)) > 0.0000000001) + if ((Math.Abs(d_and_a[i].s - last.s)) > 0.0000000001) { d_and_a[i_last] = last = d_and_a[i]; // different positions - take over i_last++; } - else - { - d_and_a[--i_last] = last = d_and_a[i]; // same positions - skip both 2023-11-07 + else + { + d_and_a[--i_last] = last = d_and_a[i]; // same positions - skip both 2023-11-07 } } diff --git a/GRBL-Plotter/GCodeCreation/GraphicGenerateSpecial.cs b/GRBL-Plotter/GCodeCreation/GraphicGenerateSpecial.cs index 0da86e21..70653b7c 100644 --- a/GRBL-Plotter/GCodeCreation/GraphicGenerateSpecial.cs +++ b/GRBL-Plotter/GCodeCreation/GraphicGenerateSpecial.cs @@ -604,4 +604,404 @@ private static void AddBackgroundText(Point pos, float emSize, string txt) catch (Exception err) { Logger.Error(err, "AddBackgroundText "); } } } + + + //https://github.com/WardBenjamin/SimplexNoise/blob/master/SimplexNoise/Noise.cs + public static class Noise + { + /// + /// Creates 1D Simplex noise + /// + /// The number of points to generate + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 1D Simplex noise + public static float[] Calc1D(int width, float scale) + { + var values = new float[width]; + for (var i = 0; i < width; i++) + values[i] = Generate(i * scale) * 128 + 128; + return values; + } + + /// + /// Creates 2D Simplex noise + /// + /// The number of points to generate in the 1st dimension + /// The number of points to generate in the 2nd dimension + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 2D Simplex noise + public static float[,] Calc2D(int width, int height, float scale) + { + var values = new float[width, height]; + for (var i = 0; i < width; i++) + for (var j = 0; j < height; j++) + values[i, j] = Generate(i * scale, j * scale) * 128 + 128; + return values; + } + + /// + /// Creates 3D Simplex noise + /// + /// The number of points to generate in the 1st dimension + /// The number of points to generate in the 2nd dimension + /// The number of points to generate in the 3nd dimension + /// The scale of the noise. The greater the scale, the denser the noise gets + /// An array containing 3D Simplex noise + public static float[,,] Calc3D(int width, int height, int length, float scale) + { + var values = new float[width, height, length]; + for (var i = 0; i < width; i++) + for (var j = 0; j < height; j++) + for (var k = 0; k < length; k++) + values[i, j, k] = Generate(i * scale, j * scale, k * scale) * 128 + 128; + return values; + } + + /// + /// Gets the value of an index of 1D simplex noise + /// + /// Index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 1D simplex noise + public static float CalcPixel1D(int x, float scale) + { + return Generate(x * scale);// * 128 + 128; + } + + /// + /// Gets the value of an index of 2D simplex noise + /// + /// 1st dimension index + /// 2st dimension index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 2D simplex noise + public static float CalcPixel2D(int x, int y, float scale) + { + return Generate(x * scale, y * scale);// * 128 + 128; + } + + + /// + /// Gets the value of an index of 3D simplex noise + /// + /// 1st dimension index + /// 2nd dimension index + /// 3rd dimension index + /// The scale of the noise. The greater the scale, the denser the noise gets + /// The value of an index of 3D simplex noise + public static float CalcPixel3D(int x, int y, int z, float scale) + { + return Generate(x * scale, y * scale, z * scale) * 128 + 128; + } + + static Noise() + { + _perm = new byte[PermOriginal.Length]; + PermOriginal.CopyTo(_perm, 0); + } + + /// + /// Arbitrary integer seed used to generate lookup table used internally + /// + public static int Seed + { + get => _seed; + set + { + if (value == 0) + { + _perm = new byte[PermOriginal.Length]; + PermOriginal.CopyTo(_perm, 0); + } + else + { + _perm = new byte[512]; + var random = new Random(value); + random.NextBytes(_perm); + } + + _seed = value; + } + } + + private static int _seed; + + /// + /// 1D simplex noise + /// + /// + /// + private static float Generate(float x) + { + var i0 = FastFloor(x); + var i1 = i0 + 1; + var x0 = x - i0; + var x1 = x0 - 1.0f; + + var t0 = 1.0f - x0 * x0; + t0 *= t0; + var n0 = t0 * t0 * Grad(_perm[i0 & 0xff], x0); + + var t1 = 1.0f - x1 * x1; + t1 *= t1; + var n1 = t1 * t1 * Grad(_perm[i1 & 0xff], x1); + // The maximum value of this noise is 8*(3/4)^4 = 2.53125 + // A factor of 0.395 scales to fit exactly within [-1,1] + return 0.395f * (n0 + n1); + } + + /// + /// 2D simplex noise + /// + /// + /// + /// + private static float Generate(float x, float y) + { + const float F2 = 0.366025403f; // F2 = 0.5*(sqrt(3.0)-1.0) + const float G2 = 0.211324865f; // G2 = (3.0-Math.sqrt(3.0))/6.0 + + float n0, n1, n2; // Noise contributions from the three corners + + // Skew the input space to determine which simplex cell we're in + var s = (x + y) * F2; // Hairy factor for 2D + var xs = x + s; + var ys = y + s; + var i = FastFloor(xs); + var j = FastFloor(ys); + + var t = (i + j) * G2; + var X0 = i - t; // Unskew the cell origin back to (x,y) space + var Y0 = j - t; + var x0 = x - X0; // The x,y distances from the cell origin + var y0 = y - Y0; + + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { i1 = 1; j1 = 0; } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { i1 = 0; j1 = 1; } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + var y1 = y0 - j1 + G2; + var x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords + var y2 = y0 - 1.0f + 2.0f * G2; + + // Wrap the integer indices at 256, to avoid indexing perm[] out of bounds + var ii = Mod(i, 256); + var jj = Mod(j, 256); + + // Calculate the contribution from the three corners + var t0 = 0.5f - x0 * x0 - y0 * y0; + if (t0 < 0.0f) n0 = 0.0f; + else + { + t0 *= t0; + n0 = t0 * t0 * Grad(_perm[ii + _perm[jj]], x0, y0); + } + + var t1 = 0.5f - x1 * x1 - y1 * y1; + if (t1 < 0.0f) n1 = 0.0f; + else + { + t1 *= t1; + n1 = t1 * t1 * Grad(_perm[ii + i1 + _perm[jj + j1]], x1, y1); + } + + var t2 = 0.5f - x2 * x2 - y2 * y2; + if (t2 < 0.0f) n2 = 0.0f; + else + { + t2 *= t2; + n2 = t2 * t2 * Grad(_perm[ii + 1 + _perm[jj + 1]], x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 40.0f * (n0 + n1 + n2); // TODO: The scale factor is preliminary! + } + + + private static float Generate(float x, float y, float z) + { + // Simple skewing factors for the 3D case + const float F3 = 0.333333333f; + const float G3 = 0.166666667f; + + float n0, n1, n2, n3; // Noise contributions from the four corners + + // Skew the input space to determine which simplex cell we're in + var s = (x + y + z) * F3; // Very nice and simple skew factor for 3D + var xs = x + s; + var ys = y + s; + var zs = z + s; + var i = FastFloor(xs); + var j = FastFloor(ys); + var k = FastFloor(zs); + + var t = (i + j + k) * G3; + var X0 = i - t; // Unskew the cell origin back to (x,y,z) space + var Y0 = j - t; + var Z0 = k - t; + var x0 = x - X0; // The x,y,z distances from the cell origin + var y0 = y - Y0; + var z0 = z - Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + + /* This code would benefit from a backport from the GLSL version! */ + if (x0 >= y0) + { + if (y0 >= z0) + { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; } // X Y Z order + else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; } // X Z Y order + else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; } // Z X Y order + } + else + { // x0 0) ? ((int)x) : (((int)x) - 1); + } + + private static int Mod(int x, int m) + { + var a = x % m; + return a < 0 ? a + m : a; + } + + private static float Grad(int hash, float x) + { + var h = hash & 15; + var grad = 1.0f + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0 + if ((h & 8) != 0) grad = -grad; // Set a random sign for the gradient + return (grad * x); // Multiply the gradient with the distance + } + + private static float Grad(int hash, float x, float y) + { + var h = hash & 7; // Convert low 3 bits of hash code + var u = h < 4 ? x : y; // into 8 simple gradient directions, + var v = h < 4 ? y : x; // and compute the dot product with (x,y). + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -2.0f * v : 2.0f * v); + } + + private static float Grad(int hash, float x, float y, float z) + { + var h = hash & 15; // Convert low 4 bits of hash code into 12 simple + var u = h < 8 ? x : y; // gradient directions, and compute dot product. + var v = h < 4 ? y : h == 12 || h == 14 ? x : z; // Fix repeats at h = 12 to 15 + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -v : v); + } + + private static float Grad(int hash, float x, float y, float z, float t) + { + var h = hash & 31; // Convert low 5 bits of hash code into 32 simple + var u = h < 24 ? x : y; // gradient directions, and compute dot product. + var v = h < 16 ? y : z; + var w = h < 8 ? z : t; + return ((h & 1) != 0 ? -u : u) + ((h & 2) != 0 ? -v : v) + ((h & 4) != 0 ? -w : w); + } + } }