Is it possible to check a collision with a line using an Area object?
Currently the way I am doing it is not working:
This returns false when you run it. But the line is very clearing touching the rectangle, in fact its completely inside it.
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
public class collision {
public static void main(String[] args) {
Area area1 = new Area(new Rectangle(0, 0, 100, 100));
Area area2 = new Area(new Line2D.Double(0, 0, 100, 100));
System.out.println(isColliding(area1, area2));
}
public static boolean isColliding(Area area1, Area area2) {
if (area2 != null) {
Area collide1 = new Area(area1);
collide1.subtract(area2);
if (!collide1.equals(area1)) {
return true;
}
}
return false;
}
}
I was able to solve this problem by making a recursive function that checks points along the thing.
The higher you set the depth the more accurately it will check, but will take longer to complete. I have been using 10 as my depth (which I believe checks 2047 points along the line) and I encountered no performance loss. Unless your Area object contains really thin parts I don't believe you will need more than this.
Someone feel free to comment and revise my method if you believe you can improve it in any way :)
Thanks to ajb for his suggestion of using a PathIterator which gave me the idea to check points along the line.
public static boolean findPoints(Area area1, Line2D line1, int depth) {
Point p1 = new Point((int) (line1.getX2() + line1.getX1()) / 2,
(int) (line1.getY2() + line1.getY1()) / 2);
if (depth == 0) {
return false;
}
pointMiddle = new Point(p1);
if (area1.contains(p1)) {
return true;
} else {
return findPoints(area1, new Line2D.Double(p1, line1.getP2()),
depth - 1)
|| findPoints(area1, new Line2D.Double(line1.getP1(), p1),
depth - 1);
}
}
If, as you say in the comments, you know that you will always be checking a Line2D and a Rectangle for collision, you can use the intersects method of Line2D (see javadoc):
public static void main(String[] args) {
Rectangle rect1 = new Rectangle(0, 0, 100, 100);
Line2D line2 = new Line2D.Double(0, 0, 100, 100);
System.out.println(isColliding(rect1, line2));
}
public static boolean isColliding(Rectangle2D rect1, Line2D line2) {
if (line2 != null) {
return line2.intersects(rect1);
}
return false;
}
To test if a line collides with a more general Area is more difficult; I don't think there's a method in the library for this. If the Area is built up of rectangles, you could try saving an array (or List) of the Rectangle objects, and testing whether the line intersects with any of them. Another approach: You can try using the contains method of Area, which tests whether an area contains a point. If either endpoint of the Line2D is contained by the Area, then the line and the area collide. If both endpoints are outside the area, but the area consists of straight lines (area.isPolygonal()), you could retrieve each line segment from the polygon and test whether the line intersects each line segment. You can do this with area.getPathIterator(null), and then using something like this on the resulting path iterator:
double[] coords = new double[6];
double moveX, moveY, prevX, prevY, newX, newY;
while (!pathIterator.isDone()) {
switch (pathIterator.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
moveX = coords[0]; moveY = coords[1];
prevX = moveX; prevY = moveY;
break;
case PathIterator.SEG_LINETO:
newX = coords[0]; newY = coords[1];
if ([line2 intersects the line from (prevX,prevY) to (newX,newY)]) {
return true;
}
prevX = newX; prevY = newY;
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
throw new RuntimeException("What is a curve doing in my rectangle?");
case PathIterator.SEG_CLOSE:
// go back to the last SEG_MOVETO point, usually the first point
if ([line2 intersects the line from (prevX,prevY) to (moveX,moveY)]) {
return true;
}
prevX = newX; prevY = newY;
break;
}
}
There are methods in Line2D to test whether a line segment intersects another.
Note: I have not tested this at all. I hope it works.
Related
I have a rectangular and circle. I need to verify whether a rectangle is inside that circle.
I tried to Shape.intersects but intersects is checked the number 1.
Does anyone know this kind of algorithm in javafx?
Just to exemplifly, in the figure only rectangles 1, 2, 3, and 4 are inside the circle.
thanks for your help.
Solution
The basic idea behind this solution is that any polygon is contained within any convex (see comments) shape iff every point within the polygon is within the shape. The intersects() method that you're attempting to use returns true if at least one point of the polygon is within the shape. You've already figured out that it'll work, but it'll also offer false positives for any partially-intersected shapes. To fix it, we define our own intersection test which looks at all points.
This can be generalized to scan any given polygon for "total intersection" with any given shape:
public boolean totalIntersects(Polygon poly, Shape testShape) {
List<Point> points = flatDoublesToPoints(poly.getPoints());
boolean inside = true; // If this is false after testing all points, the poly has at least one point outside of the shape.
for(Point point : points) {
if(!testShape.intersects(point.x, point.y, 1, 1)) { // The 3rd and 4th parameters here are "width" and "height". 1 for a point.
inside = false;
}
}
return inside;
}
where flatDoublesToPoints() and Point are defined as:
private List<Point> flatDoublesToPoints(List<Double> flatDoubles) {
List<Point> points = new ArrayList<>();
for(int i = 0; i < flatDoubles.size(); i += 2) {
points.add(new Point(flatDoubles.get(i), flatDoubles.get(i + 1)));
}
return points;
}
class Point {
public double x, y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
}
flatDoublesToPoints() is needed to split the "flat" {x1, y1, x2, y2, x3, y3...} polygon lists into a more easy-to-understand data structure. If you're doing tons of comparisons, it may be helpful to skip this step, however, and operate on the "flat list" directly for memory reasons.
Application
The following applies the other methods to a situation extremely similar to yours. (Not exact, because I didn't have your code.)
public class Main extends Application {
public static final int SIZE = 600;
#Override
public void start(Stage primaryStage) throws Exception {
Pane rootPane = new Pane();
List<Rectangle> rects = new ArrayList<>();
for (int j = 0; j < 2; j++) {
for(int i = 0; i < 5; i++) {
Rectangle r = new Rectangle(i * 100, j == 0 ? 0 : 300, 100, 200);
r.setFill(Color.BEIGE);
r.setStroke(Color.BLACK);
rects.add(r);
}
}
rootPane.getChildren().addAll(rects);
Circle circle = new Circle(350, 100, 200);
circle.setStroke(Color.BLACK);
circle.setFill(null);
rootPane.getChildren().add(circle);
List<Polygon> polys = new ArrayList<>();
for(Rectangle rect : rects) {
polys.add(rectangleToPolygon(rect));
}
List<Polygon> intersects = getTotalIntersections(polys, circle);
System.out.println(intersects);
primaryStage.setScene(new Scene(rootPane, SIZE, SIZE));
primaryStage.show();
}
public List<Polygon> getTotalIntersections(List<Polygon> polys, Shape testShape) {
List<Polygon> intersections = new ArrayList<>();
for(Polygon poly : polys) {
if(totalIntersects(poly, testShape)) {
intersections.add(poly);
}
}
return intersections;
}
public static Polygon rectangleToPolygon(Rectangle rect) {
double[] points = {rect.getX(), rect.getY(),
rect.getX() + rect.getWidth(), rect.getY(),
rect.getX() + rect.getWidth(), rect.getY() + rect.getHeight(),
rect.getX(), rect.getY() + rect.getHeight()};
return new Polygon(points);
}
public static void main(String[] args) {
Main.launch(args);
}
}
This code will print the following:
[Polygon[points=[200.0, 0.0, 300.0, 0.0, 300.0, 200.0, 200.0, 200.0], fill=0x000000ff], Polygon[points=[300.0, 0.0, 400.0, 0.0, 400.0, 200.0, 300.0, 200.0], fill=0x000000ff], Polygon[points=[400.0, 0.0, 500.0, 0.0, 500.0, 200.0, 400.0, 200.0], fill=0x000000ff]]
Which is your three polygons labeled 2, 3, and 4.
I don't think that JavaFX will have some special methods for this case.
To draw that circle you need coordinates (X_c, Y_c) of center and radius (R).
To draw rectangles you need to have coordinates ((X_1, Y_1), (X_2, Y_2) etc.) of angle points.
Then all you need is to check if all points of the rectangle is inside of the circle:
(X_1 - X_c)^2 + (Y_1 - Y_c)^2 < R^2
(X_2 - X_c)^2 + (Y_2 - Y_c)^2 < R^2
...
Try this :
import javafx.geometry.Point2D;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
/*
Check if a rectangle is contained with in a circle by checking
all rectangle corners.
For the rectangle to be contained in a circle, all its corners should be
in a distance smaller or equal to the circle's radius, from the circle's center.
Note:
Requires some more testing. I tested only a few test cases.
I am not familiar with javafx. This solution does not take into
calculation rectangle's arc or other attributes I may not be aware of.
*/
public class Test{
//apply
public static void main(String[] args){
Circle circle = new Circle(0 ,0, 100);
Rectangle rec = new Rectangle(0, 0, 50 , 50);
System.out.println("Is rectungle inside the circle ? "
+ isContained(circle,rec));
}
//check if rectangle is contained within a circle
private static boolean isContained(Circle circle,Rectangle rec) {
boolean isInside = true;
//get circle center & radius
Point2D center = new Point2D(circle.getCenterX(), circle.getCenterY());
double radius= circle.getRadius();
Point2D[] corners = getRectangleCorners(rec);
for(Point2D corner : corners) {
//if any corner falls outside the circle
//the rectangle is not contained in the circle
if(distanceBetween2Points(corner, center) > radius) {
return false;
}
}
return isInside;
}
//calculate distance between two points
//(updated a per fabian's suggestion)
private static double distanceBetween2Points
(Point2D corner, Point2D center) {
return corner.distance(center);
}
private static Point2D[] getRectangleCorners(Rectangle rec) {
Point2D[] corners = new Point2D[4];
corners[0] = new Point2D(rec.getX(), rec.getY());
corners[1] = new Point2D(rec.getX()+ rec.getWidth() , rec.getY());
corners[2] = new Point2D(rec.getX()+ rec.getWidth(), rec.getY()+ rec.getHeight());
corners[3] = new Point2D(rec.getX(), rec.getY()+ rec.getHeight());
return corners;
}
}
There is a working simple solution here : https://stackoverflow.com/a/8721483/1529139
Copy-paste here:
class Boundary {
private final Point[] points; // Points making up the boundary
...
/**
* Return true if the given point is contained inside the boundary.
* See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
* #param test The point to check
* #return true if the point is inside the boundary, false otherwise
*
*/
public boolean contains(Point test) {
int i;
int j;
boolean result = false;
for (i = 0, j = points.length - 1; i < points.length; j = i++) {
if ((points[i].y > test.y) != (points[j].y > test.y) &&
(test.x < (points[j].x - points[i].x) * (test.y - points[i].y) / (points[j].y-points[i].y) + points[i].x)) {
result = !result;
}
}
return result;
}
}
I'm making a game where you're controlling a square, and objects will spawn in random places, which you have to pick up to get points, while also having to dodge big squares going from side to side. Right now I have 2 classes (one for enemies - big squares -, and one for the hero), I haven't done the point system yet with the spawning objects, but that's not what I'm trying to do now.
So my problem right now is that I don't really know how to make the person lose the game/a life when you touch the "enemies" with your hero. I would know how to do it without classes, but I'd like to know how to do it when they are in separate classes.
If someone could explain with code and comments how this would be done, it would help me out a lot :) (I read something about 'extends' for classes but I'm not sure if this is what I should use or not).
Here's a screenshot of what my game looks like at the moment, just to better illustrate it:
Here's the main code page:
Hero myHero = new Hero(400,480,5);
Enemies myEnemies = new Enemies(50,50,10);
Enemies myEnemies2 = new Enemies(50,350,15);
Enemies myEnemies3 = new Enemies(50,650,12);
void setup() {
size(900,800);
frameRate(30);
smooth();
}
void draw() {
background(0);
myHero.keyPressed();
myEnemies.enemyDisplay();
myEnemies.enemyMove();
myEnemies2.enemyDisplay();
myEnemies2.enemyMove();
myEnemies3.enemyDisplay();
myEnemies3.enemyMove();
}
Class 1:
class Enemies {
float xpos, ypos, speed;
Enemies(float x, float y, float s) {
xpos = x;
ypos = y;
speed = s;
}
void enemyDisplay() {
rect(xpos, ypos, 100, 100);
}
void enemyMove() {
xpos += speed;
if((xpos > width - 100) || (xpos < 0)) {
speed *= -1;
}
}
}
Class 2:
class Hero {
float xpos_, ypos_, speed_;
Hero(float x, float y, float s) {
xpos_ = x;
ypos_ = y;
speed_ = s;
}
void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
ypos_ -= speed_;
}
if (keyCode == DOWN) {
ypos_ += speed_;
}
if (keyCode == LEFT) {
xpos_ -= speed_;
}
if (keyCode == RIGHT) {
xpos_ += speed_;
}
}
rect(xpos_,ypos_,30,30);
}
}
I believe the question you are asking involves basic collision detection and object interactions.
I would first make the Enemies a List and create it / add elements during the setup() call:
List<Enemies> enemies = new List<Enemies>();
enemies.add(new Enemies(50,50,10));
This allows you to store all of your Enemies under one object. So your draw() method would look something like:
void draw(){
background(0);
myHero.keyPressed();
for(Enemies enemy : enemies)
{
enemy.enemyDisplay();
enemy.enemyMove();
if (hero.isCollidingWith(enemy)) // collision method defined in the hero object, but you could define it in the Enemies class as well, it doesn't really matter
{
hero.removeHealth(); // method defined in hero that removes health
}
}
}
This method would be in one of your classes:
public boolean isColliding(Enemies enemy)
{
// check the x and y coordinates of each object
}
I hope this helps to point you in the right direction.
you need to figure out collision detection and when your objects collide with each other, something basic like:
class Enemy
{
//...
public boolean isColliding(Hero hero)
{
//figure out the distance between two objects, if its less than their size, they are colliding..
//...
}
//...
}
then you need a part of your Game Loop that checks if anything is colliding with your hero, pickups, walls, etc...
It seems the first part you need help with is collision detection. The short answer I would give that will almost undoubtedly lead you to more questions is to look at the Area class (specifically Area.intersect). You might also want to look at the classes I've put together for displaying & managing areas in this project here
There are several problems here that touch on both application design as well as conventions. These should be addressed first before trying to tackle the collision detection problem.
The Enemies class only represents a single enemy, so the name of the class should reflect that. Additionally, prefixing the method names with "enemy" is redundant and can be removed. Other changes have been commented in the revised class below.
public class Enemy {
// Instead of hard-coding in the width and height of an enemy, allow the
// instantiating code to specify the enemy's size. This will allow you
// to have different size enemies and prevents you from having "magic numbers"
// in your code.
private float xpos, ypos, width, height, speed;
public Enemy(float x, float y, float s, float w, float h) {
xpos = x;
ypos = y;
width = w;
height = h;
speed = s;
}
/* These getters will be used for collision detection later */
public float getX() {
return xpos;
}
public float getY() {
return ypos;
}
public float getWidth() {
return width;
}
public float getHeight() {
return height;
}
// I've changed `display` to `draw` to be consistent with the method name in
// your main `draw` method.
public void draw() {
rect(xpos, ypos, width, height);
}
// This method now accepts a screenWidth parameter so that the enemy can know
// when they've collided with the left or right wall of the screen without
// having to rely on an global variable.
public void move(int screenWidth) {
xpos += speed;
if ((xpos > screenWidth - width) || (xpos < 0)) {
speed *= -1;
}
}
}
I mention the "magic numbers" in one of the comments above. See this wikipedia article for more on that.
The Hero class contains property names that have an underscore postfix. This ranges from unconventional to inconsistent with respect to all of your other property names in your other classes. The original keyPressed() method mixes the logic for both drawing and moving. These two things have been separated and the methods named like those of the Enemy class for consistency.
public class Hero {
private float xpos, ypos, width, height, speed;
public Hero(float x, float y, float s, float w, float h) {
xpos = x;
ypos = y;
width = w;
height = h;
speed = s;
}
// Change this method name to draw for consistency with the Enemy class
public void draw() {
// Key press functionality has been moved to the `move` method for consistency
// with the Enemy class.
rect(xpos, ypos, WIDTH, HEIGHT);
}
// This method uses the variables key, keyCoded, UP, DOWN, LEFT, and RIGHT. You
// did not include any import statements with your code, so they may be coming
// from there; however, if they are globals, you should pass them to this method
// as arguments whenever you call it.
public void move() {
// If this condition isn't satisfied, return immediately. This prevents
// unnecessary nesting and work below.
if (key != CODED) {
return;
}
if (keyCode == UP) {
ypos -= speed;
}
// Use `else if` here and below to prevent multiple unnecessary
// comparisons of keyCode.
else if (keyCode == DOWN) {
ypos += speed;
}
else if (keyCode == LEFT) {
xpos -= speed;
}
else if (keyCode == RIGHT) {
xpos += speed;
}
}
public boolean isColliding(Enemy enemy) {
// Collision detection is easy since all of your game entities (the hero and
// the enemies) are all rectangles and axis-aligned (not rotated). You can
// use a method known as "bounding box intersection."
return (Math.abs(enemy.getX() - xpos) * 2 < (enemy.getWidth() + width))
&& (Math.abs(enemy.getY() - ypos) * 2 < (enemy.getHeight() + height));
}
}
For more on bounding box intersection, see this gamedev stackexchange question.
Now that your classes are in order, its time to address your main code. We'll need to update the method names and, as #James T suggested, you should make a list of enemies instead of creating a new independent object for each enemy. This will make it easier for you to add or remove enemies in the future and to be able to process all enemies with one block of code without repeating yourself.
// Use constants to remove magic numbers.
private static final int SCREEN_WIDTH = 900;
private static final int SCREEN_HEIGHT = 800;
private Hero myHero = new Hero(400, 480, 30, 30, 5);
private List<Enemy> enemies = new ArrayList<Enemy>();
void setup() {
size(SCREEN_WIDTH, SCREEN_HEIGHT);
frameRate(30);
smooth();
enemies.add(new Enemy(50, 50, 100, 100, 10));
enemies.add(new Enemy(50, 350, 100, 100, 15));
enemies.add(new Enemy(50, 650, 100, 100, 12));
}
void draw() {
hasCollision = false;
background(0);
// I've changed the order of draw->move to move->draw. If you draw first, then
// move, then detect collisions, it will appear to your user that your hero has
// not yet collided with an enemy even though you act as they have (e.g.: they
// will not see the collision until the next time you draw the scene).
myHero.move();
myHero.draw();
for (Enemy enemy : enemies) {
enemy.move();
enemy.draw(SCREEN_WIDTH);
if (!hasCollision && myHero.isColliding(enemy)) {
hasCollision = true;
}
}
if (hasCollision) {
// Handle enemy collision here
}
}
You'll notice that I've also added accessibility modifiers to everything. While it is technically valid to exclude them and use the defaults, it makes your code more readable to include them because it is more obvious. When you're first starting out, the more obvious the better.
Alright got it to work thanks to all you helpful wonderful people!
I did this:
public boolean isColliding(Enemies h){
float distance = dist(x,y,h.x,h.y);
if(distance<100){
return true;
}else{
return false;
}
}
and in my draw(){ I have
if(myHero.isColliding(myEnemies)){
println("You lost!");
}
I had a very similar 'fix' earlier, but the reason I got an error was because I had Hero h instead of Enemies h in the 'if' function, so it was just a very dumb error that I overlooked :P
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);
}
}
}
I am trying to rotate a custom shape around its center, but can not get the result as expected.
what i want is
*shape should be rotated around its center without moving itself.*
what my solution is currently doing is
rotating a whole shape around its center , by every rotation its changing its position.
I have multiple shapes so i have created a class to encapsulate a shape with its transform in following class
public abstract class Shoe implements Shape, ShoeShape {
// variable declaration
/**
*
*/
public Shoe() {
position = new Point();
lastPosition = new Point();
}
public void draw(Graphics2D g2, AffineTransform transform, boolean firstTime) {
AffineTransform af = firstTime ? getInitTransform()
: getCompositeTransform();
if (af != null) {
Shape s = af.createTransformedShape(this);
if (getFillColor() != null) {
g2.setColor(getFillColor());
g2.fill(s);
} else {
g2.draw(s);
}
}
}
}
public AffineTransform getCompositeTransform() {
AffineTransform af = new AffineTransform();
af.setToIdentity();
af.translate(position.getX(), position.getY());
Point2D centerP = calculateShapeCenter();
af.rotate(orientation, centerP.getX(), centerP.getY());
return af;
}
public void onMouseDrag(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
// MOVEMENT
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
Point2D origin = calculateShapeCenter();
Point2D.Double starting = new Point2D.Double(me.getX(), me.getY());
currentAngle = RotationHelper.getAngle(origin, starting);
rotationAngle = currentAngle - startingAngle;
rotate(rotationAngle);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void onMousePress(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
Point2D origin = calculateShapeCenter();
Point2D.Double starting = new Point2D.Double(me.getX(), me.getY());
startingAngle = RotationHelper.getAngle(origin, starting);
setShapeOperation(selectionOperation);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void onMouseRelease(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
// FIXME rotation angle computation
setShapeOperation(-1);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void rotate(double angle) {
orientation = (float) angle;
}
public void translate(double deltaX, double deltaY) {
position.setLocation(deltaX, deltaY);
lastPosition.setLocation(deltaX, deltaY);
}
// another getter and setter
I am calculating angle of rotation using following method
public static double getAngle(Point2D origin, Point2D other) {
double dy = other.getY() - origin.getY();
double dx = other.getX() - origin.getX();
double angle;
if (dx == 0) {// special case
angle = dy >= 0 ? Math.PI / 2 : -Math.PI / 2;
} else {
angle = Math.atan(dy / dx);
if (dx < 0) // hemisphere correction
angle += Math.PI;
}
// all between 0 and 2PI
if (angle < 0) // between -PI/2 and 0
angle += 2 * Math.PI;
return angle;
}
in mouse press event of the canvas mouse listener
selectedShape.onMousePress(me, canvasBoundary, shoeViewer
.getShapeOperation());
i am just calling selected shape's onMousePress method
and in my mouse drag method of the canvas mouse listener , i am just calling the selected shape's onMouseDrag method which updates the rotation angle as you can see from the very first class
selectedShape.onMouseDrag(me, canvasBoundary, shoeViewer
.getShapeOperation());
and you can see the draw method of the individual shape , to draw the shape according to current transform , i am calling from paintComponent like
Iterator<Shoe> shoeIter = shoeShapeMap.values().iterator();
while (shoeIter.hasNext()) {
Shoe shoe = shoeIter.next();
shoe.draw(g2, firstTime);
}
where shoeShapeMap contains all of the custom shapes currently on the canvas.
is i am doing mistake in calculating angle or determining anchor point ? my current solution rotates shape 360 degree by checking all the conditions[90 degree etc.] as you can see in the above mentioned method.
i want the shape should be rotated around its center without resizing its positions ?
in the word it is difficult to explain , so please suggest me any better way to show here what i want to accomplish ?
i think i have mentioned all the things related to this issue. if you have any doubts please feel free to ask me.
i found 2 related posts here but i could not find much information from them.
I think that the solution may be to (either/and):
invert the order of operations on your AffineTransform, put translate after rotate
use -x and -y for your translation values
I am making a java game using tiles. I am having trouble with the collision element. I am defining rectangles for each tile on the map, and another rectangle for the player. What I am having trouble with is knowing which side the player is coming from when it hits the rectangle, and then pushing the player off in the direction the player came from. I have already made a method that checks how much the character is inside the rectangle, so it can know how much to push it out, but I can't figure out how to tell which side the character is coming from.
here is my current collision methoe - note rect1 is the character and rect2 is the tile
public void collision(Rectangle rect1, Rectangle rect2) {
float xAdd;
float xAdd2;
float yAdd;
float yAdd2;
boolean hitRight = false;
boolean hitLeft = false;
boolean hitTop = false;
boolean hitBot = false;
Vector2f rect1Origin = new Vector2f(rect1.x, rect1.y);
Vector2f rect2Origin = new Vector2f(rect2.x, rect2.y);
Vector2f rect1Mid = new Vector2f((rect1.x + rect1.width) / 2,(rect1.y + rect1.height) / 2);
Vector2f rect2Mid = new Vector2f((rect2.x + rect2.width) / 2,(rect2.y + rect2.height) / 2);
Vector2f rect1A = new Vector2f(rect1Origin.x + rect1.width, rect1.y);
Vector2f rect1B = new Vector2f(rect1Origin.x, rect1Origin.y+ rect1.height);
Vector2f rect1C = new Vector2f(rect1Origin.x + rect1.width,rect1Origin.y + rect1.height);
Vector2f rect2A = new Vector2f(rect2Origin.x + rect2.width, rect2.y);
Vector2f rect2B = new Vector2f(rect2Origin.x, rect2Origin.y
+ rect2.height);
Vector2f rect2C = new Vector2f(rect2Origin.x + rect2.width,
rect2Origin.y + rect2.height);
xAdd = rect2C.x - rect1B.x;
xAdd2 = rect1C.x - rect2B.x;
yAdd = rect2A.y - rect1B.y;
yAdd2 = rect2C.y - rect1A.y;
if (rect1Mid.y < rect2Mid.y) {
if (rect1.intersects(rect2)) {
y_pos += yAdd;
}
}
if (rect1Mid.y > rect2Mid.y) {
if (rect1.intersects(rect2)) {
System.out.println(yAdd2);
y_pos += yAdd2;
}
}
if(rect1Mid.x > rect2Mid.x){
if(rect1.intersects(rect2)){
hitRight = true; x_pos += xAdd;
}
}
if(rect1Mid.x< rect2Mid.x){
if(rect1.intersects(rect2)) {
x_pos += -xAdd2;
}
}
}
Any help would be greatly appreciated
Thanks
Keep two positions for your character - where the it is (as of last frame, move, etc.), and where you want to move it. Then only move it if you don't detect collision - if you don't allow for corrupt state, you don't have to fix it.
Edit: The collision method should be boolean - it should be done before actually moving the character, like
if (!collision(character, tile))
{
doMove(character);
}
else
{
//custom handling if required
}
Edit2: The previous would work only for a small step, if you need a partial move, you really need to know the character's original position, like move(originalPosition, desiredPosition, tile), where you can deduce the direction from the originalPosition and the tile.
The main point is, you don't actually move the character, before you have a valid position for it.
First of all, any Sprite (the character and the tile here) should have four members: xPos, yPos, xVec, yVec. With that in mind, you know where the character was and will be in the next frame.
sprite.xPos += sprite.xVec;
sprite.yPos += sprite.yVec;
Also take a look at your code, you should improve the propositions (for instance, you check if(rect1.intersects(rect2)) in the four if statements). Instead, check only once for the intersects(), and inside check for each possible case (left, right, top, bottom hit).
Finally, the collision method should receive 2 sprites objects (for instance, sprite1 and sprite2), and then check for the intersects() method, taking into account the original positions of both sprites and its vectors. If the sprites intersect in the new position, then you invert the object's vector accordingly.
Assuming you can detect the collision with your code. Here is a way to determine the position of your character with respect to the rectangle object.
Find the middle point of the rectangle. ( Rx,Ry).
Find the middle point of your character sprite. ( Sx,Sy).
Now you can compare Rx,Ry , Sx , Sy to determine in which side is Sx and Sy of Rx and Ry
for ex:
if Sx < Rx then
Sx = left side of Rx
Was struggling with this myself, but after some thinking this check can be done by having all your entities in a List, in my code I had to block the player from moving through blocks. All blocks is in a List. Now, when checking players position I create a new Rectangle and put the players position one frame forward in the direction the update will happened next frame, if the the rectangle intersects that update for that direction will not happened. Heres my code for this:
if (isKeyPressed(KeyEvent.VK_LEFT)) {
if(!collisionWithBlocks(1)){
pl.x = pl.x - updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_RIGHT)) {
if(!collisionWithBlocks(0)){
pl.x = pl.x + updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_UP)) {
if(!collisionWithBlocks(3)){
pl.y = pl.y - updatePlayerPosition;
}
}
if (isKeyPressed(KeyEvent.VK_DOWN)) {
if(!collisionWithBlocks(2)){
pl.y = pl.y + updatePlayerPosition;
}
}
collisionWithBlocks():
public boolean collisionWithBlocks(int side){
for(Block b : main.blocks){
Rectangle block = b.getBounds();
Rectangle player = null;
if(side == 0){
player = new Rectangle(pl.x + updatePlayerPosition, pl.y, pl.getWidth(), pl.getHeight());
} else if(side == 1){
player = new Rectangle(pl.x - updatePlayerPosition, pl.y, pl.getWidth(), pl.getHeight());
} else if(side == 2){
player = new Rectangle(pl.x, pl.y + updatePlayerPosition, pl.getWidth(), pl.getHeight());
} else if(side == 3){
player = new Rectangle(pl.x, pl.y - updatePlayerPosition, pl.getWidth(), pl.getHeight());
}
if(player.intersects(block)){
return true;
}
}
return false;
}
updatePlayerPosition is 2, and changes in my code but enough about that.
In your case, I recommend putting the entities in a List and then checking like I did over.