Transformation of objects to different coordinate sets - java

I am using a tree structure of objects to control my 2D world of objects. The root element or camera contains a list of sub objects, in this case a simple "hand" object. The hand object contains a list of sub objects which are 5 card objects.
Now the hand object is rotated 90 degrees. The Card objects' x position is increasing. So the result when you view it on the screen is that the cards appear to be building downwards (because the hand object is rotated)
Eventually I will need a card object to transfer to another hand object and I want to do that with smooth animation, so I need to map the card object to the camera's world, but so that it doesn't look like it moved.
For example, let's say one of the card objects has an X position of 100 with a y position of 0. When I rotate the hand object 90 degrees, it appears as if the card object is 0 in the x dimension and 100 in the y position.
If I simply remove the card from the hand and add it to the camera, then the card rotates back and moves in the x position appearing at 100,0.
For all rotations and positions (and scalings) how can I transfer the card object to the camera object and keep it's position on the screen?
In the code below, if the 'g' is pressed, I attempt to remove a card from the hand and add it to the camera. I call a method called 'transformObjectToTheseCoords' but I don't think my math is right...
public void update(LinkedList<Object> messages)
{
super.update(messages);
if(messages.size() > 0 && messages.get(0) instanceof KeyEvent)
{
KeyEvent ke = (KeyEvent)messages.get(0);
if(ke.getKeyChar() == 'g')
{
if(super.getSubObjects().size() > 0)
{
GameObject o = super.getSubObjects().remove(0);
GameObject parent = o.getParentObject();
while(parent != camera)
{
parent.transformObjectToTheseCoords(o);
parent = parent.getParentObject();
}
camera.addSubObject(o);
}
}
}
}
public void transformObjectToTheseCoords(GameObject o)
{
o.xPos += xPos;
o.yPos += yPos;
o.rPos += rPos;
}
If anyone has another technique to use, I would appreciate it.

The solution was to work with AffineTransform matrices... this appropriately handles the x and y transformations between different coordinate sets.
#Override
public void update(LinkedList<Object> messages)
{
super.update(messages);
if(messages.size() > 0 && messages.get(0) instanceof KeyEvent)
{
KeyEvent ke = (KeyEvent)messages.get(0);
if(ke.getKeyChar() == 'g')
{
if(super.getSubObjects().size() > 0)
{
GameObject o = super.getSubObjects().remove(0);
//We are inside the position layout, so first let's transform the object to these coordinates...
o.setRotationPosition(this.rPos);
//It's x,y position are relative to this already, so don't need to change that...
//Now the next layer up is the camera, so let's calculate the new X and Y
float x = (float) ((Math.cos(this.rPos)*o.getXPosition())-(Math.sin(this.rPos)*o.getYPosition())+this.xPos);
float y = (float) ((Math.sin(this.rPos)*o.getXPosition())+(Math.cos(this.rPos)*o.getYPosition())+this.yPos);
o.setPosition(x, y);
camera.addSubObject(o);
}
}
}
}

Related

How to implement multiple walls that detect intersection in Java GUI

I have a GUI Maze game that I am creating and I am having trouble implementing walls that prevent the player from moving. The walls are rectangles but the player is a circle so I had to create a collision detection method between a rectangle and a circle. The problem I am having is that my collidesWith() method is in the player class and it takes a single wall as a parameter and returns a string that tells you which side of the wall is the player intersecting with. This means I can only check if the player is colliding with one wall at a time. Here is the code for the collidesWith() method. x and y are the x and y coordinates of the player since this method is in the player class:
I should mention that the code above doesn't exactly work how I wanted it to since it only works when the player is coming from the sides. If the player is coming from the top or bottom, the player just goes through the wall.
The reason I need this method to return a string to tell me where the player is coming from is so that I can explicitly restrict the movement of the player when the up, down, left, right keys are pressed. This is in another class where all the GUI components are. Here is the code for that. I have created a wall object on top so that it can be passed as a parameter here when checking for intersection:
In my view, it is better to avoid to iterate through the whole array of wallsArraylist. If I understood correctly, it is necessary to explore neighbour cells, if yes, then you can explore neighbours in a two-dimensional array using this approach:
(pseudo-code)
row_limit = count(array);
if(row_limit > 0){
column_limit = count(array[0]);
for(x = max(0, i-1); x <= min(i+1, row_limit); x++){
for(y = max(0, j-1); y <= min(j+1, column_limit); y++){
if(x != i || y != j){
print array[x][y];
}
}
}
}
In addition, try to avoid magic strings. Replace "comingFromRight" and etc to constant string:
public static final String COMING_FROM_RIGHT = "comingFromRight";

