I'm drawing a line in javascript using the Midpoint Algorithm Line Drawing.
When the line is in a descent trajectory, it computes the wrong values.
Can somebody help me?
data input
//midline(1,6,8,4);
function midline(x0, y0, x1, y1) {
/* Using midpoint algorithm for lines. */
const dx = x1 - x0;
const dy = y1 - y0;
var d = 2 * dy - dx;
const incrE = 2 * dy;
const incrNE = 2 * (dy - dx);
var x = x0;
var y = y0;
var output = [];
//listeners
console.log("dy = ", dy, "dx = ", dx);
console.log("d = ", d);
console.log("incrE = ", incrE);
console.log("incrNE = ", incrNE);
console.log("----------------------------------");
while (x <= x1) {
// if not the last x
console.log("x = ", x, " y = ", y);
output.push([x,y]);
if (d <= 0) {
console.log("E");
d = d + incrE;
x++;
} else {
console.log("NE");
d = d + incrNE;
x++;
y++;
}
}
console.table(output);
}
If per my comment the Mid Point Line Drawing algorithm is only suited for the first quadrant, that is...
x0 < x1 and y0 < y1
...must hold true, then adjustments are required if x1 < x0 or y1 < y0.
Taking the case presented (1,6) - (8,4), this is a downward slope because y1 < y0 (ie, 4 < 6 ). To make this a workable case for the Mid Point Line Drawing algorithm, you can simply negate the y values, in which case y0 < y1 will then hold true. Of course, when capturing the results, the values need to then be adjusted by multiplying by -1 again. So, suggest wedging in the following before putting x0, y0, x1, and y1 to use...
let xReflect = 1;
if ( x1 < x0 ) {
x0 = -x0;
x1 = -x1;
xReflect = -1;
}
let yReflect = 1;
if ( y1 < y0 ) {
y0 = -y0;
y1 = -y1;
yReflect = -1;
}
...and then, when pushing the output to the array, you will need to perform the following...
output.push( [ x * xReflect, y * yReflect ] );
Hope this helps.
Related
I am working on an algorithm to snap 3D lines to a 3D tessellated space.
Here is a 2D example of the algorithm that works for positive and negative slopes between -1 and 1 inclusive.
Using the slope to calculate the value of y at each x results in slow and error-prone floating calculations. The solution is to simulate the division with a remainder variable.
When dx >= dy, start with an initial remainder variable ry = 0. Then, for each x increment, add dy to ry variable. When it surpasses dx, increment y, then set ry equal to ry - dx.
function line(x1, y1, x2, y2) {
let points = []
let dx = Math.abs(x2 - x1);
let dy = Math.abs(y2 - y1);
// The remainder variable for y axes.
// No rx is created because we are assuming dx >= dy.
let ry = 0;
// Current value of y for a given point
let y = 0;
// The slope could be positive or negative, so increments coordinates as they go down or up.
let pointIncrement;
if (x2 > x1) {
pointIncrement = 1;
} else if (x2 < x1) {
pointIncrement = -1;
y = y1
}
for (let x = x1; pointIncrement < 0 ? x >= x2 : x <= x2; x += pointIncrement) {
if (ry >= dx) {
ry -= dx;
y += pointIncrement;
}
// Add dy to ry until it surpasses dx. This simulates the division of dy/dx for slope.
ry += dy;
points.push([x, y])
}
return points
}
Now, if you call the function with a slope of 1/4th:
line(0,0,20,5)
You get the following results:
[[0,0],[1,0],[2,0],[3,0],[4,1],[5,1],[6,1],[7,1],[8,2],[9,2],[10,2],[11,2],[12,3],[13,3],[14,3],[15,3],[16,4],[17,4],[18,4],[19,4],[20,5]]
Now, if you call it again but in the negative direction, then reverse the coordinate order:
line(20,5,0,0).reverse()
You get the following results:
[[0,0],[1,1],[2,1],[3,1],[4,1],[5,2],[6,2],[7,2],[8,2],[9,3],[10,3],[11,3],[12,3],[13,4],[14,4],[15,4],[16,4],[17,5],[18,5],[19,5],[20,5]]
Why is this occurring?
Is anyone aware of a solution to this problem to make the negative slope symmetric to the positive slope?
In the upward direction, you are calculating y = y1 + floor((x-x1) * dy / dx). In the downward direction, you are calculating y = y1 + ceil((x-x1) * dy / dx)
In both cases, this produces "unbalanced" lines, with dy/dx points with y == y1, but only one point with y == y2.
What you want to calculate is y = y1 + round((x-x1) * dy / dx). That will make the line ends balanced. If you want the upward and downward directions to be exactly symmetric, then you need to take care to make sure that 0.5 rounds in the same direction in both cases.
One way to accomplish rounding in this kind of implementation is with an offset. If A and B are integers, and B is positive, then round(A/B) == floor((A+floor(B/2))/B). This floor(B/2) offset can be added to your remainder at the start.
Taking care to make sure 0.5 always rounds up, you can fix your asymmetry just by changing the initial value for ry to:
let ry = y2 >= y1 ? Math.floor(dx/2) : Math.floor((dx-1)/2);
There are a couple other bugs in your code, too, though. If I fix them all, it looks like this:
function line(x1, y1, x2, y2) {
let points = []
// increment direction for x axis
const incX = x2 >= x1 ? 1 : -1;
// increment direction for y axis
const incY = y2 >= y1 ? 1 : -1;
// absolute span in x and y
const dx = (x2 - x1) * incX;
const dy = (y2 - y1) * incY;
// The remainder variable for y axes.
let ry = y2 >= y1 ? Math.floor(dx/2) : Math.floor((dx-1)/2);
// Current value of y for a given point
let y = y1;
for (let x = x1; incX < 0 ? x >= x2 : x <= x2; x += incX) {
if (ry >= dx) {
ry -= dx;
y += incY;
}
// Add dy to ry until it surpasses dx. This simulates the division of dy/dx for slope.
ry += dy;
points.push([x, y])
}
return points
}
It's apparently very hard to find the answer to whether a line segment and circle intersect. For example, if you google you'll find this question and even the top two answers seem wrong.
The accepted answer has a comment saying: This seems to compute the intersection of a circle with a line, not a segment Scroll down to the next answer and you'll find another comment saying Isn't this answer in incomplete? It finds whether a circle and line intersect, not a line segment.
I've then tried to search for a function for determining if just a segment intersects a circle, but to no avail. The best I could find is a pseudocode explanation here.
I've tried to adapt his code and while it seems to work, it seems overly verbose and I'm not sure if my implementation is correct. I'm asking whether or not this is correct and if it is, is there indeed no better way of determining this? What is the ideal way of determining if a line segment and circle intersects? Please note, I only need to know if they intersect, not where they intersect.
I've provided a full demo reproduction below so you can also visualize it.
function lineSegmentIntersectsCircle(x1, y1, x2, y2, cx, cy, r) {
let deltaX = x2 - x1;
let deltaY = y2 - y1;
let mag = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
let unitX = deltaX / mag;
let unitY = deltaY / mag;
let d = (cx - x1) * unitY - (cy - y1) * unitX;
if (d < -r || d > r) { return false; }
let x1CXDelta = x1 - cx;
let y1CYDelta = y1 - cy;
let x2CXDelta = x2 - cx;
let y2CYDelta = y2 - cy;
let pointOneWithinCircle = x1CXDelta * x1CXDelta + y1CYDelta * y1CYDelta < r * r;
if (pointOneWithinCircle) { return true; }
let pointTwoWithinCircle = x2CXDelta * x2CXDelta + y2CYDelta * y2CYDelta < r * r;
if (pointTwoWithinCircle) { return true; }
let foo = unitX * x1 + unitY * y1;
let bar = unitX * cx + unitY * cy;
let baz = unitX * x2 + unitY * y2;
return (foo < bar && bar < baz) || (baz < bar && bar < foo);
}
let ctx = document.querySelector("canvas").getContext("2d");
function drawCircle(xCenter, yCenter, radius) {
ctx.beginPath();
ctx.arc(xCenter, yCenter, radius, 0, 2 * Math.PI);
ctx.fill();
}
function drawLine(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
let circleX = 340;
let circleY = 250;
let circleR = 60;
let lineX1 = 50;
let lineY1 = 350;
let lineX2 = 285;
let lineY2 = 250;
draw = () => {
ctx.fillStyle = "#b2c7ef";
ctx.fillRect(0, 0, 800, 800);
ctx.fillStyle = "#ffffff";
drawCircle(circleX, circleY, circleR);
drawLine(lineX1, lineY1, lineX2, lineY2);
}
console.log(lineSegmentIntersectsCircle(lineX1, lineY1, lineX2, lineY2, circleX, circleY, circleR))
draw();
canvas { display: flex; margin: 0 auto; }
<canvas width="400" height="400"></canvas>
I think it would be a simpler to (1) compute the line-disk intersection, which is either empty, a point, or a line segment (2) test whether the intersection intersects the line segment.
The points of the line are ((1-t) x1 + t x2, (1-t) y1 + t y2) for all real t. Let x(t) = (1-t) x1 + t x2 - cx and y(t) = (1-t) y1 + t y2 - cy. The line-disk intersection is nonempty if and only if the polynomial x(t)^2 + y(t)^2 - r^2 = 0 has real roots t1 <= t2. In this case, the line-disk intersection is all t in [t1, t2]. The line segment is all t in [0, 1]. The two intersect if and only if t1 <= 1 and t2 >= 0.
Computing t1 and t2 requires a square root, which we can avoid. Let a, b, c be such that x(t)^2 + y(t)^2 - r^2 = a t^2 + b t + c. We have t1 + t2 = -b/a and t1 t2 = c/a.
The roots t1 and t2 are real if and only if b^2 - 4 a c >= 0.
The condition t1 <= 1 is false if and only if t1 - 1 > 0 and t2 - 1 > 0, which in turn is true if and only if (t1 - 1) + (t2 - 1) > 0 and (t1 - 1) (t2 - 1) > 0, which is equivalent to -b/a - 2 > 0 and c/a + b/a + 1 > 0. Since a > 0, this simplifies to -b > 2 a and c + b + a > 0.
The condition t2 >= 0 is false if and only if t1 < 0 and t2 < 0, which in turn is true if and only if t1 + t2 = -b/a < 0 and t1 t2 = c/a > 0. Since a > 0, this simplifies to b > 0 and c > 0.
Implementation in Javascript.
function lineSegmentIntersectsCircleOptimized(x1, y1, x2, y2, cx, cy, r) {
let x_linear = x2 - x1;
let x_constant = x1 - cx;
let y_linear = y2 - y1;
let y_constant = y1 - cy;
let a = x_linear * x_linear + y_linear * y_linear;
let half_b = x_linear * x_constant + y_linear * y_constant;
let c = x_constant * x_constant + y_constant * y_constant - r * r;
return (
half_b * half_b >= a * c &&
(-half_b <= a || c + half_b + half_b + a <= 0) &&
(half_b <= 0 || c <= 0)
);
}
function lineSegmentIntersectsCircle(x1, y1, x2, y2, cx, cy, r) {
let deltaX = x2 - x1;
let deltaY = y2 - y1;
let mag = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
let unitX = deltaX / mag;
let unitY = deltaY / mag;
let d = (cx - x1) * unitY - (cy - y1) * unitX;
if (d < -r || d > r) {
return false;
}
let x1CXDelta = x1 - cx;
let y1CYDelta = y1 - cy;
let x2CXDelta = x2 - cx;
let y2CYDelta = y2 - cy;
let pointOneWithinCircle =
x1CXDelta * x1CXDelta + y1CYDelta * y1CYDelta < r * r;
if (pointOneWithinCircle) {
return true;
}
let pointTwoWithinCircle =
x2CXDelta * x2CXDelta + y2CYDelta * y2CYDelta < r * r;
if (pointTwoWithinCircle) {
return true;
}
let foo = unitX * x1 + unitY * y1;
let bar = unitX * cx + unitY * cy;
let baz = unitX * x2 + unitY * y2;
return (foo < bar && bar < baz) || (baz < bar && bar < foo);
}
function test() {
for (let i = 0; i < 10000000; i++) {
let x1 = Math.random();
let y1 = Math.random();
let x2 = Math.random();
let y2 = Math.random();
let cx = Math.random();
let cy = Math.random();
let r = Math.random();
if (
lineSegmentIntersectsCircle(x1, y1, x2, y2, cx, cy, r) !=
lineSegmentIntersectsCircleOptimized(x1, y1, x2, y2, cx, cy, r)
) {
console.log("bad");
break;
}
}
}
test();
Suppose (say triangle), I want to move an object from A to B then B to C and then C to A. How can I do this?
I googled a lot, but can't find an example of moving an object (say ball) around polygon. I know, i can done it with bezier curves, but for a simple triangle or rectangle how can I do without it? Please give some pseudo code or code in any language. (prefer JavaScript/Processing).
Interpolate
You can get the position by using interpolation:
x = x1 + (x2 - x1) * f;
y = y1 + (y2 - y1) * f;
where x1, y1 is your first point, x2, y2 your end point.
f is a value between 0.0 and 1.0 which determines where on the line you are (ie. 0 = beginning, 0.5 is half way, 1 = end).
When you f = 1 you just go to the next segment in the polygon and reset f to 0.
Fiddle (JS)
Pseudo:
//prepare first segment:
x1 = nextX1;
y1 = nextY1;
x2 = nextX2;
y2 = nextY2;
loop:
f += speed; /// f.ex. 0.01
x = x1 + (x2 - x1) * f;
y = y1 + (y2 - y1) * f;
if f=1 then
f = 0;
x1 = nextX1;
y1 = nextY1;
x2 = nextX2;
y2 = nextY2;
repeat loop;
Example in JavaScript (see demo link above for full working example)
function loop() {
/// leave a trace behind for demo
ctx.clearRect(x + 1, y + 1, 6, 6);
f += speed;
if (f >= 1) {
/// initialize next line in polygon (see demo)
getNextSegment();
}
/// Here: get x and y based on interpolation
x = x1 + (x2 - x1) * f;
y = y1 + (y2 - y1) * f;
/// draw something to show position
ctx.fillRect(x, y, 8, 8);
/// loop
requestAnimationFrame(loop);
}
For constant speed calculate distance between start end end point and divide a step value (predefined fixed value) on that distance which you use for speed.
I need to verify if a coordinate LAT/LNG point is between other two points (a segment line like a road).
I followed THIS topic with no success.
function inRange(start, point, end) {
start_x = start.lat();
start_y = start.lng();
end_x = end.lat();
end_y = end.lng();
point_x = point.lat();
point_y = point.lng();
var dx = end_x - start_x;
var dy = end_y - start_y;
var innerProduct = (point_x - start_x)*dx + (point_y - start_y)*dy;
return 0 <= innerProduct && innerProduct <= dx*dx + dy*dy;
}
function checkRange(start, point, end){
var x1 = start.lat();
var y1 = start.lng();
var x2 = end.lat();
var y2 = end.lng();
var x = point.lat();
var y = point.lng();
if (x1 == x2) { // special case
return y1 < y2 ? (y1 <= y && y <= y2) : (y2 <= y && y <= y1);
}
var m = (y2 - y1) / (x2 - x1);
var r1 = x1 + m * y1;
var r2 = x2 + m * y2;
var r = x + m * y;
return r1 < r2 ? (r1 <= r && r <= r2) : (r2 <= r && r <= r1);
}
First test
Start: (44.4963217, 11.327993300000003)
End: (44.4973624, 11.32760170000006)
Point (44.4958434, 11.328122000000008)
InRange == false (OK)
So Point becomes new start Point
Second test
Start: (44.4958434, 11.328122000000008)
End: (44.4973624, 11.32760170000006)
Point: (44.4966928, 11.32781620000003)
InRange == false (ERROR)
Point2 is between start/end but the function returns false :(
Your method is responding as it should given the data you've passed it:
var start_lng = 11.328122000000008;
var end_lng = 11.32760170000006; // This longitude is less than the point longitude.
var point_lng = 11.32781620000003; // This longitude more than the end longitude.
I want to get all the x,y coordinates between 2 given points, on a straight line.
While this seems like such an easy task, I can't seem to get my head around it.
So, for example:
Point 1: (10,5)
Point 2: (15,90)
Edit: The solution below only applies from a geometrical point of view. Drawing on a screen is different than theoretical geometry, you should listen to the people suggesting Bresenham's algorithm.
Given, two points, and knowing that the line's equation is y = m*x + b, where m is the slope and b the intercept, you can calculate m and b and then apply the equation to all the values of the X axis between your A and B points:
var A = [10, 5];
var B = [15, 90];
function slope(a, b) {
if (a[0] == b[0]) {
return null;
}
return (b[1] - a[1]) / (b[0] - a[0]);
}
function intercept(point, slope) {
if (slope === null) {
// vertical line
return point[0];
}
return point[1] - slope * point[0];
}
var m = slope(A, B);
var b = intercept(A, m);
var coordinates = [];
for (var x = A[0]; x <= B[0]; x++) {
var y = m * x + b;
coordinates.push([x, y]);
}
console.log(coordinates); // [[10, 5], [11, 22], [12, 39], [13, 56], [14, 73], [15, 90]]
Given the point A(10, 5) and B(15, 90) and C(x, y) in AB we have:
(x - 10) / (y - 5) = (15 - 10) / (90 - 5)
What you can do is to iterate from x=10 to x=15 and calculate the corresponding y. Since x and y are integers, some times you have to round the result (or skip it).
Based on the wiki article, here's my JS code which handles both high and low lines:
const drawLine = (x0, y0, x1, y1) => {
const lineLow = (x0, y0, x1, y1) => {
const dx = x1 - x0
let dy = y1 - y0
let yi = 1
if (dy < 0) {
yi = -1
dy = -dy
}
let D = 2 * dy - dx
let y = y0
for (let x = x0; x < x1; x++) {
drawPoint(x, y)
if (D > 0) {
y = y + yi
D = D - 2 * dx
}
D = D + 2 * dy
}
}
const lineHigh = (x0, y0, x1, y1) => {
let dx = x1 - x0
const dy = y1 - y0
let xi = 1
if (dx < 0) {
xi = -1
dx = -dx
}
let D = 2 * dx - dy
let x = x0
for (let y = y0; y < y1; y++) {
drawPoint(x, y)
if (D > 0) {
x = x + xi
D = D - 2 * dy
}
D = D + 2 * dx
}
}
const { abs } = Math
if (abs(y1 - y0) < abs(x1 - x0)) {
if (x0 > x1) {
lineLow(x1, y1, x0, y0)
} else {
lineLow(x0, y0, x1, y1)
}
} else {
if (y0 > y1) {
lineHigh(x1, y1, x0, y0)
} else {
lineHigh(x0, y0, x1, y1)
}
}
}