Banach Fractal Curve Java - Recursive - java

I have the following Banach Fractal problem:
The so-called Banach Curve can be generated using the following fractal rule:
Draw a circle.
Draw 9 smaller circles, each with a radius ⅓ of the original
circle. One of the smaller circles should have the same center as that of the original circle. The centers of the remaining
8 smaller circles should be equally spaced along the circumference of the original circle.
Repeat step b with each of the smaller circles.
Note: The circle of radius r centered at point (x, y) is the set of all the points
(x + r · cos(t), y + r · sin(t)) where 0 ≤ t ≤ 2π , and t is given in radians. I can use Math.toRadians()
Guidelines:
Only recursive solutions, no loops allowed
No imports & no lists (so no map or so) & no ?
I can only use the functions public static void banachCurve(int n) and help functions private static void banachCurve(double x, double y, double r, int n)
Can only use StdDraw to draw or call other functions from it, no other Std classes are allowed
I thought of adding circles each time since there are supposed to be 9 at the edges and 1 in the center each time, however I can only seem to get the circles on the right or left, and for some reason a RuntimeError.
public static void banachCurve(int n) {
banachCurve (0.5,0.5,1,n);
}
private static void banachCurve(double x, double y, double r, int n) {
if (n == 0) {
return;
}
double d = (r/3);
StdDraw.circle (x,y,d);
// StdDraw.ellipse(x, y, r, r);
banachCurve (x + d, y, d, n - 1); // centre
banachCurve (x + d+ d, y+d, d, n--); // left
banachCurve (x , y + d, d, n--); // right
banachCurve (x+d , y +d+ d, d, n--);
banachCurve (x+d , y +d, d, n--);
}
my output:
stages of Banach Curve:

Every time you call n--, you are passing n to the function and then decrementing it by one for the next call. Instead, you need to pass n - 1 to each call, as the call itself will decrement n further in its own recursive call, eventually stopping at 0 as you correctly have.
For the four cardinal points, using (x + d, y), (x, y + d), (x - d, y) and (x, y - d) works correctly, but for the four diagonal points, you will need to use either the square root (Math.sqrt) for the Pythagoras method, or the sine and cosine (Math.sin and Math.cos) for the trigonometric method. Using (x + d, y + d) and the like would place them on a square instead.
Assuming that x and y mark the centre of your circle, your function will then become:
private static void banachCurve(final double x, final double y, final double r, final int n) {
if (n == 0) {
return;
}
final double d = r / 3;
StdDraw.circle (x, y, d);
banachCurve (x, y, d, n - 1); // centre
banachCurve (x, y + d, d, n - 1); // north
banachCurve (x + d, y, d, n - 1); // east
banachCurve (x, y - d, d, n - 1); // south
banachCurve (x - d, y, d, n - 1); // west
// Get the diagonal radius for a point at 45 degrees on the circle
final double diagD = Math.cos(Math.toRadians(45)) * d;
banachCurve (x + diagD, y + diagD, d, n - 1); // north-east
banachCurve (x + diagD, y - diagD, d, n - 1); // south-east
banachCurve (x - diagD, y - diagD, d, n - 1); // south-west
banachCurve (x - diagD, y + diagD, d, n - 1); // north-west
}
Here is the output for banachCurve(0.5, 0.5, 1, 6);:

If you're going to drag Math.cos() into the picture, a la the currenly accepted answer, why not go the whole hog and use sine and cosine to move around the circle:
private static void banachCurve(double x, double y, double r, int n) {
if (n == 0) {
return;
}
double d = r / 3;
StdDraw.circle (x, y, d);
banachCurve (x, y, d, n - 1); // center
for (double angle = 0; angle < 360; angle += 45) {
double theta = Math.toRadians(angle);
double dx = x + d * Math.cos(theta);
double dy = y + d * Math.sin(theta);
banachCurve (dx, dy, d, n - 1);
}
}
Output for banachCurve(0.5, 0.5, 1, 3);
This approach makes easy work of testing out #tucuxi's suggestion, of six instead of eight surrounding circles. Just increase the increment angle in the for loop from 45 to 60:
Though I can't say its an improvement over the original design. Though seven surrounding circles, again trivial to test given this code design, catches one's eye:

