Rotating shape and drawing it at the original position - java

I'm trying to draw a rotated shape at a given point. To give an example, in the following image, the red rectangle is a non-rotated rectangle drawn at a point and then the blue rectangle is rotated and drawn at the same position. The blue rectangle is the outcome I'm aiming for.
I've been experimenting and trying different methods. Currently, here is what I used for the image:
Point point = new Point(300, 300);
Dimension dim = new Dimension(200, 100);
double radians = Math.toRadians(30);
g.setColor(new java.awt.Color(1f, 0f, 0f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
translate(g, dim, radians);
g.rotate(radians, point.getX(), point.getY());
g.setColor(new java.awt.Color(0f, 0f, 1f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
private static void translate(Graphics2D g, Dimension dim, double radians) {
if (radians > Math.toRadians(360)) {
radians %= Math.toRadians(360);
}
int xOffsetX = 0;
int xOffsetY = 0;
int yOffsetX = 0;
int yOffsetY = 0;
if (radians > 0 && radians <= Math.toRadians(90)) {
xOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(90) && radians <= Math.toRadians(180)) {
xOffsetX -= dim.getWidth();
xOffsetY -= dim.getHeight();
yOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(180) && radians <= Math.toRadians(270)) {
xOffsetX -= dim.getWidth();
yOffsetX -= dim.getWidth();
yOffsetY -= dim.getHeight();
} else {
yOffsetX -= dim.getWidth();
}
int x = rotateX(xOffsetX, xOffsetY, radians);
int y = rotateY(yOffsetX, yOffsetY, radians);
g.translate(x, y);
}
private static int rotateX(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.cos(radians) - y * Math.sin(radians));
}
private static int rotateY(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.sin(radians) + y * Math.cos(radians));
}
This works for rectangles but doesn't work for other types of shapes. I'm trying to figure out if there is a way to accomplish this for every type of shape. Also note that the code is just for testing purposes and there are a lot of bad practices in it, like calling Math.toRadians so much.

Something like this?
It can be achieved using a rotate transform first, then using the bounds of the rotated shape as a basis, the translate transform can be used to shift it back to meet the top most y and leftmost x values of the original rectangle.
See the getImage() method for one implementation of that.
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
// rotate the original shape with no regard to the final bounds
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
// get the bounds of the rotated shape
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
// calculate the x,y offset needed to shift it to top/left bounds of original rectangle
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
// shift the new shape to the top left of original rectangle
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Here is the complete source code:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.EmptyBorder;
public class TransformedShape {
private JComponent ui = null;
JLabel output = new JLabel();
JToolBar tools = new JToolBar("Tools");
ChangeListener changeListener = (ChangeEvent e) -> {
refresh();
};
int pad = 5;
Rectangle2D.Double rectangle = new Rectangle2D.Double(pad,pad,200,100);
SpinnerNumberModel angleModel = new SpinnerNumberModel(30, 0, 90, 1);
public TransformedShape() {
initUI();
}
private BufferedImage getImage() {
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Area combinedShape = new Area(rotateAndTranslateShape);
combinedShape.add(new Area(rectangle));
Rectangle2D r = combinedShape.getBounds2D();
BufferedImage bi = new BufferedImage((int)(r.getWidth()+(2*pad)), (int)(r.getHeight()+(2*pad)), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(new Color(255,0,0,127));
g.fill(rectangle);
g.setColor(new Color(0,0,255,127));
g.fill(rotateAndTranslateShape);
g.dispose();
return bi;
}
private void addModelToToolbar(String label, SpinnerNumberModel model) {
tools.add(new JLabel(label));
JSpinner spinner = new JSpinner(model);
spinner.addChangeListener(changeListener);
tools.add(spinner);
}
public final void initUI() {
if (ui!=null) return;
ui = new JPanel(new BorderLayout(4,4));
ui.setBorder(new EmptyBorder(4,4,4,4));
ui.add(output);
ui.add(tools,BorderLayout.PAGE_START);
addModelToToolbar("Angle", angleModel);
refresh();
}
private void refresh() {
output.setIcon(new ImageIcon(getImage()));
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
TransformedShape o = new TransformedShape();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}

You have a shape, any shape.
You have a point (px,py) and you want to rotate the shape around this point and angle ag measured counter-clokwise.
For each point of the shape the proccess has three steps:
Translate to (px,py)
Rotate
Translate back to (0,0)
The translation is fully simple
xNew = xOld - px
yNew = yOld - py
The rotation is a bit less simple
xRot = xNew * cos(ag) - yNew * sin(ag)
yRot = xNew * sin(ag) + yNew * cos(ag)
Finally the translation back:
xDef = xRot + px
yDef = yRot + py
A bit of explanation: Any transformation can be seen in two ways: 1) I move the shape 2) I move the axis-system. If you think about it, you'll find that the trasnsformation is relative: seen from the axis point of view or seen from the shape point of view.
So, you can say "I want coordinates in the translated system", or you can also say "I want the coordinates of the translated shape".
It doesn't matter what point of view you chose, the equations are the same.
I'm explaining this so much, just to achieve you realize which is the positive direction of the angle: clockwise or counter-clockwise.

Related

How to fill a triangle using lines?

I'm trying to fill a triangle using horizontal lines and I can't figure out what's wrong with my current method. Before anyone says to just use fillPolygon, I can't use that. I need to fill it using lines.
It seems to work ok in some situations and completely break in others.
That's how it should look. But then I tried applying my method to a rotating 3D cube and...
I have no idea what's wrong. Also, the red borders are also one of my triangle methods. Those work perfectly and the filled triangles and the outlined triangles have the same vertices inputted.
public void filledTri(int x1,int y1,int x2,int y2,int x3,int y3){
int[] xs = {x1,x2,x3};
int[] ys = {y1,y2,y3};
//Sort vertices in vertical order so A/1 is highest and C/3 is lowest
int I,tempx,tempy;
for(int i=1;i<3;i++){
I = i-1;
tempx = xs[i];
tempy = ys[i];
while(I>=0&&tempy<ys[I]){
xs[I+1] = xs[I];
ys[I+1] = ys[I];
I--;
}
xs[I+1] = tempx;
ys[I+1] = tempy;
}
//Set left and right edges
linepts ab = new linepts(xs[0],ys[0],xs[1],ys[1]),
ac = new linepts(xs[0],ys[0],xs[2],ys[2]);
linepts[] lines = {ab.getEndX() < ac.getEndX() ? ab : ac,
ab.getEndX() > ac.getEndX() ? ab : ac,
new linepts(xs[1],ys[1],xs[2],ys[2])};
//Fill triangle
int startY = ys[0],endY = ys[2];
for(int y=startY;y<=endY;y++){
if(y>ys[1])
horizontalLine((int)Math.round(lines[2].getX(y)),
y,
(int)Math.round(lines[1].getX(y)));
else
horizontalLine((int)Math.round(lines[0].getX(y)),
y,
(int)Math.round(lines[1].getX(y)));
}
getX(int y) gets me the x coordinate where the line passes through the y value. If it's a horizontal line it just returns the line's start x
Point A is the highest on screen and the lowest value, B is the middle, and C is the lowest on screen and highest value
I'm using a buffered image on a jframe to draw it if that helps.
I've seen what you are doing in a Software Renderer tutorial. It is explained in this and this episodes.
What he does there is scanning the longest to get every pixel on that line, it stores the min X value and max X value, (given by the other 2 lines). He originally makes it for specific triangles, but then he upgrades the code to accept generic triangles.
Here's a nice diagram to explain that:
I assume what you're experiencing is because of projecting 3D triangles into 2D ones (clipping, triangles get infinite coordinates, or because you're program doesn't takes too well empty triangles.
One way is to draw the lines to an image, then use that image in a TexturePaint to fill a Shape (the triangle in this case).
It might look something like this: (if you use a single image containing one red line, put it over a random BG color, and use a smoothed 1.5 pixel stroke to draw the shape itself in blue).
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.util.*;
public class LinesFillShape {
private JComponent ui = null;
LinesFillShape() {
initUI();
}
public final void initUI() {
if (ui != null) {
return;
}
ui = new JPanel(new BorderLayout(4, 4));
ui.setBorder(new EmptyBorder(4, 4, 4, 4));
ui.add(new JLabel(new ImageIcon(getImage())));
}
private void drawPolygon(Graphics2D g, int sz, Random r) {
int[] xpoints = {
r.nextInt(sz), r.nextInt(sz), r.nextInt(sz)
};
int[] ypoints = {
r.nextInt(sz), r.nextInt(sz), r.nextInt(sz)
};
Polygon p = new Polygon(xpoints, ypoints, 3);
Color bg = new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255));
g.setColor(bg);
g.fill(p);
g.setPaint(
new TexturePaint(getTexture(),
new Rectangle2D.Double(0, 0, 8, 8)));
g.fill(p);
g.setStroke(new BasicStroke(1.5f));
g.setColor(Color.BLUE);
g.draw(p);
}
private BufferedImage getImage() {
int sz = 600;
BufferedImage bi = new BufferedImage(sz, sz, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Random r = new Random();
drawPolygon(g, sz, r);
drawPolygon(g, sz, r);
drawPolygon(g, sz, r);
g.dispose();
return bi;
}
private BufferedImage getTexture() {
BufferedImage bi = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.setColor(Color.RED);
// TODO: something more interesting here..
g.drawLine(0, 0, 0, 8);
g.dispose();
return bi;
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
LinesFillShape o = new LinesFillShape();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
I have not scrutinized your code but I can tell you that you are not always joining intersections with the relevant sides.
You can work as follows:
For a given scanline (some Y),
compare the ordinates of the endpoints of the three sides in pairs (Y0-Y1, Y1-Y2, Y2-Y0),
there will be zero or two sides that straddle Y; use the condition (Yi > Y) != (Yi+1 > Y) (indexes modulo 3), and no other,
for the sides that straddle Y, compute the intersection point.
You will scan from min(Y0, Y1, Y2) to max(Y0, Y1, Y2) and each time join the two intersections.

Drawing fully transparent "white" in Java BufferedImage

This might sound like a bit of strange title, but bear with me, there is a reason:
I am trying to generate a white glow around a text on a gray background.
To generate the glow, I created a new BufferedImage that's bigger than the text, then I drew the text in white onto the canvas of the image and ran a Gaussian Blur over the image via a ConvolveOp, hoping for something like this:
At first I was a bit surprised when the glow turned out darker than the gray background of the text:
But after a bit of thinking, I understood the problem:
The convolution operates on each color channel (R, G, B, and A) independently to calculate the blurred image. The transparent background of the picture has color value 0x00000000, i.e. a fully transparent black! So, when the convolution filter runs over the image, it not only blends the alpha value, but also mixes the black into the RGB values of the white pixels. This is why the glow comes out dark.
To fix this, I need to initialize the image to 0x00FFFFFF, i.e. a fully transparent white instead, but if I just set that color and fill a rectangle with it, it simply does nothing as Java says "well, it's a fully transparent rectangle that you're drawing! That's not going to change the image... Let me optimize that away for you... Done... You're welcome.".
If I instead set the color to 0x01FFFFFF, i.e. an almost fully transparent white, it does draw the rectangle and the glow looks beautiful, except I end up with a very faint white box around it...
Is there a way I can initialize the image to 0x00FFFFFF everywhere?
UPDATE:
I found one way, but it's probably as non-optimal as you can get:
I draw an opaque white rectangle onto the image and then I run a RescaleOp over the image that sets all alpha values to 0. This works, but it's probably a terrible approach as far as performance goes.
Can I do better somehow?
PS: I'm also open to entirely different suggestions for creating such a glow effect
The main reason why the glow appeared darker with your initial approach is most likely that you did not use an image with a premultiplied alpha component. The JavaDoc of ConvolveOp contains some information about how the alpha component is treated during a convolution.
You could work around this with an "almost fully transparent white". But alternatively, you may simply use an image with premultiplied alpha, i.e. one with the type TYPE_INT_ARGB_PRE.
Here is a MCVE that draws a panel with some text, and some pulsing glow around the text (remove the timer and set a fixed radius to remove the pulse - I couldn't resist playing around a little here ...).
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TextGlowTest
{
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 TextGlowPanel());
f.setSize(300,200);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class TextGlowPanel extends JPanel
{
private BufferedImage image;
private int radius = 1;
TextGlowPanel()
{
Timer t = new Timer(50, new ActionListener()
{
long startMillis = -1;
#Override
public void actionPerformed(ActionEvent e)
{
if (startMillis == -1)
{
startMillis = System.currentTimeMillis();
}
long d = System.currentTimeMillis() - startMillis;
double s = d / 1000.0;
radius = (int)(1 + 15 * (Math.sin(s * 3) * 0.5 + 0.5));
repaint();
}
});
t.start();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
gr.setColor(Color.GRAY);
int w = getWidth();
int h = getHeight();
gr.fillRect(0, 0, w, h);
if (image == null || image.getWidth() != w || image.getHeight() != h)
{
// Must be prmultiplied!
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
}
Graphics2D g = image.createGraphics();
Font font = g.getFont().deriveFont(70.0f).deriveFont(Font.BOLD);
g.setFont(font);
g.setComposite(AlphaComposite.Src);
g.setColor(new Color(255,255,255,0));
g.fillRect(0,0,w,h);
g.setComposite(AlphaComposite.SrcOver);
g.setColor(new Color(255,255,255,0));
g.fillRect(0,0,w,h);
g.setColor(Color.WHITE);
g.drawString("Glow!", 50, 100);
image = getGaussianBlurFilter(radius, true).filter(image, null);
image = getGaussianBlurFilter(radius, false).filter(image, null);
g.dispose();
g = image.createGraphics();
g.setFont(font);
g.setColor(Color.BLUE);
g.drawString("Glow!", 50, 100);
g.dispose();
gr.drawImage(image, 0, 0, null);
}
// From
// http://www.java2s.com/Code/Java/Advanced-Graphics/GaussianBlurDemo.htm
public static ConvolveOp getGaussianBlurFilter(
int radius, boolean horizontal)
{
if (radius < 1)
{
throw new IllegalArgumentException("Radius must be >= 1");
}
int size = radius * 2 + 1;
float[] data = new float[size];
float sigma = radius / 3.0f;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
for (int i = -radius; i <= radius; i++)
{
float distance = i * i;
int index = i + radius;
data[index] =
(float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
total += data[index];
}
for (int i = 0; i < data.length; i++)
{
data[i] /= total;
}
Kernel kernel = null;
if (horizontal)
{
kernel = new Kernel(size, 1, data);
}
else
{
kernel = new Kernel(1, size, data);
}
return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
}
}
I've found that clearRect should paint a transparent color.
g.setBackground(new Color(0x00FFFFFF, true));
g.clearRect(0, 0, img.getWidth(), img.getHeight());
You should also be able to force the BufferedImage to fill with a transparent color by setting the pixel data directly.
public static void forceFill(BufferedImage img, int rgb) {
for(int x = 0; x < img.getWidth(); x++) {
for(int y = 0; y < img.getHeight(); y++) {
img.setRGB(x, y, rgb);
}
}
}
It is not clearly documented but I tested it and setRGB appears to accept an ARGB value.

Java draw circle and lines on Swing

I'm trying to draw a circle with a random center inside a big bigger circular surface. (I'm actually trying to simulate a human and his eyesight inside a room!) I need to draw a random line (call it line1) passing through its center which will intersect with the surface. line1 does not necessarily pass the center of circular surface. I also need to draw two lines forming 60 degree, facing on one side of line1. Can anyone help me with that?
I created an example of what I need to draw.
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;
import javax.swing.JFrame;
public class ShapeTest extends JFrame{
int width=500;
int height=500;
public ShapeTest(){
setSize(width,height);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String a[]){
new ShapeTest();
}
public void paint(Graphics g){
// Circular Surface
drawCircleByCenter(g, width/2, height/2, width/2);
Random r = new Random();
Point center = new Point();
center.x=r.nextInt(width/2);
center.y=r.nextInt(width/2);
drawCircleByCenter(g, center.x, center.y, width/15);
}
void drawCircleByCenter(Graphics g, int x, int y, int radius){
//g.setColor(Color.LIGHT_GRAY);
g.drawOval(x-radius, y-radius, 2*radius, 2*radius);
}
}
Start by changing your method to draw a circle based on its center and radius to a method which returns a Ellipse2D object representing the circle. This will allow us to do some clipping and other things with the shape besides just draw it.
Setting the clip to be the shape of your large circle prevents stray marks from being made where you don't want them (think "color inside the lines"). This is important because when we draw the circles and lines inside the big circle, some of them will be too big and would otherwise mark outside the bounds of the big circle.
Once we set the clip, we use the method Line2D getVector(Point2D, double, length) with an origin at the center of the large circle, a random angle and a random length (capped to keep the small blue circle inside the big circle). Think of this a random polar coordinate with the center of the large circle as the origin. The end point of this vector is used to mark the center of the small circle.
Using the center of the small circle as a starting point, we can generate two vectors in opposite directions (just negate the length of one to get it going the other direction) by using a random direction angle. We use a length equal to the diameter of the big circle to make certain that the lines will always go all the way up to the edge of the big circle (but not past, thanks to our clip).
We simply add 60 and 120 degrees to the angle of our blue dashed line and draw two green lines calculating the vectors the same way we did for the two blue dashed lines, except we don't need to create ones with negated lengths. We can also add a normal vector in for good measure simply by adding 90 degrees to the angle of the blue dashed line.
Lastly, we pick some random polar coordinates (just like we did for the small blue circle) to represent some people, and using the intersection of the people with the areas created by the various lines, we can see where they are at and draw them up with color coded values.
Now that we have all the people, we eliminate the clip and draw the big circle and voila!
Check out Draw a line at a specific angle in Java for details on how I calculated the vectors for the lines.
But enough talk, here's the code:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeTest extends JFrame {
private static final long serialVersionUID = 1L;
private int width = 500;
private int height = 500;
private int padding = 50;
private BufferedImage graphicsContext;
private JPanel contentPanel = new JPanel();
private JLabel contextRender;
private Stroke dashedStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2f, new float[] {3f, 3f}, 0f);
private Stroke solidStroke = new BasicStroke(3.0f);
private RenderingHints antialiasing;
private Random random = new Random();
public static void main(String[] args) {
//you should always use the SwingUtilities.invodeLater() method
//to perform actions on swing elements to make certain everything
//is happening on the correct swing thread
Runnable swingStarter = new Runnable()
{
#Override
public void run(){
new ShapeTest();
}
};
SwingUtilities.invokeLater(swingStarter);
}
public ShapeTest(){
antialiasing = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsContext = new BufferedImage(width + (2 * padding), width + (2 * padding), BufferedImage.TYPE_INT_RGB);
contextRender = new JLabel(new ImageIcon(graphicsContext));
contentPanel.add(contextRender);
contentPanel.setSize(width + padding * 2, height + padding * 2);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setContentPane(contentPanel);
//take advantage of auto-sizing the window based on the size of its contents
this.pack();
this.setLocationRelativeTo(null);
this.paint();
setVisible(true);
}
public void paint() {
Graphics2D g2d = graphicsContext.createGraphics();
g2d.setRenderingHints(antialiasing);
//Set up the font to print on the circles
Font font = g2d.getFont();
font = font.deriveFont(Font.BOLD, 14f);
g2d.setFont(font);
FontMetrics fontMetrics = g2d.getFontMetrics();
//clear the background
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, graphicsContext.getWidth(), graphicsContext.getHeight());
//set up the large circle
Point2D largeCircleCenter = new Point2D.Double((double)width / 2 + padding, (double)height / 2 + padding);
double largeCircleRadius = (double)width / 2;
Ellipse2D largeCircle = getCircleByCenter(largeCircleCenter, largeCircleRadius);
//here we build the small circle
Point2D smallCircleCenter = new Point2D.Double();
double smallCircleRadius = 15;
//we need to make certain it is confined inside the larger circle
//so we choose the following values carefully
//we want to go a random direction from the circle, so chose an
//angle randomly in any direction
double smallCenterVectorAngle = random.nextDouble() * 360.0d;
//and we want to be a random distance from the center of the large circle, but
//we limit the distance based on the radius of the small circle to prevent it
//from appearing outside the large circle
double smallCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
Line2D vectorToSmallCenter = getVector(largeCircleCenter, smallCenterVectorAngle, smallCenterVectorLength);
//the resulting end point of the vector is a random distance from the center of the large circle
//in a random direction, and guaranteed to not place the small circle outside the large
smallCircleCenter.setLocation(vectorToSmallCenter.getP2());
Ellipse2D smallCircle = getCircleByCenter(smallCircleCenter, smallCircleRadius);
//before we draw any of the circles or lines, set the clip to the large circle
//to prevent drawing outside our boundaries
g2d.setClip(largeCircle);
//chose a random angle for the line through the center of the small circle
double angle = random.nextDouble() * 360.0d;
//we create two lines that start at the center and go out at the angle in
//opposite directions. We use 2*largeCircleRadius to make certain they
//will be large enough to fill the circle, and the clip we set prevent stray
//marks outside the big circle
Line2D centerLine1 = getVector(smallCircleCenter, angle, largeCircleRadius * 2);
Line2D centerLine2 = getVector(smallCircleCenter, angle, -largeCircleRadius * 2);
//now we just add 20 and 120 to our angle for the center-line, start at the center
//and again, use largeCircleRadius*2 to make certain the lines are big enough
Line2D sightVector1 = getVector(smallCircleCenter, angle + 60, largeCircleRadius * 2);
Line2D sightVector2 = getVector(smallCircleCenter, angle + 120, largeCircleRadius * 2);
Path2D visible = new Path2D.Double();
visible.moveTo(sightVector1.getX2(), sightVector1.getY2());
visible.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
visible.lineTo(sightVector2.getX2(), sightVector2.getY2());
visible.closePath();
Path2D greenSide = new Path2D.Double();
greenSide.moveTo(centerLine1.getX2(), centerLine1.getY2());
greenSide.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
greenSide.lineTo(centerLine2.getX2(), centerLine2.getY2());
greenSide.lineTo(sightVector1.getX2(), sightVector1.getY2());
greenSide.closePath();
int personCount = 5;
Area visibleArea = new Area(visible);
visibleArea.intersect(new Area(largeCircle));
Area greenSideArea = new Area(greenSide);
greenSideArea.intersect(new Area(largeCircle));
//we create a list of the people in the circle to
//prevent overlap
ArrayList<Shape> people = new ArrayList<Shape>();
people.add(smallCircle);
int i = 0;
personLoop: while (i < personCount){
double personCenterVectorAngle = random.nextDouble() * 360.0d;
double personCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
Line2D vectorToPersonCenter = getVector(largeCircleCenter, personCenterVectorAngle, personCenterVectorLength);
Point2D personCircleCenter = vectorToPersonCenter.getP2();
Ellipse2D personCircle = getCircleByCenter(personCircleCenter, smallCircleRadius);
//this little loop lets us skip a person if they have overlap
//with another person, since people don't generally overlap
Area personArea = new Area(personCircle);
for (Shape person : people)
{
Area overlapArea = new Area(person);
overlapArea.intersect(personArea);
//this means that we have found a conflicting
//person, so should skip them
if (!overlapArea.isEmpty()){
continue personLoop;
}
}
people.add(personCircle);
personArea.intersect(visibleArea);
Area greenSideAreaTest = new Area(personCircle);
greenSideAreaTest.intersect(greenSideArea);
if (personArea.isEmpty()){
if (greenSideAreaTest.isEmpty()){
g2d.setColor(Color.orange);
System.out.println("Person " + i + " is behind the blue line");
}
else {
System.out.println("Person " + i + " is in front of the blue line");
g2d.setColor(Color.cyan);
}
}
else
{
System.out.println("Person " + i + " is between the green lines");
g2d.setColor(Color.magenta);
}
//alternatively to circles intersecting the area of interest, we can check whether the center
//is in the area of interest which may make more intuitive sense visually
// if (visibleArea.contains(personCircleCenter)){
// System.out.println("Person " + i + " is between the green lines");
// g2d.setColor(Color.magenta);
// }
// else {
// if (greenSideArea.contains(personCircleCenter)) {
// System.out.println("Person " + i + " is in front of the blue line");
// g2d.setColor(Color.cyan);
// }
// else{
// g2d.setColor(Color.orange);
// System.out.println("Person " + i + " is behind the blue line");
// }
// }
g2d.fill(personCircle);
g2d.setColor(Color.black);
String itemString = "" + i;
Rectangle2D itemStringBounds = fontMetrics.getStringBounds(itemString, g2d);
double textX = personCircleCenter.getX() - (itemStringBounds.getWidth() / 2);
double textY = personCircleCenter.getY() + (itemStringBounds.getHeight()/ 2);
g2d.drawString("" + i, (float)textX, (float)textY);
i++;
}
//fill the small circle with blue
g2d.setColor(Color.BLUE);
g2d.fill(smallCircle);
//draw the two center lines lines
g2d.setStroke(dashedStroke);
g2d.draw(centerLine1);
g2d.draw(centerLine2);
//create and draw the black offset vector
Line2D normalVector = getVector(smallCircleCenter, angle + 90, largeCircleRadius * 2);
g2d.setColor(Color.black);
g2d.draw(normalVector);
//draw the offset vectors
g2d.setColor(new Color(0, 200, 0));
g2d.draw(sightVector1);
g2d.draw(sightVector2);
//we save the big circle for last, to cover up any stray marks under the stroke
//of its perimeter. We also set the clip back to null to prevent the large circle
//itselft from accidentally getting clipped
g2d.setClip(null);
g2d.setStroke(solidStroke);
g2d.setColor(Color.BLACK);
g2d.draw(largeCircle);
g2d.dispose();
//force the container for the context to re-paint itself
contextRender.repaint();
}
private static Line2D getVector(Point2D start, double degrees, double length){
//we just multiply the unit vector in the direction we want by the length
//we want to get a vector of correct direction and magnitute
double endX = start.getX() + (length * Math.sin(Math.PI * degrees/ 180.0d));
double endY = start.getY() + (length * Math.cos(Math.PI * degrees/ 180.0d));
Point2D end = new Point2D.Double(endX, endY);
Line2D vector = new Line2D.Double(start, end);
return vector;
}
private static Ellipse2D getCircleByCenter(Point2D center, double radius)
{
Ellipse2D.Double myCircle = new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius);
return myCircle;
}
}
The logic of the geometry turned out to be more tricky than I'd presumed, but this is what I think you are after.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;
class HumanEyesightLines {
int rad = 150;
int radSmall = 15;
int pad = 10;
JPanel gui = new JPanel(new BorderLayout());
BufferedImage img = new BufferedImage(
2 * (rad + pad),
2 * (rad + pad),
BufferedImage.TYPE_INT_RGB);
Timer timer;
JLabel imgDisplay;
Random rnd = new Random();
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
HumanEyesightLines() {
imgDisplay = new JLabel(new ImageIcon(img));
gui.add(imgDisplay);
File f = new File(System.getProperty("user.home"));
final File f0 = new File("HumanEyesiteLines");
f0.mkdirs();
try {
Desktop.getDesktop().open(f0);
} catch (IOException ex) {
ex.printStackTrace();
}
ActionListener animationListener = new ActionListener() {
int ii = 0;
#Override
public void actionPerformed(ActionEvent e) {
paintImage();
ii++;
if (ii < 100) {
System.out.println(ii);
File f1 = new File(f0, "eg" + ii + ".png");
try {
ImageIO.write(img, "png", f1);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
};
timer = new Timer(100, animationListener);
paintImage();
}
float[] dash = {3f, 3f};
float phase = 0f;
private final void paintImage() {
Graphics2D g = img.createGraphics();
g.setRenderingHints(rh);
g.setStroke(new BasicStroke(2f));
// fill the BG
g.setColor(Color.WHITE);
g.fillRect(0, 0, 2 * (rad + pad), 2 * (rad + pad));
// draw the big circle
Point center = new Point(rad + pad, rad + pad);
Shape bigCircle = new Ellipse2D.Double(pad, pad, 2 * rad, 2 * rad);
g.setColor(Color.MAGENTA.darker());
g.fill(bigCircle);
// set the clip to that of the big circle
g.setClip(bigCircle);
// draw the small circle
int xOff = rnd.nextInt(rad) - rad / 2;
int yOff = rnd.nextInt(rad) - rad / 2;
int x = center.x - xOff;
int y = center.y - yOff;
Shape smallCircle = new Ellipse2D.Double(
x - radSmall, y - radSmall,
2 * radSmall, 2 * radSmall);
g.setColor(Color.YELLOW);
g.fill(smallCircle);
g.setColor(Color.ORANGE);
g.draw(smallCircle);
g.setStroke(new BasicStroke(
1.5f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND,
2f,
dash,
phase));
// I don't know what the rule is for where the blue line goes, so
// will use the top left corner of the image as a 2nd anchor point.
int x0 = 0;
int y0 = 0;
double grad = (double) (y - y0) / (double) (x - x0);
// now calculate the RHS point from y = mx + b
// where b = 0 and m is the gradient
int x1 = 2 * (pad + rad);
int y1 = (int) (grad * x1);
Line2D.Double line1 = new Line2D.Double(x0, y0, x1, y1);
g.setColor(Color.BLUE);
g.draw(line1);
//find the perpendicular gradient.
double perpGrad = -1d / grad;
double perpTheta = Math.atan(perpGrad);
// angle from perp
double diffTheta = Math.PI / 6d;
g.setColor(Color.GREEN);
double viewLine1Theta = perpTheta + diffTheta;
Line2D.Double viewLine1 = getLine(x, y, viewLine1Theta);
double viewLine2Theta = perpTheta - diffTheta;
Line2D.Double viewLine2 = getLine(x, y, viewLine2Theta);
g.draw(viewLine1);
g.draw(viewLine2);
g.setColor(Color.BLACK);
Line2D.Double viewPerp = getLine(x, y, perpTheta);
g.draw(viewPerp);
g.setColor(Color.RED);
g.draw(bigCircle);
g.dispose();
imgDisplay.repaint();
}
/**
* Returns a Line2D starting at the point x1,y1 at angle theta.
*/
private final Line2D.Double getLine(double x1, double y1, double theta) {
double m;
double b;
double x2;
double y2;
if (theta < (-Math.PI / 2d)) {
System.out.println("CHANGE IT! " + theta);
m = Math.tan(theta);
b = y1 - (m * x1);
x2 = 0;
y2 = (m * x2) + b;
} else {
m = Math.tan(theta);
b = y1 - (m * x1);
x2 = 2 * (rad + pad);
y2 = (m * x2) + b;
}
/*
* System.out.println("Perp theta: " + theta); System.out.println("Line
* grad: " + m); System.out.println("Line off: " + b);
* System.out.println("x1,y1: " + x1 + "," + y1);
* System.out.println("x2,y2: " + x2 + "," + y2);
*
*/
return new Line2D.Double(x1, y1, x2, y2);
}
public JComponent getGui() {
return gui;
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
HumanEyesightLines hel = new HumanEyesightLines();
hel.start();
JOptionPane.showMessageDialog(null, hel.getGui());
hel.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}

How to rotate a polygon/points around a point in Java

I've been trying to rotate a polygon around a specified center point but everything I've tried has failed miserably. I've googled for example and found many but not a single one seems to work.
The result I'm trying to replicate is similar with the first answer to this
How to rotate an image gradually in Swing?
The difference is that I need the polygon to actually rotate, just drawing it in an angle won't cut it. (it's for simple physics modelling)
Here's my code, with several different methods I've tried to implement
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
public class rotationPanel extends JPanel {
private static final int SIZE = 500;
private static final Shape outline = makeShape();
Point p;
Point p2;
Point p3;
Point p4;
Point[] points;
Point[] npoints;
Point center;
Polygon poly;
double angle;
Timer timer;
long start;
long sleepTime;
static int runTime;
public rotationPanel(){
setSize(500,500);
setBackground(Color.DARK_GRAY);
setVisible(true);
runTime = 100; //ms
start = 0;
sleepTime = 0;
timer = new Timer();
center = new Point(250,250);
p = new Point(200,200);
p2 = new Point(150,150);
p3 = new Point(250,150);
p4 = new Point(200,100);
/*
points = new Point[4];
points[0]=p;
points[1]=p2;
points[2]=p3;
points[3]=p4;
npoints = new Point[4];
npoints[0]=p;
npoints[1]=p2;
npoints[2]=p3;
npoints[3]=p4;
poly = new Polygon();
*/
}
public void mainloop(){
start= System.currentTimeMillis();
//rotate(points,2);
p = rotatePoint(p,center);
p2 = rotatePoint(p2,center);
p3 = rotatePoint(p3,center);
p4 = rotatePoint(p4,center);
repaint();
sleepTime = runTime -(System.currentTimeMillis()-start);
System.out.println("Looped. Sleeping for:" +sleepTime+"ms");
if(sleepTime>0)
timer.schedule(new loop(), sleepTime);
else
mainloop();
}
private static Shape makeShape() {
AffineTransform at = new AffineTransform();
at.translate(SIZE/2, SIZE/2);
at.scale(20, 20);
at.rotate(Math.toRadians(35));
return at.createTransformedShape(initPoly());
}
/** Create a U shaped outline. */
private static Polygon initPoly() {
Polygon poly = new Polygon();
poly.addPoint( 1, 0);
poly.addPoint( 1, -2);
poly.addPoint( 2, -2);
poly.addPoint( 2, 1);
poly.addPoint(-2, 1);
poly.addPoint(-2, -2);
poly.addPoint(-1, -2);
poly.addPoint(-1, 0);
return poly;
}
public void rotatePoint(Point pt, double rotationAngle){
AffineTransform.getRotateInstance
(Math.toRadians(rotationAngle), center.x, center.y)
.transform(pt,pt);
}
public Point rotatePoint(Point pt, Point center)
{
angle = (Math.toRadians(150));
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
pt.x = center.x + (int) ((pt.x-center.x)*cosAngle-(pt.y-center.y)*sinAngle);
pt.y = center.y + (int) ((pt.x-center.x)*sinAngle+(pt.y-center.y)*cosAngle);
return pt;
}
public void rotate(Point[] pts, int angle){
AffineTransform.getRotateInstance
(Math.toRadians(angle), center.x, center.y)
.transform(pts,0,npoints,0,4);
points = new Point[4];
points[0]=npoints[0];
points[1]=npoints[1];
points[2]=npoints[2];
points[3]=npoints[3];
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(center.x-4, center.y-4, 8, 8);
g.setColor(Color.YELLOW);
//g.fillRect(p.x-4, p.y-4, 8, 8);
//g.fillRect(p2.x-4, p2.y-4, 8, 8);
//g.fillRect(p3.x-4, p3.y-4, 8, 8);
//g.fillRect(p4.x-4, p4.y-4, 8, 8);
g.fillRect(p.x, p.y, 2, 2);
g.fillRect(p2.x, p2.y, 2, 2);
g.fillRect(p3.x, p3.y, 2, 2);
g.fillRect(p4.x, p4.y, 2, 2);
}
class loop extends TimerTask{
public void run() {
mainloop();
}
}
}
As you didn't help me vary much I was forced to figure this out by myself. Here we go:
The correct approach (or at least one of them) is to use affine transform to the points of the polygon you wish to rotate. The catch is that you cannot rotate the same polygon over and over again as it will severely deform due to the continuous rounding.
So the trick is to keep " an original version" of the polygon and always rotate that one.
Of course, this approach is only critical when rotating the polygon several times. If you want to only rotate it once you can simply use the values from the polygon you want to rotate.
Here's a little example I managed to put together:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class rotationPanel extends JPanel {
private static final long serialVersionUID = 117L;
private static final int SIZE = 500;
// point arrays which contain the points that are rotated around the center
Point[] points1;
Point[] points2;
Point[] points3;
// The center of rotation
Point center;
// the polygons being rotated
Polygon poly1;
Polygon poly2;
Polygon poly3;
// the angle of rotation
double angle;
Timer timer;
long start;
long sleepTime;
static int runTime;
public rotationPanel(){
setSize(500,500);
setBackground(Color.DARK_GRAY);
setVisible(true);
// time loop is set to run at fixed rate of 50 ms
runTime = 50;
start = 0;
sleepTime = 0;
timer = new Timer();
angle = 0;
// initializing the arrays (not neccesary)
points1 = getOriginalPoints(1);
points3 = getOriginalPoints(3);
points2 = getOriginalPoints(2);
// setting the rotation to the middle of the screen
center = new Point(250,250);
// start the looping
mainloop();
}
public void mainloop(){
start= System.currentTimeMillis();
// rotate the points the spcified angle and store the rotated
//points to the correct array
rotatePointMatrix(getOriginalPoints(1),angle,points1);
rotatePointMatrix(getOriginalPoints(2),angle,points2);
rotatePointMatrix(getOriginalPoints(3),angle,points3);
// Make the points into a polygon
poly1 = polygonize(points1);
poly2 = polygonize(points2);
poly3 = polygonize(points3);
// increase the angle by one degree, resulting to rotation in the longer run
angle++;
if (angle>=360){
angle=0;
}
// restatring the sequence
repaint();
sleepTime = runTime -(System.currentTimeMillis()-start);
System.out.println("Looped. Sleeping for:" +sleepTime+"ms");
if(sleepTime>0)
timer.schedule(new loop(), sleepTime);
else
mainloop();
}
public void rotatePointMatrix(Point[] origPoints, double angle, Point[] storeTo){
/* We ge the original points of the polygon we wish to rotate
* and rotate them with affine transform to the given angle.
* After the opeariont is complete the points are stored to the
* array given to the method.
*/
AffineTransform.getRotateInstance
(Math.toRadians(angle), center.x, center.y)
.transform(origPoints,0,storeTo,0,5);
}
public Polygon polygonize(Point[] polyPoints){
//a simple method that makes a new polygon out of the rotated points
Polygon tempPoly = new Polygon();
for(int i=0; i < polyPoints.length; i++){
tempPoly.addPoint(polyPoints[i].x, polyPoints[i].y);
}
return tempPoly;
}
public Point[] getOriginalPoints(int type){
/* In this example the rotated "polygon" are stored in this method.
* The Point is that if we want to rotate a polygon constatnly/frequently
* we cannot use the values of an already rotated polygon as this will
* lead to the polygon deforming severely after few translations due
* to the points being constantly rounded. So the trick is to save the
* original Points of the polygon and always rotate that one to the new
* angle instead of rotating the same one again and again.
*/
Point[] originalPoints = new Point[5];
if(type == 2){
originalPoints[0]= new Point(200, 100);
originalPoints[1]= new Point(250, 50);
originalPoints[2]= new Point(300, 100);
originalPoints[3]= new Point(300, 400);
originalPoints[4]= new Point(200, 400);
}
else if(type == 1){
originalPoints[0]= new Point(210, 150);
originalPoints[1]= new Point(250, 150);
originalPoints[2]= new Point(250, 190);
originalPoints[3]= new Point(230, 220);
originalPoints[4]= new Point(210, 190);
}
else{
originalPoints[0]= new Point(250, 300);
originalPoints[1]= new Point(290, 300);
originalPoints[2]= new Point(290, 340);
originalPoints[3]= new Point(270, 370);
originalPoints[4]= new Point(250, 340);
}
return originalPoints;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GRAY);
g2d.fillPolygon(poly2);
g2d.setColor(Color.yellow);
g2d.fillPolygon(poly1);
g2d.setColor(Color.yellow);
g2d.fillPolygon(poly3);
g2d.setColor(Color.WHITE);
for(int i=0; i < points1.length; i++){
g2d.fillRect(points1[i].x-1, points1[i].y-1, 3, 3);
g2d.fillRect(points3[i].x-1, points3[i].y-1, 3, 3);
}
g2d.setColor(Color.BLUE);
g2d.fillOval(center.x-4, center.y-4, 8, 8);
g2d.setColor(Color.yellow);
g2d.drawString("Angle: "+angle, 10,450);
}
class loop extends TimerTask{
public void run() {
mainloop();
}
}
public static void main(String[] args){
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new rotationPanel());
f.setSize(500,500);
f.setVisible(true);
}
}
I hope this helps! Don't hesitate to contact me if you run into trouble!
Here is a simple method to build a polygon from a set of points, rotated around a center point, at a specified angle:
/**
* Builds a polygon from a set of points, rotated around a point, at the
* specified rotation angle.
*
* #param centerX the int center x coordinate around which to rotate
* #param centerY the int center y coordinate around which to rotate
* #param xp the int[] of x points which make up our polygon points. This
* array is parallel to the yp array where each index in this array
* corresponds to the same index in the yp array.
* #param yp the int[] of y points which make up our polygon points. This
* array is parallel to the xp array where each index in this array
* corresponds to the same index in the xp array.
* #param rotationAngle the double angle in which to rotate the provided
* coordinates (specified in degrees).
* #return a Polygon of the provided coordinates rotated around the center point
* at the specified angle.
* #throws IllegalArgumentException when the provided x points array is not the
* same length as the provided y points array
*/
private Polygon buildPolygon(int centerX, int centerY, int[] xp, int[] yp, double rotationAngle) throws IllegalArgumentException {
// copy the arrays so that we dont manipulate the originals, that way we can
// reuse them if necessary
int[] xpoints = Arrays.copyOf(xp,xp.length);
int[] ypoints = Arrays.copyOf(yp,yp.length);
if(xpoints.length != ypoints.length){
throw new IllegalArgumentException("The provided x points are not the same length as the provided y points.");
}
// create a list of Point2D pairs
ArrayList<Point2D> list = new ArrayList();
for(int i = 0; i < ypoints.length; i++){
list.add(new Point2D.Double(xpoints[i], ypoints[i]));
}
// create an array which will hold the rotated points
Point2D[] rotatedPoints = new Point2D[list.size()];
// rotate the points
AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(rotationAngle), centerX, centerY);
transform.transform(list.toArray(new Point2D[0]), 0, rotatedPoints, 0, rotatedPoints.length);
// build the polygon from the rotated points and return it
int[] ixp = new int[list.size()];
int[] iyp = new int[list.size()];
for(int i = 0; i < ixp.length; i++){
ixp[i] = (int)rotatedPoints[i].getX();
iyp[i] = (int)rotatedPoints[i].getY();
}
return new Polygon(ixp, iyp, ixp.length);
}

