More efficient way to draw lines on canvas? - android

I'm drawing a grid over a bitmap. The user can change the size, aspect ratio, or drag the grid around. Doing so with drawLines causes fairly bad stuttering when these operations occur. Is there a better approach?
private void drawGrid()
{
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
gridOverlayView.invalidate();
int count = 0;
for(int i = 0; i < numColumns +1 ; i++)
{
points[count] = originX + (cellWidth * i);
points[count+1] = originY;
points[count+2] = originX + (cellWidth * i);
points[count+3] = originY + (gridHeight);
count += 4;
}
for(int i = 0; i < numRows +1; i++)
{
points[count] = originX;
points[count+1] = originY + (cellHeight * i);
points[count+2] = originX + (gridWidth);
points[count+3] = originY + (cellHeight * i);
count += 4;
}
canvas.drawLines(points, 0, points.length, paint);
gridOverlayView.setImageBitmap(gridOverlayBitmap);
}
this is being called from:
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
originX += e2.getX() - e1.getX();
if(originX < 0f)
{
originX = 0f;
}
if(originX + gridWidth > imageWidth)
{
originX = imageWidth - gridWidth;
}
originY += e2.getY() - e1.getY();
if(originY < 0f)
{
originY = 0f;
}
if(originY + gridHeight > imageHeight)
{
originY = imageHeight - gridHeight;
}
drawGrid();
return true;
}

Since there's no source code you're calling that function, I cannot find the exact reason of stuttering. I guess you want to change the grids in 'real-time' with user interaction.
I think you should avoid redrawing the entire grid while user is dragging.
Instead, you can translate, scale or rotate(if you need) the previous Bitmap as you wish.
Of course the result image will sometimes not in a good quality, but you can recreate the whole grid bitmap when the user stops interacting.
By this way you can achieve 'real-time-like' grid creating effect.

I solved the problem by doing a couple things: Optimizing the layout following the dev guide. Then I simply scaled down the bitmap I was using to draw the grid, then scaled it back up when I wanted to save it onto the original image. That got everything moving nice and smoothly... (actually too fast)

Related

hitTest on children of a View - how to translate the MotionEvent coordinates

Being Android programming newbie I am trying to find out, if the user has touched a child (the yellow tile at the left in the picture below) at a custom View (source code: MyView.java):
For hitTesting I have written the following method:
private Drawable hitTest(int x, int y) {
for (Drawable tile: mTiles) {
Rect rect = tile.getBounds();
if (rect.contains(x, y))
return tile;
}
return null;
}
However I don't know, how to translate the MotionEvent coordinates, before passing them to the above method. I have tried many possible combinations, involving the mScale and mOffset properties of my custom View (I do not want to use View.scrollTo() and View.setScaleX() methods - but handle the offset and scale myself).
When I start randomly touching the screen, then I sometimes hit the tiles - so I see that my hitTest method is okay, but I just need to figure out the proper translation.
Do I miss something here, should I maybe add some translation between dp and real pixels?
public boolean onTouchEvent(MotionEvent e) {
Log.d("onToucheEvent", "mScale=" + mScale +
", mOffsetX=" + mOffsetX +
", mOffsetY=" + mOffsetY +
", e.getX()=" + e.getX() +
", e.getY()=" + e.getY() +
", e.getRawX()=" + e.getRawX() +
", e.getRawY()=" + e.getRawY()
);
int x = (int) (e.getX() / mScale - mOffsetX);
int y = (int) (e.getY() / mScale- mOffsetY);
Drawable tile = hitTest(x, y);
Log.d("onToucheEvent", "tile=" + tile);
boolean retVal = mScaleDetector.onTouchEvent(e);
retVal = mGestureDetector.onTouchEvent(e) || retVal;
return retVal || super.onTouchEvent(e);
}
UPDATE: Following pskink's advice (thanks) I am trying to use Matrix and have change my custom MyView.java to:
protected void onDraw(Canvas canvas) {
mMatrix.reset();
mMatrix.setTranslate(mOffsetX, mOffsetY);
mMatrix.postScale(mScale, mScale);
canvas.setMatrix(mMatrix);
mGameBoard.draw(canvas);
for (Drawable tile: mTiles) {
tile.draw(canvas);
}
}
public boolean onTouchEvent(MotionEvent e) {
float[] point = new float[] {e.getX(), e.getY()};
Matrix inverse = new Matrix();
mMatrix.invert(inverse);
inverse.mapPoints(point);
float density = getResources().getDisplayMetrics().density;
point[0] /= density;
point[1] /= density;
Drawable tile = hitTest((int) point[0], (int) point[1]);
Log.d("onToucheEvent", "tile=" + tile);
}
But unfortunately my hitTest() does not find any touched tiles.

