GTViewpublic class GTView extends android.view.SurfaceView implements SurfaceHolder.CallbackThe main View of the GlobalTime Activity. |
Fields Summary |
---|
private static final TimeZone | UTC_TIME_ZONEA TimeZone object used to compute the current UTC time. | private static final float[] | SUNLIGHT_COLORThe Sun's color is close to that of a 5780K blackbody. | private static final float | EARTH_INCLINATIONThe inclination of the earth relative to the plane of the ecliptic
is 23.45 degrees. | private static final int | SECONDS_PER_DAYSeconds in a day | private static final boolean | PERFORM_DEPTH_TESTFlag for the depth test | private static final boolean | USE_RAW_OFFSETSUse raw time zone offsets, disregarding "summer time." If false,
current offsets will be used, which requires a much longer startup time
in order to sort the city database. | private static final Annulus | ATMOSPHEREThe earth's atmosphere. | private static final int | SPHERE_LATITUDESThe tesselation of the earth by latitude. | private static int | SPHERE_LONGITUDESThe tesselation of the earth by longitude. | private static Sphere | worldFlatA flattened version of the earth. The normals are computed identically
to those of the round earth, allowing the day/night lighting to be
applied to the flattened surface. | private android.opengl.Object3D | mWorldThe earth. | private PointCloud | mLightsGeometry of the city lights | boolean | mInitializedTrue if the activiy has been initialized. | private boolean | mAlphaKeySetTrue if we're in alphabetic entry mode. | private EGLContext | mEGLContext | private EGLSurface | mEGLSurface | private EGLDisplay | mEGLDisplay | private EGLConfig | mEGLConfig | GLView | mGLView | private float | mRotAngle | private float | mTiltAngle | private float | mRotVelocity | private float | mWrapX | private float | mWrapVelocity | private float | mWrapVelocityFactor | private boolean | mDisplayAtmosphere | private boolean | mDisplayClock | private boolean | mClockShowing | private boolean | mDisplayLights | private boolean | mDisplayWorld | private boolean | mDisplayWorldFlat | private boolean | mSmoothShading | private String | mCityName | private List | mClockCities | private List | mCityNameMatches | private List | mCities | private long | mClockFadeTime | private android.view.animation.Interpolator | mClockSizeInterpolator | private int | mCityIndex | private Clock | mClock | private boolean | mFlyToCity | private long | mCityFlyStartTime | private float | mCityFlightTime | private float | mRotAngleStart | private float | mRotAngleDest | private float | mTiltAngleStart | private float | mTiltAngleDest | private android.view.animation.Interpolator | mFlyToCityInterpolator | private static int | sNumLights | private static int[] | sLightCoords | private float[] | mClipPlaneEquation | private float[] | mLightDir | Calendar | mSunCal | private int | mNumTriangles | private long | startTime | private static final int | MOTION_NONE | private static final int | MOTION_X | private static final int | MOTION_Y | private static final int | MIN_MANHATTAN_DISTANCE | private static final float | ROTATION_FACTOR | private static final float | TILT_FACTOR | private float | mMotionStartX | private float | mMotionStartY | private float | mMotionStartRotVelocity | private float | mMotionStartTiltAngle | private int | mMotionDirection | private boolean | mPaused | private boolean | mHaveSurface | private boolean | mStartAnimating | private static final int | INVALIDATE | private static final int | ONE_MINUTE | private final android.os.Handler | mHandlerControls the animation using the message queue. Every time we receive
an INVALIDATE message, we redraw and place another message in the queue. |
Constructors Summary |
---|
public GTView(android.content.Context context)Set up the view.
super(context);
getHolder().addCallback(this);
getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
startTime = System.currentTimeMillis();
mClock = new Clock();
startEGL();
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
|
Methods Summary |
---|
private boolean | atEndOfTimeZone(int incr)Returns true if there is another city within the current time zone
that is the given increment away from the current city.
if (mCities.size() <= 1) {
return true;
}
float offset = getOffset(mCities.get(mCityIndex));
int nindex = (mCityIndex + mCities.size() + incr) % mCities.size();
if (tzEqual(getOffset(mCities.get(nindex)), offset)) {
return false;
}
return true;
| private java.io.InputStream | cache(java.io.InputStream is)
int nbytes = is.available();
byte[] data = new byte[nbytes];
int nread = 0;
while (nread < nbytes) {
nread += is.read(data, nread, nbytes - nread);
}
return new ByteArrayInputStream(data);
| private void | clearCityMatches()Clears the current matching prefix, while keeping the focus on
the current city.
// Determine the global city index that matches the current city
if (mCityNameMatches.size() > 0) {
City city = mCityNameMatches.get(mCityIndex);
for (int i = 0; i < mClockCities.size(); i++) {
City ncity = mClockCities.get(i);
if (city.equals(ncity)) {
mCityIndex = i;
break;
}
}
}
mCityName = "";
mCityNameMatches.clear();
mCities = mClockCities;
goToCity();
| private void | computeSunDirection()Computes the vector from the center of the earth to the sun for a
particular moment in time.
mSunCal.setTimeInMillis(System.currentTimeMillis());
int day = mSunCal.get(Calendar.DAY_OF_YEAR);
int seconds = 3600 * mSunCal.get(Calendar.HOUR_OF_DAY) +
60 * mSunCal.get(Calendar.MINUTE) + mSunCal.get(Calendar.SECOND);
day += (float) seconds / SECONDS_PER_DAY;
// Approximate declination of the sun, changes sinusoidally
// during the year. The winter solstice occurs 10 days before
// the start of the year.
float decl = (float) (EARTH_INCLINATION *
Math.cos(Shape.TWO_PI * (day + 10) / 365.0));
// Subsolar latitude, convert from (-PI/2, PI/2) -> (0, PI) form
float phi = decl + Shape.PI_OVER_TWO;
// Subsolar longitude
float theta = Shape.TWO_PI * seconds / SECONDS_PER_DAY;
float sinPhi = (float) Math.sin(phi);
float cosPhi = (float) Math.cos(phi);
float sinTheta = (float) Math.sin(theta);
float cosTheta = (float) Math.cos(theta);
// Convert from polar to rectangular coordinates
float x = cosTheta * sinPhi;
float y = cosPhi;
float z = sinTheta * sinPhi;
// Directional light -> w == 0
mLightDir[0] = x;
mLightDir[1] = y;
mLightDir[2] = z;
mLightDir[3] = 0.0f;
| public void | destroy()
stopAnimating();
stopEGL();
| private float | distance(float lat1, float lon1, float lat2, float lon2)Computes the approximate spherical distance between two
(latitude, longitude) coordinates.
lat1 *= Shape.DEGREES_TO_RADIANS;
lat2 *= Shape.DEGREES_TO_RADIANS;
lon1 *= Shape.DEGREES_TO_RADIANS;
lon2 *= Shape.DEGREES_TO_RADIANS;
float r = 6371.0f; // Earth's radius in km
float dlat = lat2 - lat1;
float dlon = lon2 - lon1;
double sinlat2 = Math.sin(dlat / 2.0f);
sinlat2 *= sinlat2;
double sinlon2 = Math.sin(dlon / 2.0f);
sinlon2 *= sinlon2;
double a = sinlat2 + Math.cos(lat1) * Math.cos(lat2) * sinlon2;
double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return (float) (r * c);
| private void | drawAtmosphere(GL10 gl)Draws the atmosphere.
gl.glDisable(GL10.GL_LIGHTING);
gl.glDisable(GL10.GL_CULL_FACE);
gl.glDisable(GL10.GL_DITHER);
gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glShadeModel(mSmoothShading ? GL10.GL_SMOOTH : GL10.GL_FLAT);
// Draw the atmospheric layer
float tx = mGLView.getTranslateX();
float ty = mGLView.getTranslateY();
float tz = mGLView.getTranslateZ();
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslatef(tx, ty, tz);
// Blend in the atmosphere a bit
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
ATMOSPHERE.draw(gl);
mNumTriangles += ATMOSPHERE.getNumTriangles();
| private void | drawCityLights(GL10 gl, float brightness)Draws the city lights, using a clip plane to restrict the lights
to the night side of the earth.
gl.glEnable(GL10.GL_POINT_SMOOTH);
gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glDisable(GL10.GL_LIGHTING);
gl.glDisable(GL10.GL_DITHER);
gl.glShadeModel(GL10.GL_FLAT);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glPointSize(1.0f);
float ls = lerp(0.8f, 0.3f, brightness);
gl.glColor4f(ls * 1.0f, ls * 1.0f, ls * 0.8f, 1.0f);
if (mDisplayWorld) {
mClipPlaneEquation[0] = -mLightDir[0];
mClipPlaneEquation[1] = -mLightDir[1];
mClipPlaneEquation[2] = -mLightDir[2];
mClipPlaneEquation[3] = 0.0f;
// Assume we have glClipPlanef() from OpenGL ES 1.1
((GL11) gl).glClipPlanef(GL11.GL_CLIP_PLANE0,
mClipPlaneEquation, 0);
gl.glEnable(GL11.GL_CLIP_PLANE0);
}
mLights.draw(gl);
if (mDisplayWorld) {
gl.glDisable(GL11.GL_CLIP_PLANE0);
}
mNumTriangles += mLights.getNumTriangles()*2;
| private void | drawClock(android.graphics.Canvas canvas, long now, int w, int h, float lerp)Draws the clock.
float clockAlpha = lerp(0.0f, 0.8f, lerp);
mClockShowing = clockAlpha > 0.0f;
if (clockAlpha > 0.0f) {
City city = mCities.get(mCityIndex);
mClock.setCity(city);
mClock.setTime(now);
float cx = w / 2.0f;
float cy = h / 2.0f;
float smallRadius = 18.0f;
float bigRadius = 0.75f * 0.5f * Math.min(w, h);
float radius = lerp(smallRadius, bigRadius, lerp);
// Only display left/right arrows if we are in a name search
boolean scrollingByName =
(mCityName.length() > 0) && (mCities.size() > 1);
mClock.drawClock(canvas, cx, cy, radius,
clockAlpha,
1.0f,
lerp == 1.0f, lerp == 1.0f,
!atEndOfTimeZone(-1),
!atEndOfTimeZone(1),
scrollingByName,
mCityName.length());
}
| protected void | drawOpenGLScene()Draws the 3D layer.
long now = System.currentTimeMillis();
mNumTriangles = 0;
EGL10 egl = (EGL10)EGLContext.getEGL();
GL10 gl = (GL10)mEGLContext.getGL();
if (!mInitialized) {
init(gl);
}
int w = getWidth();
int h = getHeight();
gl.glViewport(0, 0, w, h);
gl.glEnable(GL10.GL_LIGHTING);
gl.glEnable(GL10.GL_LIGHT0);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glFrontFace(GL10.GL_CCW);
float ratio = (float) w / h;
mGLView.setAspectRatio(ratio);
mGLView.setTextureParameters(gl);
if (PERFORM_DEPTH_TEST) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
} else {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
}
if (mDisplayWorldFlat) {
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-1.0f, 1.0f, -1.0f / ratio, 1.0f / ratio, 1.0f, 2.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -1.0f);
} else {
mGLView.setProjection(gl);
mGLView.setView(gl);
}
if (!mDisplayWorldFlat) {
if (mFlyToCity) {
float lerp = (now - mCityFlyStartTime)/mCityFlightTime;
if (lerp >= 1.0f) {
mFlyToCity = false;
}
lerp = Math.min(lerp, 1.0f);
lerp = mFlyToCityInterpolator.getInterpolation(lerp);
mRotAngle = lerp(mRotAngleStart, mRotAngleDest, lerp);
mTiltAngle = lerp(mTiltAngleStart, mTiltAngleDest, lerp);
}
// Rotate the viewpoint around the earth
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glRotatef(mTiltAngle, 1, 0, 0);
gl.glRotatef(mRotAngle, 0, 1, 0);
// Increment the rotation angle
mRotAngle += mRotVelocity;
if (mRotAngle < 0.0f) {
mRotAngle += 360.0f;
}
if (mRotAngle > 360.0f) {
mRotAngle -= 360.0f;
}
}
// Draw the world with lighting
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, mLightDir, 0);
mGLView.setLights(gl, GL10.GL_LIGHT0);
if (mDisplayWorldFlat) {
drawWorldFlat(gl);
} else if (mDisplayWorld) {
drawWorldRound(gl);
}
if (mDisplayLights && !mDisplayWorldFlat) {
// Interpolator for clock size, clock alpha, night lights intensity
float lerp = Math.min((now - mClockFadeTime)/1000.0f, 1.0f);
if (!mDisplayClock) {
// Clock is receding
lerp = 1.0f - lerp;
}
lerp = mClockSizeInterpolator.getInterpolation(lerp);
drawCityLights(gl, lerp);
}
if (mDisplayAtmosphere && !mDisplayWorldFlat) {
drawAtmosphere(gl);
}
mGLView.setNumTriangles(mNumTriangles);
egl.eglSwapBuffers(mEGLDisplay, mEGLSurface);
if (egl.eglGetError() == EGL11.EGL_CONTEXT_LOST) {
// we lost the gpu, quit immediately
Context c = getContext();
if (c instanceof Activity) {
((Activity)c).finish();
}
}
| private void | drawWorldFlat(GL10 gl)Draws the world in a 2D map view.
gl.glDisable(GL10.GL_BLEND);
gl.glEnable(GL10.GL_DITHER);
gl.glShadeModel(mSmoothShading ? GL10.GL_SMOOTH : GL10.GL_FLAT);
gl.glTranslatef(mWrapX - 2, 0.0f, 0.0f);
worldFlat.draw(gl);
gl.glTranslatef(2.0f, 0.0f, 0.0f);
worldFlat.draw(gl);
mNumTriangles += worldFlat.getNumTriangles() * 2;
mWrapX += mWrapVelocity * mWrapVelocityFactor;
while (mWrapX < 0.0f) {
mWrapX += 2.0f;
}
while (mWrapX > 2.0f) {
mWrapX -= 2.0f;
}
| private void | drawWorldRound(GL10 gl)Draws the world in a 2D round view.
gl.glDisable(GL10.GL_BLEND);
gl.glEnable(GL10.GL_DITHER);
gl.glShadeModel(mSmoothShading ? GL10.GL_SMOOTH : GL10.GL_FLAT);
mWorld.draw(gl);
mNumTriangles += mWorld.getNumTriangles();
| private void | enableClock(boolean enabled)Fade the clock in or out.
mClockFadeTime = System.currentTimeMillis();
mDisplayClock = enabled;
mClockShowing = true;
mAlphaKeySet = enabled;
if (enabled) {
// Find the closest matching city
locateCity(false, 0.0f);
}
clearCityMatches();
| private static float | getOffset(City c)Returns the offset from UTC for the given city. If USE_RAW_OFFSETS
is true, summer/daylight savings is ignored.
return USE_RAW_OFFSETS ? c.getRawOffset() : c.getOffset();
| private void | goToCity()Animates the earth to be centered at the current city.
City city = mCities.get(mCityIndex);
float dist = distance(city.getLatitude(), city.getLongitude(),
mTiltAngle, mRotAngle - 90.0f);
mFlyToCity = true;
mCityFlyStartTime = System.currentTimeMillis();
mCityFlightTime = dist / 5.0f; // 5000 km/sec
mRotAngleStart = mRotAngle;
mRotAngleDest = city.getLongitude() + 90;
if (mRotAngleDest - mRotAngleStart > 180.0f) {
mRotAngleDest -= 360.0f;
} else if (mRotAngleStart - mRotAngleDest > 180.0f) {
mRotAngleDest += 360.0f;
}
mTiltAngleStart = mTiltAngle;
mTiltAngleDest = city.getLatitude();
mRotVelocity = 0.0f;
| private boolean | hasMatches(java.lang.String prefix)Returns true if there are cities matching the given name prefix.
for (int i = 0; i < mClockCities.size(); i++) {
City city = mClockCities.get(i);
if (nameMatches(city, prefix)) {
return true;
}
}
return false;
| private void | incrementRotationalVelocity(float incr)Increases or decreases the rotational speed of the earth.
if (mDisplayWorldFlat) {
mWrapVelocity -= incr;
} else {
mRotVelocity -= incr;
}
| private synchronized void | init(GL10 gl)Initialize OpenGL ES drawing.
mGLView = new GLView();
mGLView.setNearFrustum(5.0f);
mGLView.setFarFrustum(50.0f);
mGLView.setLightModelAmbientIntensity(0.225f);
mGLView.setAmbientIntensity(0.0f);
mGLView.setDiffuseIntensity(1.5f);
mGLView.setDiffuseColor(SUNLIGHT_COLOR);
mGLView.setSpecularIntensity(0.0f);
mGLView.setSpecularColor(SUNLIGHT_COLOR);
if (PERFORM_DEPTH_TEST) {
gl.glEnable(GL10.GL_DEPTH_TEST);
}
gl.glDisable(GL10.GL_SCISSOR_TEST);
gl.glClearColor(0, 0, 0, 1);
gl.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST);
mInitialized = true;
| private float | lerp(float a, float b, float lerp)Returns a linearly interpolated value between two values.
return a + (b - a)*lerp;
| private void | loadAssets(android.content.res.AssetManager am)Load the city and lights databases.
Locale locale = Locale.getDefault();
String language = locale.getLanguage();
String country = locale.getCountry();
InputStream cis = null;
try {
// Look for (e.g.) cities_fr_FR.dat or cities_fr_CA.dat
cis = am.open("cities_" + language + "_" + country + ".dat");
} catch (FileNotFoundException e1) {
try {
// Look for (e.g.) cities_fr.dat or cities_fr.dat
cis = am.open("cities_" + language + ".dat");
} catch (FileNotFoundException e2) {
try {
// Use English city names by default
cis = am.open("cities_en.dat");
} catch (FileNotFoundException e3) {
throw e3;
}
}
}
cis = cache(cis);
City.loadCities(cis);
City[] cities;
if (USE_RAW_OFFSETS) {
cities = City.getCitiesByRawOffset();
} else {
cities = City.getCitiesByOffset();
}
mClockCities = new ArrayList<City>(cities.length);
for (int i = 0; i < cities.length; i++) {
mClockCities.add(cities[i]);
}
mCities = mClockCities;
mCityIndex = 0;
this.mWorld = new Object3D() {
@Override
public InputStream readFile(String filename)
throws IOException {
return cache(am.open(filename));
}
};
mWorld.load("world.gles");
// lights.dat has the following format. All integers
// are 16 bits, low byte first.
//
// width
// height
// N [# of lights]
// light 0 X [in the range 0 to (width - 1)]
// light 0 Y ]in the range 0 to (height - 1)]
// light 1 X [in the range 0 to (width - 1)]
// light 1 Y ]in the range 0 to (height - 1)]
// ...
// light (N - 1) X [in the range 0 to (width - 1)]
// light (N - 1) Y ]in the range 0 to (height - 1)]
//
// For a larger number of lights, it could make more
// sense to store the light positions in a bitmap
// and extract them manually
InputStream lis = am.open("lights.dat");
lis = cache(lis);
int lightWidth = readInt16(lis);
int lightHeight = readInt16(lis);
sNumLights = readInt16(lis);
sLightCoords = new int[3 * sNumLights];
int lidx = 0;
float lightRadius = 1.009f;
float lightScale = 65536.0f * lightRadius;
float[] cosTheta = new float[lightWidth];
float[] sinTheta = new float[lightWidth];
float twoPi = (float) (2.0 * Math.PI);
float scaleW = twoPi / lightWidth;
for (int i = 0; i < lightWidth; i++) {
float theta = twoPi - i * scaleW;
cosTheta[i] = (float)Math.cos(theta);
sinTheta[i] = (float)Math.sin(theta);
}
float[] cosPhi = new float[lightHeight];
float[] sinPhi = new float[lightHeight];
float scaleH = (float) (Math.PI / lightHeight);
for (int j = 0; j < lightHeight; j++) {
float phi = j * scaleH;
cosPhi[j] = (float)Math.cos(phi);
sinPhi[j] = (float)Math.sin(phi);
}
int nbytes = 4 * sNumLights;
byte[] ilights = new byte[nbytes];
int nread = 0;
while (nread < nbytes) {
nread += lis.read(ilights, nread, nbytes - nread);
}
int idx = 0;
for (int i = 0; i < sNumLights; i++) {
int lx = (((ilights[idx + 1] & 0xff) << 8) |
(ilights[idx ] & 0xff));
int ly = (((ilights[idx + 3] & 0xff) << 8) |
(ilights[idx + 2] & 0xff));
idx += 4;
float sin = sinPhi[ly];
float x = cosTheta[lx]*sin;
float y = cosPhi[ly];
float z = sinTheta[lx]*sin;
sLightCoords[lidx++] = (int) (x * lightScale);
sLightCoords[lidx++] = (int) (y * lightScale);
sLightCoords[lidx++] = (int) (z * lightScale);
}
mLights = new PointCloud(sLightCoords);
| private void | locateCity(boolean useOffset, float offset)Locates the closest city to the currently displayed center point,
optionally restricting the search to cities within a given time zone.
float mindist = Float.MAX_VALUE;
int minidx = -1;
for (int i = 0; i < mCities.size(); i++) {
City city = mCities.get(i);
if (useOffset && !tzEqual(getOffset(city), offset)) {
continue;
}
float dist = distance(city.getLatitude(), city.getLongitude(),
mTiltAngle, mRotAngle - 90.0f);
if (dist < mindist) {
mindist = dist;
minidx = i;
}
}
mCityIndex = minidx;
| private boolean | nameMatches(City city, java.lang.String prefix)Returns true if the city name matches the given prefix, ignoring spaces.
String cityName = city.getName().replaceAll("[ ]", "");
return prefix.regionMatches(true, 0,
cityName, 0,
prefix.length());
| protected void | onDraw(android.graphics.Canvas canvas)Draws the 2D layer.
long now = System.currentTimeMillis();
if (startTime != -1) {
startTime = -1;
}
int w = getWidth();
int h = getHeight();
// Interpolator for clock size, clock alpha, night lights intensity
float lerp = Math.min((now - mClockFadeTime)/1000.0f, 1.0f);
if (!mDisplayClock) {
// Clock is receding
lerp = 1.0f - lerp;
}
lerp = mClockSizeInterpolator.getInterpolation(lerp);
// we don't need to make sure OpenGL rendering is done because
// we're drawing in to a different surface
drawClock(canvas, now, w, h, lerp);
mGLView.showMessages(canvas);
mGLView.showStatistics(canvas, w);
| public boolean | onKeyDown(int keyCode, android.view.KeyEvent event)
if (mInitialized && mGLView.processKey(keyCode)) {
boolean drawing = (mClockShowing || mGLView.hasMessages());
this.setWillNotDraw(!drawing);
return true;
}
boolean handled = false;
// If we're not in alphabetical entry mode, convert letters
// to their digit equivalents
if (!mAlphaKeySet) {
char numChar = event.getNumber();
if (numChar >= '0" && numChar <= '9") {
keyCode = KeyEvent.KEYCODE_0 + (numChar - '0");
}
}
switch (keyCode) {
// The 'space' key toggles the clock
case KeyEvent.KEYCODE_SPACE:
mAlphaKeySet = !mAlphaKeySet;
enableClock(mAlphaKeySet);
handled = true;
break;
// The 'left' and 'right' buttons shift time zones if the clock is
// displayed, otherwise they alters the rotational speed of the earthh
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mDisplayClock) {
shiftTimeZone(-1);
} else {
mClock.setCity(null);
incrementRotationalVelocity(1.0f);
}
handled = true;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (mDisplayClock) {
shiftTimeZone(1);
} else {
mClock.setCity(null);
incrementRotationalVelocity(-1.0f);
}
handled = true;
break;
// The 'up' and 'down' buttons shift cities within a time zone if the
// clock is displayed, otherwise they tilt the earth
case KeyEvent.KEYCODE_DPAD_UP:
if (mDisplayClock) {
shiftWithinTimeZone(-1);
} else {
mClock.setCity(null);
if (!mDisplayWorldFlat) {
mTiltAngle += 360.0f / 48.0f;
}
}
handled = true;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mDisplayClock) {
shiftWithinTimeZone(1);
} else {
mClock.setCity(null);
if (!mDisplayWorldFlat) {
mTiltAngle -= 360.0f / 48.0f;
}
}
handled = true;
break;
// The center key stops the earth's rotation, then toggles between the
// round and flat views of the earth
case KeyEvent.KEYCODE_DPAD_CENTER:
if ((!mDisplayWorldFlat && mRotVelocity == 0.0f) ||
(mDisplayWorldFlat && mWrapVelocity == 0.0f)) {
mDisplayWorldFlat = !mDisplayWorldFlat;
} else {
if (mDisplayWorldFlat) {
mWrapVelocity = 0.0f;
} else {
mRotVelocity = 0.0f;
}
}
handled = true;
break;
// The 'L' key toggles the city lights
case KeyEvent.KEYCODE_L:
if (!mAlphaKeySet && !mDisplayWorldFlat) {
mDisplayLights = !mDisplayLights;
handled = true;
}
break;
// The 'W' key toggles the earth (just for fun)
case KeyEvent.KEYCODE_W:
if (!mAlphaKeySet && !mDisplayWorldFlat) {
mDisplayWorld = !mDisplayWorld;
handled = true;
}
break;
// The 'A' key toggles the atmosphere
case KeyEvent.KEYCODE_A:
if (!mAlphaKeySet && !mDisplayWorldFlat) {
mDisplayAtmosphere = !mDisplayAtmosphere;
handled = true;
}
break;
// The '2' key zooms out
case KeyEvent.KEYCODE_2:
if (!mAlphaKeySet && !mDisplayWorldFlat) {
mGLView.zoom(-2);
handled = true;
}
break;
// The '8' key zooms in
case KeyEvent.KEYCODE_8:
if (!mAlphaKeySet && !mDisplayWorldFlat) {
mGLView.zoom(2);
handled = true;
}
break;
}
// Handle letters in city names
if (!handled && mAlphaKeySet) {
switch (keyCode) {
// Add a letter to the city name prefix
case KeyEvent.KEYCODE_A:
case KeyEvent.KEYCODE_B:
case KeyEvent.KEYCODE_C:
case KeyEvent.KEYCODE_D:
case KeyEvent.KEYCODE_E:
case KeyEvent.KEYCODE_F:
case KeyEvent.KEYCODE_G:
case KeyEvent.KEYCODE_H:
case KeyEvent.KEYCODE_I:
case KeyEvent.KEYCODE_J:
case KeyEvent.KEYCODE_K:
case KeyEvent.KEYCODE_L:
case KeyEvent.KEYCODE_M:
case KeyEvent.KEYCODE_N:
case KeyEvent.KEYCODE_O:
case KeyEvent.KEYCODE_P:
case KeyEvent.KEYCODE_Q:
case KeyEvent.KEYCODE_R:
case KeyEvent.KEYCODE_S:
case KeyEvent.KEYCODE_T:
case KeyEvent.KEYCODE_U:
case KeyEvent.KEYCODE_V:
case KeyEvent.KEYCODE_W:
case KeyEvent.KEYCODE_X:
case KeyEvent.KEYCODE_Y:
case KeyEvent.KEYCODE_Z:
char c = (char)(keyCode - KeyEvent.KEYCODE_A + 'A");
if (hasMatches(mCityName + c)) {
mCityName += c;
shiftByName();
}
handled = true;
break;
// Remove a letter from the city name prefix
case KeyEvent.KEYCODE_DEL:
if (mCityName.length() > 0) {
mCityName = mCityName.substring(0, mCityName.length() - 1);
shiftByName();
} else {
clearCityMatches();
}
handled = true;
break;
// Clear the city name prefix
case KeyEvent.KEYCODE_ENTER:
clearCityMatches();
handled = true;
break;
}
}
boolean drawing = (mClockShowing ||
((mGLView != null) && (mGLView.hasMessages())));
this.setWillNotDraw(!drawing);
// Let the system handle other keypresses
if (!handled) {
return super.onKeyDown(keyCode, event);
}
return true;
| public void | onPause()
mPaused = true;
stopAnimating();
stopEGL();
| public void | onResume()
mPaused = false;
startEGL();
| public boolean | onTouchEvent(android.view.MotionEvent event)Use the touchscreen to alter the rotational velocity or the
tilt of the earth.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mMotionStartX = event.getX();
mMotionStartY = event.getY();
mMotionStartRotVelocity = mDisplayWorldFlat ?
mWrapVelocity : mRotVelocity;
mMotionStartTiltAngle = mTiltAngle;
// Stop the rotation
if (mDisplayWorldFlat) {
mWrapVelocity = 0.0f;
} else {
mRotVelocity = 0.0f;
}
mMotionDirection = MOTION_NONE;
break;
case MotionEvent.ACTION_MOVE:
// Disregard motion events when the clock is displayed
float dx = event.getX() - mMotionStartX;
float dy = event.getY() - mMotionStartY;
float delx = Math.abs(dx);
float dely = Math.abs(dy);
// Determine the direction of motion (major axis)
// Once if has been determined, it's locked in until
// we receive ACTION_UP or ACTION_CANCEL
if ((mMotionDirection == MOTION_NONE) &&
(delx + dely > MIN_MANHATTAN_DISTANCE)) {
if (delx > dely) {
mMotionDirection = MOTION_X;
} else {
mMotionDirection = MOTION_Y;
}
}
// If the clock is displayed, don't actually rotate or tilt;
// just use mMotionDirection to record whether motion occurred
if (!mDisplayClock) {
if (mMotionDirection == MOTION_X) {
if (mDisplayWorldFlat) {
mWrapVelocity = mMotionStartRotVelocity +
dx * ROTATION_FACTOR;
} else {
mRotVelocity = mMotionStartRotVelocity +
dx * ROTATION_FACTOR;
}
mClock.setCity(null);
} else if (mMotionDirection == MOTION_Y &&
!mDisplayWorldFlat) {
mTiltAngle = mMotionStartTiltAngle + dy * TILT_FACTOR;
if (mTiltAngle < -90.0f) {
mTiltAngle = -90.0f;
}
if (mTiltAngle > 90.0f) {
mTiltAngle = 90.0f;
}
mClock.setCity(null);
}
}
break;
case MotionEvent.ACTION_UP:
mMotionDirection = MOTION_NONE;
break;
case MotionEvent.ACTION_CANCEL:
mTiltAngle = mMotionStartTiltAngle;
if (mDisplayWorldFlat) {
mWrapVelocity = mMotionStartRotVelocity;
} else {
mRotVelocity = mMotionStartRotVelocity;
}
mMotionDirection = MOTION_NONE;
break;
}
return true;
| private int | readInt16(java.io.InputStream is)Read a two-byte integer from the input stream.
int lo = is.read();
int hi = is.read();
return (hi << 8) | lo;
| private void | shiftByName()Shifts to the nearest city that matches the new prefix.
// Attempt to keep current city if it matches
City finalCity = null;
City currCity = mCities.get(mCityIndex);
if (nameMatches(currCity, mCityName)) {
finalCity = currCity;
}
mCityNameMatches.clear();
for (int i = 0; i < mClockCities.size(); i++) {
City city = mClockCities.get(i);
if (nameMatches(city, mCityName)) {
mCityNameMatches.add(city);
}
}
mCities = mCityNameMatches;
if (finalCity != null) {
for (int i = 0; i < mCityNameMatches.size(); i++) {
if (mCityNameMatches.get(i) == finalCity) {
mCityIndex = i;
break;
}
}
} else {
// Find the closest matching city
locateCity(false, 0.0f);
}
goToCity();
| private void | shiftTimeZone(int incr)Move to a different time zone.
// If only 1 city in the current set, there's nowhere to go
if (mCities.size() <= 1) {
return;
}
float offset = getOffset(mCities.get(mCityIndex));
do {
mCityIndex = (mCityIndex + mCities.size() + incr) % mCities.size();
} while (tzEqual(getOffset(mCities.get(mCityIndex)), offset));
offset = getOffset(mCities.get(mCityIndex));
locateCity(true, offset);
goToCity();
| private void | shiftWithinTimeZone(int incr)Shifts cities within the current time zone.
float offset = getOffset(mCities.get(mCityIndex));
int nindex = (mCityIndex + mCities.size() + incr) % mCities.size();
if (tzEqual(getOffset(mCities.get(nindex)), offset)) {
mCityIndex = nindex;
goToCity();
}
| public void | startAnimating()Begin animation.
if (mEGLSurface == null) {
mStartAnimating = true; // will start when egl surface is created
} else {
mHandler.sendEmptyMessage(INVALIDATE);
}
| private void | startEGL()Creates an egl context. If the state of the activity is right, also
creates the egl surface. Otherwise the surface will be created in a
future call to createEGLSurface().
EGL10 egl = (EGL10)EGLContext.getEGL();
if (mEGLContext == null) {
EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
egl.eglInitialize(dpy, version);
int[] configSpec = {
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] num_config = new int[1];
egl.eglChooseConfig(dpy, configSpec, configs, 1, num_config);
mEGLConfig = configs[0];
mEGLContext = egl.eglCreateContext(dpy, mEGLConfig,
EGL10.EGL_NO_CONTEXT, null);
mEGLDisplay = dpy;
AssetManager am = mContext.getAssets();
try {
loadAssets(am);
} catch (IOException ioe) {
ioe.printStackTrace();
throw new RuntimeException(ioe);
} catch (ArrayIndexOutOfBoundsException aioobe) {
aioobe.printStackTrace();
throw new RuntimeException(aioobe);
}
}
if (mEGLSurface == null && !mPaused && mHaveSurface) {
mEGLSurface = egl.eglCreateWindowSurface(mEGLDisplay, mEGLConfig,
this, null);
egl.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface,
mEGLContext);
mInitialized = false;
if (mStartAnimating) {
startAnimating();
mStartAnimating = false;
}
}
| public void | stopAnimating()Quit animation.
mHandler.removeMessages(INVALIDATE);
| private void | stopEGL()Destroys the egl context. If an egl surface has been created, it is
destroyed as well.
EGL10 egl = (EGL10)EGLContext.getEGL();
if (mEGLSurface != null) {
egl.eglMakeCurrent(mEGLDisplay,
egl.EGL_NO_SURFACE, egl.EGL_NO_SURFACE, egl.EGL_NO_CONTEXT);
egl.eglDestroySurface(mEGLDisplay, mEGLSurface);
mEGLSurface = null;
}
if (mEGLContext != null) {
egl.eglDestroyContext(mEGLDisplay, mEGLContext);
egl.eglTerminate(mEGLDisplay);
mEGLContext = null;
mEGLDisplay = null;
mEGLConfig = null;
}
| public void | surfaceChanged(android.view.SurfaceHolder holder, int format, int w, int h)
// nothing to do
| public void | surfaceCreated(android.view.SurfaceHolder holder)
mHaveSurface = true;
startEGL();
| public void | surfaceDestroyed(android.view.SurfaceHolder holder)
mHaveSurface = false;
stopEGL();
| private boolean | tzEqual(float o1, float o2)Returns true if two time zone offsets are equal. We assume distinct
time zone offsets will differ by at least a few minutes.
return Math.abs(o1 - o2) < 0.001;
|
|