I'm parsing a Json file which contains a lot of multipolygons to Realm. I've set it up the following way:
RealmMultiPolygon class:
public int dangerLevel;
public int timeOfDay;
public RealmList<RealmPolygon> realmPolygons
RealmPolygon class:
public RealmList<RealmPolygonCoordinate> coordinates;
RealmPolygonCoordinate
public double latitude;
public double longitude;
As far as items concern:
RealmMultiPolygon contains 8 items
RealmPolygon contains 22260 items
RealmPolygonCoordinate contains 352241 items
I'm trying to figure out if a RealmPolygon is in a direction where i'm looking at and near my current location. I'm taking 31 locationsamples with bearings like this:
private void calculateUserDirectionLocations() {
Log.d(Constants.DEBUG, "update the locationbar");
if(compassData < 0) {
return;
}
int step = 50;
int bearingstep = 1;
double bearing = Math.round(compassData / bearingstep) * bearingstep;
if(userLocation != null) {
if(Math.abs(bearing - oldBearing) > 1) {
locationSamples.clear();
Log.d(Constants.DEBUG, "START location samples");
for(int i = 0; i <= Constants.MAX_DISTANCE; i+=step) {
if (i % step == 0) {
locationSamples.add(locationWithBearing((double) i));
}
}
Log.d(Constants.DEBUG, "END location samples");
updateColors();
oldBearing = bearing;
}
}
}
Where compassData is gained from the RotationVectorSensor (value between 0 and 360 degrees) and the MAX_Distance is 1.5Km.
Now if I want to know if one of the 31 locationSample points is near a polygon I have to loop through all the polygonCoordinates which I currently have in my Realm database 31 times. which means:
31 * 352241 = 10919471
This is incredibly slow but I can't really seem to find a better solution for this. Anyone have an idea how to do this better / faster?
Update 1
At the moment i'm looping through the coordinates like this:
for(RealmMultiPolygon rmp : area.avalanche.multiPolygon) {
if(rmp.timeOfDay == 2) {
for (RealmPolygon polygon : rmp.realmPolygons) {
for(LatLng sample : locationSamples) {
tempPoint = new LatLng(polygon.coordinates.first().latitude, polygon.coordinates.first().longitude);
if(SphericalUtil.computeDistanceBetween(tempPoint, sample) <= 500) {
coords.add(tempPoint);
} else {
break;
}
}
}
}
}
I finally fixed this by coming up with a totally different solution. I'm now creating an image based on the width and height of the area the polygons are in as a background task. Then I calculate the pixel position based on the locationsample I take and use the getPixel method to get the pixels on that position. This is way faster than looping through each polygon.
First this is not a duplicate of other "smooth line" questions because I also need to be able to delete parts of my line at will and as such I need a special way to store my line.
I need to make a line follow the user's finger. However I also need to be able to delete the end of this line at will.
Basically I need the behavior of this line to look like the blue line that follows the user's mouse in this game:
http://hakim.se/experiments/html5/coil/
To do this I have some code in my onTouch method that adds a point to an array each time the user's finger moves.
#Override
public boolean onTouch(View v, MotionEvent event) {
//This for loop is supposed to add all points that were in between this
//motion event and the previous motion event to the "linePoints" array.
for(int i = 0; i < event.getHistorySize(); i++) {
linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i));
arrayIndex++;
}
//This adds the current location of the user's finger to "linePoints"
// array
linePoints[arrayIndex] = new Point((int) event.getX(), (int) event.getY());
arrayIndex++;
//This switch statement makes it so that if the user lifts their finger
// off the screen the line will get deleted.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
screenPressed = true;
setEventTime(); //Ignore setEventTime();
break;
case MotionEvent.ACTION_UP:
screenPressed = false;
linePoints = new Point[10000];
arrayIndex = 0;
break;
}
return true;
}
Then in the onDraw() method the game draws every point on the line:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
//This code loops through all of linePoints and then draws every point
// in linePoints to create a line on screen.
for(Point p : linePoints) {
if(p == null) {
break;
}
canvas.drawRect(p.x, p.y, p.x+ 2, p.y + 2, black);
invalidate();
// I have not added in the deletion behavior yet, because my current
// method cannot create a smooth line.
}
The reason I chose to draw points to make a line instead of using Android's
Path() class is because I wanted to delete parts of the line at will (by removing points from the array "linePoints").
The problem is if I move my finger too fast then the points spread out and it stops looking like a line.
How can I make sure the line stays smooth but also is stored in such a way I can delete parts of it?
EDIT: Someone asked for more specifics on how the line will be detailed so I will provide.
I want to start deleting the line if the user has been drawing the line for more than "X" seconds. The way I want to delete the line is:
The end of the line will start disappearing until (meanwhile the user is still drawing it) until the line is completely deleted or the user has lifted their finger off the screen.
EDIT 2: I also need to know if the line has intersected itself or created some sort of closed off shape (hence why I chose the point storage system, I thought that if 2 points in the array had the same coordinates then I would know if the line had intersected itself). I currently have no idea how to implement this (because the points are not continuous) but I will provide further edits if I figure something out.
EDIT 3: I have figured out a solution to determine if the line intersects itself (even if the points are spaced out sporadically)! However I still not have solved the problem of creating a smooth line with no gaps.
Solution:
Each time the game adds a new point to the array it will compare it to the previous point it added to the array and model a line segment "A". It will then compare line segment "A" to all the previous line segments made out of 2 points in the array and determine if the compared segments cross. If they do then I know that there is an intersection in the line.
EDIT 4: This is the complete up to date code that I am currently using.
Inside this code I (try) to provide detailed comments and a summary at the top that explains my objectives and what I have done so far.
To preface this large piece of code, my current problem is being able to delete the line at a consistent pace (e.g 10 millimeters per second) if the user has been drawing their line for more than a certain amount of time.
package com.vroy.trapper;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class GameView extends View implements View.OnTouchListener {
// I am basically trying to replicate the game: http://hakim.se/experiments/html5/coil/
// without any of the lighting effects and my version will have slightly
// different behavior.
// Right now all I am concerned with is allowing the line to be deleted at a constant pace
// if the user has been drawing a line for more than "X" seconds.
/*
OVERVIEW:
array of points "linePoints" stores all locations of user touching screen
that are captured by system.
Each time a new point is added to "linePoints" I draw a path from the previous point
to the new point. (Sidenote: While this does make the line look very smooth it can still look odd sometimes)
The game also checks for intersections in the line to see if the line has made a
polygon. I do this because this is important for a feature that will be implemented.
The system then draws the path on screen.
The system also checks if the user has lifted their finger off the screen,
if the user has then the system deletes the current line on screen and resets all variables.
TO BE IMPLEMENTED:
If the line has formed a polygon then the game will check if that polygon contains certain
objects that will randomly spawn onscreen.
PROBLEMS:
1. Currently I want the line to start deleting itself from the back if the user
has been drawing the line for more then "X" seconds. However I am not sure how to do this.
*/
// General variables.
private int screenWidth;
private int screenHeight;
public static boolean screenPressed; //Might not need.
// public static float contactLocX;
// public static float contactLocY;
//Time variables.
private static long startTime; //This variable is used in conjunction with the
//elapsedTime() method to determine if the user
// has been drawing a line for more then "X" seconds.
//Game variables.
private static int orbWidth; //Not used currently. This will be the width of the randomly spawned tokens.
private Point[] linePoints; //The array that holds all captured points.
private int arrayIndex;
private Path linePath; //The path that the canvas draws.
private boolean firstPoint; //If firstPoint is true then that means is 1st point in current line.
//I need this for the path.MoveTo() method.
//Debug values. (Not used currently)
private int debug;
private String strdebug;
//Paints
Paint black = new Paint();
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
black.setARGB(255, 0, 0, 0); //Paint used to draw line.
black.setStyle(Paint.Style.STROKE);
black.setStrokeWidth(3);
linePoints = new Point[10000];
GameView gameView = (GameView) findViewById(R.id.GameScreen); //Setting up onTouch listener.
gameView.setOnTouchListener(this);
arrayIndex = 0;
linePath = new Path(); //Setting up initial path.
firstPoint = true;
}
//Currently OnSizeChanged is not needed, I only keep it for the future when I implement
// the random object spawning system.
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenHeight = getHeight();
screenWidth = getWidth();
orbWidth = screenHeight / 20;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(linePath, black);
//Currently "1000000000" is a placeholder value (in nano-seconds)
if(elapsedTime() > 1000000000 ) {
//Code that evenly deletes the line starting from the back
//(this is where I most need your assistance).
}
invalidate(); //I don't know if this is the best way to refresh the screen
}
#Override
public boolean onTouch(View v, MotionEvent event) {
//Sets up starting point of path
if(firstPoint) {
firstPoint = false;
linePath.moveTo(event.getX(),event.getY());
linePoints.add(new TimeStampedPoint((int)event.getX(), (int)event.getY(),event.getEventTime()));
}
//Adds points to path & linePoints that were missed.
for(int i = 0; i < event.getHistorySize(); i++) {
linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i));
linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y);
if(arrayIndex >= 1) {
checkForIntersections(linePoints[arrayIndex - 1], linePoints[arrayIndex]);
}
arrayIndex++;
}
//Adds current point to path & linePath();
linePoints[arrayIndex] = new Point((int) event.getX(), (int) event.getY());
if (arrayIndex >= 1) {
checkForIntersections(linePoints[arrayIndex - 1] ,linePoints[arrayIndex]);
}
linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y);
arrayIndex++;
//This switch statements creates initial actions for when the finger is pressed/lifted.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
screenPressed = true;
setEventTime(); //This starts the timer that will eventually reach "X" seconds.
break;
case MotionEvent.ACTION_UP: //The primary purpose of this "switch" is to delete the old line
// & reset variables in preparation for new line
screenPressed = false;
linePoints = new Point[10000]; //Possibly filling heap with empty arrays.
linePath = new Path();
arrayIndex = 0;
firstPoint = true;
break;
}
return true;
}
private void checkForIntersections(Point p, Point p2) {
for(int i = arrayIndex - 3; i > 0; i--) {
if(intersect(p,p2,linePoints[i],linePoints[i-1])) {
//RETURN POINTS IN THE POLYGON THAT WILL BE USED TO DETERMINE IF "TOKENS"
// ARE IN POLYGON.
}
}
}
private void setEventTime() {
startTime = System.nanoTime();
}
//Checks current time since setEventTime
private long elapsedTime() {
return System.nanoTime() - startTime;
}
// Things used to determine intersections.
//Used to determine orientation of <something>
private static int orientation(Point p, Point q, Point r) {
double val = (q.y - p.y) * (r.x - q.x)
- (q.x - p.x) * (r.y - q.y);
if (val == 0.0)
return 0; // colinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}
//Determines intersection of 2 lines (P1,Q1) & (P2,Q2).
private static boolean intersect(Point p1, Point q1, Point p2, Point q2) {
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
if (o1 != o2 && o3 != o4)
return true;
return false;
}
//Will shorten checking process by determining if 2 lines do/don't have the same bounding box.
//Not yet implemented.
private static boolean boundBoxCheck(Point p1, Point q1, Point p2, Point q2) {
return true; //Placeholder code
}
}
EDIT 5:
I did my implementation of stKent's/Titan's code and my code crashes due to a index out of bounds error.
I will be trying to find the problem and fix it but until I do I will post my code here incase someone else wants to take a hand at fixing it.
package com.vroy.trapper;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;
public class GameView extends View implements View.OnTouchListener {
// I am basically trying to replicate the game: http://hakim.se/experiments/html5/coil/
// without any of the lighting effects and my version will have slightly
// different behavior.
// Right now all I am concerned with is allowing the line to be deleted at a constant pace
// if the user has been drawing a line for more than "X" seconds.
/*
OVERVIEW:
array of points "linePoints" stores all locations of user touching screen
that are captured by system.
Each time a new point is added to "linePoints" I draw a path from the previous point
to the new point. (Sidenote: While this does make the line look very smooth it can still look odd sometimes)
The game also checks for intersections in the line to see if the line has made a
polygon. I do this because this is important for a feature that will be implemented.
The system then draws the path on screen.
The system also checks if the user has lifted their finger off the screen,
if the user has then the system deletes the current line on screen and resets all variables.
TO BE IMPLEMENTED:
If the line has formed a polygon then the game will check if that polygon contains certain
objects that will randomly spawn onscreen.
PROBLEMS:
1. Currently I want the line to start deleting itself from the back if the user
has been drawing the line for more then "X" seconds. However I am not sure how to do this.
*/
// General variables.
private int screenWidth;
private int screenHeight;
public static boolean screenPressed; //Might not need.
// public static float contactLocX;
// public static float contactLocY;
//Time variables.
private static long startTime; //This variable is used in conjunction with the
//elapsedTime() method to determine if the user
// has been drawing a line for more then "X" seconds.
//Game variables.
private static int orbWidth; //Not used currently. This will be the width of the randomly spawned tokens.
private List<TimeStampedPoint> linePoints; //The array that holds all captured points.
private int arrayIndex;
private Path linePath; //The path that the canvas draws.
private List<TimeStampedPoint> validPoints;
private boolean firstPoint; //If firstPoint is true then that means is 1st point in current line.
//I need this for the path.MoveTo() method.
//Debug values. (Not used currently)
private int debugint;
private String strdebug;
//Paints
Paint black = new Paint();
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
black.setARGB(255, 0, 0, 0); //Paint used to draw line.
black.setStyle(Paint.Style.STROKE);
black.setStrokeWidth(3);
linePoints = new ArrayList<>();
validPoints = new ArrayList<>();
GameView gameView = (GameView) findViewById(R.id.GameScreen); //Setting up onTouch listener.
gameView.setOnTouchListener(this);
arrayIndex = 0;
linePath = new Path(); //Setting up initial path.
validPoints = new ArrayList<>();
firstPoint = true;
}
//Currently OnSizeChanged is not needed, I only keep it for the future when I implement
// the random object spawning system.
#Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
screenHeight = getHeight();
screenWidth = getWidth();
orbWidth = screenHeight / 20;
}
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
linePath.rewind();
validPoints = removeExpiredPoints();
updatePathUsingPoints(validPoints);
canvas.drawPath(linePath, black);
linePoints = validPoints;
invalidate(); //I don't know if this is the best way to refresh the screen
}
#Override
public boolean onTouch(View v, MotionEvent event) {
debugint = arrayIndex;
strdebug = Integer.toString(debugint);
Log.i("ARRAY INDEX: ",strdebug);
debugint = linePoints.size();
strdebug = Integer.toString(debugint);
Log.i("LIST SIZE: ",strdebug);
//Sets up starting point of path
if(firstPoint) {
firstPoint = false;
linePath.moveTo(event.getX(),event.getY());
linePoints.add(new TimeStampedPoint((int)event.getX(),(int)event.getY(),event.getEventTime()));
}
//Adds points to path & linePoints that were missed.
for(int i = 0; i < event.getHistorySize(); i++) {
linePoints.add(new TimeStampedPoint((int) event.getHistoricalX(i), (int) event.getHistoricalY(i), event.getHistoricalEventTime(i)));
linePath.lineTo(linePoints.get(arrayIndex).x,linePoints.get(arrayIndex).y);
if(arrayIndex >= 1) {
checkForIntersections(linePoints.get(arrayIndex), linePoints.get(arrayIndex));
}
arrayIndex++;
}
//Adds current point to path & linePath();
debugint = linePoints.size();
strdebug = Integer.toString(debugint);
Log.i("Before" , strdebug);
linePoints.add(new TimeStampedPoint((int) event.getX(), (int) event.getY(),event.getEventTime()));
debugint = linePoints.size();
strdebug = Integer.toString(debugint);
Log.i("After:", strdebug);
if (arrayIndex >= 1) {
checkForIntersections(linePoints.get(arrayIndex - 1) ,linePoints.get(arrayIndex));
}
linePath.lineTo(linePoints.get(arrayIndex).x,linePoints.get(arrayIndex).y);
arrayIndex++;
//This switch statements creates initial actions for when the finger is pressed/lifted.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
screenPressed = true;
setEventTime(); //This starts the timer that will eventually reach "X" seconds.
break;
case MotionEvent.ACTION_UP: //The primary purpose of this "switch" is to delete the old line
// & reset variables in preparation for new line
screenPressed = false;
linePoints.clear();
linePath = new Path();
arrayIndex = 0;
firstPoint = true;
break;
}
return true;
}
private void checkForIntersections(TimeStampedPoint p, TimeStampedPoint p2) {
for(int i = arrayIndex - 3; i > 0; i--) {
if(intersect(p,p2,linePoints.get(i),linePoints.get(i-1))) {
//RETURN POINTS IN THE POLYGON THAT WILL BE USED TO DETERMINE IF "TOKENS"
// ARE IN POLYGON.
}
}
}
private void setEventTime() {
startTime = System.nanoTime();
}
//Checks current time since setEventTime
private long elapsedTime() {
return System.nanoTime() - startTime;
}
// Things used to determine intersections.
//Used to determine orientation of <something>
private static int orientation(Point p, Point q, Point r) {
double val = (q.y - p.y) * (r.x - q.x)
- (q.x - p.x) * (r.y - q.y);
if (val == 0.0)
return 0; // colinear
return (val > 0) ? 1 : 2; // clock or counterclock wise
}
//Determines intersection of 2 lines (P1,Q1) & (P2,Q2).
private static boolean intersect(TimeStampedPoint p1, TimeStampedPoint q1, TimeStampedPoint p2, TimeStampedPoint q2) {
int o1 = orientation(p1, q1, p2);
int o2 = orientation(p1, q1, q2);
int o3 = orientation(p2, q2, p1);
int o4 = orientation(p2, q2, q1);
if (o1 != o2 && o3 != o4)
return true;
return false;
}
//Will shorten checking process by determining if 2 lines do/don't have the same bounding box.
//Not yet implemented.
private static boolean boundBoxCheck(Point p1, Point q1, Point p2, Point q2) {
return true; //Placeholder code
}
//Point class that also stores time of creation
#SuppressLint("ParcelCreator")
private static class TimeStampedPoint extends Point {
private final long timeStamp;
private TimeStampedPoint(final int x, final int y, final long timeStamp) {
super(x, y);
this.timeStamp = timeStamp;
}
}
private List<TimeStampedPoint> removeExpiredPoints() {
final List<TimeStampedPoint> result = new ArrayList<>();
for (final TimeStampedPoint point: linePoints) {
if (System.currentTimeMillis() - point.timeStamp <= 10000) {
// We only include points in the result if they are not expired.
result.add(point);
}
}
return result;
}
private void updatePathUsingPoints(final List<TimeStampedPoint> validPoints) {
if (validPoints.size() < 2) {
return; // Return the empty path here; nothing to draw.
}
linePath.moveTo(validPoints.get(0).x,validPoints.get(0).y);
for (int i = 1; i < validPoints.size(); i++) {
final Point targetPoint = validPoints.get(i);
linePath.lineTo(targetPoint.x, targetPoint.y);
}
}
}
There is also something else that is very very important that I must note.
I believe it is my fault for not noting this until edit 4 but while I want the line to be deleted from the end I would also like it to be deleted evenly, I think the current code provided by stkent and Titan deletes the points in the line at a consistent pace however that does not actually mean the line itself will be deleted at a consistent pace (Because the points are spread out unevenly).
Much thanks to everyone for sticking with me through the numerous edits until now I hope a solution can be found that also allows the line to be deleted at a consistent pace.
I suggest using an ArrayList instead of a static array, as you may not always need to store 10000 Points. I also suggest making a subclass of Point, and have it store a timestamp upon instantiation. Consider:
public class TimedPoint extends Point {
private static final int KEEP_ALIVE_TIME_MS = 200; //tweak this value to your needs
private final long time;
public TimedPoint(int x, int y) {
super(x, y);
time = System.currentTimeMillis();
}
public TimedPoint(int x, int y, long time) {
super(x, y);
this.time = time;
}
public boolean hasExpired(long time) {
return (time-this.time>KEEP_ALIVE_TIME_MS);
}
}
public class GameView extends View ... {
ArrayList<TimedPoint> linePoints = new ArrayList<>(); //Lists can grow and shrink to demand
//this implementation is backed by an array.
...
public void addPoint(int x, int y) {
linePoints.add(new TimedPoint(x, y);
}
public void removeOldPoints() {
long time = System.currentTimeMillis();
Iterator<TimedPoint> i = linePoints.iterator();
while(i.hasNext()) {
TimedPoint point = i.next();
if(point.hasExpired(time))
i.remove();
}
}
}
removeOldPoints() will remove any points from linePoints whose time difference is greater than the threshold defined in TimedPoint. This assumes you can call removeOldPoints() regularly. Hint hint, calling in onDraw() would be great.
If removeOldPoints() is called in onDraw before the line is drawn, you can guarantee that any point that's held in linePoints should be drawn. At that point it's as simple as iterating over the list and drawing the points as a line, and the "tail" will start to disappear as you draw.
You could also pass linePoints to TimedPoint and set a Timer upon construction, and schedule() each TimedPoint to remove itself at a certain time in the future. This does not assume you can call removeOldPoints() regularly. Consider:
public class TimedPoint extends Point {
private static final long KEEP_ALIVE_TIME_MS = 200; //tweak this value to your needs
//we don't need a timestamp, because every point disposes of itself. We do need a timer, though.
private final Timer lifetime = new Timer();
public TimedPoint(final List<TimedPoint> linePoints, int x, int y) {
super(x, y);
lifetime.schedule(new TimerTask() {
#Override
public void run() {
linePoints.remove(TimedPoint.this);
}
}, KEEP_ALIVE_TIME_MS);
}
}
public class GameView extends View ... {
List<TimedPoint> linePoints = Collections.synchronizedList(new ArrayList<>()); //Lists can grow and shrink to demand
//this implementation is backed by an array.
//and is thread safe for Timer
...
public void addPoint(int x, int y) {
linePoints.add(new TimedPoint(x, y);
}
//notice removeOldPoints() is gone! Each point now disposes of itself, no calls needed.
}
There are a couple of things you could tweak with this approach as well. For instance, points start to "die" as soon as they're "born". We can change that to only when added to the list, if that's more appropriate.
Also, there is probably room for optimization as well, as I think this may spawn a new Thread per point. This should actually improve performance(if removeOldPoints() was the bottleneck), up until your cpu is crippled by context switches. If you're feeling pedantic, or performance becomes an issue; you could use a threadpool and a queue.
Here is the documentation for ArrayList to help you get acclimated to the new class.
Happy coding :)
EDIT it seems you're still having trouble. Try this and let me know what it does for you.
public class GameView ... {
ArrayList<TimedPoint> linePoints = new ArrayList<>(); //Lists can grow and shrink to demand
//this implementation is backed by an array.
...
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
removeOldPoints();
Path path = linePointsToPath(); //I'm not sure if you need to store path, let's generate it.
if(path != null)
canvas.drawPath(path, black);
}
public void addPoint(int x, int y) {
linePoints.add(new TimedPoint(x, y);
invalidate();
}
public void removeOldPoints() {
int oldLen = linePoints.size();
long time = System.currentTimeMillis();
Iterator<TimedPoint> i = linePoints.iterator();
while(i.hasNext()) {
TimedPoint point = i.next();
if(point.hasExpired(time))
i.remove();
}
int newLen = linePoints.size();
if(newLen != oldLen) //if we removed items from list
invalidate();
}
//small tweaks to stKents method
private Path linePointsToPath() {
if(linePoints.size() < 2)
return null;
Path path = new Path();
Point p = points.get(0);
Path.moveTo(p.x, p.y);
for(Point point : linePoints) {
if(p != point)
path.lineTo(point.x, point.y); //skip first point, because of moveTo
}
return path;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
...
addPoint(...);
}
}
Based on your most recent code, here's what I'd try first. I'm making the following assumptions in this answer:
You will only be drawing one line/path at any given time (if not, you'll need to perform the procedure outlined below for each path, by iterating over some collection of Paths)
Create a wrapper around the Point class that adds a timestamp:
private static class TimeStampedPoint extends Point {
private final long timeStamp;
private TimeStampedPoint(final int x, final int y, final long timeStamp) {
super(x, y);
this.timeStamp = timeStamp;
}
}
Then update your point storage to the following:
List<TimeStampedPoint> linePoints = new ArrayList<>();
(You'll need to make a bunch of changes to the code as a result of this. In particular, you can use the List method add to append new points to the end of this list, rather than tracking the arrayIndex explicitly.)
In your onTouchEvent method, replace this block of code:
for(int i = 0; i < event.getHistorySize(); i++) {
linePoints[arrayIndex] = new Point((int) event.getHistoricalX(i), (int) event.getHistoricalY(i));
linePath.lineTo(linePoints[arrayIndex].x,linePoints[arrayIndex].y);
if(arrayIndex >= 1) {
checkForIntersections(linePoints[arrayIndex - 1], linePoints[arrayIndex]);
}
arrayIndex++;
}
with something that looks like this:
for(int i = 0; i < event.getHistorySize(); i++) {
TimeStampedPoint point = new TimeStampedPoint((int) event.getHistoricalX(i), (int) event.getHistoricalY(i), event.getHistoricalEventTime(i));
linePoints.add(point);
linePath.lineTo(point.x, point.y);
int numberOfPoints = linePoints.size();
if(numberOfPoints >= 2) {
checkForIntersections(linePoints.get(numberOfPoints - 2), linePoints.get(numberOfPoints - 1));
}
}
Make a similar adjustment everywhere else you add values to the linePoints array. Note also that we are no longer creating the Path incrementally during this loop. That's because we'll perform some sanitization (i.e., removing expired points) before constructing the Path. To do this, clear the linePath each time you prepare to draw (you might be able to move this method somewhere else if performance is poor; I'm just suggesting it happen in onDraw to make the suggested lifecycle clear). Your onDraw method would then look something like this:
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Reset the Path.
linePath.rewind();
validPoints = removeExpiredPoints();
updatePathUsingPoints(validPoints);
canvas.drawPath(linePath, black);
linePoints = validPoints;
invalidate(); //I don't know if this is the best way to refresh the screen
}
where validPoints is another field of type List<TimeStampedPoint>s. [In general, calling invalidate from inside onDraw is probably not the best idea, but that is outside the scope of this question.]
Two new methods have been introduced here:
private List<TimeStampedPoint> removeExpiredPoints() {
final List<TimeStampedPoint> result = new ArrayList<>();
for (final TimeStampedPoint point: linePoints) {
if (System.uptimeMillis() - point.getTimeStamp <= 10000) {
// We only include points in the result if they are not expired.
result.add(point);
}
}
return result;
}
and
private void updatePathUsingPoints(final List<TimeStampedPoint> validPoints) {
if (validPoints.size() < 2) {
return linePath; // Return the empty path here; nothing to draw.
}
linePath.moveTo(validPoints.get(0));
for (int i = 1; i < validPoints.size(); i++) {
final Point targetPoint = validPoints.get(i);
linePath.lineTo(targetPoint.x, targetPoint.y);
}
}
Hopefully this gives you enough of a framework to get started. If you notice the disappearing end of the line is jerky, I have ideas that can help, but it's a bunch more typing - so let's not prematurely optimize :)
Does anyone know the double-tap-zoom function on normal Android application (like web view or image view) is accomplished by zoom animation or by dividing the zoom into small steps and perform them one by one?
If using zoom animation, since animation is cached drawing, is there any problem when I do zoom in like this?
mView.zoomTo(newScale);
mView.startAnimation(zoomInAnimation);
Should I only set the new scale after animation finished?
And if using multiple steps of zooming, is this really a good approach? How to perform each step? Send messages multiple times?
What is exactly the best way of doing smooth zoom in/out when double tapping?
extend Animation class and override applyTransformarion method, use interpolatedTime parameter to compute the current zoom level
EDIT
as simple as it can be, works with really ancient droids:
public class ZoomAnimation extends Animation {
private float mFrom;
private float mTo;
public ZoomAnimation(float from, float to) {
mFrom = from;
mTo = to;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float currentZoom = mFrom + ((mTo - mFrom) * interpolatedTime);
// do something with currentZoom
}
}
While waiting for the best answer, I tried "multiple-step-zooming" by myself, and it turns out charming.
If anyone could give out a better answer, I will accept yours.
The multiple-step-zooming is performed by posting the nested Runnable instance, the mCanvasScale is the field used to control the drawing of the view. setZoomValue() is setting the mCanvasScale and call invalidate().
public void smoothScaleTo(final float targetScale) {
if(targetScale != mCanvasScale) {
post(new SmoothScaleExecutor(mCanvasScale, targetScale));
}
}
private class SmoothScaleExecutor implements Runnable {
public static final int SMOOTH_SCALE_STEPS = 4;
private float mStartingScale;
private float mTargetScale;
private float mScaleStep;
public SmoothScaleExecutor(float startingScale, float targetScale) {
mStartingScale = startingScale;
mTargetScale = targetScale;
mScaleStep = (targetScale - startingScale) / SMOOTH_SCALE_STEPS;
}
#Override
public void run() {
if(mStartingScale < mTargetScale) {
if(mCanvasScale < mTargetScale) {
float f = mCanvasScale + mScaleStep;
if(f > mTargetScale) f = mTargetScale;
setZoomValue(f);
post(this);
}
} else {
if(mCanvasScale > mTargetScale) {
float f = mCanvasScale + mScaleStep;
if(f < mTargetScale) f = mTargetScale;
setZoomValue(f);
post(this);
}
}
}
}
I use followed example (described here) to animate my sprite sheet.
from example I wrote my Object:
public class Sprite {
private int x, y;
private int width, height;
private Bitmap b;
MainGamePanel ov;
int currentFrame = 0;
public Sprite(MainGamePanel mainGamePanel, Bitmap blob) {
this.ov = mainGamePanel;
this.b = blob;
// 1x12
height = b.getHeight();
width = b.getWidth()/12;
x = y = 50;
}
private void update(int dist) {
currentFrame = ++currentFrame % 12;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//return _b;
}
public void draw(int shift, Canvas canvas, int dist) {
update(dist);
int srcX = currentFrame * width;
Rect src = new Rect(srcX, 0, srcX+width, height);
Rect dst = new Rect(x, y+shift, x+width, y+height+shift);
canvas.drawBitmap(b, src, dst, null);
}
Here every 100 msec I take different part from image (Bitmap) and show it.
1 -> 2 -> 3 -> 4 -> ... -> 12
So my creature flies and moves with wings.
If I show only 1 object, it seems good but when I try to run 20 creatures in loop:
Bitmap blob = BitmapFactory.decodeResource(getResources(), R.drawable.sprite3);
for(int i=0; i<20; i++){
spriteList.add(new Sprite(this, blob));
}
....
for(int i=0; i<spriteList.size(); i++){
sprite = spriteList.get(0);
sprite.draw(canvas, dist);
}
My objects start to be slow according to drawn object count.
It happens I think because of Thread.sleep(100);.
I don't see any performance problem.
Sounds like each object sleep pauses all objects.
For 20 objects this sleep grows to 2 sec.
For sure I use workaround like:
int sleep = 100/spriteList.size();
Thread.sleep(sleep);
But code looks messy.
Can anyone help me how to fix it?
Here is sprite3 image:
[EDIT]
Maybe I need create each object in separate Thread?
You should definitely not sleep while rendering; it greatly reduces the performance, especially, as you add new Animation Clips, etc. Also, don't create objects within your onDraw method and do try to reuse the Rect objects. Creating objects during rendering is very expensive.
I've written a game which uses a lot of lines, circles (some outlined, some filled, some with both) and text elements throughout to create a guitar fretboard which I get the user to interact with. Some of these elements are animated (coordinate, alpha, colour or a combination) and the app starts to skip lots of frames during most of the animations which I'd like to fix. I think OpenGL is the way to go, but I'm interested in some pointers before I jump in.
Currently my animation is achieved with lookup tables, async tasks and dynamic bitmap creation (for the text - I render it to bitmaps as I use custom fonts - so I never draw text directly to the canvas). I've got the async task running for n * 1000 ms and in the thread it waits for x ms (typically 50ms) and then pushes a progress message out - the helper classes then work out where in the time indexed lookup table the animation is and calculates the relative values based on that. I draw the static bits of the fretboard directly to the canvas with the included draw circle and draw line methods.
I'm not sure what is slowing my app down currently (mostly because I've not yet profiled it) but I'm pretty sure that even though I cache the bitmaps and have been fairly sensible about the way that I'm changing size & transparency, the use of bitmaps drawn directly to the Canvas is what is causing the slow down.
I've followed some tutorials and have written some OpenGL, but not enough to know much at all - I know it's fast though which is important. I don't know if I can use the same methods of drawing lines and circles directly to the canvas with OpenGL, and I think I'll still have to create some bitmaps for the text and I think I have to apply these as textures in order to show them.
So can anyone give me some pointers? Some sample code of drawing lines, circles and text would be amazing. Any pointers on animating within OpenGL - I think my current setup is pretty solid and can prob port it over but any advice would be great as this is my first look in to animating.
* EDIT *
Here's a basic overview of my code - there are many pieces, but I'm including them in the hope that someone else may be able to use some of it.:
I have a look up table
public enum LookUpTable {
COUNT_DOWN {
#Override
public float[][] getLookUpTable() {
/*
* 1 = total time left
* 2 = font alpha
* 3 = font size
*/
float[][] lookUpTable = {
{ 0f, 0f, 400f },
{ 400f, 255f, 200f },
{ 700f, 255f, 150f },
{ 1000f, 0f, 5f }
};
return lookUpTable;
}
#Override
public LookUpType getLookUpType() {
return LookUpType.REPEAT;
}
};
// does the timer loop around, or is it a one off run
private enum LookUpType {
REPEAT, SINGLE
}
abstract public LookUpType getLookUpType();
abstract public float[][] getLookUpTable();
}
I have extended the AsyncTask task into a builder function:
public class CountDownTimerBuilder {
// callbacks - instantiated in the view
protected CountDownEndEvent countDownEndEvent;
protected CountDownProgressEvent countDownProgressEvent;
protected CountDownInitEvent countDownInitEvent;
protected int updatePeriod;
protected float runTime;
public CountDownTimerBuilder withCountDownEndEvent(CountDownEndEvent countDownEndEvent) {
this.countDownEndEvent = countDownEndEvent;
return this;
}
public CountDownTimerBuilder withCountDownProgressEvent(CountDownProgressEvent countDownProgressEvent) {
this.countDownProgressEvent = countDownProgressEvent;
return this;
}
public CountDownTimerBuilder withCountDownInitEvent(CountDownInitEvent countDownInitEvent) {
this.countDownInitEvent = countDownInitEvent;
return this;
}
public CountDownTimerBuilder withUpdatePeriod(int updatePeriod) {
this.updatePeriod = updatePeriod;
return this;
}
public CountDownTimerBuilder withRunTime(float runTime) {
this.runTime = runTime;
return this;
}
public CountDownTimer build() {
return new CountDownTimer();
}
public static interface CountDownEndEvent {
public abstract void dispatch(Long... endResult);
}
public static interface CountDownInitEvent {
public abstract void dispatch();
}
public static interface CountDownProgressEvent {
public abstract void dispatch(Long... progress);
}
public class CountDownTimer {
AsyncTask<Void, Long, Long> genericTimerTask;
/**
* Starts the internal timer
*/
public void start() {
genericTimerTask = new GenericCountDownTimer().execute(new Void[] {});
}
public void cancel() {
if (genericTimerTask != null) {
genericTimerTask.cancel(true);
genericTimerTask = null;
}
}
private class GenericCountDownTimer extends AsyncTask<Void, Long, Long> {
#Override
protected Long doInBackground(Void... params) {
long startTime = System.currentTimeMillis();
long currentTime;
long countDown;
Log.i(ApplicationState.getLogTag(getClass()), "Timer running for " + runTime + " ms, updating every " + updatePeriod + " ms");
do {
try {
Thread.sleep(updatePeriod);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (this.isCancelled()) {
Log.i(ApplicationState.getLogTag(getClass()), "Timer Cancelled");
break;
}
currentTime = System.currentTimeMillis();
countDown = currentTime - startTime;
publishProgress((long)runTime - countDown);
} while (countDown <= runTime);
return 0l;
}
#Override
protected void onPreExecute() {
if (countDownInitEvent != null) {
countDownInitEvent.dispatch();
}
}
#Override
protected void onProgressUpdate(Long... progress) {
Log.v(ApplicationState.getLogTag(getClass()), "Timer progress " + progress[0] + " ms");
if (countDownProgressEvent != null) {
countDownProgressEvent.dispatch(progress);
}
}
#Override
protected void onPostExecute(Long endresult) {
if (countDownEndEvent != null) {
countDownEndEvent.dispatch(endresult);
}
}
}
}
}
I have a class where my animation values are calculated:
public class AnimationHelper {
private LookUpTable lookUpTable;
private float[][] lookUpTableData;
private float currentTime = -1;
private float multiplier;
private int sourceIndex;
public void setLookupTableData(LookUpTable lookUpTable) {
if (this.lookUpTable != lookUpTable) {
this.lookUpTableData = lookUpTable.getLookUpTable();
this.currentTime = -1;
this.multiplier = -1;
this.sourceIndex = -1;
}
}
private void setCurrentTime(float currentTime) {
this.currentTime = currentTime;
}
public float calculate(float currentTime, int index) {
if (this.currentTime == -1 || this.currentTime != currentTime) {
setCurrentTime(currentTime);
getCurrentLookupTableIndex();
getMultiplier();
}
return getCurrentValue(index);
}
private void getCurrentLookupTableIndex() {
sourceIndex = -1;
for (int scanTimeRange = 0; scanTimeRange < (lookUpTableData.length - 1); scanTimeRange++) {
if (currentTime < lookUpTableData[scanTimeRange + 1][0]) {
sourceIndex = scanTimeRange;
break;
}
}
}
private void getMultiplier() {
if ((lookUpTableData[sourceIndex][0] - lookUpTableData[sourceIndex + 1][0]) == 0.0f) {
multiplier = 0.0f;
} else {
multiplier = (currentTime - lookUpTableData[sourceIndex][0]) / (lookUpTableData[sourceIndex + 1][0] - lookUpTableData[sourceIndex][0]);
}
}
public float getCurrentValue(int index) {
float currentValue = lookUpTableData[sourceIndex][index] + ((lookUpTableData[sourceIndex + 1][index] - lookUpTableData[sourceIndex][index]) * multiplier);
return currentValue > 0 ? currentValue : 0;
}
}
In my game code I tie it all together by specifying the lookup table to use and creating callbacks for each of the different states, creating the timer with the builder class and starting it:
AnimationHelper animHelper = new AnimationHelper();
animHelper.setLookupTableData(LookUpTable.COUNT_DOWN);
CountDownInitEvent animationInitEvent = new CountDownInitEvent() {
public void dispatch() {
genericTimerState = TimerState.NOT_STARTED;
}
};
CountDownProgressEvent animationProgressEvent = new CountDownProgressEvent() {
public void dispatch(Long... progress) {
genericTimerState = TimerState.IN_PROGRESS;
// update the generic timer - we'll use this in all animations
genericTimerCountDown = progress[0];
invalidate();
}
};
CountDownEndEvent animationEndEvent = new CountDownEndEvent() {
public void dispatch(Long... endValue) {
genericTimerState = TimerState.FINISHED;
startGame();
}
};
CountDownTimer timer = new CountDownTimerBuilder()
.withRunTime(getCountDownPeriod(countDownTimePeriod)) // getCountDownPeriod() is used for handling screen rotation - esentially returns the run time for the timer in ms
.withUpdatePeriod(TIMER_UPDATE_PERIOD) // currently set at 50
.withCountDownInitEvent(animationInitEvent)
.withCountDownProgressEvent(animationProgressEvent)
.withCountDownEndEvent(animationEndEvent)
.build();
timer.start();
in my onDraw I get the specific values from the lookup table and act on them:
private int IFTL = 0; // total time left
private int IFY1 = 1; // initial instructions y offset
private int IFY2 = 2; // start message y offset
private int IFA1 = 3; // note to guess alpha
float yPosition1 = animHelper.calculate(genericTimerCountDown, IFY1);
float yPosition2 = animHelper.calculate(genericTimerCountDown, IFY2);
float alpha1 = animHelper.calculate(genericTimerCountDown, IFA1);
// getScreenDrawData() returns the coordinates and other positioning info for the bitmap
final ScreenDrawData guessNoteTitleDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_TITLE);
//change the y position of the bitmap being drawn to screen
guessNoteTitleDrawValues.withAlteredCoordinate(Constants.Y_COORDINATE, 0-yPosition1);
//
DrawBitmapBuilder.createInstance()
.withCanvas(getCanvas())
.withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_TITLE))
.withBitmapDrawValues(guessNoteTitleDrawValues)
.draw();
Paint paint = new Paint();
paint.setAlpha((int)alpha1);
final ScreenDrawData initialNoteDrawValues = FretBoardDimensionHelper.getScreenDrawData(AssetId.GUESS_NOTE_INITIAL_NOTE);
// draw to screen with specified alpha
DrawBitmapBuilder
.createInstance()
.withCanvas(getCanvas())
.withBitmap(bitmapCacheGet(AssetId.GUESS_NOTE_INITIAL_NOTE))
.withBitmapDrawValues(initialNoteDrawValues)
.withPaint(paint)
.draw();
You'll have to read between the lines a bit in that last bit of code as there are a load of helper functions in there.
Does this look like a reasonable approach? I'd love a code review if anyone can be bothered - more than happy to post up more code or explanations if required
I've been working a bit with OpenGL and well... It's not trivial.
For the text, you'll have to create a mutable Bitmap, write some text in it with canvas.drawText(...), and then convert this Bitmap (which should be in powerOfTwo dimension) to a gl texture, and apply it to a rectangle.
For circles... it's gonna be complicated. OpenGL draws lines, triangles, dots... not circles i'm afraid. One way to achieve a circle in OpenGL is to have a texture of a circle, and apply it to a rectangle.
Each time your application is put on pause, and resumed, you'll have to recreate each of your textures, and set up your gl surface once again...
If you need blending, you'll probably find that, with a lot of textures, your phone doesn't have enough memory to handle it all...
Now that I have scared you off : open gl is really one of the way to go ! It'll allow your app to delegate the drawing to the phone GPU, which will let you keep CPU for calculating animations and such.
You'll find some information about using OpenGL ES for android on this website :
http://blog.jayway.com/2009/12/03/opengl-es-tutorial-for-android-part-i/
There are 6 tutorials, and the 6th one is about textures. The 2nd tutorial tells you about drawing polygon, and lines.
Last of all, you should read this book : http://my.safaribooksonline.com/book/office-and-productivity-applications/9781430226475/copyright/ii
It's about android, openGL, ...
Good luck.
Edit :
Look like I don't have enough privilege to write a comment, therefor I'll edit this :
The way I'd do it would be maybe a lot more simpler :
I'd have a surface view, with a renderer dedicated to draw as often as possible. He would draw synchronized list of items (Synchronized, because of multi thread), with position, alpha, ... no async task here, just a regular rendering thread.
The surface view would also start a thread (regular thread once again, no async task), with a List (or something like that). This thread would, every 16 ms or something, run through the animation list, apply them. (synchronously of course if it needs to change some items used by the rendering thread).
When an animation is over (like, if it's been in the list for more than 2000 ms, easy to check with function such as System.currentTimeMillis()), I'd remove it from the list.
Voila !
Then you'll tell me : hey, but if i do a lot of animation, calculation might takes longer than 16 ms, and it appears to get slower !
My answer is : there is an easy enough solution for that : in the thread that deals with animation, before applying them you do something like that :
long lastTime = currentTime;
// Save current time for next frame;
currentTime = System.currentTimeMillis();
// Get elapsed time since last animation calculus
long ellapsedTime = currentTime - lastTime;
animate(ellapsedTime);
And in your animate function, you animate more if more time elapsed !
Hope this helps.