Drawing and scrolling paths performance issues

Hi I'm doing and endless sidescroller game where the terrain looks like a tunnel which is infinite. I managed to randomly generate the tunnel using this code:
private void createPaths() {
if(startingPath) {
pathBottom.setLastPoint(0, canvasHeight);
pathTop.setLastPoint(0, 0);
slopeWidth = 0;
slopeHeight = generateRandomNumber(canvasHeight / 4, canvasHeight / 2);
lastX = 0;
lastY = canvasHeight - slopeHeight;
newX = lastX;
newY = lastY;
startingPath = false;
} else {
lastX = canvasWidth;
lastY = newY;
newX = lastX;
newY = canvasHeight - slopeHeight;
}
pathBottom.lineTo(lastX, lastY);
pathTop.lineTo(lastX, lastY - OFFSET);
do {
lastX = newX;
lastY = newY;
slopeWidth = generateRandomNumber(canvasWidth / 8, canvasWidth / 2);
newX += slopeWidth;
if(i % 2 == 0) {
slopeHeight = generateRandomNumber(canvasHeight / 12, canvasHeight / 6);
newY = canvasHeight - slopeHeight;
} else {
slopeHeight = generateRandomNumber(canvasHeight / 4, canvasHeight / 2);
newY = canvasHeight - slopeHeight;
}
pathBottom.cubicTo(
interpolateLinear(lastX, newX, 0.333f),
lastY,
interpolateLinear(lastX, newX, 0.666f),
newY,
newX,
newY);
pathTop.cubicTo(
interpolateLinear(lastX, newX, 0.333f),
lastY - OFFSET,
interpolateLinear(lastX, newX, 0.666f),
newY - OFFSET,
newX,
newY - OFFSET);
i++;
} while (newX < canvasWidth * 2);
pathBottom.lineTo(newX, canvasHeight);
pathTop.lineTo(newX, 0);
}
and scroll it using:
public void updateTerrain() {
moveX -= speed;
int pos = newX - canvasWidth + moveX;
if(pos > 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-speed, 0);
pathBottom.transform(matrix);
pathTop.transform(matrix);
} else {
createPaths();
moveX = 0;
}
}
The problem is: the longer the path is the game becomes more "choppy". I think I should reduce the points that are being draw in the path after some time but to be honest I have no idea how to do it and still let the terrain scroll and generate. I would be grateful if you could help me. Thanks.
This looks like a small piece of a larger piece of logic. The performance issue may lie in some other code not shown here.
The general advice ( according to people like Romain Guy and Chet Haase ) is to avoid object allocation ( aka new ) during onDraw. Any "new" has the potential to trigger GC.
I would re-use the same instance of Matrix and just update it.
Also, as fadden mentioned "fixed-size sliding window structure" ( similar to circular buffer or ring buffer ) the comment above, you should ensure that your Path objects are a fixed size.
Pick a fixed number of points for the path ( let's say 200 )
Keep track of those points in an array and keep a "startindex" variable to track of the "logical" start of the array. When you need to add a new point, increment the index modulo the array size, overwrite the last point ( index - 1 modulo array size ). When you get to the end of the array you have to wrap ( start back at the beginning and go to startindex - 1 ).
Use path.incReserve to preallocate the memory when the view is created.
Use path.rewind to reset the path
Then re-use the same Path instance, to re-add all your points from your array of points ( starting at the "startIndex" )

Andengine - Shooting bullets in front of rotating gun

Hello I searched in the forum,but coudn't find a helpful answer.
I'm making a game with AndEngine and I'm stuck for 3 days on shooting from rotating sprite.
That is my code and how I rotate the gun.I tried here to shoot a bullet ,but it shoots from a wrong starting point I would want to shoot a bullet from the end of the gun.
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if(pSceneTouchEvent.isActionMove()){
final float dX = pSceneTouchEvent.getX() - machine.getX();
final float dY = pSceneTouchEvent.getY() - machine.getY();
float angle = (float) Math.atan2(dX,dY);
float rotation = MathUtils.radToDeg(angle) + 1;
machine.setRotation(rotation - 90);
Log.d("BUG",machine.getRotation() + "");
if(machine.getRotation() >= 84 ){
machine.setRotation(84);
}
if(machine.getRotation() <= -54 ){
machine.setRotation(-54);
}
final int incrementXValue = 15;
long sElapsed = System.currentTimeMillis() - lastFire;
if(bulletsAmout > 0 && sElapsed > cooldownBetweenShoot * cdModd){
e = new Entity(0,machine.getY());
e.setRotation(getRotation());
SceneManager.getInstance().getCurrentScene().attachChild(e);
float x2 = (float) (machine.getSceneCenterCoordinates()[0] + machine.getWidth() /2 * Math.cos(machine.getRotation()));
float y2 = (float) (machine.getSceneCenterCoordinates()[1] + machine.getWidth() /2 * Math.sin(machine.getRotation()));
float realX = (float) (Math.toRadians(x2) + machine.getWidth());
realY = (float) Math.toRadians(y2);
bullets = new Sprite(realX,realY, resourcesManager.bulletRegion.deepCopy(), vbom){
protected void onManagedUpdate(float pSecondsElapsed) {
float currentX = this.getX();
this.setX(currentX + incrementXValue);
super.onManagedUpdate(pSecondsElapsed);
}
};
bullets.setScale(0.06f);
e.attachChild(bullets);
projectilesToBeAdded.add(bullets);
bulletsAmout--;
lastFire = System.currentTimeMillis();
setBulletsText(bulletsAmout);
resourcesManager.pistolSound.play();
}
return true;
}
return false;
}
Assuming you are using GLES2-AnchorCenter:
You can position the bullet by setting it to the position of the end of the gun that you can get by calling gun.convertLocalToSceneCoordinates(gunMuzzleX, gunMuzzleY).
Then set the bullets rotation to the rotation of the gun.
apply velocity to the bullet. Calculate the speed-vector as follows FloatMath.sin(rotationOfBulletInRadians) * speed and FloatMath.cos(rotationOfBulletInRadians) * speed.
Be aware that you have to pass the rotation in radians to the sin and cos function NOT in degrees!
So I found how to fix that.
The problem is in this line of code :
e = new Entity(0,machine.getY());
Should be :
e = new Entity(machine.getX() - (machine.getHeight() / 2),machine.getY())