Need help fixing a function for drawing hexagons

I've got a program that can hexagonal tiles using images that are loaded into it. It creates these tiles images by replacing any pixels outside the hexagon with transparent pixels using a function called inHex(). This works.
Now I want to create a border on the inner edge of the hexagon. So I modified a copy of inHex(), changing the &&s to ||s, and adding two lines for the flat edges. The problem is that only the upper borders get drawn.
I've tried changing the order of the statements, and replacing all of the ors with separate if statements, no dice. Using System.out.print line in the loop I know hexEdge() doesn't return true when it should except for the upper lines.
public boolean hexEdge(int x, int y)
{
/**
* returns true if the coordinates are inside the hexagon bounded by the width and height of the tile
*/
double slope = (tileHeight/2.0)/(tileWidth);
boolean inside=false;
//check in acordance to sides
if (x<(tileWidth/2 +1) )
{
inside=
( y == (int)(0.25*tileHeight-slope*x) ) ||//this works
( y == (int)(0.75*tileHeight+slope*x) ) ||
(x==0 && y>(int)(0.25*tileHeight) && y<=(int)(0.25*tileHeight) );
}else {
int x2=x-tileWidth/2;
inside =
(y == (int)(0+slope*x2) )||//this works
(y == (int)(tileHeight -slope*x2 ) )||
(x==tileWidth-1 && y>(int)(0.25*tileHeight) && y<=(int)(0.25*tileHeight) );
}
return inside;
}

java movement in a 2d array

