Non-axis aligned scaling - scaling

Finding a good way to do this has stumped me for a while now: assume I have a selection box with a set of points in it. By dragging the corners you can scale the (distance between) points in the box. Now for an axis aligned box this is easy. Take a corner as an anchor point (subtract this corner from each point, scale it, then add it to the point again) and multiply each points x and y by the factor with which the box has gotten bigger.
But now take a box that is not aligned with the x and y axis. How do you scale the points inside this box when you drag its corners?

Any box is contained inside a circle.
You find the circle which binds the box, find its center and do exactly the same as you do with an axis aligned box.

You pick one corner of the rectangle as the origin. The two edges connected to it will be the basis (u and v, which should be perpendicular to each other). You would need to normalize them first.
Subtract the origin from the coordinates and calculate the dot-product with the scaling vector (u), and with the other vector (v). This would give you how much u and v contributes to the coordinate.
Then you scale the component you want. To get the final coordinate, you just multiply the the (now scaled) components with their respective vector, and add them together.
For example:
Points: p1 = (3,5) and p2 = (6,4)
Selection corners: (0,2),(8,0),(9,4),(1,6)
selected origin = (8,0)
u = ((0,2)-(8,0))/|(0,2)-(8,0)| = <-0.970, 0.242>
v = <-0.242, -0.970>
(v is u, but with flipped coordinates, and one of them negated)
p1´ = p1 - origin = (-5, 5)
p2´ = p2 - origin = (-2, 4)
p1_u = p1´ . u = -0.970 * (-5) + 0.242 * 5 = 6.063
p1_v = p1´ . v = -0.242 * (-5) - 0.970 * 5 = -3.638
Scale p1_u by 0.5: 3.038
p1_u * u + p1_v * v + origin = <5.941, 4.265>
Same for p2: <7.412, 3.647>
As you maybe can see, they have moved towards the line (8,0)-(9,4), since we scaled by 0.5, with (0,8) as the origin.
Edit: This turned out to be a little harder to explain than I anticipated.
In python code, it could look something like this:
def scale(points, origin, u, scale):
# normalize
len_u = (u[0]**2 + u[1]**2) ** 0.5
u = (u[0]/len_u, u[1]/len_u)
# create v
v = (-u[1],u[0])
ret = []
for x,y in points:
# subtract origin
x, y = x - origin[0], y - origin[1]
# calculate dot product
pu = x * u[0] + y * u[1]
pv = x * v[0] + y * v[1]
# scale
pu = pu * scale
# transform back to normal space
x = pu * u[0] + pv * v[0] + origin[0]
y = pu * u[1] + pv * v[1] + origin[1]
ret.append((x,y))
return ret
>>> scale([(3,5),(6,4)],(8,0),(-8,2),0.5)
[(5.9411764705882355, 4.2647058823529411), (7.4117647058823533, 3.6470588235294117)]

