001package com.fs.starfarer.api.impl.campaign.terrain;
002
003import java.util.Arrays;
004import java.util.Random;
005import java.util.zip.DataFormatException;
006import java.util.zip.Deflater;
007import java.util.zip.Inflater;
008
009import java.awt.Color;
010
011import javax.xml.bind.DatatypeConverter;
012
013import org.lwjgl.opengl.GL11;
014import org.lwjgl.util.vector.Vector2f;
015
016import com.fs.starfarer.api.Global;
017import com.fs.starfarer.api.campaign.CampaignEngineLayers;
018import com.fs.starfarer.api.campaign.CampaignFleetAPI;
019import com.fs.starfarer.api.campaign.SectorEntityToken;
020import com.fs.starfarer.api.combat.ViewportAPI;
021import com.fs.starfarer.api.graphics.SpriteAPI;
022import com.fs.starfarer.api.util.Misc;
023
024public abstract class BaseTiledTerrain extends BaseTerrain {
025        
026        public static class TileParams {
027                public String tiles;
028                public int w;
029                public int h;
030                public String cat;
031                public String key;
032                public int tW;
033                public int tH;
034                public String name;
035                public TileParams(String tiles, int width, int height,
036                                String tileTexCat, String tileTexKey, int tilesWide, int tilesHigh, String name) {
037                        this.tiles = tiles;
038                        this.w = width;
039                        this.h = height;
040                        this.cat = tileTexCat;
041                        this.key = tileTexKey;
042                        this.tW = tilesWide;
043                        this.tH = tilesHigh;
044                        this.name = name;
045                }
046                
047        }
048        
049//      public static class Tile {
050//              public int cellX;
051//              public int cellY;
052//              public int texCellX;
053//              public int texCellY;
054//      }
055        
056        protected TileParams params;
057        protected transient SpriteAPI texture;
058        protected transient SpriteAPI mapTexture;
059        //protected Tile[][] tiles;
060        protected transient int [][] tiles;
061        
062        protected long tileSeed;
063        protected String savedTiles;
064        public void init(String terrainId, SectorEntityToken entity, Object param) {
065                super.init(terrainId, entity, param);
066                
067                this.params = (TileParams) param;
068                name = params.name;
069                if (name == null) name = "Unknown";
070                
071                tiles = new int [params.w][params.h];
072                
073                
074                tileSeed = new Random().nextLong();
075//              Random random = new Random(tileSeed);
076                
077                for (int i = 0; i < tiles.length; i++) {
078                        for (int j = 0; j < tiles[0].length; j++) {
079                                int index = i + (tiles[0].length - j - 1) * tiles.length;
080                                char c = params.tiles.charAt(index);
081                                if (!Character.isWhitespace(c)) {
082//                                      int texX = (int) (Math.random() * params.tW);
083//                                      int texY = (int) (Math.random() * params.tH);
084//                                      int texX = (int) (random.nextFloat() * params.tW);
085//                                      int texY = (int) (random.nextFloat() * params.tH);
086//                                      tiles[i][j] = texX + texY * params.tW;
087                                        tiles[i][j] = 1;
088                                } else {
089                                        tiles[i][j] = -1;
090                                }
091                        }
092                }
093                
094                savedTiles = encodeTiles(tiles);
095                readResolve();
096                
097                params.tiles = null; // don't need to save this
098        }
099        
100        protected void regenTiles() {
101                Random random = new Random(tileSeed);
102                for (int i = 0; i < tiles.length; i++) {
103                        for (int j = 0; j < tiles[0].length; j++) {
104                                if (tiles[i][j] >= 0) {
105                                        int texX = (int) (random.nextFloat() * params.tW);
106                                        int texY = (int) (random.nextFloat() * params.tH);
107                                        tiles[i][j] = texX + texY * params.tW;
108                                } else {
109                                        tiles[i][j] = -1;
110                                }
111                        }
112                }
113                sampleCache = null;
114        }
115        
116        
117        public int[][] getTiles() {
118                return tiles;
119        }
120
121        public TileParams getParams() {
122                return params;
123        }
124
125
126        Object readResolve() {
127                texture = Global.getSettings().getSprite(params.cat, params.key);
128                mapTexture = Global.getSettings().getSprite(params.cat, params.key + "_map");
129
130                if (savedTiles != null) {
131                        try {
132                                tiles = decodeTiles(savedTiles, params.w, params.h);
133                        } catch (DataFormatException e) {
134                                throw new RuntimeException("Error decoding tiled terrain tiles", e);
135                        }
136                } else {
137                         // shouldn't be here, if we are then savedTiles == null and something went badly wrong
138                        tiles = new int [params.w][params.h];
139                }
140                regenTiles();
141                
142                return this;
143        }
144        
145        Object writeReplace() {
146                params.tiles = null;
147                savedTiles = encodeTiles(tiles);
148                return this;
149        }
150        
151        @Override
152        public boolean containsEntity(SectorEntityToken other) {
153                if (other.getContainingLocation() != this.entity.getContainingLocation()) return false;
154                return containsPoint(other.getLocation(), other.getRadius()) && !isPreventedFromAffecting(other);
155        }
156        
157        public boolean containsPoint(Vector2f test, float r) {
158                
159                float dist = Misc.getDistance(this.entity.getLocation(), test) - r;
160                if (dist > getRenderRange()) return false;
161                
162                float x = this.entity.getLocation().x;
163                float y = this.entity.getLocation().y;
164                float size = getTileSize();
165                float containsSize = getTileContainsSize();
166                
167                float w = tiles.length * size;
168                float h = tiles[0].length * size;
169
170                x -= w/2f;
171                y -= h/2f;
172                
173                float extra = (containsSize - size) / 2f;
174                
175                if (test.x + r + extra < x) return false;
176                if (test.y + r + extra < y) return false;
177                if (test.x > x + w + r + extra) return false;
178                if (test.y > y + h + r + extra) return false;
179                
180                int xIndex = (int) ((test.x - x) / size);
181                int yIndex = (int) ((test.y - y) / size);
182                
183                if (xIndex < 0) xIndex = 0;
184                if (yIndex < 0) yIndex = 0;
185                
186                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
187                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
188                
189//              if (entity.isPlayerFleet()) {
190//                      System.out.println(this + " " + xIndex + "," + yIndex);
191//              }
192                
193                for (float i = Math.max(0, xIndex - 1); i <= xIndex + 1 && i < tiles.length; i++) {
194                        for (float j = Math.max(0, yIndex - 1); j <= yIndex + 1 && j < tiles[0].length; j++) {
195                                int texIndex = tiles[(int) i][(int) j];
196                                if (texIndex >= 0) {
197                                        float tx = x + i * size + size/2f - containsSize/2f;
198                                        float ty = y + j * size + size/2f - containsSize/2f;
199                                         
200                                        if (test.x + r < tx) continue;
201                                        if (test.y + r < ty) continue;
202                                        if (test.x > tx + containsSize + r) continue;
203                                        if (test.y > ty + containsSize + r) continue;
204                                        return true;
205                                }
206                        }
207                }
208                return false;
209        }
210        
211
212        public abstract float getTileSize();
213        public abstract float getTileRenderSize();
214        public abstract float getTileContainsSize();
215        public abstract void preRender(CampaignEngineLayers layer, float alphaMult);
216        public abstract void preMapRender(float alphaMult);
217        public abstract Color getRenderColor();
218        
219        public float getRenderRange() {
220                float size = getTileSize();
221                float renderSize = getTileRenderSize();
222                float w = tiles.length * size * 0.5f + (renderSize - size) * 0.5f;
223                float h = tiles[0].length * size * 0.5f + (renderSize - size) * 0.5f;
224                return Math.max(w, h) * 1.5f;
225        }
226
227        public void render(CampaignEngineLayers layer, ViewportAPI v) {
228                texture.bindTexture();
229                GL11.glEnable(GL11.GL_TEXTURE_2D);
230                //GL11.glDisable(GL11.GL_TEXTURE_2D);
231                
232                preRender(layer, v.getAlphaMult());
233                //GL11.glDisable(GL11.GL_TEXTURE_2D);
234                
235                float x = this.entity.getLocation().x;
236                float y = this.entity.getLocation().y;
237                float size = getTileSize();
238                float renderSize = getTileRenderSize();
239                
240                float w = tiles.length * size;
241                float h = tiles[0].length * size;
242                x -= w/2f;
243                y -= h/2f;
244                float extra = (renderSize - size) / 2f + 100f;
245                
246                float llx = v.getLLX();
247                float lly = v.getLLY();
248                float vw = v.getVisibleWidth();
249                float vh = v.getVisibleHeight();
250                
251                if (llx > x + w + extra) return;
252                if (lly > y + h + extra) return;
253                if (llx + vw + extra < x) return;
254                if (lly + vh + extra < y) return;
255                
256                float xStart = (int)((llx - x - extra) / size);
257                if (xStart < 0) xStart = 0;
258                float yStart = (int)((lly - y - extra) / size);
259                if (yStart < 0) yStart = 0;
260                
261                float xEnd = (int)((llx + vw - x + extra) / size) + 1;
262                if (xEnd >= tiles.length) xEnd = tiles.length - 1;
263                float yEnd = (int)((lly + vw - y + extra) / size) + 1;
264                if (yEnd >= tiles.length) yEnd = tiles[0].length - 1;
265                
266                renderSubArea(xStart, xEnd, yStart, yEnd, 1f, 1, v.getAlphaMult());
267                
268                //renderSubArea(0, tiles.length, 0, tiles[0].length, 1f);
269        }
270        
271        public boolean isTileVisible(int i, int j) {
272                float x = this.entity.getLocation().x;
273                float y = this.entity.getLocation().y;
274                float size = getTileSize();
275                float renderSize = getTileRenderSize();
276                
277                float w = tiles.length * size;
278                float h = tiles[0].length * size;
279                x -= w/2f;
280                y -= h/2f;
281                float extra = (renderSize - size) / 2f + 100f;
282                
283                ViewportAPI v = Global.getSector().getViewport();
284                float llx = v.getLLX();
285                float lly = v.getLLY();
286                float vw = v.getVisibleWidth();
287                float vh = v.getVisibleHeight();
288                
289                if (llx > x + w + extra) return false;
290                if (lly > y + h + extra) return false;
291                if (llx + vw + extra < x) return false;
292                if (lly + vh + extra < y) return false;
293                
294                float xStart = (int)((llx - x - extra) / size);
295                if (xStart < 0) xStart = 0;
296                float yStart = (int)((lly - y - extra) / size);
297                if (yStart < 0) yStart = 0;
298                
299                float xEnd = (int)((llx + vw - x + extra) / size) + 1;
300                if (xEnd >= tiles.length) xEnd = tiles.length - 1;
301                float yEnd = (int)((lly + vw - y + extra) / size) + 1;
302                if (yEnd >= tiles.length) yEnd = tiles[0].length - 1;
303                
304                if (i < xStart) return false;
305                if (i > xEnd) return false;
306                if (j < yStart) return false;
307                if (j > yEnd) return false;
308                
309                return true;
310        }
311        
312        public void renderOnMap(float factor, float alphaMult) {
313                mapTexture.bindTexture();
314                GL11.glEnable(GL11.GL_TEXTURE_2D);
315                preMapRender(alphaMult);
316                renderSubArea(0, tiles.length, 0, tiles[0].length, factor, getNumMapSamples(), alphaMult);
317        }
318        
319        public int getNumMapSamples() {
320                return 5;
321        }
322
323        public void renderOnMapAbove(float factor, float alphaMult) {
324                
325        }
326        
327        public float[] getTileCenter(int i, int j) {
328                float x = entity.getLocation().x;
329                float y = entity.getLocation().y;
330                float size = getTileSize();
331                        
332                float w = tiles.length * size;
333                float h = tiles[0].length * size;
334                        
335                float [] result = new float[2];
336                result[0] = x - w / 2f + (float)i * size + size / 2f;
337                result[1] = y - h / 2f + (float)j * size + size / 2f;
338                return result;
339        }
340        
341        public static class TileSample {
342                public int texIndex = -1;
343                public float angle = 0;
344                public float xOff = 0;
345                public float yOff = 0;
346                public float weight = 0;
347        }
348        
349        protected transient TileSample [][] sampleCache = null;
350        protected transient int samplesForCache = 0;
351        
352        public boolean isUseSampleCache() {
353                return false;
354        }
355
356        public void forceClearSampleCache() {
357                sampleCache = null;
358        }
359        public void updateSampleCache(int samples, boolean force) {
360                if (tiles == null) return;
361                if (sampleCache != null && samplesForCache == samples && !force) {
362                        return;
363                }
364                samplesForCache = samples;
365                
366                //(0, tiles.length, 0, tiles[0].length
367                
368                int cacheW = tiles.length / samples + 1; 
369                int cacheH = tiles[0].length / samples + 1;
370                sampleCache = new TileSample[cacheW][cacheH];
371                
372                float renderSize = getTileRenderSize();
373                renderSize *= samples;
374                
375                for (float i = 0; i <= tiles.length; i+=samples) {
376                        if (i < 0 || i >= tiles.length) continue;
377                        for (float j = 0; j <= tiles[0].length; j+=samples) {
378                                if (j < 0 || j >= tiles[0].length) continue;
379                                int texIndex = -1;
380                                float angle = 0;
381                                float xOff = 0;
382                                float yOff = 0;
383                                float weight = 0;
384                                for (int m = 0; m < samples && i + m <= tiles.length; m++) {
385                                        if (i + m < 0 || i + m >= tiles.length) continue;
386                                        for (int n = 0; n < samples && j + n < tiles[0].length; n++) {
387                                                if (j + n < 0 || j + n >= tiles[0].length) continue;
388                                                int currIndex = tiles[(int) i + m][(int) j + n];
389                                                if (currIndex >= 0 && texIndex < 0) {
390                                                        texIndex = currIndex;
391                                                        Random rand = new Random((long) (i + j * tiles.length) * 1000000);
392                                                        angle = rand.nextFloat() * 360f;
393                                                        float offRange = renderSize * 0.25f;
394                                                        xOff = -offRange / 2f + offRange * rand.nextFloat();
395                                                        yOff = -offRange / 2f + offRange * rand.nextFloat();
396//                                                      if (Keyboard.isKeyDown(Keyboard.KEY_O)) {
397//                                                              xOff = yOff = 0f;
398//                                                      }
399                                                }
400                                                if (currIndex >= 0) {
401                                                        weight++;
402                                                }
403                                        }
404                                }
405                                TileSample sample = new TileSample();
406                                sample.texIndex = texIndex;
407                                sample.angle = angle;
408                                sample.xOff = xOff;
409                                sample.yOff = yOff;
410                                sample.weight = weight;
411                                
412                                sampleCache[(int)i/samples][(int)j/samples] = sample;
413                        }
414                }
415        }
416        
417        
418        
419        protected void renderSubArea(float startColumn, float endColumn, float startRow, float endRow, float factor, int samples, float alphaMult) {
420                float x = entity.getLocation().x;
421                float y = entity.getLocation().y;
422                float size = getTileSize();
423                float renderSize = getTileRenderSize();
424                
425                float w = tiles.length * size;
426                float h = tiles[0].length * size;
427                //if (true) return;
428                
429                //Random rand = new Random(tiles.length + tiles[0].length);
430                //Random rand = new Random();
431                
432                if (samples == 1) {
433                        GL11.glBegin(GL11.GL_QUADS);
434                        for (float i = startColumn; i <= endColumn; i++) {
435                                if (i < 0 || i >= tiles.length) continue;
436                                for (float j = startRow; j <= endRow; j++) {
437                                        if (j < 0 || j >= tiles[0].length) continue;
438                                        int texIndex = tiles[(int) i][(int) j];
439                                        if (texIndex >= 0) {
440                                                int texCellX = texIndex % params.tW;
441                                                int texCellY = texIndex / params.tW;
442                                                
443                                                Random rand = new Random((long) (i + j * tiles.length) * 1000000);
444                                                float angle = rand.nextFloat() * 360f;
445                                                float offRange = renderSize * 0.25f;
446                                                float xOff = -offRange / 2f + offRange * rand.nextFloat();
447                                                float yOff = -offRange / 2f + offRange * rand.nextFloat();
448//                                              if (Keyboard.isKeyDown(Keyboard.KEY_O)) {
449//                                                xOff = yOff = 0f;
450//                                              }
451        //                                      angle += angleOffset;
452        //                                      angle = Misc.normalizeAngle(angle);
453                                                renderQuad((int)i, (int)j, 
454                                                                   (x + xOff - w / 2f + i * size + size/2f - renderSize/2f) * factor,
455                                                                   (y + yOff - h / 2f +  j * size + size/2f - renderSize/2f) * factor,
456                                                                   renderSize * factor, renderSize * factor,
457                                                                   texCellX * 0.25f, texCellY * 0.25f,
458                                                                   0.25f, 0.25f,
459                                                                   angle);
460                                        }
461                                }
462                        }
463                        GL11.glEnd();
464                } else {
465                        //renderSize = (size * samples) + (renderSize - size);
466                        renderSize *= samples;
467                        size *= samples;
468                        alphaMult *= 0.67f;
469                        //alphaMult = 1f;
470                        GL11.glBegin(GL11.GL_QUADS);
471                        float max = samples * samples;
472                        for (float i = startColumn; i <= endColumn; i+=samples) {
473                                if (i < 0 || i >= tiles.length) continue;
474                                for (float j = startRow; j <= endRow; j+=samples) {
475                                        int texIndex = -1;
476                                        float angle = 0;
477                                        float xOff = 0;
478                                        float yOff = 0;
479                                        float weight = 0;
480                                        
481                                        boolean usingSample = false;
482                                        if (isUseSampleCache()) {
483                                                updateSampleCache(samples, false);
484                                                if (sampleCache != null) {
485                                                        TileSample sample = null;
486                                                        int sampleI = (int)i/samples;
487                                                        int sampleJ = (int)j/samples;
488                                                        if (sampleI >= 0 && sampleI < sampleCache.length &&
489                                                                        sampleJ >= 0 && sampleJ < sampleCache[0].length) {
490                                                                sample = sampleCache[sampleI][sampleJ];
491                                                                if (sample != null) {
492                                                                        texIndex = sample.texIndex;
493                                                                        angle = sample.angle;
494                                                                        xOff = sample.xOff;
495                                                                        yOff = sample.yOff;
496                                                                        weight = sample.weight;
497                                                                        usingSample = true;
498                                                                }
499                                                        }
500                                                }
501                                        }
502                                        
503                                        if (!usingSample) {
504                                                for (int m = 0; m < samples && i + m <= endColumn; m++) {
505                                                        if (i + m < 0 || i + m >= tiles.length) continue;
506                                                        for (int n = 0; n < samples && j + n < endRow; n++) {
507                                                                if (j + n < 0 || j + n >= tiles[0].length) continue;
508                                                                int currIndex = tiles[(int) i + m][(int) j + n];
509                                                                if (currIndex >= 0 && texIndex < 0) {
510                                                                        texIndex = currIndex;
511                                                                        Random rand = new Random((long) (i + j * tiles.length) * 1000000);
512                                                                        angle = rand.nextFloat() * 360f;
513                                                                        float offRange = renderSize * 0.25f;
514                                                                        xOff = -offRange / 2f + offRange * rand.nextFloat();
515                                                                        yOff = -offRange / 2f + offRange * rand.nextFloat();
516        //                                                              if (Keyboard.isKeyDown(Keyboard.KEY_O)) {
517        //                                                                      xOff = yOff = 0f;
518        //                                                              }
519                                                                }
520                                                                if (currIndex >= 0) {
521                                                                        weight++;
522                                                                }
523                                                        }
524                                                }
525                                        }
526
527                                        if (texIndex >= 0) {
528                                                int texCellX = texIndex % params.tW;
529                                                int texCellY = texIndex / params.tW;
530                                                
531                                                Color color = getRenderColor();
532                                                float b = alphaMult * weight / max;
533//                                              if (tiles.length > 30) {
534//                                                      b *= weight / max;
535//                                                      b *= weight / max;
536//                                              }
537                                                //b = alphaMult;
538                                                GL11.glColor4ub((byte)color.getRed(),
539                                                                (byte)color.getGreen(),
540                                                                (byte)color.getBlue(),
541                                                                (byte)((float)color.getAlpha() * b));
542                                                //xOff = yOff = 0f;
543                                                renderQuad((int)i, (int)j, (x + xOff - w / 2f + i/samples * size + size/2f - renderSize/2f) * factor,
544                                                                   (y + yOff - h / 2f +  j/samples * size + size/2f - renderSize/2f) * factor,
545                                                                   renderSize * factor, renderSize * factor,
546                                                                   texCellX * 0.25f, texCellY * 0.25f,
547                                                                   0.25f, 0.25f,
548                                                                   angle);
549                                        }
550                                }
551                        }
552                        
553                        GL11.glEnd();
554                }
555        }
556        
557        
558        protected float elapsed = 0f;
559        @Override
560        public void advance(float amount) {
561                super.advance(amount);
562//              angleOffset += days * 20f;
563                float days = Global.getSector().getClock().convertToDays(amount);
564                elapsed += days; 
565        }
566
567        protected void renderQuad(int i, int j, float x, float y, float width, float height, float texX, float texY, float texW, float texH, float angle) {
568                if (angle != 0) {
569                        float vw = width / 2f;
570                        float vh = height / 2f;
571                        float cx = x + vw;
572                        float cy = y + vh;
573
574                        float cos = (float) Math.cos(angle * Misc.RAD_PER_DEG);
575                        float sin = (float) Math.sin(angle * Misc.RAD_PER_DEG);
576                        
577                        GL11.glTexCoord2f(texX, texY);
578                        GL11.glVertex2f(cx + (-vw * cos + vh * sin), cy + (-vw * sin - vh * cos));
579        
580                        GL11.glTexCoord2f(texX, texY + texH);
581                        GL11.glVertex2f(cx + (-vw * cos - vh * sin), cy + (-vw * sin + vh * cos));
582        
583                        GL11.glTexCoord2f(texX + texW, texY + texH);
584                        GL11.glVertex2f(cx + (vw * cos - vh * sin), cy + (vw * sin + vh * cos));
585        
586                        GL11.glTexCoord2f(texX + texW, texY);
587                        GL11.glVertex2f(cx + (vw * cos + vh * sin), cy + (vw * sin - vh * cos));
588                        
589                } else {
590                        GL11.glTexCoord2f(texX, texY);
591                        GL11.glVertex2f(x, y);
592        
593                        GL11.glTexCoord2f(texX, texY + texH);
594                        GL11.glVertex2f(x, y + height);
595        
596                        GL11.glTexCoord2f(texX + texW, texY + texH);
597                        GL11.glVertex2f(x + width, y + height);
598        
599                        GL11.glTexCoord2f(texX + texW, texY);
600                        GL11.glVertex2f(x + width, y);
601                }
602        }
603        
604        public float getMaxEffectRadius(Vector2f locFrom) {
605                // TODO: do intersection check from locFrom to an actual filled tile?
606                
607                float size = getTileSize();
608                float renderSize = getTileRenderSize();
609                float w = tiles.length * size * 0.5f + (renderSize - size) * 0.5f;
610                float h = tiles[0].length * size * 0.5f + (renderSize - size) * 0.5f;
611                return Math.max(w, h) * 1.5f;
612        }
613        public float getMinEffectRadius(Vector2f locFrom) {
614                return 0f;
615        }
616        
617        public float getOptimalEffectRadius(Vector2f locFrom) {
618                return getMaxEffectRadius(locFrom);
619        }
620        
621        
622        
623        @Override
624        protected float getExtraSoundRadius() {
625                return 200f;
626        }
627
628
629        public float getProximitySoundFactor() {
630                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
631                float r = player.getRadius() + getExtraSoundRadius();
632                Vector2f test = player.getLocation();
633                
634                float x = this.entity.getLocation().x;
635                float y = this.entity.getLocation().y;
636                float size = getTileSize();
637                float containsSize = getTileContainsSize();
638                
639                float w = tiles.length * size;
640                float h = tiles[0].length * size;
641
642                x -= w/2f;
643                y -= h/2f;
644                
645                float extra = (containsSize - size) / 2f;
646                
647                if (test.x + r + extra < x) return 0f;
648                if (test.y + r + extra < y) return 0f;
649                if (test.x > x + w + r + extra) return 0f;
650                if (test.y > y + h + r + extra) return 0f;
651                
652                int xIndex = (int) ((test.x - x) / size);
653                int yIndex = (int) ((test.y - y) / size);
654                
655                if (xIndex < 0) xIndex = 0;
656                if (yIndex < 0) yIndex = 0;
657                
658                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
659                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
660                
661
662                float closestDist = Float.MAX_VALUE;
663                
664                for (float i = Math.max(0, xIndex - 2); i <= xIndex + 2 && i < tiles.length; i++) {
665                        for (float j = Math.max(0, yIndex - 2); j <= yIndex + 2 && j < tiles[0].length; j++) {
666                                int texIndex = tiles[(int) i][(int) j];
667                                if (texIndex >= 0) {
668                                        float tx = x + i * size + size/2f - containsSize/2f;
669                                        float ty = y + j * size + size/2f - containsSize/2f;
670                                         
671                                        if (test.x + r < tx) continue;
672                                        if (test.y + r < ty) continue;
673                                        if (test.x > tx + containsSize + r) continue;
674                                        if (test.y > ty + containsSize + r) continue;
675                                        
676                                        //float dist = Misc.getDistance(test, new Vector2f(tx + containsSize/2f, ty + containsSize/2f));
677                                        float dx = Math.abs(test.x - tx - containsSize / 2f);
678                                        float dy = Math.abs(test.y - ty - containsSize / 2f);
679                                        float dist = Math.max(dx, dy);
680                                        if (dist < closestDist) {
681                                                closestDist = dist;
682                                        }
683                                }
684                        }
685                }
686                
687                //System.out.println("Closest: " + closestDist);
688                //float max = containsSize * 0.5f * 1.41f + EXTRA_SOUND_RADIUS;
689                float max = containsSize * 0.5f + getExtraSoundRadius();
690                if (closestDist < containsSize * 0.5f) return 1f;
691                
692                float p = (max - closestDist) / (max - containsSize * 0.5f);
693                if (p < 0) p = 0;
694                if (p > 1) p = 1;
695                
696                return p;
697        }
698        
699        
700        
701        public static String encodeTiles(int [][] tiles) {
702                int w = tiles.length;
703                int h = tiles[0].length;
704                int total = w * h;
705                
706//              int [] masks = new int [] {
707//                      1,
708//                      2,
709//                      4,
710//                      8,
711//                      16,
712//                      32,
713//                      64,
714//                      128,
715//              };
716                int [] masks = new int [] {
717                                128,
718                                64,
719                                32,
720                                16,
721                                8,
722                                4,
723                                2,
724                                1,
725                        };
726                
727                int bit = 0;
728                int curr = 0;
729                //List<Byte> bytes = new ArrayList<Byte>();
730                byte [] input = new byte [(int) Math.ceil(total / 8f)];
731                for (int i = 0; i < total; i++) {
732                        int x = i % w;
733                        int y = i / w;
734                        int val = tiles[x][y];
735                        int mask = masks[bit];
736                
737                        if (val >= 0) {
738                                curr = (curr | mask);
739                        }
740                        
741                        bit++;
742                        bit %= 8;
743                        
744                        if (bit == 0) {
745                                input[i/8] = ((byte) curr);
746                                curr = 0;
747                        }
748                }
749                if (bit != 0) {
750                        input[input.length - 1] = ((byte) curr);
751                        curr = 0;
752                }
753
754                /*
755                List<Byte> bytes = new ArrayList<Byte>();
756                String seq = "";
757                for (int i = 0; i < total; i++) {
758                        int x = i % w;
759                        int y = i / w;
760                        int val = tiles[x][y];
761                        String curr = "0";
762                        if (val >= 0) curr = "1";
763                        seq += curr;
764                        if (seq.length() == 8) {
765                                //byte b = Byte.parseByte(seq, 2);
766                                int b = Integer.parseInt(seq, 2);
767                                bytes.add((byte) b);
768                                seq = "";
769                        }
770                }
771                if (seq.length() > 0) {
772                        while (seq.length() < 8) {
773                                seq = seq + "0";
774                        }
775                        //byte b = Byte.parseByte(seq, 2);
776                        //bytes.add(b);
777                        int b = Integer.parseInt(seq, 2);
778                        bytes.add((byte) b);
779                }
780                
781                byte [] input = new byte [bytes.size()];
782                for (int i = 0; i < bytes.size(); i++) {
783                        input[i] = bytes.get(i);
784                }
785                */
786                
787                Deflater compressor = new Deflater();
788                //compresser.setLevel(Deflater.BEST_COMPRESSION);
789                //compresser.setStrategy(Deflater.HUFFMAN_ONLY);
790                compressor.setInput(input);
791                compressor.finish();
792
793                StringBuilder result = new StringBuilder();
794                byte [] temp = new byte[100];
795                
796                while (!compressor.finished()) {
797                        int read = compressor.deflate(temp);
798                        result.append(toHexString(Arrays.copyOf(temp, read)));
799                }
800                
801//              result = new StringBuilder();
802//              result.append(toHexString(input));
803
804                compressor.end();
805                
806                return result.toString();
807        }
808        
809        public static int [][] decodeTiles(String string, int w, int h) throws DataFormatException {
810                byte [] input = toByteArray(string);
811                
812                Inflater decompressor = new Inflater();
813                decompressor.setInput(input);
814                
815                int [][] tiles = new int [w][h];
816                int total = w * h;
817                int curr = 0;
818                
819                byte [] temp = new byte[100];
820                OUTER: while (!decompressor.finished()) {
821                        int read = decompressor.inflate(temp);
822                        for (int i = 0; i < read; i++) {
823                                byte b = temp[i];
824                                
825                                for (int j = 7; j >= 0; j--) {
826                                        int x = curr % w;
827                                        int y = curr / w;
828                                        curr++;
829                                
830                                        if (curr > total) break OUTER;
831                                        //System.out.println("bytes read: " + curr);
832                                        if ((b & (0x01 << j)) > 0) {
833                                                tiles[x][y] = 1;
834                                        } else {
835                                                tiles[x][y] = -1;       
836                                        }
837                                }
838                        }
839                }
840
841                decompressor.end();
842
843                return tiles;
844        }
845        
846        
847        public static String toHexString(byte[] array) {
848                return DatatypeConverter.printBase64Binary(array);
849        }
850
851        public static byte[] toByteArray(String s) {
852            return DatatypeConverter.parseBase64Binary(s);
853        }
854        
855        
856        
857        public static void main(String[] args) throws DataFormatException {
858                
859//              int [][] tiles = new int[][] {
860//                      {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
861//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
862//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
863//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1},
864//                      {1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
865//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
866//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
867//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
868//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
869//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
870//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
871//                      {1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
872//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
873//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
874//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
875//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
876//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1},
877//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
878//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
879//                      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
880//              };
881                
882                int [][] tiles = new int[][] {
883                                {-1, 1, 1, 1, -1},
884                                {1, 1, 1, 1, 1},
885                                {1, 1, 1, 1, -1},
886                                {1, 1, 1, -1, 1},
887                };
888                
889//              int w = 128;
890//              int h = 128;
891//              int [][] tiles = new int [w][h];
892//              
893//              for (int i = 0; i < w; i++) {
894//                      for (int j = 0; j < h; j++) {
895//                              if ((float) Math.random() > 0.8f) {
896//                                      tiles[i][j] = 0;
897//                              } else {
898//                                      tiles[i][j] = -1;
899//                              }
900//                      }
901//              }
902                
903                
904                System.out.println("Original:");
905                for (int i = 0; i < tiles.length; i++) {
906                        for (int j = 0; j < tiles[0].length; j++) {
907                                System.out.print(String.format("% 2d,", tiles[i][j]));
908                        }
909                        System.out.println();
910                }
911                
912                String result = encodeTiles(tiles);
913                System.out.println(result);
914                //System.out.println(result.length() + ", would be " + (w * h / 4) + " without compression");
915                int [][] tilesBack = decodeTiles(result, tiles.length, tiles[0].length);
916                
917                System.out.println("Decoded:");
918                for (int i = 0; i < tilesBack.length; i++) {
919                        for (int j = 0; j < tilesBack[0].length; j++) {
920                                System.out.print(String.format("% 2d,", tilesBack[i][j]));
921                        }
922                        System.out.println();
923                }
924                
925                boolean equals = true;
926                for (int i = 0; i < tiles.length; i++) {
927                        for (int j = 0; j < tiles[0].length; j++) {
928                                if (tiles[i][j] != tilesBack[i][j]) {
929                                        equals = false;
930                                }
931                        }
932                }
933                
934                System.out.println("Equal: " + equals);
935                
936//              for (int x = 0; x < tilesBack.length; x++) {
937//                      for (int y = 0; y < tilesBack.length; y++) {
938//                              System.out.print(tilesBack[x][y] + " ");
939//                      }
940//                      System.out.println();
941//              }
942        }
943}
944
945
946
947
948
949
950