Comparing two sets of data (x,y) - c#

I have stored numerical data in lists with its coordinates (xValues, yValues), and if I want to compare (add, subtract, divide...) that set of data to another I have to be aware of that I can't compare if the xValues don't match (because there is nothing to compare with). So I need to interpolate linearly between the "missing" xValues, that actually exist in the other set and generate new points. Please check this picture:
The cyan squares on the red line represent the stored points (xValues2), and (generally) they won't match the other's set xValues (xValues1). The two squares on the green line are examples of the desired generated points. With them I can work with this two graphs without problem.
For linear interpolation It's pretty straightforward: If I have two points (x0,y0) and (x1,y1) and I want to add a new point between them given a "x2":
y2=y0+(x2-x0)*(y1-y0)/(x1-x0)
To make this work I think I have to implement something like this:
Create new lists (xValuesNew, yValuesNew).
Make a union between xValues1 and xValues2 (xValuesNew).
Check what are the differences between the original xValues1 and the xValuesNew.
For each new value found generate the "y" using the formula written above.
Put that 4 steps in a method and use it again but now with the set2.
I've been on this all day, trying to find an easy solution, maybe using Linq or lambda expressions but I'm not used to work with them and my lack of knowledge on that topics ishuge. Note that this operation will be made pretty often so I have to make it not too heavy. I've thought that it will be a good idea to generate a new list instead inserting points in the middle of the original for that reason.
Please if someone could guide me a little bit or tell me if there is a math library actually doing this would be great. Thank you.
EDIT: Sorry if I haven't explained me properly.
Here I have an example (done in Excel):
Note that I can't directly add together Series1 and Series2 (+) or any other operation because the X spacing in them is different. So what I want is to generate a new point in the Series1 when is needed.
For that I would like to simple use a linear interpolation. Say that I have P1(0,40) and P2(0,60) in series1, but in series2 I have a point (1,10). I need to generate a point P3 between P1 and P2 with (1,50) coordinates.
I was trying to do this with SkipWhile and comparing the next X value of both series, if XValue of series1 is lower, then add that XValue and corresponding YValue in the newSeries. Else use the XValue2 for generating an Y and add it to the newSeries. Here is one of my attempts (doesn't work):
List<double> series1X = new List<double> { 0, 2, 4, 6, 8 };
List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
List<double> series2X = new List<double> { 0, 1, 7, 8,9 };
List<double> newSeries1X = new List<double>();
List<double> newSeries1Y = new List<double>();
double lastX1 = series1X[series1X.Count()-1];
int i = 0;
while (next1X <= lastX1)
{
next2X = series2X.SkipWhile(p => p <= next1X).First();
Console.WriteLine(next2X.ToString());
if (next1X <= next2X)
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
}
if (next2X < next1X)
{
while (next2X < next1X)
{
newSeries1X.Add(next2X);
newY = series1Y[i] + (next2X - series1X[i]) * (series1Y[i + 1] - series1Y[i]) / (series1X[i + 1] - series1X[i]);
newSeries1Y.Add(newY);
next2X = series2X.SkipWhile(p => p <= next2X).First();
}
}
next1X = series1X.SkipWhile(p => p <= next2X).First();
Console.WriteLine(next1X.ToString());
i++;
}
It would be AWESOME to do this with your Zip method. But I have no idea how to write that condition in the predicate.