Android:Scrollbar for running graph

I've a custom view which draws a running graph - some magnitude versus time. Now I want to implement a custom scroll bar for this so that I can view past data which are offscreen. The data is available to me. I just need the %offset selection by the user.
Any help/suggestions on implementation would be v helpful.
Code Snippet from my Custom view's onDraw method
public void onDraw(Canvas canvas) {
int totalpts = data.size();
scale = getWidth() / (float) maxpoints;
List<Data> display = new ArrayList<Data>();
int initial = 1;
if (totalpts > maxpoints) {
initial = totalpts - maxpoints;
display = data.subList(initial, data.size() - 1);
} else {
display = data;
}
int size = display.size();
Data start = null;
float x1 = 0, x2 = 0, x = 0;
if (size > 1) {
x1 = getWidth();
start = display.get(display.size() - 1);
for (int i = display.size() - 1; i >= 0; i--) {
Data stop = display.get(i);
x = x1;
x1 -= (stop.x * scale / 1000);
canvas.drawLine(x, start.Y, x1, stop.Y, paint1);
start = stop;
}
}
}
Try putting your custom control inside a HorizonatalScrollView (assuming you want it to scroll horizontally), use ScrollView otherwise), setting the width of your control to "WRAP_CONTENT", and the HoizontalScrollView to "FILL_PARENT". Without seeing the code for your custom view, it's difficult to know whether you might need to do some tinkering with the width calculation to get this working.

Android image with clickable areas

