Monday, July 25, 2011

Rendering results... WAHOO!

Wow!  I can't believe how easy rendering is and the HUGE improvement I had versus the Box2D renderer.  I had it in my mind that it would be overly complicated, but it's that's not the case at all.  The results are so good that my conveyor belt actually WORKS on my phone.  That's great because I've got a couple of cool ideas for some levels using this construct.

My (simplistic) design, as mentioned in the previous post, is working great.  I created a new renderer class that is passed the the Box2D world instance.  From here, I get the list of bodies that are in the world.  I thought that I would have to iterate over all my fixtures in order to get things to appear.  That's not really the case.  Although your object may be comprised of several polygons (especially in the event that you want to a concave type object), your actual object is a single entity.  If you have a texture that 'fits over' that entity, then you are all set.  Much easier than trying to iterate over the fixture list to define individual elements!

I added some info to my user data class -- object texture, size, origin, and color.  The renderer uses these to scale textures appropriately.  When I get the real textures drawn up, it will use those instead.  I wanted to implement scaling because some of my objects don't have a predefined size at compile time (platforms have variable length, gears have variable radius, etc.)  Other objects, on the other hand, will have static sizes so handling those should have less of an impact on CPU time when I define the 'real' textures.

I added a grand total of 2 textures for my own debugging - one shape for circles and one for squares.  I also added a simple line so I can see rotation.  See the screenshot below for an example:


Observations:

1) I had defined some objects with rotation and instead of rotating the object, I rotated the fixture.  This resulted in rendering images that were out of whack with the body (a vertical platform, for example, rendered a texture horizontally).  Bad!  This was easily corrected by setting the shape's rotation to 0 and the body's .angle parameter to the desired angle.  I love quick fixes like that.

2) I need to have ordered rendering. I have some instances where objects overlap.  Sometimes the player's arm/gun is rendered before the body and sometimes it is rendered after.  You either see the gun peaking out when it is rotated or you see it 'correctly' on top of the player.  I need to correct this.  Since I'm iterating over the entire world of objects, I probably will skip this special case and have the player object render itself separately... it knows to draw the player body and then the arm next.  Skipping should be a snap because my user data includes object type info.

3) My fps on my phone on the conveyor belt map was on the order of 14 fps using the debug renderer.  Using my renderer, my fps shot up to close to the max -- 50-60 fps.

4) The screenshot above shows a capture of my game running on the desktop.  The fps says 4774.  With the debug renderer, I was getting 1/4 of that... say around 1400 fps or so.  Notably slower than what I am getting right now.  Most of the operational time was spent rendering joints (40%+).

5) This doesn't obsolete my usage of the debug renderer.  I can always run that AFTER I do my rendering to 'overlay' what the physics engine is thinking it should be displaying.  That's a pretty neat trick.

That's all for now. All in all, I'm pretty happy I got this working.  It was a big disappointment yesterday to see that the conveyor belt killed things!

Rendering

So, yesterday was a major downer with the conveyor belt failure on my phone.  I did some profiling (which is REALLY EASY using the DDMS tool in Eclipse), and the majority of the time was spent rendering / drawing the joints (43% of the time.)  To combat that, and to finally force myself to quit relying solely on the debug renderer, my goal today is to get a basic renderer up and running.

My idea is to use user data for defining texture regions references at object instantation.  Then, during rendering time, iterate over all the objects in the world and simply call their draw methods, accessed via the user data reference.

Sunday, July 24, 2011

5000+ downloads of the slide rule app

Just checked today and the Google Developer Console indicates that there have been 5000+ people that have checked out the slide rule app.  Cool! :)  Now... where's my money?!  Oh right, I released it free of charge.  Heh.

Today's achievement: a conveyor belt

I'm starting to work on my "Main Menu" screen.  My plan is to have some non-interactive game action going on in the background.  I drew up an idea to implement a conveyor belt.  I had three ideas for this and implemented two of them. 

The first (unimplemented) idea consists of creating a surface that detects collisions.  When an object has collided with that surface, an impulse is applied in the direction of the conveyor belt direction.  I think this would work, but seems kinda kludgy.

The second idea consists of creating several rotating circles (ie. gears).  Here's a picture of that implementation:

It works ok.  It does suffer from objects occasionally getting stuck between each gear.  I've got a 'slop' implemented so the gears aren't fighting each other -- it may take some tweaking to nail that down (maybe add joints or a filter type that prevents gear collisions?)