First off, I'd probably use an appropriate 'point' class that contains both the x and y coordinates instead of two separate lists for each coordinate. Then you can use the Zip method to quickly iterate through them:
IEnumerable<PointF> points0 = ...
IEnumerable<PointF> points0 = ...
float x2 = ...
IEnumerable<PointF> newPoints = point0.Zip(points1,
(p0, p1) => new PointF(p0.X, p0.Y + (x2-p0.X) * (p1.Y-p0.Y) / (p1.X-p0.X)));
This makes it easy to calculate a new set of points from your input data. If you just care about a single y-value, you can still do this with your current data, it will just look weird:
IEnumerable<double> y2values =
xValues1.Zip(yValues1, (x, y) => new { x, y }).Zip(
xValues2.Zip(yValues2, (x, y) => new { x, y }),
(p0, p1) => p0.y + (x2-p0.x) * (p1.y-p0.y) / (p1.x-p0.x));
I appologize if in the process of coding this answer I somehow mangled your math.
Update
Now that I have a better grasp on what you're trying to do, I don't think any Linq method will work out quite right. Here what I've come up with using indexes:
List<double> series1X = new List<double> { 0, 2, 4, 6, 8 };
List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
List<double> series2X = new List<double> { 0, 1, 7, 8, 9 };
// in the worst case there are n + m new points
List<double> newSeries1X = new List<double>(series1X.Count + series2X.Count);
List<double> newSeries1Y = new List<double>(series1X.Count + series2X.Count);
int i = 0, j = 0;
for ( ; i < series1X.Count && j < series2X.Count; )
{
if (series1X[i] <= series2X[j])
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
if (series1X[i] == series2X[j])
{
j++;
}
i++;
}
else
{
int k = (i == 0) ? i : i - 1;
// interpolate
double y0 = series1Y[k];
double y1 = series1Y[k + 1];
double x0 = series1X[k];
double x1 = series1X[k + 1];
double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
newSeries1X.Add(series2X[j]);
newSeries1Y.Add(y);
j++;
}
}
for ( ; i < series1X.Count; i++)
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
}
for ( ; j < series2X.Count; j++)
{
// interpolate
double y0 = series1Y[i - 2];
double y1 = series1Y[i - 1];
double x0 = series1X[i - 2];
double x1 = series1X[i - 1];
double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
newSeries1X.Add(series2X[j]);
newSeries1Y.Add(y);
}
Output is
newSeries1X = { 0, 1, 2, 4, 6, 7, 8, 0 }
newSeries1Y = { 120, 110, 100, 110, 105, 87.5, 70, 52.5 }
This solution handles cases where the first series2X[0] < series1X[0] and when series2X[n] > series1X[m] by linearly 'projecting' the data outward from the first / last pair of points.
Here's another solution using enumerators (mostly), but it's not nearly as elegant as I'd hoped it would be. It could probably be improved a bit:
bool hasS1 = true, hasS2 = true, preinterp = true;
double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x = 0, y = 0;
using(var s1xEnumerator = series1X.GetEnumerator())
using(var s1yEnumerator = series1Y.GetEnumerator())
using(var s2xEnumerator = series2X.GetEnumerator())
{
hasS1 = s1xEnumerator.MoveNext();
hasS2 = s2xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
while(hasS1 && hasS2)
{
x1 = s1xEnumerator.Current;
y1 = s1yEnumerator.Current;
x = s2xEnumerator.Current;
if (x1 <= x)
{
newSeries1X.Add(x1);
newSeries1Y.Add(y1);
hasS1 = s1xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
preinterp = false;
if (hasS1)
{
x0 = x1;
y0 = y1;
}
if (x1 == x)
{
hasS2 = s2xEnumerator.MoveNext();
}
}
else
{
// we have to look ahead to get the next interval to interpolate before x0
if (preinterp)
{
x0 = x1;
y0 = y1;
x1 = series1X[1]; // can't peek with enumerator
y1 = series1Y[1];
preinterp = false;
}
y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
newSeries1X.Add(x);
newSeries1Y.Add(y);
hasS2 = s2xEnumerator.MoveNext();
}
}
while(hasS1)
{
newSeries1X.Add(s1xEnumerator.Current);
newSeries1Y.Add(s1yEnumerator.Current);
hasS1 = s1xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
}
while(hasS2)
{
x = s2xEnumerator.Current;
y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
newSeries1X.Add(x);
newSeries1Y.Add(y);
hasS2 = s2xEnumerator.MoveNext();
}
}

For working with two series with different spacing I first need to generate points in the first set, then in the second (with the same method) and finally sum point to point.
Here is the code for that method:
using OxyPlot.Series;
using OxyPlot;
namespace Algorithm1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<DataPoint> S1 = new List<DataPoint> ();
List<DataPoint> S2 = new List<DataPoint>();
List<DataPoint> NS1 = new List<DataPoint>();
S1.Add(new DataPoint(4, 10));
S1.Add(new DataPoint(6, 20));
S1.Add(new DataPoint(8, 15));
S1.Add(new DataPoint(9, 70));
S1.Add(new DataPoint(10, 5));
S2.Add(new DataPoint(1, 0));
S2.Add(new DataPoint(2, 0));
S2.Add(new DataPoint(3, 0));
S2.Add(new DataPoint(6, 0));
S2.Add(new DataPoint(7, 0));
S2.Add(new DataPoint(8.1, 0));
S2.Add(new DataPoint(8.2, 0));
S2.Add(new DataPoint(8.3, 0));
S2.Add(new DataPoint(8.4, 0));
S2.Add(new DataPoint(9, 0));
S2.Add(new DataPoint(9.75, 0));
S2.Add(new DataPoint(11, 0));
S2.Add(new DataPoint(12, 0));
S2.Add(new DataPoint(16, 0));
NS1 = GetMiddlePoints(S1, S2);
foreach (DataPoint pointin NS1)
{
MessageBox.Show( point.X.ToString()+" : "+ point.Y.ToString());
}
}
#region GetMiddlePoints
private List<DataPoint> GetMiddlePoints(List<DataPoint> S1, List<DataPoint> S2)
{
List<DataPoint> NS1 = new List<DataPoint>();
int i = 0;
int j = S2.TakeWhile(p => p.X < S1[0].X).Count();
int PointsInS1 = S1.Count;
int PointsInS2 = S2.Count;
DataPoint newPoint = new DataPoint();
while (i < PointsInS1 )
{
if (j < PointsInS2 )
{
if (S1[i].X < S2[j].X)
{
newPoint = S1[i];
NS1.Add(newPoint );
i++;
}
else if (S1[i].X == S2[j].X)
{
newPoint = S1[i];
NS1.Add(newPoint );
i++;
j++;
}
else if (S1[i].X > S2[j].X)
{
newPoint .X = S2[j].X;
newPoint .Y = S1[i-1].Y + (S2[j].X - S1[i-1].X) * (S1[i].Y - S1[i-1].Y) / (S1[i].X - S1[i-1].X);
NS1.Add(newPoint );
j++;
}
}
if (j == PointsInS2 )
{
newPoint = S1[i];
NS1.Add(newPoint );
i++;
}
}
return NS1;
}
#endregion
}
}

