Author: matt.burkhart
Date: Thu Dec 25 12:11:40 2008
New Revision: 64
Modified:
trunk/content_package/jump.humanoid.animation
trunk/content_package/run.humanoid.animation
trunk/content_package/stand.humanoid.animation
trunk/src/android/com/abb/ArticulatedEntity.java
trunk/src/android/com/abb/GameView.java
Log:
• Add animation root offsets. Updated jump animation to better reflect a
rotation around the center of mass.
• Code style clean up in GameView.java including better thread lock and
notification handling.
Modified: trunk/content_package/jump.humanoid.animation
==============================================================================
--- trunk/content_package/jump.humanoid.animation (original)
+++ trunk/content_package/jump.humanoid.animation Thu Dec 25 12:11:40 2008
@@ -1,5 +1,8 @@
+offsey_x -50
+offset_y -20
+
root 0.0 0
-root 0.75 360
+root 0.5 360
thigh_r 0.0 -45
thigh_l 0.0 -40
Modified: trunk/content_package/run.humanoid.animation
==============================================================================
--- trunk/content_package/run.humanoid.animation (original)
+++ trunk/content_package/run.humanoid.animation Thu Dec 25 12:11:40 2008
@@ -1,3 +1,6 @@
+offsey_x 0
+offset_y 0
+
root 0.0 0
thigh_r 0.0 -60
Modified: trunk/content_package/stand.humanoid.animation
==============================================================================
--- trunk/content_package/stand.humanoid.animation (original)
+++ trunk/content_package/stand.humanoid.animation Thu Dec 25 12:11:40 2008
@@ -1,3 +1,6 @@
+offsey_x 0
+offset_y 0
+
root 0.0 0
thigh_r 0.0 -40
Modified: trunk/src/android/com/abb/ArticulatedEntity.java
==============================================================================
--- trunk/src/android/com/abb/ArticulatedEntity.java (original)
+++ trunk/src/android/com/abb/ArticulatedEntity.java Thu Dec 25 12:11:40
2008
@@ -166,9 +166,22 @@
sprite_flipped_horizontal, false);
}
- // Draw children.
+ // Draw children. The root node transformation is handled specially
to
+ // rotate *around* the offset coordinates specified in the animation
file
+ // instead of the origin.
transformation.set(base_transformation);
- transformation.preRotate(joint_angle);
+ if (name.equals("root")) {
+ float offset_x = animation.getOffsetX();
+ float offset_y = animation.getOffsetY();
+ if (sprite_flipped_horizontal) {
+ offset_x = -offset_x;
+ }
+ transformation.preTranslate(-offset_x, -offset_y);
+ transformation.preRotate(joint_angle);
+ transformation.preTranslate(offset_x, offset_y);
+ } else {
+ transformation.preRotate(joint_angle);
+ }
transformation.preTranslate(0.0f, image_rect.height() - joint_size);
for (int child_index = 0; child_index < children.size();
++child_index) {
children.get(child_index).draw(
@@ -197,28 +210,34 @@
private class Animation {
public void loadFromUri(Uri uri) {
// The file format is expected to be an ASCII text file laid out
with a
- // single key-frame per line. Each line / key frame must have the
format
- // "<part name> <time in seconds> <angle>". Animations are expected
to
- // repeat exactly after the final key frame. Key frames must be
specified
- // in temporal order with respect to each track, but tracks may be
- // interleaved. Angle units are *degrees*.
+ // single key-frame per line. The first two lines must
contain "offset_x
+ // #" and "offset_y #". Each consecutive line / key frame must have
the
+ // format "<part name> <time in seconds> <angle>". Animations are
expected
+ // to repeat exactly after the final key frame. Key frames must be
+ // specified in temporal order with respect to each track, but
tracks may
+ // be interleaved. Angle units are *degrees*.
//
// For example:
- // thigh 0.0 45
- // thigh 0.5 60
- // leg 0.0 180
- // leg 0.5 170
+ // offset_x 10
+ // offset_y 0
+ // thigh 0.0 45
+ // thigh 0.5 60
+ // leg 0.0 180
+ // leg 0.5 170
String file_path = Content.getTemporaryFilePath(uri);
String[] tokens = Content.readFileTokens(file_path);
final int kLineTokenCount = 3;
Assert.assertTrue("Animation file empty.", tokens.length > 0);
- Assert.assertEquals("Animation file improperly formatted.",
- tokens.length % kLineTokenCount, 0);
+ Assert.assertEquals("Animation improperly formatted: " +
uri.toString(),
+ (tokens.length - 4) % kLineTokenCount, 0);
+
+ mOffsetX = Float.parseFloat(tokens[1]);
+ mOffsetY = Float.parseFloat(tokens[3]);
mLength = 0.0f;
- for (int index = 0; index < tokens.length; index += kLineTokenCount)
{
+ for (int index = 4; index < tokens.length; index += kLineTokenCount)
{
KeyFrame key_frame = new KeyFrame();
key_frame.time = Float.parseFloat(tokens[index + 1]);
key_frame.angle = Float.parseFloat(tokens[index + 2]);
@@ -243,6 +262,14 @@
}
}
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
public float getPartAngle(String part_name) {
ArrayList<KeyFrame> part_keyframes = mKeyFrames.get(part_name);
if (part_keyframes == null) {
@@ -288,6 +315,8 @@
}
private float mLength;
+ private float mOffsetX;
+ private float mOffsetY;
private float mTime;
private TreeMap<String, ArrayList<KeyFrame>> mKeyFrames =
new TreeMap<String, ArrayList<KeyFrame>>();
Modified: trunk/src/android/com/abb/GameView.java
==============================================================================
--- trunk/src/android/com/abb/GameView.java (original)
+++ trunk/src/android/com/abb/GameView.java Thu Dec 25 12:11:40 2008
@@ -20,6 +20,7 @@
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
@@ -38,24 +39,25 @@
public class GameView extends SurfaceView implements
SurfaceHolder.Callback {
class GameThread extends Thread {
public GameThread(SurfaceHolder surface_holder) {
- surface_holder_ = surface_holder;
+ mSurfaceHolder = surface_holder;
}
@Override
public void run() {
- while (game_ == null) {
- try {
- Thread.sleep(100); // Wait 100ms.
- } catch (InterruptedException ex) {}
- continue;
- }
+ synchronized (this) {
+ while (mGame == null && mRunning) {
+ try {
+ wait(); // Sleep thread until notification.
+ } catch (java.lang.InterruptedException ex) {
+ continue;
+ }
+ }
- synchronized (game_) {
Assert.assertEquals(
- "GameView thread must only be run once.", graphics_, null);
- graphics_ = new Graphics();
- graphics_.initialize(surface_holder_);
- game_.initializeGraphics(graphics_);
+ "GameView thread must only be run once.", mGraphics, null);
+ mGraphics = new Graphics();
+ mGraphics.initialize(mSurfaceHolder);
+ mGame.initializeGraphics(mGraphics);
}
// Since our target platform is a mobile device, we should do what
we can
@@ -73,17 +75,20 @@
//
http://blogs.sun.com/dholmes/entry/inside_the_hotspot_vm_clocks
long time = System.nanoTime();
- while (running_) {
- if (paused_) {
- try {
- Thread.sleep(100); // Wait 100ms.
- } catch (InterruptedException ex) {}
- continue;
+ while (mRunning) {
+ synchronized (this) {
+ while (mPaused && mRunning) {
+ try {
+ wait(); // Sleep thread until notification.
+ } catch (java.lang.InterruptedException ex) {
+ continue;
+ }
+ }
}
// Calculate the interval between this and the previous frame. See
note
// above regarding system timers. If we have exceeded our framerate
- // budget, sleep. TODO: Look into the cost of these clocks.
+ // budget, sleep.
long current_time = System.nanoTime();
float time_step = (float)(current_time - time) * 1.0e-9f;
time = current_time;
@@ -104,50 +109,51 @@
// other processes. This should usually only happen in the case
// something "big" is happening and we don't need / want to
starve the
// more important system threads.
- try {
- Thread.sleep(0); // Yield.
- } catch (InterruptedException ex) {}
+ yield();
}
time_step = Math.max(time_step, kMinTimeStep);
time_step = Math.min(time_step, kMaxTimeStep);
Canvas canvas = null;
try {
- synchronized (game_) {
- graphics_.beginFrame();
- game_.onFrame(graphics_, time_step);
+ synchronized (this) {
+ mGraphics.beginFrame();
+ mGame.onFrame(mGraphics, time_step);
}
} finally {
- graphics_.endFrame();
+ mGraphics.endFrame();
}
}
- graphics_.destroy();
+ mGraphics.destroy();
}
- public void setGame(Game game) {
- game_ = game;
+ synchronized public void setGame(Game game) {
+ mGame = game;
+ notifyAll();
}
- public void pause(boolean pause) {
- paused_ = pause;
+ synchronized public void pause(boolean pause) {
+ mPaused = pause;
+ notifyAll();
}
- public void surfaceChanged(SurfaceHolder surface_holder,
- int width, int height) {
- if (graphics_ != null) {
- graphics_.surfaceChanged(surface_holder, width, height);
+ synchronized public void surfaceChanged(SurfaceHolder surface_holder,
+ int width, int height) {
+ if (mGraphics != null) {
+ mGraphics.surfaceChanged(surface_holder, width, height);
}
}
- public void halt() {
- running_ = false;
+ synchronized public void halt() {
+ mRunning = false;
+ notifyAll();
}
- boolean running_ = true;
- boolean paused_ = false;
- Game game_;
- Graphics graphics_;
- SurfaceHolder surface_holder_;
+ boolean mRunning = true;
+ boolean mPaused = false;
+ Game mGame;
+ Graphics mGraphics;
+ SurfaceHolder mSurfaceHolder;
}
public GameView(Context context, AttributeSet attrs) {
@@ -157,28 +163,38 @@
}
public void setGame(Game game) {
- game_ = game;
- if (game_thread_ != null) {
- game_thread_.setGame(game);
+ mGame = game;
+ if (mGameThread != null) {
+ mGameThread.setGame(game);
}
}
/** Set up the android widget for the title screen to be displayed until
any
* key is pressed. */
public void setTitleView(TextView title_view) {
- title_view_ = title_view;
+ mTitleView = title_view;
}
/** Standard override to get key-press events. */
@Override
public boolean onKeyDown(int key_code, KeyEvent msg) {
- if (!title_view_hidden_) {
- title_view_.setText("");
- title_view_hidden_ = true;
+ if (!mTitleViewHidden) {
+ mTitleView.setText("");
+ mTitleViewHidden = true;
+ }
+
+ if (key_code == kProfileKey) {
+ if (!mProfiling) {
+ Debug.startMethodTracing(kProfilePath);
+ mProfiling = true;
+ } else {
+ Debug.stopMethodTracing();
+ mProfiling = false;
+ }
}
- synchronized (game_) {
- return game_.onKeyDown(key_code);
+ synchronized (mGame) {
+ return mGame.onKeyDown(key_code);
}
}
@@ -186,8 +202,8 @@
* off the engine or stop rotating. */
@Override
public boolean onKeyUp(int key_code, KeyEvent msg) {
- synchronized (game_) {
- return game_.onKeyUp(key_code);
+ synchronized (mGame) {
+ return mGame.onKeyUp(key_code);
}
}
@@ -196,8 +212,8 @@
@Override
public void onWindowFocusChanged(boolean window_has_focus) {
super.onWindowFocusChanged(window_has_focus);
- if (game_thread_ != null) {
- game_thread_.pause(!window_has_focus);
+ if (mGameThread != null) {
+ mGameThread.pause(!window_has_focus);
}
}
@@ -210,35 +226,39 @@
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
- game_thread_ = new GameThread(holder);
- game_thread_.setGame(game_);
- game_thread_.start();
- game_thread_started_ = true;
+ mGameThread = new GameThread(holder);
+ mGameThread.setGame(mGame);
+ mGameThread.start();
+ mGameThreadStarted = true;
}
/** Callback invoked when the surface dimensions change. */
public void surfaceChanged(SurfaceHolder surface_holder, int format,
int width, int height) {
- game_thread_.surfaceChanged(surface_holder, width, height);
+ mGameThread.surfaceChanged(surface_holder, width, height);
}
/** Callback invoked when the Surface has been destroyed and must no
longer be
* touched. */
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
- game_thread_.halt();
+ mGameThread.halt();
while (retry) {
try {
- game_thread_.join();
- game_thread_ = null;
+ mGameThread.join();
+ mGameThread = null;
retry = false;
} catch (InterruptedException e) {}
}
}
- private Game game_;
- private GameThread game_thread_;
- private boolean game_thread_started_ = false;
- private TextView title_view_;
- private boolean title_view_hidden_ = false;
+ private Game mGame;
+ private GameThread mGameThread;
+ private boolean mGameThreadStarted = false;
+ private boolean mProfiling = false;
+ private TextView mTitleView;
+ private boolean mTitleViewHidden = false;
+
+ private static final int kProfileKey = KeyEvent.KEYCODE_T;
+ private static final String kProfilePath = "abb.trace";
}