Related
I'm developing a small project, which is a grid of 100 x 100 hexagons.
In the script below, I paint my hexagons with the perlin noise, but the format I want to island does not go away.
I'll leave my code and 2 examples as my map stays and how I wish it to stay.
My island
My Island
As i need
Im Need
int getColor(float x, float z)
{
xTO = (int)x / terrainWidth - 30;
zTO = (int)z / terrainHeight - 30;
float v = Mathf.PerlinNoise((xTO + x + seed) * freq, (zTO + z) * freq);
// v += 0.001f;
float form = formWorld(x, z);
if (v < 0.25f)
{
//water
return 0;
}
else if (v < 0.5f)
{
//sand
return 1;
}
else if (v < 0.75f)
{
//grass
return 2;
}
else
{
//Trees / Forest
MakeNewTree(new Vector3(xx, 0, z * 7.5f));
return 2;
}
}
If you want your image to look more like the second one, the best option is going to be adding a circular gradient which offsets your Perlin Noise.
The easiest way to do this is to measure the distance from the center and combine that with the perlin noise.
Here's some untested code.
int getColor(float x, float z)
{
xTO = (int)x / terrainWidth - 30;
zTO = (int)z / terrainHeight - 30;
float v = Mathf.PerlinNoise((xTO + x + seed) * freq, (zTO + z) * freq);
// v += 0.001f;
v -= CircleOffset(x,z)/2; //Change the two to make the island bigger.
float form = formWorld(x, z);
if (v < 0.25f)
{
//water
return 0;
}
else if (v < 0.5f)
{
//sand
return 1;
}
else if (v < 0.75f)
{
//grass
return 2;
}
else
{
//Trees / Forest
MakeNewTree(new Vector3(xx, 0, z * 7.5f));
return 2;
}
}
float CircleOffset(float x, float y)
{
Vector2 center = new Vector2(terrainWidth/2,terrainHeight/2);
float distance = Mathf.Sqrt((center.x - x)*(center.x - x) + (center.y - y) * (center.y - y));
return distance/terrainWidth;
}
Hope this helps!
I have an evenly-spaced grid of numerical values, each in the center of a cell. For simplification, let's assume that every cell is 1.0 by 1.0 big. I am looking for a way to calculate an average from the neighboring cells and the center cell, given any arbitrary point. I tried to come up with an algorithm that used weights and distances, and it mostly works, but I get incorrect values on the edges of cells (the transition between cells wasn't smooth as I was moving p0 around; there is a sudden, steep change in value when it moves to an adjacent cell). That's probably because the values are not spaced out on a circle but on a square, and so diagonally they have different distances from the center point than horizontally or vertically.
In the picture above, there are 9 values designated as v0-v8. v0 is the center value. p0 is an arbitrary point within the center cell. If p0 moves onto the range of another cell, then that cell will become the center cell (the perspective changes obviously).
Requirements:
If p0 is at the same location as v0, then p0 == v0.
No trigonometric, power, or root functions. This piece of code will be critical to performance. Only simple arithmetic operations.
The reason behind all this is that I want to create a faster way to access values that are very slow to calculate, but if they were precalculated with a specific granularity into a grid-like cache, I could just take an average of the nearest ones.
This is what I have come up with so far:
double value = 0;
// If we're closer to the northern edge
if (z < CellRadius)
{
double centeredness = z / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (
centerWeight * Cells[i, j]
+ edgeWeight * Cells[i, j - 1]
) / 4.0;
}
// If we're closer to the southern edge
else if (z >= CellRadius)
{
double centeredness = (
CellRadius - (z - CellRadius)
) / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (
centerWeight * Cells[i, j]
+ edgeWeight * Cells[i, j + 1]
) / 4.0;
}
// If we're closer to the western edge
if (x < CellRadius)
{
double centeredness = x / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (
centerWeight * Cells[i, j]
+ edgeWeight * Cells[i - 1, j]
) / 4.0;
}
// If we're closer to the eastern edge
else if (x >= CellRadius)
{
double centeredness = (
CellRadius - (x - CellRadius)
) / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (
centerWeight * Cells[i, j]
+ edgeWeight * Cells[i + 1, j]
) / 4.0;
}
// If we're closer to the north-western edge
if (x < CellRadius && z < CellRadius)
{
double centeredness = (x / CellRadius) * (z / CellRadius);
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (centerWeight * Cells[i, j] + edgeWeight * (
Cells[i - 1, j - 1]
)) / 2.0;
}
// If we're closer to the south-eastern edge
else if (x >= CellRadius && z >= CellRadius)
{
double centeredness = (
CellRadius - (x - CellRadius)
) / CellRadius * (
CellRadius - (z - CellRadius)
) / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (centerWeight * Cells[i, j] + edgeWeight * (
Cells[i + 1, j + 1]
)) / 2.0;
}
// If we're closer to the north-eastern edge
else if (x >= CellRadius && z < CellRadius)
{
double centeredness = (
CellRadius - (x - CellRadius)
) / CellRadius * (z / CellRadius);
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (centerWeight * Cells[i, j] + edgeWeight * (
Cells[i + 1, j - 1]
)) / 2.0;
}
// If we're closer to the south-western edge
else if (x < CellRadius && z >= CellRadius)
{
double centeredness = (x / CellRadius) * (
CellRadius - (z - CellRadius)
) / CellRadius;
double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);
value += (centerWeight * Cells[i, j] + edgeWeight * (
Cells[i - 1, j + 1]
)) / 2.0;
}
return value;
And as explained earlier, it doesn't work correctly.
I find it hard to understand what you're trying to accomplish:
If you want the average of these points, you could just take 1/10th of offset p0 from the center point v0.
Since v0-v8 are 9 points total, and p0 is 1 point, that makes 10 points, hence the 1/10th.
Say we ignore p0 for now and try to calculate the average of v0-v8, it will be (0, 0) in the situation you're describing. Taking a "weight" into account for this situation, we could say that this is 9, because they are 9 points.
Even if we move both points of any of the pairs v2&v8, v4&v6, v3&v7 or v1&v5 the same distance away from v0, the average will still be (0, 0). Taking this into account means that it doesnt matter how far the distance is between the points of the mentioned pairs, away from v0, as long as (for example v2 and v8) the distance of both of the points is the same.
In your code i see that you treat "north-west" differently than "north" or "west" for example, because of above explanation i don't think this is necessary. Besides that I think the algorithm is pretty complicated while I described above that you could just take 1/10th of p0.
Hope this helps, if I misunderstood your question, please elaborate! :)
EDIT:
In your question you made the assumption that because the values are not spaced out on a circle but on a square, diagonally they have different distances from the center point than horizontally or vertically. In theory, this is true.
About this assumption I want to point out that to even consider this distance in code, you would need a square root (Pythagorean theorem). The available data for this distance, I would assume is a difference in x and y.
Following up on your assumption, looking at the example image, the difference in y between v3 and p0/v2 and p0 is the same, and the difference in x between v2 and p0/v1 and p0 is also the same.
Either way, only difference in x and y between v0 and p0 is needed for the calculation i'd say.
I think this should do. I am using bi-linear interpolation.
public class Mesh
{
public Mesh(int n)
{
this.Size = n;
this.Cells = new double[n, n];
}
public int Size { get; }
public double[,] Cells { get; }
public double CellRadius { get; } = 1;
/// <summary>
/// Peform inertpolation among cells.
/// </summary>
public double Value(double x, double z)
{
// | |
// --*----------*-----------> x
// | v(i,j) | v(i,j+1)
// | |
// | |
// --*----------*--
// | v(i+1,k) | v(i+1,j+1)
// |
// v
// z
// Find top left cell location
double h = CellRadius;
int i = (int)Math.Floor(z/h);
if(i>=Size-1) { i=Size-2; }
int j = (int)Math.Floor(x/h);
if(j>=Size-1) { j=Size-2; }
var v_11 = Cells[i, j];
var v_12 = Cells[i, j+1];
var v_21 = Cells[i+1, j];
var v_22 = Cells[i+1, j+1];
x -= j*h;
z -= i*h;
var ξ = x/h; //pre compute ratio for speed
var v_1 = (1-ξ)*v_11 + (ξ)*v_12;
var v_2 = (1-ξ)*v_21 + (ξ)*v_22;
var ζ = z/h; //pre compute ratio for speed
return (1-ζ)*v_1 + (ζ)*v_2;
}
}
class Program
{
static void Main(string[] args)
{
var mesh = new Mesh(3);
mesh.Cells[0, 0] = 1;
mesh.Cells[0, 1] = 2;
mesh.Cells[0, 2] = 2;
mesh.Cells[1, 0] = 1;
mesh.Cells[1, 1] = 1;
mesh.Cells[1, 2] = 2;
mesh.Cells[2, 0] = 0;
mesh.Cells[2, 1] = 1;
mesh.Cells[2, 2] = 0;
Console.WriteLine($"{"x",12} {"z",12} {"v",12}");
for(int i = 0; i < 10; i++)
{
var x = 1.2;
var z = 3.0*i/9;
var v = mesh.Value(x, z);
Console.WriteLine($"{x,12:g6} {z,12:g6} {v,12:g6}");
}
}
}
with sample output:
x z v
1.2 0 2
1.2 0.333333 1.73333
1.2 0.666667 1.46667
1.2 1 1.2
1.2 1.33333 1.06667
1.2 1.66667 0.933333
1.2 2 0.8
1.2 2.33333 0.666667
1.2 2.66667 0.533333
1.2 3 0.4
What average are you trying to find? A linear average? That really doesn't make to much sense when you are basically defining a non linear surface.
One way to go about this would be to evaluate linear averages on all verticees; numerating left to right and top - down you'd have:
1: v2
2: (v2 + v3) / 2
3: (v3 + v4) / 2
4: v4
5: (v1 + v2) / 2
6: (v0 + v1 + v2 + v3) / 4
etc.
And now you have 16 (x, y, z) coordinates. Find a least squares algorithm that produces the best fit with simple functions x, y, x^2, y^2, x^3, y^3 (shouldn't need more than cubic functions) and once you have the surface equation, evaluating the elevation of any valid point inside the problem's domain is straightforward.
Lets use C# in our example.
public class Sphere
{
public Point Center { get; set; }
public float Radius { get; set; }
public Sphere(IEnumerable<Point> points)
{
Point first = points.First();
Point vecMaxZ = first;
Point vecMinZ = first;
Point vecMaxY = first;
Point vecMinY = first;
Point vecMinX = first;
Point vecMaxX = first;
foreach (Point current in points)
{
if (current.X < vecMinX.X)
{
vecMinX = current;
}
if (current.X > vecMaxX.X)
{
vecMaxX = current;
}
if (current.Y < vecMinY.Y)
{
vecMinY = current;
}
if (current.Y > vecMaxY.Y)
{
vecMaxY = current;
}
if (current.Z < vecMinZ.Z)
{
vecMinZ = current;
}
if (current.Z > vecMaxZ.Z)
{
vecMaxZ = current;
}
}
//the lines bellow assure at least 2 points sit on the surface of the sphere.
//I'm pretty sure the algorithm is solid so far, unless I messed up the if/elses.
//I've been over this, looking at the variables and the if/elses and they all
//seem correct, but our own errors are the hardest to spot,
//so maybe there's something wrong here.
float diameterCandidateX = vecMinX.Distance(vecMaxX);
float diameterCandidateY = vecMinY.Distance(vecMaxY);
float diameterCandidateZ = vecMinZ.Distance(vecMaxZ);
Point c;
float r;
if (diameterCandidateX > diameterCandidateY)
{
if (diameterCandidateX > diameterCandidateZ)
{
c = vecMinX.Midpoint(vecMaxX);
r = diameterCandidateX / 2f;
}
else
{
c = vecMinZ.Midpoint(vecMaxZ);
r = diameterCandidateZ / 2f;
}
}
else if (diameterCandidateY > diameterCandidateZ)
{
c = vecMinY.Midpoint(vecMaxY);
r = diameterCandidateY / 2f;
}
else
{
c = vecMinZ.Midpoint(vecMaxZ);
r = diameterCandidateZ / 2f;
}
//the lines bellow look for points outside the sphere, and if one is found, then:
//1 let dist be the distance from the stray point to the current center
//2 let diff be the equal to dist - radius
//3 radius will then the increased by half of diff.
//4 a vector with the same direction as the stray point but with magnitude equal to diff is found
//5 the current center is moved by half the vector found in the step above.
//
//the stray point will now be included
//and, I would expect, the relationship between the center and other points will be mantained:
//if distance from p to center = r / k,
//then new distance from p to center' = r' / k,
//where k doesn't change from one equation to the other.
//this is where I'm wrong. I cannot figure out how to mantain this relationship.
//clearly, I'm moving the center by the wrong amount, and increasing the radius wrongly too.
//I've been over this problem for so much time, I cannot think outside the box.
//my whole world is the box. The box and I are one.
//maybe someone from outside my world (the box) could tell me where my math is wrong, please.
foreach (Point current in points)
{
float dist = current.Distance(c);
if (dist > r)
{
float diff = dist - r;
r += diff / 2f;
float scaleFactor = diff / current.Length();
Point adjust = current * scaleFactor;
c += adjust / 2f;
}
}
Center = c;
Radius = r;
}
public bool Contains(Point point) => Center.Distance(point) <= Radius;
public override string ToString() => $"Center: {Center}; Radius: {Radius}";
}
public class Point
{
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
public Point(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public float LengthSquared() => X * X + Y * Y + Z * Z;
public float Length() => (float) Math.Sqrt(X * X + Y * Y + Z * Z);
public float Distance(Point another)
{
return (float) Math.Sqrt(
(X - another.X) * (X - another.X)
+ (Y - another.Y) * (Y - another.Y)
+ (Z - another.Z) * (Z - another.Z));
}
public float DistanceSquared(Point another)
{
return (X - another.X) * (X - another.X)
+ (Y - another.Y) * (Y - another.Y)
+ (Z - another.Z) * (Z - another.Z);
}
public Point Perpendicular()
{
return new Point(-Y, X, Z);
}
public Point Midpoint(Point another)
{
return new Point(
(X + another.X) / 2f,
(Y + another.Y) / 2f,
(Z + another.Z) / 2f);
}
public override string ToString() => $"({X}, {Y}, {Z})";
public static Point operator +(Point p1, Point p2)
{
return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);
}
public static Point operator *(Point p1, float v)
{
return new Point(p1.X * v, p1.Y * v, p1.Z * v);
}
public static Point operator /(Point p1, float v)
{
return new Point(p1.X / v, p1.Y / v, p1.Z / v);
}
}
//Note: this class is here so I can be able to solve the problems suggested by
//Eric Lippert.
public class Line
{
private float coefficient;
private float constant;
public Line(Point p1, Point p2)
{
float deltaY = p2.Y - p1.Y;
float deltaX = p2.X - p1.X;
coefficient = deltaY / deltaX;
constant = coefficient * -p1.X + p1.Y;
}
public Point FromX(float x)
{
return new Point(x, x * coefficient + constant, 0);
}
public Point FromY(float y)
{
return new Point((y - constant) / coefficient, y, 0);
}
public Point Intersection(Line another)
{
float x = (another.constant - constant) / (coefficient - another.coefficient);
float y = FromX(x).Y;
return new Point(x, y, 0);
}
}
Can I safely assume this will run at least just as fast as the fancy algorithms out there that usually consider, for robustness sake, the possibility of the Points having any number of dimensions, from 2 to anything, like 1000 or 10,000 dimensions.
I only need it for 3 dimensions, never more and never less than that. Since I have no academic degree on computer science (or any degree for that matter, I'm a highschool sophomore), I have difficulties in analyzing algorithms for performance and resource consumption. So my question basically is: Is my "smallest enclosing sphere for dumbs" algoritm good in performance and resource consumption when compared with the fancy ones? Is there a point where my algorithm breaks while the professional ones don't, meaning it performs so bad it will cause noticeable loss (like, if I have too many points).
EDIT 1: I editted the code because it made no sense at all (I was hungry, it was 4pm and I haven't eaten all day). This one makes more sense I think, not sure if it's correct though. The original question stands: If this one solves the problem, does it do it well enough to compete with the stardard professional algorithms in case we know in advance that all points have 3 dimensions?
EDIT 2: Now I'm pretty sure the performance is bad, and I lost all hope of implementing a naive algorithm to find the smallest enclosing sphere. I just want to make something that work. Please, check the latest update.
EDIT 3: Doesn't work either. I quit.
EDIT 4: Finally, after, I don't know... some 5 hours. I figured it out. Jesus Christ. This one works. Could someone tell me about the performance issue? Is it really bad compared to the professional algorithms? What lines can I change to make it better? Is there a point where it breaks? Remember, I will always use it for 3D points.
EDIT 5: I learned from Bychenko the previous algorithm still didn't work. I slept on this issue, and this is my new version of the algorithm. I know it doesn't work, and I have a good clue where it is wrong, could anyone please tell why those particular calculations are wrong and how to fix them? I'm inclined to think this has something to do with trigonometry. My assumptions don't hold true for Euclidean space, because I can't stop seeing vectors as real numbers instead
of sets of real numbers that, in my case, I use to pin-point a location in Euclidean space. I'm pretty sure I'm missing some sine or cosine somewhere in the last loop (of course, not exactly sine or cosine, but the equivalent in cartesian coordinates, since we don't know any angles.
Addendum to EDIT 5: About the problems proposed by Eric Lippert:
(1) argh too trivial :p
(2) I will do it for the circle first; I will add a class Line for that.
Point a, b, c; //they are not collinear
Point midOfAB = a.Midpoint(b);
Point midOfBC = b.Midpoint(c);
//multiplying the vector by a scalar as I do bellow doesn't matter right?
Point perpendicularToAB = midOfAB.Perpendicular() * 3;
Point perpendicularToBC = midOfBC.Perpendicular() * 3;
Line bisectorAB = new Line(perpendicularToAB, midOfAB);
Line bisectorBC = new Line(perpendicularToBC, midOfBC);
Point center = bisectorAB.Intersection(bisectorBC);
float distA = center.Distance(a);
float distB = center.Distance(b);
float distC = center.Distance(c);
if(distA == distB && distB == distC)
//it works (spoiler alert: it doesn't)
else
//you're a failure, programmer, pick up your skate and practice some ollies
Sorry, but your algorithm is wrong. It doesn't solve the problem.
Counter example (3 points):
A = (0, 0, 0) - closest to origin (0)
B = (3, 3, 0) - farthest from origin (3 * sqrt(2) == 4.2426...)
C = (4, 0, 0)
your naive algorithm declares that the sphere has center at
P = (3 / sqrt(2), 3 / sqrt(2), 0)
and radius
R = 3 / sqrt(2)
and you can see that the point C = (4, 0, 0) is beyond the sphere
Edit the updated (but naive) algorithm is still wrong.
Counter example (3 points):
A = (0, 0, 0)
B = (1, 2, 0)
C = (4, 1, 0)
according the algorithm the sphere has its center at
P = (2, 1, 0)
with radius
R = sqrt(5)
and you can see that the sphere is not a minimal (smallest) one.
Nth Edit you still have an incorrect algorithm. When exploring gray zone (you know the problem, but partially, with holes) it's a good practice to invest into testing automatition. As you should know, in case of triangle all the vertexes should be on the sphere; let's validate your the solution on this fact:
public static class SphereValidator {
private static Random m_Random = new Random();
private static String Validate() {
var triangle = Enumerable
.Range(0, 3)
.Select(i => new Point(m_Random.Next(100), m_Random.Next(100), m_Random.Next(100)))
.ToArray();
Sphere solution = new Sphere(triangle);
double tolerance = 1.0e-5;
for (int i = 0; i < triangle.Length; ++i) {
double r = triangle[i].Distance(solution.Center);
if (Math.Abs(r - solution.Radius) > tolerance) {
return String.Format("Counter example\r\n A: {0}\r\n B: {1}\r\n C: {2}\r\n expected distance to \"{3}\": {4}; actual R {5}",
triangle[0], triangle[1], triangle[2], (char) ('A' + i), r, solution.Radius);
}
}
return null;
}
public static String FindCounterExample(int attempts = 10000) {
for (int i = 0; i < attempts; ++i) {
String result = Validate();
if (!String.IsNullOrEmpty(result))
Console.WriteLine(result);
return;
}
Console.WriteLine(String.Format("Yes! All {0} tests passed!", attempts));
}
}
I've just run the code above and got:
Counter example
A: (3, 30, 9)
B: (1, 63, 40)
C: (69, 1, 16)
expected distance to "A": 35.120849609375; actual R 53.62698
For a crude approximation, compute the Axis-Aligned Bounding Box, then the bounding sphere of that box (same center, diameter = √(W² + H² + D²) ).
You can refine by computing the largest distance from that center.
I'm currently using GDI+ to draw a line graph, and using Graphics.DrawCurve to smooth out the line. The problem is that the curve doesn't always match the points I feed it, and that makes the curve grow out of the graph frame in some points, as seen below(red is Graphics.DrawLines, green is Graphics.DrawCurve).
How would I go about solving this?
The simplest solution is to set a tension:
The green curve is drawn with the default tension, the blue one set a tension of 0.1f:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawLines(Pens.Red, points.ToArray());
e.Graphics.DrawCurve(Pens.Green, points.ToArray());
e.Graphics.DrawCurve(Pens.Blue, points.ToArray(), 0.1f);
}
You will need to test what is the best compromise, 0.2f is still ok, 0.3f is already overdrawing quite a bit..
For a really good solution you will need to use DrawBeziers. This will let you draw curves that can go through the points without any overdrawing and with full control of the radius of the curves; but to to so you will need to 'find', i.e. calculate good control points, which is anything but trivial..:
This result is by no means perfect but already complicated enough.. I have displayed the curve points and their respective control points in the same color. For each point there is an incoming and an outgoing control point. For a smooth curve they need to have the same tangents/gradients in their curve points.
I use a few helper functions to calculate a few things about the segments:
A list of gradients
A list of signs of the gradients
A list of segment lengths
Lists of horizontal and of vertical gaps between points
The main function calculates the array of bezier points, that is the curve points and between each pair the previous left and the next right control points.
In the Paint event it is used like this:
List<PointF> bezz = getBezz(points);
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawBeziers(pen, bezz.ToArray());
Here are the functions I used:
List<float> getGradients(List<PointF> p)
{
List<float> grads = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
if (dx == 0) grads.Add(dy == 0 ? 0 : dy > 0 ?
float.PositiveInfinity : float.NegativeInfinity);
else grads.Add(dy / dx);
}
return grads;
}
List<float> getLengths(List<PointF> p)
{
List<float> lengs = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
lengs.Add((float)Math.Sqrt(dy * dy + dx * dx));
}
return lengs;
}
List<float> getGaps(List<PointF> p, bool horizontal)
{
List<float> gaps = new List<float>();
for (int i = 0; i < p.Count - 1; i++)
{
float dx = p[i + 1].X - p[i].X;
float dy = p[i + 1].Y - p[i].Y;
gaps.Add(horizontal ? dx : dy);
}
return gaps;
}
List<int> getSigns(List<float> g)
{
return g.Select(x => x > 0 ? 1 : x == 0 ? 0 : -1).ToList();
}
And finally the main function; here I make a distinction: Extreme points ( minima & maxima) should have their control points on the same height as the points themselves. This will prevent vertical overflowing. They are easy to find: The signs of their gradients will always altenate.
Other points need to have the same gradient for incoming and outcoming control points. I use the average between the segments' gradients. (Maybe a weighed average would be better..) And I weigh their distance according to the segment lengths..
List<PointF> getBezz(List<PointF> points)
{
List<PointF> bezz = new List<PointF>();
int pMax = points.Count;
List<float> hGaps = getGaps(points, true);
List<float> vGaps = getGaps(points, false);
List<float> grads = getGradients(points);
List<float> lengs = getLengths(points);
List<int> signs = getSigns(grads);
PointF[] bezzA = new PointF[pMax * 3 - 2];
// curve points
for (int i = 0; i < pMax; i++) bezzA[i * 3] = points[i];
// left control points
for (int i = 1; i < pMax; i++)
{
float x = points[i].X - hGaps[i - 1] / 2f;
float y = points[i].Y;
if (i < pMax - 1 && signs[i - 1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y - hGaps[i-1] / 2f * m * vGaps[i-1] / lengs[i-1];
}
bezzA[i * 3 - 1] = new PointF(x, y);
}
// right control points
for (int i = 0; i < pMax - 1; i++)
{
float x = points[i].X + hGaps[i] / 2f;
float y = points[i].Y;
if (i > 0 && signs[i-1] == signs[i])
{
float m = (grads[i-1] + grads[i]) / 2f;
y = points[i].Y + hGaps[i] / 2f * m * vGaps[i] / lengs[i];
}
bezzA[i * 3 + 1] = new PointF(x, y);
}
return bezzA.ToList();
}
Note that I didn't code for the case of points with the same x-coordinate. So this is ok for 'functional graphs' but not for, say figures, like e.g. stars..
Maybe you just want to look at the "overshooting the bounds" problem as not a problem with the overshoot, but with the bounds. In which case, you can determine the actual bounds of a curve using the System.Drawing.Drawing2D.GraphicsPath object:
GraphicsPath gp = new GraphicsPath();
gp.AddCurve(listOfPoints);
RectangleF bounds = gp.GetBounds();
You can draw that GraphicsPath directly:
graphics.DrawPath(Pens.Black, gp);
As far as solving the bounds problem, the line necessarily overshoots the vertex on some axis. It's easier to see this fact when the lines are aligned to the bounds.
Given these points:
In order for them to be curved, they must exceed their bounds in some way:
If you never want to exceed their vertical bounds, you could simply ensure that the bezier handles have the same Y value as the vertex, but they will overshoot on the X:
Or vice-versa:
You could deliberately undershoot just enough to avoid the way curves can overshoot. This can be done by swapping the bezier handles, which would maybe be at the line-centers, with the vertices:
I have line segment defined by these two points: A(x1,y1,z1) and B(x2,y2,z2). I have point p(x,y,z). How can I check if the point lies on the line segment?
Find the distance of point P from both the line end points A, B. If AB = AP + PB, then P lies on the line segment AB.
AB = sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)+(z2-z1)*(z2-z1));
AP = sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1)+(z-z1)*(z-z1));
PB = sqrt((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z));
if(AB == AP + PB)
return true;
If the point is on the line then:
(x - x1) / (x2 - x1) = (y - y1) / (y2 - y1) = (z - z1) / (z2 - z1)
Calculate all three values, and if they are the same (to some degree of tolerance), your point is on the line.
To test if the point is in the segment, not just on the line, you can check that
x1 < x < x2, assuming x1 < x2, or
y1 < y < y2, assuming y1 < y2, or
z1 < z < z2, assuming z1 < z2
First take the cross product of AB and AP. If they are colinear, then it will be 0.
At this point, it could still be on the greater line extending past B or before A, so then I think you should be able to just check if pz is between az and bz.
This appears to be a duplicate, actually, and as one of the answers mentions, it is in Beautiful Code.
in case if someone looks for inline version:
public static bool PointOnLine2D (this Vector2 p, Vector2 a, Vector2 b, float t = 1E-03f)
{
// ensure points are collinear
var zero = (b.x - a.x) * (p.y - a.y) - (p.x - a.x) * (b.y - a.y);
if (zero > t || zero < -t) return false;
// check if x-coordinates are not equal
if (a.x - b.x > t || b.x - a.x > t)
// ensure x is between a.x & b.x (use tolerance)
return a.x > b.x
? p.x + t > b.x && p.x - t < a.x
: p.x + t > a.x && p.x - t < b.x;
// ensure y is between a.y & b.y (use tolerance)
return a.y > b.y
? p.y + t > b.y && p.y - t < a.y
: p.y + t > a.y && p.y - t < b.y;
}
Your segment is best defined by parametric equation
for all points on your segment, following equation holds:
x = x1 + (x2 - x1) * p
y = y1 + (y2 - y1) * p
z = z1 + (z2 - z1) * p
Where p is a number in [0;1]
So, if there is a p such that your point coordinates satisfy those
3 equations, your point is on this line. And it p is between 0 and 1 -
it is also on line segment
Here's some C# code for the 2D case:
public static bool PointOnLineSegment(PointD pt1, PointD pt2, PointD pt, double epsilon = 0.001)
{
if (pt.X - Math.Max(pt1.X, pt2.X) > epsilon ||
Math.Min(pt1.X, pt2.X) - pt.X > epsilon ||
pt.Y - Math.Max(pt1.Y, pt2.Y) > epsilon ||
Math.Min(pt1.Y, pt2.Y) - pt.Y > epsilon)
return false;
if (Math.Abs(pt2.X - pt1.X) < epsilon)
return Math.Abs(pt1.X - pt.X) < epsilon || Math.Abs(pt2.X - pt.X) < epsilon;
if (Math.Abs(pt2.Y - pt1.Y) < epsilon)
return Math.Abs(pt1.Y - pt.Y) < epsilon || Math.Abs(pt2.Y - pt.Y) < epsilon;
double x = pt1.X + (pt.Y - pt1.Y) * (pt2.X - pt1.X) / (pt2.Y - pt1.Y);
double y = pt1.Y + (pt.X - pt1.X) * (pt2.Y - pt1.Y) / (pt2.X - pt1.X);
return Math.Abs(pt.X - x) < epsilon || Math.Abs(pt.Y - y) < epsilon;
}
Or let the dotnet do the heavy lifting for you if using visual studio use a GraphicsPath
this will also allow you to add tolerances for if just clicked outside the line.
using (Drawing2D.GraphicsPath gp = new Drawing2D.GraphicsPath())
{
gp.AddLine(new Point(x1, y1), new Point(x2, y2));
// Make the line as wide as needed (make this larger to allow clicking slightly outside the line)
using (Pen objPen = new Pen(Color.Black, 6))
{
gp.Widen(objPen);
}
if (gp.IsVisible(Mouse.x, Mouse.y))
{
// The line was clicked
}
}
The cross product (B - A) × (p - A) should be much much shorter than B - A. Ideally, the cross product is zero, but that's unlikely on finite-precision floating-point hardware.
I use this to calculate the distance AB between points a and b.
static void Main(string[] args)
{
double AB = segment(0, 1, 0, 4);
Console.WriteLine("Length of segment AB: {0}",AB);
}
static double segment (int ax,int ay, int bx, int by)
{
Vector a = new Vector(ax,ay);
Vector b = new Vector(bx,by);
Vector c = (a & b);
return Math.Sqrt(c.X + c.Y);
}
struct Vector
{
public readonly float X;
public readonly float Y;
public Vector(float x, float y)
{
this.X = x;
this.Y = y;
}
public static Vector operator &(Vector a, Vector b)
{
return new Vector((b.X - a.X) * (b.X - a.X), (b.Y - a.Y) * (b.Y - a.Y));
}
}
based on Calculate a point along the line A-B at a given distance from A
Let V1 be the vector (B-A), and V2 = (p-A), normalize both V1 and V2.
If V1==(-V2) then the point p is on the line, but preceding A, & therefore not in the segment.
If V1==V2 the point p is on the line. Get the length of (p-A) and check if this is less-or-equal to length of (B-A), if so the point is on the segment, else it is past B.
This is my code which can run in WPF
public static class Math2DExtensions
{
public static bool CheckIsPointOnLineSegment(Point point, Line line, double epsilon = 0.1)
{
// Thank you #Rob Agar
// (x - x1) / (x2 - x1) = (y - y1) / (y2 - y1)
// x1 < x < x2, assuming x1 < x2
// y1 < y < y2, assuming y1 < y2
var minX = Math.Min(line.APoint.X, line.BPoint.X);
var maxX = Math.Max(line.APoint.X, line.BPoint.X);
var minY = Math.Min(line.APoint.Y, line.BPoint.Y);
var maxY = Math.Max(line.APoint.Y, line.BPoint.Y);
if (!(minX <= point.X) || !(point.X <= maxX) || !(minY <= point.Y) || !(point.Y <= maxY))
{
return false;
}
if (Math.Abs(line.APoint.X - line.BPoint.X) < epsilon)
{
return Math.Abs(line.APoint.X - point.X) < epsilon || Math.Abs(line.BPoint.X - point.X) < epsilon;
}
if (Math.Abs(line.APoint.Y - line.BPoint.Y) < epsilon)
{
return Math.Abs(line.APoint.Y - point.Y) < epsilon || Math.Abs(line.BPoint.Y - point.Y) < epsilon;
}
if (Math.Abs((point.X - line.APoint.X) / (line.BPoint.X - line.APoint.X) - (point.Y - line.APoint.Y) / (line.BPoint.Y - line.APoint.Y)) < epsilon)
{
return true;
}
else
{
return false;
}
}
}
public record Line
{
public Point APoint { get; init; }
public Point BPoint { get; init; }
}
My code is in github
Thank you #Rob Agar and #MetaMapper
You could check if the point lies between the two planes defined by point1 and point2 and the line direction:
/// Returns the closest point from #a point to this line on this line.
vector3 <Type>
line3d <Type>::closest_point (const vector3 <Type> & point) const
{
return this -> point () + direction () * dot (point - this -> point (), direction ());
}
/// Returns true if #a point lies between point1 and point2.
template <class Type>
bool
line_segment3 <Type>::is_between (const vector3 <Type> & point) const
{
const auto closest = line () .closest_point (point);
return abs ((closest - point0 ()) + (closest - point1 ())) <= abs (point0 () - point1 ());
}