Related

How to convert the following C++ code to C#

1. sort(arr1.begin(), arr1.end(), [](Point2f lhs, Point2f rhs) { return lhs.x<rhs.x; } );
2. sort(arr1.begin(), arr1.begin()+2, [](Point2f lhs, Point2f rhs) { return lhs.y<rhs.y; });
3. sort(arr1.begin()+2, arr1.end(), [](Point2f lhs, Point2f rhs) { return lhs.y<rhs.y; });
I'm attempting to sort an array of points (top-left --> bottom-left --> top-right --> bottom-right). I'd like to convert the above to C# but I'm unsure how to accomplish the above. I have done the following so far:
var sortedArr1 = arr1.OrderBy(r => r.X).ThenBy(r=>r.Y).ToList();
Which I believe accomplishes statements 1 and 2, but doesn't solve the last statement.
[EDIT]
Based on the comments below I have added a snippet from source code.
public List<Point2d> DetectCorners(Mat src)
{
var lines = src.HoughLines(1, Cv2.PI / 180, 50, 0, 0);
Mat labels = new Mat();
Mat centers = new Mat();
List<Point2f> data = new List<Point2f>();
for (uint i = 0; i < lines.Length; i++)
{
float rho = lines[i].Rho;
float theta = lines[i].Theta;
float x = rho * (float)Math.Cos(theta);
float y = rho * (float)Math.Sin(theta);
data.Add(new Point2f(x, y));
}
Cv2.Kmeans(InputArray.Create(data), 4, labels,
new TermCriteria(CriteriaType.Eps & CriteriaType.Count, 10, 1.0), 5, KMeansFlags.PpCenters, centers);
List<Point2f> fourPoints = new List<Point2f>();
List<Point2f> xyPoints = new List<Point2f>();
for (int i = 0; i < 4; i++)
{
float x = centers.At<float>(i, 0);
float y = centers.At<float>(i, 1);
float rho = (float)Math.Sqrt(x * x + y * y);
float theta = (float)Math.Atan2(y, x);
xyPoints.Add(new Point2f(x, y));
fourPoints.Add(new Point2f(rho, theta));
}
var sortedXyPoints = xyPoints.OrderBy(r => Math.Abs(r.Y / r.X)).ToArray();
List<Point2d> ans = new List<Point2d>();
for (uint i = 0; i < 2; i++)
{
float x0 = sortedXyPoints[i].X;
float y0 = sortedXyPoints[i].Y;
for (uint j = 2; j < 4; j++)
{
float x1 = sortedXyPoints[j].X;
float y1 = sortedXyPoints[j].Y;
float x = (y0 * (x1 * x1 + y1 * y1) - y1 * (x0 * x0 + y0 * y0)) / (y0 * x1 - x0 * y1);
float y = (x0 * (x1 * x1 + y1 * y1) - x1 * (x0 * x0 + y0 * y0)) / (y1 * x0 - x1 * y0);
ans.Add(new Point2d(x, y));
}
}
// order of points (top-left, bottom-left, top-right, bottom-right)
var sortedAns = ans.OrderBy(r => r.X).ThenBy(r=>r.Y).ToArray();
//TODO: convert sort(arr1.begin()+2, arr1.end(), [](Point2f lhs, Point2f rhs) { return lhs.y<rhs.y; }); to c#
return new List<Point2d>(sortedAns);
}
If you want to replicate the exact behavior you can use Array.Sort or List.Sort that takes a start and length of the range to sort. You will need to implement an IComparer<T> instead of using a delegate, but it is fairly trivial to make a implementation that takes a delegate to compare a property of the object. This should let you replicate the exact behavior:
public class KeyComparer<T, TKey> : IComparer<T> where TKey : IComparable<TKey>
{
private readonly Func<T, TKey> selector;
public KeyComparer(Func<T, TKey> selector) => this.selector = selector;
public int Compare(T x, T y) => selector(x).CompareTo(selector(y));
}
...
var xComparer = new KeyComparer<Point, float>(p => p.X);
var yComparer = new KeyComparer<Point, float>(p => p.Y);
myList.Sort(xComparer);
myList.Sort(0, 2, yComparer);
myList.Sort(2, myList.Length -2, yComparer);
However, I would be very hesitant to just use such a solution without understanding why this is done. I would see if I could find the description of the implemented algorithm, and see if that matches the implementation, or look for other implementations and see if they uses the same kind of sorting.
There is a useful tool for converting short snippets of codes like that.
Notice that the free edition limits output to 100 lines per file (no limit on the number of files).
visit:
https://www.tangiblesoftwaresolutions.com/product_details/cplusplus_to_csharp_converter_details.html