The third implementation took quite a while to figure out.  It deals with defining a chained set of objects (that I call the ChainLink) and two gears.  I took a bit of thinking to figure out an algorithm to define the chain link lengths and how you can programmatically adjust the width / height.  The biggest sticking point was a snafu I had with drawing the chain to revolve around the gears -- I was using degrees and the (poorly documented) API I was using didn't indicate that my units should have been in radians.  Whoops.  I ran into this before and was a bit frustrated that it was something as simple as that.  At any rate, here's an example of the conveyor belt I made up.


There's still a bit of slop to it, so I added a 'magic' variable that I named 'tension' that allows you to adjust the size of the gears.  This either tightens up a loose ChainLink or loosens up one that is too tight.  I've tried it on a couple of different sized gears / widths between gears and it works pretty well.

Saturday, July 23, 2011

Uncovered latent bugs

I've implemented my 'restart' & quit level menu buttons and uncovered a couple of bugs as result:

1) When switching screens, there is a potential bug in the frameworks that I patterned after Mario Zechner's game screen implementation.  Two methods are executed from the libgdx's render() method: update() and then present() -- in that order.  If the update() method logic decides to switch screens, the Game instance will do just that... and then run the new screen's present() method immediately upon returning to the render() method.  This could be a problem if the present() method is making the assumption that the update() has executed at least once.  I added a transition check in the Game class to ensure that a guaranteed transition will occur at the end of the Game's render() method.  The alternatives are to: ignore this behavior (most screens probably don't care) or encode the actual transition in the Screen's present() method.

2) My Level class was braindead.  Some levels included push buttons, doors, and wire lists that are instantiated when the Level class is instantiated.  When I reset the level, the lists were updated without being destroyed before hand.  Oops.  The net result was that after several instances of resetting the level, I'd get a native code error while trying to look up a prismatic joint translation.  In essence, the lists were maintaining references to dead objects (my world object is destroyed and recreated at every reset).  Yeah, that's classically what is known as a "BAD IDEA (tm)".  At any rate.  FIXED.  I added a dispose method to the parent Level class to remind me that I need to nuke those objects on level exit / reset.

Popup Menu first pass implemented

I spent the greater part of today messing around with getting a pop-up menu going and have the first pass done.  I also decided to bite the bullet (to a degree) and implement some placeholder graphics until I get far enough along where I can either spend more time working on better icons or hire someone to do them for me.

Here's what the pop-up menu looks like at the moment:
It consists of 6 graphics: 5 images for buttons and one for the background.  When the user presses the phone's menu button, it slides in from the left.  When the user hits the dismiss button on the screen, it slides out to the left.  Here's what I did to get this effect.

First off, I'm using the screen frameworks that I patterned after SuperJumper from Mario Zechner's libgdx demo suite.  Part of that frameworks includes states of the screens: GAME_READY, GAME_RUNNING, GAME_PAUSED, GAME_LEVEL_END, and GAME_OVER.  Under normal conditions, the state is GAME_RUNNING.  This performs updates and rendering - which currently includes the Box2d renderer and Box2d world step() functions.  When the user hits the menu button, the screen detects this and switches to the GAME_PAUSED screen.  This halts updates to the screen (but still allows the debug renderer to execute - which keeps the background in view).  Further, it's during this state that I allow the pop-menu renderer to execute.

The popup menu is a separate class that I named InGameMenu.  It contains a 2d scene stage, 5 buttons, a background and a group.  All the button and image items are added to a BoundGroup.  The BroundGroup acts kinda like a panel or layout.  Everything that it contains is locally referenced, and the group itself is referenced from the world coordinates.  Therefore, I can stick a button at 0,0, and then move the group itself around without having to worry about updating the button.  Pretty cool.

The popmenu also contains some simple state information indicating whether it is animating or not.  I create it such that it is off to the left side of the screen.  When it activates, the render() method simply increments the group's x parameter until it hits 0 -- meaning the left side of the screen.  To deactivate it, the render() method decrements the group's x parameter until it hits -viewportwidth/2... meaning fully off the screen.

The background graphic is a 1 pixel high x 350 pixel wide image.  I set the height to match the height of the viewport and the libgdx drivers take over rescaling it to fit.

The next bit of programming will be for me to create a scrolling pane text bit within this group (or perhaps, just switch it to another group.)

