I have a sprite sheet that has 7 columns. I want to animate (change the column) smoothly. However, if I don't control sprite speed using update % 10, it animates too fast, consequently, the animation is not recognizable. At the moment, I switch between sprite columns every 10 frames, this will not be consistent based on the FPS of the different android devices. Is there a standard way to control sprite animation speed?
private final int BMP_ROWS = 1;
private final int BMP_COLUMNS = 7;
// sprite update logic
public boolean update(float deltaTime) {
delayCounterMs += deltaTime;
animate = true;
if (update % 10 == 0) {
currentFrame = ++currentFrame % BMP_COLUMNS;
int srcX = currentFrame * width;
src = new Rect(srcX, 0, srcX + width, height);
dst = new Rect(x, y, x + width, y + height);
}
update++;
return false;
}
// sprite draw logic
public void draw(Canvas canvas) {
if (animate) {
canvas.drawBitmap(bmp, src, dst, null);
}
}
Related
Im trying to build a simple Music Visualisation App which just should resize a Circle. So if the Music Part which is currently playing is loud it should get bigger and if not it should get smaller.
To Visualize the Circle I just created a custom View Class which draws the circle in the onDraw Method.
To get the informations out of the current Audio, I found the Visualizer Class of Android and also used the setDataCaptureListener.
mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
mVisualizer.setDataCaptureListener(
new Visualizer.OnDataCaptureListener() {
public void onWaveFormDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
mVisualizerView.updateVisualizer(bytes);
}
public void onFftDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
}
}, (int)(Visualizer.getMaxCaptureRate() / 1.5), true, false);
But my Problem is that I don't really know how I use the byte Array which is given back, to find out the music change in general (got louder or not ?).
I just tried to get the average of the array but this gives me completely bad results. The circle changed his size like it is on drugs. So I thought maybe the array has too many outlined/extreme values (which was true) so I calculated the median of the array. This gaved me better results but still isn't what I want. It's not very smooth and it's to complex. I always have to sort the array which is not really efficient. What am I thinking wrong ?
Im really a beginner in this AudioFX section and Im completely sorry If this is a dumb question and attempt of me.
Thank you for your help !
EDIT:
private float schwelle = 5000;
private float last = 0;
...
float summe = 0;
for (Byte currentByte: mBytes)
summe += currentByte;
if (summe > schwelle && summe > last)
{
last = summe; //make it bigger
}
else {
last -= 100; //make circle smaller
}
canvas.drawCircle(getWidth()/2,getHeight()/2,last / 100,mForePaint);
A really good git project is https://github.com/felixpalmer/android-visualizer.
I myself came up with this:(it's a lot simple than the git solution)
You can use the values of the array to draw the the waveform on the outline of a circle using trigonometry, and make the start radius of the circle bigger if the sum of the array is bigger than certain treshhold:
class StarWaveformRenderer implements Renderer {
private Paint p = new Paint();
private static final int BOOST_TRASH_HOLD = 10000;
private float stretchFade = 1; //circle fades after a prominent beat
#Override
public void render(Canvas canvas, byte[] data) {
if (data == null || data.length == 0)
return;
int centerX = canvas.getWidth() / 2;
int centerY = canvas.getHeight() / 2;
float stretch = stretchFade;
int sum = RenderUtils.sum(data);
p.setColor((p.getColor() + sum / 2)); //change color of circle
if (sum > BOOST_TRASH_HOLD) {//prominent beat
stretch = (float) Math.min(canvas.getWidth(), canvas.getHeight()) / Byte.MAX_VALUE / 3; //maximum
stretchFade = stretch;
}
double radDif = 2 * Math.PI / data.length; //the angle between each element of the array
double radPos = 0;
float lX = (float) Math.cos(radPos) * data[0] + centerX;
float lY = (float) Math.sin(radPos) * data[0] + centerY;
float cX;
float cY;
for (byte b : data) {
cX = (float) Math.cos(radPos) * b * stretch + centerX;
cY = (float) Math.sin(radPos) * b * stretch + centerY;//calculate position of outline, stretch indicates promince of the beat
canvas.drawLine(lX, lY, cX, cY, p);
lX = cX;
lY = cY;
radPos += radDif;
}
stretchFade = Math.max(1, stretchFade / 1.2f);//beat fades out
}
}
You can programm your own renderes and let the user select which one he wants to use. Just pass the array from onWaveformDataCapture to the onRender method.
Utils for analysing the waveform (the amplitude is stored kind of weird):
class RenderUtils {
private static final byte SHIFT = Byte.MAX_VALUE;
static int sum(byte[] data) {
int sum = 0;
for (byte b : data)
sum += b;
return sum;
}
static int toAmplitude(byte b) {
return b > 0 ? b + SHIFT : -b;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
static float toAmplitude(float f) {
return f > 0 ? f + SHIFT : -f;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
}
I have a Canvas that is scaled so everything fits better:
#Override
public void draw(Canvas c){
super.draw(c);
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
if(c!=null) {
final int savedState = c.save();
c.scale(scaleFactorX, scaleFactorY);
(rendering)
c.restoreToCount(savedState);
}
}
It scales based on these two:
public static final int WIDTH = 856;
public static final int HEIGHT = 1050;
Which causes the problem that the coordinates of the MotionEvent that handles touch events is not equal to the coordinates that is created with the Canvas. This causes problems when I try to check collision between the MotionEvent Rect and the Rect of a class that is based on the rendering scale. This causes the class SuperCoin's X coordinate to not be equal to MotionEvent X coordinates.
Usually, MotionEvent's coordinates, both X and Y is way bigger than the screen's max size(defined by WIDTH and HEIGHT)
#Override
public boolean onTouchEvent(MotionEvent e) {
super.onTouchEvent(e);
switch (MotionEventCompat.getActionMasked(e)) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
(...)
Rect r = new Rect((int)e.getX(), (int)e.getY(), (int)e.getX() + 3, (int)e.getY() + 3);
if(superCoins.size() != 0) {
for (SuperCoin sc : superCoins) {
if (sc.checkCollision(r)) {
progress++;
superCoins.remove(sc);
}
}
}
break;
}
return true;
}
And the SuperCoin:
public class SuperCoin {
private Bitmap bm;
public int x, y, orgY;
Clicker cl;
private Long startTime;
Random r = new Random();
public SuperCoin(Bitmap bm, int x, int y, Clicker c){
this.x = x;
this.y = y;
this.orgY = y;
this.bm = bm;
this.cl = c;
startTime = System.nanoTime();
bounds = new Rect(x, y, x + bm.getWidth(), y + bm.getHeight());
}
private Rect bounds;
public boolean checkCollision(Rect second){
if(second.intersect(bounds)){
return true;
}
return false;
}
private int velX = 0, velY = 0;
public void render(Canvas c){
long elapsed = (System.nanoTime()-startTime)/1000000;
if(elapsed>50) {
int cx;
cx = r.nextInt(2);
if(cx == 0){
velX = r.nextInt(4);
}else if(cx == 1){
velX = -r.nextInt(4);
}
velY = r.nextInt(10) + 1;
startTime = System.nanoTime();
}
if(x < 0) velX = +2;
if(x > Clicker.WIDTH) velX = -2;
x += velX;
y -= velY;
c.drawBitmap(bm, x, y, null);
}
}
How can I check collision between the two different when the MotionEvent X coordinate is bigger than the screen's scaled max coordinates?
Honestly, I am not completly sure why the Rect defined in the SuperCoin class is different from the one defined in the onTouchEvent method. I'm guessing because the X and Y is permanently different between the one defined by MotionEvent and the ones defined by the scaled canvas. The Rect in the SuperCoin class goes by the width of the Bitmap it has been passed. It scales it with the width and height of the Bitmap.
After looking through StackOverflow and Google for the past 2 days looking for something that comes close to a solution, I came over this: Get Canvas coordinates after scaling up/down or dragging in android Which solved the problem. It was really hard to find because the title was slightly misleading(of the other question)
float px = e.getX() / mScaleFactorX;
float py = e.getY() / mScaleFactorY;
int ipy = (int) py;
int ipx = (int) px;
Rect r = new Rect(ipx, ipy, ipx+2, ipy+2);
I added this as an answer and accepting it so it no longer will be an unanswered question as it is solved. The code above converts the coordinates to integers so they can be used for checking collision between the finger and the object I'm checking with
Don't scale the canvas directly. Make a Matrix object, scale that once. Then concat that to the canvas. Then you can make an inverted matrix for your touch events.
And just make the invert matrix whenever you change the view matrix:
viewMatrix = new Matrix();
viewMatrix.scale(scalefactor,scalefactor);
invertMatrix = new Matrix(viewMatrix);
invertMatrix.invert(invertMatrix);
Then apply these two matrices to the relevant events.
#Override
public boolean onTouchEvent(MotionEvent event) {
event.transform(invertMatrix);
And then on the draw events, concat the matrix.
#Override
protected void onDraw(Canvas canvas) {
canvas.concat(viewMatrix);
And you're done. everything is taken care of for you. Whatever modifications you do to the view matrix will change your viewbox and your touch events will be translated into that same scene too.
If you want to add panning or rotation, or even skew the view, it's all taken care of. Just apply that to the matrix, get the inverted matrix, and the view will look that way and the touch events will respond as you expect.
I have a custom ImageTextButton in which I render the button to a FrameBuffer first and then draw with frameBuffer.getColorBufferTexture(). I don't really want to do this but I use a custom shader with this button that creates some visual effects and the only way I have been able to achieve it is with a FrameBuffer. I was surprised to find this actually works very smooth and fast though, the whole process takes 1-2ms on slow devices and having several instances doesn't cause any kind of framerate drop, so I am happy with this bit.
The issue I am having though is when I enable clipping on the ImageTextButton (with setClip(true)). The reason for this is the button can change in width, and I would like it to clip the text within the bounds of the button. If I disable the FrameBuffer and render normally, this part also works very well. If I combine the 2, it seems the clipping process gets confused and the result is either no text or very small parts of the text.
So here is the relevant code. I assumed it was because I set the FrameBuffer and SpriteBatch size/projection matrix just to deal with the active area (for efficiency) however if I don't modify any of this and use the same batch/projection matrix, so the FrameBuffer manages the whole screen, it is still the same result.
public void initFrameBuffer(){
xCache = (int) super.getX(); yCache = (int) super.getY();
widthCache = (int) super.getWidth(); heightCache = (int) super.getHeight();
frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, widthCache, heightCache, false);
fboProjectionMatrix.setToOrtho2D(xCache, yCache+heightCache, widthCache, -heightCache);
this.fbBatch = new SpriteBatch();
this.fbBatch.setProjectionMatrix(fboProjectionMatrix);
this.frameBufferReady = true;
}
public void doFrameBuffer(Batch batch, float parentAlpha){
batch.end();
frameBuffer.begin();
fbBatch.begin();
Gdx.gl20.glClearColor(0f, 0.0f, 0.0f, 0.0f);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
super.draw(fbBatch, parentAlpha);
fbBatch.end();
frameBuffer.end();
batch.begin();
}
public void drawFrameBufferObject(Batch batch, float parentAlpha){
batchColorCache = batch.getColor();
batch.setColor(1.0f, 1.0f, 1.0f, parentAlpha);
batch.draw(frameBuffer.getColorBufferTexture(), getX(), getY());
batch.setColor(batchColorCache);
}
#Override
public void draw(Batch batch, float parentAlpha) {
if (!this.frameBufferReady) initFrameBuffer();
doFrameBuffer(batch, parentAlpha);
drawFrameBufferObject(batch, parentAlpha);
}
Sorry for the long code, it's actually heavily trimmed down for the necessary parts..
Help hugely appreciated as always!
After much playing, the solution I have found is one that could probably be useful in other situations, and that is true clipping of the BitmapFontCache by vertex modification, no scissors involved! So if anyone would find this useful, the code is;
float xStart = ...start position of clip
float xEnd = ...end position of clip
//vertex offset numbers
int x_1 = 0, x_2 = 5, x2_1 = 10, x2_2 = 15;
int u_1 = 3, u_2 = 8, u2_1 = 13, u2_2 = 18;
for (int j = 0, n = pageCount; j < n; j++) {
int c = cache.getVertexCount(j);
int newIdx = 0;
if (c > 0) { // ignore if this texture has no glyphs
float[] vertices = cache.getVertices(j);
for(int i = 0; i < vertices.length; i+=20){
//if any of the vertices are outside the label, don't put them in the new cache
if(vertices[i+x2_1] > xStart && vertices[i+x_1] < xEnd){
for(int k = 0; k < 20; k++){
clippedVerts[j][newIdx+k] = vertices[i+k];
}
//case on major left glyph
if(vertices[i+x_1] < xStart){
float xDiff = vertices[i+x2_1]-xStart; //difference between right of glyph and clip
float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
float uDiff = vertices[i+u2_1] - vertices[i+u_1];
float newU = vertices[i+u2_1] - uDiff*xRatio;
clippedVerts[j][newIdx+x_1] = xStart;
clippedVerts[j][newIdx+x_2] = xStart;
clippedVerts[j][newIdx+u_1] = newU;
clippedVerts[j][newIdx+u_2] = newU;
}
//case on major right glyph
if(vertices[i+x2_1] > xEnd){
float xDiff = xEnd-vertices[i+x_1]; //difference between left of glyph and clip
float xRatio = xDiff / (vertices[i+x2_1]-vertices[i+x_1]);
float uDiff = vertices[i+u2_1] - vertices[i+u_1];
float newU_2 = vertices[i+u_1] + uDiff*xRatio;
clippedVerts[j][newIdx+x2_1] = xEnd;
clippedVerts[j][newIdx+x2_2] = xEnd;
clippedVerts[j][newIdx+u2_1] = newU_2;
clippedVerts[j][newIdx+u2_2] = newU_2;
}
newIdx += 20;
}
}
}
clippedIdx[j] = newIdx;
}
for (int j = 0, n = pageCount; j < n; j++) {
int idx = clippedIdx[j];
if (idx > 0) { // ignore if this texture has no glyphs
float[] vertices = clippedVerts[j];
batch.draw(regions.get(j).getTexture(), vertices, 0, idx);
}
}
I'm trying to develop a simple game. My problem is I am trying to rotate an arrow image in the bottom of the screen by following the "x" event from Touch Listener.
Below is my Arrow class:
public class Arrow extends GameObject{
boolean show = true;
Bitmap bmp;
Game game;
Matrix matrix;
int i = 0;
public Arrow(Handler handler, int x, int y, int xSpeed, int ySpeed , Game game) {
super(handler, x, y, xSpeed, ySpeed);
this.game = game;
bmp = BitmapFactory.decodeResource(game.getResources(), R.drawable.arrow);
matrix = new Matrix();
}
#Override
public void tick() {
}
#Override
public void render(Canvas c) {
matrix.postRotate(x);
c.drawBitmap(bmp,matrix, null);
}
}
and this is the Event Listener
#Override
public boolean onTouch(View v, MotionEvent event) {
x = (int) event.getX();
y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
arrow.setX(x);
break;
case MotionEvent.ACTION_MOVE :
touch.move();
break;
case MotionEvent.ACTION_UP:
break;
}
}
}
}
return true;
}
How do I write code which rotates the bitmap?
Here is an example.
Matrix matrix = new Matrix();
//Calculate the rotation of the bitmap.
rotation += 10;
matrix.postRotate(rotation); // or matrix.postRotate(rotation,cx,cy);
canvas.drawBitmap(bitmap, matrix, null);
As an optimization, create the Matrix once outside this method and replace the creation with a call to matrix.reset().This way the canvas stays directed as before, and you can do more stuff with your Matrix like translating, scaling etc. and the matrix's content encapsulates the real meaning of your manipulation.
Calculating the angle :
For calculating the angle first you need to know the total progress length(Min value of progress - Max value of progress) of the Seekbar that can and the change in the value after seek.
Consider
Min of seek bar = 0
Max value of seekbar = 30
Now calculate the angle per unit that is,
1 unit = 360/30 = 12.
Suppose if the user has changed the seekbar position from 10 to 20. Now calculate the diff
int diff = 10-20 = -10.
rotation angle = (-10 * 12) = -120;
Example 2 :
if the user has changed the seekbar position from 15 to 10. Now calculate the diff
int diff = 15-10 = 5.
rotation angle = (5 * 12) = 60;
Here is the answer to my own question, it rotates the image according to the x input data:
float zeroPoint =MainGame.SCREEN_W / 2;
zeroPoint-=x;
float lala = MainGame.SCREEN_W/(180-y);
matrix.setRotate(zeroPoint/=lala, bmp.getWidth() /2, bmp.getHeight());
I intend to create an application that can take photos in the following way:
When the user touches the screen, it starts to take photos
It takes several photos within a few microseconds, each with different focus
In pseudocode:
Camera camera = getAndroidCamera();
for(i<10)
{
camera.setFocus(i*0.1);
camera.takePhoto(path, pictureName+i);
}
So basically I intend to take photos of the same object with different values of focus.
According to this, it is not possible, only assisted autofocus is viable.
Can you confirm it?
If possible, how should I do it? Should I set autofocus to different areas?
Answer -- Android setFocusArea and Auto Focus
All I had to do is cancel previously called autofocus. Basically the correct order of actions is this:
protected void focusOnTouch(MotionEvent event) {
if (camera != null) {
camera.cancelAutoFocus();
Rect focusRect = calculateTapArea(event.getX(), event.getY(), 1f);
Rect meteringRect = calculateTapArea(event.getX(), event.getY(), 1.5f);
Parameters parameters = camera.getParameters();
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
parameters.setFocusAreas(Lists.newArrayList(new Camera.Area(focusRect, 1000)));
if (meteringAreaSupported) {
parameters.setMeteringAreas(Lists.newArrayList(new Camera.Area(meteringRect, 1000)));
}
camera.setParameters(parameters);
camera.autoFocus(this);
}}
..... update
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
...
Parameters p = camera.getParameters();
if (p.getMaxNumMeteringAreas() > 0) {
this.meteringAreaSupported = true;
}
...
}
/**
* Convert touch position x:y to {#link Camera.Area} position -1000:-1000 to 1000:1000.
*/
private Rect calculateTapArea(float x, float y, float coefficient) {
int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue();
int left = clamp((int) x - areaSize / 2, 0, getSurfaceView().getWidth() - areaSize);
int top = clamp((int) y - areaSize / 2, 0, getSurfaceView().getHeight() - areaSize);
RectF rectF = new RectF(left, top, left + areaSize, top + areaSize);
matrix.mapRect(rectF);
return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom));
}
private int clamp(int x, int min, int max) {
if (x > max) {
return max;
}
if (x < min) {
return min;
}
return x;
}