Cutting part of an image out and keeping the original image

So I have a black image that acts as darkness (In my game). I want to show a small portion around the character only. Like so
The red square is the player.
The green bit is the ground in the game (grass).
The black is the shadow/darkness.
What I want to do is cut a Ellipse/hole out of the blank, black image. I want this Ellipse to be centered around the players (The red square) x and y position.
Currently I am using this to get the effect:
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
//draw mask
g.setColor(Color.black);
if(!nearPlayer(x, y)) {
g.drawLine(x, y, x, y);
}
}
}
But, this processes extremely slow and laggs the players movement drastically.
Is this possible?
..the Player is the red square. Basically what i want to do is cut a circle out of the blank, black image around the coordinates of the player relative to said black image.
What DYM by 'black image' - exactly? To me that just looks like the BG is painted black, which would make more sense for any solid color. In that case, just create the red thing using an Area, fill it, then for the border set a stroke & the color to black, and draw it. This example shows how.
The relevant part of that short code is..
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
I must be bored. This is an animated SSCCE version of the code that drew the image above. It is typically showing >130 FPS. And that is on a clunky machine for which I told the guy my spec. was 'cheap' & reminded him twice that I don't play (heavy rendering) games.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class DaisyDisplay {
DaisyDisplay() {
JPanel gui = new JPanel(new BorderLayout(2,2));
final BufferedImage daisy = new BufferedImage(
200,200,BufferedImage.TYPE_INT_RGB);
final JLabel daisyLabel = new JLabel(new ImageIcon(daisy));
gui.add(daisyLabel,BorderLayout.CENTER);
final Daisy daisyPainter = new Daisy();
daisyPainter.setSize(200);
final JLabel fps = new JLabel("FPS: ");
gui.add(fps,BorderLayout.SOUTH);
ActionListener animator = new ActionListener() {
int counter = 0;
long timeLast = 0;
long timeNow = 0;
public void actionPerformed(ActionEvent ae) {
Graphics2D g = daisy.createGraphics();
g.setColor(Color.GREEN.darker());
g.fillRect(0, 0, 200, 200);
daisyPainter.paint(g);
g.dispose();
daisyLabel.repaint();
counter++;
timeNow = System.currentTimeMillis();
if (timeLast<timeNow-1000) {
fps.setText("FPS: " + counter);
counter = 0;
timeLast = timeNow;
}
}
};
Timer timer = new Timer(1,animator);
timer.start();
JOptionPane.showMessageDialog(null, gui);
timer.stop();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DaisyDisplay();
}
});
}
}
class Daisy {
double size = 200;
Point location;
double offset = 0.0;
public void paint(Graphics2D g) {
Area daisyArea = getDaisyShape();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
offset += .02d;
AffineTransform plain = g.getTransform();
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*1/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*3/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset,
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(AffineTransform.getRotateInstance(
offset + (Math.PI*2/8),
100,100));
paintDaisyPart(g,daisyArea);
g.setTransform(plain);
}
public void setLocation(Point location) {
this.location = location;
}
public void paintDaisyPart(Graphics2D g, Area daisyArea) {
g.setClip(daisyArea);
g.setColor(Color.YELLOW);
g.fillRect(0, 0, 200, 200);
g.setColor(Color.YELLOW.darker());
g.setClip(null);
g.setStroke(new BasicStroke(3));
g.draw(daisyArea);
}
public void setSize(double size) {
this.size = size;
}
public Area getDaisyShape() {
int diameter = (int)size*6/20;
Ellipse2D.Double core = new Ellipse2D.Double(
(size-diameter)/2,(size-diameter)/2,diameter,diameter);
int pad = 10;
int petalWidth = 50;
int petalLength = 75;
Area area = new Area(core);
// left petal
area.add(new Area(new Ellipse2D.Double(
pad,(size-petalWidth)/2,petalLength,petalWidth)));
// right petal
area.add(new Area(new Ellipse2D.Double(
(size-petalLength-pad),(size-petalWidth)/2,petalLength,petalWidth)));
// top petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,pad,petalWidth,petalLength)));
// bottom petal
area.add(new Area(new Ellipse2D.Double(
(size-petalWidth)/2,(size-petalLength-pad),petalWidth,petalLength)));
return area;
}
}

Categories