creating interactive animation with libgdx - android

I am looking for a way to connect pan gesture with percentage of animation completion. Let me show you what I mean.
This image represents an animation that I want to execute, namely a moving Image actor or a sprite. The animation gets executed by pan gesture. Animation is 100% complete and at stage 6 when user slides for a 200px. If user slided only 100px, it would be 50% complete and at stage 3. If the user didn't execute pan gesture the animation stays at 0% and at stage 1. I am looking for tips on how to start building such a model. I believe it is called interactive. Do you have any suggestions?

You can use a GestureDetector to handle the panning input. The gestureListener.pan method can update an animation position parameter.
private int screenPixelsToAnimationPositionRatio = 0.01f; //will need to tweak this and probably adjust it at runtime based on screen dimensions and resolution
private float animationPosition = 0; //from 0 to 1, fraction of animation complete
public void create () {
//...
GestureAdapter gestureAdapter = new GestureAdapter {
#Override
public boolean pan (float x, float y, float deltaX, float deltaY) {
animationPosition += deltaX * screenPixelsToAnimationPositionRatio;
animationPosition = MathUtils.clamp(animationPosition, 0, 1);
return true;
}
};
GestureDetector gestureDetector = new GestureDetector(gestureAdapter);
Gdx.input.setInputProcessor(gestureDetector); //or put the detector in an InputMultiplexer with your other input listeners.
}
Then you would create a method that can update your object's position and rotation based on the current value of animationPosition. You would need to figure out the equations that determine the movement you want. For example, something that looks sort of like what you illustrated above:
private void updateAnimation (){
x = animationPosition * 30;
float y = 0, rotation = 0;
if (animationPosition >= 0.25f) {
float jumpPosition = Math.min(1, (animationPosition - 0.25f) / 0.5f);
y = 30 * (1 - Interpolation.pow2In.apply(Math.abs(2 * jumpPosition - 1)));
rotation = 180 * jumpPosition;
}
mySprite.setPosition(x, y);
mySprite.setRotation(rotation);
}
Then call this update method somewhere in render.

Related

Android - How to spin the image

I have an image and its in imageView. I want to add a functionally that allows users to spin the image if they try to spin it to the right side.For example if user tries to spin it to the left side, it doesnt spin.
This is my spinwheel.
I tried to use GestureDetector and onFling event but it wasn't enough for me to detect if user tries to spin it to the right side or left side. How can i do this ?
Edit:
var normalVectorX = e2?.x!! - 515
var normalVectorY = e2?.y!! - 515
var tangentVectorX = -normalVectorY
var tangentVectorY = normalVectorX
var tangentVectorLength = (Math.sqrt(Math.pow((515 - e2?.y!!).toDouble(), 2.0)) + Math.pow((e2?.x!! - 515).toDouble(), 2.0))
var unitTangentX = tangentVectorX / tangentVectorLength
var unitTangentY = tangentVectorY / tangentVectorLength
var scalarProjection = (velocityX * unitTangentX) + (velocityY * unitTangentY)
if (scalarProjection > 0) // Right Side
spinWheel((spinCountX * 360).toFloat() + 360 - (mPrizeIndex * 60) , 12000)
This is the code implementation based on the pseudo code of answer.
515 = center of the wheel.
onFling gives you the MotionEvents of the corresponding fling gesture. You can then call getX(int) and getY(int) to get the coordinates.
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
float x = e2.getX(0); // Assuming you only care about the first finger
float y = e2.getY(0);
if (x < imageView.getWidth() / 2) {
// fling is on the left side
} else {
// fling is on the right side
}
}
Side note: I think the code above will behave unnaturally if the fling is horizontal. If you want your spin gesture to be more realistic, I think you will be better off calculating the tangential component.
Edit: Here's how you would calculate the tangential component.
You would want to calculate the scalar projection of the velocity vector on the unit tangent of the circle at the point where the touch event is.
Pseudo code:
velocityVector = (velocityX, velocityY)
locationOfTouch = (e2.getX(0), e2.getY(0))
centerOfWheel = (166, 155) # Measured from your image
normalVector = locationOfTouch - centerOfWheel
= (locationOfTouch.x - centerOfWheel.x, locationOfTouch.y - centerOfWheel.y)
To get the clockwise tangent vector, we take the normalVector, swap the X and Y components, and negate the X (from 2D Euclidean vector rotations).
tangentVector = (-normalVector.y, normalVector.x)
unitTangent = tangentVector / tangentVector.length()
Finally, you take the dot product to get the scalar projection:
scalarProjection = velocityVector.dotProduct(unitTangent)
= (velocityVector.x * unitTangent.x) + (velocityVector.y * unitTangent.y)
If scalarProjection is positive, that means the fling gesture is in the clockwise direction. If it is negative, that means the gesture is in anti-clockwise direction.
As a bonus, since we used the unit tangent, the resulting scalar projection also represents how fast the fling is in the clockwise direction, so you can change the spin animation to be faster or slower, or set a threshold such that a small velocity in that direction doesn't trigger the wheel to spin.

