I am coding a simple shooter game whereby i want to rotate my character to the direction of the mouse and "fire". I have done all the code for this except rotating the actual image. Here is some of my code so far: (THIS IS ALL UNDER THE PAINT COMPONENT METHOD)
xCent = x + 50;
yCent = y + 50; // x and y center of image ( x and y change depending on Keyboard Input)
a.setToRotation(theta, xCent,yCent); // a = new AffineTransform() Here is my calculation of theta (under the MouseMotionListener): theta = Math.atan2(e.getY() - yCent,e.getX() - xCent);
a.setToTranslation(x,y);
a.setToRotation(theta, xCent,yCent);
g2.drawImage(charac,a, null);
How do i go about now "setting" the x and y coordinates of the image for the Graphics2D object to draw it??
Have a look at this:
http://www.javalobby.org/java/forums/t19387.html
public void paint(Graphics g) {`
AffineTransform transformer = new AffineTransform();
transformer.translate(5,5);
transformer.scale(2,2);
Graphics2D g2d = (Graphics2D)g;
g2d.setTransform(transformer);
// draw to g2d.
}
In your case off course you would do the rotation and then translation rather than translate and scale.
Related
I need to place an image onto a canvas with the corners at specific co-ordinates.
// Blank canvas
BufferedImage img = new BufferedImage(2338, 1654, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, width, EXTRA_HEADER_HEIGHT);
I have all 4 corner co-ordinates that the image corners must be placed at on the background canvas. The problem is that the original image might need to be rotated. This is basically what I need to achieve:
I don't have much experience with Graphics2D but based on a quick review of the API I can't see a method to achieve this. I am hoping that I am wrong here and that somebody can save me some time but my current thinking is:
Use the co-ordinates to calculate the rotation of the placed image relative to the supplied image.
Place the image with one of its corners in the correct position.
Rotate the image around that corner (without rotating background canvas).
Any help with the above would be appreciated.
As tucuxi commented, if you really have 4 points and want the transform to place the image corners at these exact points, and affine transform won't do -- you'll need a perspective transform.
However, if you select two points of the four, you can do what you want, but you may have to scale the image. So let's say you just want to place a rotated and scaled version of your image such that its top edge goes from A' to B'. What you'll have to do is compute the affine transform, which involves determining the rotation angle, scaling factor, and translation from the segment AB to A'B'.
Here's a commented method that should do just that. I have not thoroughly tested it, but it shows how to implement the algorithm in Java.
package stackoverflow;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class ComputeImageTransform
{
public static AffineTransform computeTransform(
Rectangle2D imageBounds, Point2D a2, Point2D b2) {
double dx = b2.getX() - a2.getX();
double dy = b2.getY() - a2.getY();
// compute length of segment
double length = Math.hypot(dx, dy);
// compute scaling factor from image width to segment length
double scaling = length / imageBounds.getWidth();
// compute rotation angle
double rotation = Math.atan2(dy, dx);
// build the corresponding transform
// NOTE: the order of the individual transformations are applied is the
// reverse of the order in which the transform will apply them!
AffineTransform transform = new AffineTransform();
transform.translate(a2.getX(), a2.getY());
transform.rotate(rotation);
transform.scale(scaling, scaling);
transform.translate(-imageBounds.getX(), -imageBounds.getY());
return transform;
}
public static void main(String[] args) {
// transform top edge of image within this axis-aligned rectangle...
double imageX = 20;
double imageY = 30;
double imageWidth = 400;
double imageHeight = 300;
Rectangle2D imageBounds = new Rectangle2D.Double(
imageX, imageY, imageWidth, imageHeight);
// to the line segment a2-b2:
Point2D a2 = new Point2D.Double(100, 30);
Point2D b2 = new Point2D.Double(120, 200);
System.out.println("Transform image bounds " + imageBounds);
System.out.println(" to top edge " + a2 + ", " + b2 + ":");
AffineTransform transform = computeTransform(imageBounds, a2, b2);
// test
Point2D corner = new Point2D.Double();
corner.setLocation(imageX, imageY);
System.out.println("top left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY);
System.out.println("top right: " + transform.transform(corner, null));
corner.setLocation(imageX, imageY + imageHeight);
System.out.println("bottom left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY + imageHeight);
System.out.println("bottom right: " + transform.transform(corner, null));
}
}
This is the output:
Transform image bounds java.awt.geom.Rectangle2D$Double[x=20.0,y=30.0,w=400.0,h=300.0]
to top edge Point2D.Double[100.0, 30.0], Point2D.Double[120.0, 200.0]:
top left: Point2D.Double[100.0, 30.0]
top right: Point2D.Double[119.99999999999999, 199.99999999999997]
bottom left: Point2D.Double[-27.49999999999997, 44.999999999999986]
bottom right: Point2D.Double[-7.499999999999986, 214.99999999999997]
As you can see, you'll get some rounding errors due to the nature of floating-point computations.
I'm drawing some text over an image using LineBreakMeasurer in conjunction with TextLayout but for some reason the stroke is only stroking the inside, and it's not very clean.
Here's an example of what I'm talking about:
http://i.imgur.com/eHtTw4p.png
And when I don't draw the letter over top and increase the stroke width, it actually will get thicker on the inside and not outside.
Here's my code:
float y = 0.0f;
float wrappingWidth = img.getWidth() * 0.8f;
LineBreakMeasurer measurer = new LineBreakMeasurer(str.getIterator(), imageGraphics.getFontRenderContext());
while (measurer.getPosition() < sentence.length()) {
TextLayout layout = measurer.nextLayout(wrappingWidth);
y += layout.getAscent();
float x = ((wrappingWidth * 0.8f) - layout.getVisibleAdvance()) / 2f + (wrappingWidth * 0.2f);
AffineTransform transform = new AffineTransform();
transform.translate((double)x, (double)y);
Shape outline = layout.getOutline(transform);
imageGraphics.setColor(Color.black);
imageGraphics.setClip(outline);
imageGraphics.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
imageGraphics.draw(outline);
imageGraphics.setColor(Color.white);
imageGraphics.setStroke(new BasicStroke());
layout.draw(imageGraphics, x, y);
y += layout.getDescent() + layout.getLeading();
}
I'm not sure what I'm doing wrong. Does anyone know?
Create another copy of the Graphics context before you draw the outline...
Graphics2D sg = (Graphics2D)imageGraphics.create();
sg.setColor(Color.black);
sg.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
sg.draw(outline);
sg.dispose();
I'd also get rid of the clip...
Instead of "stroking" the resulting shape, I'd be tempted to "fill" the background color and "draw" the outline color ontop of it, for example...
Graphics2D sg = (Graphics2D) g2d.create();
AffineTransform transform = new AffineTransform();
transform.translate((double) drawPosX, (double) drawPosY);
Shape outline = layout.getOutline(transform);
sg.setColor(Color.WHITE);
sg.fill(outline);
sg.setColor(Color.BLACK);
sg.draw(outline);
sg.dispose();
But if you want a "nice" thick stroke, use BasicStroke.JOIN_ROUND instead of BasicStroke.JOIN_MITER
Graphics2D sg = (Graphics2D) g2d.create();
AffineTransform transform = new AffineTransform();
transform.translate((double) drawPosX, (double) drawPosY);
Shape outline = layout.getOutline(transform);
sg.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
sg.setColor(Color.BLACK);
sg.draw(outline);
sg.dispose();
I've been researching for the past hour or so and I can't seem to render an isometric map. I want to achieve something like this.
But I am getting this.... I am storing my map as tiles in a 1 dimensional array like so:
private final int width, height;
private final int tileWidth, length;
private int[] tiles;
public Level(int width, int height) {
this.width = width;
this.height = height;
tiles = new int[width * height];
tileWidth = 68;
length = 48;
}
I am passing through 10, 10 as the parameters for width and height. And I render the map like so:
public void render(Graphics g) {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g.setColor(Color.red);
if (x % 2 == 0)
g.drawRect(x * tileWidth, y * length / 2, tileWidth, length);
else
g.fillRect((x * tileWidth) + (tileWidth / 2), (y * length / 2), width, length);
}
}
}
Any help would be really appreciated, I've wanted to learn to make isometric games but have been stuck with flat 2D for a while.
For just tiles, you could use a shear transform:
Graphics2D g2d = (Graphics2D) g;
AffineTransform at = AffineTransform.getShearInstance(1, 0);
g2d.transform(at);
// rest of your drawing code here
You may also want to set the shear anchor point:
double sa_x = 100, sa_y = 100; // or whatever
AffineTransform at = new AffineTransform();
// S3: Move back to original origin
at.translate(sa_x, sa_y);
// S2: Shear
at.shear(1, 0);
// S1: Set origin
at.translate(-sa_x, -sa_y);
You can vary the shear factor 1 to get different amounts of shear.
Instead of drawing rects, you need to draw lines at isometric angles.
The angles in isometric geometry are 30 degrees, 90 degrees, 150 degrees, 210 degrees and 270 degrees (in radians: pi/6, pi/2, 5pi/6, 7pi/6, 3pi/2, 11pi/6.).
cos(pi/6) is sqrt(3)/2 or 0.866... and sin(pi/6) is 1/2 or 0.5. (This is meaningful because of http://en.wikipedia.org/wiki/File:Sin-cos-defn-I.png )
This means that if you want to draw a line at the angle pi/6 that is D pixels long starting at x1,y1:
x2 = x1+cos(pi/6)*D e.g. x1+D*sqrt(3)/2
y2 = y1+sin(pi/6)*D e.g. y1+D/2
and draw from x1,y1 to x2,y2.
All the other angles are either reflections of this (one dimension or both are made negative) or straight up and down (trivial to draw).
To calculate where on the screen to draw an isometric object, consider that isometric geometry has three dimensions: X, Y, Z. Movement by Z will just make you draw D higher or D lower. Movement by X or Y will move you in one isometric angled direction or the other, by the same x and y as the distance of drawing one tile line in that direction (so similar formula to the above).
I am trying to draw a rectangle in JPanel that would translate and then rotate itself to mimic the movement of a car. I have been able to make the rectangle translate and rotate, however it rotates around the origin of (0,0). I'm very pleased that I was able to have the rectangle move and rotate as I am very new to Java GUI, but I can not seem to get how to have the rectangle rotate around itself, because I experimented more with it, and when I initialized the rectangle and rotate it 45 degrees it's position was changed, which I would assume is the transform matrix that is appended from the rotate method.
I checked through the site on how would I solve this, however I only found how to rotate a rectangle and not on how to rotate and move like the movement of a simulated car. I would presume it is concerning about its transform matrix, but I'm only speculating. So my question is how would I be able to have the rectangle be able to rotate and move around itself and not against a point in JPanel.
Here's the code that I have come up so far:
public class Draw extends JPanel implements ActionListener {
private int x = 100;
private int y = 100;
private double theta = Math.PI;
Rectangle rec = new Rectangle(x,y,25,25);
Timer timer = new Timer(25,this);
Draw(){
setBackground(Color.black);
timer.start();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.white);
rec.x = 100;
rec.y = 100;
g2d.rotate(theta);
g2d.draw(rec);
g2d.fill(rec);
}
public void actionPerformed(ActionEvent e) {
x = (int) (x + (Math.cos(theta))*1);
y = (int) (y + (Math.sin(theta))*1);
theta = theta - (5*Math.PI/180);
repaint();
}
One of two approaches are commonly used:
Rotate the graphics context around the center (x, y) of the Shape, as shown here.
rotate(double theta, double x, double y)
Translate to the origin, rotate and translate back, as shown here.
g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
g2d.rotate(theta);
g2d.translate(-image.getWidth(null) / 2, -image.getHeight(null) / 2);
Note the apparent reverse order of concatenation in the second example.
Addendum: Looking more closely at your example, the following change rotates the Rectangle around the panel's center.
g2d.rotate(theta, getWidth() / 2, getHeight() / 2);
Also, use the #Override annotation, and give your panel a reasonable preferred size:
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
Use affine transform to rotate the rectangle and convert it into the rotated polynomial. Check the code below:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.white);
/* rotate rectnagle around rec.x and rec.y */
AffineTransform at = AffineTransform.getRotateInstance(theta,
rec.x, rec.y);
/* create the plunomial */
Polygon p = new Polygon();
/* path interator of the affine transformed polynomial */
PathIterator i = rec.getPathIterator(at);
while (!i.isDone()) {
double[] points = new double[2];
i.currentSegment(points);
p.addPoint((int) points[0], (int) points[1]);
i.next();
}
g2d.fill(p);
}
I need to create rectangles that are rotated around their center (so they don't need to be parallel to the axes of the coordinate system). So basicelly each rectangle can be defined by center-X, center-Y, width, height and angle. What I want to do then is to perform calculations on whether certain points are contained in these rectangles or not (so no drawing will be involved). I guess I cant use the Rectangle2D class because these rectangles will always be parallel to the x and y-axis of the coordinate system. Is the only way to get this functionality by writing my own rectangle class or is there anything existing (similar to Rectangle2D) I can use?
Rotate all the points you want to test and use contains(Point) method of the Rectangle2D as Mihai did.
But if you really want to rotate the rectangles you can do it like this (this is the integer version but probably you can do it with Rectangle2D aswell :)).
public class TestRotate {
public static void main(String... args) {
Rectangle r = new Rectangle(50, 50, 100, 100);
Point check = new Point(100, 151); // clearly outside
System.out.println("first: " + r.contains(check));
AffineTransform at = AffineTransform.getRotateInstance(
Math.PI/4, r.getCenterX(), r.getCenterY());
Polygon p = new Polygon();
PathIterator i = r.getPathIterator(at);
while (!i.isDone()) {
double[] xy = new double[2];
i.currentSegment(xy);
p.addPoint((int) xy[0], (int) xy[1]);
System.out.println(Arrays.toString(xy));
i.next();
}
// should now be inside :)
System.out.println("second: " + p.contains(check));
}
}
You can use Rectangle2D to check for containment, if instead of rotating your rectangle by an angle, say, counterclockwise, you rotate each of the points you need to check by the same angle clockwise, relative to the center of the rectangle. Something like
double dx = point.x - rectangleCenter.x;
double dy = point.y - rectangleCenter.y;
double newX = rectangleCenter.x - dx*Math.cos(angle) + dy*Math.sin(angle);
double newY = rectangleCenter.x - dx*Math.sin(angle) - dy*Math.cos(angle);