Get the distance from a Point to a Geometry - c#

I have System.Windows.Media.Geometry g and System.Windows.Point p.
I want to know the shortest distance between the point and the outline of the geometry. How should I do it?
Here is my effort:
Geometry.GetOutlinedPathGeometry() returns PathGeometry.
PathGeometry.Figures returns PathFigure.
PathFigure.Segments returns PathSegment.
No useful method on PathSegment...

Basically, what you need to do is go through every point on that path that you got from your geometry and measure distance between one of those points and the segregated point.
There's a post on SO that finds the closest point to the segregated point:
https://stackoverflow.com/a/19031758/2006048
And there's a C++ algorithm that does the distance measuring. You'll just have to convert it to C#:
https://stackoverflow.com/a/1501725/2006048
You can probably also use Point.Subtract() method to get and compare Vectors between each of those points.
If you can get an array or list of points from that shape, then you can probably do something like this (Note that this is not as elaborate as the links I've provided. This will give you the shortest distance to one of the available points and not the segment itself):
public static double GetShortestDistance(Point target, Point[] points)
{
var dist = 0;
foreach (var point in points)
{
var xDist = target.X - point.X;
var yDist = target.Y - point.Y;
var nDist = Math.Sqrt(xDist * xDist + yDist * yDist);
if (nDist < dist)
{
dist = nDist;
}
}
return dist;
}
I recommend using the C++ algorithm in the second link.

Related

finding unknown 4th point in arbitrary space programmatically

Problem:
I know the x and y of three arbitrary points on a 2d plane.
I know the vague distance from each point to the unknown, though I don't know the x y components.
I want to find the position of the 4th point.
The data is stored in a list >3 of type Data where
public class Data
{
double m_x, m_y, m_distance;
}
I've tried:
Walking the list, calculating the components of the distance, then adding the known x and y. I then calculated the average position of the predicted point from the 3 known points, but the accuracy was inconsistent.
foreach (var item in data_list)
{
var dx = item.m_x + item.m_distance * Math.Cos(item.m_distance);
var dy = item.m_y + item.m_distance * Math.Sin(item.m_distance);
out_list.Add(new Data { m_x = dx, m_y = dy });
}
foreach (var item in out_list)
{
__dx += item.m_x;
__dy += item.m_y;
}
__dx /= return_list.Count;
__dy /= return_list.Count;
Creating three circles at the known x and y, extending their radii equal to the distance component and checking intersection. The problem is that the distance varies since its rather imprecise, more like a suggestion.
Is there a simple, ok-performing, witty solution to this problem that I can't grasp?
I've thought of extending lines 360 degrees around each point and checking where three lines intersect, the shortest distance away from the origin, but I'm not entirely sure about the implementation.

Making a Pen's LineCap go more into the drawn Line/Curve

Currently I have the following code:
AdjustableArrowCap arrow = new AdjustableArrowCap(10, 15, false);
penStateOutline.CustomEndCap = arrow;
And it draws this:
I have tried all day to make the arrow point to the ellipse itself rather than the center of it..
Update (I was wrong about the cap extending the line; it doesn't!)
To let the line and its cap end at the cirlce's outside you need to make the line shorter by the radius of the circle.
There are two approaches:
You can find a new endpoint that sits on the circle by calculating it, either with Pythagoras or by trigonometry. Then replace the endpoint, i.e. the circle's center, when drawing the line or curve by that new point.
Or put the other way round: You need to calculate a point on the circle as the new endpoint of the line.
It requires a little math, unless the line is horizontal or vertical...
This will work well for straight lines but for curves it may cause considerable changes in the shape, depending on how close the points get and how curved the shape is.
This may or may not be a problem.
To avoid it you can replace the curve points by a series of line points that are close enough to look like a curve when drawn. From the list of points we subtract all those that do not lie inside the circle.
This sounds more complicated than it is as there is a nice class called GraphicsPath that will allow you to add a curve and then flatten it. The result is a more or less large number of points. The same class also allows you to determine whether a point lies inside a shape, i.e. our case inside the circle.
To implement the latter approach, here is a routine that transforms a list of curve points to a list of line points that will end close to the circle..:
void FlattenCurveOutside(List<Point> points, float radius)//, int count)
{
using (GraphicsPath gp = new GraphicsPath())
using (GraphicsPath gpc = new GraphicsPath())
{
// firt create a path that looks like our circle:
PointF l = points.Last();
gpc.AddEllipse(l.X - radius, l.Y - radius, radius * 2, radius* 2);
// next one that holds the curve:
gp.AddCurve(points.ToArray());
// now we flatten it to a not too large number of line segments:
Matrix m = new Matrix(); // dummy matrix
gp.Flatten(m, 0.75f); // <== play with this param!!
// now we test the pathpoints from bach to front until we have left the circle:
int k = -1;
for (int i = gp.PathPoints.Length - 1; i >= 0; i--)
{
if ( !gpc.IsVisible(gp.PathPoints[i])) k = i;
if (k > 0) break;
}
// now we know how many pathpoints we want to retain:
points.Clear();
for (int i = 1; i <= k; i++)
points.Add(Point.Round(gp.PathPoints[i]));
}
}
Note that when the last part of the curve is too straight the result may look a little jagged..
Update 2
To implement the former approach here is a function that returns a PointF on a circle of radius r and a line connecting a Point b with the circle center c:
PointF Intersect(Point c, Point a, int rad)
{
float dx = c.X - a.X;
float dy = c.Y - a.Y;
var radians = Math.Atan2(dy, dx);
var angle = 180 - radians * (180 / Math.PI);
float alpha = (float)(angle * Math.PI / 180f);
float ry = (float)(Math.Sin(alpha) * rad );
float rx = (float)(Math.Cos(alpha) * rad );
return new PointF(c.X + rx, c.Y - ry);
}
The result thankfully looks rather similar:
The math can be simplified a little..
To apply it you can get the new endpoint and replace the old one:
PointF I = Intersect(c, b, r);
points2[points2.Count - 1] = Point.Round(I);
Thank you so much to #TaW for helping.
Unfortunately the answer he provided did not match my stupid OCD..
You made me think about the problem from a different perspective and I now have found a solution that I'll share if anyone else needs, note that the solution is not perfect.
public static Point ClosestPointOnCircle(int rad, PointF circle, PointF other)
{
if (other.X == circle.X && other.Y == circle.Y) // dealing with division by 0
other.Y--;
return new Point((int)Math.Floor(circle.X + rad * ((other.X - circle.X) / (Math.Sqrt(Math.Pow(other.X - circle.X, 2) + Math.Pow(other.Y - circle.Y, 2))))), (int)Math.Floor(circle.Y + rad * ((other.Y - circle.Y) / (Math.Sqrt(Math.Pow(other.X - circle.X, 2) + Math.Pow(other.Y - circle.Y, 2))))));
}
What the code does is return a point on the circle that is the closest to the another point, then, I used the curve middle point as the other point to change the end point of the curve.
Then I used the arrow cap as normal and got this:image
Which is good enough for my project.

Approximating an ellipse with a polygon

I am working with geographic information, and recently I needed to draw an ellipse. For compatibility with the OGC convention, I cannot use the ellipse as it is; instead, I use an approximation of the ellipse using a polygon, by taking a polygon which is contained by the ellipse and using arbitrarily many points.
The process I used to generate the ellipse for a given number of point N is the following (using C# and a fictional Polygon class):
Polygon CreateEllipsePolygon(Coordinate center, double radiusX, double radiusY, int numberOfPoints)
{
Polygon result = new Polygon();
for (int i=0;i<numberOfPoints;i++)
{
double percentDone = ((double)i)/((double)numberOfPoints);
double currentEllipseAngle = percentDone * 2 * Math.PI;
Point newPoint = CalculatePointOnEllipseForAngle(currentEllipseAngle, center, radiusX, radiusY);
result.Add(newPoint);
}
return result;
}
This has served me quite while so far, but I've noticed a problem with it: if my ellipse is 'stocky', that is, radiusX is much larger than radiusY, the number of points on the top part of the ellipse is the same as the number of points on the left part of the ellipse.
That is a wasteful use of points! Adding a point on the upper part of the ellipse would hardly affect the precision of my polygon approximation, but adding a point to the left part of the ellipse can have a major effect.
What I'd really like, is a better algorithm to approximate the ellipse with a polygon. What I need from this algorithm:
It must accept the number of points as a parameter; it's OK to accept the number of points in every quadrant (I could iteratively add points in the 'problematic' places, but I need good control on how many points I'm using)
It must be bounded by the ellipse
It must contain the points straight above, straight below, straight to the left and straight to the right of the ellipse's center
Its area should be as close as possible to the area of the ellipse, with preference to optimal for the given number of points of course (See Jaan's answer - appearantly this solution is already optimal)
The minimal internal angle in the polygon is maximal
What I've had in mind is finding a polygon in which the angle between every two lines is always the same - but not only I couldn't find out how to produce such a polygon, I'm not even sure one exists, even if I remove the restrictions!
Does anybody have an idea about how I can find such a polygon?
finding a polygon in which the angle between every two lines is
always the same
Yes, it is possible. We want to find such points of (the first) ellipse quadrant, that angles of tangents in these points form equidistant (the same angle difference) sequence. It is not hard to find that tangent in point
x=a*Cos(fi)
y=b*Sin(Fi)
derivatives
dx=-a*Sin(Fi), dy=b*Cos(Fi)
y'=dy/dx=-b/a*Cos(Fi)/Sin(Fi)=-b/a*Ctg(Fi)
Derivative y' describes tangent, this tangent has angular coefficient
k=b/a*Cotangent(Fi)=Tg(Theta)
Fi = ArcCotangent(a/b*Tg(Theta)) = Pi/2-ArcTan(a/b*Tg(Theta))
due to relation for complementary angles
where Fi varies from 0 to Pi/2, and Theta - from Pi/2 to 0.
So code for finding N + 1 points (including extremal ones) per quadrant may look like (this is Delphi code producing attached picture)
for i := 0 to N - 1 do begin
Theta := Pi/2 * i / N;
Fi := Pi/2 - ArcTan(Tan(Theta) * a/b);
x := CenterX + Round(a * Cos(Fi));
y := CenterY + Round(b * Sin(Fi));
end;
// I've removed Nth point calculation, that involves indefinite Tan(Pi/2)
// It would better to assign known value 0 to Fi in this point
Sketch for perfect-angle polygon:
One way to achieve adaptive discretisations for closed contours (like ellipses) is to run the Ramer–Douglas–Peucker algorithm in reverse:
1. Start with a coarse description of the contour C, in this case 4
points located at the left, right, top and bottom of the ellipse.
2. Push the initial 4 edges onto a queue Q.
while (N < Nmax && Q not empty)
3. Pop an edge [pi,pj] <- Q, where pi,pj are the endpoints.
4. Project a midpoint pk onto the contour C. (I expect that
simply bisecting the theta endpoint values will suffice
for an ellipse).
5. Calculate distance D between point pk and edge [pi,pj].
if (D > TOL)
6. Replace edge [pi,pj] with sub-edges [pi,pk], [pk,pj].
7. Push new edges onto Q.
8. N = N+1
endif
endwhile
This algorithm iteratively refines an initial discretisation of the contour C, clustering points in areas of high curvature. It terminates when, either (i) a user defined error tolerance TOL is satisfied, or (ii) the maximum allowable number of points Nmax is used.
I'm sure that it's possible to find an alternative that's optimised specifically for the case of an ellipse, but the generality of this method is, I think, pretty handy.
I assume that in the OP's question, CalculatePointOnEllipseForAngle returns a point whose coordinates are as follows.
newPoint.x = radiusX*cos(currentEllipseAngle) + center.x
newPoint.y = radiusY*sin(currentEllipseAngle) + center.y
Then, if the goal is to minimize the difference of the areas of the ellipse and the inscribed polygon (i.e., to find an inscribed polygon with maximal area), the OP's original solution is already an optimal one. See Ivan Niven, "Maxima and Minima Without Calculus", Theorem 7.3b. (There are infinitely many optimal solutions: one can get another polygon with the same area by adding an arbitrary constant to currentEllipseAngle in the formulae above; these are the only optimal solutions. The proof idea is quite simple: first one proves that these are the optimal solutions in case of a circle, i.e. if radiusX=radiusY; secondly one observes that under a linear transformation that transforms a circle into our ellipse, e.g. a transformation of multiplying the x-coordinate by some constant, all areas are multiplied by a constant and therefore a maximal-area inscribed polygon of the circle is transformed into a maximal-area inscribed polygon of the ellipse.)
One may also regard other goals, as suggested in the other posts: e.g. maximizing the minimal angle of the polygon or minimizing the Hausdorff distance between the boundaries of the polygon and ellipse. (E.g. the Ramer-Douglas-Peucker algorithm is a heuristic to approximately solve the latter problem. Instead of approximating a polygonal curve, as in the usual Ramer-Douglas-Peucker implementation, we approximate an ellipse, but it is possible to devise a formula for finding on an ellipse arc the farthest point from a line segment.) With respect to these goals, the OP's solution would usually not be optimal and I don't know if finding an exact solution formula is feasible at all. But the OP's solution is not as bad as the OP's picture shows: it seems that the OP's picture has not been produced using this algorithm, as it has less points in the more sharply curved parts of the ellipse than this algorithm produces.
I suggest you switch to polar coordinates:
Ellipse in polar coord is:
x(t) = XRadius * cos(t)
y(t) = YRadius * sin(t)
for 0 <= t <= 2*pi
The problems arise when Xradius >> YRadius (or Yradius >> Yradius)
Instead of using numberOfPoints you can use an array of angles obviously not all identical.
I.e. with 36 points and dividing equally you get angle = 2*pi*n / 36 radiants for each sector.
When you get around n = 0 (or 36) or n = 18 in a "neighborhood" of these 2 values the approx method doesn't works well cause the ellipse sector is significantly different from the triangle used to approximate it. You can decrease the sector size around this points thus increasing precision. Instead of just increasing the number of points that would also increase segments in other unneeded areas. The sequence of angles should become something like (in degrees ):
angles_array = [5,10,10,10,10.....,5,5,....10,10,...5]
The first 5 deg. sequence is for t = 0 the second for t = pi, and again the last is around 2*pi.
Here is an iterative algorithm I've used.
I didn't look for theoretically-optimal solution, but it works quit well for me.
Notice that this algorithm gets as an input the maximal error of the prime of the polygon agains the ellipse, and not the number of points as you wish.
public static class EllipsePolygonCreator
{
#region Public static methods
public static IEnumerable<Coordinate> CreateEllipsePoints(
double maxAngleErrorRadians,
double width,
double height)
{
IEnumerable<double> thetas = CreateEllipseThetas(maxAngleErrorRadians, width, height);
return thetas.Select(theta => GetPointOnEllipse(theta, width, height));
}
#endregion
#region Private methods
private static IEnumerable<double> CreateEllipseThetas(
double maxAngleErrorRadians,
double width,
double height)
{
double firstQuarterStart = 0;
double firstQuarterEnd = Math.PI / 2;
double startPrimeAngle = Math.PI / 2;
double endPrimeAngle = 0;
double[] thetasFirstQuarter = RecursiveCreateEllipsePoints(
firstQuarterStart,
firstQuarterEnd,
maxAngleErrorRadians,
width / height,
startPrimeAngle,
endPrimeAngle).ToArray();
double[] thetasSecondQuarter = new double[thetasFirstQuarter.Length];
for (int i = 0; i < thetasFirstQuarter.Length; ++i)
{
thetasSecondQuarter[i] = Math.PI - thetasFirstQuarter[thetasFirstQuarter.Length - i - 1];
}
IEnumerable<double> thetasFirstHalf = thetasFirstQuarter.Concat(thetasSecondQuarter);
IEnumerable<double> thetasSecondHalf = thetasFirstHalf.Select(theta => theta + Math.PI);
IEnumerable<double> thetas = thetasFirstHalf.Concat(thetasSecondHalf);
return thetas;
}
private static IEnumerable<double> RecursiveCreateEllipsePoints(
double startTheta,
double endTheta,
double maxAngleError,
double widthHeightRatio,
double startPrimeAngle,
double endPrimeAngle)
{
double yDelta = Math.Sin(endTheta) - Math.Sin(startTheta);
double xDelta = Math.Cos(startTheta) - Math.Cos(endTheta);
double averageAngle = Math.Atan2(yDelta, xDelta * widthHeightRatio);
if (Math.Abs(averageAngle - startPrimeAngle) < maxAngleError &&
Math.Abs(averageAngle - endPrimeAngle) < maxAngleError)
{
return new double[] { endTheta };
}
double middleTheta = (startTheta + endTheta) / 2;
double middlePrimeAngle = GetPrimeAngle(middleTheta, widthHeightRatio);
IEnumerable<double> firstPoints = RecursiveCreateEllipsePoints(
startTheta,
middleTheta,
maxAngleError,
widthHeightRatio,
startPrimeAngle,
middlePrimeAngle);
IEnumerable<double> lastPoints = RecursiveCreateEllipsePoints(
middleTheta,
endTheta,
maxAngleError,
widthHeightRatio,
middlePrimeAngle,
endPrimeAngle);
return firstPoints.Concat(lastPoints);
}
private static double GetPrimeAngle(double theta, double widthHeightRatio)
{
return Math.Atan(1 / (Math.Tan(theta) * widthHeightRatio)); // Prime of an ellipse
}
private static Coordinate GetPointOnEllipse(double theta, double width, double height)
{
double x = width * Math.Cos(theta);
double y = height * Math.Sin(theta);
return new Coordinate(x, y);
}
#endregion
}

Algorithm for points inside a polyline inexplicably fails (AutoCAD)

I'm using the .NET API for Autocad, I have an algorithm (which I did not write) for determining if a point lies within a polygon (straight lines only).
I have been testing my command on the same 51 polygons repeatedly. 99% it will work perfectly. Every once in a while it will fail on 1 or more of the polygons, returning false for over 2000 points I am creating inside the bounding box of the polyline. I have seen it fail when the polyline isa simple rectangle and all of the points lie distributed in a grid within the polyline. It should have returned true over 2000 times in that case. It will never fail for just 1 of the points, it will fail all of them. I have confirmed that the points are being correctly created where I expect them to be and that the vertices of the polygon are where I expect them to be. When it fails, the last angle variable for the last point is at exactly double PI.
I am not doing any multi-threading. The only possibly 'funny' thing I am doing is COM Interop with Excel. This is happening after the transaction has been committed for the part with this algorithm, and I am sure I am cleaning up all my COM objects. I have not been able to reproduce the failure without the COM Interop part but I don't think I've tested it enough yet to have enough absence of evidence.
Any ideas what could be wrong?
bool IsInsidePolygon(Polyline polygon, Point3d pt)
{
int n = polygon.NumberOfVertices;
double angle = 0;
Point pt1, pt2;
for (int i = 0; i < n; i++)
{
pt1.X = polygon.GetPoint2dAt(i).X - pt.X;
pt1.Y = polygon.GetPoint2dAt(i).Y - pt.Y;
pt2.X = polygon.GetPoint2dAt((i + 1) % n).X - pt.X;
pt2.Y = polygon.GetPoint2dAt((i + 1) % n).Y - pt.Y;
angle += Angle2D(pt1.X, pt1.Y, pt2.X, pt2.Y);
}
if (Math.Abs(angle) < Math.PI)
return false;
else
return true;
}
public struct Point
{
public double X, Y;
};
public static double Angle2D(double x1, double y1, double x2, double y2)
{
double dtheta, theta1, theta2;
theta1 = Math.Atan2(y1, x1);
theta2 = Math.Atan2(y2, x2);
dtheta = theta2 - theta1;
while (dtheta > Math.PI)
dtheta -= (Math.PI * 2);
while (dtheta < -Math.PI)
dtheta += (Math.PI * 2);
return (dtheta);
}
Some ideas:
floating point comparison have to be done using a tolerence, this might cause kind of arbitrary results especially in case where the point lies on the polyline (same remark for point3d, they must be compared using a tolerence)
maybe the last point of your polyline is at the same location as the first one, in that case, the angle cannot be computed (perhaps this is why you get a double pi value for the last point). You should then test is first and last points are equals.
I'm not sure your algorithm works regardless if the polyline is clockwise or counterclockwise (I think yes)
you may convert your polyline into a region and rely on region point containment method
another method.
Make one "temporary" point outside the polygon (find the min X and Y and make a point with X-1 and Y-1).
Then make a line between your point and the new "temporary" point.
Check if this line crosses the polygon - use polyline.IntersectWith.
If number of cross points is odd - then your point is inside, if the number of crosses is even - the your point is not inside.
This works for me, hope it will helps you also.
If you find trouble with implementing this, i can send you an example code.
Regards,
Dobriyan Benov
I used some code from Kean Walmsley to convert 3d lines into 2d lines. But be aware that the following is not (always) true:
Point2d pt = lwp.GetPoint2dAt(i);
Point2d npt = new Point2d(lwp.GetPoint3dAt(i).X, lwp.GetPoint3dAt(i).Y);
pt == npt;
I encountered it using it on a polylines, with 3d vertices. I ended up using the npt.
http://through-the-interface.typepad.com/through_the_interface/2007/04/iterating_throu.html

XNA CatmullRom Curves

I'm in need of some clarification over a technique I'm trying. I'm trying to move an entity from point A to point B, but I don't want the entity to travel in a straight line.
For example if the entity is positioned at x: 0, y:0 and I want to get to point x:50, y: 0, I want the entity to travel in a curve to the target, I would imagine the maximum distance it would be away is x:25 y: 25 so it's travelled on the X towards the target but has moved away from the target on the y.
I've investigated a couple of options including splines, curves but what I thought would do the job is the CatmullRom curve. I'm a bit confused how to use it? I want to know where to move my entity each frame rather than what the function returns which is the interpolation. I would appreciate some gudiance as to how to use it.
If there's any alternative methods which might be easier that I've missed, I'd appreciate hearing them as well.
Edit:
To show how I'm getting a curve:
Vector2 blah = Vector2.CatmullRom(
StartPosition,
new Vector2(StartPosition.X + 5, StartPosition.Y + 5),
new Vector2(StartPosition.X + 10, StartPosition.Y + 5),
/*This is the end position*/
new Vector2(StartPosition.X + 15, StartPosition.Y), 0.25f);
The idea eventually is I generate these points on the fly but I'm just trying to work it out at the moment.
As you've noticed, splines produce line segments of different lengths. The tighter the curve, the shorter the segments. This is fine for display purposes, not so useful for path generation for mobiles.
To get a reasonable approximation of a constant-speed traversal of a spline path, you need to do some interpolation along the segments of the curve. Since you already have a set of line segments (between pairs of points returned by Vector2.CatmullRom()) you need an method of walking those segments in constant speed.
Given a set of points and a total distance to move along the path defined as lines between those points, the following (more-or-less pseudo-)code will find a point that lies a specific distance along the path:
Point2D WalkPath(Point2D[] path, double distance)
{
Point curr = path[0];
for (int i = 1; i < path.Length; ++i)
{
double dist = Distance(curr, path[i]);
if (dist < distance)
return Interpolate(curr, path[i], distance / dist;
distance -= dist;
curr = path[i];
}
return curr;
}
There are various optimizations you can do to speed this up, such as storing the path distance with each point in the path to make it easier to lookup during a walk operation. This becomes more important as your paths get more complex, but is probable overkill for a path with only a few segments.
Edit: Here's an example that I did with this method in JavaScript a while back. It's a proof-of-concept, so don't look too critically at the code :P
Edit: more information on spline generation
Given a set of 'knot' points - being points that a curve must pass through in sequence - the most obvious fit for a curve algorithm is Catmull-Rom. The downside is that C-R needs two additional control points that can be awkward to generate automatically.
A while back I found a fairly useful article online (which I can't locate anymore to give correct attribution) that calculated a set of control points based on the locations of sets of points within your path. Here's my C# code for the method that calculates the control points:
// Calculate control points for Point 'p1' using neighbour points
public static Point2D[] GetControlsPoints(Point2D p0, Point2D p1, Point2D p2, double tension = 0.5)
{
// get length of lines [p0-p1] and [p1-p2]
double d01 = Distance(p0, p1);
double d12 = Distance(p1, p2);
// calculate scaling factors as fractions of total
double sa = tension * d01 / (d01 + d12);
double sb = tension * d12 / (d01 + d12);
// left control point
double c1x = p1.X - sa * (p2.X - p0.X);
double c1y = p1.Y - sa * (p2.Y - p0.Y);
// right control point
double c2x = p1.X + sb * (p2.X - p0.X);
double c2y = p1.Y + sb * (p2.Y - p0.Y);
// return control points
return new Point2D[] { new Point2D(c1x, c1y), new Point2D(c2x, c2y) };
}
The tension parameter adjusts the control point generation to change the tightness of the curve. Higher values result in broader curves, lower values in tighter curves. Play with it and see what value works best for you.
Given a set of 'n' knots (points on the curve), we can generate a set of control points that will be used to generate the curves between pairs of knots:
// Generate all control points for a set of knots
public static List<Point2D> GenerateControlPoints(List<Point2D> knots)
{
if (knots == null || knots.Count < 3)
return null;
List<Point2D> res = new List<Point2D>();
// First control point is same as first knot
res.Add(knots.First());
// generate control point pairs for each non-end knot
for (int i = 1; i < knots.Count - 1; ++i)
{
Point2D[] cps = GetControlsPoints(knots[i - 1], knots[i], knots[i+1]);
res.AddRange(cps);
}
// Last control points is same as last knot
res.Add(knots.Last());
return res;
}
So now you have an array of 2*(n-1) control points, which you can then use to generate the actual curve segments between the knot points.
public static Point2D LinearInterp(Point2D p0, Point2D p1, double fraction)
{
double ix = p0.X + (p1.X - p0.X) * fraction;
double iy = p0.Y + (p1.Y - p0.Y) * fraction;
return new Point2D(ix, iy);
}
public static Point2D BezierInterp(Point2D p0, Point2D p1, Point2D c0, Point2D c1, double fraction)
{
// calculate first-derivative, lines containing end-points for 2nd derivative
var t00 = LinearInterp(p0, c0, fraction);
var t01 = LinearInterp(c0, c1, fraction);
var t02 = LinearInterp(c1, p1, fraction);
// calculate second-derivate, line tangent to curve
var t10 = LinearInterp(t00, t01, fraction);
var t11 = LinearInterp(t01, t02, fraction);
// return third-derivate, point on curve
return LinearInterp(t10, t11, fraction);
}
// generate multiple points per curve segment for entire path
public static List<Point2D> GenerateCurvePoints(List<Point2D> knots, List<Point2D> controls)
{
List<Point2D> res = new List<Point2D>();
// start curve at first knot
res.Add(knots[0]);
// process each curve segment
for (int i = 0; i < knots.Count - 1; ++i)
{
// get knot points for this curve segment
Point2D p0 = knots[i];
Point2D p1 = knots[i + 1];
// get control points for this curve segment
Point2D c0 = controls[i * 2];
Point2D c1 = controls[i * 2 + 1];
// calculate 20 points along curve segment
int steps = 20;
for (int s = 1; s < steps; ++s)
{
double fraction = (double)s / steps;
res.Add(BezierInterp(p0, p1, c0, c1, fraction));
}
}
return res;
}
Once you have run this over your knots you now have a set of interpolated points that are a variable distance apart, distance depending on the curvature of the line. From this you run the original WalkPath method iteratively to generate a set of points that are a constant distance apart, which define your mobile's progression along the curve at constant speed.
The heading of your mobile at any point in the path is (roughly) the angle between the points on either side. For any point n in the path, the angle between p[n-1] and p[n+1] is the heading angle.
// get angle (in Radians) from p0 to p1
public static double AngleBetween(Point2D p0, Point2D p1)
{
return Math.Atan2(p1.X - p0.X, p1.Y - p0.Y);
}
I've adapted the above from my code, since I use a Point2D class I wrote ages ago that has a lot of the functionality - point arithmetic, interpolation, etc - built in. I might have added some bugs during translation, but hopefully they'll be easy to spot when you're playing with it.
Let me know how it goes. If you run into any particular difficulties I'll see what I can do to help.

Categories

Resources