In the game i'm building, I have made a basic collision detection system.
My current method is explained below:
I workout where the player will be in the next step of the game:
double checkforx = x+vx;
double checkfory = y+vy;
I then check for a collision with blocks (1) in mapArray.
public static Boolean checkForMapCollisions(double character_x,double character_y){
//First find our position in the map so we can check for things...
int map_x = (int) Math.round((character_x-10)/20);
int map_y = (int) Math.round((character_y-10)/20);
//Now find out where our bottom corner is on the map
int map_ex = (int) Math.round((character_x+10)/20);
int map_ey = (int) Math.round((character_y+10)/20);
//Now check if there's anything in the way of our character being there...
try{
for(int y = map_y; y <= map_ey; y++){
for(int x = map_x; x <= map_ex; x++){
if (levelArray[y][x] == 1){
return true;
}
}
}
}catch (Exception e){
System.out.println("Player outside the map");
}
return false;
}
If true is returned {nothing}
If false is returned {Player physics}
I need the player to be able to land on a block and then be able to walk around but I cannot find and adequate tutorial for this.
Can someone give me an idea on how to run my collision detection and/or movement?
There are 2 parts to this question. Collision detection, meaning determining whether a volume is touching or intersecting another volume. The second is collision response. Collision response is the physics portion.
I'll cover collision detection here as that's primarily what you asked about.
Ddefine a class for the map like so:
int emptyTile = 0;
//this assumes level is not a ragged array.
public boolean inBounds(int x, int y){
return x>-1 && y>-1 && x<levelArray[0].length && y<levelArray.length;
}
public boolean checkForCollisions(Rectangle rectangle){
boolean wasCollision = false;
for(int x=0;x<rectangle.width && !wasCollision;x++){
int x2 = x+rectangle.x;
for(int y=0;y<rectangle.height && !wasCollision;y++){
int y2 = y+rectangle.y;
if(inBounds(x2,y2) && levelArray[y2][x2] != emptyTile){
//collision, notify listeners.
wasCollision=true;
}
}
}
}
Do not make your methods static. You probably want more than one instance of a level right? Static is for when you need to share state which remains constant across multiple instances of a class. Level data will surely not remain constant for every level.
Instead of passing in a coordinate, try passing in an entire rectangle. This rectangle will be the bounding box of your character (the bounding box is also sometimes referred to as AABB, which means Axis-aligned bounding box, just FYI in case you're reading tutorials online for this sort of thing.) Let your Sprite class decide what its bounding rectangle is, that's not the map class's responsibility. All the map should be used for is maybe rendering, and whether a rectangle is overlapping tiles which are not empty.
I am sorry for a very shitty explanation but here is my github code and it will help better.
https://github.com/Quillion/Engine
Just to explain what I do. I have character object (https://github.com/Quillion/Engine/blob/master/QMControls.java) and it has vectors and a boolean called standing. Every time boolean standing is false. Then we pass it to the engine to check for collision, if collision happens then standing is true and y vector is 0. As to x vector whenever you press any arrow keys you make the xvector of the object to whatever value you want. And in the update loop you displace the given box by the amount of speed.

How to do you make a click area be only part of a non rectangular part of an image?

I am working with images only and the dimensions of the window that I am using to view my application may be different on different systems. I have a mouse action listener that is listening for clicks on the main view of my program. I have a rounded rectangle that looks like a button. I want to make it so that way the mouse action listener only listens to the area of the rounded rectangle rather than the entire image on all systems. Like the title says, not the entire image has content, in particular, the corners don't look like they are part of the image, so I don't want to allow the user to be able to click on parts of the image without content and get the same result as if they clicked on the part with content.
My image looks similar to this
(source: youthedesigner.com)
So I only want the program to do something if the user clicks on the button inside the image rather than the nice stuff around the button.
This is what I have right now to listen to clicks:
#Override
public void mouseClicked(MouseEvent e) {
for(int i = 0; i <= 200; i++) {
if(e.getY() >= 100+i && e.getY() <= 300) {
if(e.getX() >= 10+100-Math.pow(10000-(Math.pow((i-100),2.0)),.5)) && e.getX() <= 10+400-Math.pow(10000-(Math.pow((i-100),2.0)),.5))) {
// do stuff
i = 201;
}
}
}
}
The math equation I am using in my code looks like 110-(10000-(y-100)^2)^(1/2)), which, if graphed, would look like an open parenthesis, and 410+(10000-(y-100)^2)^(1/2)), which would look like a close parenthesis 400 units away from the first graph.
The code works fine on my system, but on other systems, it doesn't work at all and I am curious how I could move the location I am listening to to correspond to how the image is scaled.
Thank you very much for any help you can provide.
The for-loop is superfluous.
You could ensure that pixels outside the button (.png) have some transparency, and then check for the alpha color component.
In this case you could add a Rect and look for that:
private boolean insideButton(Rectangle buttonRect, Point mousePt) {
if (buttonRect.contains(mousePt)) {
int r = buttonRect.height() / 2;
if (mousePt.x < r) {
// Left circle with O at (r, r)
int xFromO = r - mousePt.x;
int yFromO = r - mousePt.y;
if (xFromO * xFromO + yFromO * yFromO > r * r) {
return false; // Outside circle
}
}
if (mousePt.x > buttonRect.right - r) {
// Right circle:
...
}
return true;
}
return false;
}
So, I used Joop's answer to solve my problem. His answer wasn't quite what I was looking for, but it gave me the idea I needed to solve my problem. The solution I came to was:
private boolean insideButton(Rectangle buttonRect, Point mousePt) {
if (buttonRect.contains(mousePt)) {
int r = (int)buttonRect.getHeight()/2; // radius of either of the circles that make up the sides of the rectangle
if(mousePt.x <= buttonRect.getWidth()/2) { // if it is on the left of the button
Point center = new Point((int)buttonRect.getX()+r, (int)buttonRect.getY()+r); // the center of the circle on the left
double lengthToPoint = Math.pow(Math.pow(mousePt.x-center.x, 2)+Math.pow(mousePt.y-center.y, 2), 1.0/2); // length from center to the point that the user clicked at
if(lengthToPoint > r && mousePt.x < center.x) { // if it is to the left of the center and out of the circle
return false;
} else {
return true;
}
} else { // if it is on the right, the rest of the code is just about the same as the left circle
Point center = new Point((int)buttonRect.getWidth()-r, (int)buttonRect.getY()+r);
double lengthToPoint = Math.pow(Math.pow(mousePt.x-center.x, 2)+Math.pow(mousePt.y-center.y, 2), 1.0/2);
if(lengthToPoint > r && mousePt.x > center.x) {
return false;
} else {
return true;
}
}
} else {
return false;
}
}
I know it is goes a little overboard with calculations and inefficient, but I wanted to present it this way to show a better idea of how my solution works.
I can think of at least two ways.
The first is to produce a mask image (black and white), where (for example) white would indicate the clickable area. Basically, you could compare the pixel color of the mask based in click pick point of the original image.
The other way would be to build a image map, basically using something like a Shape API to allow for non-rectangular shapes. This would allow to use Shape#contains to determine if the mouse clicked inside it or not
In either case, you need to take into account the x/y position of the original image

How to build click through components in Java swing?

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.

Categories

Resources