Things are starting to pick up.  I really do need to implement a whole bunch of levels.  The game frameworks are pretty much set.  I've got tweaking to do, but it's getting close!

Joytouch demo

Here's a quick shot of a level with some point rendering I did to reflect where the "joytouch" areas are.  As in: joystick + touchpad = joytouch. 

On the left hand side is a right/left button.  When you are to the left of the button, the player moves left... on the right, it moves right.  On the right hand side are several buttons.   The two buttons at top are the fire #1 and fire #2.  The bottom two are jump and grapple.  The middle button rotates the gun around its axis.  It works, but I'm not 100% happy with how the gun moves.  I've got a couple of different ideas in mind to see if I can come up with something better.



Today I'll be working on getting my pop-up menu working.  I'll be using the UI controls that have recently been added to libgdx.  I want to get a simple toggle button working and text scrolling.  We'll see how it goes.

Friday, July 22, 2011

The Trance is the Motion

I've got player motion happening on my HTC Incredible now.  The left-hand side of the screen contains the left/right motion for the player.  The right-hand side of the screen contains rotate-left/rotate-right for the gun arm.  Along the top are two push buttons for fire #1 and fire #2, and along the bottom are two push buttons for jump and grapple engage.  Pretty cool.  Things are really starting to come together.

I *really* need to have a transparent overlay of some sort to indicate where the buttons and controls are.  I let my youngest son try the game and he had a tough time understanding what he was pressing.  However, I don't think I'll be able to do anything with this until I get to implementing sprites & textures.  Still, I should probably do *something* simple even if it is just some circle rendering.  Hrm.

Next up will be creating a pop-up menu that displays when the user hits the menu button.  This will need to suspend operations of the game and allow the user to restart or exit.

Box2D game update

With a little luck, I'll be able to hit my game pretty hard over the next few days.  I've got it to the point where I've ported it to my HTC Incredible.  The INITIAL stages at any rate.  I need to figure out how I'm going to handle the controls and how to actually perform user interfacing. 

The first swag I made hasn't gone so well -- I wanted to implement left/right motion with the user touching the left hand side of the screen and a jump button on the right.  The touch areas I've defined don't really match up with what's going on.  I believe it has to do with how the touch is interpreted versus how the point is reported by libgdx.  I *think* I'll need to unproject the point based on the Orthographic Camera to get an accurate position.  Stay tuned.


In other news, this is a really good post on separating your logic from your rendering.  It's given me pause to determine what my 'native' screen size is going to be.

Slide rule update

Just a quick note... I'm only 97 installs away from breaking the 5000 mark.  Woot!  People seem to be more curious about slide rules than anything else.  Of those (nearly) 5000 installs, only about 50% have kept the installation.

Agreed.  The slide rule is a kinda neat, but not very practical, tool.

Monday, July 11, 2011

Life intervenes

For the past two days I've been reduced to tearing down an old shed on my property in preparation for a brand new shed to take its place.  I've only been able to do coding today.  But, I've been pretty productive.

I worked on two primary items, both related.  The first was to get the latest nightlies of libgdx which now include some UI interfaces such as buttons, sliders, panes, windows, etc.  I need these in order to create my user interface.  I spent the earlier part of today working on understanding how all of that stuff goes together.  Mario Z. has things set up pretty damn nice in that you can use 'skins' to alter the look of your stuff.  You do this by referencing an XML file that in turn references a "ninepatch" .png file.  Ninepatch, simply stated, is a set of 9 textures that constitute the area of an expandable button.  You've go the four corners, which are typically rounded.  Then there are the four orthogonal directions.  Finally, you have the center piece.  This makes a total of 9 parts.  They come together and allow buttons (and related graphical items) to look 'nice' when their overall size changes.  I expanded what MZ had by adding a 'transparent' pane background for my level select simply by updating a few lines in the XML file.  COOL!

The second item I worked on was getting my level select screen implemented.  This utilized Java's 'reflection' ability.  First, my LevelSelect screen accepts an array of strings that identify the fully qualified class name for a level... such as "com.mypackage.levels.level1".  The elements of this array are then used as the button names created by the UI above.  Using reflection, I'm able to take the name of the element and create an instance of that class at run-time, depending on which button the user selected.

The code looks like this:

mGame.setScreen(new PlayScreen(mGame,(Level) Class.forName(button.name).newInstance()));

