I ran into another problem while making template matching in Halide (original link with resolved issue: output shifted in template matching)
Now I'm trying to draw a rectangle at the position with the lowest score (which indicates the best match).
template matching part:
Image<float> source = load_image("C:\\Users\\Admin\\Desktop\\templateMatchingOpenCV\\clip\\clip2.png");
Image<float> templ = load_image("C:\\Users\\Admin\\Desktop\\templateMatchingOpenCV\\clip\\object3.png");
Var x, y, xt, yt, x_outer, y_outer, x_inner, y_inner, tile_index;
RDom r(0, templ.width(), 0, templ.height());
Func limit, comparesqdiff, comparesqdiffnorm, compareccorr;
limit = BoundaryConditions::constant_exterior(source, 1.0f);
Expr function = sum(Halide::pow(templ(r.x, r.y) - limit(x + r.x, y + r.y), 2)) / (templ.width()*templ.height());
comparesqdiff(x, y) = sum(Halide::pow(templ(r.x, r.y) - limit(x + r.x, y + r.y), 2)) / (templ.width()*templ.height());
Image<float> outputsqdiff;
comparesqdiff.tile(x, y, x_outer, y_outer, x_inner, y_inner, 64,64).fuse(x_outer, y_outer, tile_index).vectorize(x_inner).unroll(y_inner).parallel(tile_index);
comparesqdiff.compile_jit();
Now it's clear to me that the function I should be using to find the position of the lowest score is argmin but i don't quite understand how it's used. Also I am aware that the drawing method would cover everything from that's below and right of the pixel but I didn't get to that part yet.
Drawing the rectangle part:
RDom wholeImage(0, source.width() - templ.width(), 0, source.height() - templ.height());
Tuple coords = argmin(r, function, "argmin");
Func show;
show(x, y) = select(x >= coords[0] && y >= coords[1] && x <= coords[0] + templ.width() && y <= coords[1] + templ.height(), 0, limit(x, y));
Image<float> test(source.width(), source.height());
test = show.realize(source.width(), source.height());
Thank you in advance.
The argmin reduction iterates over your RDom, compares the values and keeps the location of the lowest value and the value.
Halide::RDom matchDom
( 0, templ.width ()
, 0, templ.height()
);
Halide::RDom searchDom
( 0, source.width () - templ.width ()
, 0, source.height() - templ.height()
);
Halide::Expr score = Halide::sum
( matchDom
, Halide::pow
( templ( matchDom.x, matchDom.y )
- limit( searchDom.x + matchDom.x
, searchDom.y + matchDom.y)
, 2
)
)
/ ( templ.width() * templ.height() );
Halide::Tuple searchBest = Halide::argmin( searchDom, score );
Halide::Func best;
best(_) = searchBest(_);
Then you can call best.realize() to get a Halide::Realization. That realization will contain 3 buffers, each of a single value: the x coordinate of the lowest value, the y coordinate of the lowest value and the lowest value.
Halide is not the best tool for drawing geometric shapes. For my money, it would just be easier to write pixels into the image with a for loop.
ASKER'S EDIT: added marking the best result which uses the answer's method for finding best score
Realization re = best.realize();
Func draw("draw");
Image<int> x_coordinate(re[0]);
Image<int> y_coordinate(re[1]);
Image<float> s(re[2]);
draw(x,y) = select(x == x_coordinate(0, 0) || y == y_coordinate(0,0) || x == (x_coordinate(0,0) + templ.width()) || y == (y_coordinate(0,0) + templ.height()), 0.0f, source(x,y));
Image<float> drawTest;
drawTest = draw.realize(source.width(), source.height());
save_image(drawTest, path);
Example of the drawing:
Source:
Template:
Result:
ASKER'S EDIT2:
made it so it draws only the rectangle around the match
draw(x, y) = select((x == x_coordinate(0, 0) && (y >= y_coordinate(0, 0) && y <= y_coordinate(0, 0) + templ.height())) ||
((x == x_coordinate(0, 0) + templ.width()) && (y >= y_coordinate(0, 0) && y <= y_coordinate(0, 0) + templ.height())) ||
(y == y_coordinate(0, 0) && (x >= x_coordinate(0, 0) && x <= x_coordinate(0, 0) + templ.width())) ||
((y == y_coordinate(0, 0) + templ.height()) && (x >= x_coordinate(0, 0) && x <= x_coordinate(0, 0) + templ.width())), 0.0f, limit(x, y));
draw.tile(x, y, x_outer, y_outer, x_inner, y_inner, 64, 64).fuse(x_outer, y_outer, tile_index).vectorize(x_inner).unroll(y_inner).parallel(tile_index);
Result:
Related
clear
I = imread('256.jpg');
%imshow(I);
center = 128;
[x, y] = size(I); % declare image size array
Original = [x, y];
Rotated_I = zeros(x,y); %declare size of array to store pixel
theta = 90;
for row = 1:y
for column = 1:x
x_original = (column - 128) * cos(theta) - (row - 128)*sin(theta);
y_original = (column - 128) * sin(theta) + (row - 128)*cos(theta); % reverse rotate
p = floor(x_original);
q = floor(y_original);
a = y_original - p;
b = x_original - q; %
Rotated_I(column, row) = (1-a)*((1-b)*Original(p,q)+b*Original(p,q+1))+a*((q-b)*Original(p+1,q)+b*Original(p+1,q+1)); % Find pixel using bilinear interpolation
end
end
imshow(Rotated_I);
I tried to rotate image using reverse rotate and bilinear interpolation, but only i see is error message. It says "the first index exceeds array". Is there anything wrong in my code?
Here is a working version with a number of changes. The main difference is that it checks whether a coordinate exists in the original image before adding that to the rotate image. This allows for arbitrary rotations, like 45 degrees. Also, images in MATLAB have y as the first dimension and x as the second, so are accessed as I(y, x) or I(row, column).
clear
I = imread('256.jpg');
% imshow(I);
center = 128;
[y, x] = size(I); % in MATLAB, images are y-by-x in size (ie. y is dimension 1)
Original = I; % Original needs to be the image I
Rotated_I = zeros(y, x);
theta = 90;
for row = 1:y
for column = 1:x
x_original = (column - center) * cosd(theta) - (row - center)*sind(theta) + center; % theta is in degrees so use cosd and sind
y_original = (column - center) * sind(theta) + (row - center)*cosd(theta) + center; % also add center back on
p = floor(y_original); % x_original and y_original were swapped here
q = floor(x_original); % x_original and y_original were swapped here
a = y_original - p;
b = x_original - q;
% check if the coordinate is in the original image to prevent errors
if p > 0 && p <= y && q > 0 && q <= x
Rotated_I(row, column) = Rotated_I(row, column) + (1-a)*(1-b)*Original(p,q);
end
if p > 0 && p <= y && q+1 > 0 && q+1 <= x
Rotated_I(row, column) = Rotated_I(row, column) + (1-a)*b*Original(p,q+1);
end
if p+1 > 0 && p+1 <= y && q > 0 && q <= x
Rotated_I(row, column) = Rotated_I(row, column) + a*(1-b)*Original(p+1,q);
end
if p+1 > 0 && p+1 <= y && q+1 > 0 && q+1 <= x
Rotated_I(row, column) = Rotated_I(row, column) + a*b*Original(p+1,q+1);
end
end
end
% convert to uint image so it displays properly (double expects values from 0 to 1)
imshow(uint8(Rotated_I));
I do not know if you necessarily want to have your own implementation or not. But if not, you could always use imrotate:
Rotated_I = imrotate(I, 90, 'bilinear', 'crop');
90 => Degrees of rotation
'bilinear' => Bilinear interpolation (alternatives: nearest, bicubic)
'crop' => Maintain the pixel size of the rotated image the same as the input image
imrotate is part of the Image Processing Toolbox.
I was trying to follow "On the Hardware Implementation of Triangle Traversal Algorithms for Graphics Processing" (Royer, Ituero, Lopez-Vallejo, & Barrio) (page 4) to implement Zig-Zag traversal algorithm for triangle triversal/rasterization. However, the explanation in the paper is counter-intuitive for me and I was not able to make it work.
I tried to implement a finite state machine but I can't quite figure out the exact states. For now, I'm having (direction, e_1, e_2, e_3) where e_n represent edge test output for each edge. Pseudo code:
if (right, true, true, true):
x++; // Move towards right
else if (left, true, true, true):
x--; // Move towards left
else:
// This is where I stuck. There should be two cases where in one of them
// y goes down and x doesn't change direction, in the other case x simply
// flips its direction. But I wasn't able to figure it out.
Any help would be appreciated!
Edit: My effort so far:
While the edge test is working correctly, only few parts of the graph was rasterized.
/// Zig Zag (not working)
int top_row = floor(fmin(y0, fmin(y1, y2)));
int bot_row = floor(fmax(y0, fmax(y1, y2)));
if (y0 > y1) {
swap(x0, x1); swap(y0, y1);
}
if (y0 > y2) {
swap(x0, x2); swap(y0, y2);
}
if (y1 > y2) {
swap(x1, x2); swap(y1, y2);
}
assert(top_row == floor(y0));
assert(bot_row == floor(y2));
bool direction = true;
bool changed = false;
int x = floor(x0); int y = floor(y0);
while (y <= bot_row) {
bool e1, e2, e3;
e1 = edge_test((float)x+0.5, (float)y+0.5, x0, y0, x1, y1) < 0.0f;
e2 = edge_test((float)x+0.5, (float)y+0.5, x1, y1, x2, y2) < 0.0f;
e3 = edge_test((float)x+0.5, (float)y+0.5, x2, y2, x0, y0) < 0.0f;
if ((e1 == e2) && (e2 == e3)) {
if ( x < 0 || x >= width ) continue;
if ( y < 0 || y >= height ) continue;
samplebuffer[y][x].fill_pixel(color);
if (direction) x++;
else x--;
} else if (changed) {
y++;
changed = false;
} else {
direction = !direction;
changed = true;
if (direction) x++;
else x--;
}
}
How I see state machine:
Dir = +1 / -1
State 0: (moving inside)
EdgeTest:
0 => State 1 ; y++
1 => State 0 ; x = x + Dir
State 1: (outside)
EdgeTest:
0 => State 3 ; Dir = - Dir
1 => State 2 ;
State 2: (inside moving the same dir)
EdgeTest:
0 => State 3 ; Dir = - Dir
1 => State 2 ; x= x + Dir
State 3: (outside)
EdgeTest:
0 => State 3 ; x = x + Dir
1 => State 0 ;
I have a rectangular area of dimension: n*m. I also have a smaller rectangle of dimension: x*y. What will be the minimum number of smaller rectangles required to cover all the area of the bigger rectangle?
It's not necessary to pack the smaller rectangles. They are allowed to overlap with each other, cross the borders of the bigger rectangle if required. The only requirement is that we have to use the fewest number of x*y rectangles.
Another thing is that we can rotate the smaller rectangles if required (90 degrees rotation I mean), to minimise the number.
n,m,x and y: all are natural numbers. x, y need not be factors of n,m.
I couldn't solve it in the time given, neither could I figure out an approach. I initiated by taking up different cases of n,m being divisible by x,y or not.
update
sample test cases:
n*m = 3*3, x*y = 2*2. Result should be 4
n*m = 5*6, x*y = 3*2. Result should be 5
n*m = 68*68, x*y = 9*8. Result should be 65
(UPDATE: See newer version below.)
I think (but I have no proof at this time) that irregular tilings can be discarded, and finding the optimal solution means finding the point at which to switch the direction of the tiles.
You start off with a basic grid like this:
and the optimal solution will take one of these two forms:
So for each of these points, you calculate the number of required tiles for both options:
This is a very basic implementation. The "horizontal" and "vertical" values in the results are the number of tiles in the non-rotated zone (indicated in pink in the images).
The algorithm probably checks some things twice, and could use some memoization to speed it up.
(Testing has shown that you need to run the algorithm a second time with the x and y parameter switched, and that checking both types of solution is indeed necessary.)
function rectangleCover(n, m, x, y, rotated) {
var width = Math.ceil(n / x), height = Math.ceil(m / y);
var cover = {num: width * height, rot: !!rotated, h: width, v: height, type: 1};
for (var i = 0; i <= width; i++) {
for (var j = 0; j <= height; j++) {
var rect = i * j;
var top = simpleCover(n, m - y * j, y, x);
var side = simpleCover(n - x * i, y * j, y, x);
var total = rect + side + top;
if (total < cover.num) {
cover = {num: total, rot: !!rotated, h: i, v: j, type: 1};
}
var top = simpleCover(x * i, m - y * j, y, x);
var side = simpleCover(n - x * i, m, y, x);
var total = rect + side + top;
if (total < cover.num) {
cover = {num: total, rot: !!rotated, h: i, v: j, type: 2};
}
}
}
if (!rotated && n != m && x != y) {
var c = rectangleCover(n, m, y, x, true);
if (c.num < cover.num) cover = c;
}
return cover;
function simpleCover(n, m, x, y) {
return (n > 0 && m > 0) ? Math.ceil(n / x) * Math.ceil(m / y) : 0;
}
}
document.write(JSON.stringify(rectangleCover(3, 3, 2, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(5, 6, 3, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(22, 18, 5, 3)) + "<br>");
document.write(JSON.stringify(rectangleCover(1000, 1000, 11, 17)));
This is the counter-example Evgeny Kluev provided: (68, 68, 9, 8) which returns 68 while there is a solution using just 65 rectangles, as demonstrated in this image:
Update: improved algorithm
The counter-example shows the way for a generalisation of the algorithm: work from the 4 corners, try all unique combinations of orientations, and every position of the borders a, b, c and d between the regions; if a rectangle is left uncovered in the middle, try both orientations to cover it:
Below is a simple, unoptimised implementation of this idea; it probably checks some configurations multiple times, and it takes 6.5 seconds for the 11×17/1000×1000 test, but it finds the correct solution for the counter-example and the other tests from the previous version, so the logic seems sound.
These are the five rotations and the numbering of the regions used in the code. If the large rectangle is a square, only the first 3 rotations are checked; if the small rectangles are squares, only the first rotation is checked. X[i] and Y[i] are the size of the rectangles in region i, and w[i] and h[i] are the width and height of region i expressed in number of rectangles.
function rectangleCover(n, m, x, y) {
var X = [[x,x,x,y],[x,x,y,y],[x,y,x,y],[x,y,y,x],[x,y,y,y]];
var Y = [[y,y,y,x],[y,y,x,x],[y,x,y,x],[y,x,x,y],[y,x,x,x]];
var rotations = x == y ? 1 : n == m ? 3 : 5;
var minimum = Math.ceil((n * m) / (x * y));
var cover = simpleCover(n, m, x, y);
for (var r = 0; r < rotations; r++) {
for (var w0 = 0; w0 <= Math.ceil(n / X[r][0]); w0++) {
var w1 = Math.ceil((n - w0 * X[r][0]) / X[r][1]);
if (w1 < 0) w1 = 0;
for (var h0 = 0; h0 <= Math.ceil(m / Y[r][0]); h0++) {
var h3 = Math.ceil((m - h0 * Y[r][0]) / Y[r][3]);
if (h3 < 0) h3 = 0;
for (var w2 = 0; w2 <= Math.ceil(n / X[r][2]); w2++) {
var w3 = Math.ceil((n - w2 * X[r][2]) / X[r][3]);
if (w3 < 0) w3 = 0;
for (var h2 = 0; h2 <= Math.ceil(m / Y[r][2]); h2++) {
var h1 = Math.ceil((m - h2 * Y[r][2]) / Y[r][1]);
if (h1 < 0) h1 = 0;
var total = w0 * h0 + w1 * h1 + w2 * h2 + w3 * h3;
var X4 = w3 * X[r][3] - w0 * X[r][0];
var Y4 = h0 * Y[r][0] - h1 * Y[r][1];
if (X4 * Y4 > 0) {
total += simpleCover(Math.abs(X4), Math.abs(Y4), x, y);
}
if (total == minimum) return minimum;
if (total < cover) cover = total;
}
}
}
}
}
return cover;
function simpleCover(n, m, x, y) {
return Math.min(Math.ceil(n / x) * Math.ceil(m / y),
Math.ceil(n / y) * Math.ceil(m / x));
}
}
document.write("(3, 3, 2, 2) → " + rectangleCover(3, 3, 2, 2) + "<br>");
document.write("(5, 6, 3, 2) → " + rectangleCover(5, 6, 3, 2) + "<br>");
document.write("(22, 18, 5, 3) → " + rectangleCover(22, 18, 5, 3) + "<br>");
document.write("(68, 68, 8, 9) → " + rectangleCover(68, 68, 8, 9) + "<br>");
Update: fixed calculation of center region
As #josch pointed out in the comments, the calculation of the width and height of the center region 4 is not done correctly in the above code; Sometimes its size is overestimated, which results in the total number of rectangles being overestimated. An example where this happens is (1109, 783, 170, 257) which returns 23 while there exists a solution of 22. Below is a new code version with the correct calculation of the size of region 4.
function rectangleCover(n, m, x, y) {
var X = [[x,x,x,y],[x,x,y,y],[x,y,x,y],[x,y,y,x],[x,y,y,y]];
var Y = [[y,y,y,x],[y,y,x,x],[y,x,y,x],[y,x,x,y],[y,x,x,x]];
var rotations = x == y ? 1 : n == m ? 3 : 5;
var minimum = Math.ceil((n * m) / (x * y));
var cover = simpleCover(n, m, x, y);
for (var r = 0; r < rotations; r++) {
for (var w0 = 0; w0 <= Math.ceil(n / X[r][0]); w0++) {
var w1 = Math.ceil((n - w0 * X[r][0]) / X[r][1]);
if (w1 < 0) w1 = 0;
for (var h0 = 0; h0 <= Math.ceil(m / Y[r][0]); h0++) {
var h3 = Math.ceil((m - h0 * Y[r][0]) / Y[r][3]);
if (h3 < 0) h3 = 0;
for (var w2 = 0; w2 <= Math.ceil(n / X[r][2]); w2++) {
var w3 = Math.ceil((n - w2 * X[r][2]) / X[r][3]);
if (w3 < 0) w3 = 0;
for (var h2 = 0; h2 <= Math.ceil(m / Y[r][2]); h2++) {
var h1 = Math.ceil((m - h2 * Y[r][2]) / Y[r][1]);
if (h1 < 0) h1 = 0;
var total = w0 * h0 + w1 * h1 + w2 * h2 + w3 * h3;
var X4 = n - w0 * X[r][0] - w2 * X[r][2];
var Y4 = m - h1 * Y[r][1] - h3 * Y[r][3];
if (X4 > 0 && Y4 > 0) {
total += simpleCover(X4, Y4, x, y);
} else {
X4 = n - w1 * X[r][1] - w3 * X[r][3];
Y4 = m - h0 * Y[r][0] - h2 * Y[r][2];
if (X4 > 0 && Y4 > 0) {
total += simpleCover(X4, Y4, x, y);
}
}
if (total == minimum) return minimum;
if (total < cover) cover = total;
}
}
}
}
}
return cover;
function simpleCover(n, m, x, y) {
return Math.min(Math.ceil(n / x) * Math.ceil(m / y),
Math.ceil(n / y) * Math.ceil(m / x));
}
}
document.write("(3, 3, 2, 2) → " + rectangleCover(3, 3, 2, 2) + "<br>");
document.write("(5, 6, 3, 2) → " + rectangleCover(5, 6, 3, 2) + "<br>");
document.write("(22, 18, 5, 3) → " + rectangleCover(22, 18, 5, 3) + "<br>");
document.write("(68, 68, 9, 8) → " + rectangleCover(68, 68, 9, 8) + "<br>");
document.write("(1109, 783, 170, 257) → " + rectangleCover(1109, 783, 170, 257) + "<br>");
Update: non-optimality and recursion
It is indeed possible to create input for which the algorithm does not find the optimal solution. For the example (218, 196, 7, 15) it returns 408, but there is a solution with 407 rectangles. This solution has a center region sized 22×14, which can be covered by three 7×15 rectangles; however, the simpleCover function only checks options where all rectangles have the same orientation, so it only finds a solution with 4 rectangles for the center region.
This can of course be countered by using the algorithm recursively, and calling rectangleCover again for the center region. To avoid endless recursion, you should limit the recursions depth, and use simpleCover once you've reached a certain recursion level. To avoid the code becoming unusably slow, add memoization of intermediate results (but don't use results that were calculated in a deeper recursion level for a higher recursion level).
When adding one level of recursion and memoization of intermediate results, the algorithm finds the optimal solution of 407 for the example mentioned above, but of course takes a lot more time. Again, I have no proof that adding a certain recursion depth (or even unlimited recursion) will result in the algorithm being optimal.
I'm trying to create a template matching program that's using the following formula to determine the fit between the template and the image:
my code is following:
Halide::Var x, y, xt, yt;
Halide::RDom r(0, t.width(), 0, t.height());
Halide::Func limit, compare;
limit = Halide::BoundaryConditions::constant_exterior(input,255);
compare(x, y) = limit(x,y);
compare(x, y) = Halide::cast<uint8_t>(Halide::pow(t(0 + r.x, 0 + r.y) - limit(x + r.x, y + r.y),2));
Halide::Image<uint8_t> output(input.width(),input.height());
output = compare.realize(input.width(),input.height());
After executing the following code the result image is shifted like in the example:
Original image:
Template:
Result:
How can I prevent the image from shifting?
Not sure what the types of t and input are, so the following might overflow, but I think you want something like:
Halide::Var x, y, xt, yt;
Halide::RDom r(0, t.width(), 0, t.height());
Halide::Func limit, compare;
limit = Halide::BoundaryConditions::constant_exterior(input,255);
compare(x, y) = limit(x,y);
compare(x, y) = Halide::cast<uint8_t>(sum(Halide::pow(t(r.x, r.y) - limit(x + r.x - t.width()/2, y + r.y - t.height()/2),2))/(t.width()*t.height()));
Halide::Image<uint8_t> output(input.width(),input.height());
output = compare.realize(input.width(),input.height());
There is no sum. You are only storing the squared difference of the lower right pixel of the template image. There's some other things too, which I commented on:
Halide::Var x, y, xt, yt;
Halide::RDom r(0, t.width(), 0, t.height());
Halide::Func limit, compare;
// There's no point comparing the template to pixels not in the input.
// If you really wanted to do that, you should start at
// -t.width(), -t.height() and wd, ht will be plus the template size
// instead of minus.
int wd = input.width () - t.width ();
int ht = input.height() - t.height();
// constant_exterior returns a Func.
// We can copy all dimensions with an underscore.
limit(_) = Halide::BoundaryConditions::constant_exterior(input,255)(_) / 255.f;
Func tf;
tf(_) = t(_) / 255.f;
// Not necessary now and even so, should have been set to undef< uint8_t >().
// compare(x, y) = limit(x,y);
// Expr are basically cut and pasted to where they are used.
Expr sq_dif = Halide::pow(tf(r.x, r.x) - limit(x + r.x, y + r.y), 2);
Expr t_count = t.width() * t.height();
Expr val = Halide::sum(sq_dif) / t_count;
compare(x, y) = Halide::cast<uint8_t>(Halide::clamp(255 * val, 0, 255));
// The size of output is set by realize().
Halide::Image<uint8_t> output;
output = compare.realize(wd, ht);
How would I write this function? Any examples appreciated
function isPointBetweenPoints(currPoint, point1, point2):Boolean {
var currX = currPoint.x;
var currY = currPoint.y;
var p1X = point1.x;
var p1y = point1.y;
var p2X = point2.x;
var p2y = point2.y;
//here I'm stuck
}
Assuming that point1 and point2 are different, first you check whether the point lies on the line. For that you simply need a "cross-product" of vectors point1 -> currPoint and point1 -> point2.
dxc = currPoint.x - point1.x;
dyc = currPoint.y - point1.y;
dxl = point2.x - point1.x;
dyl = point2.y - point1.y;
cross = dxc * dyl - dyc * dxl;
Your point lies on the line if and only if cross is equal to zero.
if (cross != 0)
return false;
Now, as you know that the point does lie on the line, it is time to check whether it lies between the original points. This can be easily done by comparing the x coordinates, if the line is "more horizontal than vertical", or y coordinates otherwise
if (abs(dxl) >= abs(dyl))
return dxl > 0 ?
point1.x <= currPoint.x && currPoint.x <= point2.x :
point2.x <= currPoint.x && currPoint.x <= point1.x;
else
return dyl > 0 ?
point1.y <= currPoint.y && currPoint.y <= point2.y :
point2.y <= currPoint.y && currPoint.y <= point1.y;
Note that the above algorithm if entirely integral if the input data is integral, i.e. it requires no floating-point calculations for integer input. Beware of potential overflow when calculating cross though.
P.S. This algorithm is absolutely precise, meaning that it will reject points that lie very close to the line but not precisely on the line. Sometimes this is not what's needed. But that's a different story.
Distance(point1, currPoint)
+ Distance(currPoint, point2)
== Distance(point1, point2)
But be careful if you have floating point values, things are different for them...
When concerned about the computational cost of computing "the square roots", don't:
Just compare "the squares".
This is independent of Javascript. Try the following algorithm, with points p1=point1 and p2=point2, and your third point being p3=currPoint:
v1 = p2 - p1
v2 = p3 - p1
v3 = p3 - p2
if (dot(v2,v1)>0 and dot(v3,v1)<0) return between
else return not between
If you want to be sure it's on the line segment between p1 and p2 as well:
v1 = normalize(p2 - p1)
v2 = normalize(p3 - p1)
v3 = p3 - p2
if (fabs(dot(v2,v1)-1.0)<EPS and dot(v3,v1)<0) return between
else return not between
You want to check whether the slope from point1 to currPoint is the same as the slope from currPoint to point2, so:
m1 = (currY - p1Y) / (currX - p1X);
m2 = (p2Y - currY) / (p2X - currX);
You also want to check whether currPoint is inside the box created by the other two, so:
return (m1 == m2) && (p1Y <= currY && currY <= p2Y) && (p1X <= currX && currX <= p2X);
Edit: This is not a very good method; look at maxim1000's solution for a much more correct way.
I'll use Triangle approach:
First, I'll check the Area, if the Area is close to 0, then the Point lies on the Line.
But think about the case where the length of AC is so great, then the Area increases far from 0, but visually, we still see that B is on AC: that when we need to check the height of the triangle.
To do this, we need to remember the formula we learn from first grade: Area = Base * Height / 2
Here is the code:
bool Is3PointOn1Line(IList<Vector2> arrVert, int idx1, int idx2, int idx3)
{
//check if the area of the ABC triangle is 0:
float fArea = arrVert[idx1].x * (arrVert[idx2].y - arrVert[idx3].y) +
arrVert[idx2].x * (arrVert[idx3].y - arrVert[idx1].y) +
arrVert[idx3].x * (arrVert[idx1].y - arrVert[idx2].y);
fArea = Mathf.Abs(fArea);
if (fArea < SS.EPSILON)
{
//Area is zero then it's the line
return true;
}
else
{
//Check the height, in case the triangle has long base
float fBase = Vector2.Distance(arrVert[idx1], arrVert[idx3]);
float height = 2.0f * fArea / fBase;
return height < SS.EPSILON;
}
}
Usage:
Vector2[] arrVert = new Vector2[3];
arrVert[0] = //...
arrVert[1] = //...
arrVert[2] = //...
if(Is3PointOn1Line(arrVert, 0, 1, 2))
{
//Ta-da, they're on same line
}
PS: SS.EPSILON = 0.01f and I use some function of Unity (for ex: Vector2.Distance), but you got the idea.
Ready for what seems to be infinitely simpler than some of these other solutions?
You pass it three points (three objects with an x and y property). Points 1 and 2 define your line, and point 3 is the point you are testing.
function pointOnLine(pt1, pt2, pt3) {
const dx = (pt3.x - pt1.x) / (pt2.x - pt1.x);
const dy = (pt3.y - pt1.y) / (pt2.y - pt1.y);
const onLine = dx === dy
// Check on or within x and y bounds
const betweenX = 0 <= dx && dx <= 1;
const betweenY = 0 <= dy && dy <= 1;
return onLine && betweenX && betweenY;
}
console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }));
console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 0.5, y: 0.5})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 0.5, y: 0.5 }));
Edit: Simplified further according to RBarryYoung's observation.
This approach is similar to Steve's approach, just shorter and improved to use as little memory and process power as possible. But first the mathematical idea:
Let a, b be the ends of the line, ab the difference between them and p the point to check. Then p is exactly then on the line, if
a + i * ab = p
with i being a number in the interval [0;1] representing the index on the line. We can write that as two separate equations (for 2D):
a.x + i * ab.x = p.x
a.y + i * ab.y = p.y
⇔
i = (p.x - a.x) / ab.x
i = (p.y - a.y) / ab.y
Which gives us to requirements for p to be on the line from a to b:
(p.x - a.x) / ab.x = (p.y - a.y) / ab.y
and
0 ≤ i ≤ 1
In code:
function onLine(a, b, p) {
var i1 = (p.x - a.x) / (b.x - a.x), i2 = (p.y - a.y) / (b.y - a.y);
return i1 == i2 && i1 <= 0 && i1 >= 1;
}
Technically you could even inline i2 but that makes it even harder to read.
private static boolean pointInLine(int2 lineBegin, int2 lineEnd, int2 point)
{ boolean result = false;
int2 b = int2(min(lineBegin.x, lineEnd.x), min(lineBegin.y, lineEnd.y));
int2 e = int2(max(lineBegin.x, lineEnd.x), max(lineBegin.y, lineEnd.y));
if (point.x >= b.x && point.x <= e.x &&
point.y >= b.y && point.y <= e.y)
{ int2 normal = lineEnd.sub(lineBegin).perp();
int2 d0 = lineBegin.sub(point);
if (d0.dot(normal) == 0x0)
{ result = true;
}
}
return result;
}
This works for any slope, even if lineBegin == lineEnd == point.
First, create two points b and e, which ensures b <= e. (This is two support lines that have a negative slope) Check if point lands on the box (inclusive) created by those two points.
Then, get the normal of the line, which is a vector perpendicular to it.
You can do this by negating the x and transposing, x,y --> y, -x.
Then you can simply create a vector that points to the line from the point
or to the point from the line, it doesn't matter and doesn't have to be from the center of the line. Once you do that check if the vector is perpendicular to the normal by getting the dot product of the normal and the point vector.
It also makes it a bit easier on you if you've got some sort of math lib or at least a struct with x,y components. But you can do this with scalar components aswell. But getting the dot product and the normal are very simple calculations.
Here's the dot product:
.dot(int2 v) = (x * v.x + y * v.y)
Here's the perpendicular:
.perp() = new int2(y, -x)
.sub() .add() Do what you'd expect and params are in the same order.