Java2d: Translate the axes - java

I am developing an application using Java2d. The weird thing I noticed is, the origin is at the top left corner and positive x goes right and positive y increases down.
Is there a way to move the origin bottom left?
Thank you.

You are going to need to do a Scale and a translate.
in your paintComponent method you could do this:
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -height);
g2d.scale(1.0, -1.0);
//draw your component with the new coordinates
//you may want to reset the transforms at the end to prevent
//other controls from making incorrect assumptions
g2d.scale(1.0, -1.0);
g2d.translate(0, height);
}
my Swing is a little rusty but this should accomplish the task.

We can use the following way to resolve easily the problem,
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Flip the sign of the coordinate system
g2d.translate(0.0, getHeight());
g2d.scale(1.0, -1.0);
......
}

Have you tried Graphics2D.translate()?

You're going to want to just get used to it. Like luke mentioned, you technically CAN apply a transform to the graphics instance, but that will end up affecting performance negatively.
Just doing a translate could move the position of 0,0 to the bottom left, but movement along the positive axes will still be right in the x direction and down in the y direction, so the only thing you would accomplish is drawing everything offscreen. You'd need to do a rotate to accomplish what you're asking, which would add the overhead of radian calculations to the transform matrix of the graphics instance. That is not a good tradeoff.

Just for later reference, I had to swap the order of the calls to scale and translate in my code. Maybe this will help someone in the future:
#Test
public void bottomLeftOriginTest() throws IOException {
int width = 256;
int height = 512;
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics2D ig = bi.createGraphics();
// save the "old" transform
AffineTransform old = ig.getTransform();
// origin is top left:
// update graphics object with the inverted y-transform
if (true) { /* order ok */
ig.scale(1.0, -1.0);
ig.translate(0, -bi.getHeight());
} else {
ig.translate(0, -bi.getHeight());
ig.scale(1.0, -1.0);
}
int xPoints[] = new int[] { 0, width, width };
int yPoints[] = new int[] { 0, height, 0 };
int nPoints = xPoints.length;
ig.setColor(Color.BLUE);
ig.fillRect(0, 0, bi.getWidth(), bi.getHeight());
ig.setColor(Color.RED);
ig.fillPolygon(xPoints, yPoints, nPoints);
// restore the old transform
ig.setTransform(old);
// Export the result to a file
ImageIO.write(bi, "PNG", new File("origin.png"));
}

Related

Is there a way I can rotate one object and not both of them?