Let's say that the box is defined as a set of four points (P1, P2, P3 and P4).
For the sake of simplicity, we'll say you are dragging P1, and that P3 is the opposite corner (the one you are using as an anchor).
Let's label the mouse position as M, and the new points you wish to calculate as N1, N2 and N4. P3 will, of course, remain the same.
Your scaling factor can be simply computed using vector subtraction and the vector dot product:
scale = ((M - P3) dot (P1 - P3)) / ((P1 - P3) dot (P1 - P3))
And the three new points can be found using scalar multiplication and vector addition:
N1 = scale*P1 + (1 - scale)*P3
N2 = scale*P2 + (1 - scale)*P3
N4 = scale*P4 + (1 - scale)*P3
edit: I see that MizardX has answered the question already, so my answer is here to help with that difficult explanation. I hope it helps!
edit: here is the algorithm for non-proportional scaling. In this case, N1 is equal to M (the point being dragged follows the mouse), so the only points of interest are N2 and N4:
N2 = ((M - P3) dot (P2 - P3)) / ((P2 - P3) dot (P2 - P3)) * (P2 - P3) + P3
N4 = ((M - P3) dot (P4 - P3)) / ((P4 - P3) dot (P4 - P3)) * (P4 - P3) + P3
where * represents scalar multiplication
edit: Here is some C++ code which answers the question. I'm sure this question is long-dead by now, but it was an interesting problem, and I had some fun writing the code.
#include <vector>
class Point
{
public:
float x;
float y;
Point() { x = y = 0; }
Point(float nx, float ny) { x = nx; y = ny; }
};
Point& operator-(Point& A, Point& B) { return Point(A.x-B.x, A.y-B.y); }
Point& operator+(Point& A, Point& B) { return Point(A.x+B.x, A.y+B.y); }
Point& operator*(float sc, Point& P) { return Point(sc*P.x, sc*P.y); }
float dot_product(Point A, Point B) { return A.x*B.x + A.y*B.y; }
struct Rect { Point point[4]; };
void scale_points(Rect box, int anchor, Point mouse, vector<Point> points)
{
Point& P3 = box.point[anchor];
Point& P2 = box.point[(anchor + 1)%4];
Point& P1 = box.point[(anchor + 2)%4];
Point& P4 = box.point[(anchor + 3)%4];
Point A = P4 - P3;
Point aFactor = dot_product(mouse - P3, A) / dot_product(A, A) * A;
Point B = P2 - P3;
Point bFactor = dot_product(mouse - P3, B) / dot_product(B, B) * B;
for (int i = 0; i < points.size(); i++)
{
Point P = points[i] - P3;
points[i] = P3 + dot_product(P, aFactor) + dot_product(P, bFactor);
}
}

Related

How to calculate the intersection point between an infinite line and a line segment?

Basically, a function that fulfills this signature:
function getLineIntersection(vec2 p0, vec2 direction, vec2 p2, vec2 p3) {
// return a vec2
}
I have looked around at existing solutions, and they all seem to deal with how to find the intersection between two line segments, or between two infinite lines. Is there a solution for this problem where the line has an initial position, an angle, and needs to determine if it intersects with a line segment? Basically, something like this:
There should be one line segment that starts in a location and has a unit direction, and another line segment that is just a line connected by two points. Is this possible, and if so, is there a good way of calculating the intersection point, if it exists?
If you've a endless line which is defined by a point P and a normalized direction R and a second endless line, which is defined by a point Q and a direction S, then the intersection point of the endless lines X is:
alpha ... angle between Q-P and R
beta ... angle between R and S
gamma = 180° - alpha - beta
h = | Q - P | * sin(alpha)
u = h / sin(beta)
t = | Q - P | * sin(gamma) / sin(beta)
t = dot(Q-P, (S.y, -S.x)) / dot(R, (S.y, -S.x)) = determinant(mat2(Q-P, S)) / determinant(mat2(R, S))
u = dot(Q-P, (R.y, -R.x)) / dot(R, (S.y, -S.x)) = determinant(mat2(Q-P, R)) / determinant(mat2(R, S))
X = P + R * t = Q + S * u
If you want to detect if the intersection is on the lien, you need to compare the distance of the intersection point with the length of the line.
The intersection point (X) is on the line segment if t is in [0.0, 1.0] for X = p2 + (p3 - p2) * t
vec2 getLineIntersection(vec2 p0, vec2 direction, vec2 p2, vec2 p3)
{
vec2 P = p2;
vec2 R = p3 - p2;
vec2 Q = p0;
vec2 S = direction;
vec2 N = vec2(S.y, -S.x);
float t = dot(Q-P, N) / dot(R, N);
if (t >= 0.0 && t <= 1.0)
return P + R * t;
return vec2(-1.0);
}
Start with the intersection of two infinite lines, expressed in parametric form (e.g., A + tp, where A is the "start point", p is the direction vector and t is a scalar parameter). Solve a system of two equations to get the two parameters of the intersection point.
Now if one of your lines is really a segment AB, and B = A + p (i.e., the direction vector goes from A to B), then if the parameter t is between 0 and 1, the intersection lies on the segment.

Spline interpolation of animated scale

