I currently loop through all my sprites checking if they intersect with each other like this:
for (Sprite s : sprites) {
if (s.dead) {
dead.add(s);
}
for (Sprite sprite : sprites) {
if (!sprite.equals(s)) {
s.collide(sprite, maxX, maxY);
}
}
s.run();
}
and the sprite checks using the Rect.intersects() method like so:
if (getRect().intersects(s.getRect()))
But sometimes it just completely ignores a collision, and the objects just pass through each other.
Any ideas?
You should try changing the code to
if(getRect().intersects(s.getRect()) || s.getRect().intersects(getRect()))
{
// They have intersected
}
The reason for this being, the intersection method check is unique for each rectangle. Performing an intersection check to see if rectangle a intersects rectangle b, is different than performing an intersection check to see if rectangle b intersects rectangle a.
Other than this, can you give me more information on your rectangles? Are they rotating? How fast are they moving? How large are they? Other information would be use full as well, I can try to think of other reasons for why they are not colliding.
I fixed it by making it create a rectangle for the area that it covers between frames like so:
private void checkForNextCollision() {
double boundsWidth = width + dX ;
if(dX < 0){
boundsWidth= width - dX ;
}
double boundsHeight = height + dY ;
if(dY < 0){
boundsHeight = height - dY ;
}
double boundx = xWorld + dX ;
double boundy = yWorld + dY ;
betweenRect = new Rectangle((int)(boundx),(int)(boundy),(int)(boundsWidth), (int)(boundsHeight));
}
This rectangle is then checked against the rectangle created in the other sprites to check if there should be a collision in the next frame:
public void collide(Sprite s, int maxX, int maxY) {
maxWX = maxX;
maxWY = maxY;
//check for collision with borders
if (xWorld <= 0) {
dX = -dX;
xWorld += 2;
if(xWorld < -1000){
dX = 0;
xWorld += 10;
}
}
if (yWorld <= 0) {
dY = -dY;
yWorld += 2;
if(yWorld < -1000){
dX = 0;
yWorld += 10;
}
}
if (xWorld + width >= maxX) {
dX = -dX;
xWorld -= 2;
if(xWorld+width > maxX + 1000){
dX = 0;
xWorld -= 10;
}
}
if (yWorld + height >= maxY) {
dY = -dY;
yWorld -= 2;
if(yWorld+height > maxY + 1000){
dY = 0;
yWorld -= 10;
}
}
//check for collision with borders
if(betweenRect.intersects(s.betweenRect)){
willIntersect = true;
}else{
willIntersect = false;
}
// Use all checks to see if they should collide
if (getRect().intersects(s.getRect()) || s.getRect().intersects(getRect()) || willIntersect || (xWorld + width > s.xWorld && xWorld < s.xWorld + s.width && yWorld < s.yWorld+s.height && yWorld + height > s.yWorld) ) {
double lastDy = dY;
double lastsDy = s.dY;
double lastDx = dX;
double lastsDx = s.dX;
dY = (((weight - s.weight) / (weight + s.weight)) * lastDy)
+ (((2.0 * s.weight) / (weight + s.weight)) * lastsDy);
s.dY = (((s.weight - weight) / (weight + s.weight)) * lastsDy)
+ (((2.0 * weight) / (weight + s.weight)) * lastDy);
dX = (((weight - s.weight) / (weight + s.weight)) * lastDx)
+ (((2.0 * s.weight) / (weight + s.weight)) * lastsDx);
s.dX = (((s.weight - weight) / (weight + s.weight)) * lastsDx)
+ (((2.0 * weight) / (weight + s.weight)) * lastDx);
if(willIntersect){
willIntersect = false;
//s.willIntersect = false;
}
}
}
Related
I am currently generating an isometric map which should allow some sprites to move randomly within its bounds. My sprites, or 'humans' do move within a specified constraint however it is not the correct boundaries I wish to set it to. Below is my code.
public class Human implements Entity {
private int[][] map;
public static final int TILE_WIDTH = 34;
public static final int TILE_HEIGHT = 34;
private int min = 100;
private int max = 200;
private Texture img;
// position variable
private Vector2 pos;
private float time;
public Human() {
img = new Texture(Gdx.files.internal("humanFF.png"));
// coordinates of human initial position
pos = new Vector2(9, 220);
// for locking movement if need be.
time = 2;
map = randomGenerator();
}
#Override
public void render(SpriteBatch batch) {
batch.draw(img, pos.x, pos.y);
}
#Override
public void update(float delta) {
time += delta;
Random rand = new Random();
int upperbound = 2;
double double_random = rand.nextDouble(upperbound);
// lock human, can only move once every 2 secs.
if (time > 0) {
move();
time = 0;
}
}
private void move() {
/** Calculation **/
for (int row = map.length - 1; row >= 0; row--) {
for (int col = map.length - 1; col >= 0; col--) {
float x = (col - row) * (TILE_WIDTH / 2f - 2);
float y = (col + row) * (TILE_HEIGHT / 4f);
}
}
// after calculation, mapWidth is 525 pixels.
int mapWidth = map.length * (TILE_WIDTH / 2 - 2);
// after calculation, mapHeight is 280 pixels.
int mapHeight = map.length * (TILE_HEIGHT / 4);
// Calculate the minimum and maximum x-coordinates.
float minX = 0;
float maxX = mapWidth - TILE_WIDTH;
if (maxX < 0) {
maxX = 0;
}
// max-x coordinate is 491.0. min-x coordinate is 0.0.
// Calculate the minimum and maximum y-coordinates.
float minY = 0;
float maxY = mapHeight - TILE_HEIGHT;
if (maxY < 0) {
maxY = 0;
}
// check the position of human against map boundaries
if (pos.x < minX) {
pos.x = minX;
} else if (pos.x > maxX) {
pos.x = maxX;
}
if (pos.y < minY) {
pos.y = minY;
} else if (pos.y > maxY) {
pos.y = maxY;
}
// min-y coordinate is 0.0, max-y coordinate is 246.0.
// a variable to store a random generated value between 100 and 200.
int a = (int) (Math.random() * (max - min + 1) + min);
float newX = pos.x;
float newY = pos.y;
// move up
if (a <= 125) {
newX -= 15;
newY += 8.5;
}
// move down
else if (a <= 150 && a > 125) {
newX += 15;
newY -= 8.5;
}
// move left
else if (a <= 175 && a > 150) {
newX -= 15;
newY -= 8.5;
}
// move right
else if (a <= 200 && a > 175) {
newX += 15;
newY += 8.5;
}
if (newX >= minX && newX <= maxX && newY >= minY && newY <= maxY) {
pos.x = newX;
pos.y = newY;
}
}
public int[][] randomGenerator() {
Random r = new Random();
int Size = r.nextInt(35, 36);
int[][] map = new int[Size][Size];
for(int row = 0; row < map.length; row++) {
for(int col = 0; col < map.length; col++) {
int Number = r.nextInt(10);
if(Number == 0) {
map[row][col] = 0;
} else if (Number == 1) {
map[row][col] = 1;
}
else if (Number == 2) {
map[row][col] = 2;
}
else if (Number == 3) {
map[row][col] = 3;
}
else if (Number == 4) {
map[row][col] = 4;
}
else if (Number == 5) {
map[row][col] = 5;
}
else if (Number < 8) {
map[row][col] = 6;
}
else {
map[row][col] = 7;
}
}
}
map[0][0] = 1;
return map;
}
}
Based on the above, I am generating a random value which defines the movement of my 'humans' thus adding or subtracting from their x and y positions. In my attempt to tackle the problem of these sprites moving outside of my isometric map, I tried to calculate my mapWidth, mapHeight, min-x, max-x, min-y and max-x and then checking the position of my human against these boundaries to determine their movement.
Although these sprites now move within a constraint, it is not same dimension as my isometric map, but are now constrained to a rectangular-shaped boundary. How do I modify my code so that the sprites only move within the isometric map I have generated?
Below is a photo for visualisation.
The coordinate system you use for Human enties is ordinary orthogonal, you don't map these coordinates to an isometric view but you -have to- map them onto the isometric background. Screen coordinate system is basically different from the isometric one. When you render you need to map coordinates so that
(0,0)-> left corner
(0,maxY)->top corner
(maxX,maxY)->right corner
(maxX,0)->bottom corner
See here for to and back(map screen touch events to the map etc) conversion LibGdx render a sprite on top of a isometric tile
Let's first start off with what I am trying to do. I would like to be able to take PNG file with a transparent background and find anywhere from 90 to 360 points along the edge of the subject of the image. Here is a rough example of what I mean. Given this image of Mario and Yoshi:
I want to make a circle that is centered at the center of the image with a diameter slightly larger than the largest side of the image to serve as a reference. Then, I want to go around the circle at set intervals, and trace a line towards the center until it hits a non-transparent pixel. Here is what that would look like:
I have attempted to implement this a few different times, all of which failed, and I was hoping to get some guidance or insight as to what I am doing wrong. Here is an image of the math I am using behind the code (sorry if the quality is not great, I don't have a scanner):
The Line 1 is either the top, bottom, left or right edge of the image, and Line 2 goes through the center of the circle at the given angle. The point where lines 1 and 2 intersect should be on the edge of the image, and is where we should start looking for the edge of the image's subject.
Here is the code that I came up with from this idea. I did it in Java because BufferedImage is really easy to use, but I am going to translate this over to C# (XNA) for the final product.
public class Mesh {
private int angleA, angleB, angleC, angleD;
private BufferedImage image;
private Point center;
public ArrayList<Point> points = new ArrayList<>();
public Mesh(BufferedImage image) {
center = new Point(image.getWidth() / 2, image.getHeight() / 2);
angleA = (int) (Math.atan(center.y / center.x) * (180 / Math.PI));
angleB = 180 - angleA;
angleC = 180 + angleA;
angleD = 360 - angleA;
this.image = image;
for(int angle = 0; angle <= 360; angle+=4){
Point point = getNext(angle);
if(point != null) points.add(point);
}
}
private Point getNext(int angle) {
double radians = angle * Math.PI / 180;
double xStep = Math.cos(radians);
double yStep = Math.sin(radians);
int addX = angle >= 90 && angle <= 270 ? 1 : -1;
int addY = angle >= 0 && angle <= 180 ? 1 : -1;
double x, y;
if (xStep != 0) {
double slope = yStep / xStep;
double intercept = center.y - (slope * center.x);
if (angle >= angleA && angle <= angleB) {
y = 0;
x = -intercept / slope;
} else if (angle > angleB && angle < angleC) {
x = 0;
y = intercept;
} else if (angle >= angleC && angle <= angleD) {
y = image.getHeight() - 1;
x = (y - intercept) / slope;
} else {
x = image.getWidth() - 1;
y = slope * x + intercept;
}
} else {
x = center.x;
y = angle <= angleB ? 0 : image.getHeight();
}
if (x < 0) x = 0;
if (x > image.getWidth() - 1) x = image.getWidth() - 1;
if (y < 0) y = 0;
if (y > image.getHeight() - 1) y = image.getHeight() - 1;
double distance = Math.sqrt(Math.pow(x - center.x, 2) + Math.pow(y - center.y, 2));
double stepSize = Math.sqrt(Math.pow(xStep, 2) + Math.pow(yStep, 2));
int totalSteps = (int) Math.floor(distance / stepSize);
for (int step = 0; step < totalSteps; step++) {
int xVal = (int) x;
int yVal = (int) y;
if(xVal < 0) xVal = 0;
if(xVal > image.getWidth() -1) xVal = image.getWidth() -1;
if(yVal < 0) yVal = 0;
if(yVal > image.getHeight()-1) yVal = image.getHeight() -1;
int pixel = image.getRGB(xVal, yVal);
if ((pixel >> 24) == 0x00) {
x += (Math.abs(xStep) * addX);
y += (Math.abs(yStep) * addY);
} else {
return new Point(xVal, yVal);
}
}
return null;
}
}
The algorithm should be returning all positive points that are all ordered in counterclockwise rotation (and non-overlapping) but I have failed to get the desired output (this being my most recent attempt) so just to restate the question, is there a formalized way of doing this, or can someone find the mistake I made in my logic. For visual reference, the Mario and Yoshi Traced image is sort of what the final output should look like, but with many more points (which would give more detail to the mesh).
I have a question.
I'm not getting make the balls escape from the mouse pointer.
All balls go to the left corner when the mouse pointer enters the screen.
What am I doing wrong? Any tips??
My full code:
Java BounceBall mouse event
Or
http://ideone.com/vTGzb7
Method with problem:
public void move(Ball ball, Point mouse) {
try {
Point p = ball.getLocation();
Point speed = ball.getSpeed();
Dimension size = ball.getSize();
int vx = speed.x;
int vy = speed.y;
int x = p.x;
int y = p.y;
// ----------------------
if (mouse != null) {
int xDistance = Math.abs(x + size.width - mouse.x);
int yDistance = Math.abs(y + size.height - mouse.y);
if (xDistance < yDistance) {
if (x + size.width < mouse.x) {
if (vx > 0) {
vx *= -1;
}
} else {
if (vx > 0) {
vx *= -1;
}
}
} else {
if (y + size.height < mouse.y) {
if (vy > 0) {
vy *= -1;
}
} else {
if (vy > 0) {
vy *= -1;
}
}
}
}
// ----------------------
if (x + vx < 0 || x + size.width + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0
|| y + size.height + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
ball.setSpeed(new Point(vx, vy));
ball.setLocation(new Point(x, y));
} catch (Exception e) {
e.printStackTrace();
}
}
For some balls it works fine.
They hit in the mouse pointer and change your direction.
But the majority goes to the corner of the screen.
Thank You.
Problem Solved...
Problem: The bubbles were locked in the upper corner of the screen. And do not hit the mouse pointer.
Solution: I calculated the distance from the X and Y position relative to the bubble diameter and the mouse pointer. For collision.
int xDistance = Math.abs((x + (diameter / 2)) - mouse.x);
int yDistance = Math.abs((y + (diameter / 2)) - mouse.y);
Then calculated the X and Y radius of the bubbles.
int radiusX = (size.width / 2);
int radiusY = (size.height / 2);
Finally, I changed the IF to check the relationship between the distance of the bubble radius. Changing your direction.
if (xDistance <= radiusX && yDistance <= radiusY) {
if (xDistance < yDistance) {
vx *= -1;
} else {
vy *= -1;
}
System.out.println("Hit!");
}
New Move Method:
public void move(Ball ball, Point mouse) {
try {
Point p = ball.getLocation();
Point speed = ball.getSpeed();
Dimension size = ball.getSize();
int diameter = ball.dimeter;
int vx = speed.x;
int vy = speed.y;
int x = p.x;
int y = p.y;
int radiusX = (size.width / 2);
int radiusY = (size.height / 2);
// ----------------------
if (mouse != null) {
int xDistance = Math.abs((x + (diameter / 2)) - mouse.x);
int yDistance = Math.abs((y + (diameter / 2)) - mouse.y);
System.out.printf("b(%d, %d) m(%d, %d) dx(%d, %d)\n", x, y,
mouse.x, mouse.y, (x + vx) - mouse.x, (y + vy)
- mouse.y);
if (xDistance <= radiusX && yDistance <= radiusY) {
if (xDistance < yDistance) {
vx *= -1;
} else {
vy *= -1;
}
System.out.println("Hit");
}
}
if (x + vx < 0 || x + size.width + vx > getParent().getWidth()) {
vx *= -1;
}
if (y + vy < 0
|| y + size.height + vy > getParent().getHeight()) {
vy *= -1;
}
x += vx;
y += vy;
ball.setSpeed(new Point(vx, vy));
ball.setLocation(new Point(x, y));
} catch (Exception e) {
e.printStackTrace();
}
}
I have an OBB2D class based on SAT.
This is my point in OBB method:
public boolean pointInside(float x, float y)
{
float newy = (float) (Math.sin(angle) * (y - center.y) + Math.cos(angle) *
(x - center.x));
float newx = (float) (Math.cos(angle) * (x - center.x) - Math.sin(angle) *
(y - center.y));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
public boolean pointInside(Vector2D v)
{
return pointInside(v.x,v.y);
}
Here is the rest of the class; the parts that pertain:
public class OBB2D
{
private Vector2D projVec = new Vector2D();
private static Vector2D projAVec = new Vector2D();
private static Vector2D projBVec = new Vector2D();
private static Vector2D tempNormal = new Vector2D();
private Vector2D deltaVec = new Vector2D();
private ArrayList<Vector2D> collisionPoints = new ArrayList<Vector2D>();
// Corners of the box, where 0 is the lower left.
private Vector2D corner[] = new Vector2D[4];
private Vector2D center = new Vector2D();
private Vector2D extents = new Vector2D();
private RectF boundingRect = new RectF();
private float angle;
//Two edges of the box extended away from corner[0].
private Vector2D axis[] = new Vector2D[2];
private double origin[] = new double[2];
public OBB2D(float centerx, float centery, float w, float h, float angle)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(centerx,centery,w,h,angle);
}
public OBB2D(float left, float top, float width, float height)
{
for(int i = 0; i < corner.length; ++i)
{
corner[i] = new Vector2D();
}
for(int i = 0; i < axis.length; ++i)
{
axis[i] = new Vector2D();
}
set(left + (width / 2), top + (height / 2),width,height,0.0f);
}
public void set(float centerx,float centery,float w, float h,float angle)
{
float vxx = (float)Math.cos(angle);
float vxy = (float)Math.sin(angle);
float vyx = (float)-Math.sin(angle);
float vyy = (float)Math.cos(angle);
vxx *= w / 2;
vxy *= (w / 2);
vyx *= (h / 2);
vyy *= (h / 2);
corner[0].x = centerx - vxx - vyx;
corner[0].y = centery - vxy - vyy;
corner[1].x = centerx + vxx - vyx;
corner[1].y = centery + vxy - vyy;
corner[2].x = centerx + vxx + vyx;
corner[2].y = centery + vxy + vyy;
corner[3].x = centerx - vxx + vyx;
corner[3].y = centery - vxy + vyy;
this.center.x = centerx;
this.center.y = centery;
this.angle = angle;
computeAxes();
extents.x = w / 2;
extents.y = h / 2;
computeBoundingRect();
}
//Updates the axes after the corners move. Assumes the
//corners actually form a rectangle.
private void computeAxes()
{
axis[0].x = corner[1].x - corner[0].x;
axis[0].y = corner[1].y - corner[0].y;
axis[1].x = corner[3].x - corner[0].x;
axis[1].y = corner[3].y - corner[0].y;
// Make the length of each axis 1/edge length so we know any
// dot product must be less than 1 to fall within the edge.
for (int a = 0; a < axis.length; ++a)
{
float l = axis[a].length();
float ll = l * l;
axis[a].x = axis[a].x / ll;
axis[a].y = axis[a].y / ll;
origin[a] = corner[0].dot(axis[a]);
}
}
public void computeBoundingRect()
{
boundingRect.left = JMath.min(JMath.min(corner[0].x, corner[3].x), JMath.min(corner[1].x, corner[2].x));
boundingRect.top = JMath.min(JMath.min(corner[0].y, corner[1].y),JMath.min(corner[2].y, corner[3].y));
boundingRect.right = JMath.max(JMath.max(corner[1].x, corner[2].x), JMath.max(corner[0].x, corner[3].x));
boundingRect.bottom = JMath.max(JMath.max(corner[2].y, corner[3].y),JMath.max(corner[0].y, corner[1].y));
}
public void set(RectF rect)
{
set(rect.centerX(),rect.centerY(),rect.width(),rect.height(),0.0f);
}
// Returns true if other overlaps one dimension of this.
private boolean overlaps1Way(OBB2D other)
{
for (int a = 0; a < axis.length; ++a) {
double t = other.corner[0].dot(axis[a]);
// Find the extent of box 2 on axis a
double tMin = t;
double tMax = t;
for (int c = 1; c < corner.length; ++c) {
t = other.corner[c].dot(axis[a]);
if (t < tMin) {
tMin = t;
} else if (t > tMax) {
tMax = t;
}
}
// We have to subtract off the origin
// See if [tMin, tMax] intersects [0, 1]
if ((tMin > 1 + origin[a]) || (tMax < origin[a])) {
// There was no intersection along this dimension;
// the boxes cannot possibly overlap.
return false;
}
}
// There was no dimension along which there is no intersection.
// Therefore the boxes overlap.
return true;
}
public void moveTo(float centerx, float centery)
{
float cx,cy;
cx = center.x;
cy = center.y;
deltaVec.x = centerx - cx;
deltaVec.y = centery - cy;
for (int c = 0; c < 4; ++c)
{
corner[c].x += deltaVec.x;
corner[c].y += deltaVec.y;
}
boundingRect.left += deltaVec.x;
boundingRect.top += deltaVec.y;
boundingRect.right += deltaVec.x;
boundingRect.bottom += deltaVec.y;
this.center.x = centerx;
this.center.y = centery;
computeAxes();
}
// Returns true if the intersection of the boxes is non-empty.
public boolean overlaps(OBB2D other)
{
if(right() < other.left())
{
return false;
}
if(bottom() < other.top())
{
return false;
}
if(left() > other.right())
{
return false;
}
if(top() > other.bottom())
{
return false;
}
if(other.getAngle() == 0.0f && getAngle() == 0.0f)
{
return true;
}
return overlaps1Way(other) && other.overlaps1Way(this);
}
public Vector2D getCenter()
{
return center;
}
public float getWidth()
{
return extents.x * 2;
}
public float getHeight()
{
return extents.y * 2;
}
public void setAngle(float angle)
{
set(center.x,center.y,getWidth(),getHeight(),angle);
}
public float getAngle()
{
return angle;
}
public void setSize(float w,float h)
{
set(center.x,center.y,w,h,angle);
}
public float left()
{
return boundingRect.left;
}
public float right()
{
return boundingRect.right;
}
public float bottom()
{
return boundingRect.bottom;
}
public float top()
{
return boundingRect.top;
}
public RectF getBoundingRect()
{
return boundingRect;
}
public boolean overlaps(float left, float top, float right, float bottom)
{
if(right() < left)
{
return false;
}
if(bottom() < top)
{
return false;
}
if(left() > right)
{
return false;
}
if(top() > bottom)
{
return false;
}
return true;
}
public static float distance(float ax, float ay,float bx, float by)
{
if (ax < bx)
return bx - ay;
else
return ax - by;
}
public Vector2D project(float ax, float ay)
{
projVec.x = Float.MAX_VALUE;
projVec.y = Float.MIN_VALUE;
for (int i = 0; i < corner.length; ++i)
{
float dot = Vector2D.dot(corner[i].x,corner[i].y,ax,ay);
projVec.x = JMath.min(dot, projVec.x);
projVec.y = JMath.max(dot, projVec.y);
}
return projVec;
}
public Vector2D getCorner(int c)
{
return corner[c];
}
public int getNumCorners()
{
return corner.length;
}
public boolean pointInside(float x, float y)
{
float newy = (float) (Math.sin(angle) * (y - center.y) + Math.cos(angle) *
(x - center.x));
float newx = (float) (Math.cos(angle) * (x - center.x) - Math.sin(angle) *
(y - center.y));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
public boolean pointInside(Vector2D v)
{
return pointInside(v.x,v.y);
}
public ArrayList<Vector2D> getCollsionPoints(OBB2D b)
{
collisionPoints.clear();
for(int i = 0; i < corner.length; ++i)
{
if(b.pointInside(corner[i]))
{
collisionPoints.add(corner[i]);
}
}
for(int i = 0; i < b.corner.length; ++i)
{
if(pointInside(b.corner[i]))
{
collisionPoints.add(b.corner[i]);
}
}
return collisionPoints;
}
};
What could be wrong? When I getCollisionPoints for 2 OBBs I know are penetrating, it returns no points.
Thanks
I also tried:
public boolean pointInside(float x, float y)
{
float xx = (x - center.x);
float yy = (y - center.y);
float newx = (float) (xx * Math.cos(angle) - yy * Math.sin(angle));
float newy = (float) (xx * Math.sin(angle) + yy * Math.cos(angle));
return (newy > center.y - (getHeight() / 2)) &&
(newy < center.y + (getHeight() / 2))
&& (newx > center.x - (getWidth() / 2)) &&
(newx < center.x + (getWidth() / 2));
}
With no luck.
I didn't read all of your class, but I'm assuming angle is the angle by which you would need to rotate your rectangle clockwise in order to make it axis-aligned.
I believe that both sin(angle) * (y-center.y) and cos(angle) * (x-center.x) are equal to the distance between your center point and the point you're testing. So newy will always equal twice that distance, and newx will always equal 0.
This is how I prefer to rotate a point about another point: get the angle and distance between the two points, then apply the rotation to the angle, then calculate the new position from the angle and distance. In pseudocode:
//takes a point and rotates it `theta` angles
//counterclockwise around the given center point
function rotateAboutPoint(x,y, centerX, centerY, theta){
radius = sqrt((centerX-x)**2 + (centerY-y)**2) //`**` is the exponentiation operator
currentAngle = atan2(y-centerY, x-centerX) //prefer `atan2` over ordinary `atan` if you can get it
newAngle = currentAngle + theta
newX = centerX + radius*cos(newAngle)
newY = centerY + radius*sin(newAngle)
return (newX, newY)
}
function pointInside(x,y){
//point must be rotated clockwise, so we provide a negative angle
newX, newY = rotateAboutPoint(x,y,center.x, center.y, -angle)
return (
newY > center.y - (getHeight() / 2) &&
newY < center.y + (getHeight() / 2) &&
newX > center.x - (getHeight() / 2) &&
newX < center.x + (getHeight() / 2) &&
)
}
I have a standalone Java application below that is:
Generating a random line
Applied to a 2D grid where each cell value is the distance along the line perpindicular to the line
Finds the rise/run and attempts to calculate the original linear equation from the grid
Applies new line to another grid and prints out the greatest difference compared to the first grid
I expected the two grids to have identical values. The gradient lines may be different since the lines can extend outside the area of the grid, but should be similar and in two cases identical.
So is the problem a poor understanding of math, a bug in my code or a misunderstanding of floating point values?
import java.awt.geom.Point2D;
import java.awt.geom.Line2D;
import java.util.Iterator;
import java.util.ArrayList;
public final class TestGradientLine {
private static int SIZE = 3;
public TestGradientLine() {
super();
}
//y = mx + b
//b = y - mx
//m is rise / run = gradient
//width and height of bounding box
//for a box 10x10 then width and height are 9,9
public static Line2D getGradientLine(double run, double rise, double width, double height, double x, double y) {
if (run == 0 && rise == 0) {
return new Line2D.Double(x, y, x + width, y + height);
}
//calculate hypotenuse
//check for a vertical line
if (run == 0) {
return new Line2D.Double(x, y, x, y + height);
}
//check for a horizontal line
if (rise == 0) {
return new Line2D.Double(x, y, x + width, y);
}
//calculate gradient
double m = rise / run;
Point2D start;
Point2D opposite;
if (m < 0) {
//lower left
start = new Point2D.Double(x, y + height);
opposite = new Point2D.Double(x + width, y);
} else {
//upper left
start = new Point2D.Double(x, y);
opposite = new Point2D.Double(x + width, y + height);
}
double b = start.getY() - (m * start.getX());
//now calculate another point along the slope
Point2D next = null;
if (m > 0) {
next = new Point2D.Double(start.getX() + Math.abs(run), start.getY() + Math.abs(rise));
} else {
if (rise < 0) {
next = new Point2D.Double(start.getX() + run, start.getY() + rise);
} else {
next = new Point2D.Double(start.getX() - run, start.getY() - rise);
}
}
final double actualWidth = width;
final double actualHeight = height;
final double a = Math.sqrt((actualWidth * actualWidth) + (actualHeight * actualHeight));
extendLine(start, next, a);
Line2D gradientLine = new Line2D.Double(start, next);
return gradientLine;
}
public static void extendLine(Point2D p0, Point2D p1, double toLength) {
final double oldLength = p0.distance(p1);
final double lengthFraction =
oldLength != 0.0 ? toLength / oldLength : 0.0;
p1.setLocation(p0.getX() + (p1.getX() - p0.getX()) * lengthFraction,
p0.getY() + (p1.getY() - p0.getY()) * lengthFraction);
}
public static Line2D generateRandomGradientLine(int width, int height) {
//so true means lower and false means upper
final boolean isLower = Math.random() > .5;
final Point2D start = new Point2D.Float(0, 0);
if (isLower) {
//change origin for lower left corner
start.setLocation(start.getX(), height - 1);
}
//radius of our circle
double radius = Math.sqrt(width * width + height * height);
//now we want a random theta
//x = r * cos(theta)
//y = r * sin(theta)
double theta = 0.0;
if (isLower) {
theta = Math.random() * (Math.PI / 2);
} else {
theta = Math.random() * (Math.PI / 2) + (Math.PI / 2);
}
int endX = (int)Math.round(radius * Math.sin(theta));
int endY = (int)Math.round(radius * Math.cos(theta)) * -1;
if (isLower) {
endY = endY + (height - 1);
}
final Point2D end = new Point2D.Float(endX, endY);
extendLine(start, end, radius);
return new Line2D.Float(start, end);
}
public static Point2D getNearestPointOnLine(Point2D end, Line2D line) {
final Point2D point = line.getP1();
final Point2D start = line.getP2();
double a = (end.getX() - point.getX()) * (start.getX() - point.getX()) + (end.getY() - point.getY()) * (start.getY() - point.getY());
double b = (end.getX() - start.getX()) * (point.getX() - start.getX()) + (end.getY() - start.getY()) * (point.getY() - start.getY());
final double x = point.getX() + ((start.getX() - point.getX()) * a)/(a + b);
final double y = point.getY() + ((start.getY() - point.getY()) * a)/(a + b);
final Point2D result = new Point2D.Double(x, y);
return result;
}
public static double length(double x0, double y0, double x1, double y1) {
final double dx = x1 - x0;
final double dy = y1 - y0;
return Math.sqrt(dx * dx + dy * dy);
}
public static void main(String[] args) {
final Line2D line = generateRandomGradientLine(SIZE, SIZE);
System.out.println("we're starting with line " + line.getP1() + " " + line.getP2());
double[][] region = new double[SIZE][SIZE];
//load up the region with data from our generated line
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, line);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() + 1,
nearestPoint.getY() + 1);
region[x][y] = distance;
}
}
//now figure out what our line is from the region
double runTotal = 0;
double riseTotal = 0;
double runCount = 0;
double riseCount = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
if (x < SIZE - 1) {
runTotal += region[x + 1][y] - region[x][y];
runCount++;
}
if (y < SIZE - 1) {
riseTotal += region[x][y + 1] - region[x][y];
riseCount++;
}
}
}
double run = 0;
if (runCount > 0) {
run = runTotal / runCount;
}
double rise = 0;
if (riseCount > 0) {
rise = riseTotal / riseCount;
}
System.out.println("rise is " + rise + " run is " + run);
Line2D newLine = getGradientLine(run, rise, SIZE - 1, SIZE - 1, 0, 0);
System.out.println("ending with line " + newLine.getP1() + " " + newLine.getP2());
double worst = 0.0;
int worstX = 0;
int worstY = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, newLine);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() + 1,
nearestPoint.getY() + 1);
final double diff = Math.abs(region[x][y] - distance);
if (diff > worst) {
worst = diff;
worstX = x;
worstY = y;
}
}
}
System.out.println("worst is " + worst + " x: " + worstX + " y: " + worstY);
}
}
I think I have fixed your program.
a) I took out the integer cast.
b) I removed all the 'x + 1' and 'x - 1' fudges you had used.
I think when dealing with floats and doubles, subtracting '1' from the end of a line is a No-No! What is 1 anyway? - it's ok to do this just before you plot it on the screen once it's an integer. But not while calculating! line length is a 'zero-based' quantity.
This version returns approx 4E-16 always.
import java.awt.geom.Point2D;
import java.awt.geom.Line2D;
import java.awt.geom.QuadCurve2D;
import java.util.Iterator;
import java.util.ArrayList;
public final class TestGradientLine {
private static int SIZE = 3;
public TestGradientLine() {
super();
}
//y = mx + b
//b = y - mx
//m is rise / run = gradient
//width and height of bounding box
//for a box 10x10 then width and height are 9,9
public static Line2D getGradientLine(double run, double rise, double width, double height, double x, double y) {
if (run == 0 && rise == 0) {
return new Line2D.Double(x, y, x + width, y + height);
}
//calculate hypotenuse
//check for a vertical line
if (run == 0) {
return new Line2D.Double(x, y, x, y + height);
}
//check for a horizontal line
if (rise == 0) {
return new Line2D.Double(x, y, x + width, y);
}
//calculate gradient
double m = rise / run;
Point2D start;
Point2D opposite;
if (m < 0) {
//lower left
start = new Point2D.Double(x, y + height);
opposite = new Point2D.Double(x + width, y);
} else {
//upper left
start = new Point2D.Double(x, y);
opposite = new Point2D.Double(x + width, y + height);
}
double b = start.getY() - (m * start.getX());
//now calculate another point along the slope
Point2D next = null;
if (m > 0) {
next = new Point2D.Double(start.getX() + Math.abs(run), start.getY() + Math.abs(rise));
} else {
if (rise < 0) {
next = new Point2D.Double(start.getX() + run, start.getY() + rise);
} else {
next = new Point2D.Double(start.getX() - run, start.getY() - rise);
}
}
final double actualWidth = width;
final double actualHeight = height;
final double a = Math.sqrt((actualWidth * actualWidth) + (actualHeight * actualHeight));
extendLine(start, next, a);
Line2D gradientLine = new Line2D.Double(start, next);
return gradientLine;
}
public static void extendLine(Point2D p0, Point2D p1, double toLength) {
final double oldLength = p0.distance(p1);
final double lengthFraction =
oldLength != 0.0 ? toLength / oldLength : 0.0;
p1.setLocation(p0.getX() + (p1.getX() - p0.getX()) * lengthFraction,
p0.getY() + (p1.getY() - p0.getY()) * lengthFraction);
}
public static Line2D generateRandomGradientLine(int width, int height) {
//so true means lower and false means upper
final boolean isLower = Math.random() > .5;
final Point2D start = new Point2D.Float(0, 0);
if (isLower) {
//change origin for lower left corner
start.setLocation(start.getX(), height );
}
//radius of our circle
double radius = Math.sqrt(width * width + height * height);
//now we want a random theta
//x = r * cos(theta)
//y = r * sin(theta)
double theta = 0.0;
if (isLower) {
theta = Math.random() * (Math.PI / 2);
} else {
theta = Math.random() * (Math.PI / 2) + (Math.PI / 2);
}
float endX = (float)(radius * Math.sin(theta));
float endY = (float)(radius * Math.cos(theta)) * -1;
if (isLower) {
endY = endY + (height );
}
final Point2D end = new Point2D.Float(endX, endY);
extendLine(start, end, radius);
return new Line2D.Float(start, end);
}
public static Point2D getNearestPointOnLine(Point2D end, Line2D line) {
final Point2D point = line.getP1();
final Point2D start = line.getP2();
double a = (end.getX() - point.getX()) * (start.getX() - point.getX()) + (end.getY() - point.getY()) * (start.getY() - point.getY());
double b = (end.getX() - start.getX()) * (point.getX() - start.getX()) + (end.getY() - start.getY()) * (point.getY() - start.getY());
final double x = point.getX() + ((start.getX() - point.getX()) * a)/(a+b);
final double y = point.getY() + ((start.getY() - point.getY()) * a)/(a+b);
final Point2D result = new Point2D.Double(x, y);
return result;
}
public static double length(double x0, double y0, double x1, double y1) {
final double dx = x1 - x0;
final double dy = y1 - y0;
return Math.sqrt(dx * dx + dy * dy);
}
public static void main(String[] args) {
final Line2D line = generateRandomGradientLine(SIZE, SIZE);
System.out.println("we're starting with line " + line.getP1() + " " + line.getP2());
double[][] region = new double[SIZE][SIZE];
//load up the region with data from our generated line
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, line);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() ,
nearestPoint.getY() );
region[x][y] = distance;
}
}
//now figure out what our line is from the region
double runTotal = 0;
double riseTotal = 0;
double runCount = 0;
double riseCount = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
if (x < SIZE - 1) {
runTotal += region[x + 1][y] - region[x][y];
runCount++;
}
if (y < SIZE - 1) {
riseTotal += region[x][y + 1] - region[x][y];
riseCount++;
}
}
}
double run = 0;
if (runCount > 0) {
run = runTotal / runCount;
}
double rise = 0;
if (riseCount > 0) {
rise = riseTotal / riseCount;
}
System.out.println("rise is " + rise + " run is " + run);
Line2D newLine = getGradientLine(run, rise, SIZE, SIZE , 0, 0);
System.out.println("ending with line " + newLine.getP1() + " " + newLine.getP2());
double worst = 0.0;
int worstX = 0;
int worstY = 0;
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
final Point2D point = new Point2D.Double(x, y);
final Point2D nearestPoint = getNearestPointOnLine(point, newLine);
if (nearestPoint == null) {
System.err.println("uh -oh!");
return;
}
final double distance = length(line.getP1().getX(),
line.getP1().getY(), nearestPoint.getX() ,
nearestPoint.getY() );
final double diff = Math.abs(region[x][y] - distance);
if (diff > worst) {
worst = diff;
worstX = x;
worstY = y;
}
}
}
System.out.println("worst is " + worst + " x: " + worstX + " y: " + worstY);
}
}
why do you multiply by -1 at the end of this line?
int endY = (int)Math.round(radius * Math.cos(theta)) * -1;
this means that endY is always negative except radius is below 0. (cosinus always returns positive value)
is this intended or am i getting something wrong?
regards
You probably misunderstand float and/or double. This is a common problem with any language that implements the ieee spec for floats and doubles, which Java, C, C++ and just about every other language does.
Essentially
double val = 0;
for(int i=0;i<10;i++) {
val+=0.1;
System.out.println(val);
}
results in
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
And sometimes even worse. Either use BigDecimal, which alleviates a lot of the problem, or use integers.