This spawns a new PlayScreen using a reference to the game instance and creating / referencing the level instance the player has chosen.

One of the neat things I think I'll be able to do with the UI panes is make them slide in and out of view when the player hits the menu button.  I can adjust the pane location programmatically and use a state machine to do the animation / determine when the menu should be brought it.

Saturday, July 9, 2011

You're grounded!

I spent time last night working on the motion of the player body in my game last night.  I wanted the effect of moving him around to be that when he is grounded, he moves at a certain speed.  If he jumps and is moving, he continues at that speed in the x-direction and if the player tries to move him while airborne, it's possible but the motion is minimal.  I also wanted quick left / right motions if the body was on the ground.

To achieve this, I have a sensor fixture in the form of a circle on the bodies lower half.  I got this idea from Mario Z.'s physics demo doing something similar.  I use the contact listener and a counter to detect when the sensor is touching or not touching something.  In other words, when a beginContact event occurs for the sensor, it ratchets up a counter.  When an endContact event occurs, it ratchets the counter down.  When the counter reaches 0, the body is considered to be airborne.  This works out pretty well.

Body motion is checked every iteration and its velocity is adjusted if the user is pressing the left or right button.  If the body is airborne, the amount of the velocity is reduced.  If the user is not pressing a motion button and the body is grounded, it immediately stops.  Else, if it is airborne, no change to the velocity is made.  This is working out pretty well. 

Oh yeah, the player presses a button to jump.  This causes an upward impulse to be applied to the body.  If the body is in the air, then the jump button is ignored.  Easy peasy.

This is working out really well, but I need to do some adjustment to the friction when the character flies across the screen at a high rate (such as leaping off a high ledge).  I'm going to attempt to adjust the amount of friction as a persistence from being grounded -- the first short bit after coming down from being airborne will have a low friction then friction will be increased back to normal levels.  I need to play with things to see how that looks and feels.

Friday, July 8, 2011

Slow but steady

I've been making slow but steady progress on the game.  Since I've got a good chunk of the components defined and the majority of operations working, I decided to commit some time to making levels.  The levels range from easy / introductory type stuff to more complicated / tricky solutions.  After about two hours of effort last night, I now have  20 levels defined.  Ideally like to come up with about a hundred (if not more).

Today's main accomplishment was refactoring.  Since things are going to be level based, I needed a frameworks supporting this as well as the capability to have various menus.  The frameworks I created was derived after studying how Mario Zechner (libgdx guru) implemented his SuperJumper.  The concept is fairly simple.  You have two primary objects: the game object and screen objects.  The game object is the ApplicationListener of libgdx and kicks off the very first screen.  The screen objects are really simplified ApplicationListeners themselves, but controlled from the context of the game object.  They have a lot of the same methods that you find in the ApplicationListener.  Rather than having a single render() method, each screen has an update() and present() method, which are called from the game object's render() method.  The update() is for game engine operations while present is for doing all your graphical work.

In this way, you have a separate screen for each activity you want to implement: main screen, help screen, splash screen, game play screen, and so on.  At the moment, I only have a PlayScreen, but at least I have the frameworks in place to allow for the multiple screens you see in your typical games.

Thursday, July 7, 2011

Grapple gun & raycasting

Grapple gun

I implemented the grapple gun as mentioned below and it works semi-perfectly.  I discovered that joint lists are independent, whereas I assumed they'd be inter-related.  Iterating through the player body only gives me the joint for the gun.  For some reason, I expected to see the joint between the gun and the grappled object.  Not so!

I don't limit the gun's range of motion (it runs from 0..360) and have given it a HUUGE torque.  Much hilarity ensues when you grapple an object and then try to move the object through the player body.  Jumps and leaps of enormous proportions!

Raycasting

The raycasting took a little while to figure out -- I had to break out the code for the Ray-Tracing that is in the Box2D testbed demo.  First off, I had to implement this via a callback giving the start and end points of a ray projected out from the gun.  Easy enough.  Just have to do some rotations and transforms to get the ray to start where the gun is situated.  The next part was a learning experience... I was able to grapple through walls which didn't make sense.

The reason behind this is that the callback you supply is not guaranteed to return the nearest object the ray encounters.  You have to set it up to return a fractional amount as an indicator to the caller.  This clips the ray to that point.  If there is an object further out that hasn't been reported, it will not be reported.  If there is an object closer that hasn't been reported, the callback should iterate again with that object, at which point you again return a fractional amount to clip things.  Note -- this is only if you want to locate the nearest object.  Other return values will get you different results.