I am trying to understand how Adobe After Effects performs some of interpolations when animating different transformation properties. Properties like position are straightforward. It uses cubic bezier with start,end positions and 2 control tangents. But now I try to implement interpolation of scale property the same way Adobe does. I have two key frames, each contains same values for scale in 2 dimensions. Key1 (100,100) , Key2 (100,100) (in %). Basically,it could be just linear interpolation, but After Effects allows plotting a curve to allow easing even between two similar values. Here is how the curve looks for the points above:
Now,the data from this spline is as follows:
vec2 P0 = vec2(100.0f,100.0f); //start points
vec2 P1 = vec2(100.0f,100.0f); //end point
vec2 T1 = vec2(0.06f,40.12f); //first tangent
vec3 T2 = vec2(0.95f,40.84f); //second tangent
It is clear to me that X value of the tangent contains time in range [0-1],while Y value contains the value of the scale,which is percentage from the default value,which is 100%. I tried to match different types of spline in order to get same interpolated values across 50 frames as in After Effects. I tried:
Catmull- Rom
template <typename T>
T catmullrom(float t, T p0, T p1, T p2, T p3)
{
return (
(p1 * 2.0f) +
(-p0 + p2) * t +
(p0 * 2.0f - p1 * 5.0f + p2 * 4.0f - p3) * t * t +
(-p0 + p1 * 3.0f - p2 * 3.0f + p3) * t * t * t
) * 0.5f;
}
Hermite
template <typename T>
T hermite(float s, T P1, T P2, T T1, T T2)
{
float h1 = 2 * glm::pow(s, 3) - 3 * glm::pow(s, 2) + 1;
float h2 = -2 * glm::pow(s, 3) + 3 * glm::pow(s, 2);
float h3 = glm::pow(s, 3) - 2 * glm::pow(s, 2) + s;
float h4 = glm::pow(s, 3) - glm::pow(s, 2);
T p = P1 * h1 +
P2 * h2 +
T1 * h3 +
T2 * h4;
return p;
}
And even regular Cubic bezier.
template <typename T>
T cubic(float t, T p0, T p1, T p2, T p3)
{
T p = glm::pow3(1.0f - t) * p0 + 3 * glm::pow2(1.0f - t) * t * (p0 + p2) + 3 * (1.0f - t)*glm::pow2(t)*(p1 + p3) + glm::pow3(t) * p1;
return p;
}
int steps = 50;
for (int t = 0; t <= steps; t++)
{
float s = (float)t / (float)steps; // scale s to go from 0 to 1
auto p = cubic(s, P1, P2, T2, T1);
printf("t:%f | %f , %f\n",s,p.x,p.y);
}
I started with Catmull-Rom and Hermite because those define control points on curve,but the results were completely wrong. Then I thought that these two are actually least fit because the tangents in this case have nothing to do with control points on curve. Cubic bezier actually gives correct interpolation in terms of value magnitude change over time. It gave me values starting from 100,with the peak at 130 (as can be seen on the graph),then descending to 100 towards t = 1. But the interpolation steps are completely wrong. Also,only Y value gets interpolated close to the values I expect. I am not a math expert.I was reading about different spline interpolations, but most of the material is completely theoretical. I would like to understand what I am doing wrong here.

Computing the 3D coordinates on a unit sphere from a 2D point