Related

How to check if triangle2D is inside another one or overlapping it?

I'm trying to check if triangle2D is contain another triangle or overlapping it.
I can do that with circle e.g:
/** Return true if the specified point
* (x, y) is inside this circle */
public boolean contains(double x, double y) {
return Math.sqrt(Math.pow(x - this.x, 2) +
Math.pow(y - this.y, 2))
< radius;
}
/** Return true if the specified
* circle is inside this circle */
public boolean contains(Circle2D circle) {
return Math.sqrt(Math.pow(circle.getX() - x, 2) +
Math.pow(circle.getY() - y, 2))
<= Math.abs(radius - circle.getRadius());
}
/** Return true if the specified
* circle overlaps with this circle */
public boolean overlaps(Circle2D circle) {
return Math.sqrt(Math.pow(circle.getX() - x, 2) +
Math.pow(circle.getY() - y, 2))
<= radius + circle.getRadius();
}
But I don't know how to do that with triangle.
I've found this question for point only, but I don't how to do that if triangle contain other triangle or overlapping it.
You can use Line2D#contains, Line2D#linesIntersect methods inside java.awt.geom.Line2D.
Edit:
Thinking in Math:
To detect whether a point is inside a triangle, draw three dashed lines, if the point is inside a triangle, each dashed line should intersect a side only once. if a dashed line intersect a side twice, then the point must be outside the triangle.
So We can use the way in the other question (you mentioned) like below:
public boolean contains(MyPoint p) {
// double area1 = calcArea(p, p1, p2);
// double area2 = calcArea(p, p2, p3);
// double area3 = calcArea(p, p3, p1);
// double area = Math.round((area1 + area2 + area3) * 100) / 100;
// double triangleArea = Math.round(getArea() * 100) / 100;
// return (triangleArea == area)
}
But it's not efficient way, so we will implement it as below, in order to reuse it with other cases.
We should have three methods one for check max x, y, one for checking min x, y and other for checking the lineSegment.
The lineSegment would be look like:
double position = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
return position <= 0.0000000001 && ((x0 <= x2 && x2 <= x1) || (x0 >= x2 && x2 >= x1));
/**
* returns true if the specified point is inside this triangle
**/
public boolean contains(MyPoint p) {
return contains(p.getX(), p.getY());
}
public boolean contains(double x, double y) {
// Get max X & Y
double maxX = getMax(p1.getX(), p2.getY(), p3.getX());
double maxY = getMax(p1.getY(), p2.getY(), p3.getX());
// Get min X & Y
double minX = getMin(p1.getX(), p2.getX(), p3.getX());
double minY = getMin(p1.getY(), p2.getY(), p3.getY());
// Outside the bounding rectangle of the triangle
if (x < minX || x > maxX || y < minY || y > maxY) return false;
// Check if point is the border of the triangle
MyPoint p = new MyPoint(x, y);
boolean side1 = p.onTheLineSegment(p1, p2); //assume A to B
boolean side2 = p.onTheLineSegment(p1, p3); //assume B to C
boolean side3 = p.onTheLineSegment(p2, p3); //assume C to A
return side1 || side2 || side3; //return true if any point of these vertices inside triangle.
}
So to check if triangle contain other one, our method would be look like:
public boolean contains(Triangle t) {
return contains(t.p1) && contains(t.p2) && contains(t.p3); //All three points is inside the triangle
}
Or simply by using Line2D class as I mention above:
Line2D line2D = new Line2D.Double(p1.getX(), p1.getY(), p2.getX(), p2.getY());
return line2D.contains(....); //check if contain triangle or point
For the case of containing you can check all vertices of a triangle. If all of them are existed inner of the other, one of them contains the other one.
For the case of overlapping, You should consider is there any intersection between any edge of these two triangle or not. If there is not and the containing case was not happened, they are separated. For the case of intersecting two edges (as two segments) on the plane, you can using this post.
Use the LeftOf predicate, which is true when the algebraic area of the triangle ABC is positive (using the appropriate sign convention), telling you that C is on the left of the line AB.
Then check if all three vertices of the first triangle lie left of some side of the second one. If not, repeat after exchanging the two triangles.
Finally if yes, the triangles do not interfere.

