I am trying to draw a rectangle with java awt and make it rotate with mouse cursor by mouse dragging.
When I was testing it out, the rectangle was rotating ridiculously fast.
My Rectangle():
private Rectangle2D rec = new Rectangle2D.Float(x0,y0,w,h);
AffineTransform recTrans = new AffineTransform();
int pivotX = x0+w/2, pivotY = y0+h;
// (0,0) is at the top-left corner
My paintComponent():
public void paintComponent(Graphics g) {
Graphics2D graph = (Graphics2D) g;
graph.translate(x,y);
graph.transform(recTrans);
graph.fill(rec);
graph.setColor(Color.blue);
graph.draw(rec);
}
My mouse dragging event:
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
previousX = e.getX();
previousY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
currentX = e.getX();
currentY = e.getY();
double angle1 = Math.atan2(currentY-pivotY, currentX-pivotX);
double angle2 = Math.atan2(previousY-pivotY, previousX-pivotX);
double theta = angle2 - angle1;
recTrans.rotate(theta, pivotX, pivotY);
}
});
So supposedly the scenario looks like:
But when I slightly drag(theta less than 10 degree) the rectangle to the right side, the rectangle rotates even to the bottom of the pivot point.
Another note, the rectangle rotates but the coordinates of the four corners of the rectangle yet have changed.
I am quite lost when doing those transformation tasks with java..
I think that the problem is that you are accumulating the rotation for each mouse motion event you get: when you move the mouse 10 degrees you don't get just one event at the end of the movement, you get a bunch of events all along the way. And it seems that you are accumulating the rotation of all of them in the same transformation, so when you get to 10 degrees, you have actually 1 + 2 + 3 + 4... you get the idea.
The solution is easy: reset the transformation before applying the rotation:
recTrans.setToIdentity();
recTrans.rotate(theta, pivotX, pivotY);
Or equivalently but nicer, use this function that does not accumulate the transformation.
recTrans.setToRotation(theta, pivotX, pivotY);
recTrans.rotate(Math.toRadians(theta), pivotX, pivotY);
This will help.
Related
I am making a game in Java (No Libraries).
It's a 2D top-down game where the player can walk and is faced towards the mouse cursor.
public Player(int x, int y, int health, int tileId) {
super(x, y, health);
tile = new Tile(tileId, false);
mouseInput = new MouseHandler(screen);
}
public void tick() { // Executed by game tick.
// x = playerX and y = playerY
int cursorX = mouseInput.getMousePos()[0];
int cursorY = mouseInput.getMousePos()[1];
float X = cursorX - x;
float Y = cursorY - y;
rotation = Math.atan2(Y, X);
}
It looks good as long the player is at (0,0)
If the player moves and the mouse coordinates become negative it begins to show strange behaviour (Look at video below)
Youtube: https://www.youtube.com/watch?v=M6ZHCrWvt3Y
The rotation of the sprite is done in another class 'Screen.java'
By using:
if (rotation < 360)
rotation++
else
rotation = 0
I verified that the rotation is working correctly.
EDIT:
public BufferedImage rotate(BufferedImage img, double degree) {
AffineTransform tx = new AffineTransform();
tx.rotate(degree, 4, 4);
AffineTransformOp op = new AffineTransformOp(tx,AffineTransformOp.TYPE_BILINEAR);
BufferedImage image = op.filter(img,null);
return image;
}
Okay i fixed it.
The problem was the game scale i am making an 2d game and set the width, height and the scale.
But i didn't divide the mouseX and mouseY by the scale.
public void mouseMoved(MouseEvent e) {
mouseX = e.getX() / game.getScale();
mouseY = e.getY() / game.getScale();
}
I found the problem by accident when messing with the gamescale.
I have problem, that balls that move too fast can fly through wall (walls are 4 pixels wide, and speed of ball occasionally is more than 400 pixels per second (which is more than 4 pixels per update assuming fps is 60)). I researched it on StackOverflow, but the solution for others is not suitable for me, as they are using rectangles, and i am using pixel collision. Here is method which returns if ball intersects with wall (method is in Ball class):
public boolean intersects(Wall w) {
BufferedImage im1 = new BufferedImage (size, size, BufferedImage.TYPE_INT_ARGB); // size is diameter of the ball
BufferedImage im2 = new BufferedImage (size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g1 = im1.createGraphics();
Graphics2D g2 = im2.createGraphics();
g1.translate(-x + size/2, -y + size/2);
g2.translate(-x + size/2, -y + size/2);
render(g1);
w.render(g2);
g1.dispose();
g2.dispose();
for (int x = 0; x < im1.getWidth(); x++){
for (int y = 0; y < im1.getHeight(); y++){
Color c1 = new Color(im1.getRGB(x, y), true);
Color c2 = new Color(im2.getRGB(x, y), true);
if (c1.getAlpha() != 0 && c2.getAlpha() != 0){
return true;
}
}
}
return false;
}
Here is how ball is drawn:
public void render(Graphics2D g) {
color = new Color (Color.HSBtoRGB(hue, 0.5f, 0.5f));
g.setColor (color);
g.fillOval((int)(x-size/2), (int) (y-size/2), size, size);
}
The wall is simply defined as 2 points, and here is how wall is drawn:
public void render(Graphics2D g2) {
g2.setColor(new Color(r, g, b));
g2.setStroke(new BasicStroke(width)); //width = 4
g2.draw(new Line2D.Float(p1.x, p1.y, p2.x, p2.y));
}
I have a couple of quick ideas that you might want to attempt.
Why not check the endpoints against the dimensions of the wall (some simple algebra and I think what you might already be trying to do). If you need help with the algebra involved I'll include a link below. Basically just record the starting point of the ball (prior to moving), and on update check the old location versus the estimated location, and run a function to see if a collision occurs. This will become tricky however if you want realistic physics.
https://gamedev.stackexchange.com/questions/26004/how-to-detect-2d-line-on-line-collision
In the above link, just assume your walls are one line, and the previous coordinates and the expected next coordinates of the ball form the other line. This method works very well if you only have the ball moving in straight lines.
Your only alternative might be to have two simultaneous models running (basically a visual space which you have in showing the ball and walls, and a virtual setup checking the physics behind what you are showing.
I'm trying to draw a circle on my canvas. Pseudocode of my algorithm looks like that
double R = 1.0;
// Draw 11 points, lying on the circle with the radius of 1
// and angle from 0 to 90 degrees
for(int i=0; i<=10; i++)
{
drawPoint( R*cos(PI*i/20), R*sin(PI*i/20) );
}
// Draw a circle with center at the (0; 0) and with the radius of 1
drawCircle(0, 0, R);
That's what I've got:
Looks fine, but there is one problem. When I increase radius only points with angles 0, 45 and 90 lie on a circle.
That's how it looks 72 degrees:
There is no any info about accuracy of the method drawCircle on developer.android.com.
I guess that it draws, based on the values at points with angles 0, 45, 90, ..., and calculate line in other positions very approximately.
I know, that I can draw circle as accurate as I want to, if I'll draw it like a polyline with tiny step, but it will work very slow.
So I want to find out - is there any methods to draw circle accurate in Android?
UPD 1:
How do I draw a points:
int x, y;
x = getPixelX(point.getX());
y = getPixelY(point.getY());
canvas.drawCircle(x, y, point.radius, paint);
Where getPixelX and getPixelY takes a coorditate of the point on plane and returns the same coordinate on the screen, basing on scale and offset.
I thought that I could make some mistake in those methods, but they work perfectly with the lines. I can zoom in lines and there is no error, all the points lies just on the line.
UPD 2:
There are comments, that probably I make a mistake in my calculations. I do not argue, perhaps you're right. So here is my "calculations".
How do I zoom in:
public void mouseWheelMoved(MouseWheelEvent e) {
// zoomQ is 0.9 or 1.1
double zoomQ = (e.getWheelRotation() + 10) / 10.0;
scaleX *= zoomQ;
scaleY *= zoomQ;
}
How do I move the plane:
public void mouseDragged(MouseEvent e) {
centerX -= (e.getX() - lastMouseX)/scaleX;
centerY -= (e.getY() - lastMouseY)/scaleY;
lastMouseX = e.getX();
lastMouseY = e.getY();
}
How do getPixelX/Y works:
public int getPixelX(double planeX) {
return (int)Math.round( scaleX*(planeX - centerX) + ScreenWidth/2 );
}
public int getPixelY(double planeY) {
return (int)Math.round( scaleY*(planeY - centerY) + ScreenHeight/2 );
}
There is something I am missing inhere so I hope you can share some light on me.
I am drawing some text inside canvas. For this I have a class Word
public class Word {
private int x;
private int y;
private String text;
}
The app allows the user to rotate the text, and I handle the rotation withing onDraw
protected void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(angle, centerX, centerY)
...
canvas.drawText(word.getText(), word.getX(), word.getY())
....
canvas.restore();
}
The problem I get is when the user drags the canvas and there is a rotation set. When angle=0 the movement is going as expected.
#Override
public boolean onTouchEvent(MotionEvent event) {
case MotionEvent.ACTION_DOWN:
initialX = (int) event.getX();
initialY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int currentX = (int) event.getX();
int currentY = (int) event.getY();
int xMovement = currentX - initialX;
int yMovement = currentY - initialY;
dragWords(xMovement, yMovement);
.....
and on dragWords for each word I do:
private void dragText(int xMovement, int yMovement){
for (Word word : words) {
word.setX(word.getX() + xMovement);
word.setY(word.getY() + yMovement);
}
invalidate();
}
When rotation angle is 0, moving up/down/left/right makes the words move by the same distance. As angle gets bigger, the words start to move in different dirrections, for instance at 60, it is starting to go diagonally up, when 180 it only moves up/down and not left/right.
I think I need to calculate some sort of a difference based on angle and add it to xMovement/yMovement... but how should I do this ?
LE: Here is an image on how it behaves:
The blue lines is how the text is moving on drag while the orange is the finger dragging on the screen. When angle is 0 it works quite well, when angle increases, it starts to move diagonally on left/right, while when angle is even bigger, it only moves up and down and does not respond to left/right
If I understand correctly, the issue is that Canvas.rotate() does not only rotate the text direction, but rather the whole canvas. Therefore, the x-y coordinates of the words are also rotated from the specified pivot point.
In order to match the dragging movement, you can use a Matrix for this, more specifically the inverse matrix of the one you're using to rotate the canvas. It will be used to convert the x-y coordinates of the words to their original, pre-rotate locations.
For example, calculate this once, and update it whenever angle, centerX, or centerY changes.
// rotMatrix is the same operation applied on the canvas.
Matrix rotMatrix = new Matrix();
rotMatrix.postRotate(mAngle, centerX, centerY);
// Invert it to convert x, y to their transformed positions.
Matrix matrix = new Matrix();
rotMatrix.invert(matrix);
Then, when drawing each word:
int wordX = ...
int wordY = ...
String text = ...
float[] coords = new float[] { wordX, wordY };
matrix.mapPoints(coords);
canvas.drawText(text, coords[0], coords[1], paint);
In the ellipses part in the following code:
dragWords(xMovement, yMovement);
..... <<<--------------------- I hope you are updating initialX and initialY
initialX = currentX;
initialY = currentY;
Otherwise, your x and y values will not correspond correctly with the amount of distance moved during the touch gesture.
As user matiash indicated, you should use Matrix#mapPoints(float[]) to transform your x and y values. Declare and initialize a Matrix:
Matrix correctionMatrix;
// Your view's constructor
public MyView() {
....
correctionMatrix = new Matrix();
}
Here's how your onDraw(Canvas) should look like:
#Override
protected void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(angle, centerX, centerY);
...
// Neutralize the rotation
correctionMatrix.setRotate(-angle, centerX, centerY);
// Initialize a float array that holds the original coordinates
float[] src = {word.getX(), word.getY()};
// Load transformed values into `src` array
correctionMatrix.mapPoints(src);
// `src[0]` and `src[1]` hold the transformed `X` and `Y` coordinates
canvas.drawText(word.text, src[0], src[1], somePaint);
....
canvas.restore();
}
This should give you the desired results - movement in the X and Y axis irrespective of canvas rotation.
You can obviously move the call to setRotate(float, float, float) to a better place. You only need to call it once after changing the angle value.
I am writing a 2D program. On my paintComponent I created an arc.
public class Board extends Panel{
protected void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D graphics2d = (Graphics2D)g;
int x = MouseInfo.getPointerInfo().getLocation().x;//set mouses current position
int y = MouseInfo.getPointerInfo().getLocation().y;
graphics2d.setStroke(wideStroke);
graphics2d.draw(new Arc2D.Double(200, 200, 100, 100, ?, 180, Arc2D.OPEN));
}
}
In my main I am using a Thread to update the graph. The position of the ? is the starting angle. Every time I change this the arc will move in a circle like half a car wheel. Is it possible to get the arc movement to follow the mouse? e.g. ? = 270
How will I do this? (Sorry for my bad paint skills!)
So based on the information from Java 2d rotation in direction mouse point
We need two things. We need the anchor point (which would be the centre point of the arc) and the target point, which would be the mouse point.
Using a MouseMotionListener, its possible to monitor the mouse movements within the component
// Reference to the last known position of the mouse...
private Point mousePoint;
//....
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
});
Next we need to calculate the angle between these two points...
if (mousePoint != null) {
// This represents the anchor point, in this case,
// the centre of the component...
int x = width / 2;
int y = height / 2;
// This is the difference between the anchor point
// and the mouse. Its important that this is done
// within the local coordinate space of the component,
// this means either the MouseMotionListener needs to
// be registered to the component itself (preferably)
// or the mouse coordinates need to be converted into
// local coordinate space
int deltaX = mousePoint.x - x;
int deltaY = mousePoint.y - y;
// Calculate the angle...
// This is our "0" or start angle..
rotation = -Math.atan2(deltaX, deltaY);
rotation = Math.toDegrees(rotation) + 180;
}
From here, you would need to subtract 90 degrees, which would give your arcs start angle and then use an extent of 180 degrees.