Change iteration method to recursion to calculate Polygon area - java

I did some coding in Java to calculate the area of Polygon. In my code, i'm using iteration "for" to get the input of coordinate value of X and Y in ArrayList. My code calculation run successfully and I did get the correct output.
My problem is that I want to change my code to use recursion so that computeArea(ArrayList c, int size) can be call recursively and I don't want to use an iteration "for" in this method.
I would be really appreciate to those who can show me how to change this method from iteration to recursive.
Thanks.
Below is my code for Polygon.java
import java.awt.geom.Point2D;
import java.util.ArrayList;
public class Polygon
{
private ArrayList<Point2D.Double> corners;
private double area;
private double adding;
private double minus;
private double exAdding;
private double exMinus;
public Polygon()
{
corners = new ArrayList<Point2D.Double>();
area = 0;
adding = 0;
minus = 0;
exAdding = 0;
exMinus = 0;
}
//add point to the array
public void add (Point2D.Double p)
{
corners.add(p);
}
//computes area of polygon
public double getArea()
{
return computeArea(corners, corners.size());
}
public double computeArea(ArrayList<Point2D.Double> c, int size)
{
if (size < 0)
{
return 0;
}
else
{
for (int i = 0; i < size-1; i++)
{
double xA = c.get(i).getX();
double yA = c.get(i).getY();
double xB = c.get(i+1).getX();
double yB = c.get(i+1).getY();
exAdding = c.get(size-1).getX()*c.get(size-size).getY();
adding = adding + xA*yB;
exMinus = c.get(size-1).getY()*c.get(size-size).getX();
minus = minus + yA*xB;
//System.out.println("test adding : " + adding);
}
//System.out.println("extra adding : " + exAdding);
area = Math.abs((adding+exAdding) - (minus+exMinus));
return area/2;
}
}
}
This is my code for PolygonTester.java
import java.awt.geom.Point2D;
public class PolygonTester
{
public static void main (String[] args)
{
//create square
Polygon p = new Polygon();
p.add(new Point2D.Double(10, 20));
p.add(new Point2D.Double(20, 20));
p.add(new Point2D.Double(20, 10));
p.add(new Point2D.Double(10, 10));
System.out.println("Area : " + p.getArea());
System.out.println("Expected : 100");
//create square
Polygon p1 = new Polygon();
p1.add(new Point2D.Double(3, 4));
p1.add(new Point2D.Double(5, 11));
p1.add(new Point2D.Double(12, 8));
p1.add(new Point2D.Double(9, 5));
p1.add(new Point2D.Double(5, 6));
System.out.println("Area : " + p1.getArea());
System.out.println("Expected : 30");
//regular hexagon with radius 1
p = new Polygon();
for (int i = 0; i < 6; i++)
{
p.add(new Point2D.Double(Math.sin(i*Math.PI/3), Math.cos(i*Math.PI/3)));
}
System.out.println("Area : " + p.getArea());
System.out.println("Expected : " + 3*Math.sqrt(3)/2);
}
}

Add this method to your program
public void recursionMethod(ArrayList<Point2D.Double> c, int size, int count){
if(count<size-1){
double xA = c.get(count).getX();
double yA = c.get(count).getY();
double xB = c.get(count+1).getX();
double yB = c.get(count+1).getY();
exAdding = c.get(size-1).getX()*c.get(size-size).getY();
adding = adding + xA*yB;
exMinus = c.get(size-1).getY()*c.get(size-size).getX();
minus = minus + yA*xB;
recursionMethod(c, size, count+1);
}
}
and call this method instead of your for loop
recursionMethod(c, size, 0);

I suspect you might be missing the point of recursion. While it is true that most iterative methods can be replaced with recursion and vice versa, in many cases one technique or the other is a natural fit for your problem. In your case this is naturally an iterative problem: the polygon is defined by its vertices and calculating the area involves visiting each one consecutively.
I would suggest picking a more naturally recursive problem if you wish to practice recursion. In general the form of recursion is:
result recursiveproblem(context)
if context is simple enough to have obvious answer
return obvious answer
else
break context into smaller pieces
call recursive problem on each of the smaller pieces
combine the answers
So this fits situations where there is a natural simple state with an obvious answer as well as ways of breaking down and combining answers.
The canonical example is factorial. By definition factorial(0) = 1 and factorial(n) = n * factorial(n - 1)
Already that looks very naturally recursive in a way that calculating area doesn't.

