I'm trying to get my head around this- and I've literally been looking for a whole day!
I think I understand the main concepts behind it, but I'm struggling to figure out the math I need to create the axis on which to project my shapes on to?
So if I have a rectange I find out each of the points and then use these to find the side of the shape edge = v(n) - v(n-1) and go through all sides.
But I don't know how to then create the separating axis.
The theorem is not difficult to understand: If you can find a line for which all points of shape A are on the one side, and all points of shape B are on the other (dot product positive or negative), that line is separating the shapes.
What do you want to do? Find separating lines for arbitrary shapes?
I would recommend to have a look at projective geometry, as the edge for two vertices of a polygon extended to infinity can be represented by the cross product of the two vertices (x, y, 1). For convex polygons you can simply create lines for all edges and then take the dot product of all vertices of your other polygon to check on which side they are. If for one edge all points are outside, that edge is a separating line.
Also keep in mind that by keeping the line normalized you get the distance of a point to the line using dot product. The sign identifies the side on which the point lies.
If your problem is more difficult, please explain it in more detail. Maybe you can use some sort of clipping to solve it fast.
Example: projective geometry
public double[] e2p(double x, double y) {
return new double[] { x, y, 1 };
}
// standard vector maths
public double[] getCrossProduct(double[] u, double[] v) {
return new double[] { u[1] * v[2] - u[2] * v[1],
u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0] };
}
public double getDotProduct(double[] u, double[] v) {
return u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
}
// collision check
public boolean isCollision(List<Point2D> coordsA, List<Point2D> coordsB) {
return !(isSeparate(pointsA, pointsB) || isSeparate(pointsB, pointsA));
}
// the following implementation expects the convex polygon's vertices to be in counter clockwise order
private boolean isSeparate(List<Point2D> coordsA, List<Point2D> coordsB) {
edges: for (int i = 0; i < coordsA.size(); i++) {
double[] u = e2p(coordsA.get(i).getX(), coordsA.get(i).getY());
int ni = i + 1 < coordsA.size() ? i + 1 : 0;
double[] v = e2p(coordsA.get(ni).getX(), coordsA.get(ni).getY());
double[] pedge = getCrossProduct(u, v);
for (Point2D p : coordsB) {
double d = getDotProduct(pedge, e2p(p.getX(), p.getY()));
if (d > -0.001) {
continue edges;
}
}
return true;
}
return false;
}
The separating axis is one of the sides. You can find the signs of the vertexes of the shape itself when plugged in the equation of this side:
(X - Xn).(Y - Yn-1) - (X - Xn-1).(Y - Yn) = 0
Check that the vertices of the other shape yield opposite signs.
Related
I am currently trying to put together an algorithm where I can know if there is an obstruction between two defined points in a plane.
Here is an example image.
We can see with the image that point 1, 2, 3, & 6 are all accessible from the origin point. Points 4 and 5 are not. You pass through the polygon.
The code I am using is the following. pStartPoint and pEndPoint is the line from the origin to the point in question. The function checks all edges to see if the line passes through the edge.
public double GetSlopeOfLine(Point a, Point b){
double x = b.y - a.y;
double y = b.x - a.x;
return (x / y);
}
public double GetOffsetOfLine(double x, double y, double slope){
return (y - (slope * x));
}
public boolean IsPointAccessable(Point pStartPoint, Point pEndPoint){
//Define the equation of the line for these points. Once we have slope and offset the equation is
//y = slope * x + offset;
double slopeOfLine = GetSlopeOfLine(pStartPoint, pEndPoint);
double offSet = GetOffsetOfLine(pStartPoint.x, pStartPoint.y, slopeOfLine);
//Collision detection for each side of each obstacle. Once we get the point of collision, does it lie on the
//line in between the two points? If so, collision, and I can't reach that point yet.
for (Iterator<Obstacles> ObstacleIt = AdjustedObstaclesList.iterator(); ObstacleIt.hasNext();) {
Obstacles pObstacle = ObstacleIt.next();
int NumberOfEdges = pObstacle.getPoints().size();
for(int i=0; i<NumberOfEdges; i++){
//Get Edge[i];
int index = i;
Point pFirstPoint = (Point)pObstacle.getPoints().get(index);
if(i >= NumberOfEdges - 1)
index = 0;
else
index = i+1;
Point pNextPoint = (Point)pObstacle.getPoints().get(index);
double slopeOfEdge = GetSlopeOfLine(pFirstPoint, pNextPoint);
double offsetEdge = GetOffsetOfLine(pNextPoint.x, pNextPoint.y, slopeOfEdge);
int x = Math.round((float) ((-offSet + offsetEdge) / (slopeOfLine - slopeOfEdge)));
int y = Math.round((float) ((slopeOfLine * x) + offSet));
//If it lies on either point I could be looking at two adjacent points. I can still reach that point.
if(x > pStartPoint.x && x < pEndPoint.x && y > pStartPoint.y && y < pEndPoint.y &&
x > pFirstPoint.x && x < pNextPoint.x && y > pFirstPoint.y && y < pNextPoint.y){
return false;
}
}
}
return true;
}
If the line passes through and the point where the lines cross is found between pStartPoint and pEndPoint I am assuming that pEndPoint cannot be reached.
This function is not working and I am wondering if it has something to do with the fact that the origin is not at the bottom left but at the top left and that (width, height) of my window is located in the bottom right. Therefore the coordinate plane is messed up.
My mind must be mush because I cannot think how to adjust for this and if that is truly my mistake as I cannot seem to fix the error. I thought adjusting the slope and offset by multiplying each by -1 might have been the solution but that doesn't seem to work.
Is my solution the right one? Does my code seem correct in checking for an intersect point? Is there a better solution to see if a point is accessible.
There is also going to be the next step after this where once I determine what points are accessible if I am now on one of the points of the polygon. For example, from point 1 what points are accessible without crossing into the polygon?
First, I would like to say that using slopes for this kind of task is do-able, but also difficult due to the fact that they are very volatile in the sense that they can go from negative infinity to infinity with a very small change in the point. Here's a slightly different algorithm, which relies on angles rather than slopes. Another advantage of using this is that the coordinate systems don't really matter here. It goes like this (I reused as much of your existing code as I could):
public boolean IsPointAccessable(Point pStartPoint, Point pEndPoint) {
//Collision detection for each side of each obstacle. Once we get the point of collision, does it lie on the
//line in between the two points? If so, collision, and I can't reach that point yet.
for (Iterator<Obstacles> ObstacleIt = AdjustedObstaclesList.iterator(); ObstacleIt.hasNext();) {
Obstacles pObstacle = ObstacleIt.next();
int NumberOfEdges = pObstacle.getPoints().size();
for(int i=0; i<NumberOfEdges; i++){
//Get Edge[i];
int index = i;
Point pFirstPoint = (Point)pObstacle.getPoints().get(index);
if(i >= NumberOfEdges - 1)
index = 0;
else
index = i+1;
Point pNextPoint = (Point)pObstacle.getPoints().get(index);
// Here is where we get a bunch of angles that encode in them important info on
// the problem we are trying to solve.
double angleWithStart = getAngle(pNextPoint, pFirstPoint, pStartPoint);
double angleWithEnd = getAngle(pNextPoint, pFirstPoint, pEndPoint);
double angleWithFirst = getAngle(pStartPoint, pEndPoint, pFirstPoint);
double angleWithNext = getAngle(pStartPoint, pEndPoint, pNextPoint);
// We have accumulated all the necessary angles, now we must decide what they mean.
// If the 'start' and 'end' angles are different signs, then the first and next points
// between them. However, for a point to be inaccessible, it also must be the case that
// the 'first' and 'next' angles are opposite sides, as then the start and end points
// Are between them so a blocking occurs. We check for that here using a creative approach
// This is a creative way of checking if two numbers are different signs.
if (angleWithStart * angleWithEnd <= 0 && angleWithFirst * angleWithNext <= 0) {
return false;
}
}
}
return true;
}
Now, all that is left to do is find a method that calculates the signed angle formed by three points. A quick google search yielded this method (from this SO question):
private double getAngle(Point previous, Point center, Point next) {
return Math.toDegrees(Math.atan2(center.x - next.x, center.y - next.y)-
Math.atan2(previous.x- center.x,previous.y- center.y));
}
Now, this method should work in theory (I am testing to be sure and will edit my answer if I find any issues with signs of angles or something like that). I hope you get the idea and that my comments explain the code well enough, but please leave a comment/question if you want me to elaborate further. If you don't understand the algorithm itself, I recommend getting a piece of paper out and following the algorithm to see what exactly is going on. Hope this helps!
EDIT: To hopefully aid in better understanding the solution using angles, I drew a picture with the four base cases of how the start, end, first, and next could be oriented, and have attached it to this question. Sorry for the sloppiness, I drew it rather quickly, but this should in theory make the idea clearer.
If you have a low segment count (for instance, your example only shows 12 segments for three shapes, two shapes of which we know we can ignore (because of bounding box checks), then I would recommend simply performing line/line intersection checking.
Point s = your selected point;
ArrayList<Point> points = polygon.getPoints();
ArrayList<Edge> edges = polygon.getEdges();
for(Point p: points) {
Line l = new Line(s, p);
for(Edge e: edges) {
Point i = e.intersects(l);
if (i != null) {
System.out.println("collision", i.toString());
}
}
}
With an intersects method that is pretty straight forward:
Point intersects(Line l) {
// boring variable aliassing:
double x1 = this.p1.x,
y1 = this.p1.y,
x2 = this.p2.x,
y2 = this.p2.y,
x3 = l.p1.x,
y2 = l.p1.y,
x3 = l.p2.x,
y2 = l.p2.y,
// actual line intersection algebra:
nx = (x1 * y2 - y1 * x2) * (x3 - x4) -
(x1 - x2) * (x3 * y4 - y3 * x4),
ny = (x1 * y2 - y1 * x2) * (y3 - y4) -
(y1 - y2) * (x3 * y4 - y3 * x4),
d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d == 0) return null;
return new Point(nx/d, ny/d);
}
So, I'm making a game where you are to outrun enemies. Instead of having the enemies move at only up, down, left, right, and at 45 degree angles, I want the enemy to take the shortest linear path towards the player. Here's my code:
public void moveEnemy() {
if (player.pos.x > enemy.pos.x) {
enemy.vel.x = 3;
}
if (player.pos.x < enemy.pos.x) {
enemy.vel.x = -3;
}
if (player.pos.y > enemy.pos.y) {
enemy.vel.y = 3;
}
if (player.pos.y < enemy.pos.y) {
enemy.vel.y = -3;
}
if (player.pos.x == enemy.pos.x) {
enemy.vel.x = 0;
}
if (player.pos.y == enemy.pos.y) {
enemy.vel.y = 0;
}
}
So, what this does is sets the velocity in cardinal directions. What could I do to make this more accurate?
Assuming you have the position of the player and enemy, and you want the enemy to always have a velocity of 3, then pull out your trigonometry textbook and do the following:
float h = Math.sqrt(Math.pow(enemy.pos.y-player.pos.y,2) + Math.pow(enemy.pos.x-player.pos.x,2));
float a = player.pos.x - enemy.pos.x;
float o = player.pos.y - enemy.pos.y;
enemy.vel.x = 3f*(a/h);
enemy.vel.y = 3f*(o/h);
What is this code doing, you ask? It's forming a triangle between the enemy and the player. You want the enemy to travel at 3 units/sec in the direction of the hypotenuse, so what you need to do is break that down into components that are parallel to the X and Y axes.
http://www.mathwords.com/s/sohcahtoa.htm
The floats h, a and o represent the hypotenuse, adjacent, and opposite sides of the triangle.
a/h is the velocity component parallel to the X axis.
o/h is the velocity component parallel to the y axis.
double spd=3;
double vX=player.pos.x-enemy.pos.x;
double vY=player.pos.y-enemy.pos.y;
double distance=Math.sqrt(vX*vX+vY*vY);
enemy.vel.x=vX/distance*spd;
enemy.vel.y=vY/distance*spd;
Calculates a vector pointing towards the position of palyer with a length of spd
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 );
I need help to calculate the tangets of a circle in 3D space, this is what I have so far
Tangents are represented by the blue lines, and this is the method I got from a friend to calculate them
Vec3D getTangentBetweenTwoPoint( Vec3D p1, Vec3D p2 ) {
Vec3D r = new Vec3D( p1.x - p2.x,
p1.y - p2.y,
p1.z - p2.z );
r.normalize();
return r;
}
void getTangents() {
Vec3D p0, p1;
for ( int i = 1; i < curve_length + 1; i++ ) {
p0 = points[i % curve_length];
p1 = points[(i+1) % curve_length];
tangents[i % curve_length] = getTangentBetweenTwoPoint( p0, p1 );
}
}
Any help will be much appreciated
Basically, you'd find the vector from the point you need the tangent for to the circle's center and take the cross product of that vector as well as the circle's normal (which you get by taking 2 points of the circle plus the center resulting in a plane equation).
If you normalize that cross product you get the normal/tangent vector for that point.
Replace i with i-1 in your code here:
p0 = points[(i-1) % curve_length];
I am assuming your points are equally spaced on the circle, so the line between the previous point and the next point will be parallel to the tangent at the current point.
Hey, I'm trying to write a method that takes a starting Cartesian coordinate(x,y) an angle (in degrees), a length and a number of sides and draws a shape to an applet. So far this is what I have but, I cant figure out what I'm doing wrong. I plan on using line transformations for the actual angle change and that's not written in yet but the logic for drawing a line at an angle should work but isn't as far as I can tell. Could I get a couple of new eyes to look at this and tell me if I'm missing something.
public void paint(Graphics g)
{
g.setColor(Color.BLACK);
Point startPt = new Point(0,0);
//Function in question
drawRegularPolygon(g, startPt, 5,60,50);
}
public static void drawRegularPolygon(Graphics g, Point2D startPoint, int numOfSides, int angle, int length)
{
Point2D current = startPoint;
for(int i=0; i<numOfSides; i++)
{
drawAngularLine(g, current, angle, length);
current = getEndPoint(current ,length,angle);
}
}
public static void drawAngularLine(Graphics g, Point2D startPoint, int angle, int length)
{
g.setColor(Color.BLACK);
Point2D endPoint = getEndPoint(startPoint, length, angle);
((Graphics2D) g).draw(new Line2D.Double(startPoint, endPoint));
}
private static Point2D getEndPoint(Point2D p, int length, int angle)
{
//Starting point you know (x1, x2),
//end point is (x1 + l * cos(ang), y1 + l * sin(ang))
//where l is the length and ang is the angle.
Point2D retVal = p;
double x = Math.cos(Math.toRadians(angle)*length+p.getX());
double y = Math.sin(Math.toRadians(angle)*length+p.getY());
retVal.setLocation(x,y);
return retVal;
}
A couple things. The first is to be careful about what you're taking sin/cosine of. It's not cos(angle*length) but rather length*cos(angle).
The second point is to think about coordinate systems. It might help to do the math assuming the initial point is (0,0), and then translate to the screen coordinates. This helps avoid the confusion of the y-axis seeming to be upside-down (values increase from top to bottom).
So assuming we just want a point that's length,angle away from the origin in a standard right-handed system, we'd get:
x1 = length * cos(angle)
y1 = length * sin(angle)
But since negative-y is up, we actually want
x2 = length * cos(angle)
y2 = -length * sin(angle)
To mentally check this, picture that you're doing this math at the origin (0,0) which is in the upper left, and have an angle of 45°. If y2 were positive, we'd end up seeing an angle that looks to us like -45°.
Now translate the origin to our starting point (x_i, y_i), to get our final values:
x_f = x_i + length * cos(angle)
y_f = y_i + (-length * cos(angle)) = y_i - length * cos(angle)
Alternatively, if it makes more sense to work in a standard right-handed coordinate system, you probably could get away with doing all the math as if (0,0) were in the center, and then applying a translation and a y-axis mirror transformation, but this screen coordinate system isn't too difficult to work within once you get used to flipping the y values around.
You are drawing a line with the same start point and end point - so nothing is drawn.
Java objects are passed by reference, so:
private static Point2D getEndPoint(Point2D p, int length, int angle){
Point2D retVal = p;
retVal.setLocation(x,y);
return retVal;
}
is also changing the starting point p. So it draws a line of length 1 (does it show a dot on the screen?).
Try using:
Point2D retVal = p.clone();