Rotating sprite from angle a to angle b in libGdx - android

I have a little problem with a simple path following AI.
If i use setRotation() the sprite has a sudden rotation but I need to rotate it in a gradual and natural way.
I have very low knowledge of trigonometry so i ask here.
public class AISprite extends Sprite {
private Vector2 velocity = new Vector2();
private float speed = 100;
private Array<Vector2> path; // it contains viewpoints
private int waypoint = 0; // index of actual waypoint
public AISprite(Sprite sprite, Array<Vector2> path){
super(sprite);
this.path = path;
}
#Override
public void draw(SpriteBatch spriteBatch){
update(Gdx.graphics.getDeltaTime());
super.draw(spriteBatch);
}
public void update(float delta){
float angle = (float) Math.atan2(path.get(waypoint).y - getY(), path.get(waypoint).x - getX());
velocity.set((float) Math.cos(angle) * speed,(float) Math.sin(angle)*speed);
rotation(angle);
// if the last waypoint is reached, start again
if(isWaypointReached()){
if(waypoint + 1 >= path.size)
waypoint = 0;
else
waypoint++;
}
}
private void rotazione(float angle){
setRotation(angle*MathUtils.radiansToDegrees);
}
private boolean isWaypointReached() {
return Math.abs(path.get(waypoint).x - getX()) <= speed * Gdx.graphics.getDeltaTime()
&&
Math.abs(path.get(waypoint).y - getY()) <= speed * Gdx.graphics.getDeltaTime();
}
public Array<Vector2> getPath(){
return path;
}
public int getWaypoint(){
return waypoint;
}

Related

unable to make 2nd circle follow its own path

I am unable to create more circles which follows its own path with drawCircle .
I have used the code below which creates another circle but follows the path along the lines of 1st circle but not independent .How do I move both circles independent of each other?
I have added
c.drawCircle(ballX-100, ballY-100, 50, ballPaintyellow);
How do I make the above circle independent from the 1st circle?. I really appreciate any help.Thanks in Advance.
BouncingBallActivity.java
package com.stuffthathappens.games;
import static android.hardware.SensorManager.DATA_X;
import static android.hardware.SensorManager.DATA_Y;
import static android.hardware.SensorManager.SENSOR_ACCELEROMETER;
import static android.hardware.SensorManager.SENSOR_DELAY_GAME;
import java.util.concurrent.TimeUnit;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.SurfaceHolder.Callback;
/**
* This activity shows a ball that bounces around. The phone's
* accelerometer acts as gravity on the ball. When the ball hits
* the edge, it bounces back and triggers the phone vibrator.
*/
#SuppressWarnings("deprecation")
public class BouncingBallActivity extends Activity implements Callback, SensorListener {
private static final int BALL_RADIUS =20;
private SurfaceView surface;
private SurfaceHolder holder;
private final BouncingBallModel model = new BouncingBallModel(BALL_RADIUS);
private GameLoop gameLoop;
private Paint backgroundPaint;
private Paint ballPaint;
private SensorManager sensorMgr;
private long lastSensorUpdate = -1;
private Paint ballPaintyellow;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bouncing_ball);
surface = (SurfaceView) findViewById(R.id.bouncing_ball_surface);
holder = surface.getHolder();
surface.getHolder().addCallback(this);
backgroundPaint = new Paint();
backgroundPaint.setColor(Color.WHITE);
ballPaint = new Paint();
ballPaint.setColor(Color.BLUE);
ballPaint.setAntiAlias(true);
ballPaintyellow = new Paint();
ballPaintyellow.setColor(Color.YELLOW);
ballPaintyellow.setAntiAlias(true);
}
#Override
protected void onPause() {
super.onPause();
model.setVibrator(null);
sensorMgr.unregisterListener(this, SENSOR_ACCELEROMETER);
sensorMgr = null;
model.setAccel(0, 0);
}
#Override
protected void onResume() {
super.onResume();
sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE);
boolean accelSupported = sensorMgr.registerListener(this,
SENSOR_ACCELEROMETER,
SENSOR_DELAY_GAME);
if (!accelSupported) {
// on accelerometer on this device
sensorMgr.unregisterListener(this, SENSOR_ACCELEROMETER);
// TODO show an error
}
// NOTE 1: you cannot get system services before onCreate()
// NOTE 2: AndroidManifest.xml must contain this line:
// <uses-permission android:name="android.permission.VIBRATE"/>
Vibrator vibrator = (Vibrator) getSystemService(Activity.VIBRATOR_SERVICE);
model.setVibrator(vibrator);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
model.setSize(width, height);
}
public void surfaceCreated(SurfaceHolder holder) {
gameLoop = new GameLoop();
gameLoop.start();
}
private void draw() {
// thread safety - the SurfaceView could go away while we are drawing
Canvas c = null;
try {
// NOTE: in the LunarLander they don't have any synchronization here,
// so I guess this is OK. It will return null if the holder is not ready
c = holder.lockCanvas();
// this needs to synchronize on something
if (c != null) {
doDraw(c);
}
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
private void doDraw(Canvas c) {
int width = c.getWidth();
int height = c.getHeight();
c.drawRect(0, 0, width, height, backgroundPaint);
float ballX, ballY;
synchronized (model.LOCK) {
ballX = model.ballPixelX;
ballY = model.ballPixelY;
}
c.drawCircle(ballX, ballY, BALL_RADIUS, ballPaint);
c.drawCircle(ballX-100, ballY-100, 50, ballPaintyellow);
}
public void surfaceDestroyed(SurfaceHolder holder) {
try {
model.setSize(0,0);
gameLoop.safeStop();
} finally {
gameLoop = null;
}
}
private class GameLoop extends Thread {
private volatile boolean running = true;
public void run() {
while (running) {
try {
// don't like this hardcoding
TimeUnit.MILLISECONDS.sleep(5);
draw();
model.updatePhysics();
} catch (InterruptedException ie) {
running = false;
}
}
}
public void safeStop() {
running = false;
interrupt();
}
}
public void onAccuracyChanged(int sensor, int accuracy) {
}
public void onSensorChanged(int sensor, float[] values) {
if (sensor == SENSOR_ACCELEROMETER) {
long curTime = System.currentTimeMillis();
// only allow one update every 50ms, otherwise updates
// come way too fast
if (lastSensorUpdate == -1 || (curTime - lastSensorUpdate) > 50) {
lastSensorUpdate = curTime;
model.setAccel(values[DATA_X], values[DATA_Y]);
}
}
}
}
Bouncingballmodel.java
package com.stuffthathappens.games;
import java.util.concurrent.atomic.AtomicReference;
import android.os.Vibrator;
/**
* This data model tracks the width and height of the playing field along
* with the current position of a ball.
*/
public class BouncingBallModel {
// the ball speed is meters / second. When we draw to the screen,
// 1 pixel represents 1 meter. That ends up too slow, so multiply
// by this number. Bigger numbers speeds things up.
private final float pixelsPerMeter = 10;
private final int ballRadius;
// these are public, so make sure you synchronize on LOCK
// when reading these. I made them public since you need to
// get both X and Y in pairs, and this is more efficient than
// getter methods. With two getters, you'd still need to
// synchronize.
public float ballPixelX, ballPixelY;
private int pixelWidth, pixelHeight;
// values are in meters/second
private float velocityX, velocityY;
// typical values range from -10...10, but could be higher or lower if
// the user moves the phone rapidly
private float accelX, accelY;
/**
* When the ball hits an edge, multiply the velocity by the rebound.
* A value of 1.0 means the ball bounces with 100% efficiency. Lower
* numbers simulate balls that don't bounce very much.
*/
private static final float rebound = 0.8f;
// if the ball bounces and the velocity is less than this constant,
// stop bouncing.
private static final float STOP_BOUNCING_VELOCITY = 2f;
private volatile long lastTimeMs = -1;
public final Object LOCK = new Object();
private AtomicReference<Vibrator> vibratorRef =
new AtomicReference<Vibrator>();
public BouncingBallModel(int ballRadius) {
this.ballRadius = ballRadius;
}
public void setAccel(float ax, float ay) {
synchronized (LOCK) {
this.accelX = ax;
this.accelY = ay;
}
}
public void setSize(int width, int height) {
synchronized (LOCK) {
this.pixelWidth = width;
this.pixelHeight = height;
}
}
public int getBallRadius() {
return ballRadius;
}
/**
* Call this to move the ball to a particular location on the screen. This
* resets the velocity to zero, but the acceleration doesn't change so
* the ball should start falling shortly.
*/
public void moveBall(int ballX, int ballY) {
synchronized (LOCK) {
this.ballPixelX = ballX;
this.ballPixelY = ballY;
velocityX = 0;
velocityY = 0;
}
}
public void updatePhysics() {
// copy everything to local vars (hence the 'l' prefix)
float lWidth, lHeight, lBallX, lBallY, lAx, lAy, lVx, lVy;
synchronized (LOCK) {
lWidth = pixelWidth;
lHeight = pixelHeight;
lBallX = ballPixelX;
lBallY = ballPixelY;
lVx = velocityX;
lVy = velocityY;
lAx = accelX;
lAy = -accelY;
}
if (lWidth <= 0 || lHeight <= 0) {
// invalid width and height, nothing to do until the GUI comes up
return;
}
long curTime = System.currentTimeMillis();
if (lastTimeMs < 0) {
lastTimeMs = curTime;
return;
}
long elapsedMs = curTime - lastTimeMs;
lastTimeMs = curTime;
// update the velocity
// (divide by 1000 to convert ms to seconds)
// end result is meters / second
lVx += ((elapsedMs * lAx) / 1000) * pixelsPerMeter;
lVy += ((elapsedMs * lAy) / 1000) * pixelsPerMeter;
// update the position
// (velocity is meters/sec, so divide by 1000 again)
lBallX += ((lVx * elapsedMs) / 1000) * pixelsPerMeter;
lBallY += ((lVy * elapsedMs) / 1000) * pixelsPerMeter;
boolean bouncedX = false;
boolean bouncedY = false;
if (lBallY - ballRadius < 0) {
lBallY = ballRadius;
lVy = -lVy * rebound;
bouncedY = true;
} else if (lBallY + ballRadius > lHeight) {
lBallY = lHeight - ballRadius;
lVy = -lVy * rebound;
bouncedY = true;
}
if (bouncedY && Math.abs(lVy) < STOP_BOUNCING_VELOCITY) {
lVy = 0;
bouncedY = false;
}
if (lBallX - ballRadius < 0) {
lBallX = ballRadius;
lVx = -lVx * rebound;
bouncedX = true;
} else if (lBallX + ballRadius > lWidth) {
lBallX = lWidth - ballRadius;
lVx = -lVx * rebound;
bouncedX = true;
}
if (bouncedX && Math.abs(lVx) < STOP_BOUNCING_VELOCITY) {
lVx = 0;
bouncedX = false;
}
// safely copy local vars back to object fields
synchronized (LOCK) {
ballPixelX = lBallX;
ballPixelY = lBallY;
velocityX = lVx;
velocityY = lVy;
}
if (bouncedX || bouncedY) {
Vibrator v = vibratorRef.get();
if (v != null) {
v.vibrate(20L);
}
}
}
public void setVibrator(Vibrator v) {
vibratorRef.set(v);
}
}
Which view you are using has nothing to do with it ....
At the moment you have only one BouncingBallModel
private final BouncingBallModel model = new BouncingBallModel(BALL_RADIUS);
This is the one you see when you draw something. Now if you want to draw multiple balls, you will need many BouncingBallModel. So either create a BouncingBallModel model2 or make it dynamic using an array.
Then iterate over the array and draw each ball.

Background for each series GraphView

Im using Graphview and work fine, but now i have a problem.
I would like to have a background for each series that is added to the graph, and not for all series
Is this possible?
This is currently (5 August 2014) not possible on the original GraphView library.
I needed this functionality, so I forked the library and implemented the functionality myself. You can find the updated code on the feature/series_specific_styles branch of my fork:
https://github.com/mobiRic/GraphView/tree/feature/series_specific_styles
Hopefully in future these changes will be pulled into the original library.
The actual code changes are relatively simple.
Added required background fields to GraphViewSeries.GraphViewSeriesStyle
Updated LineGraphView.drawSeries() to look for these fields instead of relying on its own internal values.
I have included full updates below, but the easiest way to view them is on the commit page:
allow different background for each series
Here is the updated GraphViewSeriesStyle class:
static public class GraphViewSeriesStyle {
public int color = 0xff0077cc;
public int thickness = 3;
private ValueDependentColor valueDependentColor;
private final Paint paintBackground;
private boolean drawBackground;
private boolean drawDataPoints;
private float dataPointsRadius = 10f;
public GraphViewSeriesStyle() {
super();
paintBackground = new Paint();
paintBackground.setColor(Color.rgb(20, 40, 60));
paintBackground.setStrokeWidth(4);
paintBackground.setAlpha(128);
}
public GraphViewSeriesStyle(int color, int thickness) {
super();
this.color = color;
this.thickness = thickness;
paintBackground = new Paint();
paintBackground.setColor(Color.rgb(20, 40, 60));
paintBackground.setStrokeWidth(4);
paintBackground.setAlpha(128);
}
public ValueDependentColor getValueDependentColor() {
return valueDependentColor;
}
/**
* the color depends on the value of the data.
* only possible in BarGraphView
* #param valueDependentColor
*/
public void setValueDependentColor(ValueDependentColor valueDependentColor) {
this.valueDependentColor = valueDependentColor;
}
public boolean getDrawBackground() {
return drawBackground;
}
public void setDrawBackground(boolean drawBackground) {
this.drawBackground = drawBackground;
}
public Paint getPaintBackground() {
return paintBackground;
}
public int getBackgroundColor() {
return paintBackground.getColor();
}
/**
* sets the background colour for the series. This is not the background
* colour of the whole graph.
*/
public void setBackgroundColor(int color) {
paintBackground.setColor(color);
}
public float getDataPointsRadius() {
return dataPointsRadius;
}
public boolean getDrawDataPoints() {
return drawDataPoints;
}
/**
* sets the radius of the circles at the data points.
* #see #setDrawDataPoints(boolean)
* #param dataPointsRadius
*/
public void setDataPointsRadius(float dataPointsRadius) {
this.dataPointsRadius = dataPointsRadius;
}
/**
* You can set the flag to let the GraphView draw circles at the data points
* #see #setDataPointsRadius(float)
* #param drawDataPoints
*/
public void setDrawDataPoints(boolean drawDataPoints) {
this.drawDataPoints = drawDataPoints;
}
}
Here is the updated LineGraphView.drawSeries() method:
public void drawSeries(Canvas canvas, GraphViewDataInterface[] values, float graphwidth, float graphheight, float border, double minX, double minY, double diffX, double diffY, float horstart, GraphViewSeriesStyle style) {
// draw background
double lastEndY = 0;
double lastEndX = 0;
// draw data
paint.setStrokeWidth(style.thickness);
paint.setColor(style.color);
Path bgPath = null;
if ((drawBackground) || (style.getDrawBackground())) {
bgPath = new Path();
}
lastEndY = 0;
lastEndX = 0;
float firstX = 0;
for (int i = 0; i < values.length; i++) {
double valY = values[i].getY() - minY;
double ratY = valY / diffY;
double y = graphheight * ratY;
double valX = values[i].getX() - minX;
double ratX = valX / diffX;
double x = graphwidth * ratX;
if (i > 0) {
float startX = (float) lastEndX + (horstart + 1);
float startY = (float) (border - lastEndY) + graphheight;
float endX = (float) x + (horstart + 1);
float endY = (float) (border - y) + graphheight;
// draw data point
if (drawDataPoints) {
//fix: last value was not drawn. Draw here now the end values
canvas.drawCircle(endX, endY, dataPointsRadius, paint);
} else if (style.getDrawDataPoints()) {
canvas.drawCircle(endX, endY, style.getDataPointsRadius(), paint);
}
canvas.drawLine(startX, startY, endX, endY, paint);
if (bgPath != null) {
if (i==1) {
firstX = startX;
bgPath.moveTo(startX, startY);
}
bgPath.lineTo(endX, endY);
}
} else if ((drawDataPoints) || (style.getDrawDataPoints())) {
//fix: last value not drawn as datapoint. Draw first point here, and then on every step the end values (above)
float first_X = (float) x + (horstart + 1);
float first_Y = (float) (border - y) + graphheight;
if (drawDataPoints) {
canvas.drawCircle(first_X, first_Y, dataPointsRadius, paint);
} else if (style.getDrawDataPoints()) {
canvas.drawCircle(first_X, first_Y, style.getDataPointsRadius(), paint);
}
}
lastEndY = y;
lastEndX = x;
}
if (bgPath != null) {
// end / close path
bgPath.lineTo((float) lastEndX, graphheight + border);
bgPath.lineTo(firstX, graphheight + border);
bgPath.close();
if (style.getDrawBackground()) {
canvas.drawPath(bgPath, style.getPaintBackground());
} else {
canvas.drawPath(bgPath, paintBackground);
}
}
}
For interest, that branch also allows data points to be configured for each series - code changes visible here:
allow datapoint styling for each series

Android animation equivalent for iOS "calculationMode"

I'm trying to animate some drawables in Android, I've set a path using PathEvaluator that animates along some curves along a full path.
When I set a duration (e.g. 6 seconds) it splits the duration to the number of curves I've set regardless of their length which causes the animation to be to slow on some segments and too fast on others.
On iOS this can be fixed using
animation.calculationMode = kCAAnimationCubicPaced;
animation.timingFunction = ...;
Which lets iOS to smooth your entire path into mid-points and span the duration according to each segment length.
Is there any way to get the same result in Android?
(besides breaking the path into discrete segments and assigning each segment its own duration manually which is really ugly and unmaintainable).
I don't think that anything can be done with ObjectAnimator because there seems to be no function that can be called to assert the relative duration of a certain fragment of the animation.
I did develop something similar to what you need a while back, but it works slightly differently - it inherits from Animation.
I've modified everything to work with your curving needs, and with the PathPoint class.
Here's an overview:
I supply the list of points to the animation in the constructor.
I calculate the length between all the points using a simple distance calculator. I then sum it all up to get the overall length of the path, and store the segment lengths in a map for future use (this is to improve efficiency during runtime).
When animating, I use the current interpolation time to figure out which 2 points I'm animating between, considering the ratio of time & the ratio of distance traveled.
I calculate the time it should take to animate between these 2 points according to the relative distance between them, compared to the overall distance.
I then interpolate separately between these 2 points using the calculation in the PathAnimator class.
Here's the code:
CurveAnimation.java:
public class CurveAnimation extends Animation
{
private static final float BEZIER_LENGTH_ACCURACY = 0.001f; // Must be divisible by one. Make smaller to improve accuracy, but will increase runtime at start of animation.
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>(); // map between the end point and the length of the path to it.
public CurveAnimation(List<PathPoint> pathPoints)
{
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2)
{
Log.e("CurveAnimation", "There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
PathPoint[] startEndPart = getStartEndForTime(interpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (interpolatedTime - startTime) / (endTime - startTime);
float x, y;
float[] xy;
if (endPoint.mOperation == PathPoint.CURVE)
{
xy = getBezierXY(startPoint, endPoint, progress);
x = xy[0];
y = xy[1];
}
else if (endPoint.mOperation == PathPoint.LINE)
{
x = startPoint.mX + progress * (endPoint.mX - startPoint.mX);
y = startPoint.mY + progress * (endPoint.mY - startPoint.mY);
}
else
{
x = endPoint.mX;
y = endPoint.mY;
}
t.getMatrix().setTranslate(x, y);
super.applyTransformation(interpolatedTime, t);
}
private PathPoint[] getStartEndForTime(float time)
{
double length = 0;
if (time == 1)
{
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2), mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time)
{
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartTimeOfPoint(PathPoint point)
{
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1)
{
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength()
{
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++)
{
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index)
{
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end)
{
if (mSegmentLengths.containsKey(end))
{
return mSegmentLengths.get(end);
}
else if (end.mOperation == PathPoint.LINE)
{
return calculateLength(start.mX, end.mX, start.mY, end.mY);
}
else if (end.mOperation == PathPoint.CURVE)
{
return calculateBezeirLength(start, end);
}
else
{
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1)
{
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end)
{
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY)
{
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress)
{
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX +
3 * oneMinusT * oneMinusT * progress * end.mControl0X +
3 * oneMinusT * progress * progress * end.mControl1X +
progress * progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY +
3 * oneMinusT * oneMinusT * progress * end.mControl0Y +
3 * oneMinusT * progress * progress * end.mControl1Y +
progress * progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
Here's a sample that shows how to activate the animation:
private void animate()
{
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0);
path.lineTo(0, 300);
path.curveTo(100, 0, 300, 900, 400, 500);
CurveAnimation animation = new CurveAnimation(path.mPoints);
animation.setDuration(5000);
animation.setInterpolator(new LinearInterpolator());
btn.startAnimation(animation);
}
Now, keep in mind that I'm currently calculating the length of the curve according to an approximation. This will obviously cause some mild inaccuracies in the speed. If you feel it's not accurate enough, feel free to modify the code. Also, if you want to increase the length accuracy of the curve, try decreasing the value of BEZIER_LENGTH_ACCURACY. It must be dividable by 1, so accepted values can be 0.001, 0.000025, etc.
While you might notice some mild fluctuations in speed when using curves, I'm sure it's much better than simply dividing the time equally between all paths.
I hope this helps :)
I tried using Gil's answer, but it didn't fit how I was animating.
Gil wrote an Animation class which is used to animate Views.
I was using ObjectAnimator.ofObject() to animate custom classes using ValueProperties which can't be used with custom Animation.
So this is what I did:
I extend PathEvaluator and override its evaluate method.
I use Gil's logic to calculate path total length, and segmented lengths
Since PathEvaluator.evaluate is called for each PathPoint with t values
0..1, I needed to normalize the interpolated time given to me, so it'll be incremental and won't zero out for each segment.
I ignore the start/end PathPoints given to me so the current position can be
before start or after end along the path depending on the segment's duration.
I pass the current progress calculated to my super
(PathEvaluator) to calc the actual position.
This is the code:
public class NormalizedEvaluator extends PathEvaluator {
private static final float BEZIER_LENGTH_ACCURACY = 0.001f;
private List<PathPoint> mPathPoints;
private float mOverallLength;
private Map<PathPoint, Double> mSegmentLengths = new HashMap<PathPoint, Double>();
public NormalizedEvaluator(List<PathPoint> pathPoints) {
mPathPoints = pathPoints;
if (mPathPoints == null || mPathPoints.size() < 2) {
Log.e("CurveAnimation",
"There must be at least 2 points on the path. There will be an exception soon!");
}
calculateOverallLength();
}
#Override
public PathPoint evaluate(float interpolatedTime, PathPoint ignoredStartPoint,
PathPoint ignoredEndPoint) {
float index = getStartIndexOfPoint(ignoredStartPoint);
float normalizedInterpolatedTime = (interpolatedTime + index) / (mPathPoints.size() - 1);
PathPoint[] startEndPart = getStartEndForTime(normalizedInterpolatedTime);
PathPoint startPoint = startEndPart[0];
PathPoint endPoint = startEndPart[1];
float startTime = getStartTimeOfPoint(startPoint);
float endTime = getStartTimeOfPoint(endPoint);
float progress = (normalizedInterpolatedTime - startTime) / (endTime - startTime);
return super.evaluate(progress, startPoint, endPoint);
}
private PathPoint[] getStartEndForTime(float time) {
double length = 0;
if (time == 1) {
return new PathPoint[] { mPathPoints.get(mPathPoints.size() - 2),
mPathPoints.get(mPathPoints.size() - 1) };
}
PathPoint[] result = new PathPoint[2];
for (int i = 0; i < mPathPoints.size() - 1; i++) {
length += calculateLengthFromIndex(i);
if (length / mOverallLength >= time) {
result[0] = mPathPoints.get(i);
result[1] = mPathPoints.get(i + 1);
break;
}
}
return result;
}
private float getStartIndexOfPoint(PathPoint point) {
for (int ii = 0; ii < mPathPoints.size(); ii++) {
PathPoint current = mPathPoints.get(ii);
if (current == point) {
return ii;
}
}
return -1;
}
private float getStartTimeOfPoint(PathPoint point) {
float result = 0;
int index = 0;
while (mPathPoints.get(index) != point && index < mPathPoints.size() - 1) {
result += (calculateLengthFromIndex(index) / mOverallLength);
index++;
}
return result;
}
private void calculateOverallLength() {
mOverallLength = 0;
mSegmentLengths.clear();
double segmentLength;
for (int i = 0; i < mPathPoints.size() - 1; i++) {
segmentLength = calculateLengthFromIndex(i);
mSegmentLengths.put(mPathPoints.get(i + 1), segmentLength);
mOverallLength += segmentLength;
}
}
private double calculateLengthFromIndex(int index) {
PathPoint start = mPathPoints.get(index);
PathPoint end = mPathPoints.get(index + 1);
return calculateLength(start, end);
}
private double calculateLength(PathPoint start, PathPoint end) {
if (mSegmentLengths.containsKey(end)) {
return mSegmentLengths.get(end);
} else if (end.mOperation == PathPoint.LINE) {
return calculateLength(start.mX, end.mX, start.mY, end.mY);
} else if (end.mOperation == PathPoint.CURVE) {
return calculateBezeirLength(start, end);
} else {
return 0;
}
}
private double calculateLength(float x0, float x1, float y0, float y1) {
return Math.sqrt(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1)));
}
private double calculateBezeirLength(PathPoint start, PathPoint end) {
double result = 0;
float x, y, x0, y0;
float[] xy;
x0 = start.mX;
y0 = start.mY;
for (float progress = BEZIER_LENGTH_ACCURACY; progress <= 1; progress += BEZIER_LENGTH_ACCURACY) {
xy = getBezierXY(start, end, progress);
x = xy[0];
y = xy[1];
result += calculateLength(x, x0, y, y0);
x0 = x;
y0 = y;
}
return result;
}
private float[] getBezierXY(PathPoint start, PathPoint end, float progress) {
float[] result = new float[2];
float oneMinusT, x, y;
oneMinusT = 1 - progress;
x = oneMinusT * oneMinusT * oneMinusT * start.mX + 3 * oneMinusT * oneMinusT * progress
* end.mControl0X + 3 * oneMinusT * progress * progress * end.mControl1X + progress
* progress * progress * end.mX;
y = oneMinusT * oneMinusT * oneMinusT * start.mY + 3 * oneMinusT * oneMinusT * progress
* end.mControl0Y + 3 * oneMinusT * progress * progress * end.mControl1Y + progress
* progress * progress * end.mY;
result[0] = x;
result[1] = y;
return result;
}
}
This is the usage:
NormalizedEvaluator evaluator = new NormalizedEvaluator((List<PathPoint>) path.getPoints());
ObjectAnimator anim = ObjectAnimator.ofObject(object, "position", evaluator, path.getPoints().toArray());
UPDATE: I just realized that I might have reinvented the wheel, please look at Specifying Keyframes.
It is shocking to see that nothing is available of this kind. Anyways if you don't want to calculate path length at run time then I was able to add functionality of assigning weights to paths. Idea is to assign a weight to your path and run the animation if it feels OK then well and good otherwise just decrease or increase weight assigned to each Path.
Following code is modified code from official Android sample that you pointed in your question:
// Set up the path we're animating along
AnimatorPath path = new AnimatorPath();
path.moveTo(0, 0).setWeight(0);
path.lineTo(0, 300).setWeight(30);// assign arbitrary weight
path.curveTo(100, 0, 300, 900, 400, 500).setWeight(70);// assign arbitrary weight
final PathPoint[] points = path.getPoints().toArray(new PathPoint[] {});
mFirstKeyframe = points[0];
final int numFrames = points.length;
final PathEvaluator pathEvaluator = new PathEvaluator();
final ValueAnimator anim = ValueAnimator.ofInt(0, 1);// dummy values
anim.setDuration(1000);
anim.setInterpolator(new LinearInterpolator());
anim.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
// Special-case optimization for the common case of only two
// keyframes
if (numFrames == 2) {
PathPoint nextPoint = pathEvaluator.evaluate(fraction,
points[0], points[1]);
setButtonLoc(nextPoint);
} else {
PathPoint prevKeyframe = mFirstKeyframe;
for (int i = 1; i < numFrames; ++i) {
PathPoint nextKeyframe = points[i];
if (fraction < nextKeyframe.getFraction()) {
final float prevFraction = prevKeyframe
.getFraction();
float intervalFraction = (fraction - prevFraction)
/ (nextKeyframe.getFraction() - prevFraction);
PathPoint nextPoint = pathEvaluator.evaluate(
intervalFraction, prevKeyframe,
nextKeyframe);
setButtonLoc(nextPoint);
break;
}
prevKeyframe = nextKeyframe;
}
}
}
});
And that's it !!!.
Of course I modified other classes as well but nothing big was added. E.g. in PathPoint I added this:
float mWeight;
float mFraction;
public void setWeight(float weight) {
mWeight = weight;
}
public float getWeight() {
return mWeight;
}
public void setFraction(float fraction) {
mFraction = fraction;
}
public float getFraction() {
return mFraction;
}
In AnimatorPath I modified getPoints() method like this:
public Collection<PathPoint> getPoints() {
// calculate fractions
float totalWeight = 0.0F;
for (PathPoint p : mPoints) {
totalWeight += p.getWeight();
}
float lastWeight = 0F;
for (PathPoint p : mPoints) {
p.setFraction(lastWeight = lastWeight + p.getWeight() / totalWeight);
}
return mPoints;
}
And thats pretty much it. Oh and for better readability I added Builder Pattern in AnimatorPath, so all 3 methods were changed like this:
public PathPoint moveTo(float x, float y) {// same for lineTo and curveTo method
PathPoint p = PathPoint.moveTo(x, y);
mPoints.add(p);
return p;
}
NOTE: To handle Interpolators that can give fraction less then 0 or greater than 1 (e.g. AnticipateOvershootInterpolator) look at com.nineoldandroids.animation.KeyframeSet.getValue(float fraction) method and implement the logic in onAnimationUpdate(ValueAnimator animation).

Android Libgdx and collision detection

For my android game I use Libgdx and I detect the collision between Bob (Omino) and Plant (Pianta) with this code that works fine :
Assets.class
pianta = new Animation(0.5f,new TextureRegion(items, 160, 384, 64, 96),
new TextureRegion(items, 224, 384, 64, 96));
Pianta.class
public class Pianta extends GameObject {
public static final float PIANTA_WIDTH = 2;
public static final float PIANTA_HEIGHT = 3;
public static float stateTime;
public Pianta(float x, float y) {
super(x, y, PIANTA_WIDTH, PIANTA_HEIGHT);
stateTime = 0;
}
public void update(float deltaTime) {
stateTime += deltaTime;
}
}
World.class
Pianta pianta1_0 = new Pianta(x+10,2.2f);
piante.add(pianta1_0);
private void collisionPiante(){
int len = piante.size();
for(int i=0;i<len;i++){
if(OverlapTester.overlapRectangles(piante.get(i).bounds,omino.bounds)){
omino.ominoMorto();
}
}
}
WorldRender.class
private void renderPiante() {
TextureRegion keyFrame;
int len = world.piante.size();
for(int i = 0; i < len; i++) {
Pianta pianta = world.piante.get(i);
keyFrame = Assets.pianta.getKeyFrame(Pianta.stateTime, Animation.ANIMATION_LOOPING);
batcher.draw(keyFrame,pianta.position.x, pianta.position.y, 2, 3);
}
}
but if you watch the image 2 below, you can see that Bob hit but there isn't collision with stone (Pietra) !!
This is the code :
Assets.class
pietra1 = new TextureRegion(items,288,416,128,64);
Pietra.class
public class Pietra extends GameObject {
public static float PIETRA_WIDTH = 4;
public static float PIETRA_HEIGHT = 2;
public Pietra(float x, float y) {
super(x, y, PIETRA_WIDTH, PIETRA_HEIGHT);
}
}
World.class
Pietra pietra1_0 = new Pietra(x+25,2.2f);
pietre.add(pietra1_0);
private void collisionPietre(){
int len2 = pietre.size();
for(int l=0;l<len2;l++){
if(OverlapTester.overlapRectangles(pietre.get(l).bounds,omino.bounds)){
omino.ominoMorto();
}
}
}
WorldRender.class
private void renderPietre() {
int len = world.pietre.size();
for(int i = 0; i < len; i++) {
Pietra pietra = world.pietre.get(i);
batcher.draw(Assets.pietra1,pietra.position.x, pietra.position.y, 4, 2);
}
}
OverlapTester
public class OverlapTester {
public static boolean overlapRectangles (Rectangle r1, Rectangle r2) {
if (r1.x < r2.x + r2.width && r1.x + r1.width > r2.x && r1.y < r2.y + r2.height && r1.y + r1.height > r2.y)
return true;
else
return false;
}
Someone can tell me why the collision with the plant works fine and with stone Bob hit even if there is no collision? as you can see the code is the same, the only difference is that the plant is an animated object while the stone isn't.
Check your OverlapTester. This is how Libgdx does it in the Rectangle.java class:
/** #param rectangle the other {#link Rectangle}
* #return whether this rectangle overlaps the other rectangle. */
public boolean overlaps (Rectangle rectangle) {
return !(x > rectangle.x + rectangle.width || x + width < rectangle.x || y > rectangle.y + rectangle.height || y + height < rectangle.y);
}
If I understood right overlapRectangles checks the case if rectangle is totally inside. It is not probably thing you want.
LibGDX has special functionality for collision checking. Please, check http://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/math/Intersector.html
You may wish to replace your OverlapTester with the Rectangle's helper function contains. For instance:
World Class
if(OverlapTester.overlapRectangles(piante.get(i).bounds,omino.bounds)){
omino.ominoMorto();
}
Can be:
if (piante.get(i).bounds.contains(omino.bounds)) {
omino.ominoMorto();
}

how to bound set of points (custom shape) and touch event on it android

i am able to get my bitmap set of points (as an array) using this link
now my question is how can i bound these points as shape/region. Means when user touched on area of my bounded points, i want to move objects(shape) according to that. Above link return points of colored bitmap (it remove transparent part), only colored part points are return as an array.
This is what my code :
1) CustomSahpe.java
public class CustomShape {
private final Context context;
Bitmap bitmap;
int width, height;
int[] pixels;
private final ArrayList<Point> points = new ArrayList<Point>();
public CustomShape(Context context) {
// TODO Auto-generated constructor stub
// super(context);
this.context = context;
bitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_menu_balloon);
width = bitmap.getWidth();
height = bitmap.getHeight();
pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
getActualBitmap();
}
public ArrayList<Point> getPoints(){
return points;
}
public void getActualBitmap() {
for (int x = 0; x < width; x+=2) {
int firstY = -1, lastY = -1;
for (int y = 0; y < height; y+=2) {
boolean transparent = (pixels[y * width + x] == Color.TRANSPARENT);
if (!transparent) {
if (firstY == -1) {
firstY = y;
}
lastY = y;
}
}
if (firstY != -1) {
points.add(new Point(x, firstY));
points.add(new Point(x, lastY));
}
}
}
}
2) MyShapre.java
class MyShape{
CustomShape customShape ;
Point points[];
private int x, y;
Path path = new Path();
public MyShape(Context context) {
customShape = new CustomShape(ScaleTestActivity.this);
points = new Point[customShape.getPoints().size()];
for(int i=0;i<customShape.getPoints().size();i++){
points[i] = new Point();
points[i] = customShape.getPoints().get(i);
}
}
public Path getPath(){
return path;
}
public void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
Paint paint = new Paint();
paint.setColor(Color.WHITE);
for(int i =0 ;i<points.length;i++){
Point point = new Point(points[i].x + getX(), points[i].y + getY());
path.lineTo(points[i].x, points[i].y);
canvas.drawPoint(point.x,point.y,paint);
}
}
public void setX(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setY(int y) {
this.y = y;
}
public int getY() {
return y;
}
}
}
3) MainPanel.java
class MainPanel extends View{
Context context;
MyShape myShape;
boolean flag = false;
public MainPanel(Context context) {
super(context);
this.context = context;
myShape = new MyShape(context);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
myShape.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
int x,y;
x = (int)event.getX();
y = (int)event.getY();
Point point = new Point(x, y);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
myShape.setX(x);
myShape.setY(y);
RectF rectF = new RectF();
Path path = myShape.getPath();
path.computeBounds(rectF, true);
Region region = new Region();
region.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
if(region.contains(x,y)){
flag = true;
Log.i("System out","onDown");
}
break;
case MotionEvent.ACTION_MOVE:
Log.i("System out","onMove : "+flag);
if(flag){
myShape.setX(x);
myShape.setY(y);
Log.i("System out","onMove");
}
break;
case MotionEvent.ACTION_UP:
// myShape.setX(x);
// myShape.setY(y);
flag = false;
Log.i("System out","onUp");
break;
default:
break;
}
invalidate();
return true;
}
}
4) ScaleTestActivity.java
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MainPanel(this));
}
I use a Polygon class to detect touches on rotated bitmaps. It's based mostly on information and code from this site http://alienryderflex.com/polygon/. This should work with your code.
public class Polygon {
// Polygon coodinates.
private final int[] polyY, polyX;
// Number of sides in the polygon.
private final int polySides;
/**
* Default constructor.
* #param px Polygon y coods.
* #param py Polygon x coods.
* #param ps Polygon sides count.
*/
public Polygon( final int[] px, final int[] py, final int ps ) {
polyX = px;
polyY = py;
polySides = ps;
}
/**
* Checks if the Polygon contains a point.
* #see "http://alienryderflex.com/polygon/"
* #param x Point horizontal pos.
* #param y Point vertical pos.
* #return Point is in Poly flag.
*/
public boolean contains( final float x, final float y ) {
boolean oddTransitions = false;
for( int i = 0, j = polySides -1; i < polySides; j = i++ ) {
if( ( polyY[ i ] < y && polyY[ j ] >= y ) || ( polyY[ j ] < y && polyY[ i ] >= y ) ) {
if( polyX[ i ] + ( y - polyY[ i ] ) / ( polyY[ j ] - polyY[ i ] ) * ( polyX[ j ] - polyX[ i ] ) < x ) {
oddTransitions = !oddTransitions;
}
}
}
return oddTransitions;
}
}
You could add this constructor to help you convert a Point array to a Polygon object.
public Polygon(Point[] points){
polySides = points.length;
polyY = new int[polySides];
polyX = new int[polySides];
for(int i = 0; i < polySides; i++){
polyY[i] = points[i].y;
polyX[i] = points[i].x;
}
}
You might be able to use it in your MyShape class with this method.
public boolean isTouched(final float X, final float Y){
final Polygon p = new Polygon(points);
return p.contains(X, Y);
}
Now if you have an odd shape you should be able to detect exactly if the use touches it. I have used this method many times.
Are you looking for a way to tell whether a touch event falls on the non-transparent portion of your drawn bitmap? If so, why don't you just map the touch coordinate to the proper pixel on the bitmap and test the color?
And if that's the case, then you can skip all the path clipping stuff, since the link you posted was only doing that to overcome emulator inefficiencies.
It's a bit complicated so I am not going to provide the full source but I will give you an idea.
You need to transfer your shape in to a triangles collection, then on touch find the nearest point of your shape and check if your are inside this point triangle.
For searching and sorting points you can use red-black-red tree structure.
The search algorithm eventually should be at O(log(N)) and creating shape structure should be O(N*Log(N))

Categories

Resources