I have task to write program allowing users to draw stars, which can differ in size and amount of arms. When I was dealing with basic stars I was doing it with GeneralPath and tables of points :
int xPoints[] = { 55, 67, 109, 73, 83, 55, 27, 37, 1, 43 };
int yPoints[] = { 0, 36, 36, 54, 96, 72, 96, 54, 36, 36 };
Graphics2D g2d = ( Graphics2D ) g;
GeneralPath star = new GeneralPath();
star.moveTo( xPoints[ 0 ], yPoints[ 0 ] );
for ( int k = 1; k < xPoints.length; k++ )
star.lineTo( xPoints[ k ], yPoints[ k ] );
star.closePath();
g2d.fill( star );
What method should I choose for drawing stars with variable inner and outer radius, as well as different amount of arms ? This is what I should obtain :
alt text http://img228.imageshack.us/img228/6427/lab6c.jpg
Having n arms means you end up with 2n vertices, the even ones are on the outer circle, and the odd ones on the inner circle. Viewed from the center, the vertices are at evenly spaced angles (the angle is 2*PI/2*n = Pi/n). On an unit circle (r=1), the x,y coordinates of the points i=0..n is cos(x),sin(x). Multiply those coordinates with the respective radius (rOuter or rInner, depending of whether i is odd or even), and add that vector to the center of the star to get the coordinates for each vertex in the star path.
Here's the function to create a star shape with given number of arms, center coordinate and outer, inner radius:
public static Shape createStar(int arms, Point center, double rOuter, double rInner) {
double angle = Math.PI / arms;
GeneralPath path = new GeneralPath();
for (int i = 0; i < 2 * arms; i++) {
double r = (i & 1) == 0 ? rOuter : rInner;
Point2D.Double p = new Point2D.Double(
center.x + Math.cos(i * angle) * r,
center.y + Math.sin(i * angle) * r);
if (i == 0) {
path.moveTo(p.getX(), p.getY());
}
else {
path.lineTo(p.getX(), p.getY());
}
}
path.closePath();
return path;
}
I think you should use the same classes (GeneralPath), but here you should focus on how to compute the vertex coordinates.
The first thing that comes to my mind is positioning 2N points on a circle of radius R1, centered at (0,0). Then, "strech" every odd vertex by multiplying its vector by c. The constant c should be equal to R2/R1 (i.e. the proportion of inner and outer radiuses).
But maybe there is a simpler solution...
Here's an example of finding equally spaced points on a circle that may help. Just make the number of points, n, a parameter in the constructor.
private int n;
...
public CircleTest(int n) {
...
this.n = n;
}
...
for (int i = 0; i < n; i++) {
double t = 2 * Math.PI * i / n;
...
}
Related
In my Java application I'm creating 2D polygons using an array of vertices. For example, I want to create a simple square using these 4 vertices
[-130, -74], [-125, -74], [-125, -70], [-130, -70]
Then I want to check if a point is inside the generated Polygon. But if I check, for example, this point
[-125, -73]
using polygon.contains(x, z) it says is not inside the Polygon. Even if I check a corner, like [-125, -74] is returns false. The strange part for me is that is I check this point [-126, -74] is returns true, so some points are actually seen as inside the polygon, while others are not, and I can't understand why is it. This is a sample code I set up to test this, nothing special about it
public static void main(String[] args) {
Polygon polygon = new Polygon(new int[]{-130, -125, -125, -130}, new int[]{-74, -74, -70, -70}, 4);
System.out.println("" + polygon.contains(-125, -73));
System.out.println("" + polygon.contains(-125, -74));
System.out.println("" + polygon.contains(-126, -74));
}
And the output as well
false
false
true
I would also point out the fact that this is just a simple example, but the Polygon could be a really complex shape, for example something crazy like this
The document Polygon says
This Polygon is defined with an even-odd winding rule. See WIND_EVEN_ODD for a definition of the even-odd winding rule.
WIND_EVEN_ODD
The winding rule constant for specifying an even-odd rule for determining the interior of a path. The even-odd rule specifies that a point lies inside the path if a ray drawn in any direction from that point to infinity is crossed by path segments an odd number of times.
So you can do like this.
static Polygon mirror(Polygon p) {
int npoints = p.npoints;
int[] xpoints = new int[npoints];
int[] ypoints = new int[npoints];
for (int i = 0; i < npoints; ++i) {
xpoints[i] = -p.xpoints[i];
ypoints[i] = -p.ypoints[i];
}
return new Polygon(xpoints, ypoints, npoints);
}
static boolean onVertex(Polygon p, int x, int y) {
int npoints = p.npoints;
for (int i = 0; i < npoints; ++i)
if (p.xpoints[i] == x && p.ypoints[i] == y)
return true;
return false;
}
static boolean contains(Polygon p, int x, int y) {
return p.contains(x, y)
|| onVertex(p, x, y)
|| mirror(p).contains(-x, -y);
}
And
Polygon polygon = new Polygon(new int[]{-130, -125, -125, -130}, new int[]{-74, -74, -70, -70}, 4);
System.out.println("" + contains(polygon, -125, -73));
System.out.println("" + contains(polygon, -125, -74));
System.out.println("" + contains(polygon, -126, -74));
output:
true
true
true
A test for a polygon with a hole.
int width = 100, height = 100;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] xs = {20, 80, 80, 20, 20, 40, 60, 60, 40, 40};
int[] ys = {20, 20, 80, 80, 20, 40, 40, 60, 60, 40};
Polygon p = new Polygon(xs, ys, xs.length);
Graphics2D g = image.createGraphics();
try (Closeable c = () -> g.dispose()) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
g.setColor(Color.BLACK);
g.drawPolygon(p);
g.setColor(Color.RED);
for (int x = 0; x < width; ++x)
for (int y = 0; y < height; ++y)
if (contains(p, x, y))
g.fillRect(x, y, 1, 1);
}
ImageIO.write(image, "png", new File("data/testPolygon.png"));
output
If contains(p, x, y) -> p.contains(x, y) then
Shoot a ray from point P(x, y) and count for intersection with the edges, if intersection count is odd, then P is inside polygon.
However if ray intersects with one of the vertices it might be difficult to determine intersection point due to rounding problem. Therefore you may follow these steps:
Shoot a ray in any direction, such that the ray does not directly hit any vertices.
For each edge in polygon determine whether the ray intersects the edge, if yes - increase counter
After all edges have been checked, P inside if intersection counter is odd.
https://en.wikipedia.org/wiki/Point_in_polygon
I'm prototyping a script to plot equally spaced points around a rotating plane and Processing is producing unexpected results?
This is my code:
int WHITE = 255;
int BLACK = 0;
void setup() {
size(500, 500);
}
void draw() {
background(WHITE);
translate(width/2, height/2); // move origin to center of window
// center ellipse
noStroke();
fill(255, 0, 0);
ellipse(0, 0, 10, 10); // center point, red
// satellite ellipses
fill(BLACK);
int points = 4;
for (int i = 0; i < points; i++) {
rotate(i * (TWO_PI / points));
ellipse(0, 100, 10, 10); // after plane rotation, plot point of size(10, 10), 100 points above y axis
}
}
When points = 4 I get the output I would expect, but when points = 5 // also when points = 3 or > 4, I get an output that is missing plotted points but still spaced correctly.
Why is this happening?
You're rotating too much: you don't want to rotate by i * angle at every iteration, because if we do we end up rotating so much that points end up overlapping. For example, with the code as is, with 3 points we want to place them at 0, 120, and 240 degrees (or, 120, 240, 360). But that's not what happens:
when i=0 we rotate by 0 degrees. So far so good.
when i=1 we rotate by 120 degrees on top of 0. Still good.
when i=2 we rotate by 240 degrees on top of 120. That's 120 degrees too far!
That's clearly not what we want, so just rotate by the fixed angle TAU / points and things'll work as expected:
for (int i = 0; i < points; i++) {
rotate(TAU / points);
ellipse(0, 100, 10, 10);
}
Alternatively, keep the incrementing angle, but then place the points without using rotate(), by using trigonometry to compute the placement:
float x = 0, y = 100, nx, ny, angle;
for (int i = 0; i < points; i++) {
angle = i * TAU / points;
nx = x * cos(a) - y * sin(a);
ny = x * sin(a) + y * cos(a);
ellipse(nx, ny, 10, 10);
}
i have a mathematical problem. Im making a game where the user is a 12 year old kid. The child's goal is to calculate the area of a drawn shape. In easy and medium mode, the shapes are given and hard coded so they are not hardcore. in the hard mode 5 coordinates are randomly generated and here is where the problem comes. I need to make a shape which area is calculable by a 12 y/o child. With the random coordinates come various hard things, such as intersections, or odd points on a line connecting 2 other points and so. Is there any way to calculate and avoid such problems?
Here is my code which makes the random points + draws it on a dot grid in the application:
private void gameHard ()
{
//distance between points is 65 pixels, the numbers that are generated are 1-8
x1=(genRandomInt())*65;
x2=(genRandomInt())*65;
x3=(genRandomInt())*65;
x4=(genRandomInt())*65;
x5=(genRandomInt())*65;
y1=(genRandomInt())*65;
y2=(genRandomInt())*65;
y3=(genRandomInt())*65;
y4=(genRandomInt())*65;
y5=(genRandomInt())*65;
compareRCoordinates ();
areaImage = new JPanel ()
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.WHITE);
g2.fillRect(0,0,780,650);
g2.setColor(Color.BLACK);
int xnum = 65, ynum = 65;
for(ynum=65;ynum<650;ynum=ynum+65)
{
int x=0, y=0;
for(xnum = 65;xnum<780;xnum=xnum+65)
{
x = xnum-9;
y = ynum-9;
g2.fillOval(x,y,18,18);
}
xnum=xnum+65;
}
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke(6));
g2.drawLine(x1,y1,x2,y2);
g2.drawLine(x2,y2,x3,y3);
g2.drawLine(x3,y3,x4,y4);
g2.drawLine(x4,y4,x5,y5);
g2.drawLine(x5,y5,x1,y1);
}
};
areaImage.setBounds(20,20,780,650);
areaImage.setBorder(BorderFactory.createLineBorder(Color.black));
this.add(areaImage);
roundsPlayed++;
}
Here's the outline of a fairly straightforward method.
Choose five distinct random points.
Calculate the centroid of the five points (that is, the average X co-ordinate and the average Y co-ordinate).
Calculate the angle from the centroid to each of the five original points. If one of the points happens to be the centroid, then pick any number at all (such as 0) as the angle.
Arrange the points in order of the angle calculated. Ties can be broken arbitrarily.
OK, the points now make a pentagon in the order you've arranged them (including a line segment from the last point to the first one). It's not necessarily convex, but it won't have any "crossing over". You can draw this on the screen.
And you can calculate the area as
( x1 * y2 + x2 * y3 + x3 * y4 + x4 * y5 + x5 * y1 - y1 * x2 - y2 * x3 - y3 * x4 - y4 * x5 - y5 * x1 ) / 2
My basic idea is I divide your 64 (8 by 8) possible points into 5 disjoint rectangular areas and pick one random point from each area. The areas are picked so that connecting the points in order will never cause any connecting lines to cross. It’s quite simple — maybe too simple?
x1 = genRandomInt(1, 3) * 65;
y1 = genRandomInt(1, 4) * 65;
x2 = genRandomInt(1, 3) * 65;
y2 = genRandomInt(5, 8) * 65;
x3 = genRandomInt(4, 8) * 65;
y3 = genRandomInt(6, 8) * 65;
x4 = genRandomInt(4, 8) * 65;
y4 = genRandomInt(4, 5) * 65;
x5 = genRandomInt(6, 8) * 65;
y5 = genRandomInt(1, 3) * 65;
Write genRandomInt(int from, int to) so that it returns a random int in the interval from from through to inclusive. In the code above I have between 10 and 15 possible points in each of the rectangular areas.
Using arrays for the coordinates facilitates.
One could use a random distance to the prior points so points are not near. I'll be math lazy and simply repeat selecting new random numbers till the random point is no longer near.
Finally I cheat and use java.awt.Polygon to check that the new candidate point is not inside the polygon till that.
Polygon one can draw, and even fill.
The fields:
int[] xs = new int[5]; // xs[0] till xs[4]
int[] ys = new int[5];
Polygon pentagon;
Picking random points:
final int NEAR = 20;
for (int i = 0; i < 5; ++i) {
// Pull random numbers for this i'th point till okay.
for (;;) {
xs[i] = random ...
ys[i] = random ...
// Check that the point is not inside the polygon till now:
if (i >= 3) {
Polygon polygon = new Polygon(xs, ys, i);
if (polygon.contains(xs[i], ys[i]) {
continue; // Inside
}
}
// Check that the point are apart:
boolean near = false;
for (int j = 0; j < i && !near; ++j) {
near = Math.abs(xs[i] - xs[j]) < NEAR
&& Math.abs(ys[i] - ys[j]) < NEAR;
}
if (near) {
continue; // Too near
}
break; // Found point i
}
}
pentagon = new Polygon(xs, ys, 5);
Drawing:
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke(6));
g2.draw(pentagon);
g2.setColor(Color.TEAL);
g2.fill(pentagon);
... draw grid
As you might image there might be sufficient looping. Endless when the first four points cover the largest part of the screen.
I need to create a loop which draws rectangles in a vertical line with alternating color (e.g. black, white, black, white). Can anyone tell me how this is done?
I have tried numerous ways but can't seem to get the loop to work, thanks.
for (int x = 0; x>10;x++) {
int y= 180;
graph2D.drawRect(170, y, 20, 50);
y = y + 45;
}
This is what I have, it won't draw the rectangles for some reason and I cant get it to alternate colors.
You've got a few issues here.
Your for loop will not perform any iterations, as your condition is x > 10 instead of x < 10.
Change the first line from:
for (int x = 0; x>10;x++){
To:
for (int x = 0; x < 10; x++) {
Also, you reset y to 180 every iteration, so once your loop does start, all of the rectangles will be drawn on top of each other. Declare y outside of the loop, or use x to calculate the rectangles position.
Either like this:
int y = 180;
for (int x = 0; x < 10; x++) {
graph2D.drawRect(170, y, 20, 50);
y = y + 45;
}
Or like this:
for (int x = 0; x < 10; x++) {
graph2D.drawRect(170, (x * 45) + 180, 20, 50);
}
The above math (x * 45) + 180 is a super simple way of saying that the first rectangle will be at (x * 45) + 180 = 0 + 180 = 180, the second will be at (x * 45) + 180 = 45 + 180 = 225 and so on and so on.
To change the color of the rectangles, you'll need to make a list or array of Colors, and use a different Color from the list, in each iteration.
//Make the list
Color[] colors = {Color.black, Color.blue, Color.cyan, Color.darkGray,
Color.green, Color.lightGray, Color.magenta, Color.magenta,
Color.orange, Color.pink, Color.red, Color.white, Color.yellow};
//Draw each rectangle
for (int x = 0; x < 10; x++) {
//Change the color
g.setColor(colors[x]);
//Draw the rectangle
graph2D.drawRect(170, (x * 45) + 180, 20, 50);
}
Of course if you want the colors to be random, you can look into using the Random class, and generating a random number between 0 and the length of your colors array. Also note that I use x as an index for the colors array, and if your loop increments x to be higher than the number of colors in the array, you'll get an ArrayIndexOutOfBoundsException.
I also assumed you named your instance of Graphics as g, since it is done that way most of the time.
Why do you use the y variable instead of x that you are looping though?
#Override
public void paint(Graphics graph2D) {
for (int y=0; y<10; y++) {
int height = 50;
if (y%2==0) {
graph2D.setColor(Color.white);
} else {
graph2D.setColor(Color.black);
}
graph2D.fillRect(170, 180 + y*height, 20, 50);
}
}
Also mind the difference while drawing a rectangle:
.drawRect(..) draws the border of a rectangle.
.fillRect(..) draws the rectangle itself.
In case you want to switch between black and white color, change the color with every loop. Every even number y%2 == 0 of loop will have the one color, otherwise the second one (also mentioned in the code above):
if (y%2==0) {
graph2D.setColor(Color.white);
} else {
graph2D.setColor(Color.black);
}
I try to draw a leaf looking thing on the screen, and try to fill it with a color. It's like drawing a circle, the difference is, that it's only 270 degrees, and the radius starts from 0 to 100. I first draw the left side, and on each degree I fill the inside. At the end I draw the right side.
Here is to code, maybe it's easier to understand:
canvas = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
Color black = new Color(0,0,0);
Color green = new Color(0,130,0);
double j = 0.0; // radius
double max = 100.0; // max radius
for (int i = 0; i < 135; i++) { // left side (270 degree / 2)
j += max / 135.0;
// x, y coordinate
int x = (int)(Math.cos(Math.toRadians(i)) * j);
int y = (int)(Math.sin(Math.toRadians(i)) * j);
// draw a circle like thing with radius j
for (int l = i; l < 135 + (135 - i); l++) {
int ix = (int)(Math.cos(Math.toRadians(l)) * j);
int iy = (int)(Math.sin(Math.toRadians(l)) * j);
canvas.setRGB(ix + 256, iy + 256, green.getRGB());
}
canvas.setRGB(x + 256, y + 256, black.getRGB());
}
// draw the right side
for (int i = 135; i < 270; i++) {
j -= max / 135.0;
int x = (int)(Math.cos(Math.toRadians(i)) * j);
int y = (int)(Math.sin(Math.toRadians(i)) * j);
canvas.setRGB(x + 256, y + 256, black.getRGB());
}
This is the result:
As you can see, where the radius is bigger, the leaf is not filled completely.
If I change i to 1350, then divide it with 10 where I calculate x, y, then it's filled, but it's much slower. Is there a better way to properly fill my shape?
Later I also would like to fill my shape with a gradient, so from green to a darker green, then back to green. With my method this is easy, but super slow.
Thanks in advance!
I think that for you the best solution is to use a flood fill algorithm, it's easy to implement in Java and efficient in your case, like you have a simple shape.
Here is a wikipedia article that is really complet : http://en.wikipedia.org/wiki/Flood_fill
Here is a simple suggestion: Instead of drawing the leaf, just put the points that create the outline into an array. The array should run from xMin (smallest X coordiate of the leaf outline) to xMax. Each element is two ints: yMin and yMax.
After rendering all the points, you can just draw vertical lines to fill the space between yMin/yMax for each X coordinate.
If you have gaps in the array, fill them by interpolating between the neighboring points.
An alternative would be to sort the points clockwise or counter-clockwise and use them as the outline for a polygon.