TeeChart - Rotating Multiline Annotation (Text) Tool gives wrong result

Currently I am working on one requirement, wherein I want rotate TeeChart's Annotation Tool (.Net Version) which is having Multiline Text. I successfully updated code to rotate single line Annotation Tool. The code snippet of which can be found below.
string[] s = tmp.Split(Texts.LineSeparator);
int tmpCenterX, tmpCenterY;
Point tmpP = new Point(0, 0);
Rectangle r = shape.ShapeBounds;
if (Angle != 0)
{
Graphics3D.RectCenter(r, out tmpCenterX, out tmpCenterY);
double tmp1 = Angle * Utils.PiStep;
int tmpNumRow = 1;
int tmpRowHeight = tmpHeight;
double S = Math.Sin(tmp1);
double C = Math.Cos(tmp1);
int tmpY = tmpNumRow * tmpRowHeight - (r.Bottom - tmpCenterY);
tmpP.X = Utils.Round(tmpCenterX - Utils.Round(tmpY * S));
tmpP.Y = Utils.Round(tmpCenterY - Utils.Round(tmpY * C));
tmpP.X = tmpCenterX;
tmpP.Y = r.Top + tmpNumRow * tmpRowHeight;
if (shape.Visible)
{
shape.DrawRectRotated(g, r, Utils.Round(Angle % 360), 0);
}
}
for (int t = 1; t <= tmpN; t++)
{
if (TextAlign == StringAlignment.Center && tmpN > 1)
{
float auxW = g.Chart.Graphics3D.TextWidth(s[t - 1]);
x = Utils.Round((shape.ShapeBounds.Left + shape.ShapeBounds.Right) / 2) - Utils.Round(auxW / 2);
}
if (Angle != 0)
{
Point rotatedPoint = RotatedPoint(r, (int)Angle);
g.RotateLabel(rotatedPoint.X , rotatedPoint.Y + tmpHeight * (t - 1), 0, s[t - 1], (float)Angle);
}
else
{
CalcTextXY(ref x, ref y);
DrawString(g, x, y, t, tmpHeight, s);
}
}
But for multiline text this code is not working. Please check the following screenshot for error I am getting.
Error in Multiline Rotated Text
Any help on this will be of great help. Thanking you in advance.
RotateLabel does it correctly for me here:
private void TChart1_AfterDraw(object sender, Steema.TeeChart.Drawing.Graphics3D g)
{
String tmp = "Multiline\nText\nAngle 45 Degree";
g.RotateLabel(100, 150, tmp, 45);
}

Plotting a line in a chart given the y intercept and slope

