Line Circle intersection for Vertical and Horizontal Lines - algorithm
I'm trying to detect when a line intersects a circle in javascript. I found a function that works almost perfectly but I recently noticed that it does not work when the intersecting line is perfectly horizontal or vertical. Since I don't have a great understanding of how this function actually works, I'm not sure how to edit it to get the results I'd like.
function lineCircleCollision(circleX,circleY,radius,lineX1,lineY1,lineX2,lineY2) {
var d1 = pDist(lineX1,lineY1,circleX,circleY);
var d2 = pDist(lineX2,lineY2,circleX,circleY);
if (d1<=radius || d2<=radius) {
return true;
}
var k1 = ((lineY2-lineY1)/(lineX2-lineX1));
var k2 = lineY1;
var k3 = -1/k1;
var k4 = circleY;
var xx = (k1*lineX1-k2-k3*circleX+k4)/(k1-k3);
var yy = k1*(xx-lineX1)+lineY1;
var allow = true;
if (lineX2>lineX1) {
if (xx>=lineX1 && xx<=lineX2) {}
else {allow = false;}
} else {
if (xx>=lineX2 && xx<=lineX1) {}
else {allow = false;}
}
if (lineY2>lineY1) {
if (yy>=lineY1 && yy<=lineY2) {}
else {allow = false;}
} else {
if (yy>=lineY2 && yy<=lineY1) {}
else {allow = false;}
}
if (allow) {
if (pDist(circleX,circleY,xx,yy)<radius) {
return true;
}
else {
return false;
}
} else {
return false;
}
}
function pDist(x1,y1,x2,y2) {
var xd = x2-x1;
var yd = y2-y1;
return Math.sqrt(xd*xd+yd*yd);
}
You can express the line as two relations:
x = x1 + k * (x2 - x1) = x1 + k * dx
y = y1 + k * (y2 - y1) = y1 + k * dy
with 0 < k < 1. A point on the circle satisfies the equation:
(x - Cx)² + (y - Cy)² = r²
Replace x and y by the line equations and you'll get a quadratic equation:
a*k² + b*k + c = 0
a = dx² + dy²
b = 2*dx*(x1 - Cx) + s*dy*(y1 - Cy)
c = (x1 - Cx)² + (y1 - Cy)² - r²
Solve that and if any of the two possible solutions for k lies in the range between 0 and 1, you have a hit. This method checks real intersections and misses the case where the line is entirely contained in the circle, so an additional check whether the line's end points lie within the circle is necessary.
Here's the code:
function collision_circle_line(Cx, Cy, r, x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var sx = x1 - Cx;
var sy = y1 - Cy;
var tx = x2 - Cx;
var ty = y2 - Cy;
if (tx*tx + ty*ty < r*r) return true;
var c = sx*sx + sy*sy - r*r;
if (c < 0) return true;
var b = 2 * (dx * sx + dy * sy);
var a = dx*dx + dy*dy;
if (Math.abs(a) < 1.0e-12) return false;
var discr = b*b - 4*a*c;
if (discr < 0) return false;
discr = Math.sqrt(discr);
var k1 = (-b - discr) / (2 * a);
if (k1 >= 0 && k1 <= 1) return true;
var k2 = (-b + discr) / (2 * a);
if (k2 >= 0 && k2 <= 1) return true;
return false;
}
Another way to view the intersection check is that we're finding the point on the line segment closest to the circle center and then determining whether it's close enough. Since distance to the circle center is a convex function, there are three possibilities: the two endpoints of the segment, and the closest point on the line, assuming that it's on the segment.
To find the closest point on the line, we have an overdetermined linear system
(1 - t) lineX1 + t lineX2 = circleX
(1 - t) lineY1 + t lineY2 = circleY,
expressed as a matrix:
[lineX2 - lineX1] [t] = [circleX - lineX1]
[lineY2 - lineY1] [circleY - lineY1].
The closest point can be found by solving the normal equation
[(lineX2 - lineX1) (lineY2 - lineY1)] [lineX2 - lineX1] [t] =
[lineY2 - lineY1]
[(lineX2 - lineX1) (lineY2 - lineY1)] [circleX - lineX1]
[circleY - lineY1],
expressed alternatively as
((lineX2 - lineX1)^2 + (lineY2 - lineY1)^2) t =
(lineX2 - lineX1) (circleX - lineX1) + (lineY2 - lineY1) (circleY - lineY1),
and solved for t:
(lineX2 - lineX1) (circleX - lineX1) + (lineY2 - lineY1) (circleY - lineY1)
t = ---------------------------------------------------------------------------.
((lineX2 - lineX1)^2 + (lineY2 - lineY1)^2)
Assuming that t is between 0 and 1, we can plug it in and check the distance. When t is out of range, we can clamp it and check only that endpoint.
Untested code:
function lineCircleCollision(circleX, circleY, radius, lineX1, lineY1, lineX2, lineY2) {
circleX -= lineX1;
circleY -= lineY1;
lineX2 -= lineX1;
lineY2 -= lineY1;
var t = (lineX2 * circleX + lineY2 * circleY) / (lineX2 * lineX2 + lineY2 * lineY2);
if (t < 0) t = 0;
else if (t > 1) t = 1;
var deltaX = lineX2 * t - circleX;
var deltaY = lineY2 * t - circleY;
return deltaX * deltaX + deltaY * deltaY <= radius * radius;
}
If you do not need the point just want to know if line intersects then:
compute distance from circle center P0(x0,y0) and line endpoints P1(x1,y1),P2(x2,y2)
double d1=|P1-P0|=sqrt((x1-x0)*(x1-x0)+(y1-x0)*(y1-x0));
double d2=|P2-P0|=sqrt((x2-x0)*(x2-x0)+(y2-x0)*(y2-x0));
order d1,d2 ascending
if (d1>d2) { double d=d1; d1=d2; d2=d; }
check intersection
if ((d1<=r)&&(d2>=r)) return true; else return false;
r is circle radius
[notes]
you do not need sqrt distances
if you leave them un-sqrted then just compare them to r*r instead of r
Related
Generating and solving a maze with no boundaries?
Well, suppose that you try to walk across the "northern edge" of the maze, you'll come back to the same maze but at the "southern edge". Kinda like navigating through a sphere of maze. Would it be possible to generate and solve a maze like that? I have yet to find a documentation on this subject...
The key point is reconceptualizing the maze from a grid of pixels to a graph. You can then connect the graph so it forms a toroid. Wilson's Algorithm might be particularly easy to understand. It's also nice in that it generates a Uniform Spanning Tree, which is a spanning tree drawn uniformly from the set of all possible spanning trees of a space. Perform the following: Choose any vertex at random and add it to the UST. Select any vertex that is not already in the UST and perform a loop-erasing random walk until you encounter a vertex that is in the UST. You can modify this random walk so that when it encounters an edge it will wrap around. Add the vertices and edges touched in the random walk to the UST. Repeat 2 and 3 until all vertices have been added to the UST. Discussions are available here and here. Here's a draft of what the algorithm might look like (the remaining pink cells are an artefact of something in the drawing routine, but don't affect the result). <!DOCTYPE html> <meta charset="utf-8"> <style> body { background: #000; } </style> <body> <script src="//d3js.org/d3.v3.min.js"></script> <script> var width = 200, height = 200; var N = 1 << 0, S = 1 << 1, W = 1 << 2, E = 1 << 3; var cellSize = 4, cellSpacing = 4, cellWidth = Math.floor((width - cellSpacing) / (cellSize + cellSpacing)), cellHeight = Math.floor((height - cellSpacing) / (cellSize + cellSpacing)), cells = new Array(cellWidth * cellHeight), // each cell’s edge bits remaining = d3.range(cellWidth * cellHeight), // cell indexes to visit previous = new Array(cellWidth * cellHeight), // current random walk path i0, x0, y0; // end of current random walk var canvas = d3.select("body").append("canvas") .attr("width", width) .attr("height", height); var context = canvas.node().getContext("2d"); context.translate( Math.round((width - cellWidth * cellSize - (cellWidth + 1) * cellSpacing) / 2), Math.round((height - cellHeight * cellSize - (cellHeight + 1) * cellSpacing) / 2) ); // Add the starting cell. context.fillStyle = "white"; var start = remaining.pop(); cells[start] = 0; fillCell(start); // While there are remaining cells, // add a loop-erased random walk to the maze. context.fillStyle = "magenta"; d3.timer(function() { for (var k = 0; k < 50; ++k) { if (loopErasedRandomWalk()) { return true; } } }); function loopErasedRandomWalk() { var i1; // Pick a location that’s not yet in the maze (if any). if (i0 == null) { do if ((i0 = remaining.pop()) == null) return true; while (cells[i0] >= 0); previous[i0] = i0; fillCell(i0); x0 = i0 % cellWidth; y0 = i0 / cellWidth | 0; return; } // Perform a random walk starting at this location, // by picking a legal random direction. i1 = Math.random() * 4 | 0; if (i1 === 0) { if (y0 <= 0){ y0 = cellHeight-1; i1 = i0 - cellWidth + cellWidth*cellHeight; } else{ --y0; i1 = i0 - cellWidth; } } else if (i1 === 1) { if (y0 >= cellHeight - 1){ y0 = 0; i1 = i0 + cellWidth - cellWidth*cellHeight; } else { ++y0; i1 = i0 + cellWidth; } } else if (i1 === 2) { if (x0 <= 0){ x0 = cellWidth-1; i1 = i0+cellWidth-1; } else { --x0; i1 = i0 - 1; } } else { if (x0 >= cellWidth - 1) { x0 = 0; i1 = i0-cellWidth+1; } else { ++x0; i1 = i0 + 1; } } // If this new cell was visited previously during this walk, // erase the loop, rewinding the path to its earlier state. if (previous[i1] >= 0) eraseWalk(i0, i1); // Otherwise, just add it to the walk. else { previous[i1] = i0; fillCell(i1); if (i1 === i0 - 1) fillEast(i1); else if (i1 === i0 + 1) fillEast(i0); else if (i1 === i0 - cellWidth) fillSouth(i1); else fillSouth(i0); } // If this cell is part of the maze, we’re done walking. if (cells[i1] >= 0) { // Add the random walk to the maze by backtracking to the starting cell. // Also erase this walk’s history to not interfere with subsequent walks. context.save(); context.fillStyle = "#fff"; fillCell(i1); while ((i0 = previous[i1]) !== i1) { fillCell(i0); if (i1 === i0 + 1) cells[i0] |= E, cells[i1] |= W, fillEast(i0); else if (i1 === i0 - 1) cells[i0] |= W, cells[i1] |= E, fillEast(i1); else if (i1 === i0 + cellWidth) cells[i0] |= S, cells[i1] |= N, fillSouth(i0); else cells[i0] |= N, cells[i1] |= S, fillSouth(i1); previous[i1] = NaN; i1 = i0; } context.restore(); previous[i1] = NaN; i0 = null; } else { i0 = i1; } } function eraseWalk(i0, i2) { var i1; context.save(); context.globalCompositeOperation = "destination-out"; do { i1 = previous[i0]; if (i1 === i0 - 1) fillEast(i1); else if (i1 === i0 + 1) fillEast(i0); else if (i1 === i0 - cellWidth) fillSouth(i1); else fillSouth(i0); fillCell(i0); previous[i0] = NaN; i0 = i1; } while (i1 !== i2); context.restore(); } function fillCell(i) { var x = i % cellWidth, y = i / cellWidth | 0; context.fillRect(x * cellSize + (x + 1) * cellSpacing, y * cellSize + (y + 1) * cellSpacing, cellSize, cellSize); } function fillEast(i) { var x = i % cellWidth, y = i / cellWidth | 0; context.fillRect((x + 1) * (cellSize + cellSpacing), y * cellSize + (y + 1) * cellSpacing, cellSpacing, cellSize); } function fillSouth(i) { var x = i % cellWidth, y = i / cellWidth | 0; context.fillRect(x * cellSize + (x + 1) * cellSpacing, (y + 1) * (cellSize + cellSpacing), cellSize, cellSpacing); } d3.select(self.frameElement).style("height", height + "px"); </script>
How to convert polyline or polygon into chain-code?
I have an open or closed polyline (polygon) consisted from the set of 2D points. I need to represent that polyline as a chain-code. If I correctly understand, I need to rasterize polyline segments using Bresenham's algorithm and construct chain-code form that raster. But is there a better algorithm? What is the optimal algorithms for converting polyline (polygon) into chain-code?
Yes, it will be significantly faster to simply draw the points directly into the Fourier transform. Skip the raster and making a chaincode from that, after all you need the points correctly in order in the direction the polyline takes for what I assume is Kuhl-Giardiana 1982 algorithmic use. You want all the pixels in the correct order, you can directly get that by drawing the pixels into the algorithm itself rather than rasterizing anything. In fact, this would basically skip the chain code and the raster. All lines are going to be of the form y = mx+b and the fastest way to do this is going to be Bresenham's. Though, depending on the eventual use you might opt for Wu's algorithm so that you can be sure to include anti-aliasing, which tends to make the lines look sharper (and requires you save the alpha). Assuming you need the chain code for something specific, yes you need the actual pixels that that line will produce which means using a line drawing algorithm. Most of your drawing apis will give you the rasterized image rather than chain-code. There is the option of drawing the polyline on to an aptly sized white image in black and going through the entire image and listing every black pixel. It would be easy to code, though slow and unneeded and in mission critical operations would be a non-starter. The code is going to be pretty easy, just do bresenham and then toss the points where it would add a point into the chaincode. public void plotLines(int[] twodshape, Chaincode chain) { for (int i = 0, s = twodshape.length-4; i < s; i+=2) { plotLine(twodshape[i],twodshape[i+1],twodshape[i+2],twodshape[i+3],chain); } } public void plotLine(int x0, int y0, int x1, int y1, Chaincode chain) { int dy = y1 - y0; //BRESENHAM LINE DRAW ALGORITHM int dx = x1 - x0; int stepx, stepy; if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; } if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; } if (dx > dy) { dy <<= 1; // dy is now 2*dy dx <<= 1; int fraction = dy - (dx >> 1); // same as 2*dy - dx chain.add(x0,y0); while (x0 != x1) { if (fraction >= 0) { y0 += stepy; fraction -= dx; // same as fraction -= 2*dx } x0 += stepx; fraction += dy; // same as fraction += 2*dy chain.add(x0,y0); } chain.add(x0,y0); } else { dy <<= 1; // dy is now 2*dy dx <<= 1; // dx is now 2*dx int fraction = dx - (dy >> 1); chain.add(x0,y0); while (y0 != y1) { if (fraction >= 0) { x0 += stepx; fraction -= dy; } y0 += stepy; fraction += dx; chain.add(x0,y0); } chain.add(x0,y0); } } Update: I removed the recursive bit, I needed that for a specific issue with lines being drawn from point A to point B not being guaranteed to be the same from B to A. Due to the rounding of the slope. For example if you are going up 1 pixel and right 5. There are two equally valid ways of doing this, and it wasn't giving me a consistent answer. If you deeply need it in chaincode: public int convertToChaincode(int cx, int cy) { if ((cx == 1) && (cy == 0)) return 0; if ((cx == 1) && (cy == 1)) return 1; if ((cx == 0) && (cy == 1)) return 2; if ((cx == -1) && (cy == 1)) return 3; if ((cx == -1) && (cy == 0)) return 4; if ((cx == -1) && (cy == -1)) return 5; if ((cx == 0) && (cy == -1)) return 6; if ((cx == 1) && (cy == -1)) return 7; return -1; //error. } public void plotLine(int x0, int y0, int x1, int y1, ChainCode chain) { int dy = y1 - y0; //BRESENHAM LINE DRAW ALGORITHM int dx = x1 - x0; int stepx, stepy; int cx = 0; int cy = 0; if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; } if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; } if (dx > dy) { dy <<= 1; // dy is now 2*dy dx <<= 1; int fraction = dy - (dx >> 1); // same as 2*dy - dx //typically set start point. while (x0 != x1) { if (fraction >= 0) { y0 += stepy; cy = stepy; fraction -= dx; // same as fraction -= 2*dx } x0 += stepx; cx = stepx; fraction += dy; // same as fraction += 2*dy chain.add(convertToChaincode(cx,cy)); } } else { dy <<= 1; // dy is now 2*dy dx <<= 1; // dx is now 2*dx int fraction = dx - (dy >> 1); //typically set start point while (y0 != y1) { if (fraction >= 0) { x0 += stepx; cx = stepx; fraction -= dy; } y0 += stepy; cy = stepy; fraction += dx; chain.add(convertToChaincode(cx,cy)); } } }
algorithm to find a certain lat long position in a polygon enclosed by lat/long postions or not
I have a set of polygons drawn on google map,now I want to implement the algo that given a postion with certain lat/long in which of the polygon it lies. Note:polygons are also drawn given the lat/long positions using the google maps api So Is there any api for it or how can i convert lat/long positions to x-y planes so that i can check if a given point lies in which area using area formulas?
would google.maps.geometry.poly.containsLocation work ?
use below ray casting alogrithm that may help u to resolve the problem google.maps.Polygon.prototype.Contains = function(point) { // ray casting alogrithm var crossings = 0, path = this.getPath(); // for each edge for (var i = 0; i < path.getLength(); i++) { var a = path.getAt(i), j = i + 1; if (j >= path.getLength()) { j = 0; } var b = path.getAt(j); if (rayCrossesSegment(point, a, b)) { crossings++; } } // odd number of crossings? return (crossings % 2 == 1); function rayCrossesSegment(point, a, b) { var px = point.lng(), py = point.lat(), ax = a.lng(), ay = a.lat(), bx = b.lng(), by = b.lat(); if (ay > by) { ax = b.lng(); ay = b.lat(); bx = a.lng(); by = a.lat(); } // alter longitude to cater for 180 degree crossings if (px < 0) { px += 360 }; if (ax < 0) { ax += 360 }; if (bx < 0) { bx += 360 }; if (py == ay || py == by) py += 0.00000001; if ((py > by || py < ay) || (px > Math.max(ax, bx))) return false; if (px < Math.min(ax, bx)) return true; var red = (ax != bx) ? ((by - ay) / (bx - ax)) : Infinity; var blue = (ax != px) ? ((py - ay) / (px - ax)) : Infinity; return (blue >= red); } };
Is there a name for this sampling algorithm used in Minicraft?
For Ludum Dare 22, Notch programmed a game in 48 hours called Minicraft. It's like a 2D minecraft. Anyway the source is available (here: http://www.ludumdare.com/compo/ludum-dare-22/?action=preview&uid=398 ), and I was taking a look since I am interested in random generation of terrain and levels. In the code is a block of code which runs the core generation, and the algorithm to me seems familiar, but I can't put a name to it. I'd like to know exactly what it is so I can read more about it and learn how it works. Specifically, the code is from levelGen.java: do { int halfStep = stepSize / 2; for (int y = 0; y < w; y += stepSize) { for (int x = 0; x < w; x += stepSize) { double a = sample(x, y); double b = sample(x + stepSize, y); double c = sample(x, y + stepSize); double d = sample(x + stepSize, y + stepSize); double e = (a + b + c + d) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale; setSample(x + halfStep, y + halfStep, e); } } for (int y = 0; y < w; y += stepSize) { for (int x = 0; x < w; x += stepSize) { double a = sample(x, y); double b = sample(x + stepSize, y); double c = sample(x, y + stepSize); double d = sample(x + halfStep, y + halfStep); double e = sample(x + halfStep, y - halfStep); double f = sample(x - halfStep, y + halfStep); double H = (a + b + d + e) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5; double g = (a + c + d + f) / 4.0 + (random.nextFloat() * 2 - 1) * stepSize * scale * 0.5; setSample(x + halfStep, y, H); setSample(x, y + halfStep, g); } } stepSize /= 2; scale *= (scaleMod + 0.8); scaleMod *= 0.3; } while (stepSize > 1); Those two for loops are running some kind of sampling algorithm, and I would just like to know if this is known named algorithm, or if notch just rolled his own.
This looks like the diamond-square algorithm.
An algorithm to find bounding box of closed bezier curves?
I'm looking for an algorithm to find bounding box (max/min points) of a closed quadratic bezier curve in Cartesian axis: input: C (a closed bezier curve) output: A B C D points Image http://www.imagechicken.com/uploads/1270586513022388700.jpg Note: above image shows a smooth curve. it could be not smooth. (have corners)
Ivan Kuckir's DeCasteljau is a brute force, but works in many cases. The problem with it is the count of iterations. The actual shape and the distance between coordinates affect to the precision of the result. And to find a precise enough answer, you have to iterate tens of times, may be more. And it may fail if there are sharp turns in curve. Better solution is to find first derivative roots, as is described on the excellent site http://processingjs.nihongoresources.com/bezierinfo/. Please read the section Finding the extremities of the curves. The link above has the algorithm for both quadratic and cubic curves. The asker of question is interested in quadratic curves, so the rest of this answer may be irrelevant, because I provide codes for calculating extremities of Cubic curves. Below are three Javascript codes of which the first (CODE 1) is the one I suggest to use. ** CODE 1 ** After testing processingjs and Raphael's solutions I find they had some restrictions and/or bugs. Then more search and found Bonsai and it's bounding box function, which is based on NISHIO Hirokazu's Python script. Both have a downside where double equality is tested using ==. When I changed these to numerically robust comparisons, then script succeeds 100% right in all cases. I tested the script with thousands of random paths and also with all collinear cases and all succeeded: Various cubic curves Random cubic curves Collinear cubic curves The code is as follows. Usually left, right, top and bottom values are the all needed, but in some cases it's fine to know the coordinates of local extreme points and corresponding t values. So I added there two variables: tvalues and points. Remove code regarding them and you have fast and stable bounding box calculation function. // Source: http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html // Original version: NISHIO Hirokazu // Modifications: Timo var pow = Math.pow, sqrt = Math.sqrt, min = Math.min, max = Math.max; abs = Math.abs; function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { var tvalues = new Array(); var bounds = [new Array(), new Array()]; var points = new Array(); var a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (abs(a) < 1e-12) // Numerical robustness { if (abs(b) < 1e-12) // Numerical robustness { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; sqrtb2ac = sqrt(b2ac); if (b2ac < 0) { continue; } t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var x, y, j = tvalues.length, jlen = j, mt; while (j--) { t = tvalues[j]; mt = 1 - t; x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); bounds[0][j] = x; y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); bounds[1][j] = y; points[j] = { X: x, Y: y }; } tvalues[jlen] = 0; tvalues[jlen + 1] = 1; points[jlen] = { X: x0, Y: y0 }; points[jlen + 1] = { X: x3, Y: y3 }; bounds[0][jlen] = x0; bounds[1][jlen] = y0; bounds[0][jlen + 1] = x3; bounds[1][jlen + 1] = y3; tvalues.length = bounds[0].length = bounds[1].length = points.length = jlen + 2; return { left: min.apply(null, bounds[0]), top: min.apply(null, bounds[1]), right: max.apply(null, bounds[0]), bottom: max.apply(null, bounds[1]), points: points, // local extremes tvalues: tvalues // t values of local extremes }; }; // Usage: var bounds = getBoundsOfCurve(532,333,117,305,28,93,265,42); console.log(JSON.stringify(bounds)); // Prints: {"left":135.77684049079755,"top":42,"right":532,"bottom":333,"points":[{"X":135.77684049079755,"Y":144.86387466397255},{"X":532,"Y":333},{"X":265,"Y":42}],"tvalues":[0.6365030674846626,0,1]} CODE 2 (which fails in collinear cases): I translated the code from http://processingjs.nihongoresources.com/bezierinfo/sketchsource.php?sketch=tightBoundsCubicBezier to Javascript. The code works fine in normal cases, but not in collinear cases where all points lie on the same line. For reference, here is the Javascript code. function computeCubicBaseValue(a,b,c,d,t) { var mt = 1-t; return mt*mt*mt*a + 3*mt*mt*t*b + 3*mt*t*t*c + t*t*t*d; } function computeCubicFirstDerivativeRoots(a,b,c,d) { var ret = [-1,-1]; var tl = -a+2*b-c; var tr = -Math.sqrt(-a*(c-d) + b*b - b*(c+d) +c*c); var dn = -a+3*b-3*c+d; if(dn!=0) { ret[0] = (tl+tr)/dn; ret[1] = (tl-tr)/dn; } return ret; } function computeCubicBoundingBox(xa,ya,xb,yb,xc,yc,xd,yd) { // find the zero point for x and y in the derivatives var minx = 9999; var maxx = -9999; if(xa<minx) { minx=xa; } if(xa>maxx) { maxx=xa; } if(xd<minx) { minx=xd; } if(xd>maxx) { maxx=xd; } var ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd); for(var i=0; i<ts.length;i++) { var t = ts[i]; if(t>=0 && t<=1) { var x = computeCubicBaseValue(t, xa, xb, xc, xd); var y = computeCubicBaseValue(t, ya, yb, yc, yd); if(x<minx) { minx=x; } if(x>maxx) { maxx=x; }}} var miny = 9999; var maxy = -9999; if(ya<miny) { miny=ya; } if(ya>maxy) { maxy=ya; } if(yd<miny) { miny=yd; } if(yd>maxy) { maxy=yd; } ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd); for(i=0; i<ts.length;i++) { var t = ts[i]; if(t>=0 && t<=1) { var x = computeCubicBaseValue(t, xa, xb, xc, xd); var y = computeCubicBaseValue(t, ya, yb, yc, yd); if(y<miny) { miny=y; } if(y>maxy) { maxy=y; }}} // bounding box corner coordinates var bbox = [minx,miny, maxx,miny, maxx,maxy, minx,maxy ]; return bbox; } CODE 3 (works in most cases): To handle also collinear cases, I found Raphael's solution, which is based on the same first derivative method as the CODE 2. I added also a return value dots, which has the extrema points, because always it's not enough to know bounding boxes min and max coordinates, but we want to know the exact extrema coordinates. EDIT: found another bug. Fails eg. in 532,333,117,305,28,93,265,42 and also many other cases. The code is here: Array.max = function( array ){ return Math.max.apply( Math, array ); }; Array.min = function( array ){ return Math.min.apply( Math, array ); }; var findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) { var t1 = 1 - t; return { x: t1*t1*t1*p1x + t1*t1*3*t*c1x + t1*3*t*t * c2x + t*t*t * p2x, y: t1*t1*t1*p1y + t1*t1*3*t*c1y + t1*3*t*t * c2y + t*t*t * p2y }; }; var cubicBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x), b = 2 * (c1x - p1x) - 2 * (c2x - c1x), c = p1x - c1x, t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a, t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a, y = [p1y, p2y], x = [p1x, p2x], dot, dots=[]; Math.abs(t1) > "1e12" && (t1 = 0.5); Math.abs(t2) > "1e12" && (t2 = 0.5); if (t1 >= 0 && t1 <= 1) { dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); x.push(dot.x); y.push(dot.y); dots.push({X:dot.x, Y:dot.y}); } if (t2 >= 0 && t2 <= 1) { dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); x.push(dot.x); y.push(dot.y); dots.push({X:dot.x, Y:dot.y}); } a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y); b = 2 * (c1y - p1y) - 2 * (c2y - c1y); c = p1y - c1y; t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a; t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a; Math.abs(t1) > "1e12" && (t1 = 0.5); Math.abs(t2) > "1e12" && (t2 = 0.5); if (t1 >= 0 && t1 <= 1) { dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1); x.push(dot.x); y.push(dot.y); dots.push({X:dot.x, Y:dot.y}); } if (t2 >= 0 && t2 <= 1) { dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2); x.push(dot.x); y.push(dot.y); dots.push({X:dot.x, Y:dot.y}); } // remove duplicate dots var dots2 = []; var l = dots.length; for(var i=0; i<l; i++) { for(var j=i+1; j<l; j++) { if (dots[i].X === dots[j].X && dots[i].Y === dots[j].Y) j = ++i; } dots2.push({X: dots[i].X, Y: dots[i].Y}); } return { min: {x: Array.min(x), y: Array.min(y)}, max: {x: Array.max(x), y: Array.max(y)}, dots: dots2 // these are the extrema points }; };
Well, I would say you start by adding all endpoints to your bounding box. Then, you go through all the bezier elements. I assume the formula in question is this one: From this, extract two formulas for X and Y, respectively. Test both for extrema by taking the derivative (zero crossings). Then add the corresponding points to your bounding box as well.
Use De Casteljau algorithm to approximate the curve of higher orders. Here is how it works for cubic curve http://jsfiddle.net/4VCVX/25/ function getCurveBounds(ax, ay, bx, by, cx, cy, dx, dy) { var px, py, qx, qy, rx, ry, sx, sy, tx, ty, tobx, toby, tocx, tocy, todx, tody, toqx, toqy, torx, tory, totx, toty; var x, y, minx, miny, maxx, maxy; minx = miny = Number.POSITIVE_INFINITY; maxx = maxy = Number.NEGATIVE_INFINITY; tobx = bx - ax; toby = by - ay; // directions tocx = cx - bx; tocy = cy - by; todx = dx - cx; tody = dy - cy; var step = 1/40; // precision for(var d=0; d<1.001; d+=step) { px = ax +d*tobx; py = ay +d*toby; qx = bx +d*tocx; qy = by +d*tocy; rx = cx +d*todx; ry = cy +d*tody; toqx = qx - px; toqy = qy - py; torx = rx - qx; tory = ry - qy; sx = px +d*toqx; sy = py +d*toqy; tx = qx +d*torx; ty = qy +d*tory; totx = tx - sx; toty = ty - sy; x = sx + d*totx; y = sy + d*toty; minx = Math.min(minx, x); miny = Math.min(miny, y); maxx = Math.max(maxx, x); maxy = Math.max(maxy, y); } return {x:minx, y:miny, width:maxx-minx, height:maxy-miny}; }
I believe that the control points of a Bezier curve form a convex hull that encloses the curve. If you just want a axis-aligned bounding box, I think you need to find the min and max of each (x, y) for each control point of all the segments. I suppose that might not be a tight box. That is, the box might be slightly larger than it needs to be, but it's simple and fast to compute. I guess it depends on your requirements.
I think the accepted answer is fine, but just wanted to offer a little more explanation for anyone else trying to do this. Consider a quadratic Bezier with starting point p1, ending point p2 and "control point" pc. This curve has three parametric equations: pa(t) = p1 + t(pc-p1) pb(t) = pc + t(p2-pc) p(t) = pa(t) + t*(pb(t) - pa(t)) In all cases, t runs from 0 to 1, inclusive. The first two are linear, defining line segments from p1 to pc and from pc to p2, respectively. The third is quadratic once you substitute in the expressions for pa(t) and pb(t); this is the one that actually defines points on the curve. Actually, each of these equations is a pair of equations, one for the horizontal dimension, and one for the vertical. The nice thing about parametric curves is that the x and y can be handled independently of one another. The equations are exactly the same, just substitute x or y for p in the above equations. The important point is that the line segment defined in equation 3, that runs from pa(t) to pb(t) for a specific value of t is tangent to the curve at the corresponding point p(t). To find the local extrema of the curve, you need to find the parameter value where the tangent is flat (i.e., a critical point). For the vertical dimension, you want to find the value of t such that ya(t) = yb(t), which gives the tangent a slope of 0. For the horizontal dimension, find t such that xa(t) = xb(t), which gives the tangent an infinite slope (i.e., a vertical line). In each case, you can just plug the value of t back into equation 1 (or 2, or even 3) to get the location of that extrema. In other words, to find the vertical extrema of the curve, take just the y-component of equations 1 and 2, set them equal to each other and solve for t; plug this back into the y-component of equation 1, to get the y-value of that extrema. To get the complete y-range of the curve, find the minimum of this extreme y value and the y-components of the two end points, and likewise find the maximum of all three. Repeat for x to get the horizontal limits. Remember that t only runs in [0, 1], so if you get a value outside of this range, it means there is no local extrema on the curve (at least not between your two endpoints). This includes the case where you end up dividing by zero when solving for t, which you will probably need to check for before you do it. The same idea can be applied to higher-order Beziers, there are just more equations of higher degree, which also means there are potentially more local extrema per curve. For instance, on a cubic Bezier (two control points), solving for t to find the local extrema is a quadratic equation, so you could get 0, 1, or 2 values (remember to check for 0-denominators, and for negative square-roots, both of which indicate that there are no local extrema for that dimension). To find the range, you just need to find the min/max of all the local extrema, and the two end points.
I answered this question in Calculating the bounding box of cubic bezier curve this article explain the details and also has a live html5 demo: Calculating / Computing the Bounding Box of Cubic Bezier I found a javascript in Snap.svg to calculate that: here see the bezierBBox and curveDim functions. I rewrite a javascript function. //(x0,y0) is start point; (x1,y1),(x2,y2) is control points; (x3,y3) is end point. function bezierMinMax(x0, y0, x1, y1, x2, y2, x3, y3) { var tvalues = [], xvalues = [], yvalues = [], a, b, c, t, t1, t2, b2ac, sqrtb2ac; for (var i = 0; i < 2; ++i) { if (i == 0) { b = 6 * x0 - 12 * x1 + 6 * x2; a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; c = 3 * x1 - 3 * x0; } else { b = 6 * y0 - 12 * y1 + 6 * y2; a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; c = 3 * y1 - 3 * y0; } if (Math.abs(a) < 1e-12) { if (Math.abs(b) < 1e-12) { continue; } t = -c / b; if (0 < t && t < 1) { tvalues.push(t); } continue; } b2ac = b * b - 4 * c * a; if (b2ac < 0) { continue; } sqrtb2ac = Math.sqrt(b2ac); t1 = (-b + sqrtb2ac) / (2 * a); if (0 < t1 && t1 < 1) { tvalues.push(t1); } t2 = (-b - sqrtb2ac) / (2 * a); if (0 < t2 && t2 < 1) { tvalues.push(t2); } } var j = tvalues.length, mt; while (j--) { t = tvalues[j]; mt = 1 - t; xvalues[j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); yvalues[j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); } xvalues.push(x0,x3); yvalues.push(y0,y3); return { min: {x: Math.min.apply(0, xvalues), y: Math.min.apply(0, yvalues)}, max: {x: Math.max.apply(0, xvalues), y: Math.max.apply(0, yvalues)} }; }
Timo-s first variant adapted to Objective-C CGPoint CubicBezierPointAt(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4, CGFloat t) { CGFloat x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t); CGFloat y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t); return CGPointMake(x, y); } // array containing TopLeft and BottomRight points for curve`s enclosing bounds NSArray* CubicBezierExtremums(CGPoint p1, CGPoint p2, CGPoint p3, CGPoint p4) { CGFloat a, b, c, t, t1, t2, b2ac, sqrtb2ac; NSMutableArray *tValues = [NSMutableArray new]; for (int i = 0; i < 2; i++) { if (i == 0) { a = 3 * (-p1.x + 3 * p2.x - 3 * p3.x + p4.x); b = 6 * (p1.x - 2 * p2.x + p3.x); c = 3 * (p2.x - p1.x); } else { a = 3 * (-p1.y + 3 * p2.y - 3 * p3.y + p4.y); b = 6 * (p1.y - 2 * p2.y + p3.y); c = 3 * (p2.y - p1.y); } if(ABS(a) < CGFLOAT_MIN) {// Numerical robustness if (ABS(b) < CGFLOAT_MIN) {// Numerical robustness continue; } t = -c / b; if (t > 0 && t < 1) { [tValues addObject:[NSNumber numberWithDouble:t]]; } continue; } b2ac = pow(b, 2) - 4 * c * a; if (b2ac < 0) { continue; } sqrtb2ac = sqrt(b2ac); t1 = (-b + sqrtb2ac) / (2 * a); if (t1 > 0.0 && t1 < 1.0) { [tValues addObject:[NSNumber numberWithDouble:t1]]; } t2 = (-b - sqrtb2ac) / (2 * a); if (t2 > 0.0 && t2 < 1.0) { [tValues addObject:[NSNumber numberWithDouble:t2]]; } } int j = (int)tValues.count; CGFloat x = 0; CGFloat y = 0; NSMutableArray *xValues = [NSMutableArray new]; NSMutableArray *yValues = [NSMutableArray new]; while (j--) { t = [[tValues objectAtIndex:j] doubleValue]; x = CubicBezier(p1.x, p2.x, p3.x, p4.x, t); y = CubicBezier(p1.y, p2.y, p3.y, p4.y, t); [xValues addObject:[NSNumber numberWithDouble:x]]; [yValues addObject:[NSNumber numberWithDouble:y]]; } [xValues addObject:[NSNumber numberWithDouble:p1.x]]; [xValues addObject:[NSNumber numberWithDouble:p4.x]]; [yValues addObject:[NSNumber numberWithDouble:p1.y]]; [yValues addObject:[NSNumber numberWithDouble:p4.y]]; //find minX, minY, maxX, maxY CGFloat minX = [[xValues valueForKeyPath:#"#min.self"] doubleValue]; CGFloat minY = [[yValues valueForKeyPath:#"#min.self"] doubleValue]; CGFloat maxX = [[xValues valueForKeyPath:#"#max.self"] doubleValue]; CGFloat maxY = [[yValues valueForKeyPath:#"#max.self"] doubleValue]; CGPoint origin = CGPointMake(minX, minY); CGPoint bottomRight = CGPointMake(maxX, maxY); NSArray *toReturn = [NSArray arrayWithObjects: [NSValue valueWithCGPoint:origin], [NSValue valueWithCGPoint:bottomRight], nil]; return toReturn; }
Timo's CODE 2 answer has a small bug: the t parameter in computeCubicBaseValue function should be last. Nevertheless good job, works like a charm ;) Solution in C# : double computeCubicBaseValue(double a, double b, double c, double d, double t) { var mt = 1 - t; return mt * mt * mt * a + 3 * mt * mt * t * b + 3 * mt * t * t * c + t * t * t * d; } double[] computeCubicFirstDerivativeRoots(double a, double b, double c, double d) { var ret = new double[2] { -1, -1 }; var tl = -a + 2 * b - c; var tr = -Math.Sqrt(-a * (c - d) + b * b - b * (c + d) + c * c); var dn = -a + 3 * b - 3 * c + d; if (dn != 0) { ret[0] = (tl + tr) / dn; ret[1] = (tl - tr) / dn; } return ret; } public double[] ComputeCubicBoundingBox(Point start, Point firstControl, Point secondControl, Point end) { double xa, ya, xb, yb, xc, yc, xd, yd; xa = start.X; ya = start.Y; xb = firstControl.X; yb = firstControl.Y; xc = secondControl.X; yc = secondControl.Y; xd = end.X; yd = end.Y; // find the zero point for x and y in the derivatives double minx = Double.MaxValue; double maxx = Double.MinValue; if (xa < minx) { minx = xa; } if (xa > maxx) { maxx = xa; } if (xd < minx) { minx = xd; } if (xd > maxx) { maxx = xd; } var ts = computeCubicFirstDerivativeRoots(xa, xb, xc, xd); for (var i = 0; i < ts.Length; i++) { var t = ts[i]; if (t >= 0 && t <= 1) { var x = computeCubicBaseValue(xa, xb, xc, xd,t); var y = computeCubicBaseValue(ya, yb, yc, yd,t); if (x < minx) { minx = x; } if (x > maxx) { maxx = x; } } } double miny = Double.MaxValue; double maxy = Double.MinValue; if (ya < miny) { miny = ya; } if (ya > maxy) { maxy = ya; } if (yd < miny) { miny = yd; } if (yd > maxy) { maxy = yd; } ts = computeCubicFirstDerivativeRoots(ya, yb, yc, yd); for (var i = 0; i < ts.Length; i++) { var t = ts[i]; if (t >= 0 && t <= 1) { var x = computeCubicBaseValue(xa, xb, xc, xd,t); var y = computeCubicBaseValue(ya, yb, yc, yd,t); if (y < miny) { miny = y; } if (y > maxy) { maxy = y; } } } // bounding box corner coordinates var bbox = new double[] { minx, miny, maxx, maxy}; return bbox; }