Position an image inside a rotating image - java

I have two images, one for a zombie and one for a blade that the zombie will have a 10 % chance to spawn with. I want to position the blade so that it will overlap the zombies right hand. I would be able to do this if the zombie was the same height as width but because it is 62x55 it makes everything much more complex. The code that positions the blade and the zombies right now looks like this:
at = new AffineTransform();
angle = Math.atan2(player.getY() - zombie.getY(), player.getX() - zombie.getX()) + (Math.PI / 2);
at.setToTranslation(zombie.getX(), zombie.getY());
at.rotate(angle, zombie.getWidth() / 2, zombie.getHeight() / 2);
g2d.drawImage(zombie.getBrImage(),at,this);
ImageIcon ii = new ImageIcon("Blade.png");
Image image = ii.getImage();
int x = (int) Math.round(zombie.getX() + 47 * Math.sqrt(2) * Math.cos(angle - Math.PI / 4));
int y = (int) Math.round(zombie.getY() + 47 * Math.sqrt(2) * Math.sin(angle - Math.PI / 4));
at.setToTranslation(x, y);
at.rotate(angle, zombie.getWidth() / 2, zombie.getHeight() / 2);
g2d.drawImage(image, at, this);
This will produce the following result:
As you can see the blade is positioned at a higher y (lower y if you count 0 as the top of the screen) than is should, because the zombie will rotate so that it always faces the player I can't just change the number 47 for y because then the blade wont rotate properly.
So how would I make the blade always be positioned properly so that it would always produce the following result no matter what angle the zombie is facing?

You could have two images:
one without the blade (Img1)
one with the blade (Img2): taking the zombie and the blade, and compositing it at the beginning, e.g. in the constructor.
Then you could just say:
if (randomValue < 0.10) { zombieImg = Img1 } else { zombieImg = Img2}
And then just translate/rotate/etc. the ZombieImg after the initialization.
What do you think? :-)

Related

Code works only on one object at a time

Im trying to build a simple game, built on the bases of RealTutsGML wave game(I assume some here knows it so it might help). Anyway, Im trying to make a "bot player" that will avoid the enemies and I believe that everything should be right, but for some reason it works only on one enemy at a time(Im adding enemies to the game every few seconds and the new enemy is the only enemy that works). The weird thing is, I created a circle around my enemy, and with that circle I detect nearby enemies, I have the following code: if (circle.intersects(rectangle)), the circle recieves the player position and updates all the time, and the rectangle gets the enemies position through a for loop:
for (int i = 0; i < handler.object.size(); i++) {
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.basicEnemy) {
rectangle = new Rectangle((int) tempObject.getX(), (int) tempObject.getY(), 16, 16); }
I set a print line if a collision occures, just to test and see, and whenever enemies hit the circle around the player, it prints "collision", it works with all enemies. but the following code that causes the player to move, only occures when the last created enemy object hits.
Thats how the whole method looks like:
public void AImove() {
for (int i = 0; i < handler.object.size(); i++) {
circle = new Ellipse2D.Double((int) player.getX() - 48, (int) player.getY() - 48, 130, 130);
GameObject tempObject = handler.object.get(i);
if (tempObject.getId() == ID.basicEnemy) {
rectangle = new Rectangle((int) tempObject.getX(), (int) tempObject.getY(), 16, 16);
}
if (rectangle != null && circle != null) {
if (circle.intersects(rectangle)) {
float diffX = rectangle.x - player.getX() - 8;
float diffY = rectangle.y - player.getY() - 8;
float distance = (float) Math.sqrt((rectangle.x - player.getX()) * (rectangle.x - player.getX())
+ ((rectangle.y - player.getY()) * (rectangle.y - player.getY())));
float newX = ((-1 / distance) * diffX * 10);
float newY = ((-1 / distance) * diffY * 10);
player.setVelX(newX);
player.setVelY(newY);
} else if (player.getX() != WIDTH / 2 - 32 && player.getY() != HEIGHT / 2 - 32) {
float diffX = player.getX() - 368 + 10;
float diffY = player.getY() - 268 + 10;
float distance = (float) Math.sqrt((player.getX() - 368) * (player.getX() - 368)
+ ((player.getY() - 268) * (player.getY() - 268)));
player.setVelX((-1 / distance) * diffX * 5);
player.setVelY((-1 / distance) * diffY * 5);
}
}
}
}
(Just a short exlanation, newX/Y algorithm is not setting the player in a new position, it just sets the velocityX/Y, which moves the player to the oopsite direction of the collided enemy, if there is no collision, the player will try to go back to the center of the screen using the same algorithm).
I've also tested with a prints of the "supposed to be" new velX and velY, like so:
System.out.println((-1 / distance) * diffX * 10);
System.out.println((-1 / distance) * diffY * 10);
And again, it gives the right value no matter which enemy collides with the player's circle, but for some reason it just doesn't move, and works ONLY when the last created enemy object hits the circle.
Im kinda lost, as I really don't see a reason it wouldn't work, the condition occures, the code that needs to be executed works, but it just doesn't.
Sorry for the long post, I tried to be as specific and short as I can, if more code is needed please let me know, I just don't want to add too much code. Thanks for the help!
edit - https://github.com/pardovot/MyProjects/tree/master/AImove/Pong - link to the project in github, so you'll have all the needed information about it.
I assume that what Dezigo said about the last iteration replating the values is corret, does anyone has any ideas what I can do to fix that? Because overall I don't see a real reason for that to happen, as the actual code should run in each iteration, and actually work(which doesn't obviously....)