I've written a program that calculates the line of best fit (intercept/slope) given several input values from the user. I've plotted each of the individual values, however unsure of the code to plot the line given the slope and y-intercept.
This is the slope:
double m = ( aXY.Sum() -
((levels.Sum() * scores.Sum()) / 5)) / (newaX.Sum() - ((powLevels) / 5));
The Intercept
double b = meanY - (m * meanX);
Plotting of points
for (int i = 0; i < levels.GetLength(0); i++)
{
chart1.Series["Series1"].Points
.AddXY(levels.GetValue(i), scores.ToArray().GetValue(i));
}
Any ideas? I am by no means an expert and getting this far took a fair bit of experimenting..
Assuming your data are plotted as a scattergraph using the ChartType.Points the simplest way to add a line is to add one extra Series with ChartType.Line and set two points there.
There are other ways to create a line on a Chart, like drawing it or creating a LineAnnotation, but they are much more complicated!
Following this example to the letter here is an implementation:
Note that after creating the series for the line of best fit the thing you were looking for are just the last two lines..:
private void button1_Click(object sender, EventArgs e)
{
// create TWO series!
chart1.Series.Clear();
chart1.Series.Add("Data");
chart1.Series.Add("Line of best fit");
chart1.Series[0].ChartType = SeriesChartType.Point;
chart1.Series[1].ChartType = SeriesChartType.Line;
List<int> levels = new List<int>() { 8, 2, 11, 6, 5, 4, 12, 9, 6, 1};
List<int> scores = new List<int>() { 3, 10, 3, 6, 8, 12, 1, 4, 9, 14};
double minX = levels.ToList().Min();
double maxX = levels.ToList().Max();
double meanX = 1f * levels.Sum() / levels.Count;
double meanY = 1f * scores.Sum() / scores.Count;
double st = 0;
double sb = 0;
for (int i = 0; i < levels.Count; i++ )
{
st += (levels[i] - meanX) * (scores[i] - meanY);
sb += (levels[i] - meanX) * (levels[i] - meanX);
}
double slope = st / sb;
double y0 = meanY - slope * meanX; // y-intercept or y-crossing
for (int i = 0; i < levels.Count; i++)
{
chart1.Series[0].Points.AddXY(levels[i], scores[i]);
}
// this is the part that creates the line of best fit:
chart1.Series[1].Points.AddXY(minX, y0 + minX * slope);
chart1.Series[1].Points.AddXY(maxX, y0 + maxX * slope);
}
If you want to you can add the first line point right at the y-axis:
chart1.Series[1].Points.AddXY(0, y0 );
In this case you may want to set the minimum x-values shown in the chart to prevent it from including -1, maybe like this:
chart1.ChartAreas[0].AxisX.Minimum = minX - 1;

How do I get all the points between two Point objects?

