As you can see on the image, I have a p1 and p2 objects with (x,y) coordinates which I know the values, and I know radius of all these circle objects.
However, I want to calculate new position x,y which would be p3 center point. Basically, as you can see it's p2 position + radius.
I am doing this for java game which is based on libgdx. I would appreciate any math or java language directions/examples.
See code comments for explanation.
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.*;
class CenteredCircle extends Ellipse2D.Double {
CenteredCircle(Point2D.Double p, double radius) {
super(p.x - radius, p.y - radius, 2 * radius, 2 * radius);
}
}
public class CircleDemo extends JFrame {
public CircleDemo() {
int width = 640; int height = 480;
setSize(new Dimension(width, height));
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
JPanel p = new JPanel() {
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
// center p1
Point2D.Double p1 = new Point2D.Double(getSize().width/2, getSize().height/2);
double radius = 130.0;
// big circle
Shape circle2 = new CenteredCircle(p1, radius);
g2d.draw(circle2);
// 12 small circles
for (int angle = 0; angle < 360; angle += 30) {
// this is the magic part
// a polar co-ordinate has a length and an angle
// by changing the angle we rotate
// the transformed co-ordinate is the center of the small circle
Point2D.Double newCenter = polarToCartesian(radius, angle);
// draw line just for visualization
Line2D line = new Line2D.Double(p1.x, p1.y, p1.x + newCenter.x, p1.y+ newCenter.y);
g2d.draw(line);
// draw the small circle
Shape circle = new CenteredCircle(
new Point2D.Double(p1.x + newCenter.x, p1.y + newCenter.y),
radius/4);
g2d.draw(circle);
}
}
};
setTitle("Circle Demo");
getContentPane().add(p);
}
public static void main(String arg[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CircleDemo();
}
});
}
static Point2D.Double polarToCartesian(double r, double theta) {
theta = (theta * Math.PI) / 180.0; // multiply first, then divide to keep error small
return new Point2D.Double(r * Math.cos(theta), r * Math.sin(theta));
}
// not needed, just for completeness
public static Point2D.Double cartesianToPolar(double x, double y) {
return new Point2D.Double(Math.sqrt(x * x + y * y), (Math.atan2(y, x) * 180) / Math.PI);
}
}
Now using libgdx for the graphics. Thus no need for polar co-ordinates, on the outside.
I am not doing frame rate relative animation. Therefore, this is no perfect match to your code.
Using the following calculation (if (theta >= 360) { theta = 0.0f; }) at the end of the render method will let the animation restart with its original value.
package org.demo;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
public class CircleDemo extends ApplicationAdapter {
ShapeRenderer shapeRenderer;
float theta = 0.0f;
#Override
public void create () {
shapeRenderer = new ShapeRenderer();
}
#Override
public void render () {
ScreenUtils.clear(0, 0.4f, 0.4f, 1);
Vector2 p1 = new Vector2( Gdx.graphics.getWidth() / 2.0f , Gdx.graphics.getHeight() / 2.0f);
Vector2 smallCircleCenter = new Vector2(150.0f, 0.0f);
smallCircleCenter.add(p1); // translate center by p1
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
// static lines and circles
for (int angle = 0; angle < 360; angle += 30) {
Vector2 lineEnd = new Vector2(smallCircleCenter);
lineEnd.rotateAroundDeg(p1, angle);
shapeRenderer.line(p1, lineEnd);
shapeRenderer.circle(lineEnd.x, lineEnd.y, 20);
}
// animated line and circle in red
shapeRenderer.setColor(0.75f, 0, 0, 1);
Vector2 movingCircleCenter = new Vector2(smallCircleCenter);
movingCircleCenter.rotateAroundDeg(p1, theta);
shapeRenderer.line(p1, movingCircleCenter);
shapeRenderer.circle(movingCircleCenter.x, movingCircleCenter.y, 20);
shapeRenderer.setColor(1, 1, 1, 1);
shapeRenderer.end();
theta++;
// for the screenshot stop at 90 degrees
if (theta >= 90) {
theta = 90.0f;
}
}
#Override
public void dispose () {
shapeRenderer.dispose();
}
}
So I wrote a test in my project, based on your approach:
package com.bigbang.test.impl;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array;
import com.bigbang.Game;
import com.bigbang.graphics.g2d.shapes.impl.Ellipse;
import com.bigbang.graphics.g2d.shapes.impl.Line;
import com.bigbang.graphics.gl.Color;
import com.bigbang.math.BBMath;
public class PolarToCartesianTest extends AbstractTest {
private Array<GraphicalObject> graphicalObjectArray;
private GraphicalObject dynamicGraphicalObject;
private float radius, smallCircleRadius;
private float centerX, centerY;
public PolarToCartesianTest(Game game) {
super(game);
}
#Override
public void create() {
radius = 200f;
centerX = game.getScreenController().getScreenWidth() / 2;
centerY = game.getScreenController().getScreenHeight() / 2;
smallCircleRadius = radius / 4;
graphicalObjectArray = new Array<>();
for (int angle = 0; angle < 360; angle += 30) {
GraphicalObject graphicalObject = new GraphicalObject();
graphicalObject.angle = angle;
graphicalObjectArray.add(graphicalObject);
}
dynamicGraphicalObject = new GraphicalObject();
game.getCameraController().getCamera().position.x = game.getScreenController().getScreenWidth() / 2;
game.getCameraController().getCamera().position.y = game.getScreenController().getScreenHeight() / 2;
}
#Override
public void update(float deltaTime) {
for (GraphicalObject graphicalObject : graphicalObjectArray) {
Vector2 polarToCartesianPosition = BBMath.polarToCartesian(radius, graphicalObject.angle);
graphicalObject.line.x1 = centerX + 0;
graphicalObject.line.y1 = centerY + 0;
graphicalObject.line.x2 = centerX + polarToCartesianPosition.x;
graphicalObject.line.y2 = centerY + polarToCartesianPosition.y;
graphicalObject.line.color = Color.WHITE_COLOR;
graphicalObject.ellipse.x = centerX + polarToCartesianPosition.x;
graphicalObject.ellipse.y = centerY + polarToCartesianPosition.y;
graphicalObject.ellipse.width = 2 * smallCircleRadius;
graphicalObject.ellipse.height = 2 * smallCircleRadius;
graphicalObject.ellipse.color = Color.WHITE_COLOR;
}
float shift = 0;
float theta = (shift * smallCircleRadius) * (centerY / centerX);
Vector2 pos = BBMath.polarToCartesian(radius, theta);
dynamicGraphicalObject.line.color = new Color(Color.RED);
dynamicGraphicalObject.line.x1 = centerX + 0;
dynamicGraphicalObject.line.y1 = centerY + 0;
dynamicGraphicalObject.line.x2 = centerX + pos.x;
dynamicGraphicalObject.line.y2 = centerY + pos.y;
dynamicGraphicalObject.ellipse.x = centerX + pos.x;
dynamicGraphicalObject.ellipse.y = centerY + pos.y;
dynamicGraphicalObject.ellipse.width = 2 * smallCircleRadius;
dynamicGraphicalObject.ellipse.height = 2 * smallCircleRadius;
dynamicGraphicalObject.ellipse.color = new Color(Color.RED);
}
#Override
public void draw() {
game.getShapeRenderer().begin(ShapeRenderer.ShapeType.Line);
for (GraphicalObject graphicalObject : graphicalObjectArray) {
graphicalObject.line.draw();
graphicalObject.ellipse.draw();
}
dynamicGraphicalObject.line.draw();
dynamicGraphicalObject.ellipse.draw();
game.getShapeRenderer().end();
}
class GraphicalObject {
Ellipse ellipse;
Line line;
float angle;
public GraphicalObject() {
this.ellipse = new Ellipse(game);
this.line = new Line(game);
}
}
}
Which is same math like in your example, with some modifications:
However, you can notice I have this dynamicGraphicalObject (red circle), which I want to shift position around circle by using theta value calculated as (shift * smallCircleRadius) * (centerY / centerX);. This works perfect for shift=0 value. It's properly positioned/overlapping white. But if I would change shift variable to 1, 2, 3, or 11, you can see that it's not precisely aligned with white circles. Is this floating point issue or am I missing something in calculation of theta ?
shift values used: 2,6 and 11 in order by images
--
SOLUTION:
float fixPrecision = 1.1f;
float theta = (shift * fixPrecision) + ((shift * smallCircleRadius) * (centerY / centerX));
Related
How i want it to look like:
The circles move along with the arrow around the center circle.
How it is looking at the moment:
I want to draw a 2 lines between two circles. however these circles move all around the screen and i dont know a methodical way to draw lines between them. For example, I always have the top left corner of the two circles i want to draw a line between but thats it. I need help to draw a line in java that will adjust based on its position so that the lines move around the edge as the circles move
for (int z = 0; z < lines.size(); z++) {
if (lines.get(z).getfState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
transition.get(z).setIcon(null);
for (int x = 0; x < states.size(); x++) {
if (states.get(x).getText().equals(lines.get(z).getnState()) && states.get(a).getText().equals(lines.get(z).getfState())) {
int xbegin = (int) states.get(a).getBounds().getX();
int ybegin = (int) states.get(a).getBounds().getY();
int xend = (int) states.get(x).getBounds().getX();
int yend = (int) states.get(x).getBounds().getY();
if (xbegin > xend) {
Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
} else {
if (xend - xbegin < 75) {
xbegin = xbegin - 20;
xend = xend - 20;
}
xbegin = xbegin + 5;
ybegin = ybegin + 25;
xend = xend + 5;
yend = yend + 25;
Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
}
}
}
} else if (lines.get(z).getnState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
transition.get(z).setIcon(null);
for (int x = 0; x < states.size(); x++) {
if (states.get(x).getText().equals(lines.get(z).getfState()) && states.get(a).getText().equals(lines.get(z).getnState())) {
int xend = (int) states.get(a).getBounds().getX();
int yend = (int) states.get(a).getBounds().getY();
int xbegin = (int) states.get(x).getBounds().getX();
int ybegin = (int) states.get(x).getBounds().getY();
if (xbegin > xend) {
Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
} else {
if (xend - xbegin < 75) {
xbegin = xbegin + 20;
xend = xend + 20;
}
xbegin = xbegin + 5;
ybegin = ybegin + 25;
xend = xend + 5;
yend = yend + 25;
Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
}
}
}
public static Path2D.Double createArrowForLine(
int fromPointx,
int fromPointy,
double rotationDeg,
double length,
double wingsAngleDeg) {
double ax = fromPointx;
double ay = fromPointy;
double radB = Math.toRadians(-rotationDeg + wingsAngleDeg);
double radC = Math.toRadians(-rotationDeg - wingsAngleDeg);
Path2D resultPath = new Path2D.Double();
resultPath.moveTo(length * Math.cos(radB) + ax, length * Math.sin(radB) + ay);
resultPath.lineTo(ax, ay);
resultPath.lineTo(length * Math.cos(radC) + ax, length * Math.sin(radC) + ay);
return (Path2D.Double) resultPath;
}
Although there have been some hiccups in the question, and the code provided so far looks questionable, the core of the question as it stands now is quite interesting...
There are different options for solving this. From the images that you provided so far, it looks like the circles always have the same size, which makes things far simpler. For circles with different sizes, you'd really have to compute the tangents of the circles, in the desired direction, mutually considering the radius of the other circle. Of course, this is possible, but a bit less trivial.
For the case that you have equally-sized circles, you can
Compute the difference of the centers of two circles
Divide this by the distance, to obtain the (normalized) direction
Rotate this direction by 90°
Scale the rotated direction vector by the radius
Add the scaled and rotated vector to the circle center
This will yield one endpoint of such a line. The rotation about 90° can be done once in clockwise and once in counterclockwise direction, to obtain the "upper" and "lower" endpoint for the line, respectively.
Image was updated with the EDIT, see below
The actual computation is done in the computeLine method of the MCVE below. Note that this example uses the "simple" approach, although it uses circles of slightly different sizes. The effect is that, when the difference between the sizes of two circles is too large (compared to the distance between the circles, basically), then the lines may slightly intersect the circles. But the solution should be a reasonable trade-off between simplicity and general applicability. Particularly, for equally-sized circles, there will be no intersections at all.
Code was updated with the EDIT, see below
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LinesAtCirclesTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel linesAtCirclesTestPanel = new LinesAtCirclesTestPanel();
f.getContentPane().add(linesAtCirclesTestPanel);
f.setSize(400,400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LinesAtCirclesTestPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D draggedCenter;
private List<Point2D> centers = new ArrayList<Point2D>();
private List<Double> radii = new ArrayList<Double>();
public LinesAtCirclesTestPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
addCircle(100, 100, 30);
addCircle(200, 300, 50);
addCircle(300, 200, 40);
}
private void addCircle(double x, double y, double radius)
{
centers.add(new Point2D.Double(x,y));
radii.add(radius);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i=0; i<centers.size(); i++)
{
Point2D center0 = centers.get(i);
double radius0 = radii.get(i);
Shape ellipse = new Ellipse2D.Double(
center0.getX() - radius0, center0.getY() - radius0,
radius0 + radius0, radius0 + radius0);
g.setColor(Color.LIGHT_GRAY);
g.fill(ellipse);
g.setColor(Color.BLACK);
g.draw(ellipse);
}
g.setColor(Color.RED);
for (int i=0; i<centers.size() - 1; i++)
{
Point2D center0 = centers.get(i);
double radius0 = radii.get(i);
Point2D center1 = centers.get(i+1);
double radius1 = radii.get(i+1);
g.draw(createArrow(computeLine(center0, radius0, center1, radius1, true)));
g.draw(createArrow(computeLine(center0, radius0, center1, radius1, false)));
}
}
private static Shape createArrow(Line2D line)
{
double dx = line.getX2() - line.getX1();
double dy = line.getY2() - line.getY1();
double angleToX = Math.atan2(dy, dx);
final double angleRad = Math.toRadians(30);
final double headLength = 20.0f;
double dxL = Math.cos(Math.PI + angleToX + angleRad) * headLength;
double dyL = Math.sin(Math.PI + angleToX + angleRad) * headLength;
double dxR = Math.cos(Math.PI + angleToX - angleRad) * headLength;
double dyR = Math.sin(Math.PI + angleToX - angleRad) * headLength;
Path2D arrow = new Path2D.Double();
arrow.moveTo(line.getX1(), line.getY1());
arrow.lineTo(line.getX2(), line.getY2());
arrow.lineTo(line.getX2() + dxL, line.getY2() + dyL);
arrow.moveTo(line.getX2(), line.getY2());
arrow.lineTo(line.getX2() + dxR, line.getY2() + dyR);
return arrow;
}
private static Line2D computeLine(
Point2D center0, double radius0,
Point2D center1, double radius1,
boolean upper)
{
double dx = center1.getX() - center0.getX();
double dy = center1.getY() - center0.getY();
double invLength = 1.0 / Math.hypot(dx, dy);
double dirX = dx * invLength;
double dirY = dy * invLength;
double rotDirX = dirY;
double rotDirY = -dirX;
if (upper)
{
rotDirX = -dirY;
rotDirY = dirX;
}
double x0 = center0.getX() + rotDirX * radius0;
double y0 = center0.getY() + rotDirY * radius0;
double x1 = center1.getX() + rotDirX * radius1;
double y1 = center1.getY() + rotDirY * radius1;
if (upper)
{
return new Line2D.Double(x1, y1, x0, y0);
}
return new Line2D.Double(x0, y0, x1, y1);
}
#Override
public void mousePressed(MouseEvent e)
{
draggedCenter = null;
for (int i=0; i<centers.size(); i++)
{
Point2D center = centers.get(i);
double radius = radii.get(i);
if (e.getPoint().distance(center) < radius)
{
draggedCenter = center;
}
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedCenter = null;
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedCenter == null)
{
return;
}
draggedCenter.setLocation(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
// Not used
}
#Override
public void mouseClicked(MouseEvent e)
{
// Not used
}
#Override
public void mouseEntered(MouseEvent e)
{
// Not used
}
#Override
public void mouseExited(MouseEvent e)
{
// Not used
}
}
EDIT in response to the comment:
The original code computed Line2D objects. Creating an arrow from a line is, in the simplest case, basically done with a bit of trigonometry, and many resources exist for this on the web.
In response to the comment, I extended the example to show simple arrows, as depicted in the above image.
However, when taking a closer look at this, one may notice several degrees of freedom for such an arrow:
Should the head length be absolute or relative to the arrow?
Should the head width be absolute or relative to the arrow?
(Or: What should be the angle of the arrow head?)
Should the arrow head be filled, or consist of lines?
Should the "trunk" of the arrow be a single line, or an outline shape?
What should be the width of the trunk?
...
In order to cover some of these degrees of freedom, I created an ArrowCreator class a while ago, and there's also a sample showing how it may be used.
I'm making an application about space physics, so I do lots with orbits. Naturally, I encounter the Ellipse2D.Double to draw my orbits on the screen.
Whenever my JPanel refreshes, I draw the orbit of a body using an Ellipse2D, as well as the body itself with a different method.
Essentially, I discovered that when numbers get very large (whether it be the size of the orbits get large or the visualization is zoomed in very far), the position of the body and the Ellipse2D do not line up.
I calculate the position of the body using a conversion from polar coordinates to rectangular coordinates, and I leave the math for the Ellipse2D up to the geom package.
Take a look at this code sample. It's the most self-contained version of my problem that I can make, since scale of the circle has to be very large:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
The code above essentially draws a circle on the screen very far away with a large radius. I've picked the dimension so that a piece of the circle is visible in the small window.
Then it draws a line from the center of the circle to a point on the circle that's visible in the window: I picked an angle that was visible on the window and used geometry to convert that angle and the radius of the circle into rectangular coordinates.
This is what the program displays:
Notice that the line doesn't actually end up touching the ellipse. Now, I decided I had to find out whether it was the point I calculated or the ellipse that were incorrect. I did the math on my calculator, and found that the line was correct, and the ellipse incorrect:
Considering that the calculator is probably not wrong, I am led to believe the Ellipse2D is not drawing correctly. However, I tried many other angles, and this is the pattern I found:
And that leads me to believe the calculations are somehow wrong.
So that's my problem. Should I be using something other than Ellipse2D? Maybe Ellipse2D is not accurate enough? I used BigDecimals in my code sample because I thought it would give me more precision - is that the wrong approach? My ultimate goal is to be able to calculate the rectangular position of a point on an ellipse at a specific angle.
Thanks in advance.
You see this error because Ellipse2D is approximated by four cubic curves. To make sure just take a look at its path iterator defining shape border: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
To improve quality we should approximate ellipse by higher number of cubic curves. Here is an extention of standard java implementation with changeable number of segments:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
#Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
#Override
public void next() {
index++;
}
#Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
#Override
public boolean isDone() {
return index > getSegments() + 1;
}
#Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
#Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
And here is how you can use it in your code instead of standard one (I use 100 segments here):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
I wrote a Java program to take a triangle and either rotate, shift, or rotate and shift it, based upon a button click preformed by the user.
Beforehand, I instruct the user to enter in ranges of logical coordinates to determine how pixel coordinates will map to a real x-y coordinate system.
Initially, I have the triangle appearing in the middle of the screen, and after a button is clicked, the triangle is shown after a certain operation is preformed on it (i.e rotation, shifting, etc.)
However, after the operation is completed and the triangle is redrawn, I see an input box also drawn in the top-left corner of the JPanel.
I'm not sure how this keeps getting drawn there.
Code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class RotateAndShiftTriangles extends JFrame {
public static void main(String[] args) { new RotateAndShiftTriangles(); }
RotateAndShiftTriangles() {
super("Drawing 50 Triangles");
final JPanel drawingPanel = new DrawTriangles();
JPanel buttonPanel = new JPanel();
JButton rotate = new JButton("Rotate"),
shift = new JButton("Shift"),
rotateShift = new JButton("Rotate and Shift"),
reset = new JButton ("Reset");
drawingPanel.setBackground(Color.WHITE);
buttonPanel.add(rotate);
buttonPanel.add(shift);
buttonPanel.add(rotateShift);
buttonPanel.add(reset);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
DrawTriangles.rWidth = Float.parseFloat(JOptionPane.showInputDialog("Input rWidth"));
DrawTriangles.rHeight = Float.parseFloat(JOptionPane.showInputDialog("Input rHeight"));
rotate.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DrawTriangles.rotate = true;
drawingPanel.repaint();
}
});
shift.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DrawTriangles.shift = true;
drawingPanel.repaint();
}
});
rotateShift.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DrawTriangles.rotate = true;
DrawTriangles.shift = true;
drawingPanel.repaint();
}
});
reset.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
DrawTriangles.reset = true;
drawingPanel.repaint();
}
});
setSize(600, 400);
add("South", buttonPanel);
add("Center", drawingPanel);
setVisible(true);
}
}
class DrawTriangles extends JPanel {
static float rWidth, rHeight, pixelSize;
static int maxX, maxY, minMaxXY, centerX, centerY;
static boolean rotate = false, shift = false, reset = false;
float angle = 0;
void initialize() {
Dimension d = getSize();
maxX = d.width - 1; maxY = d.height - 1;
pixelSize = Math.max(rWidth / maxX, rHeight / maxY);
minMaxXY = Math.min(maxX, maxY);
centerX = maxX/2; centerY = maxY/2;
}
public int iX2(float x) { return Math.round(x); }
public int iY2(float y) { return maxY - Math.round(y); }
public static int iX(float x) { return Math.round(centerX + x / pixelSize); }
public static int iY(float y) { return Math.round(centerY - y / pixelSize); }
public static float fx(int x) { return (x - centerX) * pixelSize; }
public static float fy(int y) { return (centerY - y) * pixelSize; }
public void paint(Graphics g) {
super.paintComponent(g);
initialize();
int left = iX(-rWidth/2), right = iX(rWidth/2);
int top = iY(rHeight/2), bot = iY(-rHeight/2);
g.drawString("X: " + -rWidth/2 + " Y: " + rHeight/2, left, top + 10);
g.drawString("X: " + rWidth/2 + " Y: " + rHeight/2, right - 55, top + 10);
g.drawString("X: " + -rWidth/2 + " Y: " + -rHeight/2, left, bot);
g.drawString("X: " + rWidth/2 + " Y: " + -rHeight/2, right - 55, bot);
g.setColor(Color.BLUE);
g.drawRect(left, top, right - left, bot - top);
float side = 0.95f * minMaxXY, sideHalf = 0.5F * side,
h = sideHalf * (float)Math.sqrt(3),
xA, yA, xB, yB, xC, yC,
xA1, yA1, xB1, yB1, xC1, yC1, p, q;
q = 0.05F;
p = 1 - q;
xA = centerX - sideHalf;
yA = centerY - 0.5F * h;
xB = centerX + sideHalf;
yB = yA;
xC = centerX;
yC = centerY + 0.5F * h;
if(!reset) {
if(rotate) {
angle += Float.parseFloat(JOptionPane.showInputDialog("Input Angle of Rotation (in degrees)"));
float xR = fx(Integer.parseInt(JOptionPane.showInputDialog("Input X Coordinate for Rotation"))),
yR = fx(Integer.parseInt(JOptionPane.showInputDialog("Input Y Coordinate for Rotation")));
xA = rotateX(xA, yA, xR, yR, angle);
yA = rotateY(xA, yA, xR, yR, angle);
xB = rotateX(xB, yB, xR, yR, angle);
yB = rotateY(xB, yB, xR, yR, angle);
xC = rotateX(xC, yC, xR, yR, angle);
yC = rotateY(xC, yC, xR, yR, angle);
rotate = false;
}
if(shift) {
float xShift = -fx(Integer.parseInt(JOptionPane.showInputDialog("Input X Coordinate for Shift"))),
yShift = -fx(Integer.parseInt(JOptionPane.showInputDialog("Input Y Coordinate for Shift")));
xA += xShift;
yA += yShift;
xB += xShift;
yB += yShift;
xC += xShift;
yC += yShift;
shift = false;
}
}
g.setColor(Color.RED);
for (int i = 0; i < 50; i++) {
g.drawLine(iX2(xA), iY2(yA), iX2(xB), iY2(yB));
g.drawLine(iX2(xB), iY2(yB), iX2(xC), iY2(yC));
g.drawLine(iX2(xC), iY2(yC), iX2(xA), iY2(yA));
if(i == 0) {
g.setColor(Color.BLACK);
g.drawString("A: X- " + xA + " Y- " + yA, 0, 50);
g.drawString("B: X- " + xB + " Y- " + yB, 0, 60);
g.drawString("C: X- " + xC + " Y- " + yC, 0, 70);
g.setColor(Color.RED);
}
xA1 = p * xA + q * xB;
yA1 = p * yA + q * yB;
xB1 = p * xB + q * xC;
yB1 = p * yB + q * yC;
xC1 = p * xC + q * xA;
yC1 = p * yC + q * yA;
xA = xA1; xB = xB1; xC = xC1;
yA = yA1; yB = yB1; yC = yC1;
}
if(reset)
angle = 0;
reset = false;
}
public float rotateX(float x, float y, float xR, float yR, float angle) {
angle *= (Math.PI / 180.0);
float c = (float)Math.cos(angle), s = (float)Math.sin(angle),
xF = x - xR, yF = y - yR,
rx = xF * c - yF * s;
return rx + xR;
}
public float rotateY(float x, float y, float xR, float yR, float angle) {
angle *= (Math.PI / 180.0);
float c = (float)Math.cos(angle), s = (float)Math.sin(angle),
xF = x - xR, yF = y - yR,
ry = xF * s + yF * c;
return ry + yR;
}
}
I keep getting this
You are triggering JOptionPane popups inside your paint() method.
Calls to .paint() and its siblings should limit themselves to redrawing the object, nothing else. As is, your code will cause your .paint() method to block until the popup is closed, then continue processing where it left off, potentially picking up artifacts still on the screen. As you can see here, the background is painted (by the call to super.paintComponent()) then the popup is drawn and closed, then the rest of your .paint() method runs, but since the background has already been painted, nothing repaints over where the popup was.
You should move code like:
angle += Float.parseFloat(JOptionPane.showInputDialog("Input Angle of Rotation (in degrees)"));
float xR = fx(Integer.parseInt(JOptionPane.showInputDialog("Input X Coordinate for Rotation"))),
yR = fx(Integer.parseInt(JOptionPane.showInputDialog("Input Y Coordinate for Rotation")));
and
float xShift = -fx(Integer.parseInt(JOptionPane.showInputDialog("Input X Coordinate for Shift"))),
yShift = -fx(Integer.parseInt(JOptionPane.showInputDialog("Input Y Coordinate for Shift")));
out into the appropriate ActionListener methods, set the necessary values, and then use them from within your paint() method.
You should also be consistent about using .paint() and .paintComponent(), like #camickr suggests, don't have one method call its sibling's super.
public void paint(Graphics g) {
super.paintComponent(g);
Don't know if it is the only problem but, custom painting is done by overriding the paintComponent() method:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Edit:
Other comments, not directly related to the problem, but important for proper design:
add("South", buttonPanel);
add("Center", drawingPanel);
Don't use hard coded literals. The layout manager will provide variable you can use. Also, that form of the add(...) method is not recommended (read the API). The new form is:
add(buttonPanel, BordeLayout.PAGE_END);
add("Center", BorderLayout.CENTER);
Don't use static methods and variables. If you want to change a property of your class then create "setter" method. For example create a setter method:
public void setRotate(Boolean rotate)
{
this.rotate = rotate
repaint();
}
Also, not that the setter method invokes the repaint() method. This is because your custom class (not the code that uses the class) should be responsible for doing the repaint.
Then invoke the setter method:
//DrawTriangles.rotate = true; // wrong
drawingPanel.setRotate(true);
Looks like this only happens if dialogs are displayed. I've modified the code and hardcoded some values, it worked without problems.
if(!reset) {
if(rotate) {
angle += Float.parseFloat("15");
float xR = fx(3),
yR = fx(3);
// other stuff...
}
I suggest you try displaying dialogs and setting corresponding values before repainting the components, something similar to this:
shift.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
float xShift = -DrawTriangles.fx(Integer.parseInt(JOptionPane.showInputDialog("Input X Coordinate for Shift"))),
yShift = -DrawTriangles.fx(Integer.parseInt(JOptionPane.showInputDialog("Input Y Coordinate for Shift")));
drawingPanel.xShift = xShift;
drawingPanel.yShift = yShift;
DrawTriangles.shift = true;
drawingPanel.repaint();
}
});
Using BufferedImage corrected the drawing but still the exception occurred.
public void paint(Graphics gg) {
BufferedImage bf = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = bf.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
...
gg.drawImage(bf, 0, 0, null);
if(reset)
angle = 0;
reset = false;
}
I have been trying to figure this one out for some time now, I am making a program that uses a triangle as an arrow and been trying to figure out how to make an arrow with two points, meaning that that the first point would be at the midpoint of the base of the triangle, while the second point would be at the tip facing the direction away from the first point.
This crude paint drawing should help figure out what I am talking about
http://i.stack.imgur.com/f3ktz.png (Would put direct images but don't have enough rep)
Now, I went through and tried figuring out how to calculate those other two endpoints of the triangle so I could make the polygon, but I am not doing it correctly because I am getting a triangle that isn't isosceles and the endpoints don't create a line perpendicular to the original line.
What I am currently getting (With some drawing over it to show the points)
http://i.stack.imgur.com/dljsn.png
My current code
public class Triangle extends Shape{
private boolean assigned = false;
private int[] x;
private int[] y;
public Triangle(Point startPoint, Point endPoint){
this.startPoint = startPoint;
this.endPoint = endPoint;
}
#Override
public void draw(Graphics g) {
g.setColor(Color.white);
if(!assigned) {
x = new int[3];
y = new int[3];
double distance = startPoint.distance(endPoint);
double halfDistance = distance/2;
double angle = getAngle(startPoint,endPoint)- Math.PI/2.0;
x[0] = (int)endPoint.getX();
y[0] = (int)endPoint.getY();
x[1] = (int)((Math.sin(angle)*halfDistance) + startPoint.getX());
y[1] = (int)((Math.cos(angle)*halfDistance) + startPoint.getY());
x[2] = (int)(startPoint.getX() - (Math.sin(angle)*halfDistance));
y[2] = (int)(startPoint.getY() - (Math.cos(angle)*halfDistance));
assigned = true;
if(endPoint.distance(x[1],y[1]) == (Math.sqrt(5)*halfDistance))
System.out.println("DEBUG: Confirm Correct 1");
if(endPoint.distance(x[1],y[1]) == endPoint.distance(x[2],y[2]))
System.out.println("DEBUG: Confirm Correct 2");
}
g.fillPolygon(x,y,3);
g.setColor(Color.blue);
}
private double getAngle(Point pointOne, Point pointTwo){
double angle = Math.atan2(pointTwo.getY()- pointOne.getY(),pointTwo.getX()-pointOne.getX());
while(angle < 0){
angle += (2.0*Math.PI);
}
return angle;
}
}
I have working at this for hours and can't seem to figure it out, someone please help.
So, I ended up replacing double angle = getAngle(startPoint,endPoint)- Math.PI/2.0; with something more like double angle = -Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
I wrote this little test program, which allows you to move to points around a circle and which generates the resulting triangle...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane tp = new TestPane();
JPanel control = new JPanel(new BorderLayout());
control.add(tp);
final JSlider startAngel = new JSlider(0, 359);
final JSlider endAngel = new JSlider(0, 359);
JPanel sliders = new JPanel(new GridLayout(1, 2));
sliders.add(startAngel);
sliders.add(endAngel);
startAngel.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
tp.setStartAngle(startAngel.getValue());
}
});
endAngel.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
tp.setEndAngle(endAngel.getValue());
}
});
startAngel.setValue(0);
endAngel.setValue(180);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(control);
frame.add(sliders, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Point startPoint, endPoint;
private float startAngle = 0;
private float endAngle = 180;
public TestPane() {
}
#Override
public void invalidate() {
super.invalidate();
recalculate();
}
protected void recalculate() {
int dim = Math.min(getWidth(), getHeight());
dim -= 50;
float radius = dim / 2f;
startPoint = getPointOnCircle(startAngle, radius);
endPoint = getPointOnCircle(endAngle, radius);
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected Point getPointOnCircle(float degress, float radius) {
int x = Math.round(getWidth() / 2);
int y = Math.round(getHeight() / 2);
double rads = Math.toRadians(degress - 90); // 0 becomes the top
// Calculate the outter point of the line
int xPosy = Math.round((float) (x + Math.cos(rads) * radius));
int yPosy = Math.round((float) (y + Math.sin(rads) * radius));
return new Point(xPosy, yPosy);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int[] x = new int[3];
int[] y = new int[3];
double distance = startPoint.distance(endPoint);
double halfDistance = distance / 2;
double angle = -Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
System.out.println(angle);
x[0] = (int) endPoint.getX();
y[0] = (int) endPoint.getY();
x[1] = (int) ((Math.sin(angle) * halfDistance) + startPoint.getX());
y[1] = (int) ((Math.cos(angle) * halfDistance) + startPoint.getY());
x[2] = (int) (startPoint.getX() - (Math.sin(angle) * halfDistance));
y[2] = (int) (startPoint.getY() - (Math.cos(angle) * halfDistance));
g2d.setColor(Color.RED);
g2d.fillPolygon(x, y, 3);
g2d.setColor(Color.BLUE);
g2d.fillOval(startPoint.x - 5, startPoint.y - 5, 10, 10);
g2d.setColor(Color.GREEN);
g2d.fillOval(endPoint.x - 5, endPoint.y - 5, 10, 10);
g2d.dispose();
}
public void setStartAngle(float value) {
startAngle = value;
recalculate();
}
public void setEndAngle(float value) {
endAngle = value;
recalculate();
}
}
}
If that still gives you some weird results, apart from sharing some test data, I might consider using something like Math.atan2(Math.abs(endPoint.y - startPoint.y), Math.abs(endPoint.x - startPoint.x)) or simular
You don't need to calculate angles at all.
double startX = 40;
double startY = 120;
double endX = 110;
double endY = 15;
double deltaX = ( startY - endY ) / 2;
double deltaY = ( endX - startX ) / 2;
double[] polygonX = new double[3];
double[] polygonY = new double[3];
polygonX[0] = endX;
polygonY[0] = endY;
polygonX[1] = startX - deltaX;
polygonY[1] = startY - deltaY;
polygonX[2] = startX + deltaX;
polygonY[2] = startY + deltaY;
The drawing is VERY bad :D, but the point is that:
cos(ang) = 'distance' / ( startY - endY )
and
cod(ang) = ('distance'/2) / deltaX
so
deltaX = ( startY - endY ) / 2
The same aplies to deltaY = ( endX - startX ) / 2
So the other 2 point of the triangle, will be the startPoint minus and plus those deltas.
I'm trying to make some shapes with Java. I created two rectangles with two different colors but I want to create a star shape and I can't find useful source to help me doing this.
Here is my code:
import java.awt.*;
import javax.swing.*;
public class shapes extends JPanel{
#Override
public void paintComponent(Graphics GPHCS){
super.paintComponent(GPHCS);
GPHCS.setColor(Color.BLUE);
GPHCS.fillRect(25,25,100,30);
GPHCS.setColor(Color.GRAY);
GPHCS.fillRect(25,65,100,30);
GPHCS.setColor(new Color(190,81,215));
GPHCS.drawString("This is my text", 25, 120);
}
}
You could try using a polygon and some basic math:
int midX = 500;
int midY = 340;
int radius[] = {118,40,90,40};
int nPoints = 16;
int[] X = new int[nPoints];
int[] Y = new int[nPoints];
for (double current=0.0; current<nPoints; current++)
{
int i = (int) current;
double x = Math.cos(current*((2*Math.PI)/max))*radius[i % 4];
double y = Math.sin(current*((2*Math.PI)/max))*radius[i % 4];
X[i] = (int) x+midX;
Y[i] = (int) y+midY;
}
g.setColor(Color.WHITE);
g.fillPolygon(X, Y, nPoints);
You can also use existing classes e.g. http://java-sl.com/shapes.html for regular polygons and stars.
The Polygon class can be considered as a legacy class that has been there since Java 1.0, but should hardly be used any more in new code. The odd way of specifying the x/y coordinates in separate arrays, and, more importantly, the fact that it only supports int[] arrays limits its application areas. Although it implements the Shape interface, there are more modern implementations of this interface that can be used to represent polygons. In most cases, describing the polygon as a Path2D is easier and more flexible. One can create a Path2D p = new Path2D.Double(); and then do a sequence of moveTo and lineTo calls to geneate the desired shape.
The following program shows how the Path2D class may be used to generate star shapes. The most important method is the createStar method. It is very generic. It receives
the center coordinates for the star
the inner and outer radius of the star
the number of rays that the star should have
the angle where the first ray should be (i.e. the rotation angle of the star)
If desired, a simpler method may be wrapped around this one - as with the createDefaultStar example in the code below.
The program shows different stars, painted as lines and filled with different colors and radial gradient paints, as examples:
The complete program as a MCVE:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RadialGradientPaint;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawStarShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new DrawStarShapePanel());
f.setSize(600, 600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class DrawStarShapePanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.draw(createDefaultStar(50, 200, 200));
g.setPaint(Color.RED);
g.fill(createStar(400, 400, 40, 60, 10, 0));
g.setPaint(new RadialGradientPaint(
new Point(400, 200), 60, new float[] { 0, 1 },
new Color[] { Color.RED, Color.YELLOW }));
g.fill(createStar(400, 200, 20, 60, 8, 0));
g.setPaint(new RadialGradientPaint(
new Point(200, 400), 50, new float[] { 0, 0.3f, 1 },
new Color[] { Color.RED, Color.YELLOW, Color.ORANGE }));
g.fill(createStar(200, 400, 40, 50, 20, 0));
}
private static Shape createDefaultStar(double radius, double centerX,
double centerY)
{
return createStar(centerX, centerY, radius, radius * 2.63, 5,
Math.toRadians(-18));
}
private static Shape createStar(double centerX, double centerY,
double innerRadius, double outerRadius, int numRays,
double startAngleRad)
{
Path2D path = new Path2D.Double();
double deltaAngleRad = Math.PI / numRays;
for (int i = 0; i < numRays * 2; i++)
{
double angleRad = startAngleRad + i * deltaAngleRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double relX = ca;
double relY = sa;
if ((i & 1) == 0)
{
relX *= outerRadius;
relY *= outerRadius;
}
else
{
relX *= innerRadius;
relY *= innerRadius;
}
if (i == 0)
{
path.moveTo(centerX + relX, centerY + relY);
}
else
{
path.lineTo(centerX + relX, centerY + relY);
}
}
path.closePath();
return path;
}
}
I have 2 method.
1)
public static Bitmap drawStar(int W, int H, int color, boolean andRing)
{
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(color);
float midW ,min ,fat ,half ,radius;
if(andRing)
{
midW = W / 2;
min = Math.min(W, H);
half = min / 2;
midW = midW - half;
fat = min / 17;
radius = half - fat;
paint.setStrokeWidth(fat);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(midW + half, half, radius, paint);
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.5f, half * 0.84f);
path.lineTo( half * 1.5f, half * 0.84f);
path.lineTo( half * 0.68f, half * 1.45f);
path.lineTo( half * 1.0f, half * 0.5f);
path.lineTo( half * 1.32f, half * 1.45f);
path.lineTo( half * 0.5f, half * 0.84f);
}
else
{
min = Math.min(W, H);
half = min/2;
path.reset();
paint.setStyle(Paint.Style.FILL);
path.moveTo( half * 0.1f , half * 0.65f);
path.lineTo( half * 1.9f , half * 0.65f);
path.lineTo( half * 0.40f , half * 1.65f);
path.lineTo( half , 0 );
path.lineTo( half * 1.60f, half * 1.65f);
path.lineTo( half * 0.1f, half * 0.65f);
}
canvas.drawPath(path, paint);
return output;
}
2)
public static Bitmap drawStar(int W,int H,int spikes,int innerRadius,int outerRadius, int backColor,boolean border, int borderColor)
{
if(W < 10)
W = 10;
if(H < 10)
H = 10;
if(spikes < 5)
spikes = 5;
int smallL = W;
if(H < W)
smallL = H;
if(outerRadius > smallL/2)
outerRadius = smallL/2;
if(innerRadius < 5)
innerRadius = 5;
if(border)
{
outerRadius -=2;
innerRadius -=2;
}
Path path = new Path();
Bitmap output = Bitmap.createBitmap(W, H, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(backColor);
int cx = W/2;
int cy = H/2;
double rot = Math.PI / 2 * 3;
float x,y;
double step = Math.PI / spikes;
path.moveTo(cx, cy - outerRadius);
for (int i = 0; i < spikes; i++)
{
x = (float) (cx + Math.cos(rot) * outerRadius);
y = (float) (cy + Math.sin(rot) * outerRadius);
path.lineTo(x, y);
rot += step;
x = (float) (cx + Math.cos(rot) * innerRadius);
y = (float) (cy + Math.sin(rot) * innerRadius);
path.lineTo(x, y);
rot += step;
}
path.lineTo(cx, cy - outerRadius);
path.close();
canvas.drawPath(path, paint);
if(border)
{
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);
paint.setColor(borderColor);
canvas.drawPath(path, paint);
}
return output;
}