How to rotate a (muzzle)point to follow a rotated (weapon)image in 2D? [SOLVED]

Firstly, I had posted this in the Java2D section, but came to think it was more of a n00b-question than a Java2D issue.
I made this little video to better explain what I’m dealing with.

TXrJjKuAQHI

I want the projectiles to spawn at the end of the gun-barrel while the arm is waving and flailing about. Each gun has a different muzzle-offset. How would I go about figuring out the new muzzle-offset for the projectiles after I rotate the arms? Can I somehow rotate a point using the same transform-matrix as I do for the arm (which is calculated every update() so far; I will eventually change that, so it doesn’t run unnecessarily)?

I’ve been pulling hair out of my already balding head for days pondering over this. I mean, I’ve tried circle-math and stuff, but I’m just too confused about which points are important in this endeavour, and which steps to take. I finally got the arms to rotate properly, though.

Note: This is using nothing but Java2D. I know… For my next project I’ll use LWJGL

Here’s some code:

Method in Main-class for shooting projectiles (yeah yeah, it’s a temporary placement):


public void shootProjectile() {
	// 35 and 40 are the currently fixed muzzle-offsets, from player-position (at his feet, in the middle)
	double muzzleX = player.getPosX()+35, muzzleY = player.getPosY()-40;
	    
	double angleInRad = Math.atan2(mouseController.getMouseY()-(player.getPosY()-40), mouseController.getMouseX()-(player.getPosX()+35));
	    
	double angle = (double) angleInRad*180/Math.PI;

	playerProjectiles.add(creator.spawnProjectile("9mm", muzzleX, muzzleY, angle));
}

Creator-class code for spawning projectiles into the world:



public Projectile spawnProjectile(String projectileName, double originX, double originY, double angle){
		// clone the projectile from HashMap, using ID
		Projectile p = Game.projectileMap.get(projectileName).cloneProjectile();
		
		// clone Animation from HashMap, using ID in Projectile
		Animation a = Game.projectileAnimations.get(projectileName).cloneAnimation();
		
		// set Animation on new Projectile object
		p.setAnim(a);
		
		// set init position and angle
		p.setInitX(originX-(a.getImage().getWidth()/2));
		p.setInitY(originY-(a.getImage().getHeight()/2));
		p.setPosX(originX-(a.getImage().getWidth()/2));
		p.setPosY(originY-(a.getImage().getHeight()/2));
		p.setAngle(angle);
		p.setTimeLived(0);
		
		// create the collison rectangle
		p.createMainCollisionRectangle();
		
		// return Projectile
		return p;
	}

Code for rendering projectiles:


for (int i = 0; i < playerProjectiles.size(); i++) {
	
	AffineTransform transform = new AffineTransform();
	Projectile p = playerProjectiles.get(i);
	Point center = p.getImageCenter();

	// translate and rotate the sprite
	transform.setToTranslation(p.getPosX(), p.getPosY());
	transform.rotate(p.getAngle() * Math.PI / 180.0, center.x, center.y);
	            
	// draw it
	g.drawImage(playerProjectiles.get(i).getAnim().getImage(), transform, null);
}

Rotating the arm:


// Transform the arm, using the x,y offsets for the initial placement of the image.
armTransform.setToTranslation(player.getPosX()+player.getArmOffsetX(), player.getPosY()-player.getArmOffsetY());

// Factoring in the shoulder-offset of the arm-image (4 x, -10 y)
armTransform.rotate(Math.atan2(mouseController.getMouseY()-(player.getPosY()-player.getArmOffsetY()+10), mouseController.getMouseX()-(player.getPosX()+4+player.getArmOffsetX())), 4, 10);
            

You are rotating around the spawn point. You want to rotate around the same location as you rotate the arm.

I wouldn’t only rotate the sprite. (That could lead to Collision problems…)

I’d do it with trigonometry. Heard from sinus and cosinus?

Use them here :wink:

You could set other starting positions for the bullet:


double bulletX = Math.cos(Math.toRadians(angleInRadians))*distanceFromArm + muzzleX // mussleX without the distance from the arm. I mean, the position directly on the shoulder of the Player should be mussleX
double bulletY = Math.sin(Math.toRadians(angleInRadians))*distanceFromArm + muzzleY // see the comment on top ;)

I hope this helps. If you get weird issues, try switching sin and cos for x and y. I’m not sure which one to use for x and y :smiley:

Cos is for X, Sin is for Y. :slight_smile:

If the angle is in radians, you don’t want to give it to Math.toRadians(double) XD

Yeah, but that’s only because I did half the work so far. I only did the code to rotate the projectile-images, and that’s all happening by getting the angle from the spawnpoint to the mouse, and it is the spawnpoint I need to rotate around the shoulder.

So, I just woke up and I think I got it right in my dream :stuck_out_tongue:
The spawnpoint is calculated from playerPosition, with an offset relative to that. So if I rotate the playerPosition point around the shoulder, and then add the offset x and y (multiplied by the sin and cos for the angle respectively), that should get me the right point, right?

I’m going to play around with it some more.

Thanks for all the suggestions, guys! I’ll post back if I get it right

You’re absolutely correct :slight_smile:

YAY! That wasn’t so hard! I just had to remember my trigonometry. Man, I’d wish I had known I wanted to be a programmer when I was attending school. Then I might have listened to my SOB teachers, heheh.

Is it bad to make this amount of new variables every time a projectile is fired? Should I make all these variables global, since they’re used all the time, so I don’t put the GC on overdrive?
The shoulder point should be a global final static, really.

This is working perfectly, for anyone who has the same question:


	public void shootProjectile() {
		
		// Currently fixed shoulder-offsets, from player-position (player-position is at his feet, in the middle)
		double shoulderX = player.getPosX(), shoulderY = player.getPosY()-40;
		
	    // Calculate the radians for the angle the bullets should be rotated
	    double angleInRad = Math.atan2(mouseController.getMouseY()-shoulderY, mouseController.getMouseX()-shoulderX);
	    
	    // Calculate the starting-positions of the bullets.
	    Point shoulder = new Point((int)Math.round(shoulderX), (int)Math.round(shoulderY));
	    Point muzzleOffset = new Point((int)Math.round(shoulderX)+2, (int)Math.round(shoulderY)+42);
	    
	    // Find the distance from the shoulder to the muzzle; -8 for correction
	    double distanceFromShoulderToMuzzle = shoulder.distance(muzzleOffset) - 8;
	    
	    // Calculate new start-positions for the bullets
		double bulletX = Math.cos(angleInRad)*distanceFromShoulderToMuzzle + shoulderX;
		double bulletY = Math.sin(angleInRad)*distanceFromShoulderToMuzzle + shoulderY;
		
		// Calculate the angle the bullets should be traveling at; is also used to rotate the projectile images 
	    double angle = (double) angleInRad*180/Math.PI;

		playerProjectiles.add(creator.spawnProjectile("9mm", bulletX, bulletY, angle));
	}

Ups lol. Was late for me :slight_smile:

The primitive doubles are no objects and do not ever meet any GC. They end up on the stack or in registers.
And storing point objects in class variables does not ease the life of the GC if you replace them as frequently as in your actual code. But “shooting garbage” is most likely only a fraction of the whole game loop garbage anyway.
A modern garbage collector takes care of that while still being half-asleep. ;D

Thanks! :smiley:

[quote=“Ultroman,post:8,topic:39253”]
The Oracle JVM can create and destroy 100 million objects per second. You could write a game with thousands of objects and recreate all the objects every single frame with 1000 frames per second and the GC woudn’t break a sweat. On Android however things look different. But since doubles are no objects anyway it doesn’t matter here.

[quote=“DrZoidberg,post:12,topic:39253”]
I’m pretty sure they are: Double extends Number wich extends Object

A good friend of mine once told me: “In Java, EVERYTHING is an object”
And then he killed the dog.
:slight_smile:

[quote="_Al3x,post:13,topic:39253"]

Not the primitives. The primitives are pass-by-value keywords. You can use their object forms, yes, but it’s more common to use the primitives. So, not everything is an object. And I feel sorry for the dog.