1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.wallpaper.grass;
18
19import android.renderscript.Sampler;
20import static android.renderscript.ProgramStore.DepthFunc.*;
21import static android.renderscript.ProgramStore.BlendSrcFunc;
22import static android.renderscript.ProgramStore.BlendDstFunc;
23import android.renderscript.*;
24import static android.renderscript.Element.*;
25import static android.util.MathUtils.*;
26import android.renderscript.Mesh.Primitive;
27import static android.renderscript.Sampler.Value.*;
28import android.content.Context;
29import android.content.IntentFilter;
30import android.content.Intent;
31import android.content.BroadcastReceiver;
32import android.location.LocationManager;
33import android.location.LocationListener;
34import android.location.Location;
35import android.os.Bundle;
36import android.text.format.Time;
37import com.android.wallpaper.R;
38import com.android.wallpaper.RenderScriptScene;
39
40import java.util.TimeZone;
41import java.util.Calendar;
42
43class GrassRS extends RenderScriptScene {
44    @SuppressWarnings({"UnusedDeclaration"})
45    private static final String LOG_TAG = "Grass";
46    private static final boolean DEBUG = false;
47
48    private static final int LOCATION_UPDATE_MIN_TIME = DEBUG ? 5 * 60 * 1000 : 60 * 60 * 1000; // 1 hour
49    private static final int LOCATION_UPDATE_MIN_DISTANCE = DEBUG ? 10 : 150 * 1000; // 150 km
50    private static final float TESSELATION = 0.5f;
51    private static final int TEXTURES_COUNT = 5;
52    private static final int BLADES_COUNT = 200;
53
54    private ScriptField_Blade mBlades;
55    private ScriptField_Vertex mVertexBuffer;
56    private ProgramVertexFixedFunction.Constants mPvOrthoAlloc;
57
58    //private Allocation mBladesBuffer;
59    private Allocation mBladesIndicies;
60    private Mesh mBladesMesh;
61
62    private ScriptC_grass mScript;
63
64    private int mVerticies;
65    private int mIndicies;
66    private int[] mBladeSizes;
67
68    private final Context mContext;
69    private final LocationManager mLocationManager;
70
71    private LocationUpdater mLocationUpdater;
72    private GrassRS.TimezoneTracker mTimezoneTracker;
73
74    GrassRS(Context context, int width, int height) {
75        super(width, height);
76
77        mContext = context;
78        mLocationManager = (LocationManager)
79                context.getSystemService(Context.LOCATION_SERVICE);
80    }
81
82    @Override
83    public void start() {
84        super.start();
85
86        if (mTimezoneTracker == null) {
87            mTimezoneTracker = new TimezoneTracker();
88            IntentFilter filter = new IntentFilter();
89            filter.addAction(Intent.ACTION_DATE_CHANGED);
90            filter.addAction(Intent.ACTION_TIME_CHANGED);
91            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
92
93            mContext.registerReceiver(mTimezoneTracker, filter);
94        }
95
96        if (mLocationUpdater == null) {
97            mLocationUpdater = new LocationUpdater();
98            try {
99              mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
100                      LOCATION_UPDATE_MIN_TIME, LOCATION_UPDATE_MIN_DISTANCE, mLocationUpdater);
101            } catch (java.lang.IllegalArgumentException e) {
102              if (!e.getMessage().equals("provider=network")) {
103                throw e;
104              }
105            }
106        }
107
108        updateLocation();
109    }
110
111    @Override
112    public void stop() {
113        super.stop();
114
115        if (mTimezoneTracker != null) {
116            mContext.unregisterReceiver(mTimezoneTracker);
117            mTimezoneTracker = null;
118        }
119
120        if (mLocationUpdater != null) {
121            mLocationManager.removeUpdates(mLocationUpdater);
122            mLocationUpdater = null;
123        }
124    }
125
126    @Override
127    public void resize(int width, int height) {
128        super.resize(width, height);
129
130        mScript.set_gWidth(width);
131        mScript.set_gHeight(height);
132        mScript.invoke_updateBlades();
133        Matrix4f proj = new Matrix4f();
134        proj.loadOrthoWindow(width, height);
135        mPvOrthoAlloc.setProjection(proj);
136    }
137
138    @Override
139    protected ScriptC createScript() {
140        mScript = new ScriptC_grass(mRS, mResources, R.raw.grass);
141
142        final boolean isPreview = isPreview();
143        createProgramVertex();
144        createProgramFragmentStore();
145        loadTextures();
146        createProgramFragment();
147        createBlades();
148
149        mScript.set_gBladesCount(BLADES_COUNT);
150        mScript.set_gIndexCount(mIndicies);
151        mScript.set_gWidth(mWidth);
152        mScript.set_gHeight(mHeight);
153        mScript.set_gXOffset(isPreview ? 0.5f : 0.f);
154        mScript.set_gIsPreview(isPreview ? 1 : 0);
155        mScript.set_gBladesMesh(mBladesMesh);
156
157        mScript.setTimeZone(TimeZone.getDefault().getID());
158        mScript.bind_Blades(mBlades);
159        mScript.bind_Verticies(mVertexBuffer);
160
161        // set these to reasonable defaults.
162        mScript.set_gDawn(6.f / 24.f);
163        mScript.set_gDusk(18.f / 24.f);
164        mScript.set_gMorning(8.f / 24.f); // 2 hours for sunrise
165        mScript.set_gAfternoon(16.f / 24.f); // 2 hours for sunset
166
167        return mScript;
168    }
169
170    @Override
171    public void setOffset(float xOffset, float yOffset, int xPixels, int yPixels) {
172        mScript.set_gXOffset(xOffset);
173    }
174
175    private void createBlades() {
176        mVerticies = 0;
177        mIndicies = 0;
178
179        mBlades = new ScriptField_Blade(mRS, BLADES_COUNT);
180
181        mBladeSizes = new int[BLADES_COUNT];
182        for (int i = 0; i < BLADES_COUNT; i++) {
183            ScriptField_Blade.Item item = new ScriptField_Blade.Item();
184            createBlade(item);
185            mBlades.set(item, i, false);
186
187            mIndicies += item.size * 2 * 3;
188            mVerticies += item.size + 2;
189            mBladeSizes[i] = item.size;
190        }
191        mBlades.copyAll();
192
193        createMesh();
194    }
195
196    private void createMesh() {
197        mVertexBuffer = new ScriptField_Vertex(mRS, mVerticies * 2);
198
199        final Mesh.AllocationBuilder meshBuilder = new Mesh.AllocationBuilder(mRS);
200        meshBuilder.addVertexAllocation(mVertexBuffer.getAllocation());
201
202        mBladesIndicies = Allocation.createSized(mRS, Element.U16(mRS), mIndicies);
203        meshBuilder.addIndexSetAllocation(mBladesIndicies, Primitive.TRIANGLE);
204
205        mBladesMesh = meshBuilder.create();
206
207        short[] idx = new short[mIndicies];
208        int idxIdx = 0;
209        int vtxIdx = 0;
210        for (int i = 0; i < mBladeSizes.length; i++) {
211            for (int ct = 0; ct < mBladeSizes[i]; ct ++) {
212                idx[idxIdx + 0] = (short)(vtxIdx + 0);
213                idx[idxIdx + 1] = (short)(vtxIdx + 1);
214                idx[idxIdx + 2] = (short)(vtxIdx + 2);
215                idx[idxIdx + 3] = (short)(vtxIdx + 1);
216                idx[idxIdx + 4] = (short)(vtxIdx + 3);
217                idx[idxIdx + 5] = (short)(vtxIdx + 2);
218                idxIdx += 6;
219                vtxIdx += 2;
220            }
221            vtxIdx += 2;
222        }
223
224        mBladesIndicies.copyFrom(idx);
225    }
226
227    private void createBlade(ScriptField_Blade.Item blades) {
228        final float size = random(4.0f) + 4.0f;
229        final int xpos = random(-mWidth, mWidth);
230
231        //noinspection PointlessArithmeticExpression
232        blades.angle = 0.0f;
233        blades.size = (int)(size / TESSELATION);
234        blades.xPos = xpos;
235        blades.yPos = mHeight;
236        blades.offset = random(0.2f) - 0.1f;
237        blades.scale = 4.0f / (size / TESSELATION) + (random(0.6f) + 0.2f) * TESSELATION;
238        blades.lengthX = (random(4.5f) + 3.0f) * TESSELATION * size;
239        blades.lengthY = (random(5.5f) + 2.0f) * TESSELATION * size;
240        blades.hardness = (random(1.0f) + 0.2f) * TESSELATION;
241        blades.h = random(0.02f) + 0.2f;
242        blades.s = random(0.22f) + 0.78f;
243        blades.b = random(0.65f) + 0.35f;
244        blades.turbulencex = xpos * 0.006f;
245    }
246
247    private void loadTextures() {
248        mScript.set_gTNight(loadTexture(R.drawable.night));
249        mScript.set_gTSunrise(loadTexture(R.drawable.sunrise));
250        mScript.set_gTSky(loadTexture(R.drawable.sky));
251        mScript.set_gTSunset(loadTexture(R.drawable.sunset));
252        mScript.set_gTAa(generateTextureAlpha());
253    }
254
255    private Allocation generateTextureAlpha() {
256        final Type.Builder builder = new Type.Builder(mRS, A_8(mRS));
257        builder.setX(4);
258        builder.setY(1);
259        builder.setMipmaps(true);
260
261        final Allocation allocation = Allocation.createTyped(mRS, builder.create(),
262                                                             Allocation.USAGE_GRAPHICS_TEXTURE);
263        byte[] mip0 = new byte[] {0, -1, -1, 0};
264        byte[] mip1 = new byte[] {64, 64};
265        byte[] mip2 = new byte[] {0};
266
267        AllocationAdapter a = AllocationAdapter.create2D(mRS, allocation);
268        a.setLOD(0);
269        a.copyFrom(mip0);
270        a.setLOD(1);
271        a.copyFrom(mip1);
272        a.setLOD(2);
273        a.copyFrom(mip2);
274
275        return allocation;
276    }
277
278    private Allocation loadTexture(int id) {
279        return Allocation.createFromBitmapResource(mRS, mResources, id,
280                                           Allocation.MipmapControl.MIPMAP_NONE,
281                                           Allocation.USAGE_GRAPHICS_TEXTURE);
282    }
283
284    private void createProgramFragment() {
285        Sampler.Builder samplerBuilder = new Sampler.Builder(mRS);
286        samplerBuilder.setMinification(LINEAR_MIP_LINEAR);
287        samplerBuilder.setMagnification(LINEAR);
288        samplerBuilder.setWrapS(WRAP);
289        samplerBuilder.setWrapT(WRAP);
290        Sampler sl = samplerBuilder.create();
291
292        samplerBuilder.setMinification(NEAREST);
293        samplerBuilder.setMagnification(NEAREST);
294        Sampler sn = samplerBuilder.create();
295
296        ProgramFragmentFixedFunction.Builder builder = new ProgramFragmentFixedFunction.Builder(mRS);
297        builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE,
298                           ProgramFragmentFixedFunction.Builder.Format.ALPHA, 0);
299        builder.setVaryingColor(true);
300        ProgramFragment pf = builder.create();
301        mScript.set_gPFGrass(pf);
302        pf.bindSampler(sl, 0);
303
304        builder = new ProgramFragmentFixedFunction.Builder(mRS);
305        builder.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE,
306                           ProgramFragmentFixedFunction.Builder.Format.RGB, 0);
307        pf = builder.create();
308        mScript.set_gPFBackground(pf);
309        pf.bindSampler(sn, 0);
310    }
311
312    private void createProgramFragmentStore() {
313        ProgramStore.Builder builder = new ProgramStore.Builder(mRS);
314        builder.setDepthFunc(ALWAYS);
315        builder.setBlendFunc(BlendSrcFunc.SRC_ALPHA, BlendDstFunc.ONE_MINUS_SRC_ALPHA);
316        builder.setDitherEnabled(false);
317        builder.setDepthMaskEnabled(false);
318        mScript.set_gPSBackground(builder.create());
319    }
320
321    private void createProgramVertex() {
322        mPvOrthoAlloc = new ProgramVertexFixedFunction.Constants(mRS);
323        Matrix4f proj = new Matrix4f();
324        proj.loadOrthoWindow(mWidth, mHeight);
325        mPvOrthoAlloc.setProjection(proj);
326
327        ProgramVertexFixedFunction.Builder pvb = new ProgramVertexFixedFunction.Builder(mRS);
328        ProgramVertex pv = pvb.create();
329        ((ProgramVertexFixedFunction)pv).bindConstants(mPvOrthoAlloc);
330        mScript.set_gPVBackground(pv);
331    }
332
333    private void updateLocation() {
334        updateLocation(mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER));
335    }
336
337    private void updateLocation(Location location) {
338        float dawn = 0.3f;
339        float dusk = 0.75f;
340
341        if (location != null) {
342            final String timeZone = Time.getCurrentTimezone();
343            final SunCalculator calculator = new SunCalculator(location, timeZone);
344            final Calendar now = Calendar.getInstance();
345
346            final double sunrise = calculator.computeSunriseTime(SunCalculator.ZENITH_CIVIL, now);
347            dawn = SunCalculator.timeToDayFraction(sunrise);
348
349            final double sunset = calculator.computeSunsetTime(SunCalculator.ZENITH_CIVIL, now);
350            dusk = SunCalculator.timeToDayFraction(sunset);
351        }
352
353        mScript.set_gDawn(dawn);
354        mScript.set_gDusk(dusk);
355        mScript.set_gMorning(dawn + 1.0f / 12.0f); // 2 hours for sunrise
356        mScript.set_gAfternoon(dusk - 1.0f / 12.0f); // 2 hours for sunset
357    }
358
359    private class LocationUpdater implements LocationListener {
360        public void onLocationChanged(Location location) {
361            updateLocation(location);
362        }
363
364        public void onStatusChanged(String provider, int status, Bundle extras) {
365        }
366
367        public void onProviderEnabled(String provider) {
368        }
369
370        public void onProviderDisabled(String provider) {
371        }
372    }
373
374    private class TimezoneTracker extends BroadcastReceiver {
375        public void onReceive(Context context, Intent intent) {
376            getScript().setTimeZone(Time.getCurrentTimezone());
377            updateLocation();
378        }
379    }
380}
381