I have a square bitmap of a circle and I want to compute the normals of all the pixels in that circle as if it were a sphere of radius 1:
The sphere/circle is centered in the bitmap.
What is the equation for this?
Don't know much about how people program 3D stuff, so I'll just give the pure math and hope it's useful.
Sphere of radius 1, centered on origin, is the set of points satisfying:
x2 + y2 + z2 = 1
We want the 3D coordinates of a point on the sphere where x and y are known. So, just solve for z:
z = ±sqrt(1 - x2 - y2).
Now, let us consider a unit vector pointing outward from the sphere. It's a unit sphere, so we can just use the vector from the origin to (x, y, z), which is, of course, <x, y, z>.
Now we want the equation of a plane tangent to the sphere at (x, y, z), but this will be using its own x, y, and z variables, so instead I'll make it tangent to the sphere at (x0, y0, z0). This is simply:
x0x + y0y + z0z = 1
Hope this helps.
(OP):
you mean something like:
const int R = 31, SZ = power_of_two(R*2);
std::vector<vec4_t> p;
for(int y=0; y<SZ; y++) {
for(int x=0; x<SZ; x++) {
const float rx = (float)(x-R)/R, ry = (float)(y-R)/R;
if(rx*rx+ry*ry > 1) { // outside sphere
p.push_back(vec4_t(0,0,0,0));
} else {
vec3_t normal(rx,sqrt(1.-rx*rx-ry*ry),ry);
p.push_back(vec4_t(normal,1));
}
}
}
It does make a nice spherical shading-like shading if I treat the normals as colours and blit it; is it right?
(TZ)
Sorry, I'm not familiar with those aspects of C++. Haven't used the language very much, nor recently.
This formula is often used for "fake-envmapping" effect.
double x = 2.0 * pixel_x / bitmap_size - 1.0;
double y = 2.0 * pixel_y / bitmap_size - 1.0;
double r2 = x*x + y*y;
if (r2 < 1)
{
// Inside the circle
double z = sqrt(1 - r2);
.. here the normal is (x, y, z) ...
}
Obviously you're limited to assuming all the points are on one half of the sphere or similar, because of the missing dimension. Past that, it's pretty simple.
The middle of the circle has a normal facing precisely in or out, perpendicular to the plane the circle is drawn on.
Each point on the edge of the circle is facing away from the middle, and thus you can calculate the normal for that.
For any point between the middle and the edge, you use the distance from the middle, and some simple trig (which eludes me at the moment). A lerp is roughly accurate at some points, but not quite what you need, since it's a curve. Simple curve though, and you know the beginning and end values, so figuring them out should only take a simple equation.
I think I get what you're trying to do: generate a grid of depth data for an image. Sort of like ray-tracing a sphere.
In that case, you want a Ray-Sphere Intersection test:
http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter1.htm
Your rays will be simple perpendicular rays, based off your U/V coordinates (times two, since your sphere has a diameter of 2). This will give you the front-facing points on the sphere.
From there, calculate normals as below (point - origin, the radius is already 1 unit).
Ripped off from the link above:
You have to combine two equations:
Ray: R(t) = R0 + t * Rd , t > 0 with R0 = [X0, Y0, Z0] and Rd = [Xd, Yd, Zd]
Sphere: S = the set of points[xs, ys, zs], where (xs - xc)2 + (ys - yc)2 + (zs - zc)2 = Sr2
To do this, calculate your ray (x * pixel / width, y * pixel / width, z: 1), then:
A = Xd^2 + Yd^2 + Zd^2
B = 2 * (Xd * (X0 - Xc) + Yd * (Y0 - Yc) + Zd * (Z0 - Zc))
C = (X0 - Xc)^2 + (Y0 - Yc)^2 + (Z0 - Zc)^2 - Sr^2
Plug into quadratic equation:
t0, t1 = (- B + (B^2 - 4*C)^1/2) / 2
Check discriminant (B^2 - 4*C), and if real root, the intersection is:
Ri = [xi, yi, zi] = [x0 + xd * ti , y0 + yd * ti, z0 + zd * ti]
And the surface normal is:
SN = [(xi - xc)/Sr, (yi - yc)/Sr, (zi - zc)/Sr]
Boiling it all down:
So, since we're talking unit values, and rays that point straight at Z (no x or y component), we can boil down these equations greatly:
Ray:
X0 = 2 * pixelX / width
Y0 = 2 * pixelY / height
Z0 = 0
Xd = 0
Yd = 0
Zd = 1
Sphere:
Xc = 1
Yc = 1
Zc = 1
Factors:
A = 1 (unit ray)
B
= 2 * (0 + 0 + (0 - 1))
= -2 (no x/y component)
C
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2 + (0 - 1) ^ 2 - 1
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2
Discriminant
= (-2) ^ 2 - 4 * 1 * C
= 4 - 4 * C
From here:
If discriminant < 0:
Z = ?, Normal = ?
Else:
t = (2 + (discriminant) ^ 1 / 2) / 2
If t < 0 (hopefully never or always the case)
t = -t
Then:
Z: t
Nx: Xi - 1
Ny: Yi - 1
Nz: t - 1
Boiled farther still:
Intuitively it looks like C (X^2 + Y^2) and the square-root are the most prominent figures here. If I had a better recollection of my math (in particular, transformations on exponents of sums), then I'd bet I could derive this down to what Tom Zych gave you. Since I can't, I'll just leave it as above.