Move a body emulating the movement of my finger on the screen at a constant speed

I need help with the following problem since I have invested many days without arriving at an acceptable solution.
I'm doing an Android game (using libgdx) where the main character (named Hero) is seen from above (top-down view game) and walks on a field.
The user moves the character by moving his finger along the screen. The finger does't need to be on the character.
The character uses two animations, one animation when he moves forward (that is, when his "y" is greater than zero since the user looks at the game from the "sky") and another animation when he moves backwards (that is, when his "y" is less than zero, remember I'm developing a top-down view game).
Finally, I need the character to always move at a CONSTANT speed.
In short, I would like to handle the character with the finger and move it in the direction that marks my finger, always a CONSTANT speed.
This would be very easy if I could set the position of the character each delta time, but I'm using box2d which only knows about linearVelocity, impulses, forces, etc.
I tried using mouseJoint where hitbody is my main character (Hero) and groundBody is an invisible body.
// Invisible zero size ground body
// to which we can connect the mouse joint
Body groundBody;
BodyDef bodyDef = new BodyDef();
groundBody = world.createBody(bodyDef);
/* player is an instance of my Hero's class, which has a box2d body and
update, draw methods, etc.
*/
hitBody = player.getB2body();
...
InputProcessor:
#Override
public boolean touchDown(int i, int i1, int i2, int i3) {
gameCam.unproject(testPoint.set(i, i1, 0));
MouseJointDef def = new MouseJointDef();
def.bodyA = groundBody;
def.bodyB = hitBody;
def.collideConnected = true;
def.target.set(testPoint.x, testPoint.y);
def.maxForce = 1000.0f * hitBody.getMass();
mouseJoint = (MouseJoint) world.createJoint(def);
hitBody.setAwake(true);
}
#Override
public boolean touchUp(int i, int i1, int i2, int i3) {
player.getB2body().setLinearVelocity(0,0);
// if a mouse joint exists we simply destroy it
if (mouseJoint != null) {
world.destroyJoint(mouseJoint);
mouseJoint = null;
}
return false;
}
#Override
public boolean touchDragged(int i, int i1, int i2) {
// if a mouse joint exists we simply update
// the target of the joint based on the new
// mouse coordinates
if (mouseJoint != null) {
gameCam.unproject(testPoint.set(i, i1, 0));
mouseJoint.setTarget(target.set(testPoint.x, testPoint.y));
evaluateMovementDirection();
}
return false;
}
private void evaluateMovementDirection() {
float vy = player.getB2body().getLinearVelocity().y;
float vx = player.getB2body().getLinearVelocity().x;
// Test to Box2D for velocity on the y-axis.
// If Hero is going positive in y-axis he is moving forward.
// If Hero is going negative in y-axis he is moving backwards.
if (vy > 0.0f) {
player.onMovingUp(); // In draw, I'll use a "moving forward" animation
} else if (vy < 0.0f) {
player.onMovingDown(); // In draw, I'll use a "movieng backwards" animation
} else {
player.onStanding(); // vy == 0 In draw, I'll use a texture showing my Hero standig.
}
}
The problem I get with this, is that if I move my finger very fast, the character moves very fast. I would like the character to always move AT CONSTANT SPEED.
The other approach I tried is to use the pan event:
GestureListener:
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
/*
* DeltaX is positive when I move my finger to the left, negative otherwise.
* DeltaY is positive when I move my finger down, negative otherwise.
*/
// In b2body y-axes sign is the opposite.
deltaY = -deltaY;
// DeltaX and deltaY are in pixels, therefore delta is in metres.
Vector2 delta = new Vector2(deltaX / Constants.PPM, deltaY / Constants.PPM);
// Deltas too small are discarded
if (delta.len() > Constants.HERO_SENSIBILITY_METERS) {
/*
* origin.x = player.getB2body().getPosition().x
* origin.y = player.getB2body().getPosition().y
*
* destination.x = origin.x + delta.x
* destination.y = origin.y + delta.y
*
* To go from origin to destination we must subtract their position vectors: destination - origin.
* Thus destination - origin is (delta.x, delta.y).
*/
Vector2 newVelocity = new Vector2(delta.x, delta.y);
// Get the direction of the previous vector (normalization)
newVelocity.nor();
// Apply constant velocity on that direction
newVelocity.x = newVelocity.x * Constants.HERO_LINEAR_VELOCITY;
newVelocity.y = newVelocity.y * Constants.HERO_LINEAR_VELOCITY;
// To avoid shaking, we only consider the newVelocity if its direction is slightly different from the direction of the actual velocity.
// In order to determine the difference in both directions (actual and new) we calculate their angle.
if (Math.abs(player.getB2body().getLinearVelocity().angle() - newVelocity.angle()) > Constants.HERO_ANGLE_SENSIBILITY_DEGREES) {
// Apply the new velocity
player.getB2body().setLinearVelocity(newVelocity);
evaluateMovementDirection();
}
} else {
// Stop
player.getB2body().setLinearVelocity(0, 0);
evaluateMovementDirection();
}
return true;
}
The problem I have with this is that the movement is very unstable and "dirty". The character is shaking.
I tried this approach (thanks #hexafraction).
Using this code, my charecter moves more fluid along the screen.
It's not perfect, but it's something...
#Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
/*
* DeltaX is positive when I move my finger to the left, negative otherwise.
* DeltaY is positive when I move my finger down, negative otherwise.
* Both are in pixels, thus to get meters I must divide by Constants.PPM.
*/
// In b2body y-axes sign is the opposite.
deltaY = -deltaY;
/*
* origin.x = player.getB2body().getPosition().x
* origin.y = player.getB2body().getPosition().y
*
* destination.x = origin.x + deltaX / Constants.PPM
* destination.y = origin.y + deltaY / Constants.PPM
*
* To go from origin to destination we must subtract their position vectors: destination - origin.
* Thus, destination - origin is (deltaX / Constants.PPM, deltaY / Constants.PPM).
*/
candidateVelocity.x = deltaX / Constants.PPM;
candidateVelocity.y = deltaY / Constants.PPM;
// Get the direction of the previous vector (normalization)
candidateVelocity.nor();
// Apply constant velocity on that direction
candidateVelocity.x = candidateVelocity.x * Constants.HERO_LINEAR_VELOCITY;
candidateVelocity.y = candidateVelocity.y * Constants.HERO_LINEAR_VELOCITY;
// Linear interpolation to avoid character shaking
heroVelocity.lerp(candidateVelocity, Constants.HERO_ALPHA_LERP);
// Apply the result
player.getB2body().setLinearVelocity(heroVelocity);
// Depending on the result, we change the animation if needed
evaluateMovementDirection();
}
return true;
}
I need a suggestion on how to resolve this. I mean, move a box2d character with my finger along the screen at CONSTANT SPEED.
Thank you very much.
Calculate the direction in which you want to move:
dragPos.sub(currentPos);
Normalize it and multiply with the constant speed:
dragPos.sub(currentPos).nor().scl(CONSTANT_SPEED);

