diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
| commit | 52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98 (patch) | |
| tree | 2eb01432fa3da41550dd622d29fb672509122b51 /samples/GlobalTime | |
| parent | d4aee0c0caa00aa02d4c50ed28151591ac0456b5 (diff) | |
| download | android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.gz android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.tar.bz2 android_development-52d4c30ca52320ec92d1d1ddc8db3f07f69c4f98.zip | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'samples/GlobalTime')
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 Binary files differnew file mode 100644 index 000000000..c982ef47f --- /dev/null +++ b/samples/GlobalTime/assets/cities_en.dat diff --git a/samples/GlobalTime/assets/earth.raw b/samples/GlobalTime/assets/earth.raw Binary files differnew file mode 100644 index 000000000..f884e7572 --- /dev/null +++ b/samples/GlobalTime/assets/earth.raw diff --git a/samples/GlobalTime/assets/lights.dat b/samples/GlobalTime/assets/lights.dat Binary files differnew file mode 100644 index 000000000..14d4f3668 --- /dev/null +++ b/samples/GlobalTime/assets/lights.dat diff --git a/samples/GlobalTime/assets/tz512.raw b/samples/GlobalTime/assets/tz512.raw Binary files differnew file mode 100644 index 000000000..3ce1d5c2d --- /dev/null +++ b/samples/GlobalTime/assets/tz512.raw diff --git a/samples/GlobalTime/assets/world.gles b/samples/GlobalTime/assets/world.gles Binary files differnew file mode 100644 index 000000000..815db1ed6 --- /dev/null +++ b/samples/GlobalTime/assets/world.gles diff --git a/samples/GlobalTime/res/drawable/app_global_time.png b/samples/GlobalTime/res/drawable/app_global_time.png Binary files differnew file mode 100644 index 000000000..914977d51 --- /dev/null +++ b/samples/GlobalTime/res/drawable/app_global_time.png 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; + } +} |
