I have a problem.
How do I check if path in Android intersect with itself?
I have this class:
public class GameView extends SurfaceView implements Runnable {
Thread gameThread = null;
SurfaceHolder surfaceHolder;
volatile boolean playing;
Canvas canvas;
private final static int MAX_FPS = 60;
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
ArrayList<Point> points = new ArrayList<>();
private Context ctx;
Path path;
public static final int DEFAULT_COLOR = Color.RED;
public static final int DEFAULT_BG_COLOR = Color.WHITE;
private static final float TOUCH_TOLERANCE = 4;
private float mX, mY;
private Path mPath;
private Paint mPaint;
private int backgroundColor = DEFAULT_BG_COLOR;
public GameView(Context context) {
super(context);
ctx = context;
surfaceHolder = getHolder();
surfaceHolder.setFormat(PixelFormat.RGBA_8888);
path = new Path();
playing = true;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(DEFAULT_COLOR);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setXfermode(null);
mPaint.setAlpha(0xff);
mPaint.setStrokeWidth(12);
}
static Boolean isPathComplex(List<Point> path) {
if (path == null || path.size() <= 2) {
return false;
}
int len = path.size();
for (int i = 1; i < len; i++) {
Point lineAStart = path.get(i - 1);
Point lineAEnd = path.get(i);
for (int j = i + 1; j < len; j++) {
Point lineBStart = path.get(j - 1);
Point lineBEnd = path.get(j);
if (lineSegmentsIntersect(lineAStart.x,lineAStart.y,
lineAEnd.x,lineAEnd.y,
lineBStart.x,lineBStart.y,
lineBEnd.x,lineBEnd.y)) {
return true;
}
} // inner loop
} // outer loop
return false;
}
static Boolean lineSegmentsIntersect(float p0_x, float p0_y, float p1_x, float p1_y,
float p2_x, float p2_y, float p3_x, float p3_y) {
float s1_x, s1_y, s2_x, s2_y;
s1_x = p1_x - p0_x; s1_y = p1_y - p0_y;
s2_x = p3_x - p2_x; s2_y = p3_y - p2_y;
float s, t;
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
{
// Collision detected
return true;
}
return false; // No collision
}
public void update(){
isPathComplex(points);
}
public void draw(){
if(surfaceHolder.getSurface().isValid()){
canvas = surfaceHolder.lockCanvas();
if(canvas != null) {
canvas.drawColor(backgroundColor);
if (mPath != null) {
canvas.drawPath(mPath, mPaint);
}
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void run() {
while(playing){
long started = System.currentTimeMillis();
float deltaTime = (System.currentTimeMillis() - started);
int sleepTime = (int) (FRAME_PERIOD - deltaTime);
update();
draw();
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
}
catch (InterruptedException e) {
}
}
while (sleepTime < 0) {
update();
draw();
sleepTime += FRAME_PERIOD;
}
}
}
private void touchStart(float x, float y) {
mPath = new Path();
points.add(new Point((int)x,(int)y));
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touchMove(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
points.add(new Point((int)x,(int)y));
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touchUp() {
mPath.lineTo(mX, mY);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN :
touchStart(x, y);
break;
case MotionEvent.ACTION_MOVE :
touchMove(x, y);
break;
case MotionEvent.ACTION_UP :
touchUp();
break;
}
return true;
}
public void pause() {
playing = false;
try {
gameThread.join();
} catch (InterruptedException e) {
Log.e("Error:", "joining thread");
}
}
public void resume(Context context) {
playing = true;
gameThread = new Thread(this);
gameThread.start();
}
}
Please do you know some solution for this problem?
I tried a lot but nothing helped me with my problem.
What I want to achieve is to detect intersect like this
I tried solution with points from Check android.graphics.path intersection with itself but it seems not to be working for me.
I could also be doing something wrong because i am newbie to android development and programming in general :) .
Thank you in advance!
Edit:
To detect intersect i followed above solution and modified this function https://stackoverflow.com/a/1968345/9339525 but It returns almost all the time true even if path is not crossing itself.
All code for detect intersection:
static Boolean isPathComplex(List<Point> path) {
if (path == null || path.size() <= 2) {
return false;
}
int len = path.size();
for (int i = 1; i < len; i++) {
Point lineAStart = path.get(i - 1);
Point lineAEnd = path.get(i);
for (int j = i + 1; j < len; j++) {
Point lineBStart = path.get(j - 1);
Point lineBEnd = path.get(j);
if (lineSegmentsIntersect(lineAStart.x,lineAStart.y,
lineAEnd.x,lineAEnd.y,
lineBStart.x,lineBStart.y,
lineBEnd.x,lineBEnd.y)) {
return true;
}
} // inner loop
} // outer loop
return false;
}
static Boolean lineSegmentsIntersect(float p0_x, float p0_y, float p1_x, float p1_y,
float p2_x, float p2_y, float p3_x, float p3_y) {
float s1_x, s1_y, s2_x, s2_y;
s1_x = p1_x - p0_x; s1_y = p1_y - p0_y;
s2_x = p3_x - p2_x; s2_y = p3_y - p2_y;
float s, t;
s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
t = ( s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
// Collision detected
return true;
}
return false; // No collision
}
Edit:
I modified my class GameView and added method to detect intersection.
I could not stand that is was that difficult so i switched on the computer and coded something.
I tried only a few values and dont know what happens if begin-end points of segments coincide.
Please try out.
static boolean lineSegmentsDoIntersect(
float Ax, float Ay
, float Bx, float By
, float Cx, float Cy
, float Dx, float Dy) {
// two line segments: AB and CD
// segment AB intersects segment CD
// if A and B on different sides of line through C and D
// AND C and D on different sides of line through A and B
// note the difference between line and segment!
if ( ! pointsOnDifferentSidesOfLineThrough(Ax, Ay, Bx, By, Cx, Cy, Dx, Dy) )
return false;
if ( ! pointsOnDifferentSidesOfLineThrough(Cx, Cy, Dx, Dy, Ax, Ay, Bx, By) )
return false;
return true;
}
static boolean pointsOnDifferentSidesOfLineThrough(
float Ax, float Ay
, float Bx, float By
, float x1, float y1
, float x2, float y2) {
// determine equation of line through C and D
// y = ax + b
// a = (y2-y1)/(x2-x1) but.. ( x2-x1) not equal to zero
// y-y1 = a (x-x1)
// y = a (x-x1) + y1
// y = ax -ax1 + y1
// b = -ax1 + y1
//but.. (x2-x1) not 0
if ( x2==x1)
{
if ( Ax > x1 && Bx > x1 )
return false;
if ( Ax < x1 && Bx < x1 )
return false;
return true;
}
float a = (y2-y1)/(x2-x1);
float b = -a * x1 + y1;
float yA = a * Ax + b;
float yB = a * Bx + b;
if ( yA > Ay && yB > By )
return false;
if ( yA < Ay && yB < By )
return false;
return true;
}
Related
I have a cube that rotates on its 3 axis, when key[a] == true it will spin to the left as if it was rolling that way. rotating the cube 45 degrees in any direction sets it back 90 degrees for the illusion of continuing. this maintains 3 axis that are < 45 degrees off from the environment
I believe this is correct but the x axis for the cube seems to be relative to the environment while y and z are relative to the cubes orientation, I can not find reference to this in the documentation is it a bug?
https://processing.org/reference/rotateY_.html
https://processing.org/reference/rotateX_.html
if(keys[w]) {
if (x >= 359) x = 0;
x = x + 1;
}
if(keys[a]) {
if (z >= 359) z = 0;
z = z + 1;
}
if(keys[s]) {
if (x <= 0) x = 359;
x = x - 1;
}
if(keys[d]) {
if (z <= 0) z = 359;
z = z - 1;
}
// return 90 deg for magic trick
if (x > 45 && x < 180) x = 270 + x;
if (x < 316 && x > 180) x = 360 - x;
if (y > 45 && y < 180) y = 270 + y;
if (y < 316 && y > 180) y = 360 - y;
Matrix transformations are not commutative. The order matters. The matrix operations like rotate() specify a new matrix and multiply the current matrix by the new matrix.
Hence, there is a difference between doing this
rotateX(x);
rotateY(y);
rotateZ(z);
and doing that
rotateZ(z);
rotateY(y);
rotateX(x);
And
rotateX(x1 + x2);
rotateY(y1 + y2);
rotateZ(z1 + z2);
is not the same as
rotateX(x1);
rotateY(y1);
rotateZ(z1);
rotateX(x2);
rotateY(y2);
rotateZ(z2);
One possible solution to your problem would be to use Quaternions. Quaternions behave differently than Euler angles and have also no Gimbal lock issue. Processing use OpenGL under the hood and doesn't support quaternions. However a quaternion can be transformed to a matrix and a matrix can be applied by applyMatrix().
I found this ArcBall example that dose exactly what I wanted. just added a modification to work with keys instead of mouse drag.
ArcBall with mod
// Ariel and V3ga's arcball class with a couple tiny mods by Robert Hodgin & more by me
class Arcball {
float center_x, center_y, radius;
Vec3 v_down, v_drag;
Quat q_now, q_down, q_drag;
Vec3[] axisSet;
int axis;
float mxv, myv;
float x, y;
Arcball(float center_x, float center_y, float radius){
this.center_x = center_x;
this.center_y = center_y;
this.radius = radius;
v_down = new Vec3();
v_drag = new Vec3();
q_now = new Quat();
q_down = new Quat();
q_drag = new Quat();
axisSet = new Vec3[] {new Vec3(1.0f, 0.0f, 0.0f), new Vec3(0.0f, 1.0f, 0.0f), new Vec3(0.0f, 0.0f, 1.0f)};
axis = -1; // no constraints...
}
void rollforward(){
q_down.set(q_now);
v_down = XY_to_sphere(center_x, center_y);
q_down.set(q_now);
q_drag.reset();
v_drag = XY_to_sphere(center_x, center_y - 10);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
void rolldown(){
q_down.set(q_now);
v_down = XY_to_sphere(center_x, center_y);
q_down.set(q_now);
q_drag.reset();
v_drag = XY_to_sphere(center_x, center_y + 10);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
void rollleft(){
q_down.set(q_now);
v_down = XY_to_sphere(center_x + radius, center_y + radius);
q_down.set(q_now);
q_drag.reset();
v_drag = XY_to_sphere(center_x + 10 * PI + radius, center_y + radius);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
void rollright(){
q_down.set(q_now);
v_down = XY_to_sphere(center_x + radius, center_y + radius);
q_down.set(q_now);
q_drag.reset();
v_drag = XY_to_sphere(center_x - 10 * PI + radius, center_y + radius);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
void mousePressed(){
v_down = XY_to_sphere(mouseX, mouseY); // when m pressed
q_down.set(q_now);
q_drag.reset();
}
void mouseDragged(){
v_drag = XY_to_sphere(mouseX, mouseY);
q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
}
void run(){
q_now = Quat.mul(q_drag, q_down);
applyQuat2Matrix(q_now);
x += mxv;
y += myv;
mxv -= mxv * .01;
myv -= myv * .01;
}
Vec3 XY_to_sphere(float x, float y){
Vec3 v = new Vec3();
v.x = (x - center_x) / radius;
v.y = (y - center_y) / radius;
float mag = v.x * v.x + v.y * v.y;
if (mag > 1.0f){
v.normalize();
} else {
v.z = sqrt(1.0f - mag);
}
return (axis == -1) ? v : constrain_vector(v, axisSet[axis]);
}
Vec3 constrain_vector(Vec3 vector, Vec3 axis){
Vec3 res = new Vec3();
res.sub(vector, Vec3.mul(axis, Vec3.dot(axis, vector)));
res.normalize();
return res;
}
void applyQuat2Matrix(Quat q){
// instead of transforming q into a matrix and applying it...
float[] aa = q.getValue();
rotate(aa[0], aa[1], aa[2], aa[3]);
}
}
static class Vec3{
float x, y, z;
Vec3(){
}
Vec3(float x, float y, float z){
this.x = x;
this.y = y;
this.z = z;
}
void normalize(){
float length = length();
x /= length;
y /= length;
z /= length;
}
float length(){
return (float) Math.sqrt(x * x + y * y + z * z);
}
static Vec3 cross(Vec3 v1, Vec3 v2){
Vec3 res = new Vec3();
res.x = v1.y * v2.z - v1.z * v2.y;
res.y = v1.z * v2.x - v1.x * v2.z;
res.z = v1.x * v2.y - v1.y * v2.x;
return res;
}
static float dot(Vec3 v1, Vec3 v2){
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
static Vec3 mul(Vec3 v, float d){
Vec3 res = new Vec3();
res.x = v.x * d;
res.y = v.y * d;
res.z = v.z * d;
return res;
}
void sub(Vec3 v1, Vec3 v2){
x = v1.x - v2.x;
y = v1.y - v2.y;
z = v1.z - v2.z;
}
}
static class Quat{
float w, x, y, z;
Quat(){
reset();
}
Quat(float w, float x, float y, float z){
this.w = w;
this.x = x;
this.y = y;
this.z = z;
}
void reset(){
w = 1.0f;
x = 0.0f;
y = 0.0f;
z = 0.0f;
}
void set(float w, Vec3 v){
this.w = w;
x = v.x;
y = v.y;
z = v.z;
}
void set(Quat q){
w = q.w;
x = q.x;
y = q.y;
z = q.z;
}
static Quat mul(Quat q1, Quat q2){
Quat res = new Quat();
res.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z;
res.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y;
res.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z;
res.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x;
return res;
}
float[] getValue(){
// transforming this quat into an angle and an axis vector...
float[] res = new float[4];
float sa = (float) Math.sqrt(1.0f - w * w);
if (sa < EPSILON){
sa = 1.0f;
}
res[0] = (float) Math.acos(w) * 2.0f;
res[1] = x / sa;
res[2] = y / sa;
res[3] = z / sa;
return res;
}
}
main
Arcball arcball;
//framecount
int fcount, lastm;
float frate;
int fint = 3;
boolean[] keys = new boolean[4];
final int w = 0;
final int s = 1;
final int a = 2;
final int d = 3;
void setup() {
size(900, 700, P3D);
frameRate(60);
noStroke();
arcball = new Arcball(width/2, height/2+100, 360);
}
void draw() {
lights();
background(255,160,122);
if(keys[w]) { arcball.rollforward(); }
if(keys[a]) { arcball.rollleft(); }
if(keys[s]) { arcball.rolldown(); }
if(keys[d]) { arcball.rollright(); }
ambient(80);
lights();
translate(width/2, height/2-100, 0);
box(50);
translate(0, 200, 0);
arcball.run();
box(50);
}
void keyPressed() {
switch(key) {
case 97:
keys[a] = true;
break;
case 100:
keys[d] = true;
break;
case 115:
keys[s] = true;
break;
case 119:
keys[w] = true;
break;
}
}
void keyReleased() {
switch(key) {
case 97:
keys[a] = false;
break;
case 100:
keys[d] = false;
break;
case 115:
keys[s] = false;
break;
case 119:
keys[w] = false;
break;
}
}
will add support for multiple keys at once later with an edit.... stay tuned
I can create circle on touch but can not remove the previous one. I have manage to remove the very first circle but the code is poor and it does not really work as I want. I like to draw circle every time I touch the screen and remove the previous circle . So the screen starts with a circle and as I will touch a new position the previous should be removed and there will be a new one.So how to do that part?
Here is my work:
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Display display = new Display(this);
display.init();
display.backPaint.setColor(Color.WHITE);
setContentView(display);
}
static class Display extends View {
ArrayList<Point> points = new ArrayList();
int touch1_x=700;
int touch1_y=700;
Paint backPaint;
Paint circlePaint;
Paint circlePaint2;
Display(Context context) {
super(context);
}
void init() {
backPaint = new Paint();
backPaint.setColor(Color.WHITE);
backPaint.setStyle(Paint.Style.FILL);
circlePaint = new Paint();
circlePaint.setColor(Color.YELLOW);
circlePaint2 = new Paint();
circlePaint2.setColor(Color.WHITE);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
// if (event.getX(event.getActionIndex()) > 100 && event.getX(event.getActionIndex()) < 200) {
touch1_x = (int) event.getX(event.getActionIndex());
touch1_y = (int) event.getY(event.getActionIndex());
// }
System.out.println("touch1_x ===" + touch1_x);
points.add(new Point(touch1_x, touch1_y));
points.add(new Point(touch1_x, touch1_y));
return true;
}
return false;
}
});
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), backPaint);
canvas.drawCircle(touch1_x, touch1_y, 50, circlePaint);
for(Point p: points){
canvas.drawCircle(p.x, p.y, 50, circlePaint);
}
for(Point p: points){
canvas.drawCircle(p.x, p.y, 50,circlePaint2 );
}
invalidate();
}
}
}
relevant question
You could erase canvas before drawing the next circle, using
canvas.drawColor(Color.TRANSPARENT,Mode.CLEAR);
import turtle
t=turtle.Turtle()
wn=turtle.Screen()
for count in range(360):
t.fd(3)
t.rt(1)
wn.exitonclick()
I have a customised view for photo annotate, you can use this to do your job.
1: Create a file AnnotationView.java and copy the following code
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
/**
* #author Bob Gao
* http://bobgao.net
*/
public class AnnotationView extends ImageView implements OnTouchListener {
private static final float KEY_STROKE_WIDTH = 4;
private List<Annotation> annotations = new ArrayList<Annotation>();
private Annotation mAnnotation;
private Canvas mCanvas;
private Paint mPaint;
// private Rect mOutRect;
public AnnotationView(Context context) {
super(context);
setOnTouchListener(this);
setDrawingCacheEnabled(true);
setScaleType(ScaleType.FIT_XY);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.mCanvas = canvas;
for (Annotation annotation : annotations) {
mPaint.setColor(annotation.getColor());
switch (annotation.getShape()) {
case Annotation.KEY_SHAPE_CIRCLE:
drawCircle(annotation.getX(), annotation.getY(), annotation.getRadius());
break;
case Annotation.KEY_SHAPE_ARROW:
drawAL((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius(), annotation.getDegree());
break;
case Annotation.KEY_SHAPE_SQUARES:
drawRect((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius());
break;
case Annotation.KEY_SHAPE_TRIANGLE:
drawTriangle((int) annotation.getX(), (int) annotation.getY(), (int) annotation.getRadius(), annotation.getDegree());
break;
}
}
}
/**
* Draw a triangle
*
* #param x
* #param y
* #param r
*/
public void drawTriangle(int x, int y, int r, float degree) {
Path path = new Path();
path.moveTo(x, y);
path.lineTo(x - 2 * r, y + 2 * r);
path.lineTo(x + 2 * r, y + 2 * r);
path.lineTo(x, y);
path.close();
rotatePath(path, degree);
mCanvas.drawPath(path, mPaint);
}
public void drawRect(int x, int y, int r) {
int lenght = 4 * r;
int left = x - lenght / 2;
int top = y - lenght / 2;
int right = x + lenght / 2;
int bottom = y + lenght / 2;
Path path = new Path();
path.moveTo(left, top);
path.lineTo(right, top);
path.lineTo(right, bottom);
path.lineTo(left, bottom);
path.lineTo(left, top);
path.close();
mCanvas.drawPath(path, mPaint);
}
public void drawCircle(float x, float y, float r) {
mCanvas.drawCircle(x, y, r, mPaint);
}
/**
* Draw arrow
*
* #param sx
* #param sy
* #param ex
* #param ey
*/
public void drawAL(int sx, int sy, int r, float degree) {
int ex = (int) (sx + 2 * r);
int ey = (int) (sy + 2 * r);
switch (new Float(degree).intValue()) {
case 90:
ex = (int) (sx - 2 * r);
ey = (int) (sy + 2 * r);
break;
case 180:
ex = (int) (sx - 2 * r);
ey = (int) (sy - 2 * r);
break;
case 270:
ex = (int) (sx + 2 * r);
ey = (int) (sy - 2 * r);
break;
}
double H = 8; // the height of arrow
double L = 3.5; // half of bottom line
int x3 = 0;
int y3 = 0;
int x4 = 0;
int y4 = 0;
double awrad = Math.atan(L / H); // the rotation of arrow
double arraow_len = Math.sqrt(L * L + H * H); // the length of arrow
double[] arrXY_1 = rotateVec(ex - sx, ey - sy, awrad, true, arraow_len);
double[] arrXY_2 = rotateVec(ex - sx, ey - sy, -awrad, true, arraow_len);
double x_3 = ex - arrXY_1[0]; // (x3,y3) the first point
double y_3 = ey - arrXY_1[1];
double x_4 = ex - arrXY_2[0]; // (x4,y4) the second point
double y_4 = ey - arrXY_2[1];
Double X3 = new Double(x_3);
x3 = X3.intValue();
Double Y3 = new Double(y_3);
y3 = Y3.intValue();
Double X4 = new Double(x_4);
x4 = X4.intValue();
Double Y4 = new Double(y_4);
y4 = Y4.intValue();
// draw line
mCanvas.drawLine(sx, sy, ex, ey, mPaint);
Path triangle = new Path();
triangle.moveTo(ex, ey);
triangle.lineTo(x3, y3);
triangle.lineTo(x4, y4);
triangle.close();
mCanvas.drawPath(triangle, mPaint);
}
// Calculate
public double[] rotateVec(int px, int py, double ang, boolean isChLen, double newLen) {
double mathstr[] = new double[2];
// 矢量旋转函数,参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
double vx = px * Math.cos(ang) - py * Math.sin(ang);
double vy = px * Math.sin(ang) + py * Math.cos(ang);
if (isChLen) {
double d = Math.sqrt(vx * vx + vy * vy);
vx = vx / d * newLen;
vy = vy / d * newLen;
mathstr[0] = vx;
mathstr[1] = vy;
}
return mathstr;
}
#Override
public boolean onTouch(View v, MotionEvent event) {
float x = event.getX();
float y = event.getY();
if (mAnnotation != null) {
float minX = 0;
float minY = 0;
float maxX = 0;
float maxY = 0;
switch (mAnnotation.getShape()) {
case Annotation.KEY_SHAPE_ARROW:
minX = 0;
minY = 0;
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
break;
case Annotation.KEY_SHAPE_SQUARES:
minX = getLeft() + mAnnotation.getRadius() / 2;
minY = getTop() + mAnnotation.getRadius() / 2;
maxX = getLeft() + getWidth() - mAnnotation.getRadius() / 2;
maxY = getTop() + getHeight() - mAnnotation.getRadius() / 2;
break;
case Annotation.KEY_SHAPE_CIRCLE:
minX = 0;
minY = 0;
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
case Annotation.KEY_SHAPE_TRIANGLE:
minX = getLeft() + mAnnotation.getRadius();
minY = getTop() + mAnnotation.getRadius();
maxX = getLeft() + getWidth() - mAnnotation.getRadius();
maxY = getTop() + getHeight() - mAnnotation.getRadius();
break;
}
if (x > minX && x < maxX && y > minY && y < maxY) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mAnnotation.setX(x);
mAnnotation.setY(y);
postInvalidate();
case MotionEvent.ACTION_MOVE:
mAnnotation.setX(x);
mAnnotation.setY(y);
postInvalidate();
break;
}
}
return true;
}
return false;
}
public void startAnnotate(String pathName) {
mAnnotation = new Annotation();
mAnnotation.setMaxRadius(getWidth() / 2);
mAnnotation.setColor(Color.RED);
mAnnotation.setX(getWidth() / 2);
mAnnotation.setY(getHeight() / 2);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(KEY_STROKE_WIDTH);
annotations.add(mAnnotation);
postInvalidate();
}
public void stopAnnotate() {
if (annotations.isEmpty()) {
mPaint = null;
mAnnotation = null;
} else {
annotations.remove(annotations.size() - 1);
if (annotations.isEmpty()) {
mAnnotation = null;
mPaint = null;
} else {
mAnnotation = annotations.get(annotations.size() - 1);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mAnnotation.getColor());
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(KEY_STROKE_WIDTH);
}
}
postInvalidate();
}
public void plus() {
if (mAnnotation != null) {
mAnnotation.plus();
postInvalidate();
}
}
public void sub() {
if (mAnnotation != null) {
mAnnotation.sub();
postInvalidate();
}
}
public void rotateRight() {
if (mAnnotation != null) {
if (mAnnotation.getDegree() >= 360) {
mAnnotation.setDegree(0);
} else {
mAnnotation.setDegree(mAnnotation.getDegree() + 90);
}
postInvalidate();
}
}
public void rotateLeft() {
if (mAnnotation != null) {
if (mAnnotation.getDegree() <= 0) {
mAnnotation.setDegree(270);
} else {
mAnnotation.setDegree(mAnnotation.getDegree() - 90);
}
postInvalidate();
}
}
public void changeColor(int color) {
if (mAnnotation != null) {
mAnnotation.setColor(color);
mPaint.setColor(color);
}
postInvalidate();
}
/**
* Change the shape
*
* #param id
*/
public void changeShape(int id) {
if (mAnnotation != null) {
mAnnotation.setShape(id);
postInvalidate();
}
}
/**
* Draw line
*
* #param fromX
* start point x
* #param fromY
* start point y
* #param toX
* end point x
* #param toY
* end point y
*/
public void drawLine(float fromX, float fromY, float toX, float toY) {
Path linePath = new Path();
linePath.moveTo(fromX, fromY);
linePath.lineTo(toX, toY);
linePath.close();
mCanvas.drawPath(linePath, mPaint);
invalidate();
}
public List<Annotation> getAnnotations() {
return annotations;
}
private void rotatePath(Path path, float degree) {
Matrix mMatrix = new Matrix();
RectF bounds = new RectF();
path.computeBounds(bounds, true);
mMatrix.postRotate(degree, (bounds.right + bounds.left) / 2, (bounds.bottom + bounds.top) / 2);
path.transform(mMatrix);
}
}
2. In your activity, create the AnnotationView and assign your image to it, then append this view to your root view.
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AnnotationView mPreview = (FrameLayout) findViewById(R.id.camera_preview);
AnnotationView mAnnotationView = new AnnotationView(this);
Bitmap bitmap;
try {
bitmap = CBitmapUtil.decode(mPictureFile); // Get your image as a bitmap
mAnnotationView.setImageBitmap(bitmap);
mPreview.addView(mAnnotationView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
} catch (FileNotFoundException e) {
e.printStackTrace();
Toast.makeText(this, "Photo cannot be saved, please try again later.", Toast.LENGTH_SHORT).show();
}
}
Basically I wrote a "Dota like Style" based on the OrthographicCamera from libgdx.
You can test it out for youself here is the class.
I am using this to draw a TiledMap, and I have and array of tiles corresponding with the graphical tiles, however if I move the mouse, and with that the camera.
The coordinates off the mouse and the tiles are completely different.
Gdx.input x and y get their coordinates relative to the screen and not where the mouse is in the world relative to the camera.
I can't figure out a way to get the mouse position relative to the camera, so that if I move the camera I won't just get the regular mouse coordinates, but the actual world coordinates that the camera is showing, and where my mouse is located within the confines of the view of the camera relative to the world.
public class DotaCamera extends OrthographicCamera {
private float xmin;
private float xmax;
private float ymin;
private float ymax;
private float x;
private float y;
private int Width = Gdx.graphics.getWidth();;
private int Height = Gdx.graphics.getHeight();
private int camSpeedMax = 16;
private float camAcceleration = 0.3f;
private int camSpeedSmoother = 3;
private float camVelocityX = 0;
private float camVelocityY = 0;
private float fZoomMax = 1f;
private float fZoomMin = 0.5f;
private float fZoomSpeed = 0.03f;
public DotaCamera() {
this(0, 0, 0, 0);
}
public DotaCamera(float xmin, float xmax, float ymin, float ymax) {
super();
setBounds(xmin, xmax, ymin, ymax);
}
public void setBounds(float xmin, float xmax, float ymin, float ymax) {
this.xmin = xmin;
this.xmax = xmax;
this.ymin = ymin;
this.ymax = ymax;
}
public void setPosition(float x, float y) {
setPosition(x, y, 0);
}
public void setPosition(float x, float y, float z) {
position.set(x, y, z);
this.x = x;
this.y = y;
fixBounds();
}
private void fixBounds() {
if (position.x < xmin + viewportWidth / 2) {
position.x = xmin + viewportWidth / 2;
}
if (position.x > xmax - viewportWidth / 2) {
position.x = xmax - viewportWidth / 2;
}
if (position.y < ymin + viewportHeight / 2) {
position.y = ymin + viewportHeight / 2;
}
if (position.y > ymax - viewportHeight / 2) {
position.y = ymax - viewportHeight / 2;
}
}
/**
* Controls the zoom of the of the camera.
*/
public void updateZoom() {
int mouseWheelMovement = Mouse.getDWheel();
if (mouseWheelMovement > 0) {
if (this.zoom > fZoomMin) {
this.zoom -= fZoomSpeed;
} else {
this.zoom = fZoomMin;
}
}else if(mouseWheelMovement < 0){
if (this.zoom < fZoomMax) {
this.zoom += fZoomSpeed;
} else {
this.zoom = fZoomMax;
}
}
}
/**
* Update And move the Camera DOTA Stylized movement.
*/
public void updateAndMove() {
float dt = Gdx.graphics.getDeltaTime();
int MouseX = Mouse.getX(); // Get MouseX
int MouseY = Height - Mouse.getY(); // Get MouseY
int camSpeedX = 0;
int camSpeedY = 0;
String horizontalDirection = getMoveLeftRight(MouseX); // Get
// horizontalDirection
String verticalDirection = getMoveUpDown(MouseY); // Get
// verticalDirection
/* * * * * * * *
* Decide what to do with the horizontalDirection.
*/
switch (horizontalDirection) {
case "left":
camSpeedX = ((Width / 2) - (MouseX + (Width / 4)))
/ camSpeedSmoother; // Create Speed -X
camSpeedX = ((camSpeedX > camSpeedMax) ? camSpeedMax : camSpeedX); // Limit
// the
// speed.
if (camVelocityX < camSpeedX)
camVelocityX += camAcceleration;
break;
case "right":
camSpeedX = (((MouseX + (Width / 4)) - ((Width / 4) * 3)) - (Width / 4))
/ camSpeedSmoother; // Create speed +X.
camSpeedX = ((camSpeedX > camSpeedMax) ? camSpeedMax : camSpeedX); // Limit
// the
// speed.
if (camVelocityX < camSpeedX)
camVelocityX += camAcceleration; // Accelerate
camSpeedX *= -1; // To negate the speed.
break;
case "":
camVelocityX = 0;
break;
}
/* * * * * * * *
* Decide what to do with the verticalDirection.
*/
switch (verticalDirection) {
case "up":
camSpeedY = (Height / 4) - MouseY; // Create speed -Y
camSpeedY = ((camSpeedY > camSpeedMax) ? camSpeedMax : camSpeedY); // Limit
// the
// speed.
if (camVelocityY < camSpeedY)
camVelocityY += camAcceleration;
camSpeedY *= -1;
break;
case "down":
camSpeedY = (((MouseY + (Height / 4)) - ((Height / 4) * 3)) - (Height / 4))
/ camSpeedSmoother; // Create speed +Y.
camSpeedY = ((camSpeedY > camSpeedMax) ? camSpeedMax : camSpeedY); // Limit
// the
// speed.
if (camVelocityY < camSpeedY)
camVelocityY += camAcceleration;
break;
case "":
camVelocityY = 0;
break;
}
// System.out.println("vX:" +camVelocityX+ "vY: " +camVelocityY+ "sX: "
// +camSpeedX+ "sY: " +camSpeedY);
this.position.x -= (camVelocityX * camSpeedX) * dt;
this.position.y -= (camVelocityY * camSpeedY) * dt;
this.update();
}
/**
* Get the X-Axial Direction.
*
* #param MouseX
* #return Direction
*/
private String getMoveLeftRight(int MouseX) {
if (MouseX + (Width / 4) < Width / 2) {// Needs to move left?
return "left";
} else if (MouseX > (Width / 4) * 3) {// Needs to move right?
return "right";
}
return "";
}
/**
* Get the Y-Axial Direction.
*
* #param MouseY
* #return Direction
*/
private String getMoveUpDown(int MouseY) {
if (MouseY < Height / 4) {// Needs to move up?
return "up";
} else if (MouseY > (Height / 4) * 3) {// Needs to move down?
return "down";
}
return "";
}
Came across this problem and discovered the answer here:
https://gamedev.stackexchange.com/questions/27786/camera-coordinate-to-screen-coordinate
Supposedly, using Camera.unproject(Vector3 screenCoords) is the correct way of doing this.
My solution looks like this:
Vector3 getMousePosInGameWorld() {
return camera.unproject(new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0));
}
I hope you found the solution to what you need here
https://gamedev.stackexchange.com/questions/60703/libgdx-how-to-get-mouse-position-relative-to-a-tiled-map
maybe here
Get cursor position in LIBGDX
or here
http://www.netthreads.co.uk/2012/01/31/libgdx-example-of-using-scene2d-actions-and-event-handling/
I have a Custom View which draws lines an saves them to an ArrayList. What I want to be able to do is check if a specified point is on a line inside the Arraylist and return the line. I have been able to do this when the line is straight by using if (l.startX == l.stopX && l.startY < l.stopY but this doesn't work if the line is on an angle. I would also like to do this with drawText and drawCircle. Is there some simple way of doing this that I have missed?
DrawView.java
class Line {
float startX, startY, stopX, stopY;
public Line(float startX, float startY, float stopX, float stopY) {
this.startX = startX;
this.startY = startY;
this.stopX = stopX;
this.stopY = stopY;
}
public Line(float startX, float startY) { // for convenience
this(startX, startY, startX, startY);
}
}
public class DrawView extends View {
Paint paint = new Paint();
ArrayList<Line> lines = new ArrayList<Line>();
public DrawView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setStrokeWidth(6f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
}
#Override
protected void onDraw(Canvas canvas) {
for (Line l : lines) {
canvas.drawLine(l.startX, l.startY, l.stopX, l.stopY, paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
lines.add(new Line(event.getX(), event.getY()));
return true;
}
else if ((event.getAction() == MotionEvent.ACTION_MOVE ||
event.getAction() == MotionEvent.ACTION_UP) &&
lines.size() > 0) {
Line current = lines.get(lines.size() - 1);
current.stopX = event.getX();
current.stopY = event.getY();
Invalidate();
return true;
}
else {
return false;
}
}
}
I don't know if there is a canvas library for this but manually you can:
go through your list of lines
form equations for each line in let's say slope-intercept form y = mx + b
plugin in the event.X() and event.Y() into the above equation as x and y for each line
if the 2 sides are equal then your touch event is on that line
After many hours of trying I managed to come up with a an algorithm to check if a point is on a given line using a mixture of slope-intersect formula and if (x = startX && y > startY && y < stopY) Below is the code, it returns true if point is on line otherwise false, hopefully this can save someone time!
public boolean checkPosition(float x, float y, float sx, float sy, float ex, float ey) {
float mX = ex - sx;
float mY = ey - sy;
float smX = sx - ex;
float smY = sy - ey;
float pmX = mX;
float pmY = mY;
float psmX = smX;
float psmY = smY;
float yY = ey - y;
float xX = ex - x;
float sX = sx - x;
float sY = sy - y;
float m = mY / mX;
float b = sy - (m * sx);
if (mX < 0) {
pmX = mX * - 1;
}
if (mY < 0) {
pmY = mY * - 1;
}
if (smX < 0) {
psmX = smX * - 1;
}
if (smY < 0) {
psmY = smY * - 1;
}
if (yY < 0) {
yY = yY * - 1;
}
if (xX < 0) {
xX = xX * - 1;
}
if (sX < 0) {
sX = sX * - 1;
}
if (sY < 0) {
sY = sY * - 1;
}
if (sy == ey && y == sy) {
if (sx >= ex) {
if (x <= sx && x >= ex) return true;
else return false;
}
else if (ex >= sx) {
if (x <= ex && x >= sx) return true;
else return false;
}
else return false;
}
else if (sx == ex && x == sx) {
if (sy >= ey) {
if (y <= sy && y >= ey) return true;
else return false;
}
else if (ey >= sy) {
if (y <= ey && y >= sy) return true;
else return false;
}
else return false;
}
else if (xX <= pmX && sX <= psmX && yY <= pmY && sY <= psmY) {
if (y == (m * x) + b) return true;
else return false;
}
else return false;
}
I have good working TouchImageView and i want to know how does it work in a code :
What i just want to do is be able to pinch to zoom, or use double tap to zoom on any imageview i choose, and when i zoom back i return to the original size of the image.
TouchImageView.java :
public class TouchImageView extends ImageView {
Matrix matrix = new Matrix();
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
PointF last = new PointF();
PointF start = new PointF();
float minScale = 1f;
float maxScale = 3f;
float[] m;
float redundantXSpace, redundantYSpace;
float width, height;
static final int CLICK = 3;
float saveScale = 1f;
float right, bottom, origWidth, origHeight, bmWidth, bmHeight;
ScaleGestureDetector mScaleDetector;
Context context;
public TouchImageView(Context context) {
super(context);
super.setClickable(true);
this.context = context;
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
matrix.setTranslate(1f, 1f);
m = new float[9];
setImageMatrix(matrix);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
mScaleDetector.onTouchEvent(event);
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
PointF curr = new PointF(event.getX(), event.getY());
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
last.set(event.getX(), event.getY());
start.set(last);
mode = DRAG;
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
float deltaX = curr.x - last.x;
float deltaY = curr.y - last.y;
float scaleWidth = Math.round(origWidth * saveScale);
float scaleHeight = Math.round(origHeight * saveScale);
if (scaleWidth < width) {
deltaX = 0;
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
} else if (scaleHeight < height) {
deltaY = 0;
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
} else {
if (x + deltaX > 0)
deltaX = -x;
else if (x + deltaX < -right)
deltaX = -(x + right);
if (y + deltaY > 0)
deltaY = -y;
else if (y + deltaY < -bottom)
deltaY = -(y + bottom);
}
matrix.postTranslate(deltaX, deltaY);
last.set(curr.x, curr.y);
}
break;
case MotionEvent.ACTION_UP:
mode = NONE;
int xDiff = (int) Math.abs(curr.x - start.x);
int yDiff = (int) Math.abs(curr.y - start.y);
if (xDiff < CLICK && yDiff < CLICK)
performClick();
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
setImageMatrix(matrix);
invalidate();
return true; // indicate event was handled
}
});
}
#Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
bmWidth = bm.getWidth();
bmHeight = bm.getHeight();
}
public void setMaxZoom(float x)
{
maxScale = x;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mode = ZOOM;
return true;
}
#Override
public boolean onScale(ScaleGestureDetector detector) {
float mScaleFactor = (float)Math.min(Math.max(.95f, detector.getScaleFactor()), 1.05);
float origScale = saveScale;
saveScale *= mScaleFactor;
if (saveScale > maxScale) {
saveScale = maxScale;
mScaleFactor = maxScale / origScale;
} else if (saveScale < minScale) {
saveScale = minScale;
mScaleFactor = minScale / origScale;
}
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
if (mScaleFactor < 1) {
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (Math.round(origWidth * saveScale) < width) {
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
} else {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
}
}
}
} else {
matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());
matrix.getValues(m);
float x = m[Matrix.MTRANS_X];
float y = m[Matrix.MTRANS_Y];
if (mScaleFactor < 1) {
if (x < -right)
matrix.postTranslate(-(x + right), 0);
else if (x > 0)
matrix.postTranslate(-x, 0);
if (y < -bottom)
matrix.postTranslate(0, -(y + bottom));
else if (y > 0)
matrix.postTranslate(0, -y);
}
}
return true;
}
}
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = MeasureSpec.getSize(widthMeasureSpec);
height = MeasureSpec.getSize(heightMeasureSpec);
//Fit to screen.
float scale;
float scaleX = (float)width / (float)bmWidth;
float scaleY = (float)height / (float)bmHeight;
scale = Math.min(scaleX, scaleY);
matrix.setScale(scale, scale);
setImageMatrix(matrix);
saveScale = 1f;
// Center the image
redundantYSpace = (float)height - (scale * (float)bmHeight) ;
redundantXSpace = (float)width - (scale * (float)bmWidth);
redundantYSpace /= (float)2;
redundantXSpace /= (float)2;
matrix.postTranslate(redundantXSpace, redundantYSpace);
origWidth = width - 2 * redundantXSpace;
origHeight = height - 2 * redundantYSpace;
right = width * saveScale - width - (2 * redundantXSpace * saveScale);
bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
setImageMatrix(matrix);
}
}
To use is i created a private class :
private class CreateImage extends AsyncTask<String, Void, Drawable> {
protected void onPreExecute() {
}
protected Drawable doInBackground(String... urls) {
InputStream is;
Drawable d = null ;
try {
is = (InputStream)new URL(urls[0]).getContent();
d = Drawable.createFromStream(is, "Image");
return d;
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return d;
}
protected void onPostExecute(Drawable d) {
touch.setMaxZoom(4f);
touch.setImageDrawable(d);
setContentView(touch);
}
}
public void createUrlImage(String url){
new CreateImage().execute(url);
}
and in the onCreate() i put createUrlImage(url).
I modified TouchImageView by adding :
public void setImageDrawable(Drawable dr) {
super.setImageDrawable(dr);
bmWidth = dr.getIntrinsicWidth();
bmHeight = dr.getIntrinsicHeight();
}
EDIT: Double Tap Zoom, Fling and other features have been added to TouchImageView since I originally answered this question. You can check it out on github here.
I added some usage details to the original post here. The code already has pinch zoom and panning, along with boundaries. Also, zooming out will return you to the original sized image.
Adding double tap zoom will take more work. You'll have to use a GestureDetector and override onDoubleTap and onSingleTapConfirmed. You'll then want to make sure you pass your touch events to gestureDetector, without interfering with the rest of the code (see how events are passed to mScaleDetector at the beginning of onTouch). You'll want to get rid of the call to performClick() in ACTION_UP and instead place it in onSingleTapConfirmed. You can check this answer for some skeletal code to get you started in implementing GestureDetector.
Let me know if you manage to get double tap zoom solid and I'll add your changes to the original post and the Github repo.