Rotating an image to face a point

Started by
12 comments, last by tmccolgan88 10 years, 9 months ago

Hello,

I have a class which is supposed to except a touchEvent in its update method, and then rotate a bitmap to face the location of that touchEvent.

For some reason the way I have it written now the image just continues to rotate in the direction of the touchEvent. I've tried just about everything, wondering if someone could help.


package com.turretgame.mainpackage;

import android.graphics.Color;
import android.graphics.Matrix;
import android.util.Log;

import com.example.gamedevchapter3.Input.TouchEvent;
import com.example.gamedevchapter3.Pixmap;
import com.example.gamedevchapter3.Graphics;

public class Turret {

	Pixmap turretImage;
	Matrix matrix;

	float direction, tempDirection = 0;
	int xpos, ypos = 100;
	float eventSlope = 0;
	int screenWidth, screenHeight;

	public Turret(Pixmap image, int x, int y, int worldWidth, int worldHeight) {
		turretImage = image;

		matrix = new Matrix();

		xpos = 360;
		ypos = 1040;
		matrix.postTranslate(360, 1040);
	}

	public void update(TouchEvent event, float deltaTime) {

		event.x -= 360;
		eventSlope = (((float) event.x) / ((float) event.y));

		if (eventSlope != 0) {
			tempDirection = ((float) (Math.atan2((double)1 / (double)eventSlope, 0)));
		} else {
			eventSlope = 0;
		}
		
		Log.d("direction", Float.toString(direction));
        
		direction = direction + tempDirection;
		
		matrix.postRotate(direction, xpos + 8, ypos + 20);
	
		direction = tempDirection;
	}

	public void draw(Graphics g) {

		g.drawPixmap(turretImage, matrix);

	}
}
Advertisement

An asside, if event.y=0 you have a divide by zero.

When you say "continues to rotate", I assume that is because you don't have anything that turns the rotation off. You simply call postRotate every time, so the thing gets rotated every time. Is that intended?

Also, you don't give much context but I think you may want a turret that slowly rotates toward a touch. You might be better off adjusting the angle by first taking the arrcos of the dot product between the normalized current direction and the normalized touch's direction, clamping that value to a maximum (so it moves slowly), and then using it for the rotation.

An asside, if event.y=0 you have a divide by zero.

When you say "continues to rotate", I assume that is because you don't have anything that turns the rotation off. You simply call postRotate every time, so the thing gets rotated every time. Is that intended?

Also, you don't give much context but I think you may want a turret that slowly rotates toward a touch. You might be better off adjusting the angle by first taking the arrcos of the dot product between the normalized current direction and the normalized touch's direction, clamping that value to a maximum (so it moves slowly), and then using it for the rotation.

I did have a check to make sure I didn't end up dividing by 0, but I most of accidentally deleted when I moved some code around. My goal is to have the turret snap to the angle for it face the point. Right now it does rotate slowly. What I mean by continues to rotate is that it begins rotating in the correct direction, but then continues passed the desired angle. Sorry, I should have explained my intention better.

You could use one of the great ones I think. Good old mix(), A.K.A. lerp(). A.K.A. linear interpolation. A.K.A. key-frame animation.

Using the distance function: distance = positionB - PositionA and a bit of trig you can get the new angle to move towards. Let's put the trig stuff off until we are both on the same page, for me I've just coded and tested the following and it's working as expected. After this, the hard part....

//===========================================================================

blendedRotation = startRotation * (1.0 - sliderTime) + targetRotation * sliderTime;

//------------------------------------ then

if(sliderTime <= 1.0) // This will prevent the rotation from over-shooting

{

sliderTime += incrementTime;

}

//===========================================================================

We might have to throw a bit more logic to deal for cases where the various rotation values transition across 0.0. i.e. 360 to 0.0 for the angle, or positive to negative for the X, Y, Z, axis values. Math.h and its atan() function will deal with this stuff quietly behind the scenes.

I'm hesitant to throw down much code or ideas at this point without testing what I post, before I post it. I've got a basic framework started that only has this stuff in it. If you want to get a running dialogue going then I will keep working on this with you and I will only post code after I have tested it.

This issue is pretty important for many of us and I think with the help of others we should have this looking nice and clean and easy to use within a week or two. I'll be shooting for high school level math to start with. After that, we can work on implementing ideas presented by the slick math people that show up around here from time to time...if this suits you.

Consider it pure joy, my brothers and sisters, whenever you face trials of many kinds, 3 because you know that the testing of your faith produces perseverance. 4 Let perseverance finish its work so that you may be mature and complete, not lacking anything.

BAZINGA! It still does not have a reset for a second animation. However, I did test every quadrant by moving ship_02 around the screen, and also with both models on and off the axis lines in case division by zero or weird trig errors at transition points cropped up. The atan2(); is supposed to deal with this stuff automatically behind the scenes, but as the old Russian proverb goes "???????, ?? ????????."