I am working on a project that has a GUI and tanks that move. While tanks move fine, I am not able to figure out how to move/rotate them individually. I also need to clean my code as I feel I have a lot of extra stuff going on.
Here is some code, and here's what I tried.
I have four classes. Missiles, Tanks, and Board. I am calling keylisteners in the Tank class. Should I do that in the doDrawing method? The doDrawing method is in the Board class.
private void doDrawing(Graphics g)
{
final double rads = Math.toRadians(120);
final double sin = Math.abs(Math.sin(rads));
final double cos = Math.abs(Math.cos(rads));
final int w = (int) Math.floor(tank1.getX() * cos + tank1.getX() * sin);
final int h = (int) Math.floor(tank1.getY() * cos + tank1.getY() * sin);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(w, h);
g2d.rotate(rot, tank1.getX(), tank1.getY());
AffineTransform backup = g2d.getTransform();
AffineTransform trans = new AffineTransform();
g2d.setTransform(backup);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
trans.setToIdentity();
trans.rotate(rot, h, w);
trans.translate(h, w);
trans.setTransform(backup);
g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
g2d.drawImage(tank2.getImage(), tank2.getX(), tank2.getY(), this);
List<Missile> missiles = tank1.getMissiles();
for (Missile missile : missiles)
{
//trans.rotate(Math.toRadians(rads), w/2, h/2);
g2d.drawImage(missile.getImage(), missile.getX(), missile.getY() - 7, this);
//g2d.rotate(rot, missile.getX(), missile.getY() - 7);
}
}
So, just looking at...
private void doDrawing(Graphics g)
{
//...
Graphics2D g2d = (Graphics2D) g;
g2d.translate(w, h);
g2d.rotate(rot, tank1.getX(), tank1.getY());
AffineTransform backup = g2d.getTransform();
AffineTransform trans = new AffineTransform();
g2d.setTransform(backup);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
trans.setToIdentity();
trans.rotate(rot, h, w);
trans.translate(h,w);
trans.setTransform(backup);
g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
g2d.drawImage(tank2.getImage(), tank2.getX(), tank2.getY(), this);
//...
}
It's immediately obvious that you're applying a transformation to the Graphics context which is never undone, so everything painted after it will have the same transformation applied to it.
The first thing you need to do is break the paint logic down so that it can paint a single entity.
The second thing you need to do is create a copy of the Graphics context before you make any modifications to it, this will prevent the parent copy from carrying those modifications forward which will then be compounded with future modifications.
Maybe something like...
private void doDrawing(Graphics g) {
// Because I don't trust anybody
Graphics2D g2d = (Graphics2D) g.create();
drawTank(tank1, g2d);
g2d.dispose();
g2d = (Graphics2D) g.create();
drawTank(tank2, g2d);
g2d.dispose();
}
private void drawTank(Tank tank, Graphics2D g) {
final double rads = Math.toRadians(120);
final double sin = Math.abs(Math.sin(rads));
final double cos = Math.abs(Math.cos(rads));
final int w = (int) Math.floor(tank.getX() * cos + tank.getX() * sin);
final int h = (int) Math.floor(tank.getY() * cos + tank.getY() * sin);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(w, h);
// `rot` should be derived from the tank ... no idea where this is
g2d.translate(tank.getX(), tank.getY());
// Origin has now been translated to the tanks position
g2d.rotate(rot, 0, 0);
g2d.drawImage(tank.getImage(), 0, 0, this);
// We no longer to need to keep these resources
g2d.dispose();
// Create a new "clean" copy
g2d = (Graphics2D) g.create();
// I don't know if it's needed, but I would, personally, calculate
// any required rotation again, but that's me
List<Missile> missiles = tank1.getMissiles();
for (Missile missile : missiles) {
g2d.drawImage(missile.getImage(), missile.getX(), missile.getY() - 7, this);
}
}
For example, see:
Java - rotate image in place
How to move paint graphics along slope?
Recommendations:
Keep logic separate from display using a model-view code structure
In keeping with that, create a Tank logical class, one that knows its own position and its own orientation (or angle), and save your java.util.List<Tank> in the program's model.
Change the Tank position and orientation within a listener.
Use the View classes (the GUI classes) only to a) draw the state of the class's model and b) get input from the user.
Side recommendation: If this is Swing, avoid KeyListeners and instead use Key Bindings
You can use separate affine transforms for each tank
If your tanks move/rotate synchronously I guess the reason is that they all receive the same key events and react the same way.
Put the keylistener (or even better KeyBindings as suggested in another answer) into your Board class. The listener will check which key is pressed/released and give the correct tank the command to move or turn or whatever. This way you can make one tank react to WASD, while the other reacts on up/down/left/right.
The methods in the tank class however do not react to key events but could be named like forward, backward, turnleft, turnright.
The doDrawing method should do exactly as it's name suggests: just perform the drawing. This can happen any amount of times to get good screen refresh rates or redraw the board after another window no longer hides your game.

Java 2d coordinate transformation

I am trying to plot a graph using the java 2d graphics library and I thought I had it. I want to plot in the coordinate system where 0,0 is in the center of the panel on the left edge. I used the following code and it seemed to give me the result I needed.
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform saveAT = g2d.getTransform();
// get the height of the panel
int height = getHeight();
// Find the middle of the panel
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.setTransform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
//restore the old transform
g2d.setTransform(saveAT);
}
This plotted the origin centered in the window.
The problem shows itself when I added a menu. Then the origin was offset in the y direction about twice the size of the menu higher then it should be. Do I need to account for the size of the menu and other containers that I add to the panel?
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
int height = getHeight();
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.transform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
}
works, thank you for your help
You might try the approach outlined here. Override paintComponent() to obtain a graphics context relative to the enclosing panel, rather than the enclosing frame.
To center the origin at the left edge, use
g2d.translate(0, h / 2);
To get upright, cartesian coordinates, use
g2d.scale(1, -1);

Rotating the Rectangle object in java