I have just been looking at using recursion to calculate centroids. I have written the following recursive solution for the equations listed in the Wikipedia page for Centroids of Polygons. You will see that you need to use the first two 2D points on the list to calculate areas etc and then add to the same function applied to the remainder of the list of 2D points. This seems to me to meet the criterion set by "sprinter" (but perhaps I am missing something).
The following F# code generates a tuple containing the area of the polygon and the two centroids:
let AddTuple3 (a, b, c) (d, e, f) = (a+d,b+e,c+f)
let Comb3 (X1, Y1) (X2, Y2) =
let A = (X1 * Y2 - X2 * Y1) / 2.0
(A, (X1 + X2) * A / 3.0, (Y1 + Y2) * A / 3.0)
let SecProp2D PtLst =
let rec RecSecProp2D PtLst =
match PtLst with
| [A] -> (0.0, 0.0, 0.0)
| B::C -> AddTuple3 (Comb3 B C.Head) (RecSecProp2D C)
| _ -> (0.0, 0.0, 0.0)
let (A, B, C) = RecSecProp2D PtLst
(A, B/A, C/A)
// Testing...
let PtLst1 = [(0.0,0.0);(2.0,0.0);(2.0,5.0);(0.0,5.0);(0.0,0.0)] // 2x5 rectangle
printfn "Recursive Area, Cx & Cy are %A" (SecProp2D PtLst1)
You can play with it on dotnetFiddle.

Related

Simple polygon from unordered points

I've been trying to create an algorithm for finding the order of points in a simple polygon.
The aim is when given points on a 2D plane (there is always possible to form a simple polygon btw) I output the order of points in a valid simple polygon. All points must be part of said polygon.
I've somewhat achieved this, but it fails for some test cases. The way I have done this is by finding the geometrical centre
int centerX = (lowX + highX) / 2;
int centerY = (lowY + highY) / 2;
Point center = new Point(centerX, centerY, -1);
and then sorting all points by their polar angle.
Collections.sort(points, (a, b) -> {
if(a == b || a.equals(b)) {
return 0;
}
double aTheta = Math.atan2((long)a.y - center.y, (long)a.x - center.x);
double bTheta = Math.atan2((long)b.y - center.y, (long)b.x - center.x);
if(aTheta < bTheta) {
return -1;
}
else if(aTheta > bTheta) {
return 1;
}
else {
double aDist = Math.sqrt((((long)center.x - a.x) * ((long)center.x - a.x)) +
(((long)center.y - a.y) * ((long)center.y - a.y)));
double bDist = Math.sqrt((((long)center.x - b.x) * ((long)center.x - b.x)) +
(((long)center.y - b.y) * ((long)center.y - b.y)));
if (aDist < bDist) {
return -1;
}
else {
return 1;
}
}
});
I'm struggling with finding out what makes this break for some of the test cases. Any help or pointers are greatly appreciated! Also wondering if there are any efficient, yet not overly complicated algorithms that can perform this.
UPDATE
I've found one of the failing test cases: When given the points (101, 101), (100, 100), (105, 100), (103, 100), (107, 100), (102, 100), (109, 100) Labeled 0 to 9 respectively
My program outputs 2 4 6 0 3 5 1 but it is not a valid simple polygon
It should be a permutation of 1 0 6 4 2 3 5
Here's a Java implementation of the nice answer already provided by Reblochon Masque.
Note that instead of using any trig functions to calculate angles we use a comparison of the relative orientation (or turn direction) from the min point to each of the two points being compared. Personally I find this more elegant than using angles, but others may disagree. However, as with any calculations based on double the orient2D method is susceptible to errors in rounding.
Also, when there's a tie based on orientation, because the min point and the two points are collinear, we break the tie by considering the relative ordering of the two points. This means that we'll visit points in order, with no "switchbacks", which I think is preferable.
static List<Point2D> simplePolygon(Collection<Point2D> points)
{
final Point2D min = minPoint2D(points);
List<Point2D> simple = new ArrayList<>(points);
Collections.sort(simple, (p1, p2) ->
{
int cmp = orient2D(min, p2, p1);
if(cmp == 0)
cmp = order2D(p1, p2);
return cmp;
});
return simple;
}
// return lowest, leftmost point
static Point2D minPoint2D(Collection<Point2D> points)
{
Point2D min = null;
for(Point2D p : points)
if(min == null || order2D(p, min) < 0) min = p;
return min;
}
// order points by increasing y, break ties by increasing x
static int order2D(Point2D p1, Point2D p2)
{
if(p1.getY() < p2.getY()) return -1;
else if(p1.getY() > p2.getY()) return 1;
else if(p1.getX() < p2.getX()) return -1;
else if(p1.getX() > p2.getX()) return 1;
else return 0;
}
// Does p involve a CCW(+1), CW(-1) or No(0) turn from the line p1-p2
static int orient2D(Point2D p1, Point2D p2, Point2D p)
{
double dx = p2.getX() - p1.getX();
double dy = p2.getY() - p1.getY();
double px = p.getX() - p1.getX();
double py = p.getY() - p1.getY();
double dot = py * dx - px * dy;
return dot < 0 ? -1 : dot > 0 ? 1 : 0;
}
Test:
int[] a = {101, 101, 100, 100, 105, 100, 103, 100, 107, 100, 102, 100, 109, 100};
List<Point2D> points = new ArrayList<>();
for(int i=0; i<a.length; i+=2)
points.add(new Point2D.Double(a[i], a[i+1]));
List<Point2D> simple = simplePolygon(points);
for(Point2D p : simple) System.out.println(p);
Output:
Point2D.Double[100.0, 100.0]
Point2D.Double[102.0, 100.0]
Point2D.Double[103.0, 100.0]
Point2D.Double[105.0, 100.0]
Point2D.Double[107.0, 100.0]
Point2D.Double[109.0, 100.0]
Point2D.Double[101.0, 101.0]
Which I believe is correct.
here is an easy to implement O(n logn) algorithm that is guaranteed to produce a simple polygon (no edge crossings)
1- find the point the most south, (and the most westwards if you have a tie with the y values).
2- Sort all points based on their angle between this most south point, and the horizontal line.
3- the ordered sequence is a simple polygon.
In some rare cases, some points may not form a vertex, but be included in an edge, if they are collinear at the same angle.

