Rotating Asteroids ( Polygons )
I am trying to rotate asteroids(polygons) so that they look nice. I am doing this through multiple mathematical equations. To start I give the individual asteroid a rotation velocity:
rotVel = ((Math.random()-0.5)*Math.PI/16);
Then I create the polygon shape,
this.shape = new Polygon();
Followed by generating the points,
for (j = 0; j < s; j++) {
theta = 2 * Math.PI / s * j;
r = MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE));
x = (int) -Math.round(r * Math.sin(theta)) + asteroidData[0];
y = (int) Math.round(r * Math.cos(theta)) + asteroidData[1];
shape.addPoint(x, y);
}
Finally, in a loop a method is being called in which it attempts to move the polygon and its points down as well as rotating them. (I'm just pasting the rotating part as the other one is working)
for (int i = 0; i < shape.npoints; i++) {
// Subtract asteroid's x and y position
double x = shape.xpoints[i] - asteroidData[0];
double y = shape.ypoints[i] - asteroidData[1];
double temp_x = ((x * Math.cos(rotVel)) - (y * Math.sin(rotVel)));
double temp_y = ((x * Math.sin(rotVel)) + (y * Math.cos(rotVel)));
shape.xpoints[i] = (int) Math.round(temp_x + asteroidData[0]);
shape.ypoints[i] = (int) Math.round(temp_y + asteroidData[1]);
}
now, the problem is that when it prints to the screen the asteroids appear to 'warp' or rather the x and y positions on some of the polygon points 'float' off course.
I've noticed that when I make 'rotVel' be a whole number the problem is solved however the asteroid will rotate at mach speeds. So I've concluded that the problem has to be in the rounding but no matter what I do I can't seem to find a way to get it to work as the Polygon object requires an array of ints.
Does anyone know how to fix this?
Currently your asteroids rotate around (0 , 0) as far as i can see. Correct would be to rotate them around the center of the shape, which would be (n , m), where n is the average of all x-coordinates of the shape, and m is the average of all y-coordinates of the shape.
Your problem is definitely caused by rounding to int! The first improvement is to make all shape coordinates to be of type double. This will solve most of your unwanted 'effects'.
But even with double you might experience nasty rounding errors in case you do a lot of very small updates of the coordinates. The solution is simple: Just avoid iterative updates of the asteroid points. Every time, you update the coordinates based on the previous coordinates, the rounding error will get worse.
Instead, add a field for the rotation angle to the shape and increment it instead of the points themselves. Not until drawing the shape, you compute the final positions by applying the rotation to the points. Note that this will never change the points themselves.
You can extend this concept to other transformations (e.g. translation) too. What you get is some kind of local coordinate system for every shape/object. The points of the shape are defined in the local coordinate system. By moving and rotating this system, you can reposition the entire object anywhere in space.
public class Shape {
// rotation and position of the local coordinate system
private double rot, x, y;
// points of the shape in local coordinate system
private double[] xp, yp;
private int npoints;
// points of the shape in world coordinates
private int[][] wxp, wyp;
private boolean valid;
public void setRotation(double r) { this.rot = r; valid = false; }
public void setPosition(double x, double y) { this.x = x; this.y = y; valid = false; }
public void addPoint(double x, double y) {
// TODO: add point to xp, yp
valid = false;
}
public void draw(...) {
if (!valid) {
computeWorldCoordinates(wxp, wyp);
valid = true;
}
// TODO: draw shape at world coordaintes wxp and wyp
}
protected void computeWorldCoordinates(int[] xcoord, int[] ycoord) {
for (int i = 0; i < npoints; i++) {
double temp_x = xp[i] * Math.cos(rot) - yp[i] * Math.sin(rot);
double temp_y = xp[i] * Math.sin(rot) + yp[i] * Math.cos(rot);
xcoord[i] = (int) Math.round(x + temp_x);
ycoord[i] = (int) Math.round(y + temp_y);
}
}
}
Related
I am trying to get all positions in a radius from a 3 dimensional world(In this case the game Minecraft) this is the current code I use.
public static List<BlockPos> getBlocksInRadius(double radius) {
List<BlockPos> circleblocks = new ArrayList<>();
int centralx = mc.player.posX;
int centraly = mc.player.posY;
int centralz = mc.player.posZ;
for (int x = centralx - radius; x <= centralx + radius; x++) {
for (int z = centralz - radius; z <= centralz + radius; z++) {
for (int y = centraly - radius; y < centraly + radius; y++) {
double dist = mc.player.getDistance(x, y, z);
if (dist < radius) {
BlockPos l = new BlockPos(x, y, z);
circleblocks.add(l);
}
}
}
}
return circleblocks;
}
This method goes from the x coord farthest away and keeps coming closer to the player. I want it to iterate it by starting at central x,y,z and then increase distance from the player. This is to make it easier to find block x closest to player. Any help would be apreciated!
Depending on how large of a radius you have, you might try the static method BlockPos::getAllInBox. There doesn't seem to be any official documentation on it, but it looks like it takes two BlockPos parameters and returns an Iterable<BlockPos>. It finds all the blocks in a cube in between the two parameters, so you probably want to center it on the player.
Here's what I would do. This code hasn't been tested, and you might need to adapt it for all of the 1.14 and 1.13 changes, but the theory should be the same, with just name changes.
BlockPos playerPos = player.getPosition(); // Or some other method of getting a BlockPos of the player
positiveRadiusPosition = playerPos.add(radius, radius, radius); // Gets one corner of the cube in the positive X, Y, and Z direction
negativeRadiusPosition = playerPos.add(-1 * radius, -1 * radius, -1 * radius); // Gets the opposite corner
Iterable<BlockPos> cubeResult = BlockPos.getAllInBox(positiveRadiusPosition, negativeRadiusPosition);
for (BlockPos pos: cubeResult) {
// cubeResult will contain blocks that are outside of the sphere with the
// radius you want. If that's okay, cool! If that's not okay, you should
// check each pos' distance from the player. If it's outside of the radius,
// remove it from the list.
}
Now you need to figure out which block is closest. The method I would use would be to use a Comparator to sort the Iterable, which is copied into a List. For reference:
public static Iterator sortedIterator(Iterator it, Comparator comparator) {
List list = new ArrayList();
while (it.hasNext()) {
list.add(it.next());
}
Collections.sort(list, comparator);
return list.iterator();
}
In the Comparator, you should check the distance from the player to each block.
public static double getDistanceToEntity(Entity entity, BlockPos pos) {
double deltaX = entity.posX - pos.getX();
double deltaY = entity.posY - pos.getY();
double deltaZ = entity.posZ - pos.getZ();
return Math.sqrt((deltaX * deltaX) + (deltaY * deltaY) + (deltaZ * deltaZ));
}
Of course, this method doesn't actually start at the player and work outwards. It's just a cleaner and expanded version of your original method that should do what you want. If you are working with a very large radius, it's probably not a good idea to use this, as you'll have to work with the entire cube.
I am looking for some help with some game code i have inherited from a flight sim. The code below simulates bombs exploding on the ground, it works fine but i am trying to refine it.
At the moment it takes a random value for x and y as a start point and then adds another random value between -20 and 20 to this. It works ok, but doesn't simulate bombs dropping very well as the pattern does not lay along a straight line/
What i would like to achieve though is all x and y points after the first random values, to lay along a straight line, so that the effects called for all appear to lay in a line. It doesn't matter which way the line is orientated.
Thanks for any help
slipper
public static class BombUnit extends CandCGeneric
{
public boolean danger()
{
Point3d point3d = new Point3d();
pos.getAbs(point3d);
Vector3d vector3d = new Vector3d();
Random random = new Random();
Aircraft aircraft = War.GetNearestEnemyAircraft(this, 10000F, 9);
if(counter > 10)
{
counter = 0;
startpoint.set(point3d.x + (double)(random.nextInt(1000) - 500), point3d.y + (double)(random.nextInt(1000) - 500), point3d.z);
}
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
World.MaxVisualDistance = 50000F;
counter++;
String s = "weapon.bomb_std";
startpoint.x += random.nextInt(40) - 20;
startpoint.y += random.nextInt(40) - 20;
Explosions.generate(this, startpoint, 7F, 0, 30F, !Mission.isNet());
startpoint.z = World.land().HQ(startpoint.x, startpoint.y);
MsgExplosion.send(this, s, startpoint, getOwner(), 0.0F, 7F, 0, 30F);
Engine.land();
int i = Landscape.getPixelMapT(Engine.land().WORLD2PIXX(startpoint.x), Engine.land().WORLD2PIXY(startpoint.y));
if(firecounter < 100 && i >= 16 && i < 20)
{
Eff3DActor.New(null, null, new Loc(startpoint.x, startpoint.y, startpoint.z + 5D, 0.0F, 90F, 0.0F), 1.0F, "Effects/Smokes/CityFire3.eff", 300F);
firecounter++;
}
super.setTimer(15);
}
return true;
}
private static Point3d startpoint = new Point3d();
private int counter;
private int firecounter;
public BombUnit()
{
counter = 11;
firecounter = 0;
Timer1 = Timer2 = 0.05F;
}
}
The code in the question is a mess, but ignoring this and trying to focus on the relevant parts: You can generate a random position for the first point, and a random direction, and then walk along this direction in several steps.
(This still raises the question of whether the direction is really not important. Wouldn't it matter if only the first bomb was dropped in the "valid" area, and the remaining ones outside of the screen?)
However, the relevant code could roughly look like this:
class Bombs
{
private final Random random = new Random(0);
int getScreenSizeX() { ... }
int getScreenSizeY() { ... }
// Method to drop a single bomb at the given position
void dropBombAt(double x, double y) { ... }
void dropBombs(int numberOfBombs, double distanceBetweenBombs)
{
// Create a random position in the screen
double currentX = random.nextDouble() * getScreenSizeX();
double currentY = random.nextDouble() * getScreenSizeY();
// Create a random step size
double directionX = random.nextDouble();
double directionY = random.nextDouble();
double invLength = 1.0 / Math.hypot(directionX, directionY);
double stepX = directionX * invLength * distanceBetweenBombs;
double stepY = directionY * invLength * distanceBetweenBombs;
// Drop the bombs
for (int i=0; i<numberOfBombs; i++)
{
dropBombAt(currentX, currentY);
currentX += stepX;
currentY += stepY;
}
}
}
I am assuming your startpoint is a StartPoint class with x,y,z coordinates as integers in it.
I hope I have understood your problem correctly. It looks like you either want to create a vertical explosion or a horizontal explosion. Since an explosion always occurs on ground, the z coordinate will be zero. Now you can vary one of x or y coordinate to give you a random explosion along a straight line. Whether you choose x or y could be fixed or could be randomized itself. A potential randomized solution below:
public boolean danger() {
// stuff
int orientation = Random.nextInt(2);
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(orientation, startPoint);
// stuff
}
}
StartPoint randomizeStartPoint(int orientation, StartPoint startPoint) {
if(orientation == 0) {
startPoint.x += random.nextInt(40) - 20;
}
else {
startPoint.y += random.nextInt(40) - 20;
}
return startPoint;
}
In response to the image you uploaded, it seems that the orientation of the explosion need not necessarily be horizontal or vertical. So the code I posted above gives a limited solution to your problem.
Since you want any random straight line, your problem boils down to two sub parts:
1. Generate a random straight line equation.
2. Generate random point along this line.
Now, a straight line equation in coordinate geometry is y = mx + c where m is the slope and c is the constant where the line crosses the y-axis. The problem with c is that it gives rise to irrational coordinates. I am assuming you are looking for integer coordinates only, since this will ensure that your points are accurately plotted. (You could do with rational fractions, but then a fraction like 1/3 will still result in loss of accuracy). The best way to get rid of this irrational problem is to get rid of c. So now your straight line always looks like y = mx. So for step one, you have to generate a random m.
Then for step 2, you can either generate a random x or random y. It doesn't matter which one, since either one will result in random coordinates.
Here is a possible code for the solution:
int generateRandomSlope() {
return Random.nextInt(100); // arbitrarily chose 100.
}
int randomizeStartPoint(int m, StartPoint startPoint) { // takes the slope we generated earlier. without the slope, your points will never be on a straight line!
startPoint.x += random.nextInt(40) - 20;
startPoint.y += x * m; // because a line equation is y = mx
return startPoint;
}
public boolean danger() {
// stuff
int m = generateRandomSlope(); // you may want to generate this elsewhere so that it doesn't change each time danger() is called.
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(m, startPoint);
// stuff
}
}
Again, this is not a complete or the best solution.
I am currently working on making a screensaver and I want my ellipse to slowly transform to a rectangle in java. What is the easiest way of doing that?
There are some shapes that are easy to transform into one another. For instance a square is a rectangle with equal side lengths, a circle is an ellipse with equal axes. So it is easy to transform a square into a rectangle since you can just use some drawrectangle function and adjust the parameters the whole way. Ditto for circle to ellipse.
squaretorect(double width,double height)
{
//Transform a square width * width to a rectangle width * height
int n = 100;//Number of intermediate points
int i;
double currentheight;
for(i=0;i<n;i++)
{
currentheight = width + (height-width) * i/(n-1);
drawrectangle(width,currentheight);
}
}
Transforming from a rectangle to an ellipse is harder, since in between the shape is neither a rectangle nor an ellipse. It may be that there is some more general object which can be either a rectangle, an ellipse, or something in between, but I cannot think of one.
So, the easy way is out, but there is a harder way to do it. Suppose if I divide the unit circle into N pieces and write points on an ellipse Ei and a rectangle Ri. Now as the transformation happens the points Ei move into the points Ri. A simple way to do this is to use a linear combination.
Ti = (1-v) * Ei + v * Ri
So to do the transformation we slowly increment v from 0 to 1. And we draw lines(or better yet interpolate) between the points Ti.
ellipsetorectangle(double a, double b, double w, double h)
{
//(x/a)^2+(y/b)^2 = 1
//Polar r = 1/sqrt(cos(phi)^2/a^2 + sin(phi)^2/b^2)
int N = 1000;
int i;
double phi; double r;
double phirect = atan(w/h);//Helps determine which of the 4 line segments we are on
ArrayList<Point> Ei;
ArrayList<Point> Ri;
for(i=0;i<N;i++)
{
//Construct ellipse
phi = 2PI * (double)i/N;
r = 1/sqrt(cos(phi)^2/a^2 + sin(phi)^2/b^2);
Ei.add(new Point(r * cos(phi),r * sin(phi));
//Construct Rectangle (It's hard)
if(phi > 2Pi - phirect || phi < phirect)
{Ri.add(new Point(w/2,w/2 * tan(phi)));}
else if(phi > phirect)
{Ri.add(new Point(h/2 * tan(phi),h/2));}
else if(phi > PI-phirect)
{Ri.add(new Point(-w/2,-w/2 * tan(phi)));}
else if(phi > PI+phirect)
{Ri.add(new Point(-h/2,-h/2 * tan(phi)));}
}
}
Arraylist<Point> Ti;
int transitionpoints = 100;
double v;
int j;
for(j=0;j<transitionpoints;j++)
{
//This outer loop represents one instance of the object. You should probably clear the picture here. This probably belongs in a separate function but it would take awhile to write it that way.
for(i=0;i<N;i++)
{
v = (double)1 * j/(N-1);
Ti = new Point(v * Ri.get(i).getx + (1-v) * Ei.get(i).getx,
v * Ri.get(i).gety + (1-v) * Ei.get(i).gety);
if(i != 0)
drawline(Ti,Tiold);
Tiold = Ti;
}
}
Is there a reason that they decided not to add the contains method (for Path) in Android?
I'm wanting to know what points I have in a Path and hoped it was easier than seen here:
How can I tell if a closed path contains a given point?
Would it be better for me to create an ArrayList and add the integers into the array? (I only check the points once in a control statement) Ie. if(myPath.contains(x,y)
So far my options are:
Using a Region
Using an ArrayList
Extending the Class
Your suggestion
I'm just looking for the most efficient way I should go about this
I came up against this same problem a little while ago, and after some searching, I found this to be the best solution.
Java has a Polygon class with a contains() method that would make things really simple. Unfortunately, the java.awt.Polygonclass is not supported in Android. However, I was able to find someone who wrote an equivalent class.
I don't think you can get the individual points that make up the path from the Android Path class, so you will have to store the data in a different way.
The class uses a Crossing Number algorithm to determine whether or not the point is inside of the given list of points.
/**
* Minimum Polygon class for Android.
*/
public class Polygon
{
// Polygon coodinates.
private int[] polyY, polyX;
// Number of sides in the polygon.
private int polySides;
/**
* Default constructor.
* #param px Polygon y coods.
* #param py Polygon x coods.
* #param ps Polygon sides count.
*/
public Polygon( int[] px, int[] py, int ps )
{
polyX = px;
polyY = py;
polySides = ps;
}
/**
* Checks if the Polygon contains a point.
* #see "http://alienryderflex.com/polygon/"
* #param x Point horizontal pos.
* #param y Point vertical pos.
* #return Point is in Poly flag.
*/
public boolean contains( int x, int y )
{
boolean oddTransitions = false;
for( int i = 0, j = polySides -1; i < polySides; j = i++ )
{
if( ( polyY[ i ] < y && polyY[ j ] >= y ) || ( polyY[ j ] < y && polyY[ i ] >= y ) )
{
if( polyX[ i ] + ( y - polyY[ i ] ) / ( polyY[ j ] - polyY[ i ] ) * ( polyX[ j ] - polyX[ i ] ) < x )
{
oddTransitions = !oddTransitions;
}
}
}
return oddTransitions;
}
}
I would just like to comment on #theisenp answer: The code has integer arrays and if you look on the algorithm description webpage it warns against using integers instead of floating point.
I copied your code above and it seemed to work fine except for some corner cases when I made lines that didnt connect to themselves very well.
By changing everything to floating point, I got rid of this bug.
Tried the other answer, but it gave an erroneous outcome for my case. Didn't bother to find the exact cause, but made my own direct translation from the algorithm on:
http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Now the code reads:
/**
* Minimum Polygon class for Android.
*/
public class Polygon
{
// Polygon coodinates.
private int[] polyY, polyX;
// Number of sides in the polygon.
private int polySides;
/**
* Default constructor.
* #param px Polygon y coods.
* #param py Polygon x coods.
* #param ps Polygon sides count.
*/
public Polygon( int[] px, int[] py, int ps )
{
polyX = px;
polyY = py;
polySides = ps;
}
/**
* Checks if the Polygon contains a point.
* #see "http://alienryderflex.com/polygon/"
* #param x Point horizontal pos.
* #param y Point vertical pos.
* #return Point is in Poly flag.
*/
public boolean contains( int x, int y )
{
boolean c = false;
int i, j = 0;
for (i = 0, j = polySides - 1; i < polySides; j = i++) {
if (((polyY[i] > y) != (polyY[j] > y))
&& (x < (polyX[j] - polyX[i]) * (y - polyY[i]) / (polyY[j] - polyY[i]) + polyX[i]))
c = !c;
}
return c;
}
}
For completeness, I want to make a couple notes here:
As of API 19, there is an intersection operation for Paths. You could create a very small square path around your test point, intersect it with the Path, and see if the result is empty or not.
You can convert Paths to Regions and do a contains() operation. However Regions work in integer coordinates, and I think they use transformed (pixel) coordinates, so you'll have to work with that. I also suspect that the conversion process is computationally intensive.
The edge-crossing algorithm that Hans posted is good and quick, but you have to be very careful for certain corner cases such as when the ray passes directly through a vertex, or intersects a horizontal edge, or when round-off error is a problem, which it always is.
The winding number method is pretty much fool proof, but involves a lot of trig and is computationally expensive.
This paper by Dan Sunday gives a hybrid algorithm that's as accurate as the winding number but as computationally simple as the ray-casting algorithm. It blew me away how elegant it was.
My code
This is some code I wrote recently in Java which handles a path made out of both line segments and arcs. (Also circles, but those are complete paths on their own, so it's sort of a degenerate case.)
package org.efalk.util;
/**
* Utility: determine if a point is inside a path.
*/
public class PathUtil {
static final double RAD = (Math.PI/180.);
static final double DEG = (180./Math.PI);
protected static final int LINE = 0;
protected static final int ARC = 1;
protected static final int CIRCLE = 2;
/**
* Used to cache the contents of a path for pick testing. For a
* line segment, x0,y0,x1,y1 are the endpoints of the line. For
* a circle (ellipse, actually), x0,y0,x1,y1 are the bounding box
* of the circle (this is how Android and X11 like to represent
* circles). For an arc, x0,y0,x1,y1 are the bounding box, a1 is
* the start angle (degrees CCW from the +X direction) and a1 is
* the sweep angle (degrees CCW).
*/
public static class PathElement {
public int type;
public float x0,y0,x1,y1; // Endpoints or bounding box
public float a0,a1; // Arcs and circles
}
/**
* Determine if the given point is inside the given path.
*/
public static boolean inside(float x, float y, PathElement[] path) {
// Based on algorithm by Dan Sunday, but allows for arc segments too.
// http://geomalgorithms.com/a03-_inclusion.html
int wn = 0;
// loop through all edges of the polygon
// An upward crossing requires y0 <= y and y1 > y
// A downward crossing requires y0 > y and y1 <= y
for (PathElement pe : path) {
switch (pe.type) {
case LINE:
if (pe.x0 < x && pe.x1 < x) // left
break;
if (pe.y0 <= y) { // start y <= P.y
if (pe.y1 > y) { // an upward crossing
if (isLeft(pe, x, y) > 0) // P left of edge
++wn; // have a valid up intersect
}
}
else { // start y > P.y
if (pe.y1 <= y) { // a downward crossing
if (isLeft(pe, x, y) < 0) // P right of edge
--wn; // have a valid down intersect
}
}
break;
case ARC:
wn += arcCrossing(pe, x, y);
break;
case CIRCLE:
// This should be the only element in the path, so test it
// and get out.
float rx = (pe.x1-pe.x0)/2;
float ry = (pe.y1-pe.y0)/2;
float xc = (pe.x1+pe.x0)/2;
float yc = (pe.y1+pe.y0)/2;
return (x-xc)*(x-xc)/rx*rx + (y-yc)*(y-yc)/ry*ry <= 1;
}
}
return wn != 0;
}
/**
* Return >0 if p is left of line p0-p1; <0 if to the right; 0 if
* on the line.
*/
private static float
isLeft(float x0, float y0, float x1, float y1, float x, float y)
{
return (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);
}
private static float isLeft(PathElement pe, float x, float y) {
return isLeft(pe.x0,pe.y0, pe.x1,pe.y1, x,y);
}
/**
* Determine if an arc segment crosses the test ray up or down, or not
* at all.
* #return winding number increment:
* +1 upward crossing
* 0 no crossing
* -1 downward crossing
*/
private static int arcCrossing(PathElement pe, float x, float y) {
// Look for trivial reject cases first.
if (pe.x1 < x || pe.y1 < y || pe.y0 > y) return 0;
// Find the intersection of the test ray with the arc. This consists
// of finding the intersection(s) of the line with the ellipse that
// contains the arc, then determining if the intersection(s)
// are within the limits of the arc.
// Since we're mostly concerned with whether or not there *is* an
// intersection, we have several opportunities to punt.
// An upward crossing requires y0 <= y and y1 > y
// A downward crossing requires y0 > y and y1 <= y
float rx = (pe.x1-pe.x0)/2;
float ry = (pe.y1-pe.y0)/2;
float xc = (pe.x1+pe.x0)/2;
float yc = (pe.y1+pe.y0)/2;
if (rx == 0 || ry == 0) return 0;
if (rx < 0) rx = -rx;
if (ry < 0) ry = -ry;
// We start by transforming everything so the ellipse is the unit
// circle; this simplifies the math.
x -= xc;
y -= yc;
if (x > rx || y > ry || y < -ry) return 0;
x /= rx;
y /= ry;
// Now find the points of intersection. This is simplified by the
// fact that our line is horizontal. Also, by the time we get here,
// we know there *is* an intersection.
// The equation for the circle is x²+y² = 1. We have y, so solve
// for x = ±sqrt(1 - y²)
double x0 = 1 - y*y;
if (x0 <= 0) return 0;
x0 = Math.sqrt(x0);
// We only care about intersections to the right of x, so
// that's another opportunity to punt. For a CCW arc, The right
// intersection is an upward crossing and the left intersection
// is a downward crossing. The reverse is true for a CW arc.
if (x > x0) return 0;
int wn = arcXing1(x0,y, pe.a0, pe.a1);
if (x < -x0) wn -= arcXing1(-x0,y, pe.a0, pe.a1);
return wn;
}
/**
* Return the winding number of the point x,y on the unit circle
* which passes through the arc segment defined by a0,a1.
*/
private static int arcXing1(double x, float y, float a0, float a1) {
double a = Math.atan2(y,x) * DEG;
if (a < 0) a += 360;
if (a1 > 0) { // CCW
if (a < a0) a += 360;
return a0 + a1 > a ? 1 : 0;
} else { // CW
if (a0 < a) a0 += 360;
return a0 + a1 <= a ? -1 : 0;
}
}
}
Edit: by request, adding some sample code that makes use of this.
import PathUtil;
import PathUtil.PathElement;
/**
* This class represents a single geographic area defined by a
* circle or a list of line segments and arcs.
*/
public class Area {
public float lat0, lon0, lat1, lon1; // bounds
Path path = null;
PathElement[] pathList;
/**
* Return true if this point is inside the area bounds. This is
* used to confirm touch events and may be computationally expensive.
*/
public boolean pointInBounds(float lat, float lon) {
if (lat < lat0 || lat > lat1 || lon < lon0 || lon > lon1)
return false;
return PathUtil.inside(lon, lat, pathList);
}
static void loadBounds() {
int n = number_of_elements_in_input;
path = new Path();
pathList = new PathElement[n];
for (Element element : elements_in_input) {
PathElement pe = new PathElement();
pathList[i] = pe;
pe.type = element.type;
switch (element.type) {
case LINE: // Line segment
pe.x0 = element.x0;
pe.y0 = element.y0;
pe.x1 = element.x1;
pe.y1 = element.y1;
// Add to path, not shown here
break;
case ARC: // Arc segment
pe.x0 = element.xmin; // Bounds of arc ellipse
pe.y0 = element.ymin;
pe.x1 = element.xmax;
pe.y1 = element.ymax;
pe.a0 = a0; pe.a1 = a1;
break;
case CIRCLE: // Circle; hopefully the only entry here
pe.x0 = element.xmin; // Bounds of ellipse
pe.y0 = element.ymin;
pe.x1 = element.xmax;
pe.y1 = element.ymax;
// Add to path, not shown here
break;
}
}
path.close();
}
OK, so I'm trying to make a simple asteroids clone. Everything works fine, except for the collision detection.
I have two different versions, the first one uses java.awt.geom.Area:
// polygon is a java.awt.Polygon and p is the other one
final Area intersect = new Area();
intersect.add(new Area(polygon));
intersect.intersect(new Area(p.polygon));
return !intersect.isEmpty();
This works like a charm... if you don't care about 40% CPU for only 120 asteroids :(
So I searched the net for the famous separating axis theorem, since I'm not thaaaaaat good a the math I took the implementation from here and converted it to fit my Java needs:
public double dotProduct(double x, double y, double dx, double dy) {
return x * dx + y * dy;
}
public double IntervalDistance(double minA, double maxA, double minB,
double maxB) {
if (minA < minB) {
return minB - maxA;
} else {
return minA - maxB;
}
}
public double[] ProjectPolygon(double ax, double ay, int p, int[] x, int[] y) {
double dotProduct = dotProduct(ax, ay, x[0], y[0]);
double min = dotProduct;
double max = dotProduct;
for (int i = 0; i < p; i++) {
dotProduct = dotProduct(x[i], y[i], ax, ay);
if (dotProduct < min) {
min = dotProduct;
} else if (dotProduct > max) {
max = dotProduct;
}
}
return new double[] { min, max };
}
public boolean PolygonCollision(Asteroid ast) {
int edgeCountA = points;
int edgeCountB = ast.points;
double edgeX;
double edgeY;
for (int edgeIndex = 0; edgeIndex < edgeCountA + edgeCountB; edgeIndex++) {
if (edgeIndex < edgeCountA) {
edgeX = xp[edgeIndex] * 0.9;
edgeY = yp[edgeIndex] * 0.9;
} else {
edgeX = ast.xp[edgeIndex - edgeCountA] * 0.9;
edgeY = ast.yp[edgeIndex - edgeCountA] * 0.9;
}
final double x = -edgeY;
final double y = edgeX;
final double len = Math.sqrt(x * x + y * y);
final double axisX = x / len;
final double axisY = y / len;
final double[] minMaxA = ProjectPolygon(axisX, axisY, points, xp,
yp);
final double[] minMaxB = ProjectPolygon(axisX, axisY, ast.points,
ast.xp, ast.yp);
if (IntervalDistance(minMaxA[0], minMaxA[1], minMaxB[0], minMaxB[1]) > 0) {
return false;
}
}
return true;
}
It works... kinda. Actually it seems that the "collision hull" of the asteroids is too big when using this code, it's like 1.2 times the size of the asteroid. And I don't have any clue why.
Here are two pictures for comparison:
http://www.spielecast.de/stuff/asteroids1.png
http://www.spielecast.de/stuff/asteroids2.png
As you can hopefully see, the asteroids in picture one are much denser than the ones in picture 2 where is use the SAT code.
So any ideas? Or does anyone knows a Polygon implementation for Java featuring intersection tests that I could use?
It looks like your second result is doing collision detection as if the polygons were circles with their radius set to the most distant point of the polygon from the center. Most collision detection stuff I've seen creates a simple bounding box (either a circle or rectangle) into which the polygon can fit. Only if two bounding boxes intersect (a far simpler calculation) do you continue on to the more detailed detection. Perhaps the appropriated algorithm is only intended as a bounding box calculator?
EDIT:
Also, from wikipedia
The theorem does not apply if one of the bodies is not convex.
Many of the asteroids in your image have concave surfaces.