What is the logic that goes behind rotating an image via mouse movement. I know how to rotate using graphics2d.rotate...but having difficulty doing it with the mouse as the source for rotation. Here is basic steps:
get mouse x(dx) and mouse y(dy) distances from anchoring point( in this case that would be
the center of the image we want to rotate).
use this point in Math.arcTan2(dy,dx) to obtain the angle or rotation.
use the value from step to for Graphics2D.rotate method.
With that strategy, everytime i rotate the image, the image starts rotating from -pi and after 90 degrees of rotation it go goes back to -pi. I don't understand what I'm doing wrong here, it should be pretty basic.
Here is a part of the code :
// mouse dragged events get sent here.
public void mouseDragged( MouseEvent e ) {
int mx = e.getX( ), my = e.getY( );
// just checking if it falls within bounds of the image we
// want to rotate.
if( mx > speedKX || mx < speedKX + speedK.getWidth( ) || my > speedKY || my < speedKY + speedK.getHeight( )/2 )
{
theta += getTheta( e.getX( ), e.getY( ) );
}
}
As I understand you should look for the initial angle (when you clicked, the line between the anchor and your click) and the current angle (when you are dragging, same line). That angle (independent from the current distance to anchor point) will give you the rotation.
So you have to:
rotate(anglenow - angle0)
How to find it:
In both cases (initial click and mouse move event) you have to find angle between anchor and mouse point thinking the anchor as the origin.
I would use a method (getAngle(x1,y1,x2,y2). That method (except for race conditions like same x or same y, easily detectable) should calculate arctan(dy/dx).
Sign
But when you are dividing dy/dx it can be:
+ / + -> +
+ / - -> -
- / + -> -
- / - -> +
It is, four posibilities give you two kind of results. So, you have to look some condition to detect them.
I should review arctan doc or source to see what values it gives (between 0 and pi, or -pi/2 and +pi/2) and check then sign of the dx or the dy (depending on what range arctan returns) and use it to add/decrement pi to then resulting angle.
Then you'll get a getAngle method that return correctly the 360º space.
Edit
Javadoc says:
Math.atan retuns the arc tangent of an angle, in the range of -pi/2 through pi/2.
So, assuming your angle of value 0 is for the X axis as I assumed, the range it returns is the right hemisphere. So you have to distiguish the right hemisphere from the left one.
If you calculate dx = xtarget - xorigin (as you did for the division) it will be positive if the correct hemisphere is the right one, and negative if it's not.
So if dy < 0 then you have to add pi to your resulting angle. It will be between -pi/2 and 3pi/2. You also can correct the result by passing all to (-pi,pi) range or (0,2pi) range.
Edit: pseudocode, please double check!
onmousedown {
startpoint = (x,y);
startangle = getAngle(origin, startpoint);
}
onmousemove {
currentpoint = (x,y);
currentangle = getAngle(origin, currentpoint);
originalimage.rotate(currentangle - startangle);
}
getAngle(origin, other) {
dy = other.y - origin.y;
dx = other.x - origin.x;
if (dx == 0) // special case
angle = dy >= 0? PI/2: -PI/2;
else
{
angle = Math.atan(dy/dx);
if (dx < 0) // hemisphere correction
angle += PI;
}
// all between 0 and 2PI
if (angle < 0) // between -PI/2 and 0
angle += 2*PI;
return angle;
}
Related
I'm making a game with libGDX in Java. I'm trying to make a collision detection. As you can see in the image, I have a line which is a wall and a player with specified radius. The desired position is the next location which the player is trying to be in. But because there is a wall, he's placed in the Actual Position which is on the Velocity vector, but more closer to the prev location. I'm trying to figure out how can I detect that closer position?
My attempt:
private void move(float deltaTime) {
float step;
beginMovementAltitude();
if (playerComponent.isWalking())
step = handleAcceleration(playerComponent.getSpeed() + playerComponent.getAcceleration());
else step = handleDeacceleration(playerComponent.getSpeed(), playerComponent.getAcceleration());
playerComponent.setSpeed(step);
if (step == 0) return;
takeStep(deltaTime, step, 0);
}
private void takeStep(float deltaTime, float step, int rotate) {
Vector3 position = playerComponent.getCamera().position;
float x = position.x;
float y = position.y;
int radius = playerComponent.getRadius();
auxEnvelope.init(x, x + radius, y, y + radius);
List<Line> nearbyLines = lines.query(auxEnvelope);
float theta;
int numberOfIntersections = 0;
float angleToMove = 0;
Gdx.app.log(step + "", "");
for (Line line : nearbyLines) {
VertexElement src = line.getSrc();
VertexElement dst = line.getDst();
auxVector3.set(playerComponent.getCamera().direction);
auxVector3.rotate(Vector3.Z, rotate);
float nextX = x + (step * deltaTime) * (auxVector3.x);
float nextY = y + (step * deltaTime) * playerComponent.getCamera().direction.y;
float dis = Intersector.distanceLinePoint(src.getX(), src.getY(), dst.getX(), dst.getY(), nextX, nextY);
boolean bodyIntersection = dis <= 0.5f;
auxVector21.set(src.getX(), src.getY());
auxVector22.set(dst.getX(), dst.getY());
auxVector23.set(nextX, nextY);
if (bodyIntersection) {
numberOfIntersections++;
if (numberOfIntersections > 1) {
return;
}
theta = auxVector22.sub(auxVector21).nor().angle();
float angle = (float) (180.0 / MathUtils.PI * MathUtils.atan2(auxVector23.y - position.y, auxVector23.x - position.x));
if (angle < 0) angle += 360;
float diff = (theta > angle) ? theta - angle : angle - theta;
if (step < 0) step *=-1;
angleToMove = (diff > 90) ? theta + 180 : theta;
}
}
if (numberOfIntersections == 0) {
moveCameraByWalking(deltaTime, step, rotate);
} else {
moveCameraInDirection(deltaTime, step, angleToMove);
}
}
The idea is to find intersection of path of object center and the line moved by radius of the circle, see that picture.
At first, you need to find a normal to the line. How to do it, depends on how the line is defined, if it's defined by two points, the formula is
nx = ay - by
ny = bx - ax
If the line is defined by canonical equation, then coefficients at x and y define normal, if I remembered correctly.
When normal is found, we need to normalize it - set length to 1 by dividing coordinates by vector length. Let it be n.
Then, we will project starting point, desired point and randomly chosen point on line to n, treating them as radius vectors.
Projection of vector a to vector b is
project (a, b) = scalar_product (a, b) / length (b)**2 * b
but since b is n which length equals 1, we will not apply division, and also we want to only find length of the result, we do not multiply by b. So we only compute scalar product with n for each of three aforementioned points, getting three numbers, let s be the result for starting point, d for desired point, l for chosen point on the line.
Then we should modify l by radius of the circle:
if (s < d) l -= r;
else if (s > d) l += r;
If s = d, your object moves in parallel along the line, so line can't obstruct its movement. It's highly improbable case but should be dealt with.
Also, that's important, if l was initially between s and d but after modifying is no longer between then, it's a special case you may want to handle (restrict object movement for example)
Ather that, you should compute (d - s) / (l - s).
If the result is greater or equals 1, the object will not reach the line.
If the result is between 0 and 1, the line obstructs movement and the result indicates part of the path the object will complete. 0.5 means that object will stop halfway.
If the result is negative, it means the line is behind the object and will not obstruct movement.
Note that when using floating point numbers the result will not be perfectly precise, that's why we handle that special case. If you want to prevent this from happening at all, organize loop and try approximations until needed precision is reached.
I have a rectangle which when I hold down the mouse button I want that rectangle to move to that point following a strait line 1 pixel at a time.
This is my code so far (I put comments in it so you can understand)
float distanceX = finalX - x; //the number of pixels needed to get to destination on the X axis
float distanceY = finalY - y; // same as above but Y axis
float moveX = distanceX > 0 ? 1 : -1; // I only want it to move 1 pixel per render
float moveY = distanceY > 0 ? 1 : -1; // same as above
Array<Stuff> collidedX = new Array<Stuff>(); //saves collisions seperately for x and y
Array<Stuff> collidedY = new Array<Stuff>(); //because I want the square to move where the mouse is pointing even if it means only aligning one axis
for (Stuff s : collidables) {
if (overlapsT(s, x + moveX, y)) {
collidedX.add(s);
}
}
if (collidedX.size < 1) {
if (distanceX != 0)
x += moveX;
}
for (Stuff s : collidables) {
if (overlapsT(s, x, y + moveY)) {
collidedY.add(s);
}
}
if (collidedY.size < 1) {
if (distanceY != 0)
y += moveY;
}
right now the problem is it goes perfectly diagonal until it lines up with one of the axis and then moves up down left or right to the destination.
I don't want to move fractions of pixels. The way my custom physics engine works is each pixel matters, fractional pixels are no good so I am trying to figure out how to smooth the path or rather how to decide when to add 1 to x and then y.
Currently I can't comment, so I have to answer. I think the Bresenham's line algorithm will help you out. It's for drawing rasterize lines.
Bresenham
I've been thinking on some fast and brilliant pixel - perfect collision detection between a circle and any sprite. I need to get 2 points of collision to be able to calculate a normal vector out of them later. I managed to come up with some solution but the more scaling is done in my game, the more inaccurate and unprecise this collision is...It seems as if the code I posted below, was good and correct becouse I have been checking it already a few times and spent a few days reading it again and again... I also checked visually that the collision masks and areas of collision are calculated perfectly fine in the code below so the problem definitely doesn't lay there but in this method.
So I guess that the problem here is the loss of data in floating point arithmetic unless somebody finds a flaw in this method?
If however the problem is really with the float loss of data, what other solution would you recommend to find 2 points of collision between circle and any other sprite in pixel perfect? I really liked my solution becouse it was relatively fast
int xOffset1 = (int)colRectLeft; // left boundary of the collision area for the first sprite
int xOffset2 = (int)colCircleLeft; // left boundary of the collision area for the circle sprite
int yOffset1 = (int)colRectBottom; // bottom boundary of the collision area for the first sprite
int yOffset2 = (int)colCircleBottom; // bottom boundary of the collision area for the circle sprite
int width = (int)(colCircleRight - colCircleLeft); //width of the collision area - same for both sprites
int height = (int)(colCircleTop - colCircleBottom); // height of the collision area same for both sprites
// Pixel-perfect COLLISION DETECTION between circle and a sprite
// my custom vector classes - nothing special
Math2D.Vector_2 colRightPoint = new Math2D.Vector_2(-1, -1); // The right point of collision lying on the circle's circumference
Math2D.Vector_2 colLeftPoint = new Math2D.Vector_2(-1, -1); // the left point of collision lying on the circle's circumference
boolean colRightFound = false;
boolean colLeftFound = false;
// I'm going through y in the circle's area of collision
for (float y = yOffset2; y < yOffset2 + height; y += 1)
{
// from equation: (x-Sx)^2 + (y-Sy)^2 = r^2
// x1/2 = (+-)sqrt(r^2 - (y - Sy)^2) + Sx
//(Sx, Sy) is (circle's radius, circle's radius) becouse I want the points on the circle's circumference to have positive coordinates
float x1 = (float) (Math.sqrt(radius*radius - (y - radius)*(y - radius)) + radius); // the right pixel on the circumference
float x2 = (float) (-x1 + 2*radius); // the left pixel on the circumference
//first I check if the calculated x is inside of the previously calculated area of collision for both circle's area and a sprite's area
if (x1 >= xOffset2 &&
x1 <= xOffset2 + width &&
xOffset1 + x1 - xOffset2 < rectFrameW &&
yOffset1 + (int)y-yOffset2 < rectFrameH &&
yOffset1 + (int)y-yOffset2 > 0 &&
xOffset1 + x1 - xOffset2 > 0)
{
//I don't have to check if the point on the circle's circumference is opaque becouse it's always so just check if the same point translated to sprite's area of collision is opaque
boolean opaqueRectPixel = go.gameData.images.get(go.pic_nr)
.collision_mask[(int)((yOffset1 + (int)y-yOffset2)*rectFrameW +
(xOffset1 + x1 - xOffset2))];
if(opaqueRectPixel)
{
if(!colRightFound)
{
colRightPoint.x = (xOffset1 + x1 - xOffset2);
colRightPoint.y = (yOffset1 + (int)y - yOffset2);
colRightFound = true;
}
else if(!colLeftFound)
{
colLeftPoint.x = (xOffset1 + x1 - xOffset2);
colLeftPoint.y = (yOffset1 + (int)y - yOffset2);
}
}
}
//the same logic for the left point on the circle's circumference
if (x2 >= xOffset2 &&
x2 <= xOffset2 + width &&
xOffset1 + x2 - xOffset2 < rectFrameW &&
yOffset1 + (int)y-yOffset2 < rectFrameH &&
yOffset1 + (int)y-yOffset2 > 0 &&
xOffset1 + x2 - xOffset2 > 0)
{
boolean opaqueRectPixel = go.gameData.images.get(go.pic_nr)
.collision_mask[(int)((yOffset1 + (int)y-yOffset2)*rectFrameW +
(xOffset1 + x2 - xOffset2))];
if(opaqueRectPixel)
{
if(!colLeftFound)
{
colLeftPoint.x = (xOffset1 + x2 - xOffset2);
colLeftPoint.y = (yOffset1 + (int)y - yOffset2);
colLeftFound = true;
}
else if(!colRightFound)
{
colRightPoint.x = (xOffset1 + x2 - xOffset2);
colRightPoint.y = (yOffset1 + (int)y - yOffset2);
}
}
}
// if both points are already found, finish
if(colLeftFound && colRightFound)
break;
}
edit: Actually, what I'm doing in this method is finding points of intersection between circle and a sprite
edit: Ok, I'm uploading images to describe my algorithm a bit better. I really tried my best to explain it but if there's still something missing, let me know please!
Also I would accept any other good solutions to find intersection points between a circle and any sprite in pixel perfect, if you don't want to check my code :(... Eh, I'm always having problems with collisions...
If you absolutely want (or need) pixel perfect, your solution looks good.
don't forget to first make a rectangle-to-rectangle collision before testing a pixel perfect detection, to avoid unneeded processings.
If you want another accurate method which maybe more efficient, look for Separating Axis Theorem.
You can find more information about it here :
http://rocketmandevelopment.com/blog/separation-of-axis-theorem-for-collision-detection/
and here :
http://www.metanetsoftware.com/technique/tutorialA.html
The last one have nice interactive explanation and demonstration. Enjoy :)
...as I was not able to show the raster in the comments:
I did not mentally parse your code, however from the image I see that you try to detect borderline collisions. Putting round or diagonal (border)lines into a raster may cause occasions, where two crossing lines do not overlay each other - like this:
1 2
2 1
whereby 1 would be line 1 and 2 would be line 2.
However I still like the idea of checking border lines combined with rectangle pre-checks. If you would render an array of raster proved-closed line coordinates by sprites you could check them against each other. This could also be enriched by border line segmenting (such as North, East, West and South or a bit more fine grain - I guess there is an optimum). A diagonal proved-closed line in the check data set must represent something like this:
x _
x x
whereby the x represent the pixels of your line and the _ is an empty raster seat.
I'm sorry if this question was asked before, I did search, and I did not find an answer.
My problem is, that I'd like to make movement on all 3 axes with the X and Y rotation of the camera being relevant.
This is what I did:
private static void fly(int addX, int addY){ //parameters are the direction change relative to the current rotation
float angleX = rotation.x + addX; //angle is basically the direction, into which we will be moving(when moving forward this is always the same as our actual rotation, therefore addX and addY would be 0, 0)
float angleY = rotation.y + addY;
float speed = (moveSpeed * 0.0002f) * delta;
float hypotenuse = speed; //the length that is SUPPOSED TO BE moved overall on all 3 axes
/* Y-Z side*/
//Hypotenuse, Adjacent and Opposite side lengths of a triangle on the Y-Z side
//The point where the Hypotenuse and the Adjacent meet is where the player currently is.
//OppYZ is the opposite of this triangle, which is the ammount that should be moved on the Y axis.
//the Adjacent is not used, don't get confused by it. I just put it there, so it looks nicer.
float HypYZ = speed;
float AdjYZ = (float) (HypYZ * Math.cos(Math.toRadians(angleX))); //adjacent is on the Z axis
float OppYZ = (float) (HypYZ * Math.sin(Math.toRadians(angleX))); //opposite is on the Y axis
/* X-Z side*/
//Side lengths of a triangle on the Y-Z side
//The point where the Hypotenuse and the Adjacent meet is where the player currently is.
float HypXZ = speed;
float AdjXZ = (float) (HypXZ * Math.cos(Math.toRadians(angleY))); //on X
float OppXZ = (float) (HypXZ * Math.sin(Math.toRadians(angleY))); //on Z
position.x += AdjXZ;
position.y += OppYZ;
position.z += OppXZ;
}
I only implement this method when moving forwards(parameters: 0, 90) or backwards(params: 180, 270), since movement can't happen on the Y axis while going sideways, since you don't rotate on the Z axis. ( the method for going sideways(strafing) works just fine, so I won't add that.)
the problem is that when I look 90 degrees up or -90 down and then move forward I should be moving only on the Y axis(vertically) but for some reason I also move forwards(which means on the Z axis, as the X axis is the strafing).
I do realize that movement speed this way is not constant. If you have a solution for that, I'd gladly accept it as well.
I think your error lies in the fact that you don't fully project your distance (your quantity of movement hypothenuse) on your horizontal plane and vertical one.
In other words, whatever the chosen direction, what you are doing right now is moving your point of hypothenuse in the horizontal plane X-Z, even though you already move it of a portion of hypothenuse in the vertical direction Y.
What you probably want to do is moving your point of a hypothenuse quantity as a total.
So you have to evaluate how much of the movement takes place in the horizontal plane and how much in the vertical axis. Your direction gives you the answer.
Now, it is not clear to me right now what your 2 angles represent. I highly recommend you to use Tait–Bryan angles in this situation (using only yawn and pitch, since you don't seem to need the rolling - what you call the Z-rotation), to simplify the calculations.
In this configuration, the yawn angle would be apparently similar to your definition of your angleY, while the pitch angle would be the angle between the horizontal plane and your hypothenuse vector (and not the angle of the projection in the plane Y-Z).
A schema to clarify:
With :
s your quantity of movement from your initial position P_0 to P_1 (hypothenuse)
a_y the yawn angle and a_p the pitch one
D_x, D_y, D_z the displacements for each axis (to be added to position, ie AdjXZ, OppYZ and OppXZ)
So if you look at this representation, you can see that your triangle in X-Z doesn't have s as hypotenuse but its projection s_xz. The evaluation of this distance is quite straightforward: if you place yourself in the triangle P_0 P_1 P_1xz, you can see that s_xz = s * cos(a_p). Which gives you:
float HypXZ = speed * Math.cos(Math.toRadians(angleP))); // s_xz
float AdjXZ = (float) (HypXZ * Math.cos(Math.toRadians(angleY))); // D_x
float OppXZ = (float) (HypXZ * Math.sin(Math.toRadians(angleY))); // D_z
As for D_y ie OppYZ, place yourself in the triangle P_0 P_1 P_1xz again, and you'll obtain:
float OppYZ = (float) (speed * Math.sin(Math.toRadians(angleP))); // D_y
Now, if by angleX you actually meant the angle of elevation as I suppose you did, then angleP = angleX and HypXZ = AdjYZ in your code.
With this correction, if angleX = 90 or angleX = -90, then
HypXZ = speed * cos(angleX) = speed * cos(90deg) = speed * 0;
... and thus AdjXZ = 0 and OppXZ = 0. No movement in the horizontal plane.
Note:
To check if your calculations are correct, you can verify if you actually move your point of the wanted quantity of movement (hypothenuse ie speed ie s). Using Pythagorean theorem:
s² = s_xz² + D_z² // Applied in the triangle P_0 P_1 P_1xz
= D_x² + D_y² + D_z² // Applied in the triangle P_0 P_1x P_1xz
With the definitions of the displacements given above:
D_x² + D_y² + D_z²
= (s * cos(a_p) * cos(a_y))² + (s * cos(a_p) * sin(a_y))² + (s * sin(a_p))²
= s² * (cos(a_p)² * cos(a_y)² + cos(a_p)² * sin(a_y)² + sin(a_p)²)
= s² * (cos(a_p)² * (cos(a_y)² + sin(a_y)²) + sin(a_p)²)
= s² * (cos(a_p)² * 1 + sin(a_p)²)
= s² * (cos(a_p)² + sin(a_p)²)
= s² * 1 // Correct
Hope it helped... Bye!
I have a circle drawn, and I want to make it so I can have more slices than four. I can easily do four quadrants because I just check if the mouse in in the circle and inside a box.
This is how I am checking if the point is in the circle.
if( Math.sqrt((xx-x)*(xx-x) + (yy-y)*(yy-y)) <= radius)
{
return true;
}
else
{
return false;
}
How can I modify this if the circle is divided into more than 4 regions?
For radial slices (circular sectors), you have a couple of alternatives:
Use Math.atan2 to calculate the 4-quadrant angle of the line from the circle center to the point. Compare to the slice angles to determine the slice index.
For a particular slice, you can test which side of each slice edge the point falls. Classify the point accordingly. This is more complicated to calculate but probably faster (for a single slice) than calling Math.atan2.
The following sample code calculates the slice index for a particular point:
int sliceIndex(double xx, double yy, double x, double y, int nSlices) {
double angle = Math.atan2(yy - y, xx - x);
double sliceAngle = 2.0 * Math.PI / nSlices;
return (int) (angle / sliceAngle);
}
The above code makes the following assumptions:
slices are all the same (angular) width
slices are indexed counter-clockwise
slice 0 starts at the +x axis
slices own their right edge but not their left edge
You can adjust the calculations if these assumptions do not apply. (For instance, you can subtract the start angle from angle to eliminate assumption 3.)
First we can check that the point is within the circle as you did. But I woudln't combine this with a check for which quadrant (is that why you have radius/2 ?)
if( (xx-x)*(xx-x) + (yy-y)*(yy-y) > radius*radius)
return false;
Now we can look to see which region the point is in by using the atan2 function. atan2 is like Arctan except the Arctangent function always returns a value between -pi/2 and pi/2 (-90 and +90 degrees). We need the actual angle in polar coordinate fashion. Now assuming that (x,y) is the center of your circle and we are interested in the location of the point (xx,yy) we have
double theta = Math.atan2(yy-y,xx-x);
//theta is now in the range -Math.PI to Math.PI
if(theta<0)
theta = Math.PI - theta;
//Now theta is in the range [0, 2*pi]
//Use this value to determine which slice of the circle the point resides in.
//For example:
int numSlices = 8;
int whichSlice = 0;
double sliceSize = Math.PI*2 / numSlices;
double sliceStart;
for(int i=1; i<=numSlices; i++) {
sliceStart = i*sliceSize;
if(theta < sliceStart) {
whichSlice = i;
break;
}
}
//whichSlice should now be a number from 1 to 8 representing which part of the circle
// the point is in, where the slices are numbered 1 to numSlices starting with
// the right middle (positive x-axis if the center is (0,0).
It is more a trig problem Try something like this.
int numberOfSlices=8;
double angleInDegrees=(Math.toDegrees(Math.atan2(xx-x ,yy-y)));
long slice= Math.round(( numberOfSlices*angleInDegrees )/360 );