Generating and solving a maze with no boundaries? - algorithm

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>

Related

How to simplify shapes for triangulation with three.js and jsclipper

I try to display geometry which is constructed by constructpath commands like moveto lineto beziercurveto in Three.js.
Therefore I create a THREE.ShapePath(); and execute the command toShapes(isClockwise).
After this I use THREE.ExtrudeBufferGeometry to create the 3D shape.
Unfortunately the shapes are sometimes really complex and are not created correctly which means they are distorted.
Using libtess as triangulation library solves some issues. But I have still distorted geometry.
Now I want to use jsclipper to simplify the shapes prior triangulation.
I modified three.js in such way:
in the method addShape in ExtrudeBufferGeometry I have added:
$.each(vertices, function(index, item) {
vertices[index]['X'] = vertices[index]['x'];
vertices[index]['Y'] = vertices[index]['y'];
delete vertices[index]['x'];
delete vertices[index]['y'];
});
if (holes[0]) {
for (i = 0; i < holes.length; i++ ) {
$.each(holes[i], function(index, item) {
holes[i][index]['X'] = holes[i][index]['x'];
holes[i][index]['Y'] = holes[i][index]['y'];
delete holes[i][index]['x'];
delete holes[i][index]['y'];
});
}
}
var scale = 100;
ClipperLib.JS.ScaleUpPaths([vertices], scale);
if (holes[0]) {
ClipperLib.JS.ScaleUpPaths(holes, scale);
}
vertices = ClipperLib.Clipper.SimplifyPolygons([vertices], ClipperLib.PolyFillType.pftNonZero);
// or ClipperLib.PolyFillType.pftEvenOdd
if (holes[0]) {
holes = ClipperLib.Clipper.SimplifyPolygons(holes, ClipperLib.PolyFillType.pftNonZero);
// or ClipperLib.PolyFillType.pftEvenOdd
}
// var cleandelta = 0.1; // 0.1 should be the appropriate delta in different cases
// vertices = ClipperLib.Clipper.CleanPolygons([vertices], cleandelta * scale);
// if (holes[0]) {
// holes = ClipperLib.Clipper.CleanPolygons(holes, cleandelta * scale);
// }
ClipperLib.JS.ScaleDownPaths(vertices, scale);
if (holes[0]) {
ClipperLib.JS.ScaleDownPaths(holes, scale);
}
for (i = 0; i < vertices.length; i++ ) {
$.each(vertices[i], function(index, item) {
vertices[i][index]['x'] = vertices[i][index]['X'];
vertices[i][index]['y'] = vertices[i][index]['Y'];
delete vertices[i][index]['X'];
delete vertices[i][index]['Y'];
});
}
if (holes[0]) {
for (i = 0; i < holes.length; i++ ) {
$.each(holes[i], function(index, item) {
holes[i][index]['x'] = holes[i][index]['X'];
holes[i][index]['y'] = holes[i][index]['Y'];
delete holes[i][index]['X'];
delete holes[i][index]['Y'];
});
}
}
Now I can see that the vertices are "reduced".
But var faces = ShapeUtils.triangulateShape( vertices, holes ); doesn't generate faces for some examples anymore.
Please can one help how to simplify the shapes correctly?
A bit hard to figure out what the problem is actually. Clipper (also when using SimplifyPolygons or SimplifyPolygon) can only produce weakly-simple polygons, which means that there can be pseudo-duplicate points: although sequential coordinates are quaranteed to be not indentical, some of the next points can share the same coordinate. Also a coordinate can be on the line between two points.
After simplifying (or any other boolean operation) you could make a cleaning step using Offsetting with a small negative value: https://sourceforge.net/p/jsclipper/wiki/documentation/#clipperlibclipperoffsetexecute.
This possibly removes all of the pseudo-duplicate points.
I have made also a float version of Clipper (http://jsclipper.sourceforge.net/6.4.2.2_fpoint/). It is extensively tested, but because Angus Johnson, the author of the original C# Clipper (of which JS-version is ported from), has thought that using floats causes robustness problems although according to my tests the are no such, the original C# float version does not exists. The float version is simpler to use and you can try there a small negative offset: eg. -0.001 or -0.01.
You could also give a try to PolyTree or ExPolygons (https://sourceforge.net/p/jsclipper/wiki/ExPolygons%20and%20PolyTree%206/). ExPolygons can be used to get holes and contours and PolyTree can be used to get the full parent-child-relationship of holes and contours.
The last resort is a broken-pen-nib -function. It detects all pseudo-duplicate points and make a broken-pen-nib -effect to them, so that the result is free of any duplicates. The attached images shows what this effect means using large nib-effect-value to make the effect meaning clearer. Three.js polygon triangulation fails in pseudo duplicate points. There are a discussion https://github.com/mrdoob/three.js/issues/3386 of this subject.
// Make polygons to simple by making "a broken pen tip" effect on each semi-adjacent (duplicate) vertex
// ORIGPOLY can be a contour
// or exPolygon structure
function BreakPenNibs(ORIGPOLY, dist, scale)
{
if (!dist || dist < 0) return;
var sqrt = Math.sqrt;
var allpoints = {}, point = {};
var key = "";
var currX = 0.0,
currY = 0.0;
var prevX = 0.0,
prevY = 0.0;
var nextX = 0.0,
nextY;
var x = 0.0,
y = 0.0,
length = 0.0,
i = 0,
duplcount = 0,
j = 0;
var prev_i = 0,
next_i = 0,
last_i;
var extra_vertices = new Array(100),
moved_vertices = new Array(100);
// Get first all duplicates
var duplicates = new Array(100),
indexi = "",
indexstr = "",
arraystr = "",
polys, outer, holes;
if (ORIGPOLY instanceof Array)
{
outer = ORIGPOLY;
}
else if (ORIGPOLY.outer instanceof Array)
{
outer = ORIGPOLY.outer;
}
else return;
if (ORIGPOLY.holes instanceof Array) holes = ORIGPOLY.holes;
else holes = [];
polys = [outer].concat(holes);
var polys_length = polys.length;
// Get first max lenght of arrays
var max_index_len = 0;
var arr_len;
i = polys_length;
while (i--)
{
arr_len = polys[i].length;
if (arr_len > max_index_len) max_index_len = arr_len;
}
max_index_len = max_index_len.toString().length;
var max_polys_length = polys_length.toString().length;
var poly;
j = polys_length;
var scaling = scale/10;
while (j--)
{
poly = polys[j];
ilen = poly.length;
i = ilen;
while (i--)
{
point = poly[i];
//key = Math.round(point.X) + ":" + Math.round(point.Y);
key = (Math.round(point.X / scaling) * scaling)
+ ":" + (Math.round(point.Y / scaling) * scaling);
indexi = allpoints[key];
if (typeof (indexi) != "undefined")
{
// first found duplicate
duplicates[duplcount] = indexi;
duplcount++;
arraystr = j.toString();
while (arraystr.length < max_polys_length) arraystr = "0" + arraystr;
indexstr = i.toString();
while (indexstr.length < max_index_len) indexstr = "0" + indexstr;
duplicates[duplcount] = arraystr + "." + indexstr;
duplcount++;
}
arraystr = j.toString();
while (arraystr.length < max_polys_length) arraystr = "0" + arraystr;
indexstr = i.toString();
while (indexstr.length < max_index_len) indexstr = "0" + indexstr;
allpoints[key] = arraystr + "." + indexstr;
}
}
if (!duplcount) return;
duplicates.length = duplcount;
duplicates.sort();
//console.log(JSON.stringify(duplicates));
var splitted, poly_index = 0,
nth_dupl = 0;
var prev_poly_index = -1;
poly_index = 0;
for (j = 0; j < duplcount; j++)
{
splitted = duplicates[j].split(".");
poly_index = parseInt(splitted[0], 10);
if (poly_index != prev_poly_index) nth_dupl = 0;
else nth_dupl++;
i = parseInt(splitted[1], 10);
poly = polys[poly_index];
len = poly.length;
if (poly[0].X === poly[len - 1].X &&
poly[0].Y === poly[len - 1].Y)
{
last_i = len - 2;
}
else
{
last_i = len - 1;
}
point = poly[i];
// Calculate "broken pen tip" effect
// for current point by finding
// a coordinate at a distance dist
// along the edge between current and
// previous point
// This is inlined to maximize speed
currX = point.X;
currY = point.Y;
if (i === 0) prev_i = last_i; // last element in array
else prev_i = i - 1;
prevX = poly[prev_i].X;
prevY = poly[prev_i].Y;
x=0;y=0;
if (!point.Collinear)
{
length = sqrt((-currX + prevX) * (-currX + prevX) + (currY - prevY) * (currY - prevY));
//console.log(length);
x = currX - (dist * (currX - prevX)) / length;
y = currY - (dist * (currY - prevY)) / length;
}
// save the found (calculated) point
moved_vertices[j] = {
X: x,
Y: y,
Collinear:point.Collinear,
index: i,
poly_index: poly_index
};
// "broken nib effect" for next point also
if (i == len - 1) next_i = 0;
else next_i = i + 1;
nextX = poly[next_i].X;
nextY = poly[next_i].Y;
x=0;y=0;
if (!point.Collinear)
{
length = sqrt((-currX + nextX) * (-currX + nextX) + (currY - nextY) * (currY - nextY));
x = currX - (dist * (currX - nextX)) / length;
y = currY - (dist * (currY - nextY)) / length;
}
// save the found (calculated) point
extra_vertices[j] = {
X: x,
Y: y,
Collinear:point.Collinear,
index: i + nth_dupl,
poly_index: poly_index
};
prev_poly_index = poly_index;
}
moved_vertices.length = extra_vertices.length = duplcount;
//console.log("MOVED:" + JSON.stringify(moved_vertices));
//console.log("EXTRA:" + JSON.stringify(extra_vertices));
// Update moved coordinates
i = duplcount;
var point2;
while (i--)
{
point = moved_vertices[i];
x = point.X;
y = point.Y;
// Faster than isNaN: http://jsperf.com/isnan-alternatives
if (x != x || x == Infinity || x == -Infinity) continue;
if (y != y || y == Infinity || y == -Infinity) continue;
point2 = polys[point.poly_index][point.index];
point2.X = point.X;
point2.Y = point.Y;
point2.Collinear = point.Collinear;
}
// Add an extra vertex
// This is needed to remain the angle of the next edge
for (i = 0; i < duplcount; i++)
{
point = extra_vertices[i];
x = point.X;
y = point.Y;
// Faster than isNaN: http://jsperf.com/isnan-alternatives
if (x != x || x == Infinity || x == -Infinity) continue;
if (y != y || y == Infinity || y == -Infinity) continue;
polys[point.poly_index].splice(point.index + 1, 0,
{
X: point.X,
Y: point.Y,
Collinear: point.Collinear
});
}
// Remove collinear points
// and for some reason coming
// sequential duplicates
// TODO: check why seq. duplicates becomes
j = polys.length;
var prev_point = null;
while (j--)
{
poly = polys[j];
ilen = poly.length;
i = ilen;
while (i--)
{
point = poly[i];
if(prev_point!=null && point.X == prev_point.X && point.Y == prev_point.Y) poly.splice(i, 1);
else
if(point.Collinear) poly.splice(i, 1);
prev_point = point;
}
}
//console.log(JSON.stringify(polys));
// because original array is modified, no need to return anything
}
var BreakPenNipsOfExPolygons = function (exPolygons, dist, scale)
{
var i = 0,
j = 0,
ilen = exPolygons.length,
jlen = 0;
for (; i < ilen; i++)
{
//if(i!=4) continue;
BreakPenNibs(exPolygons[i], dist, scale);
}
};

Zig-zag fill algorithm?

How do I make an algorithm to zig-zag fill a grid at any size as shown in the image below?
Here is my algorithm which doesn't work. (Starting bottom left to top right corner instead):
x1 = 0;
y1 = grid_h-1;
var a = 0;
put(x1,y1);
while(!((x1 = grid_w-1) and (y1 = 0))) { //If it isn't at the top right corner
if a = 2 {
x1 += 1;
put(x1,y1);
while(x1 != grid_w-1) { //While x1 isn't at the right
//Go diagonally down
x1 += 1;
y1 += 1;
put(x1,y1);
}
y1 -= 1;
put(x1,y1);
while(y1 != 0) { //While y1 isn't at the top
//Go diagonally up
x1 -= 1;
y1 -= 1;
put(x1,y1);
}
} else if a = 1 {
while(x1 != grid_w-1) { //While x1 isn't at the right
//Go diagonally down
x1 += 1;
y1 += 1;
put(x1,y1);
}
y1 -= 1;
put(x1,y1);
while(y1 != 0) { //While y1 isn't at the top
//Go diagonally up
x1 -= 1;
y1 -= 1;
put(x1,y1);
}
x1 += 1;
put(x1,y1);
} else {
y1 -= 1;
if (y1 = 0) { a = 1; } //At top?
put(x1,y1);
while(y1 != grid_h-1) { //While y1 isn't at the bottom
//Go diagonally down
x1 += 1;
y1 += 1;
put(x1,y1);
}
x1 += 1;
put(x1,y1);
while(x1 != 0) { //While x1 isn't at the left
//Go diagonally up
x1 -= 1;
y1 -= 1;
put(x1,y1);
if (y1 = 0) { a = 2; } //At top?
}
}
}
Any simpler way to do this?
The key observation here is that you go northeast when the Manhattan distance to the top left square is odd and southwest otherwise.
Of course, you must consider hitting one of the edges. For example, when you walk southwest and hit the bottom or south edge, you move east instead; when you hit the left or west edge, you move south. You can either catch the three cases (south edge, west edge, unrestrained movement) or you can move and correct your position when you have walked out of bounds.
After hitting an adge, your new position should leave you moving the other way. That is, each correction involves an odd number of steps. (Steps here is the Manhattan distance between the point you'd have gone to normally and the point you ended up in.)
If your zigzagging algorithm works correctly, you will end up visiting each cell once. That is you make h × w moves, where h and w are the height and width. You can use this as termination criterion instead of checking whether you are in the last square.
Here's example code for this solution. The additional boolean parameter down specifies whether the first step is down or left.
function zigzag(width, height, down) {
var x = 0;
var y = 0;
var n = width * height;
if (down === undefined) down = false;
while (n--) {
var even = ((x + y) % 2 == 0);
put(x, y);
if (even == down) { // walk southwest
x--;
y++;
if (y == height) {
y--; x += 2;
}
if (x < 0) x = 0;
} else { // walk northeast
x++;
y--;
if (x == width) {
x--; y += 2;
}
if (y < 0) y = 0;
}
}
return res;
}
Here's the solution abusing if-statements.
x1 = 0;
y1 = 0;
put(x1,y1);
var a = 0;
while(!((x1 = grid_w-1) and (y1 = grid_h-1))) {
switch(a) { //Down, Right-Up, Right, Left-Down
case 0: y1++; break;
case 1: x1++;y1--; break;
case 2: x1++; break;
case 3: x1--;y1++; break;
}
put(x1,y1);
if (a = 2) { //If moved right.
if (x1 = grid_w-1) or (y1 = 0) { //If at the right or top edge. Go left-down.
a = 3
} else if (y1 = grid_h-1) { //At bottom edge. Go right-up.
a = 1
}
} else if (y1 = 0) { ///At top edge.
if (x1 = grid_w-1) { //If at the right corner. Go down.
a = 0;
} else { //Go right.
a = 2;
}
} else if (a = 3) { ///If moved left-down.
if (y1 = grid_h-1) { //At bottom n edge. Go right.
a = 2
} else if (x1 = 0) { //At left edge and not bottom. Go down.
a = 0
}
} else if (a = 0) { //If moved down.
if (x1 = 0) { //If at the left corner. Go right-up.
a = 1
} else if (x1 = grid_w-1) { //If at the right corner. Go left-down.
a = 3
} else { //Go right
a = 2
}
} else if (a = 1) { //If right-up.
if (x1 = grid_w-1) { //If at the right corner.
if (a = 2) { //If moved right. Go left-down.
a = 3
} else { //Go down.
a = 0
}
}
}
}
Doesn't work well if one of the size is 1.
Basically we can use state diagram along with recursion to solve this.
permitted_directions = {
"start":["down", "side"],
"down":["north_east", "south_west"],
"north_east":["north_east", "side","down"],
"side":["north_east", "south_west"],
"south_west":["south_west","down", "side"]
}
def is_possible(x, y, pos):
if pos == "down":
if x+1 < row and y >=0 and y < col:
return (True, x+1, y)
if pos == "side":
if x >= 0 and x < row and y+1 >=0 and y+1 < col:
return (True, x, y+1)
if pos == "north_east":
if x-1 >= 0 and x-1 < row and y+1 >= 0 and y+1 < col:
return (True, x-1, y+1)
if pos == "south_west":
if x+1 >= 0 and x+1 < row and y-1 >= 0 and y-1 < col:
return (True, x+1, y-1)
return (False, 0, 0)
def fill_the_grid(grid, x, y, position, prev):
grid[x][y] = prev
prev = (x, y)
for pos in permitted_directions[position]:
possible, p, q = is_possible(x, y, pos)
if possible:
return fill_the_grid(grid, p, q, pos, prev)
return grid

Line Circle intersection for Vertical and Horizontal Lines

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

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);
}
};

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;
}

Resources