Find max height-width of irregular shape drawn on HTML 5 canvas - javascript

I've been doing some image processing recently, and am looking for a javascript solution to determine the longest line segment that is entirely within a non-regular shape. To sum it up, the line segment should be the longest line segment that touches the shape and not overlaps or moves outside of the shape.
Here are the steps which I have followed
step 1:
step 2:
step 3:
as shown in step 3 Blue line indicates max length.
It is working perfectly to determine the length of regular shapes, but in case of irregular shapes it is not working (also in the case of 3 points).
To calculate length first I have taken points (which are mouse coordinates on the canvas down event.
Here is the snippet for Canvas down :
function getXY(e) {
var el = document.getElementById('canvas');
var rect = el.getBoundingClientRect();
/* console.log("widht "+$("#canvas").width());
console.log("heihgt "+$("#canvas").height());
console.log("X "+Math.round(e.clientX - rect.left));
console.log("y "+Math.round(e.clientY - rect.top));*/
return {
x: Math.round(e.clientX - rect.left),
y: Math.round(e.clientY - rect.top)
}
}
$('#canvas').mousedown(function(e) {
var can = document.getElementById("canvas");
var ctx = can.getContext('2d');
if (condition == 1) {
if (e.which == 1) {
//store the points on mousedown
var poss = getXY(e);
i = i + 1;
if (firstX == poss.x && firstY == poss.y) {
console.log(" inside if poss.x===" + poss.x + " poss.y===" + poss.y);
//$('#crop').click();
} else {
console.log(" inside else poss.x===" + poss.x + " poss.y===" + poss.y);
points.push(Math.round(poss.x), Math.round(poss.y));
pointsforline.push({ "x": Math.round(poss.x), "y": Math.round(poss.y) });
xarray.push(poss.x);
yarray.push(poss.y);
sendpoints.push(Math.round(poss.x), Math.round(poss.y));
if(points.length >= 6){
$('#fixMarkingBtn').show();
}
}
// Type 1 using array
if(points.length == 6 && sendpoints.length ==6 ){
$('#fixMarkingBtn').show();
}
// Type 2 using counter
/* if (i == 3) {
$('#fixMarkingBtn').show();
}*/
if (i == 1) {
$('#undoMarkingBtn').show();
$('#resetMarkingBtn').show();
firstX = poss.x;
firstY = poss.y;
//change is here
Xmax = poss.x;
Ymax = poss.y;
Xmin = poss.x;
Ymin = poss.y;
minX1 = poss.x;
maxY1 = poss.y;
minX1 = poss.x;
minY1 = poss.y;
}
if (poss.x < Xmin) {
Xmin = poss.x;
minY1 = poss.y;
}
if (poss.x > Xmax) {
Xmax = poss.x;
maxY1 = poss.y;
}
if (poss.y < Ymin) {
Ymin = poss.y;
minX1 = poss.x;
}
if (poss.y > Ymax) {
Ymax = poss.y;
maxX1 = poss.x;
}
ctx.globalCompositeOperation = 'source-over';
var oldposx = $('#oldposx').html();
var oldposy = $('#oldposy').html();
var posx = $('#posx').html();
var posy = $('#posy').html();
ctx.beginPath();
ctx.lineWidth = 13;
ctx.moveTo(oldposx, oldposy);
if (oldposx != '') {
ctx.lineTo(posx, posy);
ctx.stroke();
}
$('#oldposx').html(poss.x);
$('#oldposy').html(poss.y);
}
ctx.fillStyle = 'red';
ctx.strokeStyle = 'red';
ctx.fillRect(posx, posy, 10, 10);
$('#posx').html(posx);
$('#posy').html(posy);
} //condition
});
here is the code I have used (point of problem) :
function calMaxMin() {
for (var i = 0; i < points.length; i += 2) {
if (i == 0) {
Xmax = points[i];
Ymax = points[i + 1];
Xmin = points[i];
Ymin = points[i + 1];
minX1 = points[i];
maxY1 = points[i + 1];
minX1 = points[i];
minY1 = points[i + 1];
}
if (points[i] < Xmin) {
Xmin = points[i];
minY1 = points[i + 1];
}
if (points[i] > Xmax) {
Xmax = points[i];
maxY1 = points[i + 1];
}
if (points[i + 1] < Ymin) {
Ymin = points[i + 1];
minX1 = points[i];
}
if (points[i + 1] > Ymax) {
Ymax = points[i + 1];
maxX1 = points[i];
}
}
}
Problem image 1
Problem image 2 (what I'm getting right now)
Expected output
Any help would be appreciated.
Thanks in advance!

The problem's complexity changes when you switch from a convex polygon to a concave polygon: you need to check for intersections and "grow" candidate segments.
With a convex polygon, you have one candidate set, defined by all segments (p1, p2), (p1, p3), ..., (p2, p3), ..., (pn-1, pn), wherein the longest of those candidates is the result:
This example has a total 10 candidates. You simply select the longest one.
When you include concave polygons you must modify the candidate segments to stretch to the edges of the polygon and exclude any segments that intersect the polygon.
The red segments are excluded. The green segments are the modified ones. There are more complicated cases not depicted as well.
NOTE: I've had to play quite a bit with this math in the past and will be linking to functions of an old JavaScript library I built. Points are represented as { x: number, y: number} and polygons as arrays of points.
Segments can be excluded for two reasons:
Either endpoint starts with the segment leaving the polygon. You can test this by getting the global angle of the candidate segment (from said endpoint) and the global angles of the two adjacent polygon edges and checking if the candidate segment's angle falls between those two.
The candidate segment intersects any of the edges (inclusive of edge endpoints).
Extension of segments is somewhat complicated:
Find all segments wherein either endpoint is a concave vertex of the polygon. Include it twice if both endpoints are concave.
For said (segment, endpoint) pairs, stretch the segment through the endpoint a long distance (like 10000000) via polar projection.
Detect all intersection points of the elongated segment with the polygon.
Find the intersection point that is nearest the unmodified endpoint. This intersection point and the unmodified endpoint is the new candidate segment.
The result is the longest remaining candidate segment.
HINT: Might I recommend using GeoGebra for diagramming (I am in no way affiliated)?

Related

How to move an ellipse along a line with multiple points?

I am currently doing up a mini project on the side as part of my beginner lesson to Javascript. I am hoping to allow an ellipse to appear along points on a few lines in a graph. The coordinates are derived from a css file, using the p5js library.
Following this example, I could only produce multiple ellipses (the amount corresponds to the number of points I have on the line). Is there any way I can somehow 'clear' the ellipses and allow only one to appear each time it travels on the lines?
Would also be great if there are any suggestions as to how I can make ellipses appear on exact coordinates when my mouse hovers over the lines at different points. I'm unable to use map() accurately.
The CSV has its heading with number of days in a month, first column with months and the remaining are data according to days in each month.
Below is a screenshot of what is currently happening, as well as a snippet the said code. Please do kindly correct me if my question wasn't well-asked. Thank you!
var endDay;
var beginX;
var beginY;
var beginY;
var endY;
var distX;
var distY;
var prevStart = null;
for (var i = 0; i < this.data.getRowCount(); i++) {
var a = this.data.getRow(i);
for(var k = 1; k < numDays; k++) {
currEnd = {
'day': this.firstDay + k - 1 ,
'case': a.getNum(k) //get no. of cases by day
}
if(prevStart != null) {
// this.mapDayToWidth, this.mapCaseToHeight are helpers to
// give scaled values
for plotting
beginX = this.mapDayToWidth(prevStart.day);
endX = this.mapDayToWidth(currEnd.day);
beginY = this.mapCaseToHeight(prevStart.case);
endY = this.mapCaseToHeight(currEnd.case);
let new_endX = constrain(mouseX, endX - 20, endX + 20);
distX = endX - beginX;
distY = endY - beginY;
x = beginX + pct * distX;
y = beginY + pct * distY;
pct += 0.00001;
}
prevStart = currEnd;
if(pct < 1.0) {
this.drawEllipses(mouseX, endY, 10);
}
}
}
// how the ellipse is drawn
this.drawEllipse = function(ellipseX, ellipseY, size, begin_X, end_X) {
fill('black');
ellipse(ellipseX, ellipseY, size);
};
// how the line graph was plotted
this.drawLines = function(colr, x1, y1, x2, y2) {
strokeWeight(2);
stroke(colr);
beginShape();
line(x1,
y1,
x2,
y2
);
endShape();
};

SAT Polygon Circle Collision - resolve the intersection in the direction of velocity & determine side of collision

Summary
This question is in JavaScript, but an answer in any language, pseudo-code, or just the maths would be great!
I have been trying to implement the Separating-Axis-Theorem to accomplish the following:
Detecting an intersection between a convex polygon and a circle.
Finding out a translation that can be applied to the circle to resolve the intersection, so that the circle is barely touching the polygon but no longer inside.
Determining the axis of the collision (details at the end of the question).
I have successfully completed the first bullet point and you can see my javascript code at the end of the question. I am having difficulties with the other parts.
Resolving the intersection
There are plenty of examples online on how to resolve the intersection in the direction with the smallest/shortest overlap of the circle. You can see in my code at the end that I already have this calculated.
However this does not suit my needs. I must resolve the collision in the opposite direction of the circle's trajectory (assume I already have the circle's trajectory and would like to pass it into my function as a unit-vector or angle, whichever suits).
You can see the difference between the shortest resolution and the intended resolution in the below image:
How can I calculate the minimum translation vector for resolving the intersection inside my test_CIRCLE_POLY function, but that is to be applied in a specific direction, the opposite of the circle's trajectory?
My ideas/attempts:
My first idea was to add an additional axis to the axes that must be tested in the SAT algorithm, and this axis would be perpendicular to the circle's trajectory. I would then resolve based on the overlap when projecting onto this axis. This would sort of work, but would resolve way to far in most situations. It won't result in the minimum translation. So this won't be satisfactory.
My second idea was to continue to use magnitude of the shortest overlap, but change the direction to be the opposite of the circle's trajectory. This looks promising, but there are probably many edge-cases that I haven't accounted for. Maybe this is a nice place to start.
Determining side/axis of collision
I've figured out a way to determine which sides of the polygon the circle is colliding with. For each tested axis of the polygon, I would simply check for overlap. If there is overlap, that side is colliding.
This solution will not be acceptable once again, as I would like to figure out only one side depending on the circle's trajectory.
My intended solution would tell me, in the example image below, that axis A is the axis of collision, and not axis B. This is because once the intersection is resolved, axis A is the axis corresponding to the side of the polygon that is just barely touching the circle.
My ideas/attempts:
Currently I assume the axis of collision is that perpendicular to the MTV (minimum translation vector). This is currently incorrect, but should be the correct axis once I've updated the intersection resolution process in the first half of the question. So that part should be tackled first.
Alternatively I've considered creating a line from the circle's previous position and their current position + radius, and checking which sides intersect with this line. However, there's still ambiguity, because on occasion there will be more than one side intersecting with the line.
My code so far
function test_CIRCLE_POLY(circle, poly, circleTrajectory) {
// circleTrajectory is currently not being used
let axesToTest = [];
let shortestOverlap = +Infinity;
let shortestOverlapAxis;
// Figure out polygon axes that must be checked
for (let i = 0; i < poly.vertices.length; i++) {
let vertex1 = poly.vertices[i];
let vertex2 = poly.vertices[i + 1] || poly.vertices[0]; // neighbouring vertex
let axis = vertex1.sub(vertex2).perp_norm();
axesToTest.push(axis);
}
// Figure out circle axis that must be checked
let closestVertex;
let closestVertexDistSqr = +Infinity;
for (let vertex of poly.vertices) {
let distSqr = circle.center.sub(vertex).magSqr();
if (distSqr < closestVertexDistSqr) {
closestVertexDistSqr = distSqr;
closestVertex = vertex;
}
}
let axis = closestVertex.sub(circle.center).norm();
axesToTest.push(axis);
// Test for overlap
for (let axis of axesToTest) {
let circleProj = proj_CIRCLE(circle, axis);
let polyProj = proj_POLY(poly, axis);
let overlap = getLineOverlap(circleProj.min, circleProj.max, polyProj.min, polyProj.max);
if (overlap === 0) {
// guaranteed no intersection
return { intersecting: false };
}
if (Math.abs(overlap) < Math.abs(shortestOverlap)) {
shortestOverlap = overlap;
shortestOverlapAxis = axis;
}
}
return {
intersecting: true,
resolutionVector: shortestOverlapAxis.mul(-shortestOverlap),
// this resolution vector is not satisfactory, I need the shortest resolution with a given direction, which would be an angle passed into this function from the trajectory of the circle
collisionAxis: shortestOverlapAxis.perp(),
// this axis is incorrect, I need the axis to be based on the trajectory of the circle which I would pass into this function as an angle
};
}
function proj_POLY(poly, axis) {
let min = +Infinity;
let max = -Infinity;
for (let vertex of poly.vertices) {
let proj = vertex.projNorm_mag(axis);
min = Math.min(proj, min);
max = Math.max(proj, max);
}
return { min, max };
}
function proj_CIRCLE(circle, axis) {
let proj = circle.center.projNorm_mag(axis);
let min = proj - circle.radius;
let max = proj + circle.radius;
return { min, max };
}
// Check for overlap of two 1 dimensional lines
function getLineOverlap(min1, max1, min2, max2) {
let min = Math.max(min1, min2);
let max = Math.min(max1, max2);
// if negative, no overlap
let result = Math.max(max - min, 0);
// add positive/negative sign depending on direction of overlap
return result * ((min1 < min2) ? 1 : -1);
};
I am assuming the polygon is convex and that the circle is moving along a straight line (at least for a some possibly small interval of time) and is not following some curved trajectory. If it is following a curved trajectory, then things get harder. In the case of curved trajectories, the basic ideas could be kept, but the actual point of collision (the point of collision resolution for the circle) might be harder to calculate. Still, I am outlining an idea, which could be extended to that case too. Plus, it could be adopted as a main approach for collision detection between a circle and a convex polygon.
I have not considered all possible cases, which may include special or extreme situations, but at least it gives you a direction to explore.
Transform in your mind the collision between the circle and the polygon into a collision between the center of the circle (a point) and a version of the polygon thickened by the circle's radius r, i.e. (i) each edge of the polygon is offset (translated) outwards by radius r along a vector perpendicular to it and pointing outside of the polygon, (ii) the vertices become circular arcs of radius r, centered at the polygons vertices and connecting the endpoints of the appropriate neighboring offset edges (basically, put circles of radius r at the vertices of the polygon and take their convex hull).
Now, the current position of the circle's center is C = [ C[0], C[1] ] and it has been moving along a straight line with direction vector V = [ V[0], V[1] ] pointing along the direction of motion (or if you prefer, think of V as the velocity of the circle at the moment when you have detected the collision). Then, there is an axis (or let's say a ray - a directed half-line) defined by the vector equation X = C - t * V, where t >= 0 (this axis is pointing to the past trajectory). Basically, this is the half-line that passes through the center point C and is aligned with/parallel to the vector V. Now, the point of resolution, i.e. the point where you want to move your circle to is the point where the axis X = C - t * V intersects the boundary of the thickened polygon.
So you have to check (1) first axis intersection for edges and then (2) axis intersection with circular arcs pertaining to the vertices of the original polygon.
Assume the polygon is given by an array of vertices P = [ P[0], P[1], ..., P[N], P[0] ] oriented counterclockwise.
(1) For each edge P[i-1]P[i] of the original polygon, relevant to your collision (these could be the two neighboring edges meeting at the vertex based on which the collision is detected, or it could be actually all edges in the case of the circle moving with very high speed and you have detected the collision very late, so that the actual collision did not even happen there, I leave this up to you, because you know better the details of your situation) do the following. You have as input data:
C = [ C[0], C[1] ]
V = [ V[0], V[1] ]
P[i-1] = [ P[i-1][0], P[i-1][1] ]
P[i] = [ P[i][0], P[i][1] ]
Do:
Normal = [ P[i-1][1] - P[i][1], P[i][0] - P[i-1][0] ];
Normal = Normal / sqrt((P[i-1][1] - P[i][1])^2 + ( P[i][0] - P[i-1][0] )^2);
// you may have calculated these already
Q_0[0] = P[i-1][0] + r*Normal[0];
Q_0[1] = P[i-1][1] + r*Normal[1];
Q_1[0] = P[i][0]+ r*Normal[0];
Q_1[1] = P[i][1]+ r*Normal[1];
Solve for s, t the linear system of equations (the equation for intersecting ):
Q_0[0] + s*(Q_1[0] - Q_0[0]) = C[0] - t*V[0];
Q_0[1] + s*(Q_1[1] - Q_0[1]) = C[1] - t*V[1];
if 0<= s <= 1 and t >= 0, you are done, and your point of resolution is
R[0] = C[0] - t*V[0];
R[1] = C[1] - t*V[1];
else
(2) For the each vertex P[i] relevant to your collision, do the following:
solve for t the quadratic equation (there is an explicit formula)
norm(P[i] - C + t*V )^2 = r^2
or expanded:
(V[0]^2 + V[1]^2) * t^2 + 2 * ( (P[i][0] - C[0])*V[0] + (P[i][1] - C[1])*V[1] )*t + ( P[i][0] - C[0])^2 + (P[i][1] - C[1])^2 ) - r^2 = 0
or if you prefer in a more code-like way:
a = V[0]^2 + V[1]^2;
b = (P[i][0] - C[0])*V[0] + (P[i][1] - C[1])*V[1];
c = (P[i][0] - C[0])^2 + (P[i][1] - C[1])^2 - r^2;
D = b^2 - a*c;
if D < 0 there is no collision with the vertex
i.e. no intersection between the line X = C - t*V
and the circle of radius r centered at P[i]
else
D = sqrt(D);
t1 = ( - b - D) / a;
t2 = ( - b + D) / a;
where t2 >= t1
Then your point of resolution is
R[0] = C[0] - t2*V[0];
R[1] = C[1] - t2*V[1];
Circle polygon intercept
If the ball is moving and if you can ensure that the ball always starts outside the polygon then the solution is rather simple.
We will call the ball and its movement the ball line. It starts at the ball's current location and end at the position the ball will be at the next frame.
To solve you find the nearest intercept to the start of the ball line.
There are two types of intercept.
Line segment (ball line) with Line segment (polygon edge)
Line segment (ball line) with circle (circle at each (convex only) polygon corner)
The example code has a Lines2 object that contains the two relevant intercept functions. The intercepts are returned as a Vec2containing two unit distances. The intercept functions are for lines (infinite length) not line sgements. If there is no intercept then the return is undefined.
For the line intercepts Line2.unitInterceptsLine(line, result = new Vec2()) the unit values (in result) are the unit distance along each line from the start. negative values are behind the start.
To take in account of the ball radius each polygon edge is offset the ball radius along its normal. It is important that the polygon edges have a consistent direction. In the example the normal is to the right of the line and the polygon points are in a clockwise direction.
For the line segment / circle intercepts Line2.unitInterceptsCircle(center, radius, result = new Vec2()) the unit values (in result) are the unit distance along the line where it intercepts the circle. result.x will always contain the closest intercept (assuming you start outside the circle). If there is an intercept there ways always be two, even if they are at the same point.
Example
The example contains all that is needed
The objects of interest are ball and poly
ball defines the ball and its movement. There is also code to draw it for the example
poly holds the points of the polygon. Converts the points to offset lines depending on the ball radius. It is optimized to that it only calculates the lines if the ball radius changes.
The function poly.movingBallIntercept is the function that does all the work. It take a ball object and an optional results vector.
It returns the position as a Vec2 of the ball if it contacts the polygon.
It does this by finding the smallest unit distance to the offset lines, and point (as circle) and uses that unit distance to position the result.
Note that if the ball is inside the polygon the intercepts with the corners is reversed. The function Line2.unitInterceptsCircle does provide 2 unit distance where the line enters and exits the circle. However you need to know if you are inside or outside to know which one to use. The example assumes you are outside the polygon.
Instructions
Move the mouse to change the balls path.
Click to set the balls starting position.
Math.EPSILON = 1e-6;
Math.isSmall = val => Math.abs(val) < Math.EPSILON;
Math.isUnit = u => !(u < 0 || u > 1);
Math.TAU = Math.PI * 2;
/* export {Vec2, Line2} */ // this should be a module
var temp;
function Vec2(x = 0, y = (temp = x, x === 0 ? (x = 0 , 0) : (x = x.x, temp.y))) {
this.x = x;
this.y = y;
}
Vec2.prototype = {
init(x, y = (temp = x, x = x.x, temp.y)) { this.x = x; this.y = y; return this }, // assumes x is a Vec2 if y is undefined
copy() { return new Vec2(this) },
equal(v) { return (this.x - v.x) === 0 && (this.y - v.y) === 0 },
isUnits() { return Math.isUnit(this.x) && Math.isUnit(this.y) },
add(v, res = this) { res.x = this.x + v.x; res.y = this.y + v.y; return res },
sub(v, res = this) { res.x = this.x - v.x; res.y = this.y - v.y; return res },
scale(val, res = this) { res.x = this.x * val; res.y = this.y * val; return res },
invScale(val, res = this) { res.x = this.x / val; res.y = this.y / val; return res },
dot(v) { return this.x * v.x + this.y * v.y },
uDot(v, div) { return (this.x * v.x + this.y * v.y) / div },
cross(v) { return this.x * v.y - this.y * v.x },
uCross(v, div) { return (this.x * v.y - this.y * v.x) / div },
get length() { return this.lengthSqr ** 0.5 },
set length(l) { this.scale(l / this.length) },
get lengthSqr() { return this.x * this.x + this.y * this.y },
rot90CW(res = this) {
const y = this.x;
res.x = -this.y;
res.y = y;
return res;
},
};
const wV1 = new Vec2(), wV2 = new Vec2(), wV3 = new Vec2(); // pre allocated work vectors used by Line2 functions
function Line2(p1 = new Vec2(), p2 = (temp = p1, p1 = p1.p1 ? p1.p1 : p1, temp.p2 ? temp.p2 : new Vec2())) {
this.p1 = p1;
this.p2 = p2;
}
Line2.prototype = {
init(p1, p2 = (temp = p1, p1 = p1.p1, temp.p2)) { this.p1.init(p1); this.p2.init(p2) },
copy() { return new Line2(this) },
asVec(res = new Vec2()) { return this.p2.sub(this.p1, res) },
unitDistOn(u, res = new Vec2()) { return this.p2.sub(this.p1, res).scale(u).add(this.p1) },
translate(vec, res = this) {
this.p1.add(vec, res.p1);
this.p2.add(vec, res.p2);
return res;
},
translateNormal(amount, res = this) {
this.asVec(wV1).rot90CW().length = -amount;
this.translate(wV1, res);
return res;
},
unitInterceptsLine(line, res = new Vec2()) { // segments
this.asVec(wV1);
line.asVec(wV2);
const c = wV1.cross(wV2);
if (Math.isSmall(c)) { return }
wV3.init(this.p1).sub(line.p1);
res.init(wV1.uCross(wV3, c), wV2.uCross(wV3, c));
return res;
},
unitInterceptsCircle(point, radius, res = new Vec2()) {
this.asVec(wV1);
var b = -2 * this.p1.sub(point, wV2).dot(wV1);
const c = 2 * wV1.lengthSqr;
const d = (b * b - 2 * c * (wV2.lengthSqr - radius * radius)) ** 0.5
if (isNaN(d)) { return }
return res.init((b - d) / c, (b + d) / c);
},
};
/* END of file */ // Vec2 and Line2 module
/* import {vec2, Line2} from "whateverfilename.jsm" */ // Should import vec2 and line2
const POLY_SCALE = 0.5;
const ball = {
pos: new Vec2(-150,0),
delta: new Vec2(10, 10),
radius: 20,
drawPath(ctx) {
ctx.beginPath();
ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.TAU);
ctx.stroke();
},
}
const poly = {
bRadius: 0,
lines: [],
set ballRadius(radius) {
const len = this.points.length
this.bRadius = ball.radius;
i = 0;
while (i < len) {
let line = this.lines[i];
if (line) { line.init(this.points[i], this.points[(i + 1) % len]) }
else { line = new Line2(new Vec2(this.points[i]), new Vec2(this.points[(i + 1) % len])) }
this.lines[i++] = line.translateNormal(radius);
}
this.lines.length = i;
},
points: [
new Vec2(-200, -150).scale(POLY_SCALE),
new Vec2(200, -100).scale(POLY_SCALE),
new Vec2(100, 0).scale(POLY_SCALE),
new Vec2(200, 100).scale(POLY_SCALE),
new Vec2(-200, 75).scale(POLY_SCALE),
new Vec2(-150, -50).scale(POLY_SCALE),
],
drawBallLines(ctx) {
if (this.lines.length) {
const r = this.bRadius;
ctx.beginPath();
for (const l of this.lines) {
ctx.moveTo(l.p1.x, l.p1.y);
ctx.lineTo(l.p2.x, l.p2.y);
}
for (const p of this.points) {
ctx.moveTo(p.x + r, p.y);
ctx.arc(p.x, p.y, r, 0, Math.TAU);
}
ctx.stroke()
}
},
drawPath(ctx) {
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
ctx.closePath();
ctx.stroke();
},
movingBallIntercept(ball, res = new Vec2()) {
if (this.bRadius !== ball.radius) { this.ballRadius = ball.radius }
var i = 0, nearest = Infinity, nearestGeom, units = new Vec2();
const ballT = new Line2(ball.pos, ball.pos.add(ball.delta, new Vec2()));
for (const p of this.points) {
const res = ballT.unitInterceptsCircle(p, ball.radius, units);
if (res && units.x < nearest && Math.isUnit(units.x)) { // assumes ball started outside poly so only need first point
nearest = units.x;
nearestGeom = ballT;
}
}
for (const line of this.lines) {
const res = line.unitInterceptsLine(ballT, units);
if (res && units.x < nearest && units.isUnits()) { // first unit.x is for unit dist on line
nearest = units.x;
nearestGeom = ballT;
}
}
if (nearestGeom) { return ballT.unitDistOn(nearest, res) }
return;
},
}
const ctx = canvas.getContext("2d");
var w = canvas.width, cw = w / 2;
var h = canvas.height, ch = h / 2
requestAnimationFrame(mainLoop);
// line and point for displaying mouse interaction. point holds the result if any
const line = new Line2(ball.pos, ball.pos.add(ball.delta, new Vec2())), point = new Vec2();
function mainLoop() {
ctx.setTransform(1,0,0,1,0,0); // reset transform
if(w !== innerWidth || h !== innerHeight){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
}else{
ctx.clearRect(0,0,w,h);
}
ctx.setTransform(1,0,0,1,cw,ch); // center to canvas
if (mouse.button) { ball.pos.init(mouse.x - cw, mouse.y - ch) }
line.p2.init(mouse.x - cw, mouse.y - ch);
line.p2.sub(line.p1, ball.delta);
ctx.lineWidth = 1;
ctx.strokeStyle = "#000"
poly.drawPath(ctx)
ctx.strokeStyle = "#F804"
poly.drawBallLines(ctx);
ctx.strokeStyle = "#F00"
ctx.beginPath();
ctx.arc(ball.pos.x, ball.pos.y, ball.radius, 0, Math.TAU);
ctx.moveTo(line.p1.x, line.p1.y);
ctx.lineTo(line.p2.x, line.p2.y);
ctx.stroke();
ctx.strokeStyle = "#00f"
ctx.lineWidth = 2;
ctx.beginPath();
if (poly.movingBallIntercept(ball, point)) {
ctx.arc(point.x, point.y, ball.radius, 0, Math.TAU);
} else {
ctx.arc(line.p2.x, line.p2.y, ball.radius, 0, Math.TAU);
}
ctx.stroke();
requestAnimationFrame(mainLoop);
}
const mouse = {x:0, y:0, button: false};
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
#canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Click to position ball. Move mouse to test trajectory
Vec2 and Line2
To make it easier a vector library will help. For the example I wrote a quick Vec2 and Line2 object (Note only functions used in the example have been tested, Note The object are designed for performance, inexperienced coders should avoid using these objects and opt for a more standard vector and line library)
It's probably not what you're looking for, but here's a way to do it (if you're not looking for perfect precision) :
You can try to approximate the position instead of calculating it.
The way you set up your code has a big advantage : You have the last position of the circle before the collision. Thanks to that, you can just "iterate" through the trajectory and try to find a position that is closest to the intersection position.
I'll assume you already have a function that tells you if a circle is intersecting with the polygon.
Code (C++) :
// What we need :
Vector startPos; // Last position of the circle before the collision
Vector currentPos; // Current, unwanted position
Vector dir; // Direction (a unit vector) of the circle's velocity
float distance = compute_distance(startPos, currentPos); // The distance from startPos to currentPos.
Polygon polygon; // The polygon
Circle circle; // The circle.
unsigned int iterations_count = 10; // The number of iterations that will be done. The higher this number, the more precise the resolution.
// The algorithm :
float currentDistance = distance / 2.f; // We start at the half of the distance.
Circle temp_copy; // A copy of the real circle to "play" with.
for (int i = 0; i < iterations_count; ++i) {
temp_copy.pos = startPos + currentDistance * dir;
if (checkForCollision(temp_copy, polygon)) {
currentDistance -= currentDistance / 2.f; // We go towards startPos by the half of the current distance.
}
else {
currentDistance += currentDistance / 2.f; // We go towards currentPos by the half of the current distance.
}
}
// currentDistance now contains the distance between startPos and the intersection point
// And this is where you should place your circle :
Vector intersectionPoint = startPos + currentDistance * dir;
I haven't tested this code so I hope there's no big mistake in there. It's also not optimized and there are a few problems with this approach (the intersection point could end up inside the polygon) so it still needs to be improved but I think you get the idea.
The other (big, depending on what you're doing) problem with this is that it's an approximation and not a perfect answer.
Hope this helps !
I'm not sure if I understood the scenario correctly, but an efficient straight forward use case would be, to only use a square bounding box of your circle first, calculating intersection of that square with your polygone is extremely fast, much much faster, than using the circle. Once you detect an intersection of that square and the polygone, you need to think or to write which precision is mostly suitable for your scenarion. If you need a better precision, than at this state, you can go on as this from here:
From the 90° angle of your sqare intersection, you draw a 45° degree line until it touches your circle, at this point, where it touches, you draw a new square, but this time, the square is embedded into the circle, let it run now, until this new square intersects the polygon, once it intersects, you have a guaranteed circle intersection. Depending on your needed precision, you can simply play around like this.
I'm not sure what your next problem is from here? If it has to be only the inverse of the circles trajectory, than it simply must be that reverse, I'm really not sure what I'm missing here.

Can I get help on a Rectilinear Polygon Hull Algorithm (in javascript if possible)

Edited title and here to reflect my question better.
My first description didn't get answers of the kind I was looking for and I didn't know there was a fancy name for this kind of shape I'm trying to make.
I studied Graham Scan algorithms but they don't completely cover the answer I'm looking for either, as they tend to "cut corners" if the shape becomes strange say.
Based on some articles I found, I'd started writing my own algorithm and it works a lot of the cases to be sure but I keep coming into problems I cant seem to completely stamp out, I believe the problem might be where I'm trying to calculate each of the corners.
This is the logic of my algorithm -
1st = Find the corners, top-left, top-right, bottom-right, bottom-left
2nd = Enter a while loop, travelling towards a target, starting from top-left travelling to top-right then to bottom right to bottom left and back to top left.
3rd = in each iteration of this while loop, we find the neighbours of each tile if they exist in this list, and choose the next tile to travel to, based on which comes first in an order decided by priority.
Priority explained, if the priority is to get to the top right corner, we take our time by first trying to travel to the left, then up, right and down but ONLY if a tile in this direction exists and ONLY if this tile isn't the previous tile we were standing at.
After we arrive at the top right corner, we shift the priority list making the first priority become the last. So
priority = [left, up, right, down]
becomes
priority = [up, right, down, left]
In the majority of cases my code IS performing as expected, like the logic seems sound, but there are a few anomaly moments where it doesn't seem to pick the right tile based on the priority I'm giving it and instead it wanders about the whole array. If anyone can help me where I'm making a mistake I'd appreciate this.
Heres my current algorithm code >
draw_hull = function() {
var list = [],
coordinates = [],
original_list = this.territory,
_x = 0, // coords are in arrays, [x,y] style.
_y = 1; // this is just for readers readability
// make list to be looped through, for each tile I deliberately added 4,
//it'll look more readable in the final product.
for (var i = 0; i < original_list.length; i++) {
list.push( [ (original_list[i][_x] * 32) + 8, (original_list[i][_y] * 32) + 8 ] );
list.push( [ (original_list[i][_x] * 32) + 24, (original_list[i][_y] * 32) + 8 ] );
list.push( [ (original_list[i][_x] * 32) + 24, (original_list[i][_y] * 32) + 24 ] );
list.push( [ (original_list[i][_x] * 32) + 8, (original_list[i][_y] * 32) + 24 ] );
}
// find corners
var topleft = 0, topright = 0, bottomright = 0, bottomleft = 0, hull = [];
for (var i = 0; i < list.length; i++) {
var x = list[i][_x];
var y = list[i][1];
if (x <= list[topleft][_x] && y <= list[topleft][_y]) topleft = i;
if (x >= list[topright][_x] && y <= list[topright][_y]) topright = i;
if (x >= list[bottomright][_x] && y >= list[bottomright][_y]) bottomright = i;
if (x <= list[bottomleft][_x] && y >= list[bottomleft][_y]) bottomleft = i;
}
// start drawing paths from one corner to the next, repeating until a full loop has been made
var priorities = ["l","u","r","d"], // travel the outline in this order, left up right down
current = topleft, // current tile
last = topleft; // last tile
target = [topright,bottomright,bottomleft,topleft],
target_iterator = 0, // target iterator so we know which corner we're striving towards
xx = 0, // an iterator to make sure this loop doesnt go on forever
done = false;
while(!done) {
// add the current tile to our hull list.
hull.push(current);
var next = {},
cx = list[current][_x],
cy = list[current][_y];
for (var i = 0; i < list.length; i++) {
var x = list[i][_x], y = list[i][_y];
if (x === cx && y === cy -16) { next.up = i; continue; }
if (x === cx && y === cy +16) { next.down = i; continue; }
if (x === cx -16 && y === cy) { next.left = i; continue; }
if (x === cx +16 && y === cy) { next.right = i; continue; }
}
var i = 0, check = priorities;
for (var i = 0; i < priorities.length; i++) {
var check = priorities[i];
// skip this if next check doesnt exist, or is the tile we just came from
if (next[check] == null || next[check] == undefined|| next[check] === last) continue;
var visited = false;
for (var j = 1; j < hull.length; j++) {
if (hull[j] === next[check]) {
visited = true;
continue;
}
}
if (visited) {
continue;
}
break;
}
last = current;
current = next[check];
xcoords = list[current][_x];
ycoords = list[current][_y];
coordinates.push([xcoords,ycoords]);
if (current === target[target_iterator]) {
priorities.push(priorities.shift());
target_iterator++;
if (target_iterator === 4) break;
}
xx++;
if (xx > 50) {break; debugger;}
}
if (xx < 50) this.hull = coordinates;
}
You should use moveTo() and lineTo() with a stroke(). Further documentation is available at MDN.
A simple drawing function could look like this: (or check Fiddle)
function drawShape(coords) {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.beginPath();
for(var i = 0; i < coords.length; i++) {
if(i === 0) {
ctx.moveTo(coords[i].x, coords[i].y);
} else {
ctx.lineTo(coords[i].x, coords[i].y);
}
}
ctx.closePath();
ctx.stroke();
}
You can easily stroke an "unlimited" amount of corners, or vertices of a shape using moveTo() and lineTo(). Below is an example of how you could draw a triangle, but you could easily extend it by adding more path methods.
var ctx = canvas.getContext('2d');
// Filled triangle
ctx.beginPath();
ctx.moveTo(25,25); //moves 'pen' to coords
ctx.lineTo(105,25); //draws line from prior coords to new specified coords.
ctx.lineTo(25,105);
ctx.fill(); //fills in shape
// Stroked triangle
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke(); //strokes along path (i.e shape outline)
<canvas id="canvas" width="200" height="200"></canvas>
This code can be found at https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes along with detailed advice on how to draw more complex shapes.
Ok, further studies, I found a name for the type of shape I'm trying to draw.
Its called an Rectilinear polygon.
Wikipedia's article
Stack Overflow already has an answer for this, was just hard to find.

center of Raphael triangle

Let's say I need to put a text in the middle of the area of a triangle.
I can calculate the coordinates of the triangle's center using getBBox():
var triangle = "M0,0 L100,0 100,50 z";
var BBox = paper.path(triangle).getBBox();
var middle;
middle.x = BBox.x + BBox.width/2;
middle.y = BBox.y + BBox.height/2;
This results in the coordinates (50, 25) which are always on the long side of the triangle.
How can I make sure the calculated "middle" is inside the triangle?
The correct coordinates should be approximately: (75, 25).
The code should of course be independent of this particular example, it should work for any kind of shape.
I've done some more research in the topic, and following an advice from another list I got here:
https://en.wikipedia.org/wiki/Centroid
There is an algorithm there to calculate the centroid of an irregular polygon, which I have translated into this code:
function getCentroid(path) {
var x = new Array(11);
var y = new Array(11);
var asum = 0, cxsum = 0, cysum = 0;
var totlength = path.getTotalLength();
for (var i = 0; i < 11; i++) {
var location = path.getPointAtLength(i*totlength/10);
x[i] = location.x;
y[i] = location.y;
if (i > 0) {
asum += x[i - 1]*y[i] - x[i]*y[i - 1];
cxsum += (x[i - 1] + x[i])*(x[i - 1]*y[i] - x[i]*y[i - 1]);
cysum += (y[i - 1] + y[i])*(x[i - 1]*y[i] - x[i]*y[i - 1]);
}
}
return({x: (1/(3*asum))*cxsum, y: (1/(3*asum))*cysum});
}
It's basically an approximation of any path by 10 points (the 11th is equal to the starting point), and the function returns, for that triangle, the coordinates:
Object {x: 65.32077336966377, y: 16.33111549955705}
I've tested it with many other shapes, and it works pretty good.
Hope it helps somebody.
This snippet will calculate the center of any polygon by averaging the vertices.
var paper = Raphael(0,0, 320, 200);
var triangle = "M0,0 L100,0 100,50 z";
var tri = paper.path(triangle);
tri.attr('fill', 'blue');
var center = raphaelPathCenter( tri );
var circle = paper.circle( center.x, center.y, 5);
circle.attr("fill", "#f00");
circle.attr("stroke", "#fff");
function raphaelPathCenter( path ) {
path.getBBox(); // forces path to be traced so realPath is not null.
var vertices = parseSVGVertices( path.realPath );
var center = vertices.reduce( function(prev,cur) {
return { x: prev.x + cur.x, y: prev.y + cur.y }
}, {x:0, y:0} );
center.x /= vertices.length;
center.y /= vertices.length;
return center;
}
function parseSVGVertices( svgPath )
{
var vertices = [];
for ( var i = 0; i < svgPath.length; i ++ )
{
var vertex = svgPath[i];
if ( "ML".indexOf( vertex[0] ) > -1 ) // check SVG command
vertices.push( { x: vertex[1], y: vertex[2] } );
}
return vertices;
}
<script src="https://raw.githubusercontent.com/DmitryBaranovskiy/raphael/master/raphael-min.js"></script>
<canvas id='canvas'></canvas>
<pre id='output'></pre>
However there are a few more triangle centers to choose from.

How to move a marker on a straight line between two coordinates in Mapbox.js

Below is some code I found for moving a marker but I want to move a marker on straight path between two coordinates can any one help these are the coordinates
[90.40237426757811,23.75015391301012],[88.34930419921875,22.573438264572406]
I need the coordinates between these two points for a line. The code is:
var marker = L.marker([0, 0], {
icon: L.mapbox.marker.icon({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-77, 37.9]
},
properties: { }
})
});
var t = 0;
window.setInterval(function() {
// making a lissajous curve here just for fun. this isn't necessary
// Reassign the features
marker.setLatLng(L.latLng(
Math.cos(t * 0.5) * 50,
Math.sin(t) * 50));
t += 0.1;
}, 50);
marker.addTo(map);
How accurate do you need the lines to be? Try something like this to begin with:
var start = {lat:90.40237426757811, lng:23.75015391301012}
var end = {lat:88.34930419921875, lng:22.573438264572406}
var n = 100; // the number of coordinates you want
coordinates = []
for(var i = n - 1; i > 0; i--){
coordinates.push( {lat: start.lat*i/n + end.lat*(n-i)/n,
lng: start.lng*i/n + end.lng*(n-i)/n});
}
This isn't accurate because the world isn't flat and over long distances it will begin to look wrong.
The full maths of plotting straight lines on projected globes is more difficult but there's a great explanation here:
http://www.movable-type.co.uk/scripts/latlong.html
Not far down the page there's a formula to calculate the midpoint of two given points.
Use that formula, then use the midpoint you've found with each end point to find two more points then use those points and so on until you have enough for a smooth line.
This might help. It draws a line between two points on a 2d plane.
fromXy and toXy are arrays containing the coordinates.
pref.canvas.size is and array containing the width and height of the canvas.
pref.color is the color of the pixel you want to print.
setPx() sets a pixel given x and y coordinates and color.
function line(toXy,fromXy) {
var y;
var m = (toXy[1] - fromXy[1]) / (fromXy[0] - toXy[0]);
var b = (m * toXy[0]) + toXy[1];
if (Math.abs(fromXy[0] - toXy[0]) >= Math.abs(fromXy[1] - toXy[1])) {
if (fromXy[0] < toXy[0]) {
for (var x = fromXy[0]; x <= toXy[0]; x++) {
y = m * x - b;
setPx(x,Math.abs(Math.round(y)),pref.color,);
}
} else {
for (var x = fromXy[0]; x >= toXy[0]; x--) {
y = m * x - b;
setPx(x,Math.abs(Math.round(y)),pref.color)
}
}
} else {
if (fromXy[1] <= toXy[1]) {
for (y = fromXy[1]; y <= toXy[1]; y++) {
x = (y / -(m)) + Math.abs(b / -(m));
if (x.toString() == 'Infinity' || x.toString() == 'NaN') {
x = fromXy[0];
}
if (x > pref.canvas.size[0] - 1) {
continue;
}
setPx(Math.abs(Math.round(x)),y,pref.color);
}
} else {
for (y = fromXy[1]; y >= toXy[1]; y--) {
x = (y / -(m)) + Math.abs(b / -(m));
if (x.toString() == 'Infinity' || x.toString() == 'NaN') {
x = fromXy[0];
}
if (x > pref.canvas.size[0] - 1) {
continue;
}
setPx(Math.abs(Math.round(x)),y,pref.color);
}
}
}
}
The code basically builds a linear equation out of the two coordinates then graphs that linear equation.
You should be able to edit the code so that it fits your needs pretty easily.
Thank you all for your useful answers :)
I used the below code for my use case, its not fully correct and with lot of hard-coding too but it worked fr me
here is the link of mock-up app which i developed using this
http://nandinibhotika.com/compass/discover.htm
here is the project description http://nandinibhotika.com/portfolio/compass-exploring-stories/
var geojson = {
type: 'LineString',
coordinates: []
},
start = [90.4010009765625, 23.74763991365265];
geojson.coordinates.push(start.slice());
momentum = [.025, .01429];
for (var i = 0; i < 557; i++) {
if (start[0] > 88.36878921508789 && start[1] > 22.571377617836507) {
start[0] -= momentum[0];
start[1] -= momentum[1];
} else {
momentum = [.00899, .0231];
start[0] += momentum[0];
start[1] -= momentum[1];
}
geojson.coordinates.push(start.slice());
}

Categories

Resources