How to make circles in a panel not touch the borders? (Java Graphics)

I would like to create circles with a random diameter, position and color in a panel with the Graphics class in Java. It works fine, however I have the problem that often circles touch or overlap the border.
How can I make the circles fully stay within the panel?
Thanks in advance!
This is the code I use for creating the circles:
amount = rnd.nextInt(10);
for (int i = 0; i < amount; i++){
x = (50 + rnd.nextInt(panel.getWidth() - 50 + 1)) - 50;
y = (50 + rnd.nextInt(panel.getHeight() - 50 + 1)) - 50;
diameter = (rnd.nextInt(100));
gr.setColor(new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)));
gr.fillOval(x, y, diameter, diameter);
gr.drawString("" + (panel.getHeight() - y), x, y);
}
x = (50 + rnd.nextInt(panel.getWidth() - 50 + 1)) - 50;
y = (50 + rnd.nextInt(panel.getHeight() - 50 + 1)) - 50;
diameter = (rnd.nextInt(100));
How can you calculation the x/y position if you don't know what the diameter of the circle is going to be?
First you need to calculate the diameter. Then you need to use the diameter to make sure the x/y plus the diameter is less than the width of the panel.
I don't understand the point of adding 50 and then subtracting 50. I would think the random number should be the width minus the diameter.

Drawing a spider

