Related
Bresenham's line drawing algorithm is well known and quite simple to implement.
While there are more advanced ways to draw anti-ailesed lines, Im interested in writing a function which draws a single pixel width non anti-aliased line, based on floating point coordinates.
This means while the first and last pixels will remain the same, the pixels drawn between them will have a bias based on the sub-pixel position of both end-points.
In principle this shouldn't be all that complicated, since I assume its possible to use the sub-pixel offsets to calculate an initial error value to use when plotting the line, and all other parts of the algorithm remain the same.
No sub pixel offset:
X###
###X
Assuming the right hand point has a sub-pixel position close to the top, the line could look like this:
With sub pixel offset for example:
X######
X
Is there a tried & true method of drawing a line that takes sub-pixel coordinates into account?
Note:
This seems like a common operation, I've seen OpenGL drivers take this into account for example - using GL_LINE, though from a quick search I didn't find any answers online - maybe used wrong search terms?
At a glance this question looks like it might be a duplicate of: Precise subpixel line drawing algorithm (rasterization algorithm)However that is asking about drawing a wide line, this is asking about offsetting a single pixel line.
If there isn't some standard method, I'll try write this up to post as an answer.
Having just encountered the same challenge, I can confirm that this is possible as you expected.
First, return to the simplest form of the algorithm: (ignore the fractions; they'll disappear later)
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = -0.5
while x < x1:
if error > 0:
y += 1
error -= 1
paint(x, y)
x += 1
error += dy/dx
This means that for integer coordinates, we start half a pixel above the pixel boundary (error = -0.5), and for each pixel we advance in x, we increase the ideal y coordinate (and therefore the current error) by dy/dx.
First let's see what happens if we stop forcing x0, y0, x1 and y1 to be integers: (this will also assume that instead of using pixel centres, the coordinates are relative to the bottom-left of each pixel1, since once you support sub-pixel positions you can simply add half the pixel width to the x and y to return to pixel-centred logic)
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = (0.5 - (x0 % 1)) * dy/dx + (y0 % 1) - 1
while x < x1:
if error > 0:
y += 1
error -= 1
paint(x, y)
x += 1
error += dy/dx
The only change was the initial error calculation. The new value comes from simple trig to calculate the y coordinate when x is at the pixel centre. It's worth noting that you can use the same idea to clip the line's start position to be within some bound, which is another challenge you'll likely face when you want to start optimising things.
Now we just need to convert this into integer-only arithmetic. We'll need some fixed multiplier for the fractional inputs (scale), and the divisions can be handled by multiplying them out, just as the standard algorithm does.
# assumes x0, y0, x1 and y1 are pre-multiplied by scale
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = (scale - 2 * (x0 % scale)) * dy + 2 * (y0 % scale) * dx - 2 * dx * scale
while x < x1:
if error > 0:
y += scale
error -= 2 * dx * scale
paint(x / scale, y / scale)
x += scale
error += 2 * dy * scale
Note that x, y, dx and dy keep the same scaling factor as the input variables (scale), whereas error has a more complex scaling factor: 2 * dx * scale. This allows it to absorb the division and fraction in its original formulation, but means we need to apply the same scale everywhere we use it.
Obviously there's a lot of room to optimise here, but that's the basic algorithm. If we assume scale is a power-of-two (2^n), we can start to make things a little more efficient:
dx = x1 - x0
dy = y1 - y0
mask = (1 << n) - 1
error = (2 * (y0 & mask) - (2 << n)) * dx - (2 * (x0 & mask) - (1 << n)) * dy
x = x0 >> n
y = y0 >> n
while x < (x1 >> n):
if error > 0:
y += 1
error -= 2 * dx << n
paint(x, y)
x += 1
error += 2 * dy << n
As with the original, this only works in the (x >= y, x > 0, y >= 0) octant. The usual rules apply for extending it to all cases, but note that there are a few extra gotchyas due to the coordinates no-longer being centred in the pixel (i.e. reflections become more complex).
You'll also need to watch out for integer overflows: error has twice the precision of the input variables, and a range of up to twice the length of the line. Plan your inputs, precision, and variable types accordingly!
1: Coordinates are relative to the corner which is closest to 0,0. For an OpenGL-style coordinate system that's the bottom left, but it could be the top-left depending on your particular scenario.
I had a similar problem, with the addition of needing sub-pixel endpoints, I also needed to make sure all pixels which intersect the line are drawn.
I'm not sure that my solution will be helpful to OP, both because its been 4+ years, and because of the sentence "This means while the first and last pixels will remain the same..." For me, that is actually a problem (More on that later). Hopefully this may be helpful to others.
I don't know if this can be considered to be Bresenham's algorithm, but it is awful similar. I'll explain it for the (+,+) quadrant. Lets say you wish to draw a line from point (Px,Py) to (Qx,Qy) over a grid of pixels with width W. Having a grid width W > 1 allows for sub-pixel endpoints.
For a line going in the (+,+) quadrant, the starting point is easy to calculate, just take the floor of (Px,Py). As you will see later, this only works if Qx >= Px & Qy >= Py.
Now you need to find which pixel to go to next. There are 3 possibilities: (x+1,y), (x,y+1), & (x+1,y+1). To make this decision, I use the 2D cross product defined as:
If this value is negative, vector b is right/clockwise of vector a.
If this value is positive, vector b is left/anti-clockwise of vector a.
If this value is zero vector b points in the same direction as vector a.
To make the decision on which pixel is next, compare the cross product between the line P-Q [red in image below] and a line between the point P and the top-right pixel (x+1,y+1) [blue in image below].
The vector between P & the top-right pixel can be calculated as:
So, we will use the value from the 2D cross product:
If this value is negative, the next pixel will be (x,y+1).
If this value is positive, the next pixel will be (x+1,y).
If this value is exactly zero, the next pixel will be (x+1,y+1).
That works fine for the starting pixel, but the rest of the pixels will not have a point that lies inside them. Luckily, after the initial point, you don't need a point to be inside the pixel for the blue vector. You can keep extending it like so:
The blue vector starts at the starting point of the line, and is updated to the (x+1,y+1) for every pixel. The rule for which pixel to take is the same. As you can see, the red vector is right of the blue vector. So, the next pixel will be the one right of the green pixel.
The value for the cross product needs updated for every pixel, depending on which pixel you took.
Add dx if the next pixel was (x+1), add dy if the pixel was (y+1). Add both if the pixel went to (x+1,y+1).
This process is repeated until it reaches the ending pixel, (Qx / W, Qy / W).
All combined this leads to the following code:
int dx = x2 - x2;
int dy = y2 - y1;
int local_x = x1 % width;
int local_y = y1 % width;
int cross_product = dx*(width-local_y) - dy*(width-local_x);
int dx_cross = -dy*width;
int dy_cross = dx*width;
int x = x1 / width;
int y = y1 / width;
int end_x = x2 / width;
int end_y = y2 / width;
while (x != end_x || y != end_y) {
SetPixel(x,y,color);
int old_cross = cross_product;
if (old_cross >= 0) {
x++;
cross_product += dx_cross;
}
if (old_cross <= 0) {
y++;
cross_product += dy_cross;
}
}
Making it work for all quadrants is a matter of reversing the local coordinates and some absolute values. Heres the code which works for all quadrants:
int dx = x2 - x1;
int dy = y2 - y1;
int dx_x = (dx >= 0) ? 1 : -1;
int dy_y = (dy >= 0) ? 1 : -1;
int local_x = x1 % square_width;
int local_y = y1 % square_width;
int x_dist = (dx >= 0) ? (square_width - local_x) : (local_x);
int y_dist = (dy >= 0) ? (square_width - local_y) : (local_y);
int cross_product = abs(dx) * abs(y_dist) - abs(dy) * abs(x_dist);
dx_cross = -abs(dy) * square_width;
dy_cross = abs(dx) * square_width;
int x = x1 / square_width;
int y = y1 / square_width;
int end_x = x2 / square_width;
int end_y = y2 / square_width;
while (x != end_x || y != end_y) {
SetPixel(x,y,color);
int old_cross = cross_product;
if (old_cross >= 0) {
x += dx_x;
cross_product += dx_cross;
}
if (old_cross <= 0) {
y += dy_y;
cross_product += dy_cross;
}
}
However there is a problem! This code will not stop in some cases. To understand why, you need to really look into exactly what conditions count as the intersection between a line and a pixel.
When exactly is a pixel drawn?
I said I need to make that all pixels which intersect a line need to be drawn. But there's some ambiguity in the edge cases.
Here is a list of all possible intersections in which a pixel will be drawn for a line where Qx >= Px & Qy >= Py:
A - If a line intersects the pixel completely, the pixel will be drawn.
B - If a vertical line intersects the pixel completely, the pixel will be drawn.
C - If a horizontal line intersects the pixel completely, the pixel will be drawn.
D - If a vertical line perfectly touches the left of the pixel, the pixel will be drawn.
E - If a horizontal line perfectly touches the bottom of the pixel, the pixel will be drawn.
F - If a line endpoint starts inside of a pixel going (+,+), the pixel will be drawn.
G - If a line endpoint starts exactly on the left side of a pixel going (+,+), the pixel will be drawn.
H - If a line endpoint starts exactly on the bottom side of a pixel going (+,+), the pixel will be drawn.
I - If a line endpoint starts exactly on the bottom left corner of a pixel going (+,+), the pixel will be drawn.
And here are some pixels which do NOT intersect the line:
A' - If a line obviously doesn't intersect a pixel, the pixel will NOT be drawn.
B' - If a vertical line obviously doesn't intersect a pixel, the pixel will NOT be drawn.
C' - If a horizontal line obviously doesn't intersect a pixel, the pixel will NOT be drawn.
D' - If a vertical line exactly touches the right side of a pixel, the pixel will NOT be drawn.
E' - If a horizontal line exactly touches the top side of a pixel, the pixel will NOT be drawn.
F' - If a line endpoint starts exactly on the top right corner of a pixel going in the (+,+) direction, the pixel will NOT be drawn.
G' - If a line endpoint starts exactly on the top side of a pixel going in the (+,+) direction, the pixel will NOT be drawn.
H' - If a line endpoint starts exactly on the right side of a pixel going in the (+,+) direction, the pixel will NOT be drawn.
I' - If a line exactly touches a corner of the pixel, the pixel will NOT be drawn. This applies to all corners.
Those rules apply as you would expect (just flip the image) for the other quadrants. The problem I need to highlight is when an endpoint lies exactly on the edge of a pixel. Take a look at this case:
This is like image G' above, except the y-axis is flipped because the Qy < Py. There are 4x4 red dots because W is 4, making the pixel dimensions 4x4. Each of the 4 dots are the ONLY endpoints a line can touch. The line drawn goes from (1.25, 1.0) to (somewhere).
This shows why it's incorrect (at least how I defined pixel-line intersections) to say the pixel endpoints can be calculated as the floor of the line endpoints. The floored pixel coordinate for that endpoint seems to be (1,1), but it is clear that the line never really intersects that pixel. It just touches it, so I don't want to draw it.
Instead of flooring the line endpoints, you need to floor the minimal endpoints, and ceil the maximal endpoints minus 1 across both x & y dimensions.
So finally here is the complete code which does this flooring/ceiling:
int dx = x2 - x1;
int dy = y2 - y1;
int dx_x = (dx >= 0) ? 1 : -1;
int dy_y = (dy >= 0) ? 1 : -1;
int local_x = x1 % square_width;
int local_y = y1 % square_width;
int x_dist = (dx >= 0) ? (square_width - local_x) : (local_x);
int y_dist = (dy >= 0) ? (square_width - local_y) : (local_y);
int cross_product = abs(dx) * abs(y_dist) - abs(dy) * abs(x_dist);
dx_cross = -abs(dy) * square_width;
dy_cross = abs(dx) * square_width;
int x = x1 / square_width;
int y = y1 / square_width;
int end_x = x2 / square_width;
int end_y = y2 / square_width;
// Perform ceiling/flooring of the pixel endpoints
if (dy < 0)
{
if ((y1 % square_width) == 0)
{
y--;
cross_product += dy_cross;
}
}
else if (dy > 0)
{
if ((y2 % square_width) == 0)
end_y--;
}
if (dx < 0)
{
if ((x1 % square_width) == 0)
{
x--;
cross_product += dx_cross;
}
}
else if (dx > 0)
{
if ((x2 % square_width) == 0)
end_x--;
}
while (x != end_x || y != end_y) {
SetPixel(x,y,color);
int old_cross = cross_product;
if (old_cross >= 0) {
x += dx_x;
cross_product += dx_cross;
}
if (old_cross <= 0) {
y += dy_y;
cross_product += dy_cross;
}
}
This code itself hasn't been tested, but it comes slightly modified from my GitHub project where it has been tested.
Let's assume you want to draw a line from P1 = (x1, y1) to P2 = (x2, y2) where all the numbers are floating point pixel coordinates.
Calculate the true pixel coordinates of P1 and P2 and paint them: P* = (round(x), round(y)).
If abs(x1* - x2*) <= 1 && abs(y1* - y2*) <= 1 then you are finished.
Decide whether it is a horizontal (true) or a vertical line (false): abs(x1 - x2) >= abs(y1 - y2).
If it is a horizontal line and x1 > x2 or if it is a vertical line and y1 > y2: swap P1 with P2 (and also P1* with P2*).
If it is a horizontal line you can get the y-coordinates for all the x-coordinates between x1* and x2* with the following formula:
y(x) = round(y1 + (x - x1) / (x2 - x1) * (y2 - y1))
If you have a vertical line you can get the x-coordinates for all the y-coordinates between y1* and y2* with this formula:
x(y) = round(x1 + (y - y1) / (y2 - y1) * (x2 - x1))
Here is a demo you can play around with, you can try different points on line 12.
I am trying to draw the line formed by the intersections of two planes in 3D, but I am having trouble understanding the math, which has been explained here and here.
I tried to figure it out myself, but the closest that I got to a solution was a vector pointing along the same direction as the intersection line, by using the cross product of the normals of the planes. I have no idea how to find a point on the intersection line, any point would do. I think that this method is a dead end. Here is a screenshot of this attempt:
I tried to use the solution mentioned in this question, but it has a dead link to the original explanation, and the equation didn't work for me (it has unbalanced parentheses, which I tried to correct below).
var planeA = new THREE.Plane((new THREE.Vector3(0, 0, 1)).normalize(), 100);
var planeB = new THREE.Plane((new THREE.Vector3(1, 1, 1)).normalize(), -100);
var x1 = planeA.normal.x,
y1 = planeA.normal.y,
z1 = planeA.normal.z,
d1 = planeA.constant;
var x2 = planeB.normal.x,
y2 = planeB.normal.y,
z2 = planeB.normal.z,
d2 = planeB.constant;
var point1 = new THREE.Vector3();
point1.x = 0;
point1.z = (y2 / y1) * (d1 - d2) / (z2 - z1 * y2 / y1);
point1.y = (-z1 * point1.z - d1) / y1;
var point2 = new THREE.Vector3();
point2.x = 1;
point2.z = (y2 / y1) * (x1 * point2.x + d1) - (x2 * point2.x - d2) / (z2 - z1 * y2 / y1);
point2.y = (-z1 * point2.z - x1 * point2.x - d1) / y1;
console.log(point1, point2);
output:
THREE.Vector3 {x: -1, y: NaN, z: NaN, …}
THREE.Vector3 {x: 1, y: Infinity, z: -Infinity, …}
expected output:
A point along the intersection where x = 0, and
Another point on the same line where x = 1
If someone could point me to a good explanation of how this is supposed to work, or an example of a plane-plane intersection algorithm, I would be grateful.
Here is an implementation of a solution for plane-plane intersections described at http://geomalgorithms.com/a05-_intersect-1.html . Essentially, you first use the cross product of the normals of the planes to find the direction of a line in both planes. Secondly, you use some algebra on the implicit equation of the planes (P . n + d = 0 where P is some point on the plane, n is the normal and d is the plane constant) to solve for a point which is on the intersection of the planes and also on one of the x=0, y=0 or z=0 planes. The solution is then the line described by a point and a vector. I was using three.js version 79
/*
Algorithm taken from http://geomalgorithms.com/a05-_intersect-1.html. See the
section 'Intersection of 2 Planes' and specifically the subsection
(A) Direct Linear Equation
*/
function intersectPlanes(p1, p2) {
// the cross product gives us the direction of the line at the intersection
// of the two planes, and gives us an easy way to check if the two planes
// are parallel - the cross product will have zero magnitude
var direction = new THREE.Vector3().crossVectors(p1.normal, p2.normal)
var magnitude = direction.distanceTo(new THREE.Vector3(0, 0, 0))
if (magnitude === 0) {
return null
}
// now find a point on the intersection. We use the 'Direct Linear Equation'
// method described in the linked page, and we choose which coordinate
// to set as zero by seeing which has the largest absolute value in the
// directional vector
var X = Math.abs(direction.x)
var Y = Math.abs(direction.y)
var Z = Math.abs(direction.z)
var point
if (Z >= X && Z >= Y) {
point = solveIntersectingPoint('z', 'x', 'y', p1, p2)
} else if (Y >= Z && Y >= X){
point = solveIntersectingPoint('y', 'z', 'x', p1, p2)
} else {
point = solveIntersectingPoint('x', 'y', 'z', p1, p2)
}
return [point, direction]
}
/*
This method helps finding a point on the intersection between two planes.
Depending on the orientation of the planes, the problem could solve for the
zero point on either the x, y or z axis
*/
function solveIntersectingPoint(zeroCoord, A, B, p1, p2){
var a1 = p1.normal[A]
var b1 = p1.normal[B]
var d1 = p1.constant
var a2 = p2.normal[A]
var b2 = p2.normal[B]
var d2 = p2.constant
var A0 = ((b2 * d1) - (b1 * d2)) / ((a1 * b2 - a2 * b1))
var B0 = ((a1 * d2) - (a2 * d1)) / ((a1 * b2 - a2 * b1))
var point = new THREE.Vector3()
point[zeroCoord] = 0
point[A] = A0
point[B] = B0
return point
}
var planeA = new THREE.Plane((new THREE.Vector3(0, 0, 1)).normalize(), 100)
var planeB = new THREE.Plane((new THREE.Vector3(1, 1, 1)).normalize(), -100)
var [point, direction] = intersectPlanes(planeA, planeB)
When I have problems like this, I usually let a symbolic algebra package (Mathematica in this case) deal with it. After typing
In[1]:= n1={x1,y1,z1};n2={x2,y2,z2};p={x,y,z};
In[2]:= Solve[n1.p==d1&&n2.p==d2,p]
and simplifying and substituting x=0 and x=1, I get
d2 z1 - d1 z2 d2 y1 - d1 y2
Out[5]= {{{y -> -------------, z -> ----------------}},
y2 z1 - y1 z2 -(y2 z1) + y1 z2
d2 z1 - x2 z1 - d1 z2 + x1 z2
> {{y -> -----------------------------,
y2 z1 - y1 z2
d2 y1 - x2 y1 + (-d1 + x1) y2
> z -> -----------------------------}}}
-(y2 z1) + y1 z2
It is easy to let three.js solve this for you.
If you were to express your problem in matrix notation
m * x = v
Then the solution for x is
x = inverse( m ) * v
We'll use a 4x4 matrix for m, because three.js has an inverse() method for the Matrix4 class.
var x1 = 0,
y1 = 0,
z1 = 1,
d1 = 100;
var x2 = 1,
y2 = 1,
z2 = 1,
d2 = -100;
var c = 0; // the desired value for the x-coordinate
var v = new THREE.Vector4( d1, d2, c, 1 );
var m = new THREE.Matrix4( x1, y1, z1, 0,
x2, y2, z2, 0,
1, 0, 0, 0,
0, 0, 0, 1
);
var minv = new THREE.Matrix4().getInverse( m );
v.applyMatrix4( minv );
console.log( v );
The x-component of v will be equal to c, as desired, and the y- and z-components will contain the values you are looking for. The w-component is irrelevalent.
Now, repeat for the next value of c, c = 1.
three.js r.58
Prerequisites
Recall that to represent a line we need a vector describing its direction and a point through which this line goes. This is called parameterized form:
line_point(t) = t * (point_2 - point_1) + point_1
where point_1 and point_2 are arbitrary points through which the line goes, and t is a scalar which parameterizes our line. Now we can find any point line_point(t) on the line if we put arbitrary t into the equation above.
NOTE: The term (point_2 - point_1) is nothing, but a vector describing the direction of our line, and the term point_1 is nothing, but a point through which our line goes (of course point_2) would also be fine to use too.
The Algorithm
Find the direction direction of the intersection line by taking
cross product of plane normals, i.e. direction = cross(normal_1,
normal_2).
Take any plane, for example the first one, and find any 2 distinct points
on this plane: point_1 and point_2. If we assume that the plane equation
is of the form a1 * x + b1 * y + c1 * z + d1 = 0, then to find 2
distinct points we could do the following:
y1 = 1
z1 = 0
x1 = -(b1 + d1) / a1
y2 = 0
z2 = 1
x2 = -(c1 + d1) / a1
where point_1 = (x1, y1, z1) and point_2 = (x2, y2, z2).
Now that we have 2 points, we can construct the parameterized
representation of the line lying on this first plane:
line_point(t) = t * (point_2 - point_1) + point_1, where line_point(t)
describes any point on this line, and t is just an input scalar
(frequently called parameter).
Find the intersection point intersection_point of the line
line_point(t) and the second plane a2 * x + b2 * y + c2 * z + d2 = 0 by using
the standard line-plane intersection algorithm (pay attention to the
Algebraic form section as this is all you need to implement line-plane
intersection, if you haven't done so already).
Our intersection line is now found and can be constructed in
parameterized form as usual: intersection_line_point(s) = s *
direction + intersection_point, where intersection_line_point(s)
describes any point on this intersection line, and s is parameter.
NOTE: I didn't read this algorithm anywhere, I've just devised it from the top of my head based on my knowledge of linear algebra. That doesn't mean that it doesn't work, but it might be possible that this algorithm can be optimized further.
Conditioning
When 2 normal vectors normal_1 and normal_2 are almost collinear this problem gets extremely ill-conditioned. Geometrically it means that the 2 planes are almost parallel to each other and determining the intersection line with acceptable precision becomes impossible in finite-precision arithmetic which is floating-point arithmetic in this case.
I have three X/Y points that form a parabola. I simply need to calculate what the vertex of the parabola is that goes through these three points. Preferably a quick way as I have to do a LOT of these calculations!
The "Ask A Scientist" website provides this answer:
The general form of a parabola is given by the equation: A * x^2 + B * x + C = y where A, B, and C are arbitrary Real constants. You have three pairs of points that are (x,y) ordered pairs. Substitute the x and y values of each point into the equation for a parabola. You will get three LINEAR equations in three unknowns, the three constants. You can then easily solve this system of three equations for the values of A, B, and C, and you'll have the equation of the parabola that intersects your 3 points. The vertex is where the first derivative is 0, a little algebra gives: ( -B/2A , C - B^2/4A ) for the vertex.
It would be nice to see actual code that does this calculation in C# or C++. Anybody?
Thanks David, I converted your pseudocode to the following C# code:
public static void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, out double xv, out double yv)
{
double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
double A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
double B = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
double C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
xv = -B / (2*A);
yv = C - B*B / (4*A);
}
This is what I wanted. A simple calculation of the parabola's vertex. I'll handle integer overflow later.
This is really just a simple linear algebra problem, so you can do the calculation symbolically. When you substitute in the x and y values of your three points, you'll get three linear equations in three unknowns.
A x1^2 + B x1 + C = y1
A x2^2 + B x2 + C = y2
A x3^2 + B x3 + C = y3
The straightforward way to solve this is to invert the matrix
x1^2 x1 1
x2^2 x2 1
x3^2 x3 1
and multiply it by the vector
y1
y2
y3
The result of this is... okay, not exactly all that simple ;-) I did it in Mathematica, and here are the formulas in pseudocode:
denom = (x1 - x2)(x1 - x3)(x2 - x3)
A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
B = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3)) / denom
C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom
Alternatively, if you wanted to do the matrix math numerically, you'd typically turn to a linear algebra system (like ATLAS, though I'm not sure if it has C#/C++ bindings).
In any case, once you have the values of A, B, and C as calculated by these formulas, you just have to plug them into the expressions given in the question, -B / 2A and C - B^2/4A, to calculate the coordinates of the vertex.1
Note that if the original three points have coordinates that make denom a very large or very small number, doing the calculation directly might be susceptible to significant numerical error. In that case it might be better to modify it a bit, to avoid dividing by the denominators where they would cancel out anyway:
denom = (x1 - x2)(x1 - x3)(x2 - x3)
a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2))
b = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3))
c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3)
and then the coordinates of the vertex are -b / 2a and (c - b^2 / 4a) / denom. There are various other situations that might benefit from "tricks" like this, such as if A is very large or very small, or if C is nearly equal to B^2 / 4A so that their difference is very small, but I think those situations vary enough that a full discussion would be better left for case-by-case followup questions.
Converting all of this to code in the language of your choice is left as an exercise for the reader. (It should be pretty trivial in any language that uses standard infix operator syntax, e.g. as AZDean showed in C#.)
1In the initial version of the answer I thought this would be obvious, but it seems there are a lot of people who like having it mentioned explicitly.
You get the following three equations by direct substitution:
A*x1^2+B*x1+C=y1
A*x2^2+B*x2+C=y2
A*x3^2+B*x3+C=y3
You can solve this by noting that this is equivalent to the matrix product:
[x1^2 x1 1] [A] [y1]
|x2^2 x2 1|*|B| = |y2|
[x3^2 x3 1] [C] [y3]
So you can get A,B, and C by inverting the matrix and multiplying the inverse with the vector on the right.
I see that while I've been posting this John Rasch has linked to tutorial that goes into more depth on actually solving the matrix equation, so you can follow those instructions to get the answer. Inverting a 3x3 matrix is quite easy, so this shouldn't be too tough.
Here is a code in Fortran that implements #david-z and #AZDean's solution:
subroutine parabola_vertex(x1, y1, x2, y2, x3, y3, xv, yv)
real(dp), intent(in) :: x1, y1, x2, y2, x3, y3
real(dp), intent(out) :: xv, yv
real(dp) :: denom, A, B, C
denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
B = (x3**2 * (y1 - y2) + x2**2 * (y3 - y1) + x1**2 * (y2 - y3)) / denom
C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + &
x1 * x2 * (x1 - x2) * y3) / denom
xv = -B / (2*A)
yv = C - B**2 / (4*A)
end subroutine
I've done something similar to #piSHOCK's answer, also based on #AZDean's code. If you need to run it heavily (or to use it in Matlab like me), this might be the fastest one.
My assumption is that x1 == -1, x2 == 0, x3 == 1.
a = y2 - ( y1 + y3) / 2 % opposite signal compared to the original definition of A
b = (y3 - y1) / 4 % half of the originally defined B
xExtr = b / a
yExtr = y2 + b * xExtr % which is equal to y2 + b*b / a
def vertex(x1,x2,x3,y1,y2,y3):
'''Given three pairs of (x,y) points return the vertex of the
parabola passing through the points. Vectorized and common expression reduced.'''
#Define a sequence of sub expressions to reduce redundant flops
x0 = 1/x2
x4 = x1 - x2
x5 = 1/x4
x6 = x1**2
x7 = 1/x6
x8 = x2**2
x9 = -x7*x8 + 1
x10 = x0*x1*x5*x9
x11 = 1/x1
x12 = x3**2
x13 = x11*x12
x14 = 1/(x0*x13 - x0*x3 - x11*x3 + 1)
x15 = x14*y3
x16 = x10*x15
x17 = x0*x5
x18 = -x13 + x3
x19 = y2*(x1*x17 + x14*x18*x6*x9/(x4**2*x8))
x20 = x2*x5
x21 = x11*x20
x22 = x14*(-x12*x7 + x18*x21)
x23 = y1*(-x10*x22 - x21)
x24 = x16/2 - x19/2 - x23/2
x25 = -x17*x9 + x7
x26 = x0*x1*x14*x18*x5
x27 = 1/(-x15*x25 + y1*(x20*x7 - x22*x25 + x7) + y2*(-x17 + x25*x26))
x28 = x24*x27
return x28,x15 + x22*y1 + x24**2*x27 - x26*y2 + x28*(-x16 + x19 + x23)
This smells like homework. "Ask a Scientist" is right on. Say your 3 points are (x1, y1), (x2, y2), and (x3, y3). Then, you get three linear equations:
| M11 M12 M13 | | A | | Z1 |
| M21 M22 M23 | * | B | = | Z2 |
| M31 M32 M33 | | C | | Z3 |
Where M11 = x12, M12 = x1, M13 = 1, Z1 = y1, and similarly for the other two rows using (x2, y2) and (x3, y3) in place of (x1, y1).
Solving this system of 3 equations will give you a solution for A, B, and C.
Running at https://ideone.com/y0SxKU
#include <iostream>
using namespace std;
// calculate the vertex of a parabola given three points
// https://stackoverflow.com/q/717762/16582
// #AZDean implementation with given x values
void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, double& xv, double& yv)
{
double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
double A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
double B = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
double C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
xv = -B / (2*A);
yv = C - B*B / (4*A);
}
// #piSHOCK immplementation assuming regular x values ( wrong!!! )
void CalcParabolaVertex2( int y1, int y2, int y3, double& xv, double& yv)
{
double d1 = y1 - y2;
double d2 = y1 - y3;
double a = -d1 + 0.5 * d2;
double b = 2 * d1 - 0.5 * d2;
double c = -y1;
xv = -0.5 * b / a;
yv = c - 0.25 * b * b / a;
}
// corrected immplementation assuming regular x values
void CalcParabolaVertex3( int y1, int y2, int y3, double& xv, double& yv)
{
double d1 = y1 - y2;
double d2 = y1 - y3;
double a = d1 - 0.5 * d2;
double b = -2 * d1 + 0.5 * d2;
double c = y1;
xv = -0.5 * b / a;
yv = c - 0.25 * b * b / a;
}
int main() {
double xv, yv;
CalcParabolaVertex( 0, 100, 1, 500, 2, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
CalcParabolaVertex2( 100, 500, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
CalcParabolaVertex3( 100, 500, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
return 0;
}
I have added a couple of unit tests for negative going peaks: running live at https://ideone.com/WGK90S
I want to know a piece of a code which can actually tell me if 3 points in a 2D space are on the same line or not. A pseudo-code is also sufficient but Python is better.
You can check if the area of the ABC triangle is 0:
[ Ax * (By - Cy) + Bx * (Cy - Ay) + Cx * (Ay - By) ] / 2
Of course, you don't actually need to divide by 2.
This is C++, but you can adapt it to python:
bool collinear(int x1, int y1, int x2, int y2, int x3, int y3) {
return (y1 - y2) * (x1 - x3) == (y1 - y3) * (x1 - x2);
}
Basically, we are checking that the slopes between point 1 and point 2 and point 1 and point 3 match. Slope is change in y divided by change in x, so we have:
y1 - y2 y1 - y3
------- = --------
x1 - x2 x1 - x3
Cross multiplying gives (y1 - y2) * (x1 - x3) == (y1 - y3) * (x1 - x2);
Note, if you are using doubles, you can check against an epsilon:
bool collinear(double x1, double y1, double x2, double y2, double x3, double y3) {
return fabs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) <= 1e-9;
}
y - y0 = a(x-x0) (1) while a = (y1 - y0)/(x1 - x0) and A(x0, y0) B(x1, y1) C(x2, y2). See whether C statisfies (1). You just replace the appropriate values.
Details
Read this, and use it to find the equation of a line through the first two points. Follow the instructions to find m and b. Then for your third point, calculate mx + b - y. If the result is zero, the third point is on the same line as the first two.
Rule 1: In any linear 2d space, two points are always on the same line.
Take 2 points and build an equation that represents a line through them.
Then check if the third point is also on that line.
Good luck.
I have three X/Y points that form a parabola. I simply need to calculate what the vertex of the parabola is that goes through these three points. Preferably a quick way as I have to do a LOT of these calculations!
The "Ask A Scientist" website provides this answer:
The general form of a parabola is given by the equation: A * x^2 + B * x + C = y where A, B, and C are arbitrary Real constants. You have three pairs of points that are (x,y) ordered pairs. Substitute the x and y values of each point into the equation for a parabola. You will get three LINEAR equations in three unknowns, the three constants. You can then easily solve this system of three equations for the values of A, B, and C, and you'll have the equation of the parabola that intersects your 3 points. The vertex is where the first derivative is 0, a little algebra gives: ( -B/2A , C - B^2/4A ) for the vertex.
It would be nice to see actual code that does this calculation in C# or C++. Anybody?
Thanks David, I converted your pseudocode to the following C# code:
public static void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, out double xv, out double yv)
{
double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
double A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
double B = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
double C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
xv = -B / (2*A);
yv = C - B*B / (4*A);
}
This is what I wanted. A simple calculation of the parabola's vertex. I'll handle integer overflow later.
This is really just a simple linear algebra problem, so you can do the calculation symbolically. When you substitute in the x and y values of your three points, you'll get three linear equations in three unknowns.
A x1^2 + B x1 + C = y1
A x2^2 + B x2 + C = y2
A x3^2 + B x3 + C = y3
The straightforward way to solve this is to invert the matrix
x1^2 x1 1
x2^2 x2 1
x3^2 x3 1
and multiply it by the vector
y1
y2
y3
The result of this is... okay, not exactly all that simple ;-) I did it in Mathematica, and here are the formulas in pseudocode:
denom = (x1 - x2)(x1 - x3)(x2 - x3)
A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
B = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3)) / denom
C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom
Alternatively, if you wanted to do the matrix math numerically, you'd typically turn to a linear algebra system (like ATLAS, though I'm not sure if it has C#/C++ bindings).
In any case, once you have the values of A, B, and C as calculated by these formulas, you just have to plug them into the expressions given in the question, -B / 2A and C - B^2/4A, to calculate the coordinates of the vertex.1
Note that if the original three points have coordinates that make denom a very large or very small number, doing the calculation directly might be susceptible to significant numerical error. In that case it might be better to modify it a bit, to avoid dividing by the denominators where they would cancel out anyway:
denom = (x1 - x2)(x1 - x3)(x2 - x3)
a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2))
b = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3))
c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3)
and then the coordinates of the vertex are -b / 2a and (c - b^2 / 4a) / denom. There are various other situations that might benefit from "tricks" like this, such as if A is very large or very small, or if C is nearly equal to B^2 / 4A so that their difference is very small, but I think those situations vary enough that a full discussion would be better left for case-by-case followup questions.
Converting all of this to code in the language of your choice is left as an exercise for the reader. (It should be pretty trivial in any language that uses standard infix operator syntax, e.g. as AZDean showed in C#.)
1In the initial version of the answer I thought this would be obvious, but it seems there are a lot of people who like having it mentioned explicitly.
You get the following three equations by direct substitution:
A*x1^2+B*x1+C=y1
A*x2^2+B*x2+C=y2
A*x3^2+B*x3+C=y3
You can solve this by noting that this is equivalent to the matrix product:
[x1^2 x1 1] [A] [y1]
|x2^2 x2 1|*|B| = |y2|
[x3^2 x3 1] [C] [y3]
So you can get A,B, and C by inverting the matrix and multiplying the inverse with the vector on the right.
I see that while I've been posting this John Rasch has linked to tutorial that goes into more depth on actually solving the matrix equation, so you can follow those instructions to get the answer. Inverting a 3x3 matrix is quite easy, so this shouldn't be too tough.
Here is a code in Fortran that implements #david-z and #AZDean's solution:
subroutine parabola_vertex(x1, y1, x2, y2, x3, y3, xv, yv)
real(dp), intent(in) :: x1, y1, x2, y2, x3, y3
real(dp), intent(out) :: xv, yv
real(dp) :: denom, A, B, C
denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
B = (x3**2 * (y1 - y2) + x2**2 * (y3 - y1) + x1**2 * (y2 - y3)) / denom
C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + &
x1 * x2 * (x1 - x2) * y3) / denom
xv = -B / (2*A)
yv = C - B**2 / (4*A)
end subroutine
I've done something similar to #piSHOCK's answer, also based on #AZDean's code. If you need to run it heavily (or to use it in Matlab like me), this might be the fastest one.
My assumption is that x1 == -1, x2 == 0, x3 == 1.
a = y2 - ( y1 + y3) / 2 % opposite signal compared to the original definition of A
b = (y3 - y1) / 4 % half of the originally defined B
xExtr = b / a
yExtr = y2 + b * xExtr % which is equal to y2 + b*b / a
def vertex(x1,x2,x3,y1,y2,y3):
'''Given three pairs of (x,y) points return the vertex of the
parabola passing through the points. Vectorized and common expression reduced.'''
#Define a sequence of sub expressions to reduce redundant flops
x0 = 1/x2
x4 = x1 - x2
x5 = 1/x4
x6 = x1**2
x7 = 1/x6
x8 = x2**2
x9 = -x7*x8 + 1
x10 = x0*x1*x5*x9
x11 = 1/x1
x12 = x3**2
x13 = x11*x12
x14 = 1/(x0*x13 - x0*x3 - x11*x3 + 1)
x15 = x14*y3
x16 = x10*x15
x17 = x0*x5
x18 = -x13 + x3
x19 = y2*(x1*x17 + x14*x18*x6*x9/(x4**2*x8))
x20 = x2*x5
x21 = x11*x20
x22 = x14*(-x12*x7 + x18*x21)
x23 = y1*(-x10*x22 - x21)
x24 = x16/2 - x19/2 - x23/2
x25 = -x17*x9 + x7
x26 = x0*x1*x14*x18*x5
x27 = 1/(-x15*x25 + y1*(x20*x7 - x22*x25 + x7) + y2*(-x17 + x25*x26))
x28 = x24*x27
return x28,x15 + x22*y1 + x24**2*x27 - x26*y2 + x28*(-x16 + x19 + x23)
This smells like homework. "Ask a Scientist" is right on. Say your 3 points are (x1, y1), (x2, y2), and (x3, y3). Then, you get three linear equations:
| M11 M12 M13 | | A | | Z1 |
| M21 M22 M23 | * | B | = | Z2 |
| M31 M32 M33 | | C | | Z3 |
Where M11 = x12, M12 = x1, M13 = 1, Z1 = y1, and similarly for the other two rows using (x2, y2) and (x3, y3) in place of (x1, y1).
Solving this system of 3 equations will give you a solution for A, B, and C.
Running at https://ideone.com/y0SxKU
#include <iostream>
using namespace std;
// calculate the vertex of a parabola given three points
// https://stackoverflow.com/q/717762/16582
// #AZDean implementation with given x values
void CalcParabolaVertex(int x1, int y1, int x2, int y2, int x3, int y3, double& xv, double& yv)
{
double denom = (x1 - x2) * (x1 - x3) * (x2 - x3);
double A = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom;
double B = (x3*x3 * (y1 - y2) + x2*x2 * (y3 - y1) + x1*x1 * (y2 - y3)) / denom;
double C = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom;
xv = -B / (2*A);
yv = C - B*B / (4*A);
}
// #piSHOCK immplementation assuming regular x values ( wrong!!! )
void CalcParabolaVertex2( int y1, int y2, int y3, double& xv, double& yv)
{
double d1 = y1 - y2;
double d2 = y1 - y3;
double a = -d1 + 0.5 * d2;
double b = 2 * d1 - 0.5 * d2;
double c = -y1;
xv = -0.5 * b / a;
yv = c - 0.25 * b * b / a;
}
// corrected immplementation assuming regular x values
void CalcParabolaVertex3( int y1, int y2, int y3, double& xv, double& yv)
{
double d1 = y1 - y2;
double d2 = y1 - y3;
double a = d1 - 0.5 * d2;
double b = -2 * d1 + 0.5 * d2;
double c = y1;
xv = -0.5 * b / a;
yv = c - 0.25 * b * b / a;
}
int main() {
double xv, yv;
CalcParabolaVertex( 0, 100, 1, 500, 2, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
CalcParabolaVertex2( 100, 500, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
CalcParabolaVertex3( 100, 500, 200, xv, yv );
cout << xv <<" "<< yv << "\n";
return 0;
}
I have added a couple of unit tests for negative going peaks: running live at https://ideone.com/WGK90S