Android - how to make RotateAnimation more smooth and "physical"?

I'm implementing a kind of a "compass arrow" that follows destination depending on physical orientation of the device using magnetic field sensor. Suddenly I faced with a little problem.
Obtaining bearing and azimuth is OK, but performing a realistic animation turned into a really hard task. I tried to use different interpolators to make animation more "physical" (i. e. as in real compass, which arrow oscillate after hairpin rotation, accelerate and decelerate during movement etc).
Now I'm using interpolator.accelerate_decelerate and everything is quite good until updates start arriving quickly. That makes animations overlap each other and the arrow becomes twitchy and nervous. I want to avoid this. I tried to implement a queue to make every next animation wait until previous ends, or drop updates that come very quickly. That made animation look smooth, but arrow's behavior turned into absolutely illogical.
So I have 2 questions:
1) is there some way to make animated transitions more smooth in the cases when animations overlap each other?
2) is there a way to stop animation that is currently processing and get intermediate position of an object?
My code is below. An UpdateRotation() method handles orientation and bearing updates and executes animation of external viewArrow view.
public class DirectionArrow {
// View that represents the arrow
final View viewArrow;
// speed of rotation of the arrow, degrees/sec
final double rotationSpeed;
// current values of bearing and azimuth
float bearingCurrent = 0;
float azimuthCurrent = 0;
/*******************************************************************************/
/**
* Basic constructor
*
* #param view View representing an arrow that should be rotated
* #param rotationSpeed Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
*/
public DirectionArrow(View view, double rotationSpeed) {
this.viewArrow = view;
this.rotationSpeed = rotationSpeed;
}
/**
* Extended constructor
*
* #param viewArrow View representing an arrow that should be rotated
* #param rotationSpeed Speed of rotation in deg/sec. Recommended from 50 (slow) to 500 (fast)
* #param bearing Initial bearing
* #param azimuth Initial azimuth
*/
public DirectionArrow(View viewArrow, double rotationSpeed, float bearing, float azimuth){
this.viewArrow = viewArrow;
this.rotationSpeed = rotationSpeed;
UpdateRotation(bearing, azimuth);
}
/**
* Invoke this to update orientation and animate the arrow
*
* #param bearingNew New bearing value, set >180 or <-180 if you don't need to update it
* #param azimuthNew New azimuth value, set >360 or <0 if you don't need to update it
*/
public void UpdateRotation(float bearingNew, float azimuthNew){
// look if any parameter shouldn't be updated
if (bearingNew < -180 || bearingNew > 180){
bearingNew = bearingCurrent;
}
if (azimuthNew < 0 || azimuthNew > 360){
azimuthNew = azimuthCurrent;
}
// log
Log.println(Log.DEBUG, "compass", "Setting rotation: B=" + bearingNew + " A=" + azimuthNew);
// calculate rotation value
float rotationFrom = bearingCurrent - azimuthCurrent;
float rotationTo = bearingNew - azimuthNew;
// correct rotation angles
if (rotationFrom < -180) {
rotationFrom += 360;
}
while (rotationTo - rotationFrom < -180) {
rotationTo += 360;
}
while (rotationTo - rotationFrom > 180) {
rotationTo -= 360;
}
// log again
Log.println(Log.DEBUG, "compass", "Start Rotation to " + rotationTo);
// create an animation object
RotateAnimation rotateAnimation = new RotateAnimation(rotationFrom, rotationTo,
Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0.5);
// set up an interpolator
rotateAnimation.setInterpolator(viewArrow.getContext(), interpolator.accelerate_decelerate);
// force view to remember its position after animation
rotateAnimation.setFillAfter(true);
// set duration depending on speed
rotateAnimation.setDuration((long) (Math.abs(rotationFrom - rotationTo) / rotationSpeed * 1000));
// start animation
viewArrow.startAnimation(rotateAnimation);
// update cureent rotation
bearingCurrent = bearingNew;
azimuthCurrent = azimuthNew;
}
}
Here is my custom ImageDraw class where I implemted physical behavior of the pointing arrow based on equation of circular motion of dipole in magnetic field.
It don't uses any animators nor interpolators--on every iteration angular position is recalculated based on physical parameters. These parameters can be widely adjusted via setPhysical method. For example, to make rotations more smooth and slow, increase alpha (damping coefficient), to make arrow more responsitive, increase mB (coefficient of magnetic field), to make arrow oscillate on rotations, increase inertiaMoment.
Animation and redraw is performed implicitly by invoke of invalidate() on every iteration. There is no need to handle it explicitly.
To update the angle at which the arrow should rotate, just call rotationUpdate (by user's choice or using device orientation sensor callback).
/**
* Class CompassView extends Android ImageView to perform cool, real-life animation of objects
* such compass needle in magnetic field. Rotation is performed relative to the center of image.
*
* It uses angular motion equation of magnetic dipole in magnetic field to implement such animation.
* To vary behaviour (damping, oscillation, responsiveness and so on) set various physical properties.
*
* Use `setPhysical()` to vary physical properties.
* Use `rotationUpdate()` to change angle of "magnetic field" at which image should rotate.
*
*/
public class CompassView extends ImageView {
static final public float TIME_DELTA_THRESHOLD = 0.25f; // maximum time difference between iterations, s
static final public float ANGLE_DELTA_THRESHOLD = 0.1f; // minimum rotation change to be redrawn, deg
static final public float INERTIA_MOMENT_DEFAULT = 0.1f; // default physical properties
static final public float ALPHA_DEFAULT = 10;
static final public float MB_DEFAULT = 1000;
long time1, time2; // timestamps of previous iterations--used in numerical integration
float angle1, angle2, angle0; // angles of previous iterations
float angleLastDrawn; // last drawn anglular position
boolean animationOn = false; // if animation should be performed
float inertiaMoment = INERTIA_MOMENT_DEFAULT; // moment of inertia
float alpha = ALPHA_DEFAULT; // damping coefficient
float mB = MB_DEFAULT; // magnetic field coefficient
/**
* Constructor inherited from ImageView
*
* #param context
*/
public CompassView(Context context) {
super(context);
}
/**
* Constructor inherited from ImageView
*
* #param context
* #param attrs
*/
public CompassView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Constructor inherited from ImageView
*
* #param context
* #param attrs
* #param defStyle
*/
public CompassView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* onDraw override.
* If animation is "on", view is invalidated after each redraw,
* to perform recalculation on every loop of UI redraw
*/
#Override
public void onDraw(Canvas canvas){
if (animationOn){
if (angleRecalculate(new Date().getTime())){
this.setRotation(angle1);
}
} else {
this.setRotation(angle1);
}
super.onDraw(canvas);
if (animationOn){
this.invalidate();
}
}
/**
* Use this to set physical properties.
* Negative values will be replaced by default values
*
* #param inertiaMoment Moment of inertia (default 0.1)
* #param alpha Damping coefficient (default 10)
* #param mB Magnetic field coefficient (default 1000)
*/
public void setPhysical(float inertiaMoment, float alpha, float mB){
this.inertiaMoment = inertiaMoment >= 0 ? inertiaMoment : this.INERTIA_MOMENT_DEFAULT;
this.alpha = alpha >= 0 ? alpha : ALPHA_DEFAULT;
this.mB = mB >= 0 ? mB : MB_DEFAULT;
}
/**
* Use this to set new "magnetic field" angle at which image should rotate
*
* #param angleNew new magnetic field angle, deg., relative to vertical axis.
* #param animate true, if image shoud rotate using animation, false to set new rotation instantly
*/
public void rotationUpdate(final float angleNew, final boolean animate){
if (animate){
if (Math.abs(angle0 - angleNew) > ANGLE_DELTA_THRESHOLD){
angle0 = angleNew;
this.invalidate();
}
animationOn = true;
} else {
angle1 = angleNew;
angle2 = angleNew;
angle0 = angleNew;
angleLastDrawn = angleNew;
this.invalidate();
animationOn = false;
}
}
/**
* Recalculate angles using equation of dipole circular motion
*
* #param timeNew timestamp of method invoke
* #return if there is a need to redraw rotation
*/
protected boolean angleRecalculate(final long timeNew){
// recalculate angle using simple numerical integration of motion equation
float deltaT1 = (timeNew - time1)/1000f;
if (deltaT1 > TIME_DELTA_THRESHOLD){
deltaT1 = TIME_DELTA_THRESHOLD;
time1 = timeNew + Math.round(TIME_DELTA_THRESHOLD * 1000);
}
float deltaT2 = (time1 - time2)/1000f;
if (deltaT2 > TIME_DELTA_THRESHOLD){
deltaT2 = TIME_DELTA_THRESHOLD;
}
// circular acceleration coefficient
float koefI = inertiaMoment / deltaT1 / deltaT2;
// circular velocity coefficient
float koefAlpha = alpha / deltaT1;
// angular momentum coefficient
float koefk = mB * (float)(Math.sin(Math.toRadians(angle0))*Math.cos(Math.toRadians(angle1)) -
(Math.sin(Math.toRadians(angle1))*Math.cos(Math.toRadians(angle0))));
float angleNew = ( koefI*(angle1 * 2f - angle2) + koefAlpha*angle1 + koefk) / (koefI + koefAlpha);
// reassign previous iteration variables
angle2 = angle1;
angle1 = angleNew;
time2 = time1;
time1 = timeNew;
// if angles changed less then threshold, return false - no need to redraw the view
if (Math.abs(angleLastDrawn - angle1) < ANGLE_DELTA_THRESHOLD){
return false;
} else {
angleLastDrawn = angle1;
return true;
}
}
Are you filtering your sensor data? The Magnetometer is a pain low pass filtering isn't really enough. You could use weighted-smoothing or maybe rounding data would be helpful:
Math.round( xyz * 10) / 10; ?
You could also reduce the frequency at which you get sensor updates. That might help.
mSensorManager.registerListener(this, mMagnetometer, 10000);
Espessially for gilonm, nice implementation of fixed size queue and getting its mean value:
float queue[ARRAY_LENGTH] = {0};
int queueFront = queue.length - 1 // position of front element
float meanValue = 0; // calculated mean value
float pushNewAndGetMean(float newValue){
// recalculate mean value
meanValue = meanValue + (newValue - queue[queueFront]) / queue.length;
// overwrite value in front pointer position
queue[queueFront] = newValue;
// shift front pointer 1 step right or to '0' if end of array reached
queueStart = (queueFront + 1) % array.length;
return meanValue
};
Here, not dependent on array length, you make just 2 reassignments of variables (instead of N) and use only 3 elements in mean calculation (instead of N). This makes algorithm O(1) complexity instead of O(N).
What you could do is where you get your data from the sensors - you can just use and array to do an average of say last 5 readings - that should smooth things down.
something like this:
Declare an array private float azimArray[] = {0,0,0,0,0};
Now where you get sensor data, use:
azimArray[0] = azimArray[1];
azimArray[1] = azimArray[2];
azimArray[2] = azimArray[3];
azimArray[3] = azimArray[4];
azimArray[4] = event.values[0]; //get actual sensor data into last array cell
currentAzimuth = Math.round(azimArray[0]+azimArray[1]+azimArray[2]+azimArray[3]+azimArray[4]/5);
Now currentAzimuth holds the rounded average of last 5 readings, which should smooth things down for you.
Hope this helped!

sprite moving on finger directions with velocity

I am working with andEngine the Open source game platform. I have a sprite that moves continuously on the screen and change direction when collides with screen boundary. Now, I wanna change its direction to the players touch point on the screen. I can't manage this part. I use PhysicsHandler to move the sprite with a velocity. I understand i have to implements IOnSceneTouchListener, to get touched point and set the direction on the sprite . But found nothing now. Here is my code goes:
Pilot aPilot;
PhysicsHandler mPhysicsHandler;
aPilot = new Pilot(222, 333, pilotTexures, vbom) {
#Override
protected void onManagedUpdate(float pSecondsElapsed) {
/*
* change direction when collides with boundary wall of Screen
*/
if (this.mX < 0) {
mPhysicsHandler.setVelocityX(AtomicEngine.DEMO_VELOCITY);
} else if (this.mX + this.getWidth() > ResourcesManager.CAMERA_WIDTH) {
mPhysicsHandler.setVelocityX(-AtomicEngine.DEMO_VELOCITY);
}
if (this.mY < 0) {
mPhysicsHandler.setVelocityY(AtomicEngine.DEMO_VELOCITY);
} else if (this.mY + this.getHeight() > ResourcesManager.CAMERA_HEIGHT) {
mPhysicsHandler.setVelocityY(-AtomicEngine.DEMO_VELOCITY);
}
super.onManagedUpdate(pSecondsElapsed);
}
};
/*
* initialize mPhysicsHandler
*/
mPhysicsHandler = new PhysicsHandler(aPilot);
registerUpdateHandler(this.mPhysicsHandler);
mPhysicsHandler.setVelocity(AtomicEngine.DEMO_VELOCITY,
AtomicEngine.DEMO_VELOCITY);
attachChild(aPilot);
aPilot.setScale(3f);
And my override onSceneTouchEvent method is like:
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.isActionDown()) {
// need some idea here
}else if (pSceneTouchEvent.isActionMove()) {
}
return false;
}
Wait for your super knock.
you have to calculate difference between the pilots current position (e.g. by calling getSceneCenterCoordinates() of your pilots sprite you get the coordinates in the scene) and the position of the the touch event - with that difference in mind, you can calculate the angle (measured on the UnitCircle) or use a factor that is a percentage between your max_velocity & distance length, then use your physicshandler and set a new velocity. the factor is used to limit the speed to a max speed.
so, your code should look like something like this (didn't test, ask if it didn't work)
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
MainActivity.this.mCamera.convertCameraSceneToSceneTouchEvent(touchEvent);//see edit
float touchX = touchEvent.getX();
float touchY = touchEvent.getY();
float[] pilotCoord = aPilot.getEntity.getSceneCenterCoordinates();
float pilotX = pilotCoord[0];
float pilotY = pilotCoord[1];
float xDiff = touchX - pilotX;
float yDiff = touchY - pilotY; // could be wrong with AnchorCenter branch
// use the max velo divided by the distance to get the velo factor for x & y,
// but perhaps calculating angles is faster, dunno
float veloFactor = MAX_VELO/sqrt(xDiff^2 + yDiff^2);
float xVelo = xDiff*veloFactor;
float yVelo = yDiff*veloFactor;
mPhysicshandler.setVelocityX(xVelo);
mPhysicshandler.setVelocityY(yVelo);
return true;
}
so far the calculation for setting the velocity into the direction of the finger. if you want some kind of (de)acceleration (like: as long as the finger is down, the pilot (de)accelerates into the direction of the finger, else he will stick with his speed, you have to setLinearVelocity(xVelo, yVelo) instead and set the current velocity as velocity (to maintain speed)
Edit
The conversion of the touchEvent from a CameraScene to a SceneTouchEvent is only usefull if you add your onSceneTouchListener to your HUD. it converts the events x/y values based on the current camera position (over the scene) to xy-values as they would have occured on the scene.
else, if you add the listener directly to your Scene, you don't need to convert the touch event and the line could be deleted.

How to animate zoom out with ImageView that uses Matrix scaling

So I have an ImageView using a Matrix to scale the Bitmap I'm displaying. I can double-tap to zoom to full-size, and my ScaleAnimation handles animating the zoom-in, it all works fine.
Now I want to double-tap again to zoom out, but when I animate this with ScaleAnimation, the ImageView does not draw the newly exposed areas of the image (as the current viewport shrinks), instead you see the portion of visible image shrinking in. I have tried using ViewGroup.setClipChildren(false), but this only leaves the last-drawn artifacts from the previous frame - leading to an trippy telescoping effect, but not quite what I was after.
I know there are many zoom-related questions, but none cover my situation - specifically animating the zoom-out operation. I do have the mechanics working - ie aside from the zoom-out animation, double-tapping to zoom in and out works fine.
Any suggestions?
In the end I decided to stop using the Animation classes offered by Android, because the ScaleAnimation applies a scale to the ImageView as a whole which then combines with the scale of the ImageView's image Matrix, making it complicated to work with (aside from the clipping issues I was having).
Since all I really need is to animate the changes made to the ImageView's Matrix, I implemented the OnDoubleTapListener (at the end of this post - I leave it as an "exercise to the reader" to add the missing fields and methods - I use a few PointF and Matrix fields to avoid excess garbage creation). Basically the animation itself is implemented by using View.post to keep posting a Runnable that incrementally changes the ImageView's image Matrix:
public boolean onDoubleTap(MotionEvent e) {
final float x = e.getX();
final float y = e.getY();
matrix.reset();
matrix.set(imageView.getImageMatrix());
matrix.getValues(matrixValues);
matrix.invert(inverseMatrix);
doubleTapImagePoint[0] = x;
doubleTapImagePoint[1] = y;
inverseMatrix.mapPoints(doubleTapImagePoint);
final float scale = matrixValues[Matrix.MSCALE_X];
final float targetScale = scale < 1.0f ? 1.0f : calculateFitToScreenScale();
final float finalX;
final float finalY;
// assumption: if targetScale is less than 1, we're zooming out to fit the screen
if (targetScale < 1.0f) {
// scaling the image to fit the screen, we want the resulting image to be centred. We need to take
// into account the shift that is applied to zoom on the tapped point, easiest way is to reuse
// the transformation matrix.
RectF imageBounds = new RectF(imageView.getDrawable().getBounds());
// set up matrix for target
matrix.reset();
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
matrix.postScale(targetScale, targetScale);
matrix.mapRect(imageBounds);
finalX = ((imageView.getWidth() - imageBounds.width()) / 2.0f) - imageBounds.left;
finalY = ((imageView.getHeight() - imageBounds.height()) / 2.0f) - imageBounds.top;
}
// else zoom around the double-tap point
else {
finalX = x;
finalY = y;
}
final Interpolator interpolator = new AccelerateDecelerateInterpolator();
final long startTime = System.currentTimeMillis();
final long duration = 800;
imageView.post(new Runnable() {
#Override
public void run() {
float t = (float) (System.currentTimeMillis() - startTime) / duration;
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = interpolator.getInterpolation(t);
float tempScale = scale + interpolatedRatio * (targetScale - scale);
float tempX = x + interpolatedRatio * (finalX - x);
float tempY = y + interpolatedRatio * (finalY - y);
matrix.reset();
// translate initialPoint to 0,0 before applying zoom
matrix.postTranslate(-doubleTapImagePoint[0], -doubleTapImagePoint[1]);
// zoom
matrix.postScale(tempScale, tempScale);
// translate back to equivalent point
matrix.postTranslate(tempX, tempY);
imageView.setImageMatrix(matrix);
if (t < 1f) {
imageView.post(this);
}
}
});
return false;
}

Categories

Resources