Line/Plane intersection based on points

I have two points in space, L1 and L2 that defines two points on a line.
I have three points in space, P1, P2 and P3 that 3 points on a plane.
So given these inputs, at what point does the line intersect the plane?
Fx. the plane equation A*x+B*y+C*z+D=0 is:
A = p1.Y * (p2.Z - p3.Z) + p2.Y * (p3.Z - p1.Z) + p3.Y * (p1.Z - p2.Z)
B = p1.Z * (p2.X - p3.X) + p2.Z * (p3.X - p1.X) + p3.Z * (p1.X - p2.X)
C = p1.X * (p2.Y - p3.Y) + p2.X * (p3.Y - p1.Y) + p3.X * (p1.Y - p2.Y)
D = -(p1.X * (p2.Y * p3.Z - p3.Y * p2.Z) + p2.X * (p3.Y * p1.Z - p1.Y * p3.Z) + p3.X * (p1.Y * p2.Z - p2.Y * p1.Z))
But what about the rest?
The simplest (and very generalizable) way to solve this is to say that
L1 + x*(L2 - L1) = (P1 + y*(P2 - P1)) + (P1 + z*(P3 - P1))
which gives you 3 equations in 3 variables. Solve for x, y and z, and then substitute back into either of the original equations to get your answer. This can be generalized to do complex things like find the point that is the intersection of two planes in 4 dimensions.
For an alternate approach, the cross product N of (P2-P1) and (P3-P1) is a vector that is at right angles to the plane. This means that the plane can be defined as the set of points P such that the dot product of P and N is the dot product of P1 and N. Solving for x such that (L1 + x*(L2 - L1)) dot N is this constant gives you one equation in one variable that is easy to solve. If you're going to be intersecting a lot of lines with this plane, this approach is definitely worthwhile.
Written out explicitly this gives:
N = cross(P2-P1, P3 - P1)
Answer = L1 + (dot(N, P1 - L1) / dot(N, L2 - L1)) * (L2 - L1)
where
cross([x, y, z], [u, v, w]) = x*u + y*w + z*u - x*w - y*u - z*v
dot([x, y, z], [u, v, w]) = x*u + y*v + z*w
Note that that cross product trick only works in 3 dimensions, and only for your specific problem of a plane and a line.
This is how I ended up doing it in come code. Luckily one code library (XNA) had half of what I needed, and the rest was easy.
var lv = L2-L1;
var ray = new Microsoft.Xna.Framework.Ray(L1,lv);
var plane = new Microsoft.Xna.Framework.Plane(P1, P2, P3);
var t = ray.Intersects(plane); //Distance along line from L1
///Result:
var x = L1.X + t * lv.X;
var y = L1.Y + t * lv.Y;
var z = L1.Z + t * lv.Z;
Of course I would prefer having just the simple equations that takes place under the covers of XNA.

Perpendicular on a line from a given point