How to find a point in a line in java?

I am trying to do some experiments with path. I have two points (x0,y0) and (x2,y2). Now I have to find a point (x1,y1) that should be some distance from the final point (x2,y2).
For example
Start Point (0,0) End Point (0,5)
Point i want to find is (0,2)
For a line between
The point at distance d from the first point, (positive) in the direction of the second point, is given by:
Where L is the distance between the two points defining the line:
(For your case just take L - d instead of d)
Imagine the two points define two rectangle triangles. The bigger triangle has sides with sizes x1, y1. the smaller has sides of sizes xt, yt.
1) Now apply Pythagoras' Theorem two calculate the bigger hypotenuse, h, using the equation h^2 = x1^2 + y1^2; (where h^2 means h power of two)
2) the difference ( h - the distance) is the hypotenuse of the smaller triangle. let's call it ht.
3) Calculate xt and yt as directly proportional to hypot bigger/hypot smaller.
x1/xt = h/ht
y1/yt = h/ht
/* I used user3235832 formula and write this code and its solve my problem.
Code in java.
sourcePoint(x,y);
destinationPoint(w,h);*/
//--------------------------------------------------------------------
double l = Math.sqrt(Math.pow((w - x), 2) + Math.pow((h - y), 2));
double d = l / 10;//you can use your own value for d.
int newX = (int) (w + (((x - w) / (l) * d)));
int newy = (int) (h + (((y - h) / (l) * d)));
//--------------------------------------------------------------------
/* if you use like me Graphic2D and use g2d.fillOval() function for draw a oval
on the line you need line slop. code for this: */
double m = Double.POSITIVE_INFINITY;//line slop
if (w - x != 0) {
m = (h - y) / (w - x);
}
int r = 6;//size of Oval
if (m == 0) {
g2d.fillOval(newX, (newy - (r/2)), r, r);
} else if (m == Double.POSITIVE_INFINITY) {
g2d.fillOval((newX - (r/2)), newy, r, r);
} else if (m < 0) {
g2d.fillOval((newX - (r/2)), (newy - (r/2)), r, r);
} else {
g2d.fillOval(newX, newy, r, r);
}

Implementing Bresenham's circle drawing algorithm

