I'm attempting to play with graphics using Java/Slick 2D. I'm trying to get my sprite to rotate to wherever the mouse is on the screen and then move accordingly.
I figured the best way to do this was to keep track of the angle the sprite is at since I have to multiply the cosine/sine of the angle by the move speed in order to get the sprite to go "forwards" even if it is, for instance facing 45 degrees in quadrant 3.
However, before I even worry about that, I'm having trouble even getting my sprite to rotate in the first place.
Preliminary console tests showed that this code worked, but when applied to the sprite, it just kind twitches. Anyone know what's wrong?
int mX = Mouse.getX();
int mY = HEIGHT - Mouse.getY();
int pX = sprite.x;
int pY = sprite.y;
int tempY, tempX;
double mAng, pAng = sprite.angle;
double angRotate = 0;
if (mX != pX) {
tempY = pY - mY;
tempX = mX - pX;
mAng = Math.toDegrees(Math.atan2(Math.abs((tempY)), Math.abs((tempX))));
if (mAng == 0 && mX <= pX)
mAng = 180;
}
else {
if (mY > pY)
mAng = 270;
else
mAng = 90;
}
// Calculations
if (mX < pX && mY < pY) { // If in Q2
mAng = 180 - mAng;
}
if (mX < pX && mY > pY) { // If in Q3
mAng = 180 + mAng;
}
if (mX > pX && mY > pY) { // If in Q4
mAng = 360 - mAng;
}
angRotate = mAng - pAng;
sprite.angle = mAng;
sprite.image.setRotation((float) angRotate);
Firstly, atan2 can get the correct angle for you - just remove Math.abs from the inputs, and you won't need your three four if statements that you use to correct the quadrants of the angle. (Though you have to get the subtractions the right way around)
Secondly, you're setting the sprite's rotation to mAng - pAng, which amounts to "old angle - new angle". So in reality you're setting the rotation to how much the angle changed since last time (which makes no sense for this purpose), and not the angle itself.
Combining these suggestions I'd recommend something like this:
mAng = Math.toDegrees(Math.atan2(mY - pY, mX - pX));
sprite.angle = mAng;
sprite.image.setRotation((float) mAng);
Related
I am building a robot in java and I am implementing a turning system which calculates the angle between two coordinates (x1, y1), (x2, y2), however the robot is currently underturning i.e. if i told it to turn 90 degress it would only turn 40 so I need to input more steering. The robot can turn left or right dependning on whether the turn required is greater than 180. Below the code works but only if the starting heading is at 0 degrees so i need the code to take into consideration the current position.
double xDiff = x2 - x1;
double yDiff = y2 - y1;
double angle = Math.toDegrees(Math.atan2(yDiff, xDiff));
double currentAngle = 0; //is changed after first run
angle = (angle + 360) % 360;
angle = angle - currentAngle;
makes the angle between -180 and 180
if (angle > 180)
angle -= 360;
if(angle < 0 )
{
angle = angle - 45;
}
else if(angle > 0)
{
angle = angle + 45;
}
Don't use angles for this. (Don't use angles for anything, if you can possibly avoid it.) If the dot product between this frame's [xDiff,yDiff] and the current heading vector is negative, the angle is greater than 180 degrees. (Also, the perp dot product indicates whether you need to turn left or right.)
I'm currently working on a Top-Down-Shooter and having some issues with collision.
My world is made of tiles (64x64). The tiles and the entities are rectangles. The player moves with a speed of e.g 2.74 (and not in pixels for smoother movement). But when it comes to the collision between the player (an entity) and a wall i have some issues. To check if there is a collision i take the current position of my player and his movement speed to calculate where his next position would be and if there is any collision. But i check every pixel on the way, so i cant skip an obstacle even if the movement speed is very high. Let's just say the players current position is X:200 Y:200 and he moves 2.74 Pixels a tick in the x direction. My game now checks if there is any collision at X:201 Y:200, X:202 Y:200 or X:202.74 Y:200 and if not moves the player to that position. If I now try to move the player further in the x direction and there is a wall 0.26 Pixels away the player wont move and leave a tiny gap. I tried to calculate the distance between player and wall and add this amount to the players position but for that I need to know which side of the wall the player hits. Also I want the player to be able to move up and down when the wall he hits is in front of him and the other way around.
Here is my collision method (in Java):
public static boolean collision(float ex, float ey, int width, int height) { // ex, ey would be the next position of the player
if (ex < 0 || ex + width > worldWidth || ey < 0 || ey + height > worldHeight) return true; // checks if this position is in the world
int firstTileX = (int) (ex / Tile.TILE_SIZE); // calculates tiles he could possible collide width
int firstTileY = (int) (ey / Tile.TILE_SIZE);
int lastTileX = (int) ((ex + width - 1) / Tile.TILE_SIZE);
int lastTileY = (int) ((ey + height - 1) / Tile.TILE_SIZE);
for (int y = firstTileY; y <= lastTileY; y++) {
if (y < 0) continue; // checks for out of bounds
if (y >= worldTileHeight) break;
for (int x = firstTileX; x <= lastTileX; x++) {
if (x < 0) continue;
if (x >= worldTileWidth) break;
if (tiles[y][x].solid) return true; // if the tile is solid -> collision found
}
}
return false; // no collision found
}
And my movement method:
public void move(float xa, float ya) {
float nx, ny;
while (xa != 0 || ya != 0) {
nx = x;
ny = y;
if (xa != 0) {
if (Math.abs(xa) > 1) { // if the x-speed is greater than 1
nx = x + MathUtil.abs(xa); // returns -1 for negative numbers and 1 for positiv
xa -= MathUtil.abs(xa);
} else { // less than 1
nx = x + xa;
xa = 0;
}
}
if (ya != 0) { // same here
if (Math.abs(ya) > 1) {
ny = y + MathUtil.abs(ya);
ya -= MathUtil.abs(ya);
} else {
ny = y + ya;
ya = 0;
}
}
if (!Level.collision(nx, ny, width, height)) setPosition(nx, ny); // checks if there is an collision and sets the new position if not
else if (!Level.collision(nx, y, width, height)) x = nx; // if there was a collision check if the player can walk in x direction
else if (!Level.collision(x, ny, width, height)) y = ny; // or in y direction
}
}
My problem is the pretty much the same as CoderMusgrove's problem in his post (Pixel-perfect collision and doubles):
Summary & Question
I have a problem where if the speed of an entity isgreater thanthe distance from the tile it is going into, it will leave at least a pixel in between itself and the tile, and I really don't like this. What kind of algorithm could I use that will find the tiniest difference between the entity and the tile?
If you need any additional information, I will be glad to add it.
Thanks for your help!
Easily resolvable by changing your interpretation.
You are retaining a fractional position for the purpose of fine grained speed. Ignore the fraction for the purpose of collision detection and display (if you were to do sub-pixel rendering, do the collision on the subpixel rendering accurarcy level).
int screenX = (int) Math.round(objX);
int screenY = (int) Math.round(objY);
// rendering and collision detection based on rounded position
Since in the digital world a real collision almost never happens, we will always have a situation where the "colliding" circle overlaps the rectangle.
How to put back the circle in the situation where it collides perfectly with the rectangle without overlap?
Suppose that the rectangle is stopped (null velocity) and axis-aligned.
I would solve this problem with a posteriori approach (in two dimensions).
In short I have to solve this equation for t:
Where:
is a number that answers to the question: how many frames ago did the
collision happen perfectly?
is the radius of the circle.
is the center of the circle
is its velocity.
and are functions that return the x and y coordinates of
the point where the circle and the rectangle collide (when the circle is
at position, that is in the position in which perfectly collide with the rectangle).
Recently I solved a similar problem for collisions between circles, but now I don't know the law of the functions A and B.
After years of staring at this problem, and never coming up with a perfect solution, I've finally done it!
It's pretty much a straight forward algorithm, no need for looping and approximations.
This is how it works at a higher level:
Calculate intersection times with each side's plane IF the path from current point to future point crosses that plane.
Check each side's quadrant for single-side intersection, return the intersection.
Determine the corner that the circle is colliding with.
Solve the triangle between the current point, the corner, and the intersecting center (radius away from the corner).
Calculate time, normal, and intersection center.
And now to the gory details!
The input to the function is bounds (which has a left, top, right, bottom) and a current point (start) and a future point (end).
The output is a class called Intersection which has x, y, time, nx, and ny.
{x, y} is the center of the circle at intersection time.
time is a value from 0 to 1 where 0 is at start and 1 is at end
{nx, ny} is the normal, used for reflecting the velocity to determine the new velocity of the circle
We start off with caching variables we use often:
float L = bounds.left;
float T = bounds.top;
float R = bounds.right;
float B = bounds.bottom;
float dx = end.x - start.x;
float dy = end.y - start.y;
And calculating intersection times with each side's plane (if the vector between start and end pass over that plane):
float ltime = Float.MAX_VALUE;
float rtime = Float.MAX_VALUE;
float ttime = Float.MAX_VALUE;
float btime = Float.MAX_VALUE;
if (start.x - radius < L && end.x + radius > L) {
ltime = ((L - radius) - start.x) / dx;
}
if (start.x + radius > R && end.x - radius < R) {
rtime = (start.x - (R + radius)) / -dx;
}
if (start.y - radius < T && end.y + radius > T) {
ttime = ((T - radius) - start.y) / dy;
}
if (start.y + radius > B && end.y - radius < B) {
btime = (start.y - (B + radius)) / -dy;
}
Now we try to see if it's strictly a side intersection (and not corner). If the point of collision lies on the side then return the intersection:
if (ltime >= 0.0f && ltime <= 1.0f) {
float ly = dy * ltime + start.y;
if (ly >= T && ly <= B) {
return new Intersection( dx * ltime + start.x, ly, ltime, -1, 0 );
}
}
else if (rtime >= 0.0f && rtime <= 1.0f) {
float ry = dy * rtime + start.y;
if (ry >= T && ry <= B) {
return new Intersection( dx * rtime + start.x, ry, rtime, 1, 0 );
}
}
if (ttime >= 0.0f && ttime <= 1.0f) {
float tx = dx * ttime + start.x;
if (tx >= L && tx <= R) {
return new Intersection( tx, dy * ttime + start.y, ttime, 0, -1 );
}
}
else if (btime >= 0.0f && btime <= 1.0f) {
float bx = dx * btime + start.x;
if (bx >= L && bx <= R) {
return new Intersection( bx, dy * btime + start.y, btime, 0, 1 );
}
}
We've gotten this far so we know either there's no intersection, or it's collided with a corner. We need to determine the corner:
float cornerX = Float.MAX_VALUE;
float cornerY = Float.MAX_VALUE;
if (ltime != Float.MAX_VALUE) {
cornerX = L;
} else if (rtime != Float.MAX_VALUE) {
cornerX = R;
}
if (ttime != Float.MAX_VALUE) {
cornerY = T;
} else if (btime != Float.MAX_VALUE) {
cornerY = B;
}
// Account for the times where we don't pass over a side but we do hit it's corner
if (cornerX != Float.MAX_VALUE && cornerY == Float.MAX_VALUE) {
cornerY = (dy > 0.0f ? B : T);
}
if (cornerY != Float.MAX_VALUE && cornerX == Float.MAX_VALUE) {
cornerX = (dx > 0.0f ? R : L);
}
Now we have enough information to solve for the triangle. This uses the distance formula, finding the angle between two vectors, and the law of sines (twice):
double inverseRadius = 1.0 / radius;
double lineLength = Math.sqrt( dx * dx + dy * dy );
double cornerdx = cornerX - start.x;
double cornerdy = cornerY - start.y;
double cornerdist = Math.sqrt( cornerdx * cornerdx + cornerdy * cornerdy );
double innerAngle = Math.acos( (cornerdx * dx + cornerdy * dy) / (lineLength * cornerdist) );
double innerAngleSin = Math.sin( innerAngle );
double angle1Sin = innerAngleSin * cornerdist * inverseRadius;
// The angle is too large, there cannot be an intersection
if (Math.abs( angle1Sin ) > 1.0f) {
return null;
}
double angle1 = Math.PI - Math.asin( angle1Sin );
double angle2 = Math.PI - innerAngle - angle1;
double intersectionDistance = radius * Math.sin( angle2 ) / innerAngleSin;
Now that we solved for all sides and angles, we can determine time and everything else:
// Solve for time
float time = (float)(intersectionDistance / lineLength);
// If time is outside the boundaries, return null. This algorithm can
// return a negative time which indicates the previous intersection.
if (time > 1.0f || time < 0.0f) {
return null;
}
// Solve the intersection and normal
float ix = time * dx + start.x;
float iy = time * dy + start.y;
float nx = (float)((ix - cornerX) * inverseRadius);
float ny = (float)((iy - cornerY) * inverseRadius);
return new Intersection( ix, iy, time, nx, ny );
Woo! That was fun... this has plenty of room for improvements as far as efficiency goes. You could reorder the side intersection checking to escape as early as possible while making as few calculations as possible.
I was hoping there would be a way to do it without trigonometric functions, but I had to give in!
Here's an example of me calling it and using it to calculate the new position of the circle using the normal to reflect and the intersection time to calculate the magnitude of reflection:
Intersection inter = handleIntersection( bounds, start, end, radius );
if (inter != null)
{
// Project Future Position
float remainingTime = 1.0f - inter.time;
float dx = end.x - start.x;
float dy = end.y - start.y;
float dot = dx * inter.nx + dy * inter.ny;
float ndx = dx - 2 * dot * inter.nx;
float ndy = dy - 2 * dot * inter.ny;
float newx = inter.x + ndx * remainingTime;
float newy = inter.y + ndy * remainingTime;
// new circle position = {newx, newy}
}
And I've posted the full code on pastebin with a completely interactive example where you can plot the starting and ending points and it shows you the time and resulting bounce off of the rectangle.
If you want to get it running right away you'll have to download code from my blog, otherwise stick it in your own Java2D application.
EDIT:
I've modified the code in pastebin to also include the collision point, and also made some speed improvements.
EDIT:
You can modify this for a rotating rectangle by using that rectangle's angle to un-rotate the rectangle with the circle start and end points. You'll perform the intersection check and then rotate the resulting points and normals.
EDIT:
I modified the code on pastebin to exit early if the bounding volume of the path of the circle does not intersect with the rectangle.
Finding the moment of contact isn't too hard:
You need the position of the circle and rectangle at the timestep before the collision (B) and the timestep after (A). Calculate the distance from the center of the circle to the line of the rectangle it collides with at times A and B (ie, a common formula for a distance from a point to a line), and then the time of collision is:
tC = dt*(dB-R)/(dA+dB),
where tC is the time of collision, dt is the timestep, dB is the distance to line before the collision, dA is the distance after the collision, and R is the radius of the circle.
This assumes everything is locally linear, that is, that your timesteps are reasonably small, and so that the velocity, etc, don't change much in the timestep where you calculate the collision. This is, after all, the point of timesteps: in that with a small enough timestep, non-linear problems are locally linear. In the equation above I take advantage of that: dB-R is the distance from the circle to the line, and dA+dB is the total distance moved, so this question just equates the distance ratio to the time ratio assuming everything is approximately linear within the timestep. (Of course, at the moment of collision the linear approximation isn't its best, but to find the moment of collision, the question is whether it's linear within a timestep up to to moment of collision.)
It's a non-linear problem, right?
You take a time step and move the ball by its displacement calculated using velocity at the start of the step. If you find overlap, reduce the step size and recalculate til convergence.
Are you assuming that the balls and rectangles are both rigid, no deformation? Frictionless contact? How will you handle the motion of the ball after contact is made? Are you transforming to a coordinate system of the contact (normal + tangential), calculating, then transforming back?
It's not a trivial problem.
Maybe you should look into a physics engine, like Box2D, rather than coding it yourself.
I want the player image to point towards the mouse cursor. I use this code to get the postion of the mouse cursor:
private int cursorX = MouseInfo.getPointerInfo().getLocation().x;
private int cursorY = MouseInfo.getPointerInfo().getLocation().y;
Note: The default player image points upwards
You'll have to use trigonometry in order to calculate the angle of rotation. For that you'll first need to obtain the location of the image and the cursor. I cannot tell you how to get the position for the image as this may vary. For this example (adapted from here), I'll assume imageX and imageY are the x and y positions of your image:
float xDistance = cursorX - imageX;
float yDistance = cursorY - imageY;
double rotationAngle = Math.toDegrees(Math.atan2(yDistance, xDistance));
To find the angle from a coordinate (0,0) to another coordinate (x,y), we can use the trigonometric function tan^-1(y/x).
Java's Math class specifies a static method atan2 which acts as a tan^-1 function (also known as "arctangent", hence "atan") and returns the angle in radians. (There is a method atan which takes one argument. See the linked Javadoc.)
In order to find the angle in degrees from the coordinate of your "player" to the coordinate of the mouse cursor, (I'll assume this "player" you make mention of has x and y coordinates), we need to do something like this:
double theta = Math.atan2(cursorY - player.getY(), cursorX - player.getX());
It is also of note that an angle of zero radians would indicate that the mouse is directly to the right of the player. You mention that the "default player image" points upwards; if you mean that before rotation, your image faces upward for the player, it would be more conventional to geometry and the Java implementation of atan2 to have your player face right "by default".
Though this was asked two years ago...
If you need the mouse to keep updating the mouse position in the window, see mouseMotionListener. The current you use to get the mouse position is relative to the whole screen. Just keep that in mind.
Otherwise, here is a method I use,
public double angleInRelation(int x1, int y1, int x2, int y2) {
// Point 1 in relation to point 2
Point point1 = new Point(x1, y1);
Point point2 = new Point(x2, y2);
int xdiff = Math.abs(point2.x - point1.x);
int ydiff = Math.abs(point2.y - point1.y);
double deg = 361;
if ( (point2.x > point1.x) && (point2.y < point1.y) ) {
// Quadrant 1
deg = -Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ( (point2.x > point1.x) && (point2.y > point1.y) ) {
// Quadrant 2
deg = Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ( (point2.x < point1.x) && (point2.y > point1.y) ) {
// Quadrant 3
deg = 90 + Math.toDegrees(Math.atan(Math.toRadians(xdiff) / Math.toRadians(ydiff)));
} else if ( (point2.x < point1.x) && (point2.y < point1.y) ) {
// Quadrant 4
deg = 180 + Math.toDegrees(Math.atan(Math.toRadians(ydiff) / Math.toRadians(xdiff)));
} else if ((point2.x == point1.x) && (point2.y < point1.y)){
deg = -90;
} else if ((point2.x == point1.x) && (point2.y > point1.y)) {
deg = 90;
} else if ((point2.y == point1.y) && (point2.x > point1.x)) {
deg = 0;
} else if ((point2.y == point2.y) && (point2.x < point1.x)) {
deg = 180;
}
if (deg == 361) {
deg = 0;
}
return deg;
}
In words, you get the angle of each of the θs as shown in the picture below and check if x or y are 0 and make a special case for that.
The origin is the middle of the picture and each of the points (marked with a hand-drawn cross) is where the mouse position is.
I wrote a gui where a user draws something in a (640x480) window. It makes that drawing into a set of points stored in a Vector array. Now, how do I translate those set of points to the origin (0,0 top left corner of the window) or put it at a specified pos? The width and height of the window I want it in is also 640x480.
After that is solved, how do you scale that new set of points to a size I want?
UPDATE 1
I solved the scale issue, but not the positioning issue. The drawing is not going where I tell it to be. Code below of what I have so far.
float scaleX = (float)width/boundingPoints.width;
float scaleY = (float)height/boundingPoints.height;
for(int i = 0; i < cg_points.size()-1; i++){
Point p1 = cg_points.get(i);
Point p2 = cg_points.get(i+1);
g.drawLine((int)(p1.x*scaleX) + pos.x, (int)(p1.y*scaleY) + pos.y, (int)(p2.x*scaleX) + pos.x, (int)(p2.y*scaleY) + pos.y);
}
I want the drawing to start at where pos [x, y] is. What is currently the problem is this. It does follow what pos.x and pos.y does, but it is way off and not starting at pos[x,y].
Here is a screen shot of the issue
As you can see from the picture, the box is where the star is supposed to be. The scaling is right as you can see, just not the pos. That is because the points in the drawing may NOT start at (0,0).
Any suggestions?
Thanks!
To translate a drawing, simply
foreach point in array
point.x += translate.x
point.y += translate.y
If you're going to center a drawing, pick a center (such as averaging all your points), negate that value, then translate all your points by that value.
To scale a drawing:
foreach point in array
point.x *= scale
point.y *= scale
So I solved it...YAY!!! Here is the code below in case you run into the same issue as I had.
float scaleX = (float)width/boundingPoints.width;
float scaleY = (float)height/boundingPoints.height;
int bx = boundingPoints.x;
int by = boundingPoints.y;
for(int i = 0; i < cg_points.size()-1; i++){
Point p1 = cg_points.get(i);
Point p2 = cg_points.get(i+1);
int x1 = (int) ((p1.x-bx)*scaleX);
x1 += pos.x;
int y1 = (int) ((p1.y-by)*scaleY);
y1 += pos.y;
int x2 = (int) ((p2.x-bx)*scaleX);
x2 += pos.x;
int y2 = (int) ((p2.y-by)*scaleY);
y2 += pos.y;
g.drawLine(x1, y1, x2, y2);
}