I need to draw a spider using the Graphics package. The problem though is that its size, number of legs and eyes are specified by the user.
I've got a few questions:
how can I randomly select a point on a circle so I can draw a line (for legs) from there while keeping in mind that drawLine(), for instance, takes only integer arguments?
how can I randomly select a point inside the circle used as a center of an eye so that the circle (eye) fits within the ranges of an outer circle (body)?
Selecting a point on a circle just requires getting a random angle. Java uses radians for it's trigonometric functions so a random double between 0 and 1 is multiplied by 2π.
Random r = new Random();
double angle = r.nextDouble() * Math.PI * 2;
Drawing legs is simple trigonometry which requires finding the x and y of each line. For this sine and cosine functions are used. The line can then be drawn off the center point of the circle (centerX and centerY), ending at a specified length in pixels (legLength).
The process can be repeated to draw multiple legs with a specified offset (legOffset) and repeated and offset again (by π) to draw legs on the other side.
for (int i = 0; i < 4; i++) {
int lineX = (int) radius * Math.cos(angle);
int lineY = (int) radius * Math.sin(angle));
g.drawLine(circleX + lineX , circleY + lineY , circleX + lineX * legLength, circleY + lineY * legLength);
angle += legOffset;
}
Drawing the eyes is essentially the same process as the legs. Each eye can drawn at a specified angle and distance from the center of the circle.
int eyeX = (int) distance * Math.cos(angle);
int eyeY = (int) distance * Math.sin(angle));
g.fillOval(eyeX - eyeRadius, eyeY - eyeRadius, eyeRadius* 2, eyeRadius* 2);
The easiest way to get random integers is to create an instance of Random and with random.nextInt(bound) you get an integer between 0 (inclusive) and bound (exclusive), [0, bound).
Instead of selecting the upper left corner of the spider, I would randomly select the center of the spider and then draw everything in relation to it.
Now let's define the radius r = size / 2.
Selecting a random point with insuring that the spider is fully visible:
x = r + random.nextInt(width - 2 * r);
y = r + random.nextInt(height - 2 * r);
Drawing the body with a diameter of r and not 2r to ensure the legs are visible: g.fillOval(x - r / 2, y - r / 2, r, r);
Drawing the legs and eyes: There are numerous strategies, you could draw lines from the center with length r for the legs and very small circles at distance r/4 from the center for the eyes. After selecting an initial random angle, you can use the golden angle to calculate the position of the next leg / eye, this ensures they are never drawn at the same positon (https://en.wikipedia.org/wiki/Golden_angle).
Note: draw the legs first, then the body and the eyes last.

Rotation animation with custom view

I have a custom View, IndicatorView, which is essentially a triangle that orients itself according to a specified angle of a circle with a radius equal to the triangle's length. The angle the triangle points to is frequently updated and I would like to animate between these two positions similar to how a hand on a clock moves. Below is an illustration of my custom view (not drawn proportionally or to scale; drawn according to the Android View coordinate plane):
In the IndicatorView class, I draw the triangle using a Path object and three PointF objects:
#Override
protected void onDraw(Canvas canvas){
path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
//a, b, and c are PointF objects
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.close();
canvas.drawPath(path, paint);
}
To calculate the different points, given the angle, I use parametric equations:
public void showAngle(){
//x = centerX + radius * cos(angle)
//y = centerY + radius * sin(angle)
//TODO sloppy; tidy up / optimize once finished
//centerX, centerY, length, and bottomWidth are all values
//calculated in onSizeChanged
a = new PointF((float) (centerX + (length * Math.cos(angle))), (float) (centerY + (length * Math.sin(angle))));
//perpendicular bilateral radius
double pRadius = bottomWidth / 2;
//perpendicular angle plus or minus 90 degrees depending on point
float pAngle = angle - 90;
pAngle = (pAngle < 0) ? 360 - Math.abs(pAngle) : pAngle;
pAngle = (pAngle > 360) ? pAngle % 360 : pAngle;
b = new PointF((float) (centerX + (pRadius * Math.cos(pAngle))), (float) (centerY + (pRadius * Math.sin(pAngle))));
pAngle = angle + 90;
pAngle = (pAngle < 0) ? 360 - Math.abs(pAngle) : pAngle;
pAngle = (pAngle > 360) ? pAngle % 360 : pAngle;
c = new PointF((float) (centerX + (pRadius * Math.cos(pAngle))), (float) (centerY + pRadius * Math.sin(pAngle)));
invalidate();
}
When I have a new angle, I use an ObjectAnimator to animate between the two angles. I place an AnimatorUpdateListener on the ObjectAnimator and call my showAngle() method in my IndicatorView using the intermediate values specified from the Animator:
public void updateAngle(float newAngle){
//don't animate to an angle if the previous angle is the same
if(view.getAngle() != newAngle){
if(anim != null && anim.isRunning()){
anim.cancel();
}
anim = ObjectAnimator.ofFloat(view, "angle", view.getAngle(), newAngle);
anim.setDuration(duration);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
if(view != null){
view.showAngle();
}
}
});
}
}
However, this code produces some strange and unexpected behavior:
The width size of the triangle changes somewhat drastically. This could be due to casting between different types but it shouldn't be that dramatic.
The point of the triangle never stops at the specified angle. Instead it just keeps moving in a circle.
The angle seems to dictate the animations speed rather than where the triangle should stop.
Sometimes it seems as though there are numerous triangles on the screen. This could be due to the speed, perhaps it's moving very fast.
Obviously, somewhere along the line my calculations must be incorrect, though, I'm struggling to find out where I went wrong. Question(s): Is there a more efficient way of getting my custom view to animate rotation to a given angle? If I am approaching this correctly, where am I going wrong?
So, the solution to my problem was rather simple but simple enough to be overlooked. The angle field that was being used for the calculations was in degrees and it just had to be converted to radians in order for it to work with the sin and cos methods.
Change all PointF instantiations, for instance:
a = new PointF((float) (centerX + (length * Math.cos(angle))), (float) (centerY + (length * Math.sin(angle))));
to use the angle in radians:
a = new PointF((float) (centerX + (length * Math.cos(Math.toRadians(angle))),
(float) (centerY + (length * Math.sin(Math.toRadians(angle)))));
Also, part of the problem was due to sound constantly being analyzed and the View being updated before the previous animation had time to render a few frames. This led to the IndicatorView hardly moving when the angle was being updated often and when it was not it would quickly move to its destination. This happens because the previous animation is canceled before another animation is set (which is necessary to prevent a delay). This is a tricky problem to fix but one optimization I found was to avoid starting a new animation if the current angle and the previous angle were relatively close to each other.
Hopefully this will be useful for someone stuck with a similar problem. This was all part of a guitar tuner project I was working on and the source can be found on GitHub.

How to adjust sin (x) graph coordinates in java to fill only a portion of the window

so currently I am working on a project for class in which I am required to draw the sin(x) function via the drawLine() method. Currently this is the loop I am using to achieve this:
int xShift = getWidth() / 50;
int xShift2 = getWidth() / 100;
int yShift = getHeight() / 10;
int yShift2 = getHeight() / 17;
int xStart = xShift;
int xEnd = xShift;
int yStart = getHeight() / 2;
int yEnd = getHeight() / 2;
int scale = getHeight() / 2;
for (double i = Math.PI / 32; i <= Math.PI * 2; i+= Math.PI / 32){
xEnd += getWidth() / 64;
yEnd = scale - ((int) Math.round(Math.sin(i) * scale));
g.drawLine(xStart, yStart, xEnd, yEnd);
xStart = xEnd;
yStart = yEnd;
}
This outputs something that looks like this:
What I want to change is that the graph will be within the constraints of the dashed blue lines, and it also will go to the end of the grey line where the 2pi marker is (marked by the blue arrow). How can I go about making these changes?
Note: this is what it looks like when I maximize the window:
For some reason the sin graph goes beyond the bounds I want it to.
Thank you for your time and I appreciate any help you can offer.
First, let's see what you are doing with your calculation of Y.
yEnd = scale - ((int) Math.round(Math.sin(i) * scale));
Since the actual sine function goes between -1 and 1, it means that your yEnd will go between scale - scale and scale + scale. This means it will go between 0 (the edge of the window), and 2×scale. Since your scale is half the height of the window, 2×scale means the full height of the window. Again - the edge of the window.
First, think what happens if your scale is smaller. If instead of height/2, the scale is (height/2 - 10), then 2×scale will be the window height - 20. That's more or less the amplitude you want - but it still scale - scale, so it still starts from the edge (try it!). Reducing scale further will lower the amplitude, but still, you will start from the edge.
To prevent that, you should change the formula. It shouldn't be adding the sine to scale. Think: when the sine is -1, you want it to be at the greatest distance from the middle of the window. When it's +1, you want the line to be at the greatest distance, again, from the middle of the window. Now that you changed scale, it's no longer half the height of the window, so you shouldn't use it for your base line.
You should have one parameter that says "what is the base height of my graph", and one parameter that says "what is the maximum amplitude of my graph". The two parameters shouldn't be the same:
int baseHeight = getHeight() / 2;
int amplitude = getHeight() / 2 - getHeight() / 50;
...
// In the loop
yEnd = baseHeight + ((int) Math.round(Math.sin(i) * amplitude));
Play around with these parameters and you'll see how they affect the way your graph is drawn.
Now as for your X. You want to do 64 steps that represent 2π. But if you want the graph to be less wide than the window, the step size cannot be getWidth() / 64. You start width/50 from the edge, add 63 * getWidth() / 64 to that. Since width/50 is more than width/64, you will be drawing more than your width.
So you need to calculate the actual final width of the graph: It's supposed to be the total width, excluding the right and the left margins. So getWidth() - 2 * getWidth()/50 is the actual width, and each step should be 1/64 of that.
int step = ( getwidth() - getWidth() / 25 ) / 64;
...
// In the loop
xEnd += step;

Categories