summaryrefslogtreecommitdiffstats
path: root/samples/GlobalTime
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98 (patch)
tree2eb01432fa3da41550dd622d29fb672509122b51 /samples/GlobalTime
parentd4aee0c0caa00aa02d4c50ed28151591ac0456b5 (diff)
downloadandroid_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.gz
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.bz2
android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.zip
auto import from //depot/cupcake/@135843
Diffstat (limited to 'samples/GlobalTime')
-rw-r--r--samples/GlobalTime/Android.mk10
-rw-r--r--samples/GlobalTime/AndroidManifest.xml13
-rw-r--r--samples/GlobalTime/MODULE_LICENSE_APACHE20
-rw-r--r--samples/GlobalTime/NOTICE190
-rw-r--r--samples/GlobalTime/assets/cities_en.datbin0 -> 40350 bytes
-rw-r--r--samples/GlobalTime/assets/earth.rawbin0 -> 262148 bytes
-rw-r--r--samples/GlobalTime/assets/lights.datbin0 -> 8198 bytes
-rw-r--r--samples/GlobalTime/assets/tz512.rawbin0 -> 262148 bytes
-rw-r--r--samples/GlobalTime/assets/world.glesbin0 -> 46348 bytes
-rw-r--r--samples/GlobalTime/res/drawable/app_global_time.pngbin0 -> 3693 bytes
-rw-r--r--samples/GlobalTime/res/layout/global_time.xml22
-rw-r--r--samples/GlobalTime/res/values/strings.xml24
-rw-r--r--samples/GlobalTime/res/values/styles.xml26
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/Annulus.java101
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/City.java241
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/Clock.java332
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/GLView.java927
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/GlobalTime.java1488
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/LatLongSphere.java118
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/PointCloud.java60
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/Shape.java270
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/Sphere.java28
-rw-r--r--samples/GlobalTime/src/com/android/globaltime/Texture.java43
23 files changed, 3893 insertions, 0 deletions
diff --git a/samples/GlobalTime/Android.mk b/samples/GlobalTime/Android.mk
new file mode 100644
index 000000000..deb82f5c3
--- /dev/null
+++ b/samples/GlobalTime/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := GlobalTime
+
+include $(BUILD_PACKAGE)
diff --git a/samples/GlobalTime/AndroidManifest.xml b/samples/GlobalTime/AndroidManifest.xml
new file mode 100644
index 000000000..7aee91b65
--- /dev/null
+++ b/samples/GlobalTime/AndroidManifest.xml
@@ -0,0 +1,13 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.globaltime">
+ <application android:icon="@drawable/app_global_time"
+ android:label="Global Time">
+ <activity android:name="GlobalTime"
+ android:theme="@style/Theme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/samples/GlobalTime/MODULE_LICENSE_APACHE2 b/samples/GlobalTime/MODULE_LICENSE_APACHE2
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/samples/GlobalTime/MODULE_LICENSE_APACHE2
diff --git a/samples/GlobalTime/NOTICE b/samples/GlobalTime/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/samples/GlobalTime/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/samples/GlobalTime/assets/cities_en.dat b/samples/GlobalTime/assets/cities_en.dat
new file mode 100644
index 000000000..c982ef47f
--- /dev/null
+++ b/samples/GlobalTime/assets/cities_en.dat
Binary files differ
diff --git a/samples/GlobalTime/assets/earth.raw b/samples/GlobalTime/assets/earth.raw
new file mode 100644
index 000000000..f884e7572
--- /dev/null
+++ b/samples/GlobalTime/assets/earth.raw
Binary files differ
diff --git a/samples/GlobalTime/assets/lights.dat b/samples/GlobalTime/assets/lights.dat
new file mode 100644
index 000000000..14d4f3668
--- /dev/null
+++ b/samples/GlobalTime/assets/lights.dat
Binary files differ
diff --git a/samples/GlobalTime/assets/tz512.raw b/samples/GlobalTime/assets/tz512.raw
new file mode 100644
index 000000000..3ce1d5c2d
--- /dev/null
+++ b/samples/GlobalTime/assets/tz512.raw
Binary files differ
diff --git a/samples/GlobalTime/assets/world.gles b/samples/GlobalTime/assets/world.gles
new file mode 100644
index 000000000..815db1ed6
--- /dev/null
+++ b/samples/GlobalTime/assets/world.gles
Binary files differ
diff --git a/samples/GlobalTime/res/drawable/app_global_time.png b/samples/GlobalTime/res/drawable/app_global_time.png
new file mode 100644
index 000000000..914977d51
--- /dev/null
+++ b/samples/GlobalTime/res/drawable/app_global_time.png
Binary files differ
diff --git a/samples/GlobalTime/res/layout/global_time.xml b/samples/GlobalTime/res/layout/global_time.xml
new file mode 100644
index 000000000..237207fa1
--- /dev/null
+++ b/samples/GlobalTime/res/layout/global_time.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<EditText xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+ android:layout_width="fill_parent" android:layout_height="fill_parent"
+ android:textSize="18sp"
+ android:text="@string/global_time_text_text" />
+
+
diff --git a/samples/GlobalTime/res/values/strings.xml b/samples/GlobalTime/res/values/strings.xml
new file mode 100644
index 000000000..d35c05927
--- /dev/null
+++ b/samples/GlobalTime/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2007 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+
+<resources>
+
+ <string name="global_time_text_text">Global Time</string>
+
+</resources>
diff --git a/samples/GlobalTime/res/values/styles.xml b/samples/GlobalTime/res/values/styles.xml
new file mode 100644
index 000000000..6051f0f45
--- /dev/null
+++ b/samples/GlobalTime/res/values/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/Calendar/assets/res/any/styles.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources>
+ <style name="Theme" parent="android:Theme.Black">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@null</item>
+ <!-- <item name="android:windowFrame">@null</item> -->
+ </style>
+</resources>
diff --git a/samples/GlobalTime/src/com/android/globaltime/Annulus.java b/samples/GlobalTime/src/com/android/globaltime/Annulus.java
new file mode 100644
index 000000000..b811d88c5
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/Annulus.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A class that draws a ring with a given center and inner and outer radii.
+ * The inner and outer rings each have a color and the remaining pixels are
+ * colored by interpolation. GlobalTime uses this class to simulate an
+ * "atmosphere" around the earth.
+ */
+public class Annulus extends Shape {
+
+ /**
+ * Constructs an annulus.
+ *
+ * @param centerX the X coordinate of the center point
+ * @param centerY the Y coordinate of the center point
+ * @param Z the fixed Z for the entire ring
+ * @param innerRadius the inner radius
+ * @param outerRadius the outer radius
+ * @param rInner the red channel of the color of the inner ring
+ * @param gInner the green channel of the color of the inner ring
+ * @param bInner the blue channel of the color of the inner ring
+ * @param aInner the alpha channel of the color of the inner ring
+ * @param rOuter the red channel of the color of the outer ring
+ * @param gOuter the green channel of the color of the outer ring
+ * @param bOuter the blue channel of the color of the outer ring
+ * @param aOuter the alpha channel of the color of the outer ring
+ * @param sectors the number of sectors used to approximate curvature
+ */
+ public Annulus(float centerX, float centerY, float Z,
+ float innerRadius, float outerRadius,
+ float rInner, float gInner, float bInner, float aInner,
+ float rOuter, float gOuter, float bOuter, float aOuter,
+ int sectors) {
+ super(GL10.GL_TRIANGLES, GL10.GL_UNSIGNED_SHORT,
+ false, false, true);
+
+ int radii = sectors + 1;
+
+ int[] vertices = new int[2 * 3 * radii];
+ int[] colors = new int[2 * 4 * radii];
+ short[] indices = new short[2 * 3 * radii];
+
+ int vidx = 0;
+ int cidx = 0;
+ int iidx = 0;
+
+ for (int i = 0; i < radii; i++) {
+ float theta = (i * TWO_PI) / (radii - 1);
+ float cosTheta = (float) Math.cos(theta);
+ float sinTheta = (float) Math.sin(theta);
+
+ vertices[vidx++] = toFixed(centerX + innerRadius * cosTheta);
+ vertices[vidx++] = toFixed(centerY + innerRadius * sinTheta);
+ vertices[vidx++] = toFixed(Z);
+
+ vertices[vidx++] = toFixed(centerX + outerRadius * cosTheta);
+ vertices[vidx++] = toFixed(centerY + outerRadius * sinTheta);
+ vertices[vidx++] = toFixed(Z);
+
+ colors[cidx++] = toFixed(rInner);
+ colors[cidx++] = toFixed(gInner);
+ colors[cidx++] = toFixed(bInner);
+ colors[cidx++] = toFixed(aInner);
+
+ colors[cidx++] = toFixed(rOuter);
+ colors[cidx++] = toFixed(gOuter);
+ colors[cidx++] = toFixed(bOuter);
+ colors[cidx++] = toFixed(aOuter);
+ }
+
+ for (int i = 0; i < sectors; i++) {
+ indices[iidx++] = (short) (2 * i);
+ indices[iidx++] = (short) (2 * i + 1);
+ indices[iidx++] = (short) (2 * i + 2);
+
+ indices[iidx++] = (short) (2 * i + 1);
+ indices[iidx++] = (short) (2 * i + 3);
+ indices[iidx++] = (short) (2 * i + 2);
+ }
+
+ allocateBuffers(vertices, null, null, colors, indices);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/City.java b/samples/GlobalTime/src/com/android/globaltime/City.java
new file mode 100644
index 000000000..0b4cb84c2
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/City.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * A class representing a city, with an associated position, time zone name,
+ * and raw offset from UTC.
+ */
+public class City implements Comparable<City> {
+
+ private static Map<String,City> cities = new HashMap<String,City>();
+ private static City[] citiesByRawOffset;
+
+ private String name;
+ private String timeZoneID;
+ private TimeZone timeZone = null;
+ private int rawOffset;
+ private float latitude, longitude;
+ private float x, y, z;
+
+ /**
+ * Loads the city database. The cities must be stored in order by raw
+ * offset from UTC.
+ */
+ public static void loadCities(InputStream is) throws IOException {
+ DataInputStream dis = new DataInputStream(is);
+ int numCities = dis.readInt();
+ citiesByRawOffset = new City[numCities];
+
+ byte[] buf = new byte[24];
+ for (int i = 0; i < numCities; i++) {
+ String name = dis.readUTF();
+ String tzid = dis.readUTF();
+ dis.read(buf);
+
+// The code below is a faster version of:
+// int rawOffset = dis.readInt();
+// float latitude = dis.readFloat();
+// float longitude = dis.readFloat();
+// float cx = dis.readFloat();
+// float cy = dis.readFloat();
+// float cz = dis.readFloat();
+
+ int rawOffset =
+ (buf[ 0] << 24) | ((buf[ 1] & 0xff) << 16) |
+ ((buf[ 2] & 0xff) << 8) | (buf[ 3] & 0xff);
+ int ilat = (buf[ 4] << 24) | ((buf[ 5] & 0xff) << 16) |
+ ((buf[ 6] & 0xff) << 8) | (buf[ 7] & 0xff);
+ int ilon = (buf[ 8] << 24) | ((buf[ 9] & 0xff) << 16) |
+ ((buf[10] & 0xff) << 8) | (buf[11] & 0xff);
+ int icx = (buf[12] << 24) | ((buf[13] & 0xff) << 16) |
+ ((buf[14] & 0xff) << 8) | (buf[15] & 0xff);
+ int icy = (buf[16] << 24) | ((buf[17] & 0xff) << 16) |
+ ((buf[18] & 0xff) << 8) | (buf[19] & 0xff);
+ int icz = (buf[20] << 24) | ((buf[21] & 0xff) << 16) |
+ ((buf[22] & 0xff) << 8) | (buf[23] & 0xff);
+ float latitude = Float.intBitsToFloat(ilat);
+ float longitude = Float.intBitsToFloat(ilon);
+ float cx = Float.intBitsToFloat(icx);
+ float cy = Float.intBitsToFloat(icy);
+ float cz = Float.intBitsToFloat(icz);
+
+ City city = new City(name, tzid, rawOffset,
+ latitude, longitude, cx, cy, cz);
+
+ cities.put(name, city);
+ citiesByRawOffset[i] = city;
+ }
+ }
+
+ /**
+ * Returns the cities, ordered by name.
+ */
+ public static City[] getCitiesByName() {
+ City[] ocities = new City[cities.size()];
+ Iterator<City> iter = cities.values().iterator();
+ int idx = 0;
+ while (iter.hasNext()) {
+ ocities[idx++] = iter.next();
+ }
+ Arrays.sort(ocities);
+ return ocities;
+ }
+
+ /**
+ * Returns the cities, ordered by offset, accounting for summer/daylight
+ * savings time. This requires reading the entire time zone database
+ * behind the scenes.
+ */
+ public static City[] getCitiesByOffset() {
+ City[] ocities = new City[cities.size()];
+ Iterator<City> iter = cities.values().iterator();
+ int idx = 0;
+ while (iter.hasNext()) {
+ ocities[idx++] = iter.next();
+ }
+ Arrays.sort(ocities, new Comparator() {
+ public int compare(Object o1, Object o2) {
+ long now = System.currentTimeMillis();
+ City c1 = (City)o1;
+ City c2 = (City)o2;
+ TimeZone tz1 = c1.getTimeZone();
+ TimeZone tz2 = c2.getTimeZone();
+ int off1 = tz1.getOffset(now);
+ int off2 = tz2.getOffset(now);
+ if (off1 == off2) {
+ float dlat = c2.getLatitude() - c1.getLatitude();
+ if (dlat < 0.0f) return -1;
+ if (dlat > 0.0f) return 1;
+ return 0;
+ }
+ return off1 - off2;
+ }
+ });
+ return ocities;
+ }
+
+
+ /**
+ * Returns the cities, ordered by offset, accounting for summer/daylight
+ * savings time. This does not require reading the time zone database
+ * since the cities are pre-sorted.
+ */
+ public static City[] getCitiesByRawOffset() {
+ return citiesByRawOffset;
+ }
+
+ /**
+ * Returns an Iterator over all cities, in raw offset order.
+ */
+ public static Iterator<City> iterator() {
+ return cities.values().iterator();
+ }
+
+ /**
+ * Returns the total number of cities.
+ */
+ public static int numCities() {
+ return cities.size();
+ }
+
+ /**
+ * Constructs a city with the given name, time zone name, raw offset,
+ * latitude, longitude, and 3D (X, Y, Z) coordinate.
+ */
+ public City(String name, String timeZoneID,
+ int rawOffset,
+ float latitude, float longitude,
+ float x, float y, float z) {
+ this.name = name;
+ this.timeZoneID = timeZoneID;
+ this.rawOffset = rawOffset;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public TimeZone getTimeZone() {
+ if (timeZone == null) {
+ timeZone = TimeZone.getTimeZone(timeZoneID);
+ }
+ return timeZone;
+ }
+
+ public float getLongitude() {
+ return longitude;
+ }
+
+ public float getLatitude() {
+ return latitude;
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public float getZ() {
+ return z;
+ }
+
+ public float getRawOffset() {
+ return rawOffset / 3600000.0f;
+ }
+
+ public int getRawOffsetMillis() {
+ return rawOffset;
+ }
+
+ /**
+ * Returns this city's offset from UTC, taking summer/daylight savigns
+ * time into account.
+ */
+ public float getOffset() {
+ long now = System.currentTimeMillis();
+ if (timeZone == null) {
+ timeZone = TimeZone.getTimeZone(timeZoneID);
+ }
+ return timeZone.getOffset(now) / 3600000.0f;
+ }
+
+ /**
+ * Compares this city to another by name.
+ */
+ public int compareTo(City o) {
+ return name.compareTo(o.name);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/Clock.java b/samples/GlobalTime/src/com/android/globaltime/Clock.java
new file mode 100644
index 000000000..9aad71a12
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/Clock.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.text.format.DateUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * A class that draws an analog clock face with information about the current
+ * time in a given city.
+ */
+public class Clock {
+
+ static final int MILLISECONDS_PER_MINUTE = 60 * 1000;
+ static final int MILLISECONDS_PER_HOUR = 60 * 60 * 1000;
+
+ private City mCity = null;
+ private long mCitySwitchTime;
+ private long mTime;
+
+ private float mColorRed = 1.0f;
+ private float mColorGreen = 1.0f;
+ private float mColorBlue = 1.0f;
+
+ private long mOldOffset;
+
+ private Interpolator mClockHandInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ public Clock() {
+ // Empty constructor
+ }
+
+ /**
+ * Adds a line to the given Path. The line extends from
+ * radius r0 to radius r1 about the center point (cx, cy),
+ * at an angle given by pos.
+ *
+ * @param path the Path to draw to
+ * @param radius the radius of the outer rim of the clock
+ * @param pos the angle, with 0 and 1 at 12:00
+ * @param cx the X coordinate of the clock center
+ * @param cy the Y coordinate of the clock center
+ * @param r0 the starting radius for the line
+ * @param r1 the ending radius for the line
+ */
+ private static void drawLine(Path path,
+ float radius, float pos, float cx, float cy, float r0, float r1) {
+ float theta = pos * Shape.TWO_PI - Shape.PI_OVER_TWO;
+ float dx = (float) Math.cos(theta);
+ float dy = (float) Math.sin(theta);
+ float p0x = cx + dx * r0;
+ float p0y = cy + dy * r0;
+ float p1x = cx + dx * r1;
+ float p1y = cy + dy * r1;
+
+ float ox = (p1y - p0y);
+ float oy = -(p1x - p0x);
+
+ float norm = (radius / 2.0f) / (float) Math.sqrt(ox * ox + oy * oy);
+ ox *= norm;
+ oy *= norm;
+
+ path.moveTo(p0x - ox, p0y - oy);
+ path.lineTo(p1x - ox, p1y - oy);
+ path.lineTo(p1x + ox, p1y + oy);
+ path.lineTo(p0x + ox, p0y + oy);
+ path.close();
+ }
+
+ /**
+ * Adds a vertical arrow to the given Path.
+ *
+ * @param path the Path to draw to
+ */
+ private static void drawVArrow(Path path,
+ float cx, float cy, float width, float height) {
+ path.moveTo(cx - width / 2.0f, cy);
+ path.lineTo(cx, cy + height);
+ path.lineTo(cx + width / 2.0f, cy);
+ path.close();
+ }
+
+ /**
+ * Adds a horizontal arrow to the given Path.
+ *
+ * @param path the Path to draw to
+ */
+ private static void drawHArrow(Path path,
+ float cx, float cy, float width, float height) {
+ path.moveTo(cx, cy - height / 2.0f);
+ path.lineTo(cx + width, cy);
+ path.lineTo(cx, cy + height / 2.0f);
+ path.close();
+ }
+
+ /**
+ * Returns an offset in milliseconds to be subtracted from the current time
+ * in order to obtain an smooth interpolation between the previously
+ * displayed time and the current time.
+ */
+ private long getOffset(float lerp) {
+ long doffset = (long) (mCity.getOffset() *
+ (float) MILLISECONDS_PER_HOUR - mOldOffset);
+ int sign;
+ if (doffset < 0) {
+ doffset = -doffset;
+ sign = -1;
+ } else {
+ sign = 1;
+ }
+
+ while (doffset > 12L * MILLISECONDS_PER_HOUR) {
+ doffset -= 12L * MILLISECONDS_PER_HOUR;
+ }
+ if (doffset > 6L * MILLISECONDS_PER_HOUR) {
+ doffset = 12L * MILLISECONDS_PER_HOUR - doffset;
+ sign = -sign;
+ }
+
+ // Interpolate doffset towards 0
+ doffset = (long)((1.0f - lerp)*doffset);
+
+ // Keep the same seconds count
+ long dh = doffset / (MILLISECONDS_PER_HOUR);
+ doffset -= dh * MILLISECONDS_PER_HOUR;
+ long dm = doffset / MILLISECONDS_PER_MINUTE;
+ doffset = sign * (60 * dh + dm) * MILLISECONDS_PER_MINUTE;
+
+ return doffset;
+ }
+
+ /**
+ * Set the city to be displayed. setCity(null) resets things so the clock
+ * hand animation won't occur next time.
+ */
+ public void setCity(City city) {
+ if (mCity != city) {
+ if (mCity != null) {
+ mOldOffset =
+ (long) (mCity.getOffset() * (float) MILLISECONDS_PER_HOUR);
+ } else if (city != null) {
+ mOldOffset =
+ (long) (city.getOffset() * (float) MILLISECONDS_PER_HOUR);
+ } else {
+ mOldOffset = 0L; // this will never be used
+ }
+ this.mCitySwitchTime = System.currentTimeMillis();
+ this.mCity = city;
+ }
+ }
+
+ public void setTime(long time) {
+ this.mTime = time;
+ }
+
+ /**
+ * Draws the clock face.
+ *
+ * @param canvas the Canvas to draw to
+ * @param cx the X coordinate of the clock center
+ * @param cy the Y coordinate of the clock center
+ * @param radius the radius of the clock face
+ * @param alpha the translucency of the clock face
+ * @param textAlpha the translucency of the text
+ * @param showCityName if true, display the city name
+ * @param showTime if true, display the time digitally
+ * @param showUpArrow if true, display an up arrow
+ * @param showDownArrow if true, display a down arrow
+ * @param showLeftRightArrows if true, display left and right arrows
+ * @param prefixChars number of characters of the city name to draw in bold
+ */
+ public void drawClock(Canvas canvas,
+ float cx, float cy, float radius, float alpha, float textAlpha,
+ boolean showCityName, boolean showTime,
+ boolean showUpArrow, boolean showDownArrow, boolean showLeftRightArrows,
+ int prefixChars) {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+
+ int iradius = (int)radius;
+
+ TimeZone tz = mCity.getTimeZone();
+
+ // Compute an interpolated time to animate between the previously
+ // displayed time and the current time
+ float lerp = Math.min(1.0f,
+ (System.currentTimeMillis() - mCitySwitchTime) / 500.0f);
+ lerp = mClockHandInterpolator.getInterpolation(lerp);
+ long doffset = lerp < 1.0f ? getOffset(lerp) : 0L;
+
+ // Determine the interpolated time for the given time zone
+ Calendar cal = Calendar.getInstance(tz);
+ cal.setTimeInMillis(mTime - doffset);
+ int hour = cal.get(Calendar.HOUR_OF_DAY);
+ int minute = cal.get(Calendar.MINUTE);
+ int second = cal.get(Calendar.SECOND);
+ int milli = cal.get(Calendar.MILLISECOND);
+
+ float offset = tz.getRawOffset() / (float) MILLISECONDS_PER_HOUR;
+ float daylightOffset = tz.inDaylightTime(new Date(mTime)) ?
+ tz.getDSTSavings() / (float) MILLISECONDS_PER_HOUR : 0.0f;
+
+ float absOffset = offset < 0 ? -offset : offset;
+ int offsetH = (int) absOffset;
+ int offsetM = (int) (60.0f * (absOffset - offsetH));
+ hour %= 12;
+
+ // Get the city name and digital time strings
+ String cityName = mCity.getName();
+ cal.setTimeInMillis(mTime);
+ String time = DateUtils.timeString(cal.getTimeInMillis()) + " " +
+ DateUtils.getDayOfWeekString(cal.get(Calendar.DAY_OF_WEEK),
+ DateUtils.LENGTH_SHORT) + " " +
+ " (UTC" +
+ (offset >= 0 ? "+" : "-") +
+ offsetH +
+ (offsetM == 0 ? "" : ":" + offsetM) +
+ (daylightOffset == 0 ? "" : "+" + daylightOffset) +
+ ")";
+
+ float th = paint.getTextSize();
+ float tw;
+
+ // Set the text color
+ paint.setARGB((int) (textAlpha * 255.0f),
+ (int) (mColorRed * 255.0f),
+ (int) (mColorGreen * 255.0f),
+ (int) (mColorBlue * 255.0f));
+
+ tw = paint.measureText(cityName);
+ if (showCityName) {
+ // Increment prefixChars to include any spaces
+ for (int i = 0; i < prefixChars; i++) {
+ if (cityName.charAt(i) == ' ') {
+ ++prefixChars;
+ }
+ }
+
+ // Draw the city name
+ canvas.drawText(cityName, cx - tw / 2, cy - radius - th, paint);
+ // Overstrike the first 'prefixChars' characters
+ canvas.drawText(cityName.substring(0, prefixChars),
+ cx - tw / 2 + 1, cy - radius - th, paint);
+ }
+ tw = paint.measureText(time);
+ if (showTime) {
+ canvas.drawText(time, cx - tw / 2, cy + radius + th + 5, paint);
+ }
+
+ paint.setARGB((int)(alpha * 255.0f),
+ (int)(mColorRed * 255.0f),
+ (int)(mColorGreen * 255.0f),
+ (int)(mColorBlue * 255.0f));
+
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawOval(new RectF(cx - 2, cy - 2, cx + 2, cy + 2), paint);
+
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeWidth(radius * 0.12f);
+
+ canvas.drawOval(new RectF(cx - iradius, cy - iradius,
+ cx + iradius, cy + iradius),
+ paint);
+
+ float r0 = radius * 0.1f;
+ float r1 = radius * 0.4f;
+ float r2 = radius * 0.6f;
+ float r3 = radius * 0.65f;
+ float r4 = radius * 0.7f;
+ float r5 = radius * 0.9f;
+
+ Path path = new Path();
+
+ float ss = second + milli / 1000.0f;
+ float mm = minute + ss / 60.0f;
+ float hh = hour + mm / 60.0f;
+
+ // Tics for the hours
+ for (int i = 0; i < 12; i++) {
+ drawLine(path, radius * 0.12f, i / 12.0f, cx, cy, r4, r5);
+ }
+
+ // Hour hand
+ drawLine(path, radius * 0.12f, hh / 12.0f, cx, cy, r0, r1);
+ // Minute hand
+ drawLine(path, radius * 0.12f, mm / 60.0f, cx, cy, r0, r2);
+ // Second hand
+ drawLine(path, radius * 0.036f, ss / 60.0f, cx, cy, r0, r3);
+
+ if (showUpArrow) {
+ drawVArrow(path, cx + radius * 1.13f, cy - radius,
+ radius * 0.15f, -radius * 0.1f);
+ }
+ if (showDownArrow) {
+ drawVArrow(path, cx + radius * 1.13f, cy + radius,
+ radius * 0.15f, radius * 0.1f);
+ }
+ if (showLeftRightArrows) {
+ drawHArrow(path, cx - radius * 1.3f, cy, -radius * 0.1f,
+ radius * 0.15f);
+ drawHArrow(path, cx + radius * 1.3f, cy, radius * 0.1f,
+ radius * 0.15f);
+ }
+
+ paint.setStyle(Paint.Style.FILL);
+ canvas.drawPath(path, paint);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/GLView.java b/samples/GlobalTime/src/com/android/globaltime/GLView.java
new file mode 100644
index 000000000..425371744
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/GLView.java
@@ -0,0 +1,927 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.KeyEvent;
+
+class Message {
+
+ private String mText;
+ private long mExpirationTime;
+
+ public Message(String text, long expirationTime) {
+ this.mText = text;
+ this.mExpirationTime = expirationTime;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public long getExpirationTime() {
+ return mExpirationTime;
+ }
+}
+
+/**
+ * A helper class to simplify writing an Activity that renders using
+ * OpenGL ES.
+ *
+ * <p> A GLView object stores common elements of GL state and allows
+ * them to be modified interactively. This is particularly useful for
+ * determining the proper settings of parameters such as the view
+ * frustum and light intensities during application development.
+ *
+ * <p> A GLView is not an actual View; instead, it is meant to be
+ * called from within a View to perform event processing on behalf of the
+ * actual View.
+ *
+ * <p> By passing key events to the GLView object from the View,
+ * the application can automatically allow certain parameters to
+ * be user-controlled from the keyboard. Key events may be passed as
+ * shown below:
+ *
+ * <pre>
+ * GLView mGlView = new GLView();
+ *
+ * public boolean onKeyDown(int keyCode, KeyEvent event) {
+ * // Hand the key to the GLView object first
+ * if (mGlView.processKey(keyCode)) {
+ * return;
+ * }
+ *
+ * switch (keyCode) {
+ * case KeyEvent.KEY_CODE_X:
+ * // perform app processing
+ * break;
+ *
+ * default:
+ * super.onKeyDown(keyCode, event);
+ * break;
+ * }
+ * }
+ * </pre>
+ *
+ * <p> During drawing of a frame, the GLView object should be given the
+ * opportunity to manage GL parameters as shown below:
+ *
+ * OpenGLContext mGLContext; // initialization not shown
+ * int mNumTrianglesDrawn = 0;
+ *
+ * protected void onDraw(Canvas canvas) {
+ * int w = getWidth();
+ * int h = getHeight();
+ *
+ * float ratio = (float) w / h;
+ * mGLView.setAspectRatio(ratio);
+ *
+ * GL10 gl = (GL10) mGLContext.getGL();
+ * mGLContext.waitNative(canvas, this);
+ *
+ * // Enable a light for the GLView to manipulate
+ * gl.glEnable(GL10.GL_LIGHTING);
+ * gl.glEnable(GL10.GL_LIGHT0);
+ *
+ * // Allow the GLView to set GL parameters
+ * mGLView.setTextureParameters(gl);
+ * mGLView.setProjection(gl);
+ * mGLView.setView(gl);
+ * mGLView.setLights(gl, GL10.GL_LIGHT0);
+ *
+ * // Draw some stuff (not shown)
+ * mNumTrianglesDrawn += <num triangles just drawn>;
+ *
+ * // Wait for GL drawing to complete
+ * mGLContext.waitGL();
+ *
+ * // Inform the GLView of what was drawn, and ask it to display statistics
+ * mGLView.setNumTriangles(mNumTrianglesDrawn);
+ * mGLView.showMessages(canvas);
+ * mGLView.showStatistics(canvas, w);
+ * }
+ * </pre>
+ *
+ * <p> At the end of each frame, following the call to
+ * GLContext.waitGL, the showStatistics and showMessages methods
+ * will cause additional information to be displayed.
+ *
+ * <p> To enter the interactive command mode, the 'tab' key must be
+ * pressed twice in succession. A subsequent press of the 'tab' key
+ * exits the interactive command mode. Entering a multi-letter code
+ * sets the parameter to be modified. The 'newline' key erases the
+ * current code, and the 'del' key deletes the last letter of
+ * the code. The parameter value may be modified by pressing the
+ * keypad left or up to decrement the value and right or down to
+ * increment the value. The current value will be displayed as an
+ * overlay above the GL rendered content.
+ *
+ * <p> The supported keyboard commands are as follows:
+ *
+ * <ul>
+ * <li> h - display a list of commands
+ * <li> fn - near frustum
+ * <li> ff - far frustum
+ * <li> tx - translate x
+ * <li> ty - translate y
+ * <li> tz - translate z
+ * <li> z - zoom (frustum size)
+ * <li> la - ambient light (all RGB channels)
+ * <li> lar - ambient light red channel
+ * <li> lag - ambient light green channel
+ * <li> lab - ambient light blue channel
+ * <li> ld - diffuse light (all RGB channels)
+ * <li> ldr - diffuse light red channel
+ * <li> ldg - diffuse light green channel
+ * <li> ldb - diffuse light blue channel
+ * <li> ls - specular light (all RGB channels)
+ * <li> lsr - specular light red channel
+ * <li> lsg - specular light green channel
+ * <li> lsb - specular light blue channel
+ * <li> lma - light model ambient (all RGB channels)
+ * <li> lmar - light model ambient light red channel
+ * <li> lmag - light model ambient green channel
+ * <li> lmab - light model ambient blue channel
+ * <li> tmin - texture min filter
+ * <li> tmag - texture mag filter
+ * <li> tper - texture perspective correction
+ * </ul>
+ *
+ * {@hide}
+ */
+public class GLView {
+
+ private static final int DEFAULT_DURATION_MILLIS = 1000;
+ private static final int STATE_KEY = KeyEvent.KEYCODE_TAB;
+ private static final int HAVE_NONE = 0;
+ private static final int HAVE_ONE = 1;
+ private static final int HAVE_TWO = 2;
+
+ private static final float MESSAGE_Y_SPACING = 12.0f;
+
+ private int mState = HAVE_NONE;
+
+ private static final int NEAR_FRUSTUM = 0;
+ private static final int FAR_FRUSTUM = 1;
+ private static final int TRANSLATE_X = 2;
+ private static final int TRANSLATE_Y = 3;
+ private static final int TRANSLATE_Z = 4;
+ private static final int ZOOM_EXPONENT = 5;
+
+ private static final int AMBIENT_INTENSITY = 6;
+ private static final int AMBIENT_RED = 7;
+ private static final int AMBIENT_GREEN = 8;
+ private static final int AMBIENT_BLUE = 9;
+
+ private static final int DIFFUSE_INTENSITY = 10;
+ private static final int DIFFUSE_RED = 11;
+ private static final int DIFFUSE_GREEN = 12;
+ private static final int DIFFUSE_BLUE = 13;
+
+ private static final int SPECULAR_INTENSITY = 14;
+ private static final int SPECULAR_RED = 15;
+ private static final int SPECULAR_GREEN = 16;
+ private static final int SPECULAR_BLUE = 17;
+
+ private static final int LIGHT_MODEL_AMBIENT_INTENSITY = 18;
+ private static final int LIGHT_MODEL_AMBIENT_RED = 19;
+ private static final int LIGHT_MODEL_AMBIENT_GREEN = 20;
+ private static final int LIGHT_MODEL_AMBIENT_BLUE = 21;
+
+ private static final int TEXTURE_MIN_FILTER = 22;
+ private static final int TEXTURE_MAG_FILTER = 23;
+ private static final int TEXTURE_PERSPECTIVE_CORRECTION = 24;
+
+ private static final String[] commands = {
+ "fn",
+ "ff",
+ "tx",
+ "ty",
+ "tz",
+ "z",
+ "la", "lar", "lag", "lab",
+ "ld", "ldr", "ldg", "ldb",
+ "ls", "lsr", "lsg", "lsb",
+ "lma", "lmar", "lmag", "lmab",
+ "tmin", "tmag", "tper"
+ };
+
+ private static final String[] labels = {
+ "Near Frustum",
+ "Far Frustum",
+ "Translate X",
+ "Translate Y",
+ "Translate Z",
+ "Zoom",
+ "Ambient Intensity",
+ "Ambient Red",
+ "Ambient Green",
+ "Ambient Blue",
+ "Diffuse Intensity",
+ "Diffuse Red",
+ "Diffuse Green",
+ "Diffuse Blue",
+ "Specular Intenstity",
+ "Specular Red",
+ "Specular Green",
+ "Specular Blue",
+ "Light Model Ambient Intensity",
+ "Light Model Ambient Red",
+ "Light Model Ambient Green",
+ "Light Model Ambient Blue",
+ "Texture Min Filter",
+ "Texture Mag Filter",
+ "Texture Perspective Correction",
+ };
+
+ private static final float[] defaults = {
+ 5.0f, 100.0f,
+ 0.0f, 0.0f, -50.0f,
+ 0,
+ 0.125f, 1.0f, 1.0f, 1.0f,
+ 0.125f, 1.0f, 1.0f, 1.0f,
+ 0.125f, 1.0f, 1.0f, 1.0f,
+ 0.125f, 1.0f, 1.0f, 1.0f,
+ GL10.GL_NEAREST, GL10.GL_NEAREST,
+ GL10.GL_FASTEST
+ };
+
+ private static final float[] increments = {
+ 0.01f, 0.5f,
+ 0.125f, 0.125f, 0.125f,
+ 1.0f,
+ 0.03125f, 0.1f, 0.1f, 0.1f,
+ 0.03125f, 0.1f, 0.1f, 0.1f,
+ 0.03125f, 0.1f, 0.1f, 0.1f,
+ 0.03125f, 0.1f, 0.1f, 0.1f,
+ 0, 0, 0
+ };
+
+ private float[] params = new float[commands.length];
+
+ private static final float mZoomScale = 0.109f;
+ private static final float mZoomBase = 1.01f;
+
+ private int mParam = -1;
+ private float mIncr = 0;
+
+ private Paint mPaint = new Paint();
+
+ private float mAspectRatio = 1.0f;
+
+ private float mZoom;
+
+ // private boolean mPerspectiveCorrection = false;
+ // private int mTextureMinFilter = GL10.GL_NEAREST;
+ // private int mTextureMagFilter = GL10.GL_NEAREST;
+
+ // Counters for FPS calculation
+ private boolean mDisplayFPS = false;
+ private boolean mDisplayCounts = false;
+ private int mFramesFPS = 10;
+ private long[] mTimes = new long[mFramesFPS];
+ private int mTimesIdx = 0;
+
+ private Map<String,Message> mMessages = new HashMap<String,Message>();
+
+ /**
+ * Constructs a new GLView.
+ */
+ public GLView() {
+ mPaint.setColor(0xffffffff);
+ reset();
+ }
+
+ /**
+ * Sets the aspect ratio (width/height) of the screen.
+ *
+ * @param aspectRatio the screen width divided by the screen height
+ */
+ public void setAspectRatio(float aspectRatio) {
+ this.mAspectRatio = aspectRatio;
+ }
+
+ /**
+ * Sets the overall ambient light intensity. This intensity will
+ * be used to modify the ambient light value for each of the red,
+ * green, and blue channels passed to glLightfv(...GL_AMBIENT...).
+ * The default value is 0.125f.
+ *
+ * @param intensity a floating-point value controlling the overall
+ * ambient light intensity.
+ */
+ public void setAmbientIntensity(float intensity) {
+ params[AMBIENT_INTENSITY] = intensity;
+ }
+
+ /**
+ * Sets the light model ambient intensity. This intensity will be
+ * used to modify the ambient light value for each of the red,
+ * green, and blue channels passed to
+ * glLightModelfv(GL_LIGHT_MODEL_AMBIENT...). The default value
+ * is 0.125f.
+ *
+ * @param intensity a floating-point value controlling the overall
+ * light model ambient intensity.
+ */
+ public void setLightModelAmbientIntensity(float intensity) {
+ params[LIGHT_MODEL_AMBIENT_INTENSITY] = intensity;
+ }
+
+ /**
+ * Sets the ambient color for the red, green, and blue channels
+ * that will be multiplied by the value of setAmbientIntensity and
+ * passed to glLightfv(...GL_AMBIENT...). The default values are
+ * {1, 1, 1}.
+ *
+ * @param ambient an arry of three floats containing ambient
+ * red, green, and blue intensity values.
+ */
+ public void setAmbientColor(float[] ambient) {
+ params[AMBIENT_RED] = ambient[0];
+ params[AMBIENT_GREEN] = ambient[1];
+ params[AMBIENT_BLUE] = ambient[2];
+ }
+
+ /**
+ * Sets the overall diffuse light intensity. This intensity will
+ * be used to modify the diffuse light value for each of the red,
+ * green, and blue channels passed to glLightfv(...GL_DIFFUSE...).
+ * The default value is 0.125f.
+ *
+ * @param intensity a floating-point value controlling the overall
+ * ambient light intensity.
+ */
+ public void setDiffuseIntensity(float intensity) {
+ params[DIFFUSE_INTENSITY] = intensity;
+ }
+
+ /**
+ * Sets the diffuse color for the red, green, and blue channels
+ * that will be multiplied by the value of setDiffuseIntensity and
+ * passed to glLightfv(...GL_DIFFUSE...). The default values are
+ * {1, 1, 1}.
+ *
+ * @param diffuse an array of three floats containing diffuse
+ * red, green, and blue intensity values.
+ */
+ public void setDiffuseColor(float[] diffuse) {
+ params[DIFFUSE_RED] = diffuse[0];
+ params[DIFFUSE_GREEN] = diffuse[1];
+ params[DIFFUSE_BLUE] = diffuse[2];
+ }
+
+ /**
+ * Sets the overall specular light intensity. This intensity will
+ * be used to modify the diffuse light value for each of the red,
+ * green, and blue channels passed to glLightfv(...GL_SPECULAR...).
+ * The default value is 0.125f.
+ *
+ * @param intensity a floating-point value controlling the overall
+ * ambient light intensity.
+ */
+ public void setSpecularIntensity(float intensity) {
+ params[SPECULAR_INTENSITY] = intensity;
+ }
+
+ /**
+ * Sets the specular color for the red, green, and blue channels
+ * that will be multiplied by the value of setSpecularIntensity and
+ * passed to glLightfv(...GL_SPECULAR...). The default values are
+ * {1, 1, 1}.
+ *
+ * @param specular an array of three floats containing specular
+ * red, green, and blue intensity values.
+ */
+ public void setSpecularColor(float[] specular) {
+ params[SPECULAR_RED] = specular[0];
+ params[SPECULAR_GREEN] = specular[1];
+ params[SPECULAR_BLUE] = specular[2];
+ }
+
+ /**
+ * Returns the current X translation of the modelview
+ * transformation as passed to glTranslatef. The default value is
+ * 0.0f.
+ *
+ * @return the X modelview translation as a float.
+ */
+ public float getTranslateX() {
+ return params[TRANSLATE_X];
+ }
+
+ /**
+ * Returns the current Y translation of the modelview
+ * transformation as passed to glTranslatef. The default value is
+ * 0.0f.
+ *
+ * @return the Y modelview translation as a float.
+ */
+ public float getTranslateY() {
+ return params[TRANSLATE_Y];
+ }
+
+ /**
+ * Returns the current Z translation of the modelview
+ * transformation as passed to glTranslatef. The default value is
+ * -50.0f.
+ *
+ * @return the Z modelview translation as a float.
+ */
+ public float getTranslateZ() {
+ return params[TRANSLATE_Z];
+ }
+
+ /**
+ * Sets the position of the near frustum clipping plane as passed
+ * to glFrustumf. The default value is 5.0f;
+ *
+ * @param nearFrustum the near frustum clipping plane distance as
+ * a float.
+ */
+ public void setNearFrustum(float nearFrustum) {
+ params[NEAR_FRUSTUM] = nearFrustum;
+ }
+
+ /**
+ * Sets the position of the far frustum clipping plane as passed
+ * to glFrustumf. The default value is 100.0f;
+ *
+ * @param farFrustum the far frustum clipping plane distance as a
+ * float.
+ */
+ public void setFarFrustum(float farFrustum) {
+ params[FAR_FRUSTUM] = farFrustum;
+ }
+
+ private void computeZoom() {
+ mZoom = mZoomScale*(float)Math.pow(mZoomBase, -params[ZOOM_EXPONENT]);
+ }
+
+ /**
+ * Resets all parameters to their default values.
+ */
+ public void reset() {
+ for (int i = 0; i < params.length; i++) {
+ params[i] = defaults[i];
+ }
+ computeZoom();
+ }
+
+ private void removeExpiredMessages() {
+ long now = System.currentTimeMillis();
+
+ List<String> toBeRemoved = new ArrayList<String>();
+
+ Iterator<String> keyIter = mMessages.keySet().iterator();
+ while (keyIter.hasNext()) {
+ String key = keyIter.next();
+ Message msg = mMessages.get(key);
+ if (msg.getExpirationTime() < now) {
+ toBeRemoved.add(key);
+ }
+ }
+
+ Iterator<String> tbrIter = toBeRemoved.iterator();
+ while (tbrIter.hasNext()) {
+ String key = tbrIter.next();
+ mMessages.remove(key);
+ }
+ }
+
+ /**
+ * Displays the message overlay on the given Canvas. The
+ * GLContext.waitGL method should be called prior to calling this
+ * method. The interactive command display is drawn by this
+ * method.
+ *
+ * @param canvas the Canvas on which messages are to appear.
+ */
+ public void showMessages(Canvas canvas) {
+ removeExpiredMessages();
+
+ float y = 10.0f;
+
+ List<String> l = new ArrayList<String>();
+ l.addAll(mMessages.keySet());
+ Collections.sort(l);
+
+ Iterator<String> iter = l.iterator();
+ while (iter.hasNext()) {
+ String key = iter.next();
+ String text = mMessages.get(key).getText();
+ canvas.drawText(text, 10.0f, y, mPaint);
+ y += MESSAGE_Y_SPACING;
+ }
+ }
+
+ private int mTriangles;
+
+ /**
+ * Sets the number of triangles drawn in the previous frame for
+ * display by the showStatistics method. The number of triangles
+ * is not computed by GLView but must be supplied by the
+ * calling Activity.
+ *
+ * @param triangles an Activity-supplied estimate of the number of
+ * triangles drawn in the previous frame.
+ */
+ public void setNumTriangles(int triangles) {
+ this.mTriangles = triangles;
+ }
+
+ /**
+ * Displays statistics on frames and triangles per second. The
+ * GLContext.waitGL method should be called prior to calling this
+ * method.
+ *
+ * @param canvas the Canvas on which statistics are to appear.
+ * @param width the width of the Canvas.
+ */
+ public void showStatistics(Canvas canvas, int width) {
+ long endTime = mTimes[mTimesIdx] = System.currentTimeMillis();
+ mTimesIdx = (mTimesIdx + 1) % mFramesFPS;
+
+ float th = mPaint.getTextSize();
+
+ if (mDisplayFPS) {
+ // Use end time from mFramesFPS frames ago
+ long startTime = mTimes[mTimesIdx];
+ String fps = "" + (1000.0f*mFramesFPS/(endTime - startTime));
+
+ // Normalize fps to XX.XX format
+ if (fps.indexOf(".") == 1) {
+ fps = " " + fps;
+ }
+ int len = fps.length();
+ if (len == 2) {
+ fps += ".00";
+ } else if (len == 4) {
+ fps += "0";
+ } else if (len > 5) {
+ fps = fps.substring(0, 5);
+ }
+
+ canvas.drawText(fps + " fps", width - 60.0f, 10.0f, mPaint);
+ }
+
+ if (mDisplayCounts) {
+ canvas.drawText(mTriangles + " triangles",
+ width - 100.0f, 10.0f + th + 5, mPaint);
+ }
+ }
+
+ private void addMessage(String key, String text, int durationMillis) {
+ long expirationTime = System.currentTimeMillis() + durationMillis;
+
+ mMessages.put(key, new Message(text, expirationTime));
+ }
+
+ private void addMessage(String key, String text) {
+ addMessage(key, text, DEFAULT_DURATION_MILLIS);
+ }
+
+ private void addMessage(String text) {
+ addMessage(text, text, DEFAULT_DURATION_MILLIS);
+ }
+
+ private void clearMessages() {
+ mMessages.clear();
+ }
+
+ String command = "";
+
+ private void toggleFilter() {
+ if (params[mParam] == GL10.GL_NEAREST) {
+ params[mParam] = GL10.GL_LINEAR;
+ } else {
+ params[mParam] = GL10.GL_NEAREST;
+ }
+ addMessage(commands[mParam],
+ "Texture " +
+ (mParam == TEXTURE_MIN_FILTER ? "min" : "mag") +
+ " filter = " +
+ (params[mParam] == GL10.GL_NEAREST ?
+ "nearest" : "linear"));
+ }
+
+ private void togglePerspectiveCorrection() {
+ if (params[mParam] == GL10.GL_NICEST) {
+ params[mParam] = GL10.GL_FASTEST;
+ } else {
+ params[mParam] = GL10.GL_NICEST;
+ }
+ addMessage(commands[mParam],
+ "Texture perspective correction = " +
+ (params[mParam] == GL10.GL_FASTEST ?
+ "fastest" : "nicest"));
+ }
+
+ private String valueString() {
+ if (mParam == TEXTURE_MIN_FILTER ||
+ mParam == TEXTURE_MAG_FILTER) {
+ if (params[mParam] == GL10.GL_NEAREST) {
+ return "nearest";
+ }
+ if (params[mParam] == GL10.GL_LINEAR) {
+ return "linear";
+ }
+ }
+ if (mParam == TEXTURE_PERSPECTIVE_CORRECTION) {
+ if (params[mParam] == GL10.GL_FASTEST) {
+ return "fastest";
+ }
+ if (params[mParam] == GL10.GL_NICEST) {
+ return "nicest";
+ }
+ }
+ return "" + params[mParam];
+ }
+
+ /**
+ *
+ * @return true if the view
+ */
+ public boolean hasMessages() {
+ return mState == HAVE_TWO || mDisplayFPS || mDisplayCounts;
+ }
+
+ /**
+ * Process a key stroke. The calling Activity should pass all
+ * keys from its onKeyDown method to this method. If the key is
+ * part of a GLView command, true is returned and the calling
+ * Activity should ignore the key event. Otherwise, false is
+ * returned and the calling Activity may process the key event
+ * normally.
+ *
+ * @param keyCode the key code as passed to Activity.onKeyDown.
+ *
+ * @return true if the key is part of a GLView command sequence,
+ * false otherwise.
+ */
+ public boolean processKey(int keyCode) {
+ // Pressing the state key twice enters the UI
+ // Pressing it again exits the UI
+ if ((keyCode == STATE_KEY) ||
+ (keyCode == KeyEvent.KEYCODE_SLASH) ||
+ (keyCode == KeyEvent.KEYCODE_PERIOD))
+ {
+ mState = (mState + 1) % 3;
+ if (mState == HAVE_NONE) {
+ clearMessages();
+ }
+ if (mState == HAVE_TWO) {
+ clearMessages();
+ addMessage("aaaa", "GL", Integer.MAX_VALUE);
+ addMessage("aaab", "", Integer.MAX_VALUE);
+ command = "";
+ }
+ return true;
+ } else {
+ if (mState == HAVE_ONE) {
+ mState = HAVE_NONE;
+ return false;
+ }
+ }
+
+ // If we're not in the UI, exit without handling the key
+ if (mState != HAVE_TWO) {
+ return false;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ command = "";
+ } else if (keyCode == KeyEvent.KEYCODE_DEL) {
+ if (command.length() > 0) {
+ command = command.substring(0, command.length() - 1);
+ }
+
+ } else if (keyCode >= KeyEvent.KEYCODE_A &&
+ keyCode <= KeyEvent.KEYCODE_Z) {
+ command += "" + (char)(keyCode - KeyEvent.KEYCODE_A + 'a');
+ }
+
+ addMessage("aaaa", "GL " + command, Integer.MAX_VALUE);
+
+ if (command.equals("h")) {
+ addMessage("aaaa", "GL", Integer.MAX_VALUE);
+ addMessage("h - help");
+ addMessage("fn/ff - frustum near/far clip Z");
+ addMessage("la/lar/lag/lab - abmient intensity/r/g/b");
+ addMessage("ld/ldr/ldg/ldb - diffuse intensity/r/g/b");
+ addMessage("ls/lsr/lsg/lsb - specular intensity/r/g/b");
+ addMessage("s - toggle statistics display");
+ addMessage("tmin/tmag - texture min/mag filter");
+ addMessage("tpersp - texture perspective correction");
+ addMessage("tx/ty/tz - view translate x/y/z");
+ addMessage("z - zoom");
+ command = "";
+ return true;
+ } else if (command.equals("s")) {
+ mDisplayCounts = !mDisplayCounts;
+ mDisplayFPS = !mDisplayFPS;
+ command = "";
+ return true;
+ }
+
+ mParam = -1;
+ for (int i = 0; i < commands.length; i++) {
+ if (command.equals(commands[i])) {
+ mParam = i;
+ mIncr = increments[i];
+ }
+ }
+ if (mParam == -1) {
+ return true;
+ }
+
+ boolean addMessage = true;
+
+ // Increment or decrement
+ if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
+ keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+ if (mParam == ZOOM_EXPONENT) {
+ params[mParam] += mIncr;
+ computeZoom();
+ } else if ((mParam == TEXTURE_MIN_FILTER) ||
+ (mParam == TEXTURE_MAG_FILTER)) {
+ toggleFilter();
+ } else if (mParam == TEXTURE_PERSPECTIVE_CORRECTION) {
+ togglePerspectiveCorrection();
+ } else {
+ params[mParam] += mIncr;
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP ||
+ keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
+ if (mParam == ZOOM_EXPONENT) {
+ params[mParam] -= mIncr;
+ computeZoom();
+ } else if ((mParam == TEXTURE_MIN_FILTER) ||
+ (mParam == TEXTURE_MAG_FILTER)) {
+ toggleFilter();
+ } else if (mParam == TEXTURE_PERSPECTIVE_CORRECTION) {
+ togglePerspectiveCorrection();
+ } else {
+ params[mParam] -= mIncr;
+ }
+ }
+
+ if (addMessage) {
+ addMessage(commands[mParam],
+ labels[mParam] + ": " + valueString());
+ }
+
+ return true;
+ }
+
+ /**
+ * Zoom in by a given number of steps. A negative value of steps
+ * zooms out. Each step zooms in by 1%.
+ *
+ * @param steps the number of steps to zoom by.
+ */
+ public void zoom(int steps) {
+ params[ZOOM_EXPONENT] += steps;
+ computeZoom();
+ }
+
+ /**
+ * Set the projection matrix using glFrustumf. The left and right
+ * clipping planes are set at -+(aspectRatio*zoom), the bottom and
+ * top clipping planes are set at -+zoom, and the near and far
+ * clipping planes are set to the values set by setNearFrustum and
+ * setFarFrustum or interactively.
+ *
+ * <p> GL side effects:
+ * <ul>
+ * <li>overwrites the matrix mode</li>
+ * <li>overwrites the projection matrix</li>
+ * </ul>
+ *
+ * @param gl a GL10 instance whose projection matrix is to be modified.
+ */
+ public void setProjection(GL10 gl) {
+ gl.glMatrixMode(GL10.GL_PROJECTION);
+ gl.glLoadIdentity();
+
+ if (mAspectRatio >= 1.0f) {
+ gl.glFrustumf(-mAspectRatio*mZoom, mAspectRatio*mZoom,
+ -mZoom, mZoom,
+ params[NEAR_FRUSTUM], params[FAR_FRUSTUM]);
+ } else {
+ gl.glFrustumf(-mZoom, mZoom,
+ -mZoom / mAspectRatio, mZoom / mAspectRatio,
+ params[NEAR_FRUSTUM], params[FAR_FRUSTUM]);
+ }
+ }
+
+ /**
+ * Set the modelview matrix using glLoadIdentity and glTranslatef.
+ * The translation values are set interactively.
+ *
+ * <p> GL side effects:
+ * <ul>
+ * <li>overwrites the matrix mode</li>
+ * <li>overwrites the modelview matrix</li>
+ * </ul>
+ *
+ * @param gl a GL10 instance whose modelview matrix is to be modified.
+ */
+ public void setView(GL10 gl) {
+ gl.glMatrixMode(GL10.GL_MODELVIEW);
+ gl.glLoadIdentity();
+
+ // Move the viewpoint backwards
+ gl.glTranslatef(params[TRANSLATE_X],
+ params[TRANSLATE_Y],
+ params[TRANSLATE_Z]);
+ }
+
+ /**
+ * Sets texture parameters.
+ *
+ * <p> GL side effects:
+ * <ul>
+ * <li>sets the GL_PERSPECTIVE_CORRECTION_HINT</li>
+ * <li>sets the GL_TEXTURE_MIN_FILTER texture parameter</li>
+ * <li>sets the GL_TEXTURE_MAX_FILTER texture parameter</li>
+ * </ul>
+ *
+ * @param gl a GL10 instance whose texture parameters are to be modified.
+ */
+ public void setTextureParameters(GL10 gl) {
+ gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+ (int)params[TEXTURE_PERSPECTIVE_CORRECTION]);
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D,
+ GL10.GL_TEXTURE_MIN_FILTER,
+ params[TEXTURE_MIN_FILTER]);
+ gl.glTexParameterf(GL10.GL_TEXTURE_2D,
+ GL10.GL_TEXTURE_MAG_FILTER,
+ params[TEXTURE_MAG_FILTER]);
+ }
+
+ /**
+ * Sets the lighting parameters for the given light.
+ *
+ * <p> GL side effects:
+ * <ul>
+ * <li>sets the GL_LIGHT_MODEL_AMBIENT intensities
+ * <li>sets the GL_AMBIENT intensities for the given light</li>
+ * <li>sets the GL_DIFFUSE intensities for the given light</li>
+ * <li>sets the GL_SPECULAR intensities for the given light</li>
+ * </ul>
+ *
+ * @param gl a GL10 instance whose texture parameters are to be modified.
+ */
+ public void setLights(GL10 gl, int lightNum) {
+ float[] light = new float[4];
+ light[3] = 1.0f;
+
+ float lmi = params[LIGHT_MODEL_AMBIENT_INTENSITY];
+ light[0] = params[LIGHT_MODEL_AMBIENT_RED]*lmi;
+ light[1] = params[LIGHT_MODEL_AMBIENT_GREEN]*lmi;
+ light[2] = params[LIGHT_MODEL_AMBIENT_BLUE]*lmi;
+ gl.glLightModelfv(GL10.GL_LIGHT_MODEL_AMBIENT, light, 0);
+
+ float ai = params[AMBIENT_INTENSITY];
+ light[0] = params[AMBIENT_RED]*ai;
+ light[1] = params[AMBIENT_GREEN]*ai;
+ light[2] = params[AMBIENT_BLUE]*ai;
+ gl.glLightfv(lightNum, GL10.GL_AMBIENT, light, 0);
+
+ float di = params[DIFFUSE_INTENSITY];
+ light[0] = params[DIFFUSE_RED]*di;
+ light[1] = params[DIFFUSE_GREEN]*di;
+ light[2] = params[DIFFUSE_BLUE]*di;
+ gl.glLightfv(lightNum, GL10.GL_DIFFUSE, light, 0);
+
+ float si = params[SPECULAR_INTENSITY];
+ light[0] = params[SPECULAR_RED]*si;
+ light[1] = params[SPECULAR_GREEN]*si;
+ light[2] = params[SPECULAR_BLUE]*si;
+ gl.glLightfv(lightNum, GL10.GL_SPECULAR, light, 0);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/GlobalTime.java b/samples/GlobalTime/src/com/android/globaltime/GlobalTime.java
new file mode 100644
index 000000000..d96b644a9
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/GlobalTime.java
@@ -0,0 +1,1488 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.microedition.khronos.egl.*;
+import javax.microedition.khronos.opengles.*;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Canvas;
+import android.opengl.Object3D;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * The main View of the GlobalTime Activity.
+ */
+class GTView extends SurfaceView implements SurfaceHolder.Callback {
+
+ /**
+ * A TimeZone object used to compute the current UTC time.
+ */
+ private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("utc");
+
+ /**
+ * The Sun's color is close to that of a 5780K blackbody.
+ */
+ private static final float[] SUNLIGHT_COLOR = {
+ 1.0f, 0.9375f, 0.91015625f, 1.0f
+ };
+
+ /**
+ * The inclination of the earth relative to the plane of the ecliptic
+ * is 23.45 degrees.
+ */
+ private static final float EARTH_INCLINATION = 23.45f * Shape.PI / 180.0f;
+
+ /** Seconds in a day */
+ private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+ /** Flag for the depth test */
+ private static final boolean PERFORM_DEPTH_TEST= false;
+
+ /** Use 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 boolean USE_RAW_OFFSETS = true;
+
+ /**
+ * The earth's atmosphere.
+ */
+ private static final Annulus ATMOSPHERE =
+ new Annulus(0.0f, 0.0f, 1.75f, 0.9f, 1.08f, 0.4f, 0.4f, 0.8f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f, 50);
+
+ /**
+ * The tesselation of the earth by latitude.
+ */
+ private static final int SPHERE_LATITUDES = 25;
+
+ /**
+ * The tesselation of the earth by longitude.
+ */
+ private static int SPHERE_LONGITUDES = 25;
+
+ /**
+ * A 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 static Sphere worldFlat = new LatLongSphere(0.0f, 0.0f, 0.0f, 1.0f,
+ SPHERE_LATITUDES, SPHERE_LONGITUDES,
+ 0.0f, 360.0f, true, true, false, true);
+
+ /**
+ * The earth.
+ */
+ private Object3D mWorld;
+
+ /**
+ * Geometry of the city lights
+ */
+ private PointCloud mLights;
+
+ /**
+ * True if the activiy has been initialized.
+ */
+ boolean mInitialized = false;
+
+ /**
+ * True if we're in alphabetic entry mode.
+ */
+ private boolean mAlphaKeySet = false;
+
+ private EGLContext mEGLContext;
+ private EGLSurface mEGLSurface;
+ private EGLDisplay mEGLDisplay;
+ private EGLConfig mEGLConfig;
+ GLView mGLView;
+
+ // Rotation and tilt of the Earth
+ private float mRotAngle = 0.0f;
+ private float mTiltAngle = 0.0f;
+
+ // Rotational velocity of the orbiting viewer
+ private float mRotVelocity = 1.0f;
+
+ // Rotation of the flat view
+ private float mWrapX = 0.0f;
+ private float mWrapVelocity = 0.0f;
+ private float mWrapVelocityFactor = 0.01f;
+
+ // Toggle switches
+ private boolean mDisplayAtmosphere = true;
+ private boolean mDisplayClock = false;
+ private boolean mClockShowing = false;
+ private boolean mDisplayLights = false;
+ private boolean mDisplayWorld = true;
+ private boolean mDisplayWorldFlat = false;
+ private boolean mSmoothShading = true;
+
+ // City search string
+ private String mCityName = "";
+
+ // List of all cities
+ private List<City> mClockCities;
+
+ // List of cities matching a user-supplied prefix
+ private List<City> mCityNameMatches = new ArrayList<City>();
+
+ private List<City> mCities;
+
+ // Start time for clock fade animation
+ private long mClockFadeTime;
+
+ // Interpolator for clock fade animation
+ private Interpolator mClockSizeInterpolator =
+ new DecelerateInterpolator(1.0f);
+
+ // Index of current clock
+ private int mCityIndex;
+
+ // Current clock
+ private Clock mClock;
+
+ // City-to-city flight animation parameters
+ private boolean mFlyToCity = false;
+ private long mCityFlyStartTime;
+ private float mCityFlightTime;
+ private float mRotAngleStart, mRotAngleDest;
+ private float mTiltAngleStart, mTiltAngleDest;
+
+ // Interpolator for flight motion animation
+ private Interpolator mFlyToCityInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ private static int sNumLights;
+ private static int[] sLightCoords;
+
+ // static Map<Float,int[]> cityCoords = new HashMap<Float,int[]>();
+
+ // Arrays for GL calls
+ private float[] mClipPlaneEquation = new float[4];
+ private float[] mLightDir = new float[4];
+
+ // Calendar for computing the Sun's position
+ Calendar mSunCal = Calendar.getInstance(UTC_TIME_ZONE);
+
+ // Triangles drawn per frame
+ private int mNumTriangles;
+
+ private long startTime;
+
+ private static final int MOTION_NONE = 0;
+ private static final int MOTION_X = 1;
+ private static final int MOTION_Y = 2;
+
+ private static final int MIN_MANHATTAN_DISTANCE = 20;
+ private static final float ROTATION_FACTOR = 1.0f / 30.0f;
+ private static final float TILT_FACTOR = 0.35f;
+
+ // Touchscreen support
+ private float mMotionStartX;
+ private float mMotionStartY;
+ private float mMotionStartRotVelocity;
+ private float mMotionStartTiltAngle;
+ private int mMotionDirection;
+
+ private boolean mPaused = true;
+ private boolean mHaveSurface = false;
+ private boolean mStartAnimating = false;
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ mHaveSurface = true;
+ startEGL();
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mHaveSurface = false;
+ stopEGL();
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ // nothing to do
+ }
+
+ /**
+ * Set up the view.
+ *
+ * @param context the Context
+ * @param am an AssetManager to retrieve the city database from
+ */
+ public GTView(Context context) {
+ super(context);
+
+ getHolder().addCallback(this);
+ getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
+
+ startTime = System.currentTimeMillis();
+
+ mClock = new Clock();
+
+ startEGL();
+
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+ requestFocus();
+ }
+
+ /**
+ * 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().
+ */
+ private void startEGL() {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * Destroys the egl context. If an egl surface has been created, it is
+ * destroyed as well.
+ */
+ private void stopEGL() {
+ 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 onPause() {
+ mPaused = true;
+ stopAnimating();
+ stopEGL();
+ }
+
+ public void onResume() {
+ mPaused = false;
+ startEGL();
+ }
+
+ public void destroy() {
+ stopAnimating();
+ stopEGL();
+ }
+
+ /**
+ * Begin animation.
+ */
+ public void startAnimating() {
+ if (mEGLSurface == null) {
+ mStartAnimating = true; // will start when egl surface is created
+ } else {
+ mHandler.sendEmptyMessage(INVALIDATE);
+ }
+ }
+
+ /**
+ * Quit animation.
+ */
+ public void stopAnimating() {
+ mHandler.removeMessages(INVALIDATE);
+ }
+
+ /**
+ * Read a two-byte integer from the input stream.
+ */
+ private int readInt16(InputStream is) throws IOException {
+ int lo = is.read();
+ int hi = is.read();
+ return (hi << 8) | lo;
+ }
+
+ /**
+ * Returns the offset from UTC for the given city. If USE_RAW_OFFSETS
+ * is true, summer/daylight savings is ignored.
+ */
+ private static float getOffset(City c) {
+ return USE_RAW_OFFSETS ? c.getRawOffset() : c.getOffset();
+ }
+
+ private InputStream cache(InputStream is) throws IOException {
+ 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);
+ }
+
+ /**
+ * Load the city and lights databases.
+ *
+ * @param am the AssetManager to load from.
+ */
+ private void loadAssets(final AssetManager am) throws IOException {
+ 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);
+ }
+
+ /**
+ * Returns true if two time zone offsets are equal. We assume distinct
+ * time zone offsets will differ by at least a few minutes.
+ */
+ private boolean tzEqual(float o1, float o2) {
+ return Math.abs(o1 - o2) < 0.001;
+ }
+
+ /**
+ * Move to a different time zone.
+ *
+ * @param incr The increment between the current and future time zones.
+ */
+ private void shiftTimeZone(int incr) {
+ // 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();
+ }
+
+ /**
+ * Returns true if there is another city within the current time zone
+ * that is the given increment away from the current city.
+ *
+ * @param incr the increment, +1 or -1
+ * @return
+ */
+ private boolean atEndOfTimeZone(int incr) {
+ 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;
+ }
+
+ /**
+ * Shifts cities within the current time zone.
+ *
+ * @param incr the increment, +1 or -1
+ */
+ private void shiftWithinTimeZone(int incr) {
+ float offset = getOffset(mCities.get(mCityIndex));
+ int nindex = (mCityIndex + mCities.size() + incr) % mCities.size();
+ if (tzEqual(getOffset(mCities.get(nindex)), offset)) {
+ mCityIndex = nindex;
+ goToCity();
+ }
+ }
+
+ /**
+ * Returns true if the city name matches the given prefix, ignoring spaces.
+ */
+ private boolean nameMatches(City city, String prefix) {
+ String cityName = city.getName().replaceAll("[ ]", "");
+ return prefix.regionMatches(true, 0,
+ cityName, 0,
+ prefix.length());
+ }
+
+ /**
+ * Returns true if there are cities matching the given name prefix.
+ */
+ private boolean hasMatches(String prefix) {
+ for (int i = 0; i < mClockCities.size(); i++) {
+ City city = mClockCities.get(i);
+ if (nameMatches(city, prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Shifts to the nearest city that matches the new prefix.
+ */
+ private void shiftByName() {
+ // 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();
+ }
+
+ /**
+ * Increases or decreases the rotational speed of the earth.
+ */
+ private void incrementRotationalVelocity(float incr) {
+ if (mDisplayWorldFlat) {
+ mWrapVelocity -= incr;
+ } else {
+ mRotVelocity -= incr;
+ }
+ }
+
+ /**
+ * Clears the current matching prefix, while keeping the focus on
+ * the current city.
+ */
+ private void clearCityMatches() {
+ // 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();
+ }
+
+ /**
+ * Fade the clock in or out.
+ */
+ private void enableClock(boolean enabled) {
+ mClockFadeTime = System.currentTimeMillis();
+ mDisplayClock = enabled;
+ mClockShowing = true;
+ mAlphaKeySet = enabled;
+ if (enabled) {
+ // Find the closest matching city
+ locateCity(false, 0.0f);
+ }
+ clearCityMatches();
+ }
+
+ /**
+ * Use the touchscreen to alter the rotational velocity or the
+ * tilt of the earth.
+ */
+ @Override public boolean onTouchEvent(MotionEvent event) {
+ 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;
+ }
+
+ @Override public boolean onKeyDown(int keyCode, 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;
+ }
+
+ /**
+ * Initialize OpenGL ES drawing.
+ */
+ private synchronized void init(GL10 gl) {
+ 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;
+ }
+
+ /**
+ * Computes the vector from the center of the earth to the sun for a
+ * particular moment in time.
+ */
+ private void computeSunDirection() {
+ 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;
+ }
+
+ /**
+ * Computes the approximate spherical distance between two
+ * (latitude, longitude) coordinates.
+ */
+ private float distance(float lat1, float lon1,
+ float lat2, float lon2) {
+ 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);
+ }
+
+ /**
+ * Locates the closest city to the currently displayed center point,
+ * optionally restricting the search to cities within a given time zone.
+ */
+ private void locateCity(boolean useOffset, float offset) {
+ 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;
+ }
+
+ /**
+ * Animates the earth to be centered at the current city.
+ */
+ private void goToCity() {
+ 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;
+ }
+
+ /**
+ * Returns a linearly interpolated value between two values.
+ */
+ private float lerp(float a, float b, float lerp) {
+ return a + (b - a)*lerp;
+ }
+
+ /**
+ * Draws the city lights, using a clip plane to restrict the lights
+ * to the night side of the earth.
+ */
+ private void drawCityLights(GL10 gl, float brightness) {
+ 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;
+ }
+
+ /**
+ * Draws the atmosphere.
+ */
+ private void drawAtmosphere(GL10 gl) {
+ 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();
+ }
+
+ /**
+ * Draws the world in a 2D map view.
+ */
+ private void drawWorldFlat(GL10 gl) {
+ 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;
+ }
+ }
+
+ /**
+ * Draws the world in a 2D round view.
+ */
+ private void drawWorldRound(GL10 gl) {
+ 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();
+ }
+
+ /**
+ * Draws the clock.
+ *
+ * @param canvas the Canvas to draw to
+ * @param now the current time
+ * @param w the width of the screen
+ * @param h the height of the screen
+ * @param lerp controls the animation, between 0.0 and 1.0
+ */
+ private void drawClock(Canvas canvas,
+ long now,
+ int w, int h,
+ float lerp) {
+ 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());
+ }
+ }
+
+ /**
+ * Draws the 2D layer.
+ */
+ @Override protected void onDraw(Canvas canvas) {
+ 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);
+ }
+
+ /**
+ * Draws the 3D layer.
+ */
+ protected void drawOpenGLScene() {
+ 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 static final int INVALIDATE = 1;
+ private static final int ONE_MINUTE = 60000;
+
+ /**
+ * Controls the animation using the message queue. Every time we receive
+ * an INVALIDATE message, we redraw and place another message in the queue.
+ */
+ private final Handler mHandler = new Handler() {
+ private long mLastSunPositionTime = 0;
+
+ @Override public void handleMessage(Message msg) {
+ if (msg.what == INVALIDATE) {
+
+ // Use the message's time, it's good enough and
+ // allows us to avoid a system call.
+ if ((msg.getWhen() - mLastSunPositionTime) >= ONE_MINUTE) {
+ // Recompute the sun's position once per minute
+ // Place the light at the Sun's direction
+ computeSunDirection();
+ mLastSunPositionTime = msg.getWhen();
+ }
+
+ // Draw the GL scene
+ drawOpenGLScene();
+
+ // Send an update for the 2D overlay if needed
+ if (mInitialized &&
+ (mClockShowing || mGLView.hasMessages())) {
+ invalidate();
+ }
+
+ // Just send another message immediately. This works because
+ // drawOpenGLScene() does the timing for us -- it will
+ // block until the last frame has been processed.
+ // The invalidate message we're posting here will be
+ // interleaved properly with motion/key events which
+ // guarantee a prompt reaction to the user input.
+ sendEmptyMessage(INVALIDATE);
+ }
+ }
+ };
+}
+
+/**
+ * The main activity class for GlobalTime.
+ */
+public class GlobalTime extends Activity {
+
+ GTView gtView = null;
+
+ @Override protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ gtView = new GTView(this);
+ setContentView(gtView);
+ }
+
+ @Override protected void onResume() {
+ super.onResume();
+ gtView.onResume();
+ Looper.myQueue().addIdleHandler(new Idler());
+ }
+
+ @Override protected void onPause() {
+ super.onPause();
+ gtView.onPause();
+ }
+
+ @Override protected void onStop() {
+ super.onStop();
+ gtView.destroy();
+ gtView = null;
+ }
+
+ // Allow the activity to go idle before its animation starts
+ class Idler implements MessageQueue.IdleHandler {
+ public Idler() {
+ super();
+ }
+
+ public final boolean queueIdle() {
+ if (gtView != null) {
+ gtView.startAnimating();
+ }
+ return false;
+ }
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/LatLongSphere.java b/samples/GlobalTime/src/com/android/globaltime/LatLongSphere.java
new file mode 100644
index 000000000..b455d41c0
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/LatLongSphere.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+public class LatLongSphere extends Sphere {
+
+ public LatLongSphere(float centerX, float centerY, float centerZ,
+ float radius, int lats, int longs,
+ float minLongitude, float maxLongitude,
+ boolean emitTextureCoordinates,
+ boolean emitNormals,
+ boolean emitColors,
+ boolean flatten) {
+ super(emitTextureCoordinates, emitNormals, emitColors);
+
+ int tris = 2 * (lats - 1) * (longs - 1);
+ int[] vertices = new int[3 * lats * longs];
+ int[] texcoords = new int[2 * lats * longs];
+ int[] colors = new int[4 * lats * longs];
+ int[] normals = new int[3 * lats * longs];
+ short[] indices = new short[3 * tris];
+
+ int vidx = 0;
+ int tidx = 0;
+ int nidx = 0;
+ int cidx = 0;
+ int iidx = 0;
+
+ minLongitude *= DEGREES_TO_RADIANS;
+ maxLongitude *= DEGREES_TO_RADIANS;
+
+ for (int i = 0; i < longs; i++) {
+ float fi = (float) i / (longs - 1);
+ // theta is the longitude
+ float theta =
+ (maxLongitude - minLongitude) * (1.0f - fi) + minLongitude;
+ float sinTheta = (float) Math.sin(theta);
+ float cosTheta = (float) Math.cos(theta);
+
+ for (int j = 0; j < lats; j++) {
+ float fj = (float) j / (lats - 1);
+ // phi is the latitude
+ float phi = PI * fj;
+ float sinPhi = (float) Math.sin(phi);
+ float cosPhi = (float) Math.cos(phi);
+ float x = cosTheta * sinPhi;
+ float y = cosPhi;
+ float z = sinTheta * sinPhi;
+
+ if (flatten) {
+ // Place vertices onto a flat projection
+ vertices[vidx++] = toFixed(2.0f * fi - 1.0f);
+ vertices[vidx++] = toFixed(0.5f - fj);
+ vertices[vidx++] = toFixed(0.0f);
+ } else {
+ // Place vertices onto the surface of a sphere
+ // with the given center and radius
+ vertices[vidx++] = toFixed(x * radius + centerX);
+ vertices[vidx++] = toFixed(y * radius + centerY);
+ vertices[vidx++] = toFixed(z * radius + centerZ);
+ }
+
+ if (emitTextureCoordinates) {
+ texcoords[tidx++] = toFixed(1.0f - (theta / (TWO_PI)));
+ texcoords[tidx++] = toFixed(fj);
+ }
+
+ if (emitNormals) {
+ float norm = 1.0f / Shape.length(x, y, z);
+ normals[nidx++] = toFixed(x * norm);
+ normals[nidx++] = toFixed(y * norm);
+ normals[nidx++] = toFixed(z * norm);
+ }
+
+ // 0 == black, 65536 == white
+ if (emitColors) {
+ colors[cidx++] = (i % 2) * 65536;
+ colors[cidx++] = 0;
+ colors[cidx++] = (j % 2) * 65536;
+ colors[cidx++] = 65536;
+ }
+ }
+ }
+
+ for (int i = 0; i < longs - 1; i++) {
+ for (int j = 0; j < lats - 1; j++) {
+ int base = i * lats + j;
+
+ // Ensure both triangles have the same final vertex
+ // since this vertex carries the color for flat
+ // shading
+ indices[iidx++] = (short) (base);
+ indices[iidx++] = (short) (base + 1);
+ indices[iidx++] = (short) (base + lats + 1);
+
+ indices[iidx++] = (short) (base + lats);
+ indices[iidx++] = (short) (base);
+ indices[iidx++] = (short) (base + lats + 1);
+ }
+ }
+
+ allocateBuffers(vertices, texcoords, normals, colors, indices);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/PointCloud.java b/samples/GlobalTime/src/com/android/globaltime/PointCloud.java
new file mode 100644
index 000000000..6f4fd5558
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/PointCloud.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A class representing a set of GL_POINT objects. GlobalTime uses this class
+ * to draw city lights on the night side of the earth.
+ */
+public class PointCloud extends Shape {
+
+ /**
+ * Constructs a PointCloud with a point at each of the given vertex
+ * (x, y, z) positions.
+ * @param vertices an array of (x, y, z) positions given in fixed-point.
+ */
+ public PointCloud(int[] vertices) {
+ this(vertices, 0, vertices.length);
+ }
+
+ /**
+ * Constructs a PointCloud with a point at each of the given vertex
+ * (x, y, z) positions.
+ * @param vertices an array of (x, y, z) positions given in fixed-point.
+ * @param off the starting offset of the vertices array
+ * @param len the number of elements of the vertices array to use
+ */
+ public PointCloud(int[] vertices, int off, int len) {
+ super(GL10.GL_POINTS, GL10.GL_UNSIGNED_SHORT,
+ false, false, false);
+
+ int numPoints = len / 3;
+ short[] indices = new short[numPoints];
+ for (int i = 0; i < numPoints; i++) {
+ indices[i] = (short)i;
+ }
+
+ allocateBuffers(vertices, null, null, null, indices);
+ this.mNumIndices = mIndexBuffer.capacity();
+ }
+
+ @Override public int getNumTriangles() {
+ return mNumIndices * 2;
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/Shape.java b/samples/GlobalTime/src/com/android/globaltime/Shape.java
new file mode 100644
index 000000000..6c296ce5e
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/Shape.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * An abstract superclass for various three-dimensional objects to be drawn
+ * using OpenGL ES. Each subclass is responsible for setting up NIO buffers
+ * containing vertices, texture coordinates, colors, normals, and indices.
+ * The {@link #draw(GL10)} method draws the object to the given OpenGL context.
+ */
+public abstract class Shape {
+
+ public static final int INT_BYTES = 4;
+ public static final int SHORT_BYTES = 2;
+
+ public static final float DEGREES_TO_RADIANS = (float) Math.PI / 180.0f;
+ public static final float PI = (float) Math.PI;
+ public static final float TWO_PI = (float) (2.0 * Math.PI);
+ public static final float PI_OVER_TWO = (float) (Math.PI / 2.0);
+
+ protected int mPrimitive;
+ protected int mIndexDatatype;
+
+ protected boolean mEmitTextureCoordinates;
+ protected boolean mEmitNormals;
+ protected boolean mEmitColors;
+
+ protected IntBuffer mVertexBuffer;
+ protected IntBuffer mTexcoordBuffer;
+ protected IntBuffer mColorBuffer;
+ protected IntBuffer mNormalBuffer;
+ protected Buffer mIndexBuffer;
+ protected int mNumIndices = -1;
+
+ /**
+ * Constructs a Shape.
+ *
+ * @param primitive a GL primitive type understood by glDrawElements,
+ * such as GL10.GL_TRIANGLES
+ * @param indexDatatype the GL datatype for the index buffer, such as
+ * GL10.GL_UNSIGNED_SHORT
+ * @param emitTextureCoordinates true to enable use of the texture
+ * coordinate buffer
+ * @param emitNormals true to enable use of the normal buffer
+ * @param emitColors true to enable use of the color buffer
+ */
+ protected Shape(int primitive,
+ int indexDatatype,
+ boolean emitTextureCoordinates,
+ boolean emitNormals,
+ boolean emitColors) {
+ mPrimitive = primitive;
+ mIndexDatatype = indexDatatype;
+ mEmitTextureCoordinates = emitTextureCoordinates;
+ mEmitNormals = emitNormals;
+ mEmitColors = emitColors;
+ }
+
+ /**
+ * Converts the given floating-point value to fixed-point.
+ */
+ public static int toFixed(float x) {
+ return (int) (x * 65536.0);
+ }
+
+ /**
+ * Converts the given fixed-point value to floating-point.
+ */
+ public static float toFloat(int x) {
+ return (float) (x / 65536.0);
+ }
+
+ /**
+ * Computes the cross-product of two vectors p and q and places
+ * the result in out.
+ */
+ public static void cross(float[] p, float[] q, float[] out) {
+ out[0] = p[1] * q[2] - p[2] * q[1];
+ out[1] = p[2] * q[0] - p[0] * q[2];
+ out[2] = p[0] * q[1] - p[1] * q[0];
+ }
+
+ /**
+ * Returns the length of a vector, given as three floats.
+ */
+ public static float length(float vx, float vy, float vz) {
+ return (float) Math.sqrt(vx * vx + vy * vy + vz * vz);
+ }
+
+ /**
+ * Returns the length of a vector, given as an array of three floats.
+ */
+ public static float length(float[] v) {
+ return length(v[0], v[1], v[2]);
+ }
+
+ /**
+ * Normalizes the given vector of three floats to have length == 1.0.
+ * Vectors with length zero are unaffected.
+ */
+ public static void normalize(float[] v) {
+ float length = length(v);
+ if (length != 0.0f) {
+ float norm = 1.0f / length;
+ v[0] *= norm;
+ v[1] *= norm;
+ v[2] *= norm;
+ }
+ }
+
+ /**
+ * Returns the number of triangles associated with this shape.
+ */
+ public int getNumTriangles() {
+ if (mPrimitive == GL10.GL_TRIANGLES) {
+ return mIndexBuffer.capacity() / 3;
+ } else if (mPrimitive == GL10.GL_TRIANGLE_STRIP) {
+ return mIndexBuffer.capacity() - 2;
+ }
+ return 0;
+ }
+
+ /**
+ * Copies the given data into the instance
+ * variables mVertexBuffer, mTexcoordBuffer, mNormalBuffer, mColorBuffer,
+ * and mIndexBuffer.
+ *
+ * @param vertices an array of fixed-point vertex coordinates
+ * @param texcoords an array of fixed-point texture coordinates
+ * @param normals an array of fixed-point normal vector coordinates
+ * @param colors an array of fixed-point color channel values
+ * @param indices an array of short indices
+ */
+ public void allocateBuffers(int[] vertices, int[] texcoords, int[] normals,
+ int[] colors, short[] indices) {
+ allocate(vertices, texcoords, normals, colors);
+
+ ByteBuffer ibb =
+ ByteBuffer.allocateDirect(indices.length * SHORT_BYTES);
+ ibb.order(ByteOrder.nativeOrder());
+ ShortBuffer shortIndexBuffer = ibb.asShortBuffer();
+ shortIndexBuffer.put(indices);
+ shortIndexBuffer.position(0);
+ this.mIndexBuffer = shortIndexBuffer;
+ }
+
+ /**
+ * Copies the given data into the instance
+ * variables mVertexBuffer, mTexcoordBuffer, mNormalBuffer, mColorBuffer,
+ * and mIndexBuffer.
+ *
+ * @param vertices an array of fixed-point vertex coordinates
+ * @param texcoords an array of fixed-point texture coordinates
+ * @param normals an array of fixed-point normal vector coordinates
+ * @param colors an array of fixed-point color channel values
+ * @param indices an array of int indices
+ */
+ public void allocateBuffers(int[] vertices, int[] texcoords, int[] normals,
+ int[] colors, int[] indices) {
+ allocate(vertices, texcoords, normals, colors);
+
+ ByteBuffer ibb =
+ ByteBuffer.allocateDirect(indices.length * INT_BYTES);
+ ibb.order(ByteOrder.nativeOrder());
+ IntBuffer intIndexBuffer = ibb.asIntBuffer();
+ intIndexBuffer.put(indices);
+ intIndexBuffer.position(0);
+ this.mIndexBuffer = intIndexBuffer;
+ }
+
+ /**
+ * Allocate the vertex, texture coordinate, normal, and color buffer.
+ */
+ private void allocate(int[] vertices, int[] texcoords, int[] normals,
+ int[] colors) {
+ ByteBuffer vbb =
+ ByteBuffer.allocateDirect(vertices.length * INT_BYTES);
+ vbb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = vbb.asIntBuffer();
+ mVertexBuffer.put(vertices);
+ mVertexBuffer.position(0);
+
+ if ((texcoords != null) && mEmitTextureCoordinates) {
+ ByteBuffer tbb =
+ ByteBuffer.allocateDirect(texcoords.length * INT_BYTES);
+ tbb.order(ByteOrder.nativeOrder());
+ mTexcoordBuffer = tbb.asIntBuffer();
+ mTexcoordBuffer.put(texcoords);
+ mTexcoordBuffer.position(0);
+ }
+
+ if ((normals != null) && mEmitNormals) {
+ ByteBuffer nbb =
+ ByteBuffer.allocateDirect(normals.length * INT_BYTES);
+ nbb.order(ByteOrder.nativeOrder());
+ mNormalBuffer = nbb.asIntBuffer();
+ mNormalBuffer.put(normals);
+ mNormalBuffer.position(0);
+ }
+
+ if ((colors != null) && mEmitColors) {
+ ByteBuffer cbb =
+ ByteBuffer.allocateDirect(colors.length * INT_BYTES);
+ cbb.order(ByteOrder.nativeOrder());
+ mColorBuffer = cbb.asIntBuffer();
+ mColorBuffer.put(colors);
+ mColorBuffer.position(0);
+ }
+ }
+
+ /**
+ * Draws the shape to the given OpenGL ES 1.0 context. Texture coordinates,
+ * normals, and colors are emitted according the the preferences set for
+ * this shape.
+ */
+ public void draw(GL10 gl) {
+ gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+
+ if (mEmitTextureCoordinates) {
+ gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+ gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, mTexcoordBuffer);
+ gl.glEnable(GL10.GL_TEXTURE_2D);
+ } else {
+ gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+ gl.glDisable(GL10.GL_TEXTURE_2D);
+ }
+
+ if (mEmitNormals) {
+ gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
+ gl.glNormalPointer(GL10.GL_FIXED, 0, mNormalBuffer);
+ } else {
+ gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
+ }
+
+ if (mEmitColors) {
+ gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
+ gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
+ } else {
+ gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
+ }
+
+ gl.glDrawElements(mPrimitive,
+ mNumIndices > 0 ? mNumIndices : mIndexBuffer.capacity(),
+ mIndexDatatype,
+ mIndexBuffer);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/Sphere.java b/samples/GlobalTime/src/com/android/globaltime/Sphere.java
new file mode 100644
index 000000000..4dff05d08
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/Sphere.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import javax.microedition.khronos.opengles.GL10;
+
+public class Sphere extends Shape {
+
+ public Sphere(boolean emitTextureCoordinates,
+ boolean emitNormals, boolean emitColors) {
+ super(GL10.GL_TRIANGLES, GL10.GL_UNSIGNED_SHORT,
+ emitTextureCoordinates, emitNormals, emitColors);
+ }
+}
diff --git a/samples/GlobalTime/src/com/android/globaltime/Texture.java b/samples/GlobalTime/src/com/android/globaltime/Texture.java
new file mode 100644
index 000000000..ee3af0007
--- /dev/null
+++ b/samples/GlobalTime/src/com/android/globaltime/Texture.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globaltime;
+
+import java.nio.ByteBuffer;
+
+public class Texture {
+
+ private ByteBuffer data;
+ private int width, height;
+
+ public Texture(ByteBuffer data, int width, int height) {
+ this.data = data;
+ this.width = width;
+ this.height = height;
+ }
+
+ public ByteBuffer getData() {
+ return data;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+}