Lets say I have my first Point struct:
Point start = new Point(1, 9);
and my second:
Point end = new Point(4, 9);
I want to get all the points between the start and end. So for example I would want 2,9 and 3,9 in an array. Does .NET have something built in for this?
This is what I ended up doing. As #Cody Gray mentioned in his comment, there are infinite points on a line. So you need to specify how many points you are looking to retrieve.
My Line class:
public class Line {
public Point p1, p2;
public Line(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
}
public Point[] getPoints(int quantity) {
var points = new Point[quantity];
int ydiff = p2.Y - p1.Y, xdiff = p2.X - p1.X;
double slope = (double)(p2.Y - p1.Y) / (p2.X - p1.X);
double x, y;
--quantity;
for (double i = 0; i < quantity; i++) {
y = slope == 0 ? 0 : ydiff * (i / quantity);
x = slope == 0 ? xdiff * (i / quantity) : y / slope;
points[(int)i] = new Point((int)Math.Round(x) + p1.X, (int)Math.Round(y) + p1.Y);
}
points[quantity] = p2;
return points;
}
}
Usage:
var line = new Line(new Point(10, 15), new Point(297, 316));
var points = line.getPoints(20);
That will return a Point array of 20 Points evenly spaced between the two endpoints (inclusive). Hope that helps!
There are no build in functions for this, since there are no points between points. Mathematicaly there is a line between two points. In terms of Computer-Graphics, lines could be antialiased and so beeing not rounded to full Integer numbers.
If you are looking for a fast method of creating all integral numbers inbetween, I guess Bresenhams-Line-Algorithm would be your choice. But this is not build into .NET, you have to code it by yourself (or take Matthew Watson's implementation):
http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
There are even fasther algorithms for doing it, but I would go for Bresenham.
I know it's been quite a long time since you first asked this question but I was looking for something similar recently and found this wiki (https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm), which contained pseudo code.
So I've implemented a function with the pseudo code to perform this calculation and add the points to a List.
public List<Point> GetPoints(Point p1, Point p2)
{
List<Point> points = new List<Point>();
// no slope (vertical line)
if (p1.X == p2.X)
{
for (double y = p1.Y; y <= p2.Y; y++)
{
Point p = new Point(p1.X, y);
points.Add(p);
}
}
else
{
// swap p1 and p2 if p2.X < p1.X
if (p2.X < p1.X)
{
Point temp = p1;
p1 = p2;
p2 = temp;
}
double deltaX = p2.X - p1.X;
double deltaY = p2.Y - p1.Y;
double error = -1.0f;
double deltaErr = Math.Abs(deltaY / deltaX);
double y = p1.Y;
for (double x = p1.X; x <= p2.X; x++)
{
Point p = new Point(x, y);
points.Add(p);
Debug.WriteLine("Added Point: " + p.X.ToString() + "," + p.Y.ToString());
error += deltaErr;
Debug.WriteLine("Error is now: " + error.ToString());
while (error >= 0.0f)
{
Debug.WriteLine(" Moving Y to " + y.ToString());
y++;
points.Add(new Point(x, y));
error -= 1.0f;
}
}
if (points.Last() != p2)
{
int index = points.IndexOf(p2);
points.RemoveRange(index + 1, points.Count - index - 1);
}
}
return points;
}
You can get between points using below code. User just need to define that how many points to get between two points. Here I defined as 10 points.
PointF pStart = new PointF(10, 10);
PointF pEnd = new PointF(100, 100);
PointF[] betPoints = new PointF[10];
for (int i = 1; i <= 10; i++)
{
betPoints[i].X = (Math.Abs(pStart.X - pEnd.X) / 10) * i + pEnd.X;
betPoints[i].Y = (Math.Abs(pStart.Y - pEnd.Y) / 10) * i + pEnd.Y;
}

2D Perlin Noise

I have fully mastered the art of Perlin Noise in 3D, and now I'm trying to use my same implementation for a 2D algorithm.
The problem seems to be in picking my gradient directions. In 3D I use 16 gradients in evenly distributed directions and this works great.
In 2D I figured I'd use 8 gradients. up, down, left, right, and the four diagonal directions.
Here is what I get:
The general look of the noise is always correct, but the edges of the squares don't quite match up.
I have also tried using other gradients or fewer gradients but get similar results.
Here in another example you can see that the edges do match up sometimes and the results are fine in that area -
When I don't use gradients and instead just interpolate between a value picked randomly at each of the 4 corners I get the right results, which is what makes me think it is the gradient part that is messing it up.
Here is my code:
//8 different gradient directions
private Point[] grads = new Point[] {
new Point(0, 1), new Point(1, 1), new Point(1, 0), new Point(1, -1),
new Point(0, -1), new Point(-1, -1), new Point(-1, 0), new Point(-1, 1),};
//takes the dot product of a gradient and (x, y)
private float dot2D(int i, float x, float y)
{
return
grads[i].X * x + grads[i].Y * y;
}
public float Noise2D(float x, float y)
{
int
ix = (int)(x),
iy = (int)(y);
x = x - ix;
y = y - iy;
float
fx = fade(x),
fy = fade(y);
ix &= 255;
iy &= 255;
// here is where i get the index to look up in the list of
// different gradients.
// hashTable is my array of 0-255 in random order
int
g00 = hashTable[ix + hashTable[iy ]],
g10 = hashTable[ix + 1 + hashTable[iy ]],
g01 = hashTable[ix + hashTable[iy + 1]],
g11 = hashTable[ix + 1 + hashTable[iy + 1]];
// this takes the dot product to find the values to interpolate between
float
n00 = dot2D(g00 & 7, x, y),
n10 = dot2D(g10 & 7, x, y),
n01 = dot2D(g01 & 7, x, y),
n11 = dot2D(g11 & 7, x, y);
// lerp() is just normal linear interpolation
float
y1 = lerp(fx, n00, n10),
y2 = lerp(fx, n01, n11);
return
lerp(fy, y1, y2);
}
I'm in a bit of a rush, but this might be helpful. I adapted Perlin's reference implementation to C#. For 2D, just use the 3D Noise() function with a fixed z parameter. (public static float Noise(float x, float y, float z) towards the end of the class.)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Diagnostics;
namespace GoEngine.Content.Entities
{
public class NoiseMaker
{
/// adapted from http://cs.nyu.edu/~perlin/noise/
// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.
private static int[] p = new int[512];
private static int[] permutation = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
static NoiseMaker()
{
CalculateP();
}
private static int _octaves;
private static int _halfLength = 256;
public static void SetOctaves(int octaves)
{
_octaves = octaves;
var len = (int)Math.Pow(2, octaves);
permutation = new int[len];
Reseed();
}
private static void CalculateP()
{
p = new int[permutation.Length * 2];
_halfLength = permutation.Length;
for (int i = 0; i < permutation.Length; i++)
p[permutation.Length + i] = p[i] = permutation[i];
}
public static void Reseed()
{
var random = new Random();
var perm = Enumerable.Range(0, permutation.Length).ToArray();
for (var i = 0; i < perm.Length; i++)
{
var swapIndex = random.Next(perm.Length);
var t = perm[i];
perm[i] = perm[swapIndex];
perm[swapIndex] = t;
}
permutation = perm;
CalculateP();
}
public static float Noise(Vector3 position, int octaves, ref float min, ref float max)
{
return Noise(position.X, position.Y, position.Z, octaves, ref min, ref max);
}
public static float Noise(float x, float y, float z, int octaves, ref float min, ref float max)
{
var perlin = 0f;
var octave = 1;
for (var i = 0; i < octaves; i++)
{
var noise = Noise(x * octave, y * octave, z * octave);
perlin += noise / octave;
octave *= 2;
}
perlin = Math.Abs((float)Math.Pow(perlin,2));
max = Math.Max(perlin, max);
min = Math.Min(perlin, min);
//perlin = 1f - 2 * perlin;
return perlin;
}
public static float Noise(float x, float y, float z)
{
int X = (int)Math.Floor(x) % _halfLength;
int Y = (int)Math.Floor(y) % _halfLength;
int Z = (int)Math.Floor(z) % _halfLength;
if (X < 0)
X += _halfLength;
if (Y < 0)
Y += _halfLength;
if (Z < 0)
Z += _halfLength;
x -= (int)Math.Floor(x);
y -= (int)Math.Floor(y);
z -= (int)Math.Floor(z);
var u = Fade(x);
var v = Fade(y);
var w = Fade(z);
int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF
B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS,
return MathHelper.Lerp(
MathHelper.Lerp(
MathHelper.Lerp(
Grad(p[AA], x, y, z) // AND ADD
,
Grad(p[BA], x - 1, y, z) // BLENDED
,
u
)
,
MathHelper.Lerp(
Grad(p[AB], x, y - 1, z) // RESULTS
,
Grad(p[BB], x - 1, y - 1, z)
,
u
)
,
v
)
,
MathHelper.Lerp(
MathHelper.Lerp(
Grad(p[AA + 1], x, y, z - 1) // CORNERS
,
Grad(p[BA + 1], x - 1, y, z - 1) // OF CUBE
,
u
)
,
MathHelper.Lerp(
Grad(p[AB + 1], x, y - 1, z - 1)
,
Grad(p[BB + 1], x - 1, y - 1, z - 1)
,
u
)
,
v
)
,
w
);
}
static float Fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
static float Grad(int hash, float x, float y, float z)
{
int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
}
}
Update
Okay, I managed to create a working 2D version. Here's the class:
/// implements improved Perlin noise in 2D.
/// Transcribed from http://www.siafoo.net/snippet/144?nolinenos#perlin2003
/// </summary>
public static class Noise2d
{
private static Random _random = new Random();
private static int[] _permutation;
private static Vector2[] _gradients;
static Noise2d()
{
CalculatePermutation(out _permutation);
CalculateGradients(out _gradients);
}
private static void CalculatePermutation(out int[] p)
{
p = Enumerable.Range(0, 256).ToArray();
/// shuffle the array
for (var i = 0; i < p.Length; i++)
{
var source = _random.Next(p.Length);
var t = p[i];
p[i] = p[source];
p[source] = t;
}
}
/// <summary>
/// generate a new permutation.
/// </summary>
public static void Reseed()
{
CalculatePermutation(out _permutation);
}
private static void CalculateGradients(out Vector2[] grad)
{
grad = new Vector2[256];
for (var i = 0; i < grad.Length; i++)
{
Vector2 gradient;
do
{
gradient = new Vector2((float)(_random.NextDouble() * 2 - 1), (float)(_random.NextDouble() * 2 - 1));
}
while (gradient.LengthSquared() >= 1);
gradient.Normalize();
grad[i] = gradient;
}
}
private static float Drop(float t)
{
t = Math.Abs(t);
return 1f - t * t * t * (t * (t * 6 - 15) + 10);
}
private static float Q(float u, float v)
{
return Drop(u) * Drop(v);
}
public static float Noise(float x, float y)
{
var cell = new Vector2((float)Math.Floor(x), (float)Math.Floor(y));
var total = 0f;
var corners = new[] { new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0), new Vector2(1, 1) };
foreach (var n in corners)
{
var ij = cell + n;
var uv = new Vector2(x - ij.X, y - ij.Y);
var index = _permutation[(int)ij.X % _permutation.Length];
index = _permutation[(index + (int)ij.Y) % _permutation.Length];
var grad = _gradients[index % _gradients.Length];
total += Q(uv.X, uv.Y) * Vector2.Dot(grad, uv);
}
return Math.Max(Math.Min(total, 1f), -1f);
}
}
Call it like this:
private void GenerateNoiseMap(int width, int height, ref Texture2D noiseTexture, int octaves)
{
var data = new float[width * height];
/// track min and max noise value. Used to normalize the result to the 0 to 1.0 range.
var min = float.MaxValue;
var max = float.MinValue;
/// rebuild the permutation table to get a different noise pattern.
/// Leave this out if you want to play with changing the number of octaves while
/// maintaining the same overall pattern.
Noise2d.Reseed();
var frequency = 0.5f;
var amplitude = 1f;
var persistence = 0.25f;
for (var octave = 0; octave < octaves; octave++)
{
/// parallel loop - easy and fast.
Parallel.For(0
, width * height
, (offset) =>
{
var i = offset % width;
var j = offset / width;
var noise = Noise2d.Noise(i*frequency*1f/width, j*frequency*1f/height);
noise = data[j * width + i] += noise * amplitude;
min = Math.Min(min, noise);
max = Math.Max(max, noise);
}
);
frequency *= 2;
amplitude /= 2;
}
if (noiseTexture != null && (noiseTexture.Width != width || noiseTexture.Height != height))
{
noiseTexture.Dispose();
noiseTexture = null;
}
if (noiseTexture==null)
{
noiseTexture = new Texture2D(Device, width, height, false, SurfaceFormat.Color);
}
var colors = data.Select(
(f) =>
{
var norm = (f - min) / (max - min);
return new Color(norm, norm, norm, 1);
}
).ToArray();
noiseTexture.SetData(colors);
}
Note that I've used a couple of XNA structures (Vector2 and Texture2D), but it should be pretty clear what they do.
If you want higher frequency (more "noisy") content with fewer octaves, increase the initial frequency value that used in the octave loop.
This implementation uses "improved" Perlin noise, which should be a bit faster than the standard version. You might also have a look at Simplex noise, which is quite a bit faster at higher dimensions.
I had to change this:
n00 = dot2D(g00 & 7, x, y),
n10 = dot2D(g10 & 7, x, y),
n01 = dot2D(g01 & 7, x, y),
n11 = dot2D(g11 & 7, x, y);
to this:
n00 = dot2D(g00 & 7, x , y ),
n10 = dot2D(g10 & 7, x - 1, y ),
n01 = dot2D(g01 & 7, x , y - 1),
n11 = dot2D(g11 & 7, x - 1, y - 1);
Basically just subtracting 1 from the x and y where needed.
If you plug in a zero value for z into your 3D equation and simply follow the math through, removing terms, you'll see that you end up with a simpler equation in the end.
Your implementation looks kind of different to the one I'm using though.
Here's a comparison of a 3D and 2D function I'm using (in JavaScript):
noise3d: function(x, y, z)
{
// Find unit cube that contains point.
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255,
Z = Math.floor(z) & 255;
// Find relative x,y,z of point in cube.
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
// Compute fade curves for each of x,y,z.
var u = fade(x),
v = fade(y),
w = fade(z);
// Hash coordinates of the corners.
var A = p[X ] + Y, AA = p[A] + Z, AB = p[A + 1] + Z,
B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z;
// Add blended results from 8 corners of cube.
return scale(
lerp(
w,
lerp(
v,
lerp(
u,
grad(p[AA], x, y, z),
grad(p[BA], x - 1, y, z)
),
lerp(
u,
grad(p[AB], x, y - 1, z),
grad(p[BB], x - 1, y - 1, z)
)
),
lerp(
v,
lerp(
u,
grad(p[AA + 1], x, y, z - 1),
grad(p[BA + 1], x - 1, y, z - 1)
),
lerp(
u,
grad(p[AB + 1], x, y - 1, z - 1),
grad(p[BB + 1], x - 1, y - 1, z - 1)
)
)
)
);
}
The 2D version involves fewer computations.
noise2d: function(x, y)
{
// Find unit square that contains point.
var X = Math.floor(x) & 255,
Y = Math.floor(y) & 255;
// Find relative x,y of point in square.
x -= Math.floor(x);
y -= Math.floor(y);
// Compute fade curves for each of x,y.
var u = fade(x),
v = fade(y);
// Hash coordinates of the corners.
var A = p[X ] + Y, AA = p[A], AB = p[A + 1],
B = p[X + 1] + Y, BA = p[B], BB = p[B + 1];
// Add blended results from the corners.
return scale(
lerp(
v,
lerp(
u,
grad(p[AA], x, y, 0),
grad(p[BA], x - 1, y, 0)
),
lerp(
u,
grad(p[AB], x, y - 1, 0),
grad(p[BB], x - 1, y - 1, 0)
)
)
);
}

Categories

Resources