So I'm trying to figure out how to implement a method of selecting lines or edges in a drawing area but my math is a bit lacking. This is what I got so far:
A collection of lines, each line has two end points (one to start and one to end the line)
The lines are drawn correctly on a canvas
Mouse clicks events are received when clicking the canvas, so I can get the x and y coordinate of the mouse pointer
I know I can iterate through the list of lines, but I have no idea how to construct an algorithm to select a line by a given coordinate (i.e. the mouse click). Anyone got any ideas or point me to the right direction?
// import java.awt.Point
public Line selectLine(Point mousePoint) {
for (Line l : getLines()) {
Point start = l.getStart();
Point end = l.getEnd();
if (canSelect(start, end, mousePoint)) {
return l; // found line!
}
}
return null; // could not find line at mousePoint
}
public boolean canSelect(Point start, Point end, Point selectAt) {
// How do I do this?
return false;
}
Best way to do this is to use the intersects method of the line. Like another user mentioned, you need to have a buffer area around where they clicked. So create a rectangle centered around your mouse coordinate, then test that rectangle for intersection with your line. Here's some code that should work (don't have a compiler or anything, but should be easily modifiable)
// Width and height of rectangular region around mouse
// pointer to use for hit detection on lines
private static final int HIT_BOX_SIZE = 2;
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Line2D clickedLine = getClickedLine(x, y);
}
/**
* Returns the first line in the collection of lines that
* is close enough to where the user clicked, or null if
* no such line exists
*
*/
public Line2D getClickedLine(int x, int y) {
int boxX = x - HIT_BOX_SIZE / 2;
int boxY = y - HIT_BOX_SIZE / 2;
int width = HIT_BOX_SIZE;
int height = HIT_BOX_SIZE;
for (Line2D line : getLines()) {
if (line.intersects(boxX, boxY, width, height) {
return line;
}
}
return null;
}
Well, first off, since a mathematical line has no width it's going to be very difficult for a user to click exactly ON the line. As such, your best bet is to come up with some reasonable buffer (like 1 or 2 pixels or if your line graphically has a width use that) and calculate the distance from the point of the mouse click to the line. If the distance falls within your buffer then select the line. If you fall within that buffer for multiple lines, select the one that came closest.
Line maths here:
http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
Shortest distance between a point and a line segment
If you use the 2D api then this is already taken care of.
You can use Line2D.Double class to represent the lines. The Line2D.Double class has a contains() method that tells you if a Point is onthe line or not.
Sorry, mathematics are still required... This is from java.awt.geom.Line2D:
public boolean contains(double x,
double y)
Tests if a specified coordinate is inside the boundary of this Line2D.
This method is required to implement
the Shape interface, but in the case
of Line2D objects it always returns
false since a line contains no area.
Specified by:
contains in interface Shape
Parameters:
x - the X coordinate of the specified point to be tested
y - the Y coordinate of the specified point to be tested
Returns:
false because a Line2D contains no area.
Since:
1.2
I recommend Tojis answer
Related
I'm working on a collision system for my game, however I can't get it to work, if I add more than one wall (which is the object I'm rendering) the collision system doesn't work and I can get through the block.
However if I leave only one wall the collision works correctly, or if at the end of the loop I add a break;
the collision works but only on the first wall of the map, the others don't get the collision.
Would anyone know how to solve this? I've been trying to solve it for 2 days and I couldn't.
public boolean checkCollisionWall(int xnext, int ynext){
int[] xpoints1 = {xnext+3,xnext+3,xnext+4,xnext+3,xnext+3,xnext+4,xnext+10,xnext+11,xnext+11,xnext+10,xnext+11,xnext+11};
int[] ypoints1 = {ynext+0,ynext+8,ynext+9,ynext+11,ynext+12,ynext+15,ynext+15,ynext+12,ynext+11,ynext+9,ynext+8,ynext+0};
int npoints1 = 12;
Polygon player = new Polygon(xpoints1,ypoints1,npoints1);
Area area = new Area(player);
for(int i = 0; i < Game.walls.size(); i++){
Wall atual = Game.walls.get(i);
int[] xpoints2 = {atual.getX(),atual.getX(),atual.getX()+16,atual.getX()+16};
int[] ypoints2 = {atual.getY(),atual.getY()+16,atual.getY()+16,atual.getY()};
int npoints2 = 4;
Polygon Wall = new Polygon(xpoints2,ypoints2,npoints2);
area.intersect(new Area(Wall));
if(area.isEmpty()){
return true;
}
}
return false;
}
I'm pretty sure the problem is this call:
area.intersect(new Area(Wall));
Here's the JavaDoc for that method:
public void intersect(Area rhs)
Sets the shape of this Area to the intersection of its current shape
and the shape of the specified Area. The resulting shape of this Area
will include only areas that were contained in both this Area and also
in the specified Area.
So area, which represents the shape of your player, is going to be modified by each test with a wall, which is why it's only working with one wall.
You could fix the issue by simply making the player Area the argument of the call, as in:
Area wallArea = new Area(Wall);
wallArea.intersect(area);
if(wallArea.isEmpty()){
return true;
}
By the way, this logic is reversed isn't it. Don't you want to check that the resulting area is not empty, i.e. there was an overlap between the player and the wall?
The other option, if each Wall is actually a rectangle, would be to use the this Area method instead:
public boolean intersects(double x,
double y,
double w,
double h)
Tests if the interior of the Shape intersects the interior of a
specified rectangular area. The rectangular area is considered to
intersect the Shape if any point is contained in both the interior of
the Shape and the specified rectangular area.
Something like this:
if(area.intersects(atual.getX(), atual.getY(), 16, 16)) {
return true;
}
As this avoids the creation of an Area object for each wall, and the intersection test is going to be much simpler, and faster.
I'm writing a recursive method in Java that creates essentially creates a circle tree. It draws a circle at the top and center and then the method gets called again and creates the circles one level lower on the y axis and half way to the left and right of the new circle. I was successful but only for a certain number of objects to be drawn This is what it looks like
public void test(Graphics g, int y, int num, double instance) {
if(num<50) {
int r = 20;
for(int i=1;i<=instance;i++) {
if(i%2==1) {
g.fillOval(getWidth() * i / num, y, r, r);
}
}
if(instance==1){
instance= 2* instance;
}
test(g, y + 20, num * 2, Math.pow(instance,2.0));
}
Everything works perfectly until I try to increase the number in "if(num<50)" to exactly "if(num<65)". When I change that the JFrame appears but now it is empty and it seems like the program is frozen. I want to increase that so that I can fill the Jframe with the circle tree. Why is it doing that? Looking forward to your response! Thank you!
I found the issue. I don't know why I choose to use Math power when I only had to use *2 and that fixed the whole program.
I'm trying to write a class that shows vectors. If I create one vector object everything works as intended. In my example code the object lin1 gets drawn with the help of the draw() function.
If I now create a second vector object, the (unchanged) draw-function doesnt do anything anymore, even though the object itself is unchanged. It's the same the other way around: Is the second object the only one existing, then it can be drawn, but only as long as lin1 doesnt exist.
Does anyone know where my mistake is?
vector lin;
vector lin2;
void setup()
{
size(500,500);
background(255);
cenX = width/2;
cenY = height/2;
noLoop();
}
void draw()
{
coordSys();
lin = new vector(0,0,100,100);
lin2 = new vector(0,0,-200,-200);
lin.draw();
lin2.draw();
lin.getAll();
}
class vector
{
float x1,y1,x2,y2;
float length;
float angle;
float gegenK, anK;
vector(float nx1, float ny1, float nx2, float ny2)
{
translate(cenX,cenY);
x1 = nx1; y1 = -ny1; x2 = nx2; y2 = -ny2;
strokeWeight(2);
// Gegenkathete
gegenK = ny2 - ny1;
// Ankathete
anK = x2 - x1;
// length and angle
length = sqrt(sq(anK) + sq(gegenK));
angle = winkel(gegenK, anK);
}
void draw()
{
stroke(0);
line(x1,y1,x2,y2);
}
}
}
Please use standard naming conventions when writing code. Specifically, your class should be Vector with an upper-case V. Also, please post your code in the form of an MCVE that compiles and runs.
Anyway, the first call in your Vector() constructor is this:
translate(cenX,cenY);
This moves the origin of the window halfway across the window. When you do this once, this simply makes your drawing calls relative to the center of the window. But when you do this twice, it moves the origin to the bottom-right corner of the window, so all of your drawings are moved off the edge of the screen.
To fix your problem, you need to move this line so it only happens once (perhaps at the beginning of the draw() function) instead of every single time you draw a Vector. Another way to approach this would be to use the pushMatrix() and popMatrix() functions to avoid this stacking of window translations.
I am using the following method to try to find a point (coordinate) that hasn't been previously used, and isn't within the bounds of items that have previously used and coordinates.
The way it works is I am rendering "bases" (RTS top-down game), and I am creating two random variable locations for x and y. I pass these, along with the bases texture, into the following method. The method loops through a list of rectangles that are the rectangles of each previously rendered base. If the point is within any of the rectangles, the method is called again using a different set of coordinates. It does this until it finds a set that isn't within a rectangle. It then adds a new rectangle to the list at these coordinates, and returns them so the game can render a new base.
However, the bases still overlap.
Here is the method:
private Point getCoords(int x, int y, Texture t){
for (int i=bases.size()-1; i> -1; i--) {
if (bases.get(i).contains(new Point(x,y))){
x = new Random().nextInt(map.getWidth() * map.getTileWidth());
y = new Random().nextInt(map.getHeight() * map.getTileHeight());
getCoords(x, y, t);
}
}
bases.add(new Rectangle(x,y,t.getImage().getWidth(), t.getImage().getHeight()));
return new Point(x, y);
}
And here is where it is being called:
switch(ran){
default:
int x = new Random().nextInt(map.getWidth() * map.getTileWidth());
int y = new Random().nextInt(map.getHeight() * map.getTileHeight());
Point p = getCoords(x, y, temp);
map.generateBase("air", p.x, p.y);
break;
}
Any ideas what is wrong here?
Thanks
There are several problems:
Your algorithm might be overwritting good coordinates (free ones) with wrong coordinates, you dont have any condition to exit the loop/recursion if you find a good place
You are checking for if rectangle contains the point, but later you are adding a rectanble, so it may not contain the point, but the rectangle created later may collide
try this
private Point getCoords(int x, int y, Texture t){
boolean found = false;
final int width = map.getTileWidth();
final int height = map.getTileHeight();
while(!found) {
x = new Random().nextInt(map.getWidth() * width);
y = new Random().nextInt(map.getHeight() * height);
for (int i=bases.size()-1; i> -1; i--) {
if (!bases.get(i).intersects(new Rectanble(x,y, width, height))){
found = true;
} else found = false;
}
}
bases.add(new Rectangle(x,y,t.getImage().getWidth(), t.getImage().getHeight()));
return new Point(x, y);
}
*** EDIT: Im not sure if I had to use TileWidth and TileHeight or image width and image height for width and height :D
int x = new Random().nextInt(map.getWidth() * map.getTileHeight());
Maybe a bad copy paste. It may be :
int x = new Random().nextInt(map.getWidth() * map.getTileWidth());
In both codes :-D
Okay so after some playing around, I found the issue is the rectangles that are saved are saved with a fixed location which means as the map moves, the rectangles don't. The fix is to loop through each bases and get the base's map position, rather than screen position, and check against this. Also, I found i was checking for a point in a rectangle, which may be outside the rectangle but leaves my bases overlapping still. So i now check for rectangle-rectangle collision instead
I have built a custom component that shows only a line. The line is drawn from the top left corner to the bottom right corner as a Line2D at the paint method. The background is transparent. I extended JComponent. These line components are draggable and change their line color when the mouse pointer is located max. 15 pixels away from the drawn line.
But if I have multiple of these components added to another custom component that extends JPanel they sometimes overlap. I want to implement that if the mouse pointer is more than 15 pixels away from the line the mouse events should fall through the component. How to let it fall through is my problem.
Is that even possible?
Thanks in advance!
I want to implement that if the mouse pointer is more than 15 pixels
away from the line the mouse events should fall through the component.
If your child component has a mouse listener, then it will intercept every mouse event occurring over it. If you want to forward the MouseEvent to the parent Component you should manually do it. For example you can implement your custom mouse listener extending MouseAdapter:
public class yourMouseListener extends MouseAdapter{
//this will be called when mouse is pressed on the component
public void mousePressed(MouseEvent me) {
if (/*do your controls to decide if you want to propagate the event*/){
Component child = me.getComponent();
Component parent = child.getParent();
//transform the mouse coordinate to be relative to the parent component:
int deltax = child.getX() + me.getX();
int deltay = child.getY() + me.getY();
//build new mouse event:
MouseEvent parentMouseEvent =new MouseEvent(parent, MouseEvent.MOUSE_PRESSED, me.getWhen(), me.getModifiers(),deltax, deltay, me.getClickCount(), false)
//dispatch it to the parent component
parent.dispatchEvent( parentMouseEvent);
}
}
}
For my final year project at university I did a whiteboard program and had the same problem. For each shape the user drew on the board I created a JComponent, which was fine when they were drawing rectangles, but more difficult with the free form line tool.
The way I fixed it in the end was to do away with JComponents altogether. I had a JPanel which held a Vector (I think) of custom Shape objects. Each object held its own coordinates and line thicknesses and such. When the user clicked on the board, the mouse listener on the JPanel fired and went through each Shape calling a contains(int x, int y) method on each one (x and y being the coordinates of the event). Because the Shapes were added to the Vector as they were drawn I knew that the last one to return true was the topmost Shape.
This is what I used for a straight line contains method. The maths might be a bit iffy but it worked for me.
public boolean contains(int x, int y) {
// Check if line is a point
if(posX == endX && posY == endY){
if(Math.abs(posY - y) <= lineThickness / 2 && Math.abs(posX - x) <= lineThickness / 2)
return true;
else
return false;
}
int x1, x2, y1, y2;
if(posX < endX){
x1 = posX;
y1 = posY;
x2 = endX;
y2 = endY;
}
else{
x1 = endX;
y1 = endY;
x2 = posX;
y2 = posY;
}
/**** USING MATRIX TRANSFORMATIONS ****/
double r_numerator = (x-x1)*(x2-x1) + (y-y1)*(y2-y1);
double r_denomenator = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
double r = r_numerator / r_denomenator;
// s is the position of the perpendicular projection of the point along
// the line: s < 0 = point is left of the line; s > 0 = point is right of
// the line; s = 0 = the point is along the line
double s = ((y1-y)*(x2-x1)-(x1-x)*(y2-y1) ) / r_denomenator;
double distance = Math.abs(s)*Math.sqrt(r_denomenator);
// Point is along the length of the line
if ( (r >= 0) && (r <= 1) )
{
if(Math.abs(distance) <= lineThickness / 2){
return true;
}
else
return false;
}
// else point is at one end of the line
else{
double dist1 = (x-x1)*(x-x1) + (y-y1)*(y-y1); // distance to start of line
double dist2 = (x-x2)*(x-x2) + (y-y2)*(y-y2); // distance to end of line
if (dist1 < dist2){
distance = Math.sqrt(dist1);
}
else{
distance = Math.sqrt(dist2);
}
if(distance <= lineThickness / 2){
return true;
}
else
return false;
}
/**** END USING MATRIX TRANSFORMATIONS****/
}
posX and posY make up the coordinates of the start of the line and endX and endY are, yep, the end of the line. This returned true if the click is within lineThickness/2 of the centre of the line, otherwise you have to click right along the very middle of the line.
Drawing the Shapes was a case of passing in the JPanel's Graphics object to each Shape and doing the drawing with that.
It's been a while since I touched Swing, but I think you will need to handle your mouse events in the parent component and then loop through the child components with lines and determine which one of them should handle the event (well, the logic of deciding should still remain in the line component, but parent will explicitly invoke that logic until one of the components takes the event).
I believe that the easiest way is to catch the event and call parent.processEvent(). So, you component will be transparent for events because it will propagate them to parent.
I was struggling with this sort of question and tried all the stuff with parents and glasspane until i realised that override of contains method does just what you want. Because when parent fires some sort of getcomponent your 'Line' will reply to it: 'no, its not me, i'm not there!' and the loop will check other components.
Also, when you need to setup a complex depth to your draggable object, you can use JLayeredPane descendant.