001/*
002Copyright 2006 Jerry Huxtable
003
004Licensed under the Apache License, Version 2.0 (the "License");
005you may not use this file except in compliance with the License.
006You may obtain a copy of the License at
007
008   http://www.apache.org/licenses/LICENSE-2.0
009
010Unless required by applicable law or agreed to in writing, software
011distributed under the License is distributed on an "AS IS" BASIS,
012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013See the License for the specific language governing permissions and
014limitations under the License.
015*/
016
017package com.jhlabs.image;
018
019import java.awt.*;
020import java.awt.image.*;
021import java.util.*;
022import com.jhlabs.math.*;
023
024public class SkyFilter extends PointFilter {
025
026        private float scale = 0.1f;
027        private float stretch = 1.0f;
028        private float angle = 0.0f;
029        private float amount = 1.0f;
030        private float H = 1.0f;
031        private float octaves = 8.0f;
032        private float lacunarity = 2.0f;
033        private float gain = 1.0f;
034        private float bias = 0.6f;
035        private int operation;
036        private float min;
037        private float max;
038        private boolean ridged;
039        private FBM fBm;
040        protected Random random = new Random();
041        private Function2D basis;
042
043        private float cloudCover = 0.5f;
044        private float cloudSharpness = 0.5f;
045        private float time = 0.3f;
046        private float glow = 0.5f;
047        private float glowFalloff = 0.5f;
048        private float haziness = 0.96f;
049        private float t = 0.0f;
050        private float sunRadius = 10f;
051        private int sunColor = 0xffffffff;
052        private float sunR, sunG, sunB;
053        private float sunAzimuth = 0.5f;
054        private float sunElevation = 0.5f;
055        private float windSpeed = 0.0f;
056
057        private float cameraAzimuth = 0.0f;
058        private float cameraElevation = 0.0f;
059        private float fov = 1.0f;
060
061        private float[] exponents;
062        private float[] tan;
063        private BufferedImage skyColors;
064        private int[] skyPixels;
065        
066        private final static float r255 = 1.0f/255.0f;
067
068        private float width, height;
069
070        public SkyFilter() {
071                if ( skyColors == null ) {
072                        skyColors = ImageUtils.createImage( Toolkit.getDefaultToolkit().getImage( getClass().getResource("SkyColors.png") ).getSource() );
073                }
074        }
075
076        public void setAmount(float amount) {
077                this.amount = amount;
078        }
079
080        public float getAmount() {
081                return amount;
082        }
083
084        public void setOperation(int operation) {
085                this.operation = operation;
086        }
087        
088        public int getOperation() {
089                return operation;
090        }
091        
092        public void setScale(float scale) {
093                this.scale = scale;
094        }
095
096        public float getScale() {
097                return scale;
098        }
099
100        public void setStretch(float stretch) {
101                this.stretch = stretch;
102        }
103
104        public float getStretch() {
105                return stretch;
106        }
107
108        public void setT(float t) {
109                this.t = t;
110        }
111
112        public float getT() {
113                return t;
114        }
115
116        public void setFOV(float fov) {
117                this.fov = fov;
118        }
119
120        public float getFOV() {
121                return fov;
122        }
123
124        public void setCloudCover(float cloudCover) {
125                this.cloudCover = cloudCover;
126        }
127
128        public float getCloudCover() {
129                return cloudCover;
130        }
131
132        public void setCloudSharpness(float cloudSharpness) {
133                this.cloudSharpness = cloudSharpness;
134        }
135
136        public float getCloudSharpness() {
137                return cloudSharpness;
138        }
139
140        public void setTime(float time) {
141                this.time = time;
142        }
143
144        public float getTime() {
145                return time;
146        }
147
148        public void setGlow(float glow) {
149                this.glow = glow;
150        }
151
152        public float getGlow() {
153                return glow;
154        }
155
156        public void setGlowFalloff(float glowFalloff) {
157                this.glowFalloff = glowFalloff;
158        }
159
160        public float getGlowFalloff() {
161                return glowFalloff;
162        }
163
164        public void setAngle(float angle) {
165                this.angle = angle;
166        }
167
168        public float getAngle() {
169                return angle;
170        }
171
172        public void setOctaves(float octaves) {
173                this.octaves = octaves;
174        }
175
176        public float getOctaves() {
177                return octaves;
178        }
179
180        public void setH(float H) {
181                this.H = H;
182        }
183
184        public float getH() {
185                return H;
186        }
187
188        public void setLacunarity(float lacunarity) {
189                this.lacunarity = lacunarity;
190        }
191
192        public float getLacunarity() {
193                return lacunarity;
194        }
195
196        public void setGain(float gain) {
197                this.gain = gain;
198        }
199
200        public float getGain() {
201                return gain;
202        }
203
204        public void setBias(float bias) {
205                this.bias = bias;
206        }
207
208        public float getBias() {
209                return bias;
210        }
211
212        public void setHaziness(float haziness) {
213                this.haziness = haziness;
214        }
215
216        public float getHaziness() {
217                return haziness;
218        }
219
220        public void setSunElevation(float sunElevation) {
221                this.sunElevation = sunElevation;
222        }
223
224        public float getSunElevation() {
225                return sunElevation;
226        }
227
228        public void setSunAzimuth(float sunAzimuth) {
229                this.sunAzimuth = sunAzimuth;
230        }
231
232        public float getSunAzimuth() {
233                return sunAzimuth;
234        }
235
236        public void setSunColor(int sunColor) {
237                this.sunColor = sunColor;
238        }
239
240        public int getSunColor() {
241                return sunColor;
242        }
243
244        public void setCameraElevation(float cameraElevation) {
245                this.cameraElevation = cameraElevation;
246        }
247
248        public float getCameraElevation() {
249                return cameraElevation;
250        }
251
252        public void setCameraAzimuth(float cameraAzimuth) {
253                this.cameraAzimuth = cameraAzimuth;
254        }
255
256        public float getCameraAzimuth() {
257                return cameraAzimuth;
258        }
259
260        public void setWindSpeed(float windSpeed) {
261                this.windSpeed = windSpeed;
262        }
263
264        public float getWindSpeed() {
265                return windSpeed;
266        }
267
268float mn, mx;
269    public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
270long start = System.currentTimeMillis();
271                sunR = (float)((sunColor >> 16) & 0xff) * r255;
272                sunG = (float)((sunColor >> 8) & 0xff) * r255;
273                sunB = (float)(sunColor & 0xff) * r255;
274
275mn = 10000;
276mx = -10000;
277                exponents = new float[(int)octaves+1];
278                float frequency = 1.0f;
279                for (int i = 0; i <= (int)octaves; i++) {
280                        exponents[i] = (float)Math.pow(2, -i);
281                        frequency *= lacunarity;
282                }
283
284                min = -1;
285                max = 1;
286
287//min = -1.2f;
288//max = 1.2f;
289
290                width = src.getWidth();
291                height = src.getHeight();
292
293                int h = src.getHeight();
294                tan = new float[h];
295                for (int i = 0; i < h; i++)
296                        tan[i] = (float)Math.tan( fov * (float)i/h * Math.PI * 0.5 );
297
298                if ( dst == null )
299                        dst = createCompatibleDestImage( src, null );
300                int t = (int)(63*time);
301//              skyPixels = getRGB( skyColors, t, 0, 1, 64, skyPixels );
302                Graphics2D g = dst.createGraphics();
303                g.drawImage( skyColors, 0, 0, dst.getWidth(), dst.getHeight(), t, 0, t+1, 64, null );
304                g.dispose();
305                BufferedImage clouds = super.filter( dst, dst );
306//              g.drawRenderedImage( clouds, null );
307//              g.dispose();
308long finish = System.currentTimeMillis();
309System.out.println(mn+" "+mx+" "+(finish-start)*0.001f);
310                exponents = null;
311                tan = null;
312                return dst;
313        }
314        
315        public float evaluate(float x, float y) {
316                float value = 0.0f;
317                float remainder;
318                int i;
319                
320                // to prevent "cascading" effects
321                x += 371;
322                y += 529;
323                
324                for (i = 0; i < (int)octaves; i++) {
325                        value += Noise.noise3(x, y, t) * exponents[i];
326                        x *= lacunarity;
327                        y *= lacunarity;
328                }
329
330                remainder = octaves - (int)octaves;
331                if (remainder != 0)
332                        value += remainder * Noise.noise3(x, y, t) * exponents[i];
333
334                return value;
335        }
336
337        public int filterRGB(int x, int y, int rgb) {
338
339// Curvature
340float fx = (float)x / width;
341//y += 20*Math.sin( fx*Math.PI*0.5 );
342                float fy = y / height;
343                float haze = (float)Math.pow( haziness, 100*fy*fy );
344//              int argb = skyPixels[(int)fy];
345                float r = (float)((rgb >> 16) & 0xff) * r255;
346                float g = (float)((rgb >> 8) & 0xff) * r255;
347                float b = (float)(rgb & 0xff) * r255;
348
349                float cx = width*0.5f;
350                float nx = x-cx;
351                float ny = y;
352// FOV
353//ny = (float)Math.tan( fov * fy * Math.PI * 0.5 );
354ny = tan[y];
355nx = (fx-0.5f) * (1+ny);
356ny += t*windSpeed;// Wind towards the camera
357
358//              float xscale = scale/(1+y*bias*0.1f);
359                nx /= scale;
360                ny /= scale * stretch;
361                float f = evaluate(nx, ny);
362float fg = f;//FIXME-bump map
363                // Normalize to 0..1
364//              f = (f-min)/(max-min);
365
366                f = (f+1.23f)/2.46f;
367
368//              f *= amount;
369                int a = rgb & 0xff000000;
370                int v;
371
372                // Work out cloud cover
373                float c = f - cloudCover;
374                if (c < 0)
375                        c = 0;
376
377                float cloudAlpha = 1 - (float)Math.pow(cloudSharpness, c);
378//cloudAlpha *= amount;
379//if ( cloudAlpha > 1 )
380//      cloudAlpha = 1;
381mn = Math.min(mn, cloudAlpha);
382mx = Math.max(mx, cloudAlpha);
383
384                // Sun glow
385                float centreX = width*sunAzimuth;
386                float centreY = height*sunElevation;
387                float dx = x-centreX;
388                float dy = y-centreY;
389                float distance2 = dx*dx+dy*dy;
390//              float sun = 0;
391                //distance2 = (float)Math.sqrt(distance2);
392distance2 = (float)Math.pow(distance2, glowFalloff);
393                float sun = /*amount**/10*(float)Math.exp(-distance2*glow*0.1f);
394//              sun = glow*10*(float)Math.exp(-distance2);
395
396                // Sun glow onto sky
397                r += sun * sunR;
398                g += sun * sunG;
399                b += sun * sunB;
400
401
402//              float cloudColor = cloudAlpha *sun;
403// Bump map
404/*
405                float nnx = x-cx;
406                float nny = y-1;
407                nnx /= xscale;
408                nny /= xscale * stretch;
409                float gf = evaluate(nnx, nny);
410                float gradient = fg-gf;
411if (y == 100)System.out.println(fg+" "+gf+gradient);
412                cloudColor += amount * gradient;
413*/
414// ...
415/*
416                r += (cloudColor-r) * cloudAlpha;
417                g += (cloudColor-g) * cloudAlpha;
418                b += (cloudColor-b) * cloudAlpha;
419*/
420                // Clouds get darker as they get thicker
421                float ca = (1-cloudAlpha*cloudAlpha*cloudAlpha*cloudAlpha) /** (1 + sun)*/ * amount;
422                float cloudR = sunR * ca;
423                float cloudG = sunG * ca;
424                float cloudB = sunB * ca;
425
426                // Apply the haziness as we move further away
427                cloudAlpha *= haze;
428
429                // Composite the clouds on the sky
430                float iCloudAlpha = (1-cloudAlpha);
431                r = iCloudAlpha*r + cloudAlpha*cloudR;
432                g = iCloudAlpha*g + cloudAlpha*cloudG;
433                b = iCloudAlpha*b + cloudAlpha*cloudB;
434
435                // Exposure
436                float exposure = gain;
437                r = 1 - (float)Math.exp(-r * exposure);
438                g = 1 - (float)Math.exp(-g * exposure);
439                b = 1 - (float)Math.exp(-b * exposure);
440
441                int ir = (int)(255*r) << 16;
442                int ig = (int)(255*g) << 8;
443                int ib = (int)(255*b);
444                v = 0xff000000|ir|ig|ib;
445                return v;
446        }
447        
448        public String toString() {
449                return "Texture/Sky...";
450        }
451        
452}