The ship stuff is from another post I'm working on(post # 64443), but it should work for you once the details are renamed by you. Here it is, in this case, the first ship rotates towards, and points at the second ship: In your case, you'd want to make ship_02_POSITION the spot where the touch occurred. You may have to calibrate the touchPos variable to OpenGL's centered coordinates. I assume Android uses either top left or bottom left for the 0, 0 point of the touch screen

//========================================================================================================

GLfloat ship_01_POSITION[] = {5.0, 7.0, 0.0};
GLfloat ship_01_ROTATION[] = {0.0, 0.0, 0.0, 1.0};
GLfloat ship_02_POSITION[] = {0.0, -5.0, 0.0};
GLfloat ship_02_ROTATION[] = {0.0, 0.0, 0.0, 1.0};
GLfloat blendedRotation = 0.0;
GLfloat startRotation = 0.0;
GLfloat targetRotation = 180.0;
GLfloat sliderTime = 0.0;
GLfloat incrementTime = 0.0;
GLfloat distanceBetweenModels[] = {0.0, 0.0, 0.0};

//=========================================================================================================

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

//=========================================================================================================

void Render(void)

{

//============================= - The next seven lines of code seems to have round 1 taken care of.
incrementTime += 0.001;
//--------------------------------
distanceBetweenModels[1] = ship_02_POSITION[1] - ship_01_POSITION[1]; // Y2 - Y1 <--distance formula...high school math? check!
distanceBetweenModels[0] = ship_02_POSITION[0] - ship_01_POSITION[0]; // X2 - X1
//------------------------------------------------------------------------------------------------------
targetRotation = atan2(distanceBetweenModels[0], distanceBetweenModels[1])* 180 / 3.14159; //<- atan2 function... high school math? ...check!
blendedRotation = (startRotation * (1.0 - sliderTime)) + (targetRotation * sliderTime); //<- mix()... high school math? sure why not... check!
if(sliderTime <= 1.0) // This will prevent the rotation from over-shooting
{
sliderTime += incrementTime;
}
//=====================================================================================================================
glPushMatrix();
DrawShip(ship_01_POSITION_X, ship_01_POSITION_Y, ship_01_POSITION_Z, ship_01_ROTATION, ship_01_COLOR);
glPopMatrix();
//-----------------------------------------------------------------------------------------------------------------------------------------------------------------
glPushMatrix();
DrawShip(ship_02_POSITION_X, ship_02_POSITION_Y, ship_02_POSITION_Z, ship_02_ROTATION, ship_02_COLOR);
glPopMatrix();
//=========================================================================================
}
//===========================================================================================================
That's straight forward enough to keep me from having an anxiety attack every time I think of it. I wish i could say the same for collision handling.
Once you've gotten this far we can proceed to updating it so that every touch resets the whole she-bang for another round.

Consider it pure joy, my brothers and sisters, whenever you face trials of many kinds, 3 because you know that the testing of your faith produces perseverance. 4 Let perseverance finish its work so that you may be mature and complete, not lacking anything.

Thanks for the help Marc. Right now my graphics framework is written using on board graphics instead of opengl. Will this make much of a difference?

Ok, I have this code working. I translated some things to the standard graphics library, but it still works well. This is what I have now.


package com.turretgame.mainpackage;

import android.graphics.Color;
import android.graphics.Matrix;
import android.util.Log;

import com.example.gamedevchapter3.Input.TouchEvent;
import com.example.gamedevchapter3.Pixmap;
import com.example.gamedevchapter3.Graphics;

public class Turret {

	Pixmap turretImage;
	Matrix matrix;
	
	final int SHIP_X_POS = 360;
	final int SHIP_Y_POS = 1040;
	
	final int SHIP_X_ROTATEPOS = 360 + 8;
	final int SHIP_Y_ROTATEPOS = 1040 + 20;

	final int TOUCH_X_TRANSLATION = 360;
	
	float direction, tempDirection = 0;
	int xpos, ypos = 100;
	float eventSlope = 0;
	int screenWidth, screenHeight;
	
	float shipPosition[] = {5.0f, 7.0f};
	float shipRotation[] = {0.0f, 0.0f, 0.0f, 1.0f};
	
	float touchPosition[] = {0.0f, -5.0f, 0.0f};
	
	float blendedRotation  = 0.0f;
	float startRotation    = 0.0f;
	float targetRotation   = 180.0f;
	float sliderTime       = 0.0f;
	float incrementTime    = 0.0f;
	 
	float distanceBetweenModels[] = {0.0f, 0.0f, 0.0f};

	public Turret(Pixmap image, int x, int y, int worldWidth, int worldHeight) {
		turretImage = image;

		matrix = new Matrix();

		xpos = 360; 
		ypos = 1040;
		matrix.postTranslate(360, 1040);
	}