Is it possible to rotate the Rectangle object to a certain degree around it's axis? Is it as easy as Rectangle rect = new Rectangle(x,y,w,h,r)?
If it is not possible to rotate the object, what would be a way I could get similar results?
Edit: for clarity here is my dilemma, I have images that rotate but when they colide with other images the collisions only work at 90 and 180 degree rotations because their hit box Rectangle objects don't rotate.
Edit: for clarity here is my dilemma, I have images that rotate but when they colide with other images the collisions only work at 90 and 180 degree rotations because their hit box Rectangle objects don't rotate.
You can rotate a Shape-derived object, such as a Rectangle2D by using the AffineTransform method, createTransformedShape(...).
Rectangle2D myRect = new Rectangle2D.Double(100, 100, 200, 200);
AffineTransform at = AffineTransform.getRotateInstance(Math.PI / 4, 150, 150);
Shape rotatedRect = at.createTransformedShape(myRect);
Note: code not compiled nor tested.
To rotate the rectangle, you give the graphics context an AffineTransform for rotation. Here's an example:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform oldTransform = g2d.getTransform();
g2d.setTransform(AffineTransform.getRotateInstance(radians);
g2d.draw(rectangle);
g2d.setTransform(oldTransform);
}
You can also use g2d.rotate(radians) for rotation.
Note that the angle must be in radians. To convert degrees into radians, use degrees * (Math.PI/180) for the angle.
There's also another way to do this (besides createTransformedShape) which creates fewer temporary objects, if that's desirable.
Instead of keeping a Rectangle2D for the bounding box, you can keep a Path2D and then do the transform in place, using Path2D.transform(AffineTransform):
import java.awt.geom.*;
public class Example {
private final Path2D hitBox;
public Example(Rectangle2D initialHitBox) {
this.hitBox = new Path2D.Double(initialHitBox);
}
public void transform(AffineTransform tx) {
path.transform(tx); // In-place transformation.
}
// ...
}
This is very similar to what AffineTransform actually does under the hood.

Java rotate issue

When I try and apply a rotation to the current g2d object, it doesn't rotate it, it renders it in the same place (in my context on top of the other). From what I understand of the rotate method, it applies a transformation to the current graphics context, transforming the pixels of any rendering that comes after it (this might be where I'm going wrong). Here's the code in question:
#Override
public void paint(final Graphics graphics) {
super.paint(graphics);
final Graphics2D g2d = (Graphics2D) graphics;
....
....
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(SMALL_LINE_THICKNESS));
if (isLattice1Drawn) {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));
// lattice1 and lattice2 are Polygon objects
g2d.draw(lattice1);
// This fades in the second Polygon over the first
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
// This line should rotate it, but doesn't
g2d.rotate(Math.toRadians(210));
g2d.draw(lattice2);
.....
Thanks, Mike
Edit 1
As a suggestion from Jeff, I tried having just the rotation and drawing in paint, leaving me with the following code:
#Override
public void paint(final Graphics graphics) {
super.paint(graphics);
final Graphics2D g2d = (Graphics2D) graphics;
g2d.rotate(Math.toRadians(210));
g2d.draw(lattice2);
return;
// Rest of paint .................
Unfortunately this did not help, any other suggestions would be most welcome.
Edit 2:
When I don't call rotate, the polygon is rendered, however when I do nothing happens. Can anyone explain this?
What I understand from Edit 2 is: the rotation actually works. However, since rotation is around origin the rotated coordinates of the polygon ends up outside of the visible area. You can test this by rotating smaller degrees.
Then, if the desired operation is to rotate a polygon around its center of mass, use the following Graphics2D method instead:
void rotate(double theta, double x, double y)

Drawing a dot grid

I'm new to graphics programming. I'm trying to create a program that allows you to draw directed graphs. For a start I have managed to draw a set of rectangles (representing the nodes) and have made pan and zoom capabilities by overriding the paint method in Java.
This all seems to work reasonably well while there aren't too many nodes. My problem is when it comes to trying to draw a dot grid. I used a simple bit of test code at first that overlayed a dot grid using two nested for loops:
int iPanX = (int) panX;
int iPanY = (int) panY;
int a = this.figure.getWidth() - iPanX;
int b = this.figure.getHeight() - (int) iPanY;
for (int i = -iPanX; i < a; i += 10) {
for (int j = -iPanY; j < b; j += 10) {
g.drawLine(i, j, i, j);
}
}
This allows me to pan the grid but not zoom. However, the performance when panning is terrible! I've done a lot of searching but I feel that I must be missing something obvious because I can't find anything on the subject.
Any help or pointers would be greatly appreciated.
--Stephen
Use a BufferedImage for the dot grid. Initialize it once and later only paint the image instead of drawing the grid over and over.
private init(){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// then draw your grid into g
}
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
// then draw the graphs
}
And zooming is easily achieved using this:
g.drawImage(image, 0, 0, null); // so you paint the grid at a 1:1 resolution
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
// then draw the rest into g2 instead of g
Drawing into the zoomed Graphics will lead to proportionally larger line width, etc.
I think re-drawing all your dots every time the mouse moves is going to give you performance problems. Perhaps you should look into taking a snapshot of the view as a bitmap and panning that around, redrawing the view 'properly' when the user releases the mouse button?

Categories

Resources