I have written an implementation of Bresenham's circle drawing algorithm. This algorithms takes advantage of the highly symmetrical properties of a circle (it only computes points from the 1st octant and draws the other points by taking advantage of symmetry). Therefore I was expecting it to be very fast. The Graphics programming black book, chapter #35 was titled "Bresenham is fast, and fast is good", and though it was about the line drawing algorithm, I could reasonably expect the circle drawing algorithm to also be fast (since the principle is the same).
Here is my java, swing implementation
public static void drawBresenhamsCircle(int r, double width, double height, Graphics g) {
int x,y,d;
y = r;
x = 0;
drawPoint(x, y, width, height,g);
d = (3-2*(int)r);
while (x <= y) {
if (d <= 0) {
d = d + (4*x + 6);
} else {
d = d + 4*(x-y) + 10;
y--;
}
x++;
drawPoint(x, y, width, height,g);
drawPoint(-x, y, width, height,g);
drawPoint(x, -y, width, height,g);
drawPoint(-x, -y, width, height,g);
drawPoint(y, x, width, height,g);
drawPoint(-y, x, width, height,g);
drawPoint(y, -x, width, height,g);
drawPoint(-y, -x, width, height,g);
}
}
This method uses the following drawPointmethod:
public static void drawPoint(double x, double y,double width,double height, Graphics g) {
double nativeX = getNativeX(x, width);
double nativeY = getNativeY(y, height);
g.fillRect((int)nativeX, (int)nativeY, 1, 1);
}
The two methods getNativeX and getNativeY are used to switch coordinates from originating in the upper left corner of the screen to a system that has it origin in the center of the panel with a more classic axis orientation.
public static double getNativeX(double newX, double width) {
return newX + (width/2);
}
public static double getNativeY(double newY, double height) {
return (height/2) - newY;
}
I have also created an implementation of a circle drawing algorithm based on trigonometrical formulaes (x=R*Math.cos(angle)and y= R*Math.sin(angle)) and a third implementation using a call to the standard drawArc method (available on the Graphics object). These additional implementations are for the sole purpose of comparing Bresenham's algorithm to them.
I then created methods to draw a bunch of circles in order to be able to get good measures of the spent time. Here is the method I use to draw a bunch of circles using Bresenham's algorithm
public static void drawABunchOfBresenhamsCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawBresenhamsCircle((int)r, width, height, g);
r += step;
}
}
Finally I override the paint method of the JPanel I am using, to draw the bunch of circles and to measure the time it took each type to draw. Here is the paint method:
public void paint(Graphics g) {
Graphics2D g2D = (Graphics2D)g;
g2D.setColor(Color.RED);
long trigoStartTime = System.currentTimeMillis();
drawABunchOfTrigonometricalCircles(1000, this.getWidth(), this.getHeight(), g);
long trigoEndTime = System.currentTimeMillis();
long trigoDelta = trigoEndTime - trigoStartTime;
g2D.setColor(Color.BLUE);
long bresenHamsStartTime = System.currentTimeMillis();
drawABunchOfBresenhamsCircles(1000, this.getWidth(), this.getHeight(), g);
long bresenHamsEndTime = System.currentTimeMillis();
long bresenDelta = bresenHamsEndTime - bresenHamsStartTime;
g2D.setColor(Color.GREEN);
long standardStarTime = System.currentTimeMillis();
drawABunchOfStandardCircles(1000, this.getWidth(), this.getHeight(),g);
long standardEndTime = System.currentTimeMillis();
long standardDelta = standardEndTime - standardStarTime;
System.out.println("Trigo : " + trigoDelta + " milliseconds");
System.out.println("Bresenham :" + bresenDelta + " milliseconds");
System.out.println("Standard :" + standardDelta + " milliseconds");
}
Here is the kind of rendering it would generate (drawing 1000 circles of each type)
Unfortunately my Bresenham's implementation is very slow. I took many comparatives measures, and the Bresenham's implementation is not only slower than the Graphics.drawArcbut also slower than the trigonometrical approach. Take a look at the following measures for a various number of circles drawn.
What part of my implementation is more time-consuming? Is there any workaround I could use to improve it? Thanks for helping.
[EDITION]: as requested by #higuaro, here is my trigonometrical algorithm for drawing a circle
public static void drawTrigonometricalCircle (double r, double width, double height, Graphics g) {
double x0 = 0;
double y0 = 0;
boolean isStart = true;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + Math.PI/36) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint((double)x, y, width, height, g);
if (!isStart) {
drawLine(x0, y0, x, y, width, height, g);
}
isStart = false;
x0 = x;
y0 = y;
}
}
And the method used to draw a bunch of trigonometrical circles
public static void drawABunchOfTrigonometricalCircles(int numOfCircles, double width, double height, Graphics g) {
double r = 5;
double step = (300.0-5.0)/numOfCircles;
for (int i = 1; i <= numOfCircles; i++) {
drawTrigonometricalCircle(r, width, height, g);
r += step;
}
}
Your Bresenham method isn't slow per se, it's just comparatively slow.
Swing's drawArc() implementation is machine-dependent, using native code. You'll never beat it using Java, so don't bother trying. (I'm actually surprised the Java Bresenham method is as fast as it is compared to drawArc(), a testament to the quality of the virtual machine executing the Java bytecode.)
Your trigonometric method, however, is unnecessarily fast, because you're not comparing it to Bresenham on an equal basis.
The trig method has a set angular resolution of PI/36 (~4.7 degrees), as in this operation at the end of the for statement:
angle = angle + Math.PI/36
Meanwhile, your Bresenham method is radius-dependent, computing a value at each pixel change. As each octant produces sqrt(2) points, multiplying that by 8 and dividing by 2*Pi will give you the equivalent angular resolution. So to be on equal footing with the Bresenham method, your trig method should therefore have:
resolution = 4 * r * Math.sqrt(2) / Math.PI;
somewhere outside the loop, and increment your for by it as in:
angle += resolution
Since we will now be back to pixel-level resolutions, you can actually improve the trig method and cut out the subsequent drawline call and assignments to x0 and y0, eliminate unnecessarily casts, and furthermore reduce calls to Math. Here's the new method in its entirety:
public static void drawTrigonometricalCircle (double r, double width, double height,
Graphics g) {
double localPi = Math.PI;
double resolution = 4 * r * Math.sqrt(2) / Math.PI;
for (double angle = 0; angle <= localPi; angle += resolution) {
double x = r * Math.cos(angle);
double y = r * Math.sin(angle);
drawPoint(x, y, width, height, g);
}
}
The trig method will now be executing more often by several orders of magnitude depending on the size of r.
I'd be interested to see your results.
Your problem lies in that Bresenham's algorithm does a variable number of iterations depending on the size of the circle whereas your trigonometric approach always does a fixed number of iterations.
This also means that Bresenham's algorithm will always produce a smooth looking circle whereas your trigonometric approach will produce worse looking circles as the radius increases.
To make it more even, change the trigonometric approach to produce approximately as many points as the Bresenham implementation and you'll see just how much faster it is.
I wrote some code to benchmark this and also print the number of points produced and here are the initial results:
Trigonometric: 181 ms, 73 points average
Bresenham: 120 ms, 867.568 points average
After modifying your trigonometric class to do more iterations for smoother circles:
int totalPoints = (int)Math.ceil(0.7 * r * 8);
double delta = 2 * Math.PI / totalPoints;
for (double angle = 0; angle <= 2*Math.PI; angle = angle + delta) {
These are the results:
Trigonometric: 2006 ms, 854.933 points average
Bresenham: 120 ms, 867.568 points average
I lately wrote a bresenham circle drawing implemenation myself for a sprite rasterizer and tried to optimize it a bit. I'm not sure if it will be faster or slower than what you did but i think it should have a pretty decent execution time.
Also unfortunately it is written in C++. If i have time tomorrow i might edit my answer with a ported Java version and an example picture for the result but for now you'd have to do it yourself if you want (or someone else who would want to take his time and edit it.)
Bascically, what it does is use the bresenham algorithm to aquire the positions for the outer edges of the circle, then perform the algorithm for 1/8th of the circle and mirror that for the the remaining 7 parts by drawing straight lines from the center to the outer edge.
Color is just an rgba value
Color* createCircleColorArray(const int radius, const Color& color, int& width, int& height) {
// Draw circle with custom bresenham variation
int decision = 3 - (2 * radius);
int center_x = radius;
int center_y = radius;
Color* data;
// Circle is center point plus radius in each direction high/wide
width = height = 2 * radius + 1;
data = new Color[width * height];
// Initialize data array for transparency
std::fill(data, data + width * height, Color(0.0f, 0.0f, 0.0f, 0.0f));
// Lambda function just to draw vertical/horizontal straight lines
auto drawLine = [&data, width, height, color] (int x1, int y1, int x2, int y2) {
// Vertical
if (x1 == x2) {
if (y2 < y1) {
std::swap(y1, y2);
}
for (int x = x1, y = y1; y <= y2; y++) {
data[(y * width) + x] = color;
}
}
// Horizontal
if (y1 == y2) {
if (x2 < x1) {
std::swap(x1, x2);
}
for (int x = x1, y = y1; x <= x2; x++) {
data[(y * width) + x] = color;
}
}
};
// Lambda function to draw actual circle split into 8 parts
auto drawBresenham = [color, drawLine] (int center_x, int center_y, int x, int y) {
drawLine(center_x + x, center_y + x, center_x + x, center_y + y);
drawLine(center_x - x, center_y + x, center_x - x, center_y + y);
drawLine(center_x + x, center_y - x, center_x + x, center_y - y);
drawLine(center_x - x, center_y - x, center_x - x, center_y - y);
drawLine(center_x + x, center_y + x, center_x + y, center_y + x);
drawLine(center_x - x, center_y + x, center_x - y, center_y + x);
drawLine(center_x + x, center_y - x, center_x + y, center_y - x);
drawLine(center_x - x, center_y - x, center_x - y, center_y - x);
};
for (int x = 0, y = radius; y >= x; x++) {
drawBresenham(center_x, center_y, x, y);
if (decision > 0) {
y--;
decision += 4 * (x - y) + 10;
}
else {
decision += 4 * x + 6;
}
}
return data;
}
//Edit
Oh wow, I just realized how old this question is.

Printing circle to the screen

Why am I not getting a circle printed to the screen when I run the following block of code?
It doesn't print it accurately, seems like something I'm doing wrong when it's scanning the coordinates.
public class Question2 {
public static void main(String[] args) {
DrawMeACircle(3, 3, 3); // should print to the screen an ellipse looking circle
}
public static void DrawMeACircle(double posX, double posY, double radius) {
double xaxis = 20; // scanning the coordinates
double yaxis = 20; // " "
for (double x = 0; x < xaxis; x++) {
for (double y = 0; y < yaxis; y++) {
//using the formula for a cicrle
double a = Math.abs((posX - x) * (posX - x));
double b = Math.abs((posY - y) * (posY - y));
double c = Math.abs(a + b);
double d = Math.abs(radius * radius);
// checking if the formula stands correct at each coordinate scanned
if ( c == d) {
System.out.print('#');
}
else {
System.out.print(' ');
}
}
System.out.println();
}
}
}
I'm afraid that comparing doubles like this
if ( c == d) {
System.out.print('#');
}
is a very unreliable, they're probably missing by a little bit, and you're not printing the circle where you need to.
I'd recommend checking for a range instead
double arbitraryNumber = 2;
if ( math.abs(c - d) < arbitraryNumber)) {
System.out.print('#');
}
Or, if you want a more reliable way, I'd make a 2-d char array and treat it as a coordinate system, and then fill the 2-d array with the circle and print the array.
You can fill the array with a little bit of trigonometry. Just figure out where the dot should be every few degrees(or radians) until you've gone 360 degrees