	public void update(TouchEvent event, float deltaTime) {
		
		
		matrix.reset();	
		matrix.postTranslate(SHIP_X_POS, SHIP_Y_POS);
		
		touchPosition[1] = event.y;
		touchPosition[0] = event.x - TOUCH_X_TRANSLATION;
		
		
		//============================= - The next seven lines of code seems to have round 1 taken care of.
	       incrementTime += 0.001;     
	       //--------------------------------    
	       
	       distanceBetweenModels[1] = touchPosition[1] - shipPosition[1];                                      
	       distanceBetweenModels[0] = touchPosition[0] - shipPosition[0];                                                                  
	 
	       //------------------------------------------------------------------------------------------------------
	 
	       targetRotation = (float) ((float)Math.atan2(distanceBetweenModels[0], distanceBetweenModels[1])* 180 / 3.14159);                    
	 
	       
	       blendedRotation = (float) ((startRotation * (1.0 - sliderTime)) + (targetRotation * sliderTime));                                   
	 
	       matrix.postRotate(blendedRotation, SHIP_X_ROTATEPOS, SHIP_Y_ROTATEPOS);
	       
	       if(sliderTime <= 1.0) // This will prevent the rotation from over-shooting
	       {
	             sliderTime += incrementTime;                                                                                                   
	       }
	}

	public void draw(Graphics g) {

		g.drawPixmap(turretImage, matrix);
	}
}
 

I added some constant variables for the sake of readability. The only thing I have noticed is that if a touchEvent is too close or too far away from the turret it loses a great deal of accuracy. If the touchEvent is about half of the distance from the turret to the top of the screen its dead on.

The only thing I have noticed is that if a touchEvent is too close or too far away from the turret it loses a great deal of accuracy. If the touchEvent is about half of the distance from the turret to the top of the screen its dead on.

Edit: Ignore all the following confused musing, I've been awake for a day now, I'm hung-over and I did not read your code first. I guess I must think I'm Richard Feynman or something, like I can just imagine the solution without looking at anything first. Dunh!.. Half of what I was wondering about is written right there at the beginning of your code post. I had just assumed that it was exactly as I had written. Silly! I'm just going to leave the mess below for now so I can laugh at myself later.

How interesting. I will move this into my iOS framework and see if I have the same issue. Maybe this is related to the screen coordinates being some variant of an integer and the algorithm above uses floating point numbers. If the formula is being driven by numbers like 1, 2, 3, 4, .... 700 this might really screw things up.

Does the Android OS graphics API have translation and rotation calls that use floats in the range of let's say -10, +10? Or is it using integers that look more like the devices physical pixel coordinates? i.e. 800*400

hmmmm .... Now how do we get those screen pixel coordinate numbers down to something that matches your artworks's dimensions???

hmmmmm?? What are the artworks dimensions???

hmmmmm??? Is this the right way to word this problem????

hmmmmmm. I'm stumped for now. I guess it's time for one of those fudge-factor numbers that scientists and engineers are always so fond of.

Oh wait, I know!.... I think???? What is the model's position value when it is at the left side of the screen?? Then to the right side of the screen?

Now, what does the finger position value look like when you move your finger from the left side of the screen to the right side of the screen?

This will give the ratio that's needed to scale the coordinates for POSITION_01 and POSITION_02 so they are both in the same range.

If the numbers for your artwork's position and the numbers for your finger position look more or less exactly the same, when you move from left to right then I am stumped...

If they are very different then we may have the culprit.

I think we have to get the screen's pixel measurements down into this range, so it matches up with the characters position and rotation. Right now I think they are likely very our of proportion to one another. Maybe the finger touch screen positions are floats already. Who knows with Google...

We better both do some more digging.

Consider it pure joy, my brothers and sisters, whenever you face trials of many kinds, 3 because you know that the testing of your faith produces perseverance. 4 Let perseverance finish its work so that you may be mature and complete, not lacking anything.


The only thing I have noticed is that if a touchEvent is too close or too far away from the turret it loses a great deal of accuracy. If the touchEvent is about half of the distance from the turret to the top of the screen its dead on.

Edit: Ignore all the following confused musing, I've been awake for a day now, I'm hung-over and I did not read your code first. I guess I must think I'm Richard Feynman or something, like I can just imagine the solution without looking at anything first. Dunh!.. Half of what I was wondering about is written right there at the beginning of your code post. I had just assumed that it was exactly as I had written. Silly! I'm just going to leave the mess below for now so I can laugh at myself later.

Haha, don't worry about it. I know the feeling, and appreciate the help. I'm going to put some time into it and see if I can come up with something.

All that I can see is that the values that you are using are about 20x higher than the ones I used. I don't think that this should matter so long as everything is consistent

Maybe there is something going on with this, so far.... I have no idea what is going on with the following function.

--> matrix.postRotate(blendedRotation, SHIP_X_ROTATEPOS, SHIP_Y_ROTATEPOS);

It appears that people are using the 2nd and 3rd parameters as some sort of scaling.

If these values are not dead on for whatever they represent, then I suspect that the blendedRotation variable will be skewed towards either one axis or the other.

That's all I've got, we are way out of my league now. I just had a 15 year old flashback of dropping linear algebra after two weeks because I knew I was going to fail it.

Consider it pure joy, my brothers and sisters, whenever you face trials of many kinds, 3 because you know that the testing of your faith produces perseverance. 4 Let perseverance finish its work so that you may be mature and complete, not lacking anything.

This topic is closed to new replies.

Advertisement