I have got a list of points in 2-dimension.
For example:
x=c(4,3,3,5,6,6,4)
and
y=c(5,3,1,0,1,3,5)
Plot of these 2-D points is
I would like to draw a wrapper of this point set like this :
Note that, the perpendicular distance between the boundary(wrapper) and the nearest point is 2 unit.
Note that: I have a number of point sets like the above point set. I would like to do the same thing for all the sets.
I want to have this boundary polygon. Could anyone please suggest me how to do this.
Any ideas greatly appreciated, Janak.
To achieve this you can use this simple algorithm.
First we will need the center of your coordinates (red dot).
This can be done by adding all of your x-values and divide the result by their amount, same with the y-values.
The next step will be to calculate a rectange which wraps a current coordinate and the center point. (Don't forget to add your offset of 2 units here)
We will do this for all coordinates
At this point we could already stop. Just render all of those rectangles and then your coordinates on top of the picture, but let's improve this just a little bit more.
We don't actually need all those rectangles, what we want is a polygon to wrap those points.
This polygon is defined by the intersection of our retangles and their edges (blue dots).
Note that we will only need the edges and intersections which are the furthest away from our center.
We now can connect those blue points by connecting points who share one common coordinate and are 'neighbours'.
Update:
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
public class PolyWrapper {
public static void main(String[] args){
//your example coords:
int[] x_coords = {4,3,3,5,6,6,4};
int[] y_coords = {5,3,1,0,1,3,5};
//make sure the coordinates have the same length, else they won't match
if(x_coords.length != y_coords.length){
System.err.println("Bad parameters given. X and Y don't match!");
System.exit(1);
}
//this will hold our points:
ArrayList<Point2D> points = new ArrayList<>();
for(int i = 0; i < x_coords.length; i++){
Point2D p = new Point2D.Double(x_coords[i], y_coords[i]);
points.add(p);
}
//lets get the center of all those points:
final Point2D center = get_center(points);
ArrayList<Rectangle2D> rectangles = new ArrayList<>();
//now lets create those wrapping rectangles:
for(Point2D p : points){
Rectangle2D r = new Rectangle2D.Double();
r.setFrameFromDiagonal(center, p);
rectangles.add(r);
}
//now show the wrapping rectangles:
for(Rectangle2D r : rectangles){
System.out.println(r.toString());
}
}
//this method returns the center of a list of points
public static Point2D get_center(ArrayList<Point2D> points){
double x = 0,y =0;
for(Point2D p : points){
x += p.getX();
y += p.getY();
}
x = x / points.size();
y = y / points.size();
Point2D c = new Point2D.Double();
c.setLocation(x, y);
return c;
}
}
So here is some example code. I have not found the time to finish it yet, but since your question is really interesting I will keep working on this.
So far this code calculates the center-point and creates rectangles around the center and given coordinates.
This output provides the upper left corner of each rectangle, it's width and height.
Sample output:
java.awt.geom.Rectangle2D$Double[x=4.0,y=2.5714285714285716,w=0.4285714285714288,h=2.4285714285714284]
java.awt.geom.Rectangle2D$Double[x=3.0,y=2.5714285714285716,w=1.4285714285714288,h=0.4285714285714284]
java.awt.geom.Rectangle2D$Double[x=3.0,y=1.0,w=1.4285714285714288,h=1.5714285714285716]
java.awt.geom.Rectangle2D$Double[x=4.428571428571429,y=0.0,w=0.5714285714285712,h=2.5714285714285716]
java.awt.geom.Rectangle2D$Double[x=4.428571428571429,y=1.0,w=1.5714285714285712,h=1.5714285714285716]
java.awt.geom.Rectangle2D$Double[x=4.428571428571429,y=2.5714285714285716,w=1.5714285714285712,h=0.4285714285714284]
java.awt.geom.Rectangle2D$Double[x=4.0,y=2.5714285714285716,w=0.4285714285714288,h=2.4285714285714284]
P.s.:
I tried to improve the algorithm from this point but encountered a problem which seems to be hard to solve - Maybe I will start a new question about this problem.
(It is about the picture with the blue dots. Once you have all points from the rectangles and their intersections it is hard to find out which of the resulting points are actually necessary for our polygon). I think I am close to a solution, so watch out for my next edits.
Using Java, this becomes very simple. The program demonstrates the result by plotting it. The outline may also be obtained by iterating the area.getPathIterator(at), which will return all points, one by one.
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
public class PointSet {
public static final int W = 2;
Area area = new Area();
public void add( double x, double y ){
area.add( new Area( new Rectangle2D.Double( x-W, y-W,2*W, 2*W ) ) );
}
public void plot(){
Board board = new Board();
board.go( area );
}
public static void main( String[] args ){
PointSet ps = new PointSet();
ps.add( 4, 5);
ps.add( 3, 3);
ps.add( 3, 1);
ps.add( 5, 0);
ps.add( 6, 1);
ps.add( 6, 3);
ps.plot();
}
}
and:
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.util.*;
public class Board extends JPanel {
Area area;
void go( Area area ) {
this.area = area;
JFrame frame = new JFrame("Circle Test");
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
repaint();
frame.setPreferredSize(new Dimension(800,800));
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
AffineTransform at = new AffineTransform();
at.translate( 100, 100 );
at.scale( 50, 50 );
PathIterator pit = area.getPathIterator( at );
Path2D path = new Path2D.Double();
path.append( pit, true );
Graphics2D g2d = (Graphics2D)g;
g2d.draw( path );
}
}
Related
I have a set of two dimensions points. Their X and Y are greater than -2 and lesser than 2. Such point could be : (-0.00012 ; 1.2334 ).
I would want to display these points on a graph, using rectangles (a rectangle illustrates a point, and has its coordinates set to its point's ones - moreover, it has a size of 10*10).
Rectangles like (... ; Y) should be displayed above any rectangles like (... ; Y-1) (positive Y direction is up). Thus, I must set the graph's origin not at the top-left hand-corner, but somewhere else.
I'm trying to use Graphics2D's AffineTransform to do that.
I get the minimal value for all the X coordinates
I get the minimal value for all the Y coordinates
I get the maximal value for all the X coordinates
I get the maximal value for all the Y coordinates
I get the distance xmax-xmin and ymax-ymin
Then, I wrote the code I give you below.
Screenshots
Some days ago, using my own method to scale, I had this graph:
(so as I explained, Y are inverted and that's not a good thing)
For the moment, i.e., with the code I give you below, I have only one point that takes all the graph's place! Not good at all.
I would want to have:
(without lines, and without graph's axis. The important here is that points are correctly displayed, according to their coordinates).
Code
To get min and max coordinates value:
x_min = Double.parseDouble((String) list_all_points.get(0).get(0));
x_max = Double.parseDouble((String) list_all_points.get(0).get(0));
y_min = Double.parseDouble((String) list_all_points.get(0).get(1));
y_max = Double.parseDouble((String) list_all_points.get(0).get(1));
for(StorableData s : list_all_points) {
if(Double.parseDouble((String) s.get(0)) < x_min) {
x_min = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(0)) > x_max) {
x_max = Double.parseDouble((String) s.get(0));
}
if(Double.parseDouble((String) s.get(1)) < y_min) {
y_min = Double.parseDouble((String) s.get(1));
}
if(Double.parseDouble((String) s.get(1)) > y_max) {
y_max = Double.parseDouble((String) s.get(1));
}
}
To draw a point:
int x, y;
private void drawPoint(Cupple storable_data) {
//x = (int) (storable_data.getNumber(0) * scaling_coef + move_x);
//y = (int) (storable_data.getNumber(1) * scaling_coef + move_y);
x = storable_data.getNumber(0).intValue();
y = storable_data.getNumber(1).intValue();
graphics.fillRect(x, y, 10, 10);
graphics.drawString(storable_data.toString(), x - 5, y - 5);
}
To paint the graph:
#Override
public void paint(Graphics graphics) {
this.graphics = graphics;
Graphics2D graphics_2d = ((Graphics2D) this.graphics);
AffineTransform affine_transform = graphics_2d.getTransform();
affine_transform.scale(getWidth()/(x_max - x_min), getHeight()/(y_max - y_min));
affine_transform.translate(x_min, y_min);
graphics_2d.transform(affine_transform);
for(StorableData storable_data : list_all_points) {
graphics_2d.setColor(Color.WHITE);
this.drawPoint((Cupple) storable_data);
}
I suggest you map each data point to a point on the screen, thus avoiding the following coordinate system pitfalls. Take your list of points and create from them a list of points to draw. Take into account that:
The drawing is pixel-based, so you will want to scale your points (or you would have rectangles 1 to 4 pixels wide...).
You will need to translate all your points because negative values will be outside the boundaries of the component on which you draw.
The direction of the y axis is reversed in the drawing coordinates.
Once that is done, use the new list of points for the drawing and the initial one for calculations. Here is an example:
public class Graph extends JPanel {
private static int gridSize = 6;
private static int scale = 100;
private static int size = gridSize * scale;
private static int translate = size / 2;
private static int pointSize = 10;
List<Point> dataPoints, scaledPoints;
Graph() {
setBackground(Color.WHITE);
// points taken from your example
Point p1 = new Point(-1, -2);
Point p2 = new Point(-1, 0);
Point p3 = new Point(1, 0);
Point p4 = new Point(1, -2);
dataPoints = Arrays.asList(p1, p2, p3, p4);
scaledPoints = dataPoints.stream()
.map(p -> new Point(p.x * scale + translate, -p.y * scale + translate))
.collect(Collectors.toList());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// draw a grid
for (int i = 0; i < gridSize; i++) {
g2d.drawLine(i * scale, 0, i * scale, size);
g2d.drawLine(0, i * scale, size, i * scale);
}
// draw the rectangle
g2d.setPaint(Color.RED);
g2d.drawPolygon(scaledPoints.stream().mapToInt(p -> p.x).toArray(),
scaledPoints.stream().mapToInt(p -> p.y).toArray(),
scaledPoints.size());
// draw the points
g2d.setPaint(Color.BLUE);
// origin
g2d.fillRect(translate, translate, pointSize, pointSize);
g2d.drawString("(0, 0)", translate, translate);
// data
for (int i = 0; i < dataPoints.size(); i++) {
Point sp = scaledPoints.get(i);
Point dp = dataPoints.get(i);
g2d.fillRect(sp.x, sp.y, pointSize, pointSize);
g2d.drawString("(" + dp.x + ", " + dp.y + ")", sp.x, sp.y);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setContentPane(new Graph());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And another:
You might want to have the points aligned on the grid intersections and not below and to the right of them. I trust you will figure this one out.
Also, I ordered the points so that drawPolygon will paint the lines in the correct order. If your points are arbitrarily arranged, look for ways to find the outline. If you want lines between all points like in your example, iterate over all combinations of them with drawLine.
I cant wrap my mind about how to solve this problem. I have created 3 triangles:
I have the XY coordinates of all the individual triangles. I want to calculate the center of gravity if they are put together as shown, but only using the filled part, and wherever they intersec, you do not count the mass twice. How can I go about doing this in java?
Can I somehow combine these into 1 of some kind of object, and then do a numeric calculation of each area and find a middle ground, or is there a better way?
Convert your shape to a single polygon (there are two intersections to be computed).
Then use the centroid formula for polygons.
Another option is to fill all triangles in the same color (by polygon or seed filling), then seed fill the resultant area, while accumulating the X and Y coordinates on the fly.
First, you have to determine for what you actually want to compute the center of gravity. Obviously, when the triangles intersect (and the overlapping area should not be counted twice), then you are not computing the center of gravity of triangles, but the center of gravity of their intersection area.
Fortunately, such an intersection area can easily be computed with the Area class. And according to one comment, you already have an Area describing this area.
So one option to compute the center of gravity of this area is to compute the average of all border points of the Area.
Note that this only works when the Area does not have holes.
Otherwise, you have to compute the area of the Area.
Here is a possible implementation:
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CenterOfGravity
{
public static void main(String[] args)
{
Path2D p0 = new Path2D.Double();
p0.moveTo(100, 100);
p0.lineTo(200, 100);
p0.lineTo(150, 50);
p0.closePath();
Path2D p1 = new Path2D.Double();
p1.moveTo(150, 100);
p1.lineTo(250, 100);
p1.lineTo(200, 50);
p1.closePath();
Area a = new Area();
a.add(new Area(p0));
a.intersect(new Area(p1));
Point2D cog = computeCenterOfGravity(a);
System.out.println(cog);
}
private static Point2D computeCenterOfGravity(Shape shape)
{
return computeAverage(computePoints(shape, 1.0));
}
private static Point2D computeAverage(
Collection<? extends Point2D> points)
{
double x = 0;
double y = 0;
for (Point2D point : points)
{
x += point.getX();
y += point.getY();
}
if (!points.isEmpty())
{
x /= points.size();
y /= points.size();
}
return new Point2D.Double(x, y);
}
public static List<Point2D> computePoints(
Shape shape, double flatness)
{
List<Point2D> result = new ArrayList<Point2D>();
PathIterator pi = shape.getPathIterator(null, flatness);
double[] coords = new double[6];
while (!pi.isDone())
{
int segment = pi.currentSegment(coords);
switch (segment)
{
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
result.add(new Point2D.Double(coords[0], coords[1]));
break;
case PathIterator.SEG_CLOSE:
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
default:
throw new AssertionError(
"Invalid segment in flattened path!");
}
pi.next();
}
return result;
}
}
I'm currently working on a program which enables user to draw various geometric shapes. However, I got some issues on calculating and placing the angle objects onto my Canvas panel accurately. The angle object is basically an extension of the Arc2D object, which provides a additional method called computeStartAndExtent(). Inside my Angle class, this method computes and finds the necessary starting and extension angle values:
private void computeStartAndExtent()
{
double ang1 = Math.toDegrees(Math.atan2(b1.getY2() - b1.getY1(), b1.getX2() - b1.getX1()));
double ang2 = Math.toDegrees(Math.atan2(b2.getY2() - b2.getY1(), b2.getX2() - b2.getX1()));
if(ang2 < ang1)
{
start = Math.abs(180 - ang2);
extent = ang1 - ang2;
}
else
{
start = Math.abs(180 - ang1);
extent = ang2 - ang1;
}
start -= extent;
}
It is a bit buggy code that only works when I connect two lines to each other, however, when I connect a third one to make a triangle, the result is like the following,
As you see the ADB angle is the only one that is placed correctly. I couldn't figure how to overcome this. If you need some additional info/code please let me know.
EDIT: b1 and b2 are Line2D objects in computeStartAndExtent() method.
Thank you.
There are some of things that can be made to simplify the calculation:
Keep the vertices ordered, so that it is always clear how to calculate the vertex angles pointing away from the corner
Furthermore, always draw the polygon to the same direction; then you can always draw the angles to the same direction. The example below assumes the polygon is drawn clockwise. The same angle calculation would result in the arcs drawn outside given a polygon drawn counterclockwise.
Example code; is not quite the same as yours as I don't have your code, but has similar functionality:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Polygon extends JPanel {
private static final int RADIUS = 20;
private final int[] xpoints = {
10, 150, 80, 60
};
private final int[] ypoints = {
10, 10, 150, 60
};
final Arc2D[] arcs;
Polygon() {
arcs = new Arc2D[xpoints.length];
for (int i = 0; i < arcs.length; i++) {
// Indices of previous and next corners
int prev = (i + arcs.length - 1) % arcs.length;
int next = (i + arcs.length + 1) % arcs.length;
// angles of sides, pointing outwards from the corner
double ang1 = Math.toDegrees(Math.atan2(-(ypoints[prev] - ypoints[i]), xpoints[prev] - xpoints[i]));
double ang2 = Math.toDegrees(Math.atan2(-(ypoints[next] - ypoints[i]), xpoints[next] - xpoints[i]));
int start = (int) ang1;
int extent = (int) (ang2 - ang1);
// always draw to positive direction, limit the angle <= 360
extent = (extent + 360) % 360;
arcs[i] = new Arc2D.Float(xpoints[i] - RADIUS, ypoints[i] - RADIUS, 2 * RADIUS, 2 * RADIUS, start, extent, Arc2D.OPEN);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(160, 160);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawPolygon(xpoints, ypoints, xpoints.length);
Graphics2D g2d = (Graphics2D) g;
for (Shape s : arcs) {
g2d.draw(s);
}
}
public static void main(String args[]){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Polygon");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new Polygon());
frame.pack();
frame.setVisible(true);
}
});
}
}
Results in:
One of the exercises in my Java textbook says "Consult the API documentation to find methods for:
Computing the smallest rectangle that contains two given rectangles. • Returning a random floating-point number."
I've looked at the Java API for class Rectangle, but I can't find one that computes the smaller rectangle. The closest methods I've found are union and bounds, but I don't think that's correct.
I found min from the Java Math class and wrote a test program to see if it would work, but min cannot have arguments of rectangles.
Here's the code I wrote:
import java.awt.Rectangle;
public class RectangleSize {
public static void main(String[] args)
{
Rectangle a = new Rectangle(5, 5, 10, 10);
Rectangle b = new Rectangle(5, 5, 20, 20);
int min = Math.min(a, b); //In Eclipse, I get an error.
System.out.println(min);
}
}
I am studying java recently using the textbook of Big Java Early Object. I found the same question in chapter 2. Eventually I found the answer by looking at Java SE8 API list.
First use method .add() to combine 2 rectangles.
Note: actually, this combined one is already the one you need. But you can get a new rectangle (same size and location) at step 2.
Them use .getBounds() to get the smallest rectangle containing the combined one.
.
import java.awt.*;
public class RectangleTester01 {
public static void main(String[] args) {
Rectangle box1 = new Rectangle(10, 20, 40, 40);
Rectangle box2 = new Rectangle(20, 30, 60, 60);
box1.add(box2);
System.out.println(box1);
Rectangle box3 = box1.getBounds();
System.out.println(box3);
}
}
output is:
java.awt.Rectangle[x=10,y=20,width=70,height=70]
java.awt.Rectangle[x=10,y=20,width=70,height=70]
You want to use Rectangle.contains . You would be given many rectangles. You'd need to loop through all the rectangles and see if it contains the two rectangles given. If it does you should calculate the size of that rectangle. In the end you take the rectangle with the smallest size.
public Rectangle getSmallest(Rectangle one, Rectangle two, Rectangle[] rectangles) {
Rectangle smallest = null;
double area = Double.MAX_VALUE;
for (Rectangle r: rectangles) {
if (r.contains(one) && r.contains(two)) {
calculatedArea = r.getWidth() * r.getHeight();
if (calculatedArea < area) {
area = calculatedArea;
smallest = r;
}
}
}
return r;
}
I think the method you're looking for is Rectangle2D.createUnion. It combines two rectangles to make a bigger one that contains both with a minimum of extra space.
Here is another question to find a rectangle which contains a list of rectangles.find-smallest-area-that-contains-all-the-rectangles.
Here is my brute answer which is not accuracy and did not try the union function of Rectengle and also the createUnion(Rectengle2D r) as well.
Math.min can help find the minimum number in a number array. You may write a similar one for Rectangle. This is something from 1D to 2D in my mind. BTW, it would be much interesting if you extends it to 3D or nD objects calculation.
package com.stackoverflow.q26311076;
import java.awt.Rectangle;
public class Test {
public static void main(String[] args) {
Rectangle a = new Rectangle(5, 5, 10 , 10);
Rectangle b = new Rectangle(5, 5, 20 , 20);
Rectangle min = getMin(a, b) ;
// ...
}
public static Rectangle getMin(Rectangle a, Rectangle b) {
//find the min range in X.
{
double x1 = a.getX();
double x2 = a.getX() + a.getWidth();
double x3 = b.getX();
double x4 = b.getX() + b.getWidth();
double minX1 =Math.min( Math.min(x1, x2), Math.min(x3, x4)) ;
double maxX1 =Math.max( Math.max(x1, x2), Math.max(x3, x4)) ;
}
//find the min range in Y.
{
double y1 = a.getY();
double y2 = a.getY() + a.getHeight();
double y3 = b.getY();
double y4 = b.getY() + b.getHeight();
double minY1 =Math.min( Math.min(y1, y2), Math.min(y3, y4)) ;
double maxY1 =Math.max( Math.max(y1, y2), Math.max(y3, y4)) ;
}
//build new rectangle with X & Y
Rectangle r = new Rectangle();
r.setRect(minX1, minY1, maxX1 - minX1, maxY1 - minY1);
return r;
}
}
I've got a top down game that I'm making and I want my enemies to be able to move across the screen in an arc. Right now they move in a straight line between two edges of the screen. I generate a start position on one edge then find a random position somewhere across the screen and calculate movement speeds my multiplying the sin/cos of the angle by their speed variable.
I'd like to use these to points to generate some arc between them and then use that to move my enemies along it. I thought maybe some sort of spline would do the trick but Im not entirely sure how to create one, nor more significantly how to use it to interpolate my characters. I think at this point its more of a math question than programming but I hope someone can help anyways. Thanks.
Yes, a spline would work for you. Specifically i would recommend a cubic spline, because later on if you wanted do a different shape, maybe a Street Fighter style uppercut, you could re-use the same code. I remember cubic spline being a decent, general solution.
As far as solving for a cubic spline I would recommend you just Google for pseudo code which makes sense to you. That's only if you really want to generically solve for a suitable cubic spline on the fly.
In practice, i imagine the shape you want will be the same almost all the time? If so, you can probably solve a few general cases of a spline and save it to some fast data structure to improve performance. In example, for y=x a suitable array holding the necessary information (pre-processed) would be x[0] = 1,x[1] = 1,x[2] = 2 ... x[n] = n.
In practice, you could come up with an equation to model a simple two point spline. A cubic equation has 4 unknowns. So you have two data points at least, you're starting point and your end point. In addition, you can calculate the derivative of him when he jumps. For your fourth point you could use either another point you want him to jump through, or the derivative when he lands. Then use https://www.wolframalpha.com/ to solve the equation for you. Or use an equation to solve cubics.
Another thing you can do is just calculate the arc using the quadratic equation + gravity + wind resistance. Again, Google knows how to solve that. This page is something i quickly found that looks like it could do the trick. http://www.physicsclassroom.com/class/vectors/Lesson-2/Non-Horizontally-Launched-Projectiles-Problem-Solv
When you intend to use a spline, you can use the Path2D class that is already available in Java. You can assemble an arbitrary path by
moving to a certain point
appending a line segment
appending a quadratic curve segment
appending a cubic curve segment
So assembling this path should be easy: You can just create a quadratic curve that starts at a random point on the left border of the screen and ends at a random point on the right border of the screen. As the control points (for both ends) you can use a point at a random position in the center of the screen.
(BTW: When you manage to represent the path as a generic Path2D, then you can probably imagine that you have quite a lot of freedom when designing the path for the enemies. They could run in circles or zig-zag, just as you like...)
What might be more tricky here is to let the enemies follow this path.
The first step is not sooo tricky yet: You can walk along this path with a PathIterator. But this PathIterator will only return a single segment - namely, the quadratic curve. This can be alleviated by creating a flattening PathIterator. This will convert all curves into line segments (the resolution can be high, so you won't notice any corners).
However, now comes the really tricky part: When iterating these line segments, the movement speed may vary: Depending on the curvature of the original quadratic curve, there may be more or fewer line segments be created. In the worst case, when all 3 points are on one line, then only one line segment would be created, and the enemy would walk across the whole screen in a single step. So you have to make sure that this path is traversed with a constant speed. You have to compute how far you already have been walking when iterating over the path, and possibly interpolate a position between two points of the path.
I quickly assembled an example. It's certainly not bullet-proof, but might serve as a starting point.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplineMovementTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static PathFollower pathFollower;
private static void createAndShowGUI()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new BorderLayout());
final Random random = new Random(0);
final SplineMovementPanel p = new SplineMovementPanel();
JButton generateButton = new JButton("Generate");
generateButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
Shape spline = generateSpline(p,
random.nextDouble(),
random.nextDouble(),
random.nextDouble());
p.setSpline(spline);
pathFollower = new PathFollower(spline);
p.repaint();
}
});
frame.getContentPane().add(generateButton, BorderLayout.NORTH);
startAnimation(p);
frame.getContentPane().add(p, BorderLayout.CENTER);
frame.setSize(800, 800);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static Shape generateSpline(
JComponent c, double yLeft, double yCenter, double yRight)
{
Path2D spline = new Path2D.Double();
double x0 = 0;
double y0 = yLeft * c.getHeight();
double x1 = c.getWidth() / 2;
double y1 = yCenter * c.getHeight();
double x2 = c.getWidth();
double y2 = yRight * c.getHeight();
spline.moveTo(x0, y0);
spline.curveTo(x1, y1, x1, y1, x2, y2);
return spline;
}
private static void startAnimation(final SplineMovementPanel p)
{
Timer timer = new Timer(20, new ActionListener()
{
double position = 0.0;
#Override
public void actionPerformed(ActionEvent e)
{
position += 0.005;
position %= 1.0;
if (pathFollower != null)
{
Point2D point = pathFollower.computePointAt(
position * pathFollower.getPathLength());
p.setObjectLocation(point);
}
}
});
timer.start();
}
}
class PathFollower
{
private final List<Point2D> points;
private final double pathLength;
PathFollower(Shape spline)
{
points = createPointList(spline);
pathLength = computeLength(points);
}
public double getPathLength()
{
return pathLength;
}
Point2D computePointAt(double length)
{
if (length < 0)
{
Point2D p = points.get(0);
return new Point2D.Double(p.getX(), p.getY());
}
if (length > pathLength)
{
Point2D p = points.get(points.size()-1);
return new Point2D.Double(p.getX(), p.getY());
}
double currentLength = 0;
for (int i=0; i<points.size()-1; i++)
{
Point2D p0 = points.get(i);
Point2D p1 = points.get(i+1);
double distance = p0.distance(p1);
double nextLength = currentLength + distance;
if (nextLength > length)
{
double rel = 1 - (nextLength - length) / distance;
double x0 = p0.getX();
double y0 = p0.getY();
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double x = x0 + rel * dx;
double y = y0 + rel * dy;
return new Point2D.Double(x,y);
}
currentLength = nextLength;
}
Point2D p = points.get(points.size()-1);
return new Point2D.Double(p.getX(), p.getY());
}
private static double computeLength(List<Point2D> points)
{
double length = 0;
for (int i=0; i<points.size()-1; i++)
{
Point2D p0 = points.get(i);
Point2D p1 = points.get(i+1);
length += p0.distance(p1);
}
return length;
}
private static List<Point2D> createPointList(Shape shape)
{
List<Point2D> points = new ArrayList<Point2D>();
PathIterator pi = shape.getPathIterator(null, 0.1);
double coords[] = new double[6];
while (!pi.isDone())
{
int s = pi.currentSegment(coords);
switch (s)
{
case PathIterator.SEG_MOVETO:
points.add(new Point2D.Double(coords[0], coords[1]));
case PathIterator.SEG_LINETO:
points.add(new Point2D.Double(coords[0], coords[1]));
}
pi.next();
}
return points;
}
}
class SplineMovementPanel extends JPanel
{
void setSpline(Shape shape)
{
this.spline = shape;
}
void setObjectLocation(Point2D objectLocation)
{
this.objectLocation = objectLocation;
repaint();
}
private Shape spline = null;
private Point2D objectLocation = null;
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (spline != null)
{
g.setColor(Color.BLACK);
g.draw(spline);
}
if (objectLocation != null)
{
g.setColor(Color.RED);
int x = (int)objectLocation.getX()-15;
int y = (int)objectLocation.getY()-15;
g.fillOval(x, y, 30, 30);
}
}
}