How can I draw a perpendicular on a line segment from a given point? My line segment is defined as (x1, y1), (x2, y2), If I draw a perpendicular from a point (x3,y3) and it meets to line on point (x4,y4). I want to find out this (x4,y4).
I solved the equations for you:
k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) / ((y2-y1)^2 + (x2-x1)^2)
x4 = x3 - k * (y2-y1)
y4 = y3 + k * (x2-x1)
Where ^2 means squared
From wiki:
In algebra, for any linear equation
y=mx + b, the perpendiculars will all
have a slope of (-1/m), the opposite
reciprocal of the original slope. It
is helpful to memorize the slogan "to
find the slope of the perpendicular
line, flip the fraction and change the
sign." Recall that any whole number a
is itself over one, and can be written
as (a/1)
To find the perpendicular of a given
line which also passes through a
particular point (x, y), solve the
equation y = (-1/m)x + b, substituting
in the known values of m, x, and y to
solve for b.
The slope of the line, m, through (x1, y1) and (x2, y2) is m = (y1 - y2) / (x1 - x2)
I agree with peter.murray.rust, vectors make the solution clearer:
// first convert line to normalized unit vector
double dx = x2 - x1;
double dy = y2 - y1;
double mag = sqrt(dx*dx + dy*dy);
dx /= mag;
dy /= mag;
// translate the point and get the dot product
double lambda = (dx * (x3 - x1)) + (dy * (y3 - y1));
x4 = (dx * lambda) + x1;
y4 = (dy * lambda) + y1;
You know both the point and the slope, so the equation for the new line is:
y-y3=m*(x-x3)
Since the line is perpendicular, the slope is the negative reciprocal. You now have two equations and can solve for their intersection.
y-y3=-(1/m)*(x-x3)
y-y1=m*(x-x1)
You will often find that using vectors makes the solution clearer...
Here is a routine from my own library:
public class Line2 {
Real2 from;
Real2 to;
Vector2 vector;
Vector2 unitVector = null;
public Real2 getNearestPointOnLine(Real2 point) {
unitVector = to.subtract(from).getUnitVector();
Vector2 lp = new Vector2(point.subtract(this.from));
double lambda = unitVector.dotProduct(lp);
Real2 vv = unitVector.multiplyBy(lambda);
return from.plus(vv);
}
}
You will have to implement Real2 (a point) and Vector2 and dotProduct() but these should be simple:
The code then looks something like:
Point2 p1 = new Point2(x1, y1);
Point2 p2 = new Point2(x2, y2);
Point2 p3 = new Point2(x3, y3);
Line2 line = new Line2(p1, p2);
Point2 p4 = getNearestPointOnLine(p3);
The library (org.xmlcml.euclid) is at:
http://sourceforge.net/projects/cml/
and there are unit tests which will exercise this method and show you how to use it.
#Test
public final void testGetNearestPointOnLine() {
Real2 p = l1112.getNearestPointOnLine(new Real2(0., 0.));
Real2Test.assertEquals("point", new Real2(0.4, -0.2), p, 0.0000001);
}
Compute the slope of the line joining points (x1,y1) and (x2,y2) as m=(y2-y1)/(x2-x1)
Equation of the line joining (x1,y1) and (x2,y2) using point-slope form of line equation, would be y-y2 = m(x-x2)
Slope of the line joining (x3,y3) and (x4,y4) would be -(1/m)
Again, equation of the line joining (x3,y3) and (x4,y4) using point-slope form of line equation, would be y-y3 = -(1/m)(x-x3)
Solve these two line equations as you solve a linear equation in two variables and the values of x and y you get would be your (x4,y4)
I hope this helps.
cheers
Find out the slopes for both the
lines, say slopes are m1 and m2 then
m1*m2=-1 is the condition for
perpendicularity.
Matlab function code for the following problem
function Pr=getSpPoint(Line,Point)
% getSpPoint(): find Perpendicular on a line segment from a given point
x1=Line(1,1);
y1=Line(1,2);
x2=Line(2,1);
y2=Line(2,1);
x3=Point(1,1);
y3=Point(1,2);
px = x2-x1;
py = y2-y1;
dAB = px*px + py*py;
u = ((x3 - x1) * px + (y3 - y1) * py) / dAB;
x = x1 + u * px;
y = y1 + u * py;
Pr=[x,y];
end
Mathematica introduced the function RegionNearest[] in version 10, 2014. This function could be used to return an answer to this question:
{x4,y4} = RegionNearest[Line[{{x1,y1},{x2,y2}}],{x3,y3}]
This is mostly a duplicate of Arnkrishn's answer. I just wanted to complete his section with a complete Mathematica code snippet:
m = (y2 - y1)/(x2 - x1)
eqn1 = y - y3 == -(1/m)*(x - x3)
eqn2 = y - y1 == m*(x - x1)
Solve[eqn1 && eqn2, {x, y}]
This is a C# implementation of the accepted answer. It's also using ArcGis to return a MapPoint as that's what we're using for this project.
private MapPoint GenerateLinePoint(double startPointX, double startPointY, double endPointX, double endPointY, double pointX, double pointY)
{
double k = ((endPointY - startPointY) * (pointX - startPointX) - (endPointX - startPointX) * (pointY - startPointY)) / (Math.Pow(endPointY - startPointY, 2)
+ Math.Pow(endPointX - startPointX, 2));
double resultX = pointX - k * (endPointY - startPointY);
double resultY = pointY + k * (endPointX - startPointX);
return new MapPoint(resultX, resultY, 0, SpatialReferences.Wgs84);
}
Thanks to Ray as this worked perfectly for me.
c#arcgis
Just for the sake of completeness, here is a solution using homogeneous coordinates.
The homogeneous points are:
p1 = (x1,y1,1), p2 = (x2,y2,1), p3 = (x3,y3,1)
a line through two points is their cross-product
l_12 := p1 x p2 = (y1-y2, x2-x1, x1*y2 - x2*y1)
The (signed) distance of a point to a line is their dot product.
d := l_12 * p3 = x3*(y1-y2) + y3*(x2-x1) + x1*y2 - x2*y1
The vector from p4 to p3 is d times the normal vector of l_12 divided by the squared length of the normal vector.
n2 := (y1-y2)^2 + (x2-x1)^2
p4 := p3 + d/n2*(y1-y2, x2-x1, 0)
Note: if you divide l_12 by the length of the normal vector
l_12 := l_12 / sqrt((y1-y2)^2 + (x2-x1)^2)
the distance d will be the euclidean distance.
First, calculate the linear function determined by the points
(x1,y2),(x2,y2).
We get:
y1 = mx+b1 where m and b1 are constants.
This step is easy to calculate by the formula of linear function between two points.
Then, calculate the linear function y that goes through (x3,y3).
The function slope is -m, where m is the slope of y1.
Then calculate the const b2 by the coordinates of the point (x3,y3).
We get y2 = -mx+b2 where m and b2 are constants.
The last thing to do is to find the intersection of y1, y2.
You can find x by solving the equation: -mx+b2 = mx+b1, then place x in one of the equations to find y.
This is a vectorized Matlab function for finding pairwise projections of m points onto n line segments. Here xp and yp are m by 1 vectors holding coordinates of m different points, and x1, y1, x2 and y2 are n by 1 vectors holding coordinates of start and end points of n different line segments.
It returns m by n matrices, x and y, where x(i, j) and y(i, j) are coordinates of projection of i-th point onto j-th line.
The actual work is done in first few lines and the rest of the function runs a self-test demo, just in case where it is called with no parameters. It's relatively fast, I managed to find projections of 2k points onto 2k line segments in less than 0.05s.
function [x, y] = projectPointLine(xp, yp, x1, y1, x2, y2)
if nargin > 0
xd = (x2-x1)';
yd = (y2-y1)';
dAB = xd.*xd + yd.*yd;
u = bsxfun(#rdivide, bsxfun(#times, bsxfun(#minus, xp, x1'), xd) + ...
bsxfun(#times, bsxfun(#minus, yp, y1'), yd), dAB);
x = bsxfun(#plus, x1', bsxfun(#times, u, xd));
y = bsxfun(#plus, y1', bsxfun(#times, u, yd));
else
nLine = 3;
nPoint = 2;
xp = rand(nPoint, 1) * 2 -1;
yp = rand(nPoint, 1) * 2 -1;
x1 = rand(nLine, 1) * 2 -1;
y1 = rand(nLine, 1) * 2 -1;
x2 = rand(nLine, 1) * 2 -1;
y2 = rand(nLine, 1) * 2 -1;
tic;
[x, y] = projectPointLine(xp, yp, x1, y1, x2, y2);
toc
close all;
plot([x1'; x2'], [y1'; y2'], '.-', 'linewidth', 2, 'markersize', 20);
axis equal;
hold on
C = lines(nPoint + nLine);
for i=1:nPoint
scatter(x(i, :), y(i, :), 100, C(i+nLine, :), 'x', 'linewidth', 2);
scatter(xp(i), yp(i), 100, C(i+nLine, :), 'x', 'linewidth', 2);
end
for i=1:nLine
scatter(x(:, i)', y(:, i)', 100, C(i, :), 'o', 'linewidth', 2);
end
end
end

Resources