The intersection of two triangles is either empty or an n-gon (for n up to 6).
In theory, it is easy to come up with an algorithm to compute the intersection area. One can compute the possible intersections of all line segments and combine them with the points of the corner points of the triangles.
In practise, there are some numerical issues. If line segments are (nearly) parallel, they may or may not have an intersection point and its calculation can be imprecise (one usually divides by the determinant of the matrix, which is then approximately zero).
Any suggestions to avoid these numerical instabilities?
I don't know how to find the intersection area, but if what you want is just to know if they intersect, the algorithm in the article "Faster Triangle-Triangle Intersection Tests" by Olivier Devillers and Philippe Guigue is supposed to be very stable according to some polynomial order theory I don't quite understood nor looked up in the reference.
Follows my own JavaScript implementation of it. I can't guarantee it is correct because it hasn't seen enough real world action.
function det3(a, b, c)
{
var a11 = a[0] - c[0]; var a12 = a[1] - c[1];
var a21 = b[0] - c[0]; var a22 = b[1] - c[1];
return a11 * a22 - a21 * a12;
}
function planar_intersect(vs, t1, t2)
{
var p1, q1, r1;
var p2, q2, r2;
// I am doing this weird unpacking of the vertices because
// originally they were on R³, and I had a code here to project
// them to some orthogonal plane on R².
p1 = t1.a; q1 = t1.b; r1 = t1.c;
p2 = t2.a; q2 = t2.b; r2 = t2.c;
// Ensure both triangles are counterclockwise
var tmp;
if(det3(p1, q1, r1) < 0) {
tmp = q1;
q1 = r1;
r1 = tmp;
}
if(det3(p2, q2, r2) < 0) {
tmp = q2;
q2 = r2;
r2 = tmp;
}
// Calculate signed areas of triangles
var s1 = Array(3);
// If 0, the vertex is on the edge of the other triangle
s1[0] = det3(p1, p2, q2);
if(s1[0] == 0)
return true;
s1[1] = det3(p1, q2, r2);
if(s1[1] == 0)
return true;
s1[2] = det3(p1, r2, p2);
if(s1[2] == 0)
return true;
// If all positive, the vertex is internal to the other triangle
if(s1[0] > 0 && s1[1] > 0 && s1[2] > 0) {
return true;
}
// Reorder t2 in order for a1 to be in the right area
for(var i = 0; i < 2; ++i) {
if(s1[0] > 0 && s1[2] <= 0) {
break;
}
tmp = s1[0];
s1[0] = s1[1];
s1[1] = s1[2];
s1[2] = tmp;
tmp = p2;
p2 = q2;
q2 = r2;
r2 = tmp;
}
if(s1[1] > 0) {
// Follow Figure 9 tree.
if(det3(r2, p2, q1) >= 0) {
// II.a
if(det3(r2, p1, q1) >= 0) {
// III.a
if(det3(p1, p2, q1) >= 0) {
return true;
} else {
// IV.a
if(det3(p1, p2, r1) >= 0) {
// V
return det3(q1, r1, p2) >= 0;
} else {
return false;
}
}
} else {
return false;
}
} else {
// II.b
if(det3(r2, p2, r1) >= 0) {
// III.b
if(det3(q1, r1, r2) >= 0) {
// IV.b
return det3(p1, p2, r1) <= 0;
} else {
return false;
}
} else {
return false;
}
}
} else {
// Follow Figure 10 tree.
if(det3(r2, p2, q1) >= 0) {
// II.a
if(det3(q2, r2, q1) >= 0) {
// III.a
if(det3(p1, p2, q1) >= 0) {
// IV.a
return det3(p1, q2, q1) <= 0;
} else {
// IV.b
if(det3(p1, p2, r1) >= 0) {
// V.a
return det3(r2, p2, r1) >= 0;
} else {
return false;
}
}
} else {
// III.b
if(det3(p1, q2, q1) <= 0) {
// IV.c
if(det3(q2, r2, r1) >= 0) {
// V.b
return det3(q1, r1, q2) >= 0;
} else {
return false;
}
} else {
return false;
}
}
} else {
// II.b
if(det3(r2, p2, r1) >= 0) {
// III.c
if(det3(q1, r1, r2) >= 0) {
// IV.d
return det3(r1, p1, p2) >= 0;
} else {
// IV.c
if(det3(q1, r1, q2) >= 0) {
// V.c
return det3(q2, r2, r1) >= 0;
} else {
return false;
}
}
} else {
return false;
}
}
}
}
Related
Given a matrix of 0 and 1 (0 is free space, 1 is wall). Find the shortest path from one cell to another, passing only through 0 and also without touching 1.
enter image description here
How can I do this using Lee's Algorithm?
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
// edge case: start or end not accessible
if (grid[0][0] || grid.back().back()) return -1;
// support variables
int res = 2, len = 1, maxX = grid[0].size() - 1, maxY = grid.size() - 1;
queue<pair<int, int>> q;
// edge case: single cell matrix
if (!maxX && !maxY) return 1 - (grid[0][0] << 1);
// adding the starting point
q.push({0, 0});
// marking start as visited
grid[0][0] = -1;
while (len) {
while (len--) {
// reading and popping the coordinates on the front of the queue
auto [cx, cy] = q.front();
q.pop();
for (int x = max(0, cx - 1), lmtX = min(cx + 1, maxX); x <= lmtX; x++) {
for (int y = max(0, cy - 1), lmtY = min(cy + 1, maxY); y <= lmtY; y++) {
// check if we reached the target
if (x == maxX && y == maxY) return res;
// marking it as visited and adding it to the q if it was still a valid cell
if (!grid[y][x]) {
grid[y][x] = -1;
q.push({x, y});
}
}
}
}
// preparing for the next loop
res++;
len = q.size();
}
return -1;
}
};
I am new to processing and I need some help with this algorithm.
I cannot use the fill() and line() functions.
This should draw a convex polygon ( I choose the vertexes with mouse-clicks), and when I press the "T" button it will "fill()" the polygon.
int x1, y1, x2, y2;
int click;
boolean scanlineOn;
void setup() {
size(640, 480);
click = 0;
scanlineOn = false;
};
void draw() {
if (click == 2) {
beoszto(x1, y1, x2, y2);
click = 0;
x1 = -1;
y1 = -1;
x2 = -1;
y2 = -1;
};
if (scanlineOn == true) {
scanline();
scanlineOn = false;
};
};
void scanline() {
int x = 1, y = 1;
int xA = -1, yA = -1;
int xB = -1;
color a;
color b;
color c;
c = get(x, y);
while (x != 639 && y != 479) {
xA = -1;
yA = -1;
xB = -1;
while (x != 639) {
a = get(x, y);
b = get(x+1, y);
if (a != b && xA == -1) {
xA = x + 1;
yA = y;
c = get(xA, yA);
} else if (c == b && a != b) {
xB = x + 1;
};
if (xA != -1 && xB != -1){
myfill(xA, yA, xB);
xA = -1;
yA = -1;
xB = -1;
};
x = x + 1;
};
y = y + 1;
x = 1;
};
};
void myfill(int xA, int yA, int xB) {
while (xA < xB) {
point(xA, yA);
xA = xA + 1;
};
};
void beoszto(int x1, int y1, int x2, int y2) {
if (x1 < x2) {
sectiondraw(x2, y2, x1, y1);
} else if (x1 == x2 && y1 == y2) {
point(x1, y1);
} else {
sectiondraw(x1, y1, x2, y2);
};
};
void sectiondraw(int xA, int yA, int xB, int yB) {
int a, b, d, dE, dNE, a2, b2;
if (yA - yB <= 0) {
a2 = yB - yA;
} else {
a2 = yA - yB;
};
if (xA - xB <= 0) {
b2 = xB - xA;
} else {
b2 = xA - xB;
};
if (b2 >= a2) {
b = -b2;
a = a2;
} else {
b = -a2;
a = b2;
};
d = 2 * a + b;
dE = 2 * a;
dNE = 2 * (a + b);
point(xB, yB);
if (b2 >= a2 && yA >= yB) {
while (xB < xA) {
if (d >= 0) {
d = d - dE;
xB = xB + 1;
} else {
d = d - dNE;
yB = yB + 1;
xB = xB + 1;
};
point(xB, yB);
};
} else if (b2 >= a2 && yA < yB) {
while (xB < xA) {
if (d >= 0) {
d = d - dE;
xB = xB + 1;
} else {
d = d - dNE;
yB = yB - 1;
xB = xB + 1;
};
point(xB, yB);
};
} else if (b2 <= a2 && yA <= yB) {
while (yB > yA) {
if (d >= 0) {
d = d - dE;
yB = yB - 1;
} else {
d = d - dNE;
yB = yB - 1;
xB = xB + 1;
};
point(xB, yB);
};
} else {
while (yB < yA) {
if (d >= 0) {
d = d - dE;
yB = yB + 1;
} else {
d = d - dNE;
yB = yB + 1;
xB = xB + 1;
};
point(xB, yB);
};
};
};
void mousePressed() {
if (click == 0) {
x1 = mouseX;
y1 = mouseY;
click = 1;
} else {
x2 = mouseX;
y2 = mouseY;
click= 2;
};
};
void keyPressed() {
if (key == 't' || key == 'T') {
scanlineOn = true;
};
};
It is buggy like hell. My greatest problem is that I have to draw the polygon with multiple lines.
You should think about how you would do this with line() and/or fill(), then think about how to make your own functions for line() and fill().
Here's a hint: use an array to save the points you have clicked so far. One way to do that is like this:
int count = 0;
PVector[] vertices = new PVector[1000];
void mouseClicked() {
vertices[count] = new PVector(mouseX, mouseY);
count++;
}
Some formatting to keep in mind: in most (almost all) cases, you don't need a semicolon after a closed curly bracket. Whenever you have a semicolon inside the curly brackets, you don't need one afterward.
If you were to use the line() function you could make it like this:
//Create a list of all the clicked locations
ArrayList<PVector> clicked = new ArrayList<PVector>();
void setup(){
size(640, 480);
}
void draw(){
if(clicked.size() > 1){
for (int i = 1; i < clicked.size(); i++) {
//Draw a line between each clicked point
line(clicked.get(i).x,clicked.get(i).y,clicked.get(i-1).x,clicked.get(i-1).y);
}
}
}
void mousePressed(){
//Add new mouse presses to the list
clicked.add(new PVector(mouseX, mouseY));
}
Otherwise to not use the line() function you could create your own
void newLine(float x1, float y1, float x2, float y2){
float d = dist(x1,y1,x2,y2);
float m1 = (y2-y1)/d;
float m2 = (x2-x1)/d;
for (int i = 0; i < d; i++){
point(x1+i*m2, y1+i*m1);
}
}
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 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
I want to split a bezier curve into a polygonal chain with n straight lines. The number of lines being dependent on a maximum allowed angle between 2 connecting lines.
I'm looking for an algorithm to find the most optimal solution (ie to reduce as much as possible the number of straight lines).
I know how to split a bezier curve using Casteljau or Bernstein polynomals. I tried dividing the bezier into half calculate the angle between the straight lines, and split again if the angle between the connecting lines is within a certain threshold range, but i may run into shortcuts.
Is there a known algorithm or pseudo code available to do this conversion?
Use de Casteljau algorithm recursively until the control points are approximately collinear. See for instance http://www.antigrain.com/research/adaptive_bezier/index.html.
This was a fascinating topic. The only thing I'm adding is tested C# code, to perhaps save somebody the trouble. And I tried to write for clarity as opposed to speed, so it mostly follows the AGG web site's PDF doc (see above) on the Casteljau algorithm. The Notation follows the diagram in that PDF.
public class Bezier
{
public PointF P1; // Begin Point
public PointF P2; // Control Point
public PointF P3; // Control Point
public PointF P4; // End Point
// Made these global so I could diagram the top solution
public Line L12;
public Line L23;
public Line L34;
public PointF P12;
public PointF P23;
public PointF P34;
public Line L1223;
public Line L2334;
public PointF P123;
public PointF P234;
public Line L123234;
public PointF P1234;
public Bezier(PointF p1, PointF p2, PointF p3, PointF p4)
{
P1 = p1; P2 = p2; P3 = p3; P4 = p4;
}
/// <summary>
/// Consider the classic Casteljau diagram
/// with the bezier points p1, p2, p3, p4 and lines l12, l23, l34
/// and their midpoint of line l12 being p12 ...
/// and the line between p12 p23 being L1223
/// and the midpoint of line L1223 being P1223 ...
/// </summary>
/// <param name="lines"></param>
public void SplitBezier( List<Line> lines)
{
L12 = new Line(this.P1, this.P2);
L23 = new Line(this.P2, this.P3);
L34 = new Line(this.P3, this.P4);
P12 = L12.MidPoint();
P23 = L23.MidPoint();
P34 = L34.MidPoint();
L1223 = new Line(P12, P23);
L2334 = new Line(P23, P34);
P123 = L1223.MidPoint();
P234 = L2334.MidPoint();
L123234 = new Line(P123, P234);
P1234 = L123234.MidPoint();
if (CurveIsFlat())
{
lines.Add(new Line(this.P1, this.P4));
return;
}
else
{
Bezier bz1 = new Bezier(this.P1, P12, P123, P1234);
bz1.SplitBezier(lines);
Bezier bz2 = new Bezier(P1234, P234, P34, this.P4);
bz2.SplitBezier(lines);
}
return;
}
/// <summary>
/// Check if points P1, P1234 and P2 are colinear (enough).
/// This is very simple-minded algo... there are better...
/// </summary>
/// <returns></returns>
public bool CurveIsFlat()
{
float t1 = (P2.Y - P1.Y) * (P3.X - P2.X);
float t2 = (P3.Y - P2.Y) * (P2.X - P1.X);
float delta = Math.Abs(t1 - t2);
return delta < 0.1; // Hard-coded constant
}
The PointF is from System.Drawing, and the Line class follows:
public class Line
{
PointF P1; PointF P2;
public Line(PointF pt1, PointF pt2)
{
P1 = pt1; P2 = pt2;
}
public PointF MidPoint()
{
return new PointF((P1.X + P2.X) / 2f, (P1.Y + P2.Y) / 2f);
}
}
A sample call creates the Bezier object with 4 points (begin, 2 control, and end), and returns a list of lines that approximate the Bezier:
TopBezier = new Bezier(Point1, Point2, Point3, Point4 );
List<Line> lines = new List<Line>();
TopBezier.SplitBezier(lines);
Thanks to Dr Jerry, AGG, and all the other contributors.
There are some alternatives for RSA flattening that are reported to be faster:
RSA vs PAA:
http://www.cis.usouthal.edu/~hain/general/Theses/Ahmad_thesis.pdf
RSA vs CAA vs PAA:
http://www.cis.usouthal.edu/~hain/general/Theses/Racherla_thesis.pdf
RSA = Recursive Subdivision Algorithm
PAA = Parabolic Approximation Algorithm
CAA = Circular Approximation Algorithm
According to Rachela, CAA is slower than the PAA by a factor of 1.5–2. CAA is as slow as RSA, but achieves required flatness better in offset curves.
It seems that PAA is best choice for actual curve and CAA is best for offset's of curve (when stroking curves).
I have tested PAA of both thesis, but they fail in some cases. Ahmad's PAA fails in collinear cases (all points on same line) and Rachela's PAA fails in collinear cases and in cases where both control points are equal. With some fixes, it may be possible to get them work as expected.
A visual example on my website -> DXF -> polybezier.
it is basically a recursive split with casteljau.
Bezier2Poly.prototype.convert = function(array,init) {
if (init) {
this.vertices = [];
}
if (!init && (Math.abs(this.controlPointsDiff(array[0], array[2])) < this.threshold
|| Math.abs(this.controlPointsDiff({x:array[2].x-array[1].x, y:array[2]-array[1].y}, array[2])) < this.threshold)) {
this.vertices.push(array[2]);
} else {
var split = this.splitBezier(array);
this.convert(split.b1);
this.convert(split.b2);
}
return this.vertices;
}
And judgement by: calculating the angle between the controlpoints and the line through the endpoint.
Bezier2Poly.prototype.controlPointsDiff = function (vector1, vector2) {
var angleCp1 = Math.atan2(vector1.y, vector1.x);
var angleCp2 = Math.atan2(vector2.y, vector2.x);
return angleCp1 - angleCp2;
}
i solve it with qt for any svg path including bezier curve , i found in svg module a static function in qsvghandler.cpp which parsePathDataFast from your svg path to QPainterPath and the cherry on the cake!! QPainterPath have three native functions to convert your path to polygon (the big one toFillPolygon and the others which split in a list of polygon toSubpathPolygons or toFillPolygons) along with nice stuff like bounding box, intersected, translate ... ready to use with Boost::Geometry now, not so bad!
the header parsepathdatafast.h
#ifndef PARSEPATHDATAFAST_H
#define PARSEPATHDATAFAST_H
#include <QPainterPath>
#include <QString>
bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path);
#endif // PARSEPATHDATAFAST_H
the code parsepathdatafast.cpp
#include <QtCore/qmath.h>
#include <QtMath>
#include <QChar>
#include <QByteArray>
#include <QMatrix>
#include <parsepathdatafast.h>
Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok);
// '0' is 0x30 and '9' is 0x39
static inline bool isDigit(ushort ch)
{
static quint16 magic = 0x3ff;
return ((ch >> 4) == 3) && (magic >> (ch & 15));
}
static qreal toDouble(const QChar *&str)
{
const int maxLen = 255;//technically doubles can go til 308+ but whatever
char temp[maxLen+1];
int pos = 0;
if (*str == QLatin1Char('-')) {
temp[pos++] = '-';
++str;
} else if (*str == QLatin1Char('+')) {
++str;
}
while (isDigit(str->unicode()) && pos < maxLen) {
temp[pos++] = str->toLatin1();
++str;
}
if (*str == QLatin1Char('.') && pos < maxLen) {
temp[pos++] = '.';
++str;
}
while (isDigit(str->unicode()) && pos < maxLen) {
temp[pos++] = str->toLatin1();
++str;
}
bool exponent = false;
if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
exponent = true;
temp[pos++] = 'e';
++str;
if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
temp[pos++] = str->toLatin1();
++str;
}
while (isDigit(str->unicode()) && pos < maxLen) {
temp[pos++] = str->toLatin1();
++str;
}
}
temp[pos] = '\0';
qreal val;
if (!exponent && pos < 10) {
int ival = 0;
const char *t = temp;
bool neg = false;
if(*t == '-') {
neg = true;
++t;
}
while(*t && *t != '.') {
ival *= 10;
ival += (*t) - '0';
++t;
}
if(*t == '.') {
++t;
int div = 1;
while(*t) {
ival *= 10;
ival += (*t) - '0';
div *= 10;
++t;
}
val = ((qreal)ival)/((qreal)div);
} else {
val = ival;
}
if (neg)
val = -val;
} else {
bool ok = false;
val = qstrtod(temp, 0, &ok);
}
return val;
}
static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points)
{
while (str->isSpace())
++str;
while (isDigit(str->unicode()) ||
*str == QLatin1Char('-') || *str == QLatin1Char('+') ||
*str == QLatin1Char('.')) {
points.append(toDouble(str));
while (str->isSpace())
++str;
if (*str == QLatin1Char(','))
++str;
//eat the rest of space
while (str->isSpace())
++str;
}
}
/**
static QVector<qreal> parsePercentageList(const QChar *&str)
{
QVector<qreal> points;
if (!str)
return points;
while (str->isSpace())
++str;
while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) ||
*str == QLatin1Char('-') || *str == QLatin1Char('+') ||
*str == QLatin1Char('.')) {
points.append(toDouble(str));
while (str->isSpace())
++str;
if (*str == QLatin1Char('%'))
++str;
while (str->isSpace())
++str;
if (*str == QLatin1Char(','))
++str;
//eat the rest of space
while (str->isSpace())
++str;
}
return points;
}
**/
static void pathArcSegment(QPainterPath &path,
qreal xc, qreal yc,
qreal th0, qreal th1,
qreal rx, qreal ry, qreal xAxisRotation)
{
qreal sinTh, cosTh;
qreal a00, a01, a10, a11;
qreal x1, y1, x2, y2, x3, y3;
qreal t;
qreal thHalf;
sinTh = qSin(xAxisRotation * (M_PI / 180.0));
cosTh = qCos(xAxisRotation * (M_PI / 180.0));
a00 = cosTh * rx;
a01 = -sinTh * ry;
a10 = sinTh * rx;
a11 = cosTh * ry;
thHalf = 0.5 * (th1 - th0);
t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
x1 = xc + qCos(th0) - t * qSin(th0);
y1 = yc + qSin(th0) + t * qCos(th0);
x3 = xc + qCos(th1);
y3 = yc + qSin(th1);
x2 = x3 + t * qSin(th1);
y2 = y3 - t * qCos(th1);
path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
}
// the arc handling code underneath is from XSVG (BSD license)
/*
* Copyright 2002 USC/Information Sciences Institute
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of
* Information Sciences Institute not be used in advertising or
* publicity pertaining to distribution of the software without
* specific, written prior permission. Information Sciences Institute
* makes no representations about the suitability of this software for
* any purpose. It is provided "as is" without express or implied
* warranty.
*
* INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
* TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
* INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
* OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
*/
static void pathArc(QPainterPath &path,
qreal rx,
qreal ry,
qreal x_axis_rotation,
int large_arc_flag,
int sweep_flag,
qreal x,
qreal y,
qreal curx, qreal cury)
{
qreal sin_th, cos_th;
qreal a00, a01, a10, a11;
qreal x0, y0, x1, y1, xc, yc;
qreal d, sfactor, sfactor_sq;
qreal th0, th1, th_arc;
int i, n_segs;
qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check;
rx = qAbs(rx);
ry = qAbs(ry);
sin_th = qSin(x_axis_rotation * (M_PI / 180.0));
cos_th = qCos(x_axis_rotation * (M_PI / 180.0));
dx = (curx - x) / 2.0;
dy = (cury - y) / 2.0;
dx1 = cos_th * dx + sin_th * dy;
dy1 = -sin_th * dx + cos_th * dy;
Pr1 = rx * rx;
Pr2 = ry * ry;
Px = dx1 * dx1;
Py = dy1 * dy1;
/* Spec : check if radii are large enough */
check = Px / Pr1 + Py / Pr2;
if (check > 1) {
rx = rx * qSqrt(check);
ry = ry * qSqrt(check);
}
a00 = cos_th / rx;
a01 = sin_th / rx;
a10 = -sin_th / ry;
a11 = cos_th / ry;
x0 = a00 * curx + a01 * cury;
y0 = a10 * curx + a11 * cury;
x1 = a00 * x + a01 * y;
y1 = a10 * x + a11 * y;
/* (x0, y0) is current point in transformed coordinate space.
(x1, y1) is new point in transformed coordinate space.
The arc fits a unit-radius circle in this space.
*/
d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
sfactor_sq = 1.0 / d - 0.25;
if (sfactor_sq < 0) sfactor_sq = 0;
sfactor = qSqrt(sfactor_sq);
if (sweep_flag == large_arc_flag) sfactor = -sfactor;
xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
/* (xc, yc) is center of the circle. */
th0 = qAtan2(y0 - yc, x0 - xc);
th1 = qAtan2(y1 - yc, x1 - xc);
th_arc = th1 - th0;
if (th_arc < 0 && sweep_flag)
th_arc += 2 * M_PI;
else if (th_arc > 0 && !sweep_flag)
th_arc -= 2 * M_PI;
n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001)));
for (i = 0; i < n_segs; i++) {
pathArcSegment(path, xc, yc,
th0 + i * th_arc / n_segs,
th0 + (i + 1) * th_arc / n_segs,
rx, ry, x_axis_rotation);
}
}
bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path)
{
qreal x0 = 0, y0 = 0; // starting point
qreal x = 0, y = 0; // current point
char lastMode = 0;
QPointF ctrlPt;
const QChar *str = dataStr.constData();
const QChar *end = str + dataStr.size();
while (str != end) {
while (str->isSpace())
++str;
QChar pathElem = *str;
++str;
QChar endc = *end;
*const_cast<QChar *>(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee
QVarLengthArray<qreal, 8> arg;
parseNumbersArray(str, arg);
*const_cast<QChar *>(end) = endc;
if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
arg.append(0);//dummy
const qreal *num = arg.constData();
int count = arg.count();
while (count > 0) {
qreal offsetX = x; // correction offsets
qreal offsetY = y; // for relative commands
switch (pathElem.unicode()) {
case 'm': {
if (count < 2) {
num++;
count--;
break;
}
x = x0 = num[0] + offsetX;
y = y0 = num[1] + offsetY;
num += 2;
count -= 2;
path.moveTo(x0, y0);
// As per 1.2 spec 8.3.2 The "moveto" commands
// If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
// the subsequent pairs shall be treated as implicit 'lineto' commands.
pathElem = QLatin1Char('l');
}
break;
case 'M': {
if (count < 2) {
num++;
count--;
break;
}
x = x0 = num[0];
y = y0 = num[1];
num += 2;
count -= 2;
path.moveTo(x0, y0);
// As per 1.2 spec 8.3.2 The "moveto" commands
// If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
// the subsequent pairs shall be treated as implicit 'lineto' commands.
pathElem = QLatin1Char('L');
}
break;
case 'z':
case 'Z': {
x = x0;
y = y0;
count--; // skip dummy
num++;
path.closeSubpath();
}
break;
case 'l': {
if (count < 2) {
num++;
count--;
break;
}
x = num[0] + offsetX;
y = num[1] + offsetY;
num += 2;
count -= 2;
path.lineTo(x, y);
}
break;
case 'L': {
if (count < 2) {
num++;
count--;
break;
}
x = num[0];
y = num[1];
num += 2;
count -= 2;
path.lineTo(x, y);
}
break;
case 'h': {
x = num[0] + offsetX;
num++;
count--;
path.lineTo(x, y);
}
break;
case 'H': {
x = num[0];
num++;
count--;
path.lineTo(x, y);
}
break;
case 'v': {
y = num[0] + offsetY;
num++;
count--;
path.lineTo(x, y);
}
break;
case 'V': {
y = num[0];
num++;
count--;
path.lineTo(x, y);
}
break;
case 'c': {
if (count < 6) {
num += count;
count = 0;
break;
}
QPointF c1(num[0] + offsetX, num[1] + offsetY);
QPointF c2(num[2] + offsetX, num[3] + offsetY);
QPointF e(num[4] + offsetX, num[5] + offsetY);
num += 6;
count -= 6;
path.cubicTo(c1, c2, e);
ctrlPt = c2;
x = e.x();
y = e.y();
break;
}
case 'C': {
if (count < 6) {
num += count;
count = 0;
break;
}
QPointF c1(num[0], num[1]);
QPointF c2(num[2], num[3]);
QPointF e(num[4], num[5]);
num += 6;
count -= 6;
path.cubicTo(c1, c2, e);
ctrlPt = c2;
x = e.x();
y = e.y();
break;
}
case 's': {
if (count < 4) {
num += count;
count = 0;
break;
}
QPointF c1;
if (lastMode == 'c' || lastMode == 'C' ||
lastMode == 's' || lastMode == 'S')
c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
else
c1 = QPointF(x, y);
QPointF c2(num[0] + offsetX, num[1] + offsetY);
QPointF e(num[2] + offsetX, num[3] + offsetY);
num += 4;
count -= 4;
path.cubicTo(c1, c2, e);
ctrlPt = c2;
x = e.x();
y = e.y();
break;
}
case 'S': {
if (count < 4) {
num += count;
count = 0;
break;
}
QPointF c1;
if (lastMode == 'c' || lastMode == 'C' ||
lastMode == 's' || lastMode == 'S')
c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
else
c1 = QPointF(x, y);
QPointF c2(num[0], num[1]);
QPointF e(num[2], num[3]);
num += 4;
count -= 4;
path.cubicTo(c1, c2, e);
ctrlPt = c2;
x = e.x();
y = e.y();
break;
}
case 'q': {
if (count < 4) {
num += count;
count = 0;
break;
}
QPointF c(num[0] + offsetX, num[1] + offsetY);
QPointF e(num[2] + offsetX, num[3] + offsetY);
num += 4;
count -= 4;
path.quadTo(c, e);
ctrlPt = c;
x = e.x();
y = e.y();
break;
}
case 'Q': {
if (count < 4) {
num += count;
count = 0;
break;
}
QPointF c(num[0], num[1]);
QPointF e(num[2], num[3]);
num += 4;
count -= 4;
path.quadTo(c, e);
ctrlPt = c;
x = e.x();
y = e.y();
break;
}
case 't': {
if (count < 2) {
num += count;
count = 0;
break;
}
QPointF e(num[0] + offsetX, num[1] + offsetY);
num += 2;
count -= 2;
QPointF c;
if (lastMode == 'q' || lastMode == 'Q' ||
lastMode == 't' || lastMode == 'T')
c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
else
c = QPointF(x, y);
path.quadTo(c, e);
ctrlPt = c;
x = e.x();
y = e.y();
break;
}
case 'T': {
if (count < 2) {
num += count;
count = 0;
break;
}
QPointF e(num[0], num[1]);
num += 2;
count -= 2;
QPointF c;
if (lastMode == 'q' || lastMode == 'Q' ||
lastMode == 't' || lastMode == 'T')
c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
else
c = QPointF(x, y);
path.quadTo(c, e);
ctrlPt = c;
x = e.x();
y = e.y();
break;
}
case 'a': {
if (count < 7) {
num += count;
count = 0;
break;
}
qreal rx = (*num++);
qreal ry = (*num++);
qreal xAxisRotation = (*num++);
qreal largeArcFlag = (*num++);
qreal sweepFlag = (*num++);
qreal ex = (*num++) + offsetX;
qreal ey = (*num++) + offsetY;
count -= 7;
qreal curx = x;
qreal cury = y;
pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
int(sweepFlag), ex, ey, curx, cury);
x = ex;
y = ey;
}
break;
case 'A': {
if (count < 7) {
num += count;
count = 0;
break;
}
qreal rx = (*num++);
qreal ry = (*num++);
qreal xAxisRotation = (*num++);
qreal largeArcFlag = (*num++);
qreal sweepFlag = (*num++);
qreal ex = (*num++);
qreal ey = (*num++);
count -= 7;
qreal curx = x;
qreal cury = y;
pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
int(sweepFlag), ex, ey, curx, cury);
x = ex;
y = ey;
}
break;
default:
return false;
}
lastMode = pathElem.toLatin1();
}
}
return true;
}
One question, i doesn't find Q_PI constant in the standard qt headers and i replace it with M_PI hope is OK!!