Home | History | Annotate | Download | only in dreamtheater
      1 package com.android.dreamtheater;
      2 
      3 import android.animation.PropertyValuesHolder;
      4 import android.animation.TimeAnimator;
      5 import android.app.Activity;
      6 import android.content.Context;
      7 import android.content.Intent;
      8 import android.graphics.Canvas;
      9 import android.graphics.Matrix;
     10 import android.graphics.Paint;
     11 import android.graphics.RectF;
     12 import android.os.Bundle;
     13 import android.util.AttributeSet;
     14 import android.util.Log;
     15 import android.view.MotionEvent;
     16 import android.view.View;
     17 import android.view.Gravity;
     18 import android.view.ViewGroup;
     19 import android.widget.Button;
     20 import android.widget.FrameLayout;
     21 import android.widget.ImageView;
     22 
     23 import java.util.LinkedList;
     24 import java.util.HashMap;
     25 
     26 public class BouncyDroid extends Activity {
     27     static final boolean DEBUG = true;
     28     static final boolean CENTER_DROID = true;
     29 
     30     public static class BouncyView extends FrameLayout
     31     {
     32         boolean mShowDebug = false;
     33 
     34         static final int RADIUS = 100;
     35 
     36         static final boolean HAS_INITIAL_IMPULSE = true;
     37         static final boolean HAS_GRAVITY = true;
     38         static final boolean HAS_FRICTION = false;
     39         static final boolean HAS_EDGES = true;
     40 
     41         static final boolean STICKY_FINGERS = true;
     42 
     43         static final float MAX_SPEED = 5000f;
     44 
     45         static final float RANDOM_IMPULSE_PROB = 0.001f;
     46 
     47         public static class World {
     48             public static final float PX_PER_METER = 100f;
     49             public static final float GRAVITY = 500f;
     50             public static class Vec {
     51                 float x;
     52                 float y;
     53                 public Vec() {
     54                     x = y = 0;
     55                 }
     56                 public Vec(float _x, float _y) {
     57                     x = _x;
     58                     y = _y;
     59                 }
     60                 public Vec add(Vec v) {
     61                     return new Vec(x + v.x, y + v.y);
     62                 }
     63                 public Vec mul(float a) {
     64                     return new Vec(x * a, y * a);
     65                 }
     66                 public Vec sub(Vec v) {
     67                     return new Vec(x - v.x, y - v.y);
     68                 }
     69                 public float mag() {
     70                     return (float) Math.hypot(x, y);
     71                 }
     72                 public Vec norm() {
     73                     float k = 1/mag();
     74                     return new Vec(x*k, y*k);
     75                 }
     76                 public String toString() {
     77                     return "(" + x + "," + y + ")";
     78                 }
     79             }
     80             public static class Body {
     81                 float m, r;
     82                 Vec p = new Vec();
     83                 Vec v = new Vec();
     84                 LinkedList<Vec> forces = new LinkedList<Vec>();
     85                 LinkedList<Vec> impulses = new LinkedList<Vec>();
     86                 public Body(float _m, Vec _p) {
     87                     m = _m;
     88                     p = _p;
     89                 }
     90                 public void applyForce(Vec f) {
     91                     forces.add(f);
     92                 }
     93                 public void applyImpulse(Vec f) {
     94                     impulses.add(f);
     95                 }
     96                 public void clearForces() {
     97                     forces.clear();
     98                 }
     99                 public void removeForce(Vec f) {
    100                     forces.remove(f);
    101                 }
    102                 public void step(float dt) {
    103                     p = p.add(v.mul(dt));
    104                     for (Vec f : impulses) {
    105                         v = v.add(f.mul(dt/m));
    106                     }
    107                     impulses.clear();
    108                     for (Vec f : forces) {
    109                         v = v.add(f.mul(dt/m));
    110                     }
    111                 }
    112                 public String toString() {
    113                     return "Body(m=" + m + " p=" + p + " v=" + v + ")";
    114                 }
    115             }
    116             LinkedList<Body> mBodies = new LinkedList<Body>();
    117             public void addBody(Body b) {
    118                 mBodies.add(b);
    119             }
    120 
    121             public void step(float dt) {
    122                 for (Body b : mBodies) {
    123                     b.step(dt);
    124                 }
    125             }
    126         }
    127 
    128 
    129         TimeAnimator mAnim;
    130         World mWorld;
    131         ImageView mBug;
    132         View mShowDebugView;
    133         HashMap<Integer, World.Vec> mFingers = new HashMap<Integer, World.Vec>();
    134         World.Body mBody;
    135         World.Vec mGrabSpot;
    136         int mGrabbedPointer = -1;
    137 
    138         public BouncyView(Context context, AttributeSet as) {
    139             super(context, as);
    140 
    141             /*
    142             setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
    143                 @Override
    144                 public void onSystemUiVisibilityChange(int visibility) {
    145                     if (visibility == View.STATUS_BAR_VISIBLE) {
    146                         ((Activity)getContext()).finish();
    147                     }
    148                 }
    149             });
    150             */
    151 
    152             setBackgroundColor(0xFF444444);
    153 
    154             mBug = new ImageView(context);
    155             mBug.setScaleType(ImageView.ScaleType.MATRIX);
    156             addView(mBug, new ViewGroup.LayoutParams(
    157                         ViewGroup.LayoutParams.WRAP_CONTENT,
    158                         ViewGroup.LayoutParams.WRAP_CONTENT));
    159 
    160             if (DEBUG) {
    161                 Button b = new Button(getContext());
    162                 b.setText("Debugzors");
    163                 b.setBackgroundColor(0); // very hard to see! :)
    164                 b.setOnClickListener(new View.OnClickListener() {
    165                     @Override
    166                     public void onClick(View v) {
    167                         setDebug(!mShowDebug);
    168                     }
    169                 });
    170                 addView(b, new FrameLayout.LayoutParams(
    171                             ViewGroup.LayoutParams.WRAP_CONTENT,
    172                             ViewGroup.LayoutParams.WRAP_CONTENT,
    173                             Gravity.TOP|Gravity.RIGHT));
    174             }
    175         }
    176 
    177         public void setDebug(boolean d) {
    178             if (d != mShowDebug) {
    179                 if (d) {
    180                     mShowDebugView = new DebugView(getContext());
    181                     mShowDebugView.setLayoutParams(
    182                         new ViewGroup.LayoutParams(
    183                             ViewGroup.LayoutParams.MATCH_PARENT,
    184                             ViewGroup.LayoutParams.MATCH_PARENT
    185                         ));
    186                     addView(mShowDebugView);
    187 
    188                     mBug.setBackgroundColor(0x2000FF00);
    189                 } else {
    190                     if (mShowDebugView != null) {
    191                         removeView(mShowDebugView);
    192                         mShowDebugView = null;
    193                     }
    194                     mBug.setBackgroundColor(0);
    195                 }
    196                 invalidate();
    197                 mShowDebug = d;
    198             }
    199         }
    200 
    201         private void reset() {
    202             mWorld = new World();
    203             final float mass = 100;
    204             mBody = new World.Body(mass, new World.Vec(200,200));
    205             mBody.r = RADIUS;
    206             mWorld.addBody(mBody);
    207             mGrabbedPointer = -1;
    208 
    209             mAnim = new TimeAnimator();
    210             mAnim.setTimeListener(new TimeAnimator.TimeListener() {
    211                 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
    212                     if (deltaTime > 0) {
    213                         int STEPS = 5;
    214                         final float dt = deltaTime / (float) STEPS;
    215                         while (STEPS-->0) {
    216                             mBody.clearForces();
    217 
    218                             if (HAS_INITIAL_IMPULSE) {
    219                                 // initial oomph
    220                                 if (totalTime == 0) {
    221                                     mBody.applyImpulse(new World.Vec(400000, -200000));
    222                                 }
    223                             }
    224 
    225                             if (HAS_GRAVITY) {
    226                                 // gravity points down
    227                                 mBody.applyForce(new World.Vec(0, mass * World.GRAVITY));
    228                             }
    229 
    230                             if (mGrabbedPointer >= 0) {
    231                                 World.Vec finger = mFingers.get(mGrabbedPointer);
    232                                 if (finger == null) {
    233                                     // let go!
    234                                     mGrabbedPointer = -1;
    235                                 } else {
    236                                     // never gonna let you go
    237                                     World.Vec newPos = finger.add(mGrabSpot);
    238                                     mBody.v = mBody.v.add(newPos.sub(mBody.p).mul(dt));
    239                                     mBody.p = newPos;
    240                                 }
    241                             } else {
    242                                 // springs
    243                                 // ideal Hooke's Law plus a maximum force and a minimum length cutoff
    244                                 for (Integer i : mFingers.keySet()) {
    245                                     World.Vec finger = mFingers.get(i);
    246                                     World.Vec springForce = finger.sub(mBody.p);
    247                                     float mag = springForce.mag();
    248 
    249                                     if (STICKY_FINGERS && mag < mBody.r*0.75) {
    250                                         // close enough; we'll call this a "stick"
    251                                         mGrabbedPointer = i;
    252                                         mGrabSpot = mBody.p.sub(finger);
    253                                         mBody.v = new World.Vec(0,0);
    254                                         break;
    255                                     }
    256 
    257                                     final float SPRING_K = 30000;
    258                                     final float FORCE_MAX = 10*SPRING_K;
    259                                     mag = (float) Math.min(mag * SPRING_K, FORCE_MAX); // Hooke's law
    260             //                    float mag = (float) (FORCE_MAX / Math.pow(springForce.mag(), 2)); // Gravitation
    261                                     springForce = springForce.norm().mul(mag);
    262                                     mBody.applyForce(springForce);
    263                                 }
    264                             }
    265 
    266                             if (HAS_FRICTION) {
    267                                 // sliding friction opposes movement
    268                                 mBody.applyForce(mBody.v.mul(-0.01f * mBody.m));
    269                             }
    270 
    271                             if (HAS_EDGES) {
    272                                 if (mBody.p.x - mBody.r < 0) {
    273                                     mBody.v.x = (float) Math.abs(mBody.v.x) *
    274                                         (HAS_FRICTION ? 0.95f : 1f);
    275                                 } else if (mBody.p.x + mBody.r > getWidth()) {
    276                                     mBody.v.x = (float) Math.abs(mBody.v.x) *
    277                                         (HAS_FRICTION ? -0.95f : -1f);
    278                                 }
    279                                 if (mBody.p.y - mBody.r < 0) {
    280                                     mBody.v.y = (float) Math.abs(mBody.v.y) *
    281                                         (HAS_FRICTION ? 0.95f : 1f);
    282                                 } else if (mBody.p.y + mBody.r > getHeight()) {
    283                                     mBody.v.y = (float) Math.abs(mBody.v.y) *
    284                                         (HAS_FRICTION ? -0.95f : -1f);
    285                                 }
    286                             }
    287 
    288                             if (MAX_SPEED > 0) {
    289                                 if (mBody.v.mag() > MAX_SPEED) {
    290                                     mBody.v = mBody.v.norm().mul(MAX_SPEED);
    291                                 }
    292                             }
    293 
    294                             // ok, Euler, do your thing
    295                             mWorld.step(dt / 1000f); // dt is in sec
    296                         }
    297                     }
    298                     mBug.setTranslationX(mBody.p.x - mBody.r);
    299                     mBug.setTranslationY(mBody.p.y - mBody.r);
    300 
    301                     Matrix m = new Matrix();
    302                     m.setScale(
    303                         (mBody.v.x < 0)    ? -1 : 1,
    304                         (mBody.v.y > 1500) ? -1 : 1, // AAAAAAAAAAAAAAAA
    305                         RADIUS, RADIUS);
    306                     mBug.setImageMatrix(m);
    307                     if (CENTER_DROID) {
    308                         mBug.setImageResource(
    309                             (Math.abs(mBody.v.x) < 25)
    310                                 ? R.drawable.bouncy_center
    311                                 : R.drawable.bouncy);
    312                     }
    313 
    314                     if (mShowDebug) mShowDebugView.invalidate();
    315                 }
    316             });
    317         }
    318 
    319         @Override
    320         protected void onAttachedToWindow() {
    321             super.onAttachedToWindow();
    322             setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
    323 
    324             reset();
    325             mAnim.start();
    326         }
    327 
    328         @Override
    329         protected void onDetachedFromWindow() {
    330             super.onDetachedFromWindow();
    331             mAnim.cancel();
    332         }
    333 
    334         @Override
    335         public boolean onTouchEvent(MotionEvent event) {
    336             int i;
    337             for (i=0; i<event.getPointerCount(); i++) {
    338                 switch (event.getActionMasked()) {
    339                     case MotionEvent.ACTION_DOWN:
    340                     case MotionEvent.ACTION_MOVE:
    341                     case MotionEvent.ACTION_POINTER_DOWN:
    342                         mFingers.put(event.getPointerId(i),
    343                                 new World.Vec(event.getX(i), event.getY(i)));
    344                         break;
    345 
    346                     case MotionEvent.ACTION_UP:
    347                     case MotionEvent.ACTION_POINTER_UP:
    348                         mFingers.remove(event.getPointerId(i));
    349                         break;
    350 
    351                     case MotionEvent.ACTION_CANCEL:
    352                         mFingers.clear();
    353                         break;
    354                 }
    355             }
    356             // expired pointers
    357     //        for (; i<mFingers.length; i++) {
    358     //            mFingers[i] = null;
    359     //        }
    360             return true;
    361         }
    362 
    363         @Override
    364         public boolean isOpaque() {
    365             return true;
    366         }
    367 
    368         class DebugView extends View {
    369             public DebugView(Context ct) {
    370                 super(ct);
    371             }
    372 
    373             private void drawVector(Canvas canvas,
    374                     float x, float y, float vx, float vy,
    375                     Paint pt) {
    376                 final float mag = (float) Math.hypot(vx, vy);
    377 
    378                 canvas.save();
    379                 Matrix mx = new Matrix();
    380                 mx.setSinCos(-vx/mag, vy/mag);
    381                 mx.postTranslate(x, y);
    382                 canvas.setMatrix(mx);
    383 
    384                 canvas.drawLine(0,0, 0, mag, pt);
    385                 canvas.drawLine(0, mag, -4, mag-4, pt);
    386                 canvas.drawLine(0, mag, 4, mag-4, pt);
    387 
    388                 canvas.restore();
    389             }
    390 
    391             @Override
    392             protected void onDraw(Canvas canvas) {
    393                 super.onDraw(canvas);
    394 
    395                 Paint pt = new Paint(Paint.ANTI_ALIAS_FLAG);
    396                 pt.setColor(0xFFCC0000);
    397                 pt.setTextSize(30f);
    398                 pt.setStrokeWidth(1.0f);
    399 
    400                 for (Integer id : mFingers.keySet()) {
    401                     World.Vec v = mFingers.get(id);
    402                     float x = v.x;
    403                     float y = v.y;
    404                     pt.setStyle(Paint.Style.FILL);
    405                     canvas.drawText("#"+id, x+38, y-38, pt);
    406                     pt.setStyle(Paint.Style.STROKE);
    407                     canvas.drawLine(x-40, y, x+40, y, pt);
    408                     canvas.drawLine(x, y-40, x, y+40, pt);
    409                     canvas.drawCircle(x, y, 40, pt);
    410                 }
    411                 pt.setStyle(Paint.Style.STROKE);
    412                 if (mBody != null) {
    413                     float x = mBody.p.x;
    414                     float y = mBody.p.y;
    415                     float r = mBody.r;
    416                     pt.setColor(0xFF6699FF);
    417                     RectF bounds = new RectF(x-r, y-r, x+r, y+r);
    418                     canvas.drawOval(bounds, pt);
    419 
    420                     pt.setStrokeWidth(3);
    421                     drawVector(canvas, x, y, mBody.v.x/100, mBody.v.y/100, pt);
    422 
    423                     pt.setColor(0xFF0033FF);
    424                     for (World.Vec f : mBody.forces) {
    425                         drawVector(canvas, x, y, f.x/1000, f.y/1000, pt);
    426                     }
    427                 }
    428             }
    429         }
    430     }
    431 
    432     @Override
    433     public void onStart() {
    434         super.onStart();
    435         setContentView(new BouncyView(this, null));
    436     }
    437 }
    438