Trouble calculating the distance function in bezier clipping

I'm attempting to implement a curve interesection algorithm known as bezier clipping, which is described in a section towards the end of this article (though the article calls it "fat line clipping"). I've been following through the article and source code of the example (available here).
Note: Additional sources include this paper. More will be posted if I can find them.
A central part of this algorithm is calculating a "distance function" between curve1 and a "baseline" of curve2 (which is a line from one end point of curve2 to another). So I'd have something to compare my results to, I used the curves from the source code of the first example. I managed to replicate the shape of the distance function from the example, but the distance location of the function was off. Upon trying another curve, the distance function was nowhere near the other two curves, despite both clearly intersecting. I might be naive to the workings of this algorithm, but I think that would result in no intersection being detected.
From what I understand (which could quite possibly be wrong), the process of defining the distance function involves expressing the baseline of curve 2 in the form xa + yb + c = 0, where a2 + b2 = 1. The coefficients were obtained by rearranging the terms of the line in the form y = ux + v, where u is equal to the slope, and x and y are any points on the baseline. The formula can be rearranged to give v: v = y - ux. Rearranging the formula again, we obtain -u*x + 1*y - v = 0, where a = -u, b = 1, and c = -v. To assure the condition a2 + b2 = 1, the coefficients are divided by a scalar of Math.sqrt(uu + 1). This representation of the line is then substituted into the function of the other curve (the one the baseline isn't associated with) to get the distance function. This distance function is represented as a bezier curve, with yi = aPi x + b*Pi y + c and xi = (1 - t)x1 + tx2, where t is equal to 0, 1/3, 2/3, and 3m x1 and x2 are the endpoints of the baseline, and Pi are the control points of the curve1.
Below are a few cuts of the source code of the example program (written in the language processing) involved with calculating the distance function, which, oddly, uses a slightly different approach to the above paragraph for calculating the alternative representation of the baseline.
/**
* Set up four points, to form a cubic curve, and a static curve that is used for intersection checks
*/
void setupPoints()
{
points = new Point[4];
points[0] = new Point(85,30);
points[1] = new Point(180,50);
points[2] = new Point(30,155);
points[3] = new Point(130,160);
curve = new Bezier3(175,25, 55,40, 140,140, 85,210);
curve.setShowControlPoints(false);
}
...
flcurve = new Bezier3(points[0].getX(), points[0].getY(),
points[1].getX(), points[1].getY(),
points[2].getX(), points[2].getY(),
points[3].getX(), points[3].getY());
...
void drawClipping()
{
double[] bounds = flcurve.getBoundingBox();
// get the distances from C1's baseline to the two other lines
Point p0 = flcurve.points[0];
// offset distances from baseline
double dx = p0.x - bounds[0];
double dy = p0.y - bounds[1];
double d1 = sqrt(dx*dx+dy*dy);
dx = p0.x - bounds[2];
dy = p0.y - bounds[3];
double d2 = sqrt(dx*dx+dy*dy);
...
double a, b, c;
a = dy / dx;
b = -1;
c = -(a * flcurve.points[0].x - flcurve.points[0].y);
// normalize so that a² + b² = 1
double scale = sqrt(a*a+b*b);
a /= scale; b /= scale; c /= scale;
// set up the coefficients for the Bernstein polynomial that
// describes the distance from curve 2 to curve 1's baseline
double[] coeff = new double[4];
for(int i=0; i<4; i++) { coeff[i] = a*curve.points[i].x + b*curve.points[i].y + c; }
double[] vals = new double[4];
for(int i=0; i<4; i++) { vals[i] = computeCubicBaseValue(i*(1/3), coeff[0], coeff[1], coeff[2], coeff[3]); }
translate(0,100);
...
// draw the distance Bezier function
double range = 200;
for(float t = 0; t<1.0; t+=1.0/range) {
double y = computeCubicBaseValue(t, coeff[0], coeff[1], coeff[2], coeff[3]);
params.drawPoint(t*range, y, 0,0,0,255); }
...
translate(0,-100);
}
...
/**
* compute the value for the cubic bezier function at time=t
*/
double computeCubicBaseValue(double t, double a, double b, double c, double d) {
double mt = 1-t;
return mt*mt*mt*a + 3*mt*mt*t*b + 3*mt*t*t*c + t*t*t*d; }
And here is the class (an extension of javax.swing.JPanel) I wrote to recreate the above code:
package bezierclippingdemo2;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class ReplicateBezierClippingPanel extends JPanel {
CubicCurveExtended curve1, curve2;
public ReplicateBezierClippingPanel(CubicCurveExtended curve1, CubicCurveExtended curve2) {
this.curve1 = curve1;
this.curve2 = curve2;
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(1));
g2d.setColor(Color.black);
drawCurve1(g2d);
drawCurve2(g2d);
drawDistanceFunction(g2d);
}
public void drawCurve1(Graphics2D g2d) {
double range = 200;
double t = 0;
double prevx = curve1.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrlx1*(1 - t)*(1 - t)*t + 3*curve1.ctrlx2*(1 - t)*t*t + curve1.x2*t*t*t;
double prevy = curve1.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrly1*(1 - t)*(1 - t)*t + 3*curve1.ctrly2*(1 - t)*t*t + curve1.y2*t*t*t;
for(t += 1.0/range; t < 1.0; t += 1.0/range) {
double x = curve1.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrlx1*(1 - t)*(1 - t)*t + 3*curve1.ctrlx2*(1 - t)*t*t + curve1.x2*t*t*t;
double y = curve1.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve1.ctrly1*(1 - t)*(1 - t)*t + 3*curve1.ctrly2*(1 - t)*t*t + curve1.y2*t*t*t;
g2d.draw(new LineExtended(prevx, prevy, x, y));
prevx = x;
prevy = y;
}
}
public void drawCurve2(Graphics2D g2d) {
double range = 200;
double t = 0;
double prevx = curve2.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrlx1*(1 - t)*(1 - t)*t + 3*curve2.ctrlx2*(1 - t)*t*t + curve2.x2*t*t*t;
double prevy = curve2.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrly1*(1 - t)*(1 - t)*t + 3*curve2.ctrly2*(1 - t)*t*t + curve2.y2*t*t*t;
for(t += 1.0/range; t < 1.0; t += 1.0/range) {
double x = curve2.x1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrlx1*(1 - t)*(1 - t)*t + 3*curve2.ctrlx2*(1 - t)*t*t + curve2.x2*t*t*t;
double y = curve2.y1*(1 - t)*(1 - t)*(1 - t) + 3*curve2.ctrly1*(1 - t)*(1 - t)*t + 3*curve2.ctrly2*(1 - t)*t*t + curve2.y2*t*t*t;
g2d.draw(new LineExtended(prevx, prevy, x, y));
prevx = x;
prevy = y;
}
}
public void drawDistanceFunction(Graphics2D g2d) {
double a = (curve1.y2 - curve1.y1)/(curve1.x2 - curve1.x1);
double b = -1;
double c = -(a*curve1.x1 - curve1.y1);
double scale = Math.sqrt(a*a + b*b);
a /= scale;
b /= scale;
c /= scale;
double y1 = a*curve2.x1 + b*curve2.y1 + c;
double y2 = a*curve2.ctrlx1 + b*curve2.ctrly1 + c;
double y3 = a*curve2.ctrlx1 + b*curve2.ctrly2 + c;
double y4 = a*curve2.x2 + b*curve2.y2 + c;
double range = 200;
double t = 0;
double prevx = t*range;
double prevy = (1 - t)*(1 - t)*(1 - t)*y1 + 3*(1 - t)*(1 - t)*t*y2 + 3*(1 - t)*t*t*y3 + t*t*t*y4;
for(t += 1.0/range; t < 1.0; t += 1.0/range) {
double x = t*range;
double y = (1 - t)*(1 - t)*(1 - t)*y1 + 3*(1 - t)*(1 - t)*t*y2 + 3*(1 - t)*t*t*y3 + t*t*t*y4;
g2d.draw(new LineExtended(prevx, prevy, x, y));
prevx = x;
prevy = y;
}
}
}
Where CubicCurveExtended and LineExtended are minor extensions of java.awt.geom.CubicCurve2D.Double and java.awt.geom.Line2D.Double. Before the curves are passed into the constructor, the curves are rotated uniformly so curve1's endpoints are level, resulting in a slope of zero for the baseline.
For an input of (485, 430, 580, 60, 430, 115, 530, 160) for curve 1 and (575, 25, 455, 60, 541, 140, 486, 210) for curve2 (keep in mind that these values are rotated by the negative angle between the endpoints of curve1), the result is shown below (the distance function is the relatively smooth looking curve off in the distance):
I'm really not sure what I got wrong. The y values seem to be arranged in the right pattern, but are distant from the two curves it's based on. I realize it's possible I have the x values might be arranged at intervals along the curve rather than the baseline, but the y values are what I'm really confused about. If someone can take a look at this and explain what I got wrong, I'd really appreciate it. Thanks for taking the time to read this rather lengthy question. If more details are needed, feel free to tell me in comments.
Update: I've tested the representation of the line I've calculated. The ax + by + c = 0 representation apparently still represents the same line, as I can still plug in x1 and get y1. Additionally, for any two coordinate pairs plugged into the function, f(x, y) = 0 holds. Furthermore, I've found both the representation described in the article and the one actually used in the source code interchangeably represent the same line. From all this, I can assume the problem doesn't lie in calculating the line. An additional point of interest
Your distance function should not necessarily be anywhere near your two original curves: It's using a completely different coordinate system, i.e. t vs D, as opposed to your original curves using x and y. [edit] i.e. t only goes up to 1.0, and measures how far along, as a ratio of the total length, you are along your curve, and D measuring the distance your curve2 is from curve1's baseline.
Also, when you say ""distance function" between curve1 and a "baseline" of curve2" I think you've mixed up curve1 and curve2 here as in your code you are clearly using the baseline of curve1.
Also the algorithm assumes that "every Bézier curve is fully contained by the polygon that connects all the start/control/end points, known as its "convex hull"" which [edit] in your case for curve1 is a triangle, where the control point for the second starting value is not a vertex. I'm not sure how this affects the algorithm though.
Otherwise, it looks like your distance calculations are fine (although you could really do with optimising things a bit :) ).

Categories

Resources