See below for the player / gun body grappling a circle body.

Box2D contact listener vs solver: The Showdown

I noticed something unusual as I was testing out my map today.  I was debugging my contact listener's beginContact() method, displaying the velocity that was being reported as an object struck a pad object.  If you've been following this blog, you'll know that I take the contacts and place them in an array for later processing.  I then displayed the velocity of the object during the array processing step. THE VELOCITIES FOR THAT SAME OBJECT WERE VERY DIFFERENT.

This was not expected.  I knew that the object may have been traveling fast enough where it may have contacted the ground below the pad and attribute the difference to that.  However, the net effect I wanted was for the velocity to remain unchanged.  Upon further investigation of the Box2D physics, I saw that its Collide() method (which calls the contact listener), executes BEFORE the Solve() method.  In other words, the contact is detected and the object's speed is valid at that point of impact.  However, the solver comes along and adjusst the velocity of the object before my game engine's processing step is able to do anything about it.  Oh yeah?!  We'll see ABOUT THAT!  Heh.

To get around this, I added a velocity vector to my processing array so that I could record what the velocity was at the point of impact.  Things are now happening the way I want them too.  Woot!

Grapple gun and other stuff

Grapple Gun:

The next thing on the list to do is to make a grapple gun that can move objects around in my world.  The idea I have in mind is to take the angle of the 'gun' object, shoot a ray out from it and determine if it hits an object.  If it hits a dynamic object that is 'grapple-able' (value configured via user data settings), project it out a certain distance away from the grapple gun to serve as an anchor point and then create a revolute joint between it and the gun.  When the user decides to drop it, simply destroy the joint.  The object should stay with the facing of the gun due to the joint. 

There are a couple of unknowns -- whether the libgdx Box2D port supports raycasting (if it didn't, that would suck) and whether I need to have a joint as part of a fixture or if it's okay to let it hang in the air (I think it should be ok, but I need to experiment.)

Other stuff:

I learned yesterday that the libgdx Box2D does NOT support contacts based on bodies (see section 9.3 of the Box2d manual.)  You cannot do a myBody.getContactList().  The closest you can get is iterating over all of the world's contacts via myWorld.getContactList().  That sucks.  To work around this, I implemented some contact array lists associated with specific objects whose elements either get removed from the list when acted upon or the endContact() method gets executed, otherwise they get re-checked on the next update iteration of the game step.

Wednesday, July 6, 2011

A door has been opened on the Box2D project

I spent the day messing around with prismatic joints and have created a button, a door, and a wire control.  The button is a simple prismatic joint that is jointed with the bottom edge of my world's arena (hence the joint line you see in the debug rendering).  I give it a vector that indicates you need to push down on it to activate.  I made this as a class that contains some hysteresis and other modifiers that can potentially be affected by other elements in the game.  It has a small motor defined to act as the 'spring' up (ie. button not pushed).  Also included is a method that updates the button state -- it checks the translation to see if the button is pressed or not.

The door is very similar, but instead contains the 'openSesame' and 'closeSesame' methods.  The motor in this case is always pushing out initially.  When the openSesame method is invoked, the motor speed is reversed and the door opens up.  When the closeSesame method is invoked, the motor speed is positive and the door closes.  The graphics below demonstrate a retractable ledge in the upper left corner using this concept (button is in the mid-right portion).

The final component is the wire control.  It's the "go between" for the button and the door.  It is created and passed references to controlling buttons and controlled doors.  Its update() method is invoked on a periodic basis.  If it detects that its button is pressed, the corresponding door is then opened.  Pretty straightforward.  The screenshots below also show a couple of objects I made to kick around in the world: a box and a ball.


Today's Box2D fun will be Prismatic Joints

Did some reading on the Box2D site and discovered that a good way to get a pressure plate / push button effect is to use prismatic joints.  I added a quick prismatic joint (via a custom push button class) to my test arena and it worked great -- just needs some tweaking to prevent bouncing, etc.  The next step will be to tie the opening / closing of a door to the button being depressed.

With all of the experimentation and exploring I've been doing lately, I've thought about maintaining all my finds / tips / tricks in a cookbook format to act as a companion to the Box2D manual.  Sure, there's the demos that are included, but it would be nice to have some written text so you don't have to work at deciphering how to implement a particular activity.

Monday, July 4, 2011

Box2D progress

Here are the things I picked up today:

- Mercurial / TortoiseHG installed
- Assembla repo created
- Learned how get Mercurial to store my Assembla password:

1) From the Hg workbench right-click the project > settings > global settings tab > edit file.  Enter the following:

[extensions]
mercurial_keyring=

Hit save.
2) repo settings > edit file

Set the https URL to https://username@repo.path.com

My hgrc looks something like this:

[paths]
default = https://my_assembla_user_name@hg.assembla.com/ProjectName

- Learned that the libgdx .processEvents method (which implements calls to keyDown, etc.) is executed prior to render().  So, it should be safe to modify Box2D world objects (create, delete, etc.) from the context of a keypress, since the step() method is executed from within the render() method.

- Learned that the BeginContact() method of the libgdx Box2D implementation is executed in the context of the step() method.  It is NOT safe / allowed to modify world objects from within BeginContact.  Since I want to create / delete / whatever as a result of contacts, I created an ArrayList that is modified based on contact.  Then, after the step() resolves, this list is iterated through and actions are performed.  I maintain separate lists depending on the type of actions I want to perform. 
For example, I have one list that operates on bullets hitting objects.  I have another list that operates on special objects interacting with other objects -- the operations of which are completely different than what happens when bullets are resolved.

In my render() method, following step(), I loop on the list not being empty, and simply removing one element at a time.  Since the libgdx mainloop() is single-threaded, operating on these lists shouldn't need to be synchronized and I don't need any sync wrapper methods / protected methods for list operations.


- Discovered a stoopid mistake where I did a shape.dispose() on a shape attached to a body that had been created in the world.  The net result was that my application would crash and I'd get a "pure virtual function called".  Das ist bad, ja!

- Discovered another stoopid mistake where I destroyed a body in one resolution step without updating locally maintained object references.  Then, as I attempted to modify those same (now destroyed) objects, I got to watch the application crash hard again in the middle of a Box2D step.  Yuck.

- Got a better grasp of some simple vector methods.  To get a vector's normal, swap the x & y coordinates, and negate the new y-coordinate.  This effectively translates the vector 90 degrees anticlockwise.  Doing this again rotates that vector by another 90 degress.  Two more times brings you back to the original position.  Certainly information you can use to impress the ladies.

- Got a better understanding of collision filtering.  Bodies that have the same positive group index number always collide.  Bodies that have the same NEGATIVE group index number NEVER collide -- they just pass through each other.  This is helpful if you have a bullet object that you don't want to hit the player body if he is aiming down at his feet.

- Bodies that are connected via joints affect the overall velocity of the jointed body.  In my game, I teleport around objects.  Turns out that when I did my transform to move the object and affect the velocity, I only did it to one of the objects in a jointed system.  The velocity ended up being slower than I predicted until I realized my secondary body was affecting things too.

- Had the assumption that a dynamic fixture with zero density was a sensor.  Not so.  You have to explicitly set the sensor flag to true.  My player body now sinks into the floor because the circle bottom sensor does not evoke any collision contacts.  This is what I originally expected, but figured I must have misunderstood something. 

- I've got the rudiments of my game working.  I need to clean some things up and get rid of some glitches due to my processing multiple contacts in the bullet list described above.  After that, I'll be expanding my test arena to include some kinematic bodies.

Some major refactoring is in order, but I'm holding off with doing that after I've got some more of the game ideas implemented and understood -- things like buttons, doors, anti-grav fields, etc.

Saturday, July 2, 2011

Slide Rule stats

Just thought I'd make a quick post about the current Slide Rule stats:

- I've got one error report, which I believe is automagically generated.  There apparently was a freeze on a platform "OTHERS" relating to "ANR._keyDispatchingTimedOut".  The user did not file an error message so no clue what's up.

- I've had a total of 3927 installs.  The number of 'active' installs is currently riding at 2137 (54% installations).

- Android 2.2 based devices rule at 66.5% of all installs

- Motorola Droid X is the most popular device followed by the HTC Desire HD.  My Droid Incredible is 6th on the list.  Surprisingly, the Samsung Galaxy Tab is 5th on the list.

- The US, UK, and Australia are the top three installation locations.  India comes in at #4.

- English is the top language followed by German.

- Installations continue to climb at a slow and steady rate.