I need an advice how to achieve the following functionality under Android:
I need an image that represents something like a graph (from discrete math), with vertices and edges, where I can click every vertice or edge and fire a different action.
Please advise me how to achieve this (maybe with imagebuttons) or another approach to represent this functionality.
I was bored, so I coded up this crude example...
It assumes straight edges between points.
public class App extends Activity
{
PlotView plot;
#Override
public void onCreate(Bundle sis)
{
super.onCreate(sis);
plot = new PlotView(this);
setContentView(plot);
}
public class PlotView extends View
{
Paint paint1 = new Paint();
Paint paint2 = new Paint();
Point[] points = new Point[10];
public PlotView(Context context)
{
super(context);
paint1.setColor(Color.RED);
paint2.setColor(Color.BLUE);
for (int i = 0; i < points.length; i++)
{
points[i] = new Point();
points[i].x = (float) (Math.random() * 320);
points[i].y = (float) (Math.random() * 480);
}
Arrays.sort(points);
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE);
for (int i = 0; i < points.length; i++)
{
if (i < points.length - 1)
{
canvas.drawLine(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, paint2);
}
canvas.drawCircle(points[i].x, points[i].y, 5, paint1);
}
super.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
float x = event.getX();
float y = event.getY();
int hitPoint = -1;
int closestLeft = -1;
int closestRight = -1;
for (int i = 0; i < points.length; i++)
{
float dx = x - points[i].x;
float dy = y - points[i].y;
if(i < points.length - 1)
{
if(points[i].x < x && x < points[i + 1].x)
{
closestLeft = i;
closestRight = i + 1;
}
}
if (Math.abs(dx) <= 16.0f && Math.abs(dy) <= 16.0f)
{
hitPoint = i;
break;
}
}
if (hitPoint != -1)
{
Toast.makeText(getContext(), "Hit Point: " + hitPoint, Toast.LENGTH_SHORT).show();
}
else
if(closestLeft != -1 && closestRight != -1)
{
float dx = points[closestLeft].x - points[closestRight].x;
float dy = points[closestLeft].y - points[closestRight].y;
final float u = ((x - points[closestLeft].x) * dx + (y - points[closestLeft].y) * dy) / (dx * dx + dy * dy);
float px = points[closestLeft].x + u * dx;
float py = points[closestLeft].y + u * dy;
if (Math.abs(x - px) <= 16.0f && Math.abs(y - py) <= 16.0f)
{
Toast.makeText(getContext(), "Hit Line Between: " + closestLeft + " & " + closestRight, Toast.LENGTH_SHORT).show();
}
}
}
}
return super.onTouchEvent(event);
}
public class Point implements Comparable<Point>
{
float x;
float y;
#Override
public int compareTo(Point other)
{
if (x < other.x) return -1;
if (x > other.x) return 1;
return 0;
}
}
}
}
I can imagine how to do this with SurfaceView:
create a Vertex class, which among other things, has an x,y coordinate representing where to draw the vertex. If your vertex was a png image of a circle, then the top-left x,y coordinates of the image are stored in the Vertex class.
Have all your verticies in a List, and iterate through and draw each vertex.
the edges are more complicated since they might criss-cross or curve around.
assuming they are straight lines, then you can have a Edge class that contains the starting x,y and ending x,y coordinates.
you can iterate through a List of Edges and draw the lines accordingly
In order to detect when a user clicks on them, you should override the onTouch method and check the event.rawX() and event.rawY() values to see if they match up to a Vertex or Edge class.
for a Vertex class, you can check if x <= event.rawX <= x + image_width and y <= event.rawY <= y + image_height
for an Edge, you can check if the event.rawX, event.rawY coordinates are found in the line formed by the two sets of coordinates you stored in the Edge class.
I've used a similar method to draw a set of nodes in a game. I'm not so sure how to do the edges though - the method I outline would only work if they were straight and do not criss-cross.
I am sure there is a better way to do this using openGL, but I have not used openGL before.
Hopefully you can get some ideas out of this.
I think you might be best off with a SurfaceView:
http://developer.android.com/reference/android/view/SurfaceView.html
And handling the onTouchEvent() as a whole for the surface, and mapping that to underlying entities in the image. If you're calculating the drawing the graph as you go should be easy to also create a map of tapable areas and grabbing the X and Y of the touch event to figure out if it corresponds to an element in the image.
If you literally have an image, as an already processed PNG for example, you would need some way to also carry in the touch event areas. Depends where that image comes in from.
According to android help, "drawing to a View, is your best choice when you want to draw simple graphics that do not need to change dynamically and are not part of a performance-intensive game." This is the right way to go when making a snake or a chess game, for instance. So I don't see a point in suggesting using a SurfaceView for this, it will just overcomplicate things.
For clickable areas you override public boolean onTouchEvent(MotionEvent event) where you manage x and y coordinates of the click for identifying the clicked area.

Categories

Resources