Java - Loop through all pixels in a 2D circle with center x,y and radius?

I have a BufferedImage that I want to loop through. I want to loop through all pixels inside a circle with radius radius which has a center x and y at x,y.
I do not want to loop through it in a square fashion. It would also be nice if I could do this and cut O complexity in the process, but this is not needed. Since area of a circle is pi * r^2 and square would be 4 * r^2 that would mean I could get 4 / pi better O complexity if I looped in a perfect circle. If the circle at x,y with a radius of radius would happen to be larger than the dimensions of the BufferedImage, then prevent going out of bounds (this can be done with an if statement I believe to prevent going out of bounds at each check).
Examples: O means a recorded pixel while X means it was not looped over.
Radius 1
X O X
O O O
X O X
Radius 2
X X O X X
X O O O X
O O O O O
X O O O X
X X O X X
I think the proper way to do this is with trigonometric functions but I can't quite get it in my head. I know one easy part is that all Pixels up, left, right, and down in radius from the origin are added. Would like some advice incase anyone has any.
private LinkedList<Integer> getPixelColorsInCircle(final int x, final int y, final int radius)
{
final BufferedImage img; // Obtained somewhere else in the program via function call.
final LinkedList<Integer> ll = new Linkedlist<>();
for (...)
for (...)
{
int x = ...;
int y = ...;
ll.add(img.getRGB(x, y)); // Add the pixel
}
}
Having the center of the circle O(x,y) and the radius r the following coordinates (j,i) will cover the circle.
for (int i = y-r; i < y+r; i++) {
for (int j = x; (j-x)^2 + (i-y)^2 <= r^2; j--) {
//in the circle
}
for (int j = x+1; (j-x)*(j-x) + (i-y)*(i-y) <= r*r; j++) {
//in the circle
}
}
Description of the approach:
Go from the top to the bottom perpendicularly through the line which goes through the circle center.
Move horizontally till you reach the coordinate outside the circle, so you only hit two pixels which are outside of the circle in each row.
Move till the lowest row.
As it's only the approximation of a circle, prepare for it might look like a square for small rs
Ah, and in terms of Big-O, making 4 times less operations doesn't change complexity.
Big-O =/= complexity
While xentero's answer works, I wanted to check its actual performance (inCircle1) against the algorithm that OP thinks is too complex (inCircle2):
public static ArrayList<Point> inCircle1(Point c, int r) {
ArrayList<Point> points = new ArrayList<>(r*r); // pre-allocate
int r2 = r*r;
// iterate through all x-coordinates
for (int i = c.y-r; i <= c.y+r; i++) {
// test upper half of circle, stopping when top reached
for (int j = c.x; (j-c.x)*(j-c.x) + (i-c.y)*(i-c.y) <= r2; j--) {
points.add(new Point(j, i));
}
// test bottom half of circle, stopping when bottom reached
for (int j = c.x+1; (j-c.x)*(j-c.x) + (i-c.y)*(i-c.y) <= r2; j++) {
points.add(new Point(j, i));
}
}
return points;
}
public static ArrayList<Point> inCircle2(Point c, int r) {
ArrayList<Point> points = new ArrayList<>(r*r); // pre-allocate
int r2 = r*r;
// iterate through all x-coordinates
for (int i = c.y-r; i <= c.y+r; i++) {
int di2 = (i-c.y)*(i-c.y);
// iterate through all y-coordinates
for (int j = c.x-r; j <= c.x+r; j++) {
// test if in-circle
if ((j-c.x)*(j-c.x) + di2 <= r2) {
points.add(new Point(j, i));
}
}
}
return points;
}
public static <R extends Collection> R timing(Supplier<R> operation) {
long start = System.nanoTime();
R result = operation.get();
System.out.printf("%d points found in %dns\n", result.size(),
TimeUnit.NANOSECONDS.toNanos(System.nanoTime() - start));
return result;
}
public static void testCircles(int r, int x, int y) {
Point center = new Point(x, y);
ArrayList<Point> in1 = timing(() -> inCircle1(center, r));
ArrayList<Point> in2 = timing(() -> inCircle2(center, r));
HashSet<Point> all = new HashSet<>(in1);
assert(all.size() == in1.size()); // no duplicates
assert(in1.size() == in2.size()); // both are same size
all.removeAll(in2);
assert(all.isEmpty()); // both are equal
}
public static void main(String ... args) {
for (int i=100; i<200; i++) {
int x = i/2, y = i+1;
System.out.println("r = " + i + " c = [" + x + ", " + y + "]");
testCircles(i, x, y);
}
}
While this is by no means a precise benchmark (not much warm-up, machine doing other things, not smoothing outliers via n-fold repetition), the results on my machine are as follows:
[snip]
119433 points found in 785873ns
119433 points found in 609290ns
r = 196 c = [98, 197]
120649 points found in 612985ns
120649 points found in 584814ns
r = 197 c = [98, 198]
121905 points found in 619738ns
121905 points found in 572035ns
r = 198 c = [99, 199]
123121 points found in 664703ns
123121 points found in 778216ns
r = 199 c = [99, 200]
124381 points found in 617287ns
124381 points found in 572154ns
That is, there is no significant difference between both, and the "complex" one is often faster. My explanation is that integer operations are really, really fast - and examining a few extra points on the corners of a square that do not fall into the circle is really fast, compared to the cost of processing all those points that do fall into the circle (= the expensive part is calling points.add, and it is called the exact same number of times in both variants).
In the words of Knuth:
programmers have spent far too much time worrying about efficiency in
the wrong places and at the wrong times; premature optimization is the
root of all evil (or at least most of it) in programming
Then again, if you really need an optimal way of iterating the points of a circle, may I suggest using Bresenham's Circle Drawing Algorithm, which can provide all points of a circumference with minimal operations. It will again be premature optimization if you are actually going do anything with the O(n^2) points inside the circle, though.

Extrapolation in java

I've been able to use Apache Math's interpolation using the LinearInterpolator().interpolate(x1, y1). Unfortunately, I could not find a way to extrapolate.
How can I do linear extrapolation in java?
x1 = [1, 2, 3, 4, 5];
y1 = [2, 4, 8, 16, 32];
I would like to know the values of any x2 not just the one in the range of the x1.
If I try to extract the value of 6 I get an: OutOfRangeException if {#code v} is outside of the domain of the
* spline function (smaller than the smallest knot point or larger than the
largest knot point).
Edit: Here is my simple interpolate function. I would like an option to enable the extrapolation just like in MathLab(interp2). Using x1 and y1 arrays an input for that function I get the Apache's OutOfRangeException because the value 6 is not contained in the x1 array.
public static List<Double> interpolateLinear(double[] x1, double[] y1, Double[] x2) {
List<Double> resultList;
final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
resultList = Arrays.stream(x2).map(aDouble -> function.value(aDouble)).collect(Collectors.toList());
return resultList;
}
Edit2: Had to read a little bit on the .value method of the PolynomialSplineFunction object to get it right but there it goes (all the credit goes to user Joni) Thanks man:
public static double[] interpolateLinear(double[] x1, double[] y1, double[] x2) {
final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
final PolynomialFunction[] splines = function.getPolynomials();
final PolynomialFunction firstFunction = splines[0];
final PolynomialFunction lastFunction = splines[splines.length - 1];
final double[] knots = function.getKnots();
final double firstKnot = knots[0];
final double lastKnot = knots[knots.length - 1];
double[] resultList = Arrays.stream(x2).map(aDouble -> {
if (aDouble > lastKnot) {
return lastFunction.value(aDouble - knots[knots.length - 2]);
} else if (aDouble < firstKnot)
return firstFunction.value(aDouble - knots[0]);
return function.value(aDouble);
}).toArray();
return resultList;
}
You can get the first and last polynomial splines from the interpolator, and use those to extrapolate.
PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
PolynomialFunction[] splines = function.getPolynomials();
PolynomialFunction first = splines[0];
PolynomialFunction last = splines[splines.length-1];
// use first and last to extrapolate
You won't get 64 from 6 though. You should expect 48 from a linear extrapolation. Which goes to show that extrapolation is bound to give you wrong answers.
I have similar problem, the interpolation part is a cubic spline function, and math3.analysis.polynomials.PolynomialSplineFunction does not support the extrapolation.
In the end, I decide to write the linear extrapolation based on the leftest(/rightest) two points (i.e. x1,x2 and y1, y2). I need the extrapolation part to avoid that the function fails or get any very irregular values in the extrapolation region. In my example, I hard coded such that the extrapolated value should stay in [0.5* y1, 2 * y1] (left side) or [0.5 * yn, 2 *yn] (right side).
As mentioned by Joni, the extrapolation is dangerous, and it could leads to unexpected results. Be careful. The linear extrapolation can be replaced by any other kind extrapolation, depending on how you write the code (e.g. using the derivative at the right/left point and inferring a quadratic function for extrapolation.)
public static double getValue(PolynomialSplineFunction InterpolationFunction, double v) {
try {
return InterpolationFunction.value(v);
} catch (OutOfRangeException e) {
// add the extrapolation function: we use linear extrapolation based on the slope of the two points on the left or right
double[] InterpolationKnots = InterpolationFunction.getKnots();
int n = InterpolationKnots.length;
double first, second, firstValue, secondValue;
if (v < InterpolationKnots[0])
{ // extrapolation from the left side, linear extrapolation based on the first two points on the left
first = InterpolationKnots[0]; // the leftest point
second = InterpolationKnots[1]; // the second leftest point
}
else { // extrapolation on the right side, linear extrapolation based on the first two points on the right
first = InterpolationKnots[n - 1]; // the rightest point
second = InterpolationKnots[n - 2]; // the second rightest point
}
firstValue = InterpolationFunction.value(first);
secondValue = InterpolationFunction.value(second);
double extrapolatedValue = (firstValue - secondValue) / (first - second) * (v - first) + firstValue;
// add a boundary to the extrapolated value so that it is within [0.5, 2] * firstValue
if (extrapolatedValue > 2 * firstValue){ extrapolatedValue = 2 * firstValue;}
if (extrapolatedValue < 0.5 * firstValue) {extrapolatedValue = 0.5* firstValue;}
return extrapolatedValue;
}
}
Just sharing a complete example based on the answer provided by Joni:
import java.util.Arrays;
import org.apache.commons.math3.analysis.interpolation.LinearInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialFunction;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
public class App {
public static void main(String[] args) {
double[] x1 = { 1, 2, 3, 4, 5 };
double[] y1 = { 2, 4, 8, 16, 32 };
double[] x2 = { 6, 7 };
double[] res = interpolateLinear(x1, y1, x2);
for (int i = 0; i < res.length; i++) {
System.out.println("Value: " + x2[i] + " => extrapolation: " + res[i]);
}
}
public static double[] interpolateLinear(double[] x1, double[] y1, double[] x2) {
final PolynomialSplineFunction function = new LinearInterpolator().interpolate(x1, y1);
final PolynomialFunction[] splines = function.getPolynomials();
final PolynomialFunction firstFunction = splines[0];
final PolynomialFunction lastFunction = splines[splines.length - 1];
final double[] knots = function.getKnots();
final double firstKnot = knots[0];
final double lastKnot = knots[knots.length - 1];
double[] resultList = Arrays.stream(x2).map(aDouble -> {
if (aDouble > lastKnot) {
return lastFunction.value(aDouble - knots[knots.length - 2]);
} else if (aDouble < firstKnot)
return firstFunction.value(aDouble - knots[0]);
return function.value(aDouble);
}).toArray();
return resultList;
}
}

How do I fix this heart?

Seeing as Valentine's Day is fast approaching, I decided to create a heart. So I found this heart from mathematica.se:
I played around in Mathematica (solved for z, switching some variables around) to get this equation for the z-value of the heart, given the x and y values (click for full-size):
I faithfully ported this equation to Java, dealing with a couple out-of-bounds cases:
import static java.lang.Math.cbrt;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
...
public static double heart(double xi, double yi) {
double x = xi;
double y = -yi;
double temp = 5739562800L * pow(y, 3) + 109051693200L * pow(x, 2) * pow(y, 3)
- 5739562800L * pow(y, 5);
double temp1 = -244019119519584000L * pow(y, 9) + pow(temp, 2);
//
if (temp1 < 0) {
return -1; // this is one possible out of bounds location
// this spot is the location of the problem
}
//
double temp2 = sqrt(temp1);
double temp3 = cbrt(temp + temp2);
if (temp3 != 0) {
double part1 = (36 * cbrt(2) * pow(y, 3)) / temp3;
double part2 = 1 / (10935 * cbrt(2)) * temp3;
double looseparts = 4.0 / 9 - 4.0 / 9 * pow(x, 2) - 4.0 / 9 * pow(y, 2);
double sqrt_body = looseparts + part1 + part2;
if (sqrt_body >= 0) {
return sqrt(sqrt_body);
} else {
return -1; // this works; returns -1 if we are outside the heart
}
} else {
// through trial and error, I discovered that this should
// be an ellipse (or that it is close enough)
return Math.sqrt(Math.pow(2.0 / 3, 2) * (1 - Math.pow(x, 2)));
}
}
The only problem is that when temp1 < 0, I cannot simply return -1, like I do:
if (temp1 < 0) {
return -1; // this is one possible out of bounds location
// this spot is the location of the problem
}
That's not the behavior of the heart at that point. As it is, when I try to make my image:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import static java.lang.Math.cbrt;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
public class Heart {
public static double scale(int x, int range, double l, double r) {
double width = r - l;
return (double) x / (range - 1) * width + l;
}
public static void main(String[] args) throws IOException {
BufferedImage img = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
// this is actually larger than the max heart value
final double max_heart = 0.679;
double max = 0.0;
for (int x = 0; x < img.getWidth(); x++) {
for (int y = 0; y < img.getHeight(); y++) {
double xv = scale(x, img.getWidth(), -1.2, 1.2);
double yv = scale(y, img.getHeight(), -1.3, 1);
double heart = heart(xv, yv); //this isn't an accident
// yes I don't check for the return of -1, but still
// the -1 values return a nice shade of pink: 0xFFADAD
// None of the other values should be negative, as I did
// step through from -1000 to 1000 in python, and there
// were no negatives that were not -1
int r = 0xFF;
int gb = (int) (0xFF * (max_heart - heart));
int rgb = (r << 16) | (gb << 8) | gb;
img.setRGB(x, y, rgb);
}
}
ImageIO.write(img, "png", new File("location"));
}
// heart function clipped; it belongs here
}
I get this:
Look at that dip at the top! I tried changing that problematic -1 to a .5, resulting in this:
Now the heart has horns. But it becomes clear where that problematic if's condition is met.
How can I fix this problem? I don't want a hole in my heart at the top, and I don't want a horned heart. If I could clip the horns to the shape of a heart, and color the rest appropriately, that would be perfectly fine. Ideally, the two sides of the heart would come together as a point (hearts have a little point at the join), but if they curve together like shown in the horns, that would be fine too. How can I achieve this?
The problem is simple. If we look at that horseshoe region, we get imaginary numbers. For part of it, it should belong to our heart. In that region, if we were to evaluate our function (by math, not by programming), the imaginary parts of the function cancel. So it should look like this (generated in Mathematica):
Basically, the function for that part is almost identical; we just have to do arithmetic with complex numbers instead of real numbers. Here's a function that does exactly that:
private static double topOfHeart(double x, double y, double temp, double temp1) {
//complex arithmetic; each double[] is a single number
double[] temp3 = cbrt_complex(temp, sqrt(-temp1));
double[] part1 = polar_reciprocal(temp3);
part1[0] *= 36 * cbrt(2) * pow(y, 3);
double[] part2 = temp3;
part2[0] /= (10935 * cbrt(2));
toRect(part1, part2);
double looseparts = 4.0 / 9 - 4.0 / 9 * pow(x, 2) - 4.0 / 9 * pow(y, 2);
double real_part = looseparts + part1[0] + part2[0];
double imag_part = part1[1] + part2[1];
double[] result = sqrt_complex(real_part, imag_part);
toRect(result);
// theoretically, result[1] == 0 should work, but floating point says otherwise
if (Math.abs(result[1]) < 1e-5) {
return result[0];
}
return -1;
}
/**
* returns a specific cuberoot of this complex number, in polar form
*/
public static double[] cbrt_complex(double a, double b) {
double r = Math.hypot(a, b);
double theta = Math.atan2(b, a);
double cbrt_r = cbrt(r);
double cbrt_theta = 1.0 / 3 * (2 * PI * Math.floor((PI - theta) / (2 * PI)) + theta);
return new double[]{cbrt_r, cbrt_theta};
}
/**
* returns a specific squareroot of this complex number, in polar form
*/
public static double[] sqrt_complex(double a, double b) {
double r = Math.hypot(a, b);
double theta = Math.atan2(b, a);
double sqrt_r = Math.sqrt(r);
double sqrt_theta = 1.0 / 2 * (2 * PI * Math.floor((PI - theta) / (2 * PI)) + theta);
return new double[]{sqrt_r, sqrt_theta};
}
public static double[] polar_reciprocal(double[] polar) {
return new double[]{1 / polar[0], -polar[1]};
}
public static void toRect(double[]... polars) {
for (double[] polar: polars) {
double a = Math.cos(polar[1]) * polar[0];
double b = Math.sin(polar[1]) * polar[0];
polar[0] = a;
polar[1] = b;
}
}
To join this with your program, simply change your function to reflect this:
if (temp1 < 0) {
return topOfHeart(x, y, temp, temp1);
}
And running it, we get the desired result:
It should be pretty clear that this new function implements exactly the same formula. But how does each part work?
double[] temp3 = cbrt_complex(temp, sqrt(-temp1));
cbrt_complex takes a complex number in the form of a + b i. That's why the second argument is simply sqrt(-temp1) (notice that temp1 < 0, so I use - instead of Math.abs; Math.abs is probably a better idea). cbrt_complex returns the cube root of the complex number, in polar form: r eiθ. We can see from wolframalpha that with positive r and θ, we can write an n-th root of a complex numbers as follows:
And that's exactly how the code for the cbrt_complex and sqrt_complex work. Note that both take a complex number in rectangular coordinates (a + b i) and return a complex number in polar coordinates (r eiθ)
double[] part1 = polar_reciprocal(temp3);
It is easier to take the reciprocal of a polar complex number than a rectangular complex number. If we have r eiθ, its reciprocal (this follows standard power rules, luckily) is simply 1/r e-iθ. This is actually why we are staying in polar form; polar form makes multiplication-type operations easier, and addition type operations harder, while rectangular form does the opposite.
Notice that if we have a polar complex number r eiθ and we want to multiply by a real number d, the answer is as simple as d r eiθ.
The toRect function does exactly what it seems like it does: it converts polar coordinate complex numbers to rectangular coordinate complex numbers.
You may have noticed that the if statement doesn't check that there is no imaginary part, but only if the imaginary part is really small. This is because we are using floating point numbers, so checking result[1] == 0 will likely fail.
And there you are! Notice that we could actually implement the entire heart function with this complex number arithmetic, but it's probably faster to avoid this.

Creating a Squircle

I'm a first year programmer. I'm trying to create a squircle. (square with round corners).
So far i have managed to get. I have been given the constants of a,b and r. If anyone could help i would be really thankful. I'm a total noob to this. So be nice :)
package squircle;
import java.awt.*;
import javax.swing.*;
import java.lang.Math;
public class Main extends javax.swing.JApplet {
public void paint(Graphics g){
// (x-a)^4 + (y-b)^4 = r^4
// y = quadroot( r^4 - (x-a)^4 + b)
// x values must fall within a-r < x < a+r
int[] xPoints = new int[200];
int[] yPoints = new int[200];
int[] mypoints = new int[200];
for(int c = 0; c <200; c++){
int a = 100;
int r = 100;
int b = 100;
double x = c ;
double temp = (r*r*r*r);
double temp2 = x-a;
double temp3 = ((temp2)*(temp2)*(temp2)*(temp2));
double temp6 = Math.sqrt(temp-temp3);
double y = (Math.sqrt(temp6) + b );
double z = (y*-1)+300;
mypoints[c]=(int)z;
// if (c>100){
// y = y*1;
// }
// else if(c<100){
// y = y*1;
// }
xPoints[c]=(int)x;
yPoints[c]=(int)y;
// change the equation to find x co-ordinates
// change it to find y co-ordinates.
// r is the minor radius
// (a,b) is the location of the centre
// a = 100
// b = 100
// r = 100
// x value must fall within 0 or 200
}
g.drawPolygon(xPoints, yPoints, xPoints.length);
g.drawPolygon(xPoints, (mypoints), xPoints.length);
}
}
Is it homework or is there some other reason why you're not using Graphics#drawRoundRect()?
If you are submitting this as homework there are some elements of style that may help you. What are the roles of 200, 100 and 300? These are "magic constants" which should be avoided. Are they related or is it just chance that they have these values? Suggest you use symbols such as:
int NPOINTS = 200;
or
double radius = 100.0
That would reveal whether the 300 was actually the value you want. I haven't checked.
Personally I wouldn't write
y*-1
but
-y
as it's too easy to mistype the former.
I would also print out the 200 points as floats and see if you can tell by eye where the error is. It's highly likely that the spurious lines are either drawn at the start or end of the calculation - it's easy to make "end-effect" errors where exactly one point is omitted or calculated twice.
Also it's cheap to experiment. Try iterating c from 0 to 100. or 0 to 10, or 0 to 198 or 1 to 200. Does your spurious line/triangle always occur?
UPDATE Here is what I think is wrong and how to tackle it. You have made a very natural graphics error and a fence-post error (http://en.wikipedia.org/wiki/Off-by-one_error) and it's hard to detect what is wrong because your variable names are poorly chosen.
What is mypoints? I believe it is the bottom half of the squircle - if you had called it bottomHalf then those replying woulod have spotted the problem quicker :-).
Your graphics problem is that you are drawing TWO HALF-squircles. Your are drawing CLOSED curves - when you get to the last point (c==199) the polygon is closed by drawing back to c==0. That makes a D-shape. You have TWO D-shapes, one with the bulge UP and one DOWN. Each has a horizontal line closing the polygon.
Your fence-post error is that you are drawing points from 0 to 199. For the half-squircle you want to draw from 0 to 200. That's 201 points! The loss of one point means that you have a very slightly sloping line. The bottom lines slopes in tghe opposite direction from the top. That gives you a very then wedge shape, which you refer to as a triangle. I'm guessing that your triangle is not actually closed but like a slice from a pie but very then/sharp.
(The code below could be prettier and more compact. However it is often useful to break symmetrical problems into quadrants or octants. It would also be interesting to use an anngle to sweep out the polygon).
You actually want ONE polygon. The code should be something like:
int NQUADRANT = 100;
int NPOINTS = 4*NQUADRANT ; // closed polygon
double[] xpoints = new double[NPOINTS];
double[] ypoints = new double[NPOINTS];
Your squircle is at 100, 100 with radius 100. I have chosen different values here
to emphasize they aren't related. By using symbolic names you can easily vary them.
double xcenter = 500.0;
double ycentre = 200.0;
double radius = 100.;
double deltax = radius/(double) NQUADRANT;
// let's assume squircle is centered on 0,0 and add offsets later
// this code is NOT complete or correct but should show the way
// I might have time later
for (int i = 0; i < NPOINTS; i++) {
if (i < NQUADRANT) {
double x0 = -radius + i* deltax;
double y0 = fourthRoot(radius, x0);
x[i] = x0+xcenter;
y[i] = y0+ycenter;
}else if (i < 2*NQUADRANT) {
double x0 = (i-NQUADRANT)* deltax;
double y0 = fourthRoot(radius, x0);
x[i] = x0+xcenter;
y[i] = y0+ycenter;
}else if (i < 3*NQUADRANT) {
double x0 = (i-2*NQUADRANT)* deltax;
double y0 = -fourthRoot(radius, x0);
x[i] = x0+xcenter;
y[i] = y0+ycenter;
}else {
double x0 = -radius + (i-3*NQUADRANT)* deltax;
double y0 = -fourthRoot(radius, x0);
x[i] = x0+xcenter;
y[i] = y0+ycenter;
}
}
// draw single polygon
private double fourthRoot(double radius, double x) {
return Math.sqrt(Math.sqrt(radius*radius*radius*radius - x*x*x*x));
}
There is a javascript version here. You can view the source and "compare notes" to potentially see what you are doing wrong.
Ok, upon further investigation here is why you are getting the "triangle intersecting it". When you drawPolygon the points are drawn and the last point connects the first point, closing the points and making the polygon. Since you draw one half it is drawn (then connected to itself) and then the same happens for the other side.
As a test of this change your last couple lines to this:
for( int i = 0; i < yPoints.length; i++ ) {
g.drawString( "*", xPoints[ i ], yPoints[ i ] );
}
for( int i = 0; i < mypoints.length; i++ ) {
g.drawString( "*", xPoints[ i ], mypoints[ i ] );
}
// g.drawPolygon( xPoints, yPoints, xPoints.length );
// g.drawPolygon( xPoints, ( mypoints ), xPoints.length );
It is a little crude, but I think you'll get the point. There are lots of solutions out there, personally I would try using an array of the Point class and then sort it when done, but I don't know the specifics of what you can and can not do.
Wow, are you guys overthinking this, or what! Why not just use drawLine() four times to draw the straight parts of the rectangle and then use drawArc() to draw the rounded corners?

Categories