001package squidpony.squidgrid;
002
003import squidpony.squidmath.*;
004
005import java.util.*;
006
007/**
008 * Basic radius strategy implementations likely to be used for roguelikes.
009 *
010 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
011 */
012public enum Radius {
013
014    /**
015     * In an unobstructed area the FOV would be a square.
016     *
017     * This is the shape that would represent movement radius in an 8-way
018     * movement scheme with no additional cost for diagonal movement.
019     */
020    SQUARE,
021    /**
022     * In an unobstructed area the FOV would be a diamond.
023     *
024     * This is the shape that would represent movement radius in a 4-way
025     * movement scheme.
026     */
027    DIAMOND,
028    /**
029     * In an unobstructed area the FOV would be a circle.
030     *
031     * This is the shape that would represent movement radius in an 8-way
032     * movement scheme with all movement cost the same based on distance from
033     * the source
034     */
035    CIRCLE,
036    /**
037     * In an unobstructed area the FOV would be a cube.
038     *
039     * This is the shape that would represent movement radius in an 8-way
040     * movement scheme with no additional cost for diagonal movement.
041     */
042    CUBE,
043    /**
044     * In an unobstructed area the FOV would be a octahedron.
045     *
046     * This is the shape that would represent movement radius in a 4-way
047     * movement scheme.
048     */
049    OCTAHEDRON,
050    /**
051     * In an unobstructed area the FOV would be a sphere.
052     *
053     * This is the shape that would represent movement radius in an 8-way
054     * movement scheme with all movement cost the same based on distance from
055     * the source
056     */
057    SPHERE,
058    /**
059     * Like {@link #CIRCLE}, but always uses a rough approximation of distance instead of a more expensive (but more
060     * accurate) Euclidean calculation.
061     */
062    ROUGH_CIRCLE;
063
064    private static final double PI2 = Math.PI * 2;
065    public double radius(int startx, int starty, int startz, int endx, int endy, int endz) {
066        return radius(startx, starty, startz, endx, endy, (double) endz);
067    }
068
069    public double radius(double startx, double starty, double startz, double endx, double endy, double endz) {
070        double dx = Math.abs(startx - endx);
071        double dy = Math.abs(starty - endy);
072        double dz = Math.abs(startz - endz);
073        return radius(dx, dy, dz);
074    }
075
076    public double radius(int dx, int dy, int dz) {
077        return radius((float) dx, (float) dy, (float) dz);
078    }
079
080    public double radius(double dx, double dy, double dz) {
081        dx = Math.abs(dx);
082        dy = Math.abs(dy);
083        dz = Math.abs(dz);
084        switch (this) {
085            case SQUARE:
086            case CUBE:
087                return Math.max(dx, Math.max(dy, dz));//radius is longest axial distance
088            case CIRCLE:
089            case SPHERE:
090                return Math.sqrt(dx * dx + dy * dy + dz * dz);//standard spherical radius
091            case ROUGH_CIRCLE: // ignores z
092                if(dx == dy) return 1.5 * dx;
093                else if(dx < dy) return 1.5 * dx + (dy - dx);
094                else return 1.5 * dy + (dx - dy);
095            default:
096                return dx + dy + dz;//radius is the manhattan distance
097        }
098    }
099
100    public double radius(int startx, int starty, int endx, int endy) {
101        return radius(startx, starty, endx, (double) endy);
102    }
103    public double radius(Coord start, Coord end) {
104        return radius(start.x, start.y, end.x, (double) end.y);
105    }
106    public double radius(Coord end) {
107        return radius(0.0, 0.0, end.x, end.y);
108    }
109
110    public double radius(double startx, double starty, double endx, double endy) {
111        double dx = startx - endx;
112        double dy = starty - endy;
113        return radius(dx, dy);
114    }
115
116    public double radius(int dx, int dy) {
117        return radius(dx, (double) dy);
118    }
119
120    public double radius(double dx, double dy) {
121        dx = Math.abs(dx);
122        dy = Math.abs(dy);
123        switch (this) {
124            case SQUARE:
125            case CUBE:
126                return Math.max(dx, dy);//radius is longest axial distance
127            case ROUGH_CIRCLE: // radius is an approximation, roughly octagonal
128                if(dx == dy) return 1.5 * dx;
129                else if(dx < dy) return 1.5 * dx + (dy - dx);
130                else return 1.5 * dy + (dx - dy);
131            case CIRCLE:
132            case SPHERE:
133                return Math.sqrt(dx * dx + dy * dy);//standard spherical radius
134            default:
135                return dx + dy;//radius is the manhattan distance
136        }
137    }
138
139    public Coord onUnitShape(double distance, IRNG rng) {
140        int x, y;
141        switch (this) {
142            case SQUARE:
143            case CUBE:
144                x = rng.between((int) -distance, (int) distance + 1);
145                y = rng.between((int) -distance, (int) distance + 1);
146                break;
147            case DIAMOND:
148            case OCTAHEDRON:
149                x = rng.between((int) -distance, (int) distance + 1);
150                y = rng.between((int) -distance, (int) distance + 1);
151                if (radius(x, y) > distance) {
152                    if (x > 0) {
153                        if (y > 0) {
154                            x = (int) (distance - x);
155                            y = (int) (distance - y);
156                        } else {
157                            x = (int) (distance - x);
158                            y = (int) (-distance - y);
159                        }
160                    } else {
161                        if (y > 0) {
162                            x = (int) (-distance - x);
163                            y = (int) (distance - y);
164                        } else {
165                            x = (int) (-distance - x);
166                            y = (int) (-distance - y);
167                        }
168                    }
169                }
170                break;
171            default: // includes CIRCLE, SPHERE, and ROUGH_CIRCLE
172                double radius = distance * Math.sqrt(rng.nextDouble());
173                double theta = rng.between(0, PI2);
174                x = (int) Math.round(NumberTools.cos(theta) * radius);
175                y = (int) Math.round(NumberTools.sin(theta) * radius);
176        }
177
178        return Coord.get(x, y);
179    }
180
181    public Coord3D onUnitShape3D(double distance, IRNG rng) {
182        int x = 0, y = 0, z = 0;
183        switch (this) {
184            case SQUARE:
185            case DIAMOND:
186            case CIRCLE:
187            case ROUGH_CIRCLE:
188                Coord p = onUnitShape(distance, rng);
189                return new Coord3D(p.x, p.y, 0);//2D strategies
190            case CUBE:
191                x = rng.between((int) -distance, (int) distance + 1);
192                y = rng.between((int) -distance, (int) distance + 1);
193                z = rng.between((int) -distance, (int) distance + 1);
194                break;
195            case OCTAHEDRON:
196            case SPHERE:
197                do {
198                    x = rng.between((int) -distance, (int) distance + 1);
199                    y = rng.between((int) -distance, (int) distance + 1);
200                    z = rng.between((int) -distance, (int) distance + 1);
201                } while (radius(x, y, z) > distance);
202        }
203
204        return new Coord3D(x, y, z);
205    }
206    public double volume2D(double radiusLength)
207    {
208        switch (this) {
209            case SQUARE:
210            case CUBE:
211                return (radiusLength * 2 + 1) * (radiusLength * 2 + 1);
212            case DIAMOND:
213            case OCTAHEDRON:
214                return radiusLength * (radiusLength + 1) * 2 + 1;
215            default:
216                return Math.PI * radiusLength * radiusLength + 1;
217        }
218    }
219    public double volume3D(double radiusLength)
220    {
221        switch (this) {
222            case SQUARE:
223            case CUBE:
224                return (radiusLength * 2 + 1) * (radiusLength * 2 + 1) * (radiusLength * 2 + 1);
225            case DIAMOND:
226            case OCTAHEDRON:
227                double total = radiusLength * (radiusLength + 1) * 2 + 1;
228                for(double i = radiusLength - 1; i >= 0; i--)
229                {
230                    total += (i * (i + 1) * 2 + 1) * 2;
231                }
232                return total;
233            default:
234                return Math.PI * radiusLength * radiusLength * radiusLength * 4.0 / 3.0 + 1;
235        }
236    }
237
238
239
240
241    private int clamp(int n, int min, int max)
242    {
243        return Math.min(Math.max(min, n), max - 1);
244    }
245
246    public OrderedSet<Coord> perimeter(Coord center, int radiusLength, boolean surpassEdges, int width, int height)
247    {
248        OrderedSet<Coord> rim = new OrderedSet<>(4 * radiusLength);
249        if(!surpassEdges && (center.x < 0 || center.x >= width || center.y < 0 || center.y > height))
250            return rim;
251        if(radiusLength < 1) {
252            rim.add(center);
253            return rim;
254        }
255        switch (this) {
256            case SQUARE:
257            case CUBE:
258            {
259                for (int i = center.x - radiusLength; i <= center.x + radiusLength; i++) {
260                    int x = i;
261                    if(!surpassEdges) x = clamp(i, 0, width);
262                    rim.add(Coord.get(x, clamp(center.y - radiusLength, 0, height)));
263                    rim.add(Coord.get(x, clamp(center.y + radiusLength, 0, height)));
264                }
265                for (int j = center.y - radiusLength; j <= center.y + radiusLength; j++) {
266                    int y = j;
267                    if(!surpassEdges) y = clamp(j, 0, height);
268                    rim.add(Coord.get(clamp(center.x - radiusLength, 0, height), y));
269                    rim.add(Coord.get(clamp(center.x + radiusLength, 0, height), y));
270                }
271            }
272            break;
273            case DIAMOND:
274            case OCTAHEDRON: {
275                int xUp = center.x + radiusLength, xDown = center.x - radiusLength,
276                        yUp = center.y + radiusLength, yDown = center.y - radiusLength;
277                if(!surpassEdges) {
278                    xDown = clamp(xDown, 0, width);
279                    xUp = clamp(xUp, 0, width);
280                    yDown = clamp(yDown, 0, height);
281                    yUp = clamp(yUp, 0, height);
282                }
283
284                rim.add(Coord.get(xDown, center.y));
285                rim.add(Coord.get(xUp, center.y));
286                rim.add(Coord.get(center.x, yDown));
287                rim.add(Coord.get(center.x, yUp));
288
289                for (int i = xDown + 1, c = 1; i < center.x; i++, c++) {
290                    int x = i;
291                    if(!surpassEdges) x = clamp(i, 0, width);
292                    rim.add(Coord.get(x, clamp(center.y - c, 0, height)));
293                    rim.add(Coord.get(x, clamp(center.y + c, 0, height)));
294                }
295                for (int i = center.x + 1, c = 1; i < center.x + radiusLength; i++, c++) {
296                    int x = i;
297                    if(!surpassEdges) x = clamp(i, 0, width);
298                    rim.add(Coord.get(x, clamp(center.y + radiusLength - c, 0, height)));
299                    rim.add(Coord.get(x, clamp(center.y - radiusLength + c, 0, height)));
300                }
301            }
302            break;
303            default:
304            {
305                double theta;
306                int x, y, denom = 1;
307                boolean anySuccesses;
308                while(denom <= 256) {
309                    anySuccesses = false;
310                    for (int i = 1; i <= denom; i+=2)
311                    {
312                        theta = i * (PI2 / denom);
313                        x = (int) (NumberTools.cos(theta) * (radiusLength + 0.25)) + center.x;
314                        y = (int) (NumberTools.sin(theta) * (radiusLength + 0.25)) + center.y;
315                        
316                        if (!surpassEdges) {
317                            x = clamp(x, 0, width);
318                            y = clamp(y, 0, height);
319                        }
320                        Coord p = Coord.get(x, y);
321                        boolean test = !rim.contains(p);
322
323                        rim.add(p);
324                        anySuccesses = test || anySuccesses;
325                    }
326                    if(!anySuccesses)
327                        break;
328                    denom *= 2;
329                }
330
331            }
332        }
333        return rim;
334    }
335    public Coord extend(Coord center, Coord middle, int radiusLength, boolean surpassEdges, int width, int height)
336    {
337        if(!surpassEdges && (center.x < 0 || center.x >= width || center.y < 0 || center.y > height ||
338                middle.x < 0 || middle.x >= width || middle.y < 0 || middle.y > height))
339            return Coord.get(0, 0);
340        if(radiusLength < 1) {
341            return center;
342        }
343        double theta = NumberTools.atan2(middle.y - center.y, middle.x - center.x),
344                cosTheta = NumberTools.cos(theta), sinTheta = NumberTools.sin(theta);
345
346        Coord end = Coord.get(middle.x, middle.y);
347        switch (this) {
348            case SQUARE:
349            case CUBE:
350            case DIAMOND:
351            case OCTAHEDRON:
352            {
353                int rad2 = 0;
354                if(surpassEdges)
355                {
356                    while (radius(center.x, center.y, end.x, end.y) < radiusLength) {
357                        rad2++;
358                        end = Coord.get((int) Math.round(cosTheta * rad2) + center.x
359                                , (int) Math.round(sinTheta * rad2) + center.y);
360                    }
361                }
362                else {
363                    while (radius(center.x, center.y, end.x, end.y) < radiusLength) {
364                        rad2++;
365                        end = Coord.get(clamp((int) Math.round(cosTheta * rad2) + center.x, 0, width)
366                                      , clamp((int) Math.round(sinTheta * rad2) + center.y, 0, height));
367                        if (end.x == 0 || end.x == width - 1 || end.y == 0 || end.y == height - 1)
368                            return end;
369                    }
370                }
371
372                return end;
373            }
374            default:
375            {
376                end = Coord.get(clamp( (int) Math.round(cosTheta * radiusLength) + center.x, 0, width)
377                        , clamp( (int) Math.round(sinTheta * radiusLength) + center.y, 0, height));
378                if(!surpassEdges) {
379                    long edgeLength;
380//                    if (end.x == 0 || end.x == width - 1 || end.y == 0 || end.y == height - 1)
381                    if (end.x < 0)
382                    {
383                        // wow, we lucked out here. the only situation where cos(angle) is 0 is if the angle aims
384                        // straight up or down, and then x cannot be < 0 or >= width.
385                        edgeLength = Math.round((-center.x) / cosTheta);
386                        end = end.setY(clamp((int) Math.round(sinTheta * edgeLength) + center.y, 0, height));
387                    }
388                    else if(end.x >= width)
389                    {
390                        // wow, we lucked out here. the only situation where cos(angle) is 0 is if the angle aims
391                        // straight up or down, and then x cannot be < 0 or >= width.
392                        edgeLength = Math.round((width - 1 - center.x) / cosTheta);
393                        end = end.setY(clamp((int) Math.round(sinTheta * edgeLength) + center.y, 0, height));
394                    }
395
396                    if (end.y < 0)
397                    {
398                        // wow, we lucked out here. the only situation where sin(angle) is 0 is if the angle aims
399                        // straight left or right, and then y cannot be < 0 or >= height.
400                        edgeLength = Math.round((-center.y) / sinTheta);
401                        end = end.setX(clamp((int) Math.round(cosTheta * edgeLength) + center.x, 0, width));
402                    }
403                    else if(end.y >= height)
404                    {
405                        // wow, we lucked out here. the only situation where sin(angle) is 0 is if the angle aims
406                        // straight left or right, and then y cannot be < 0 or >= height.
407                        edgeLength = Math.round((height - 1 - center.y) / sinTheta);
408                        end = end.setX(clamp((int) Math.round(cosTheta * edgeLength) + center.x, 0, width));
409                    }
410                }
411                return end;
412            }
413        }
414    }
415
416    /**
417     * Compares two Radius enums as if they are both in a 2D plane; that is, Radius.SPHERE is treated as equal to
418     * Radius.CIRCLE, Radius.CUBE is equal to Radius.SQUARE, and Radius.OCTAHEDRON is equal to Radius.DIAMOND.
419     * @param other the Radius to compare this to
420     * @return true if the 2D versions of both Radius enums are the same shape.
421     */
422    public boolean equals2D(Radius other)
423    {
424        switch (this)
425        {
426            case ROUGH_CIRCLE:
427                return other == ROUGH_CIRCLE;
428            case CIRCLE:
429            case SPHERE:
430                return other == CIRCLE || other == SPHERE;
431            case SQUARE:
432            case CUBE:
433                return other == SQUARE || other == CUBE;
434            default:
435                return other == DIAMOND || other == OCTAHEDRON;
436        }
437    }
438    public boolean inRange(int startx, int starty, int endx, int endy, int minRange, int maxRange)
439    {
440        double dist = radius(startx, starty, endx, endy);
441        return dist >= minRange - 0.001 && dist <= maxRange + 0.001;
442    }
443
444    public int roughDistance(int xPos, int yPos) {
445        int x = Math.abs(xPos), y = Math.abs(yPos);
446        switch (this) {
447            case CIRCLE:
448            case SPHERE:
449            case ROUGH_CIRCLE:
450            {
451                if(x == y) return 3 * x;
452                else if(x < y) return 3 * x + 2 * (y - x);
453                else return 3 * y + 2 * (x - y);
454            }
455            case DIAMOND:
456            case OCTAHEDRON:
457                return 2 * (x + y);
458            default:
459                return 2 * Math.max(x, y);
460        }
461    }
462
463    public List<Coord> pointsInside(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height)
464    {
465        return pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, null);
466    }
467    public List<Coord> pointsInside(Coord center, int radiusLength, boolean surpassEdges, int width, int height)
468    {
469        if(center == null) return null;
470        return pointsInside(center.x, center.y, radiusLength, surpassEdges, width, height, null);
471    }
472
473    public List<Coord> pointsInside(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height, List<Coord> buf)
474    {
475        List<Coord> contents = buf == null ? new ArrayList<Coord>((int)Math.ceil(volume2D(radiusLength))) : buf;
476        if(!surpassEdges && (centerX < 0 || centerX >= width || centerY < 0 || centerY >= height))
477            return contents;
478        if(radiusLength < 1) {
479            contents.add(Coord.get(centerX, centerY));
480            return contents;
481        }
482        switch (this) {
483            case SQUARE:
484            case CUBE:
485            {
486                for (int i = centerX - radiusLength; i <= centerX + radiusLength; i++) {
487                    for (int j = centerY - radiusLength; j <= centerY + radiusLength; j++) {
488                        if(!surpassEdges && (i < 0 || j < 0 || i >= width || j >= height))
489                            continue;
490                        contents.add(Coord.get(i, j));
491                    }
492                }
493            }
494            break;
495            case DIAMOND:
496            case OCTAHEDRON: {
497                for (int i = centerX - radiusLength; i <= centerX + radiusLength; i++) {
498                    for (int j = centerY - radiusLength; j <= centerY + radiusLength; j++) {
499                        if ((Math.abs(centerX - i) + Math.abs(centerY- j) > radiusLength) ||
500                                (!surpassEdges && (i < 0 || j < 0 || i >= width || j >= height)))
501                            continue;
502                        contents.add(Coord.get(i, j));
503                    }
504                }
505            }
506            break;
507            default:
508            {
509                float high, changedX;
510                int rndX, rndY;
511                for (int dx = -radiusLength; dx <= radiusLength; ++dx) {
512                    changedX = dx - 0.25f * (dx >> 31 | -dx >>> 31); // project nayuki signum
513                    rndX = Math.round(changedX);
514                    high = (float) Math.sqrt(radiusLength * radiusLength - changedX * changedX);
515                    if (surpassEdges || !(centerX + rndX < 0||
516                            centerX + rndX >= width))
517                        contents.add(Coord.get(centerX + rndX, centerY));
518                    for (float dy = high; dy >= 0.75f; --dy) {
519                        rndY = Math.round(dy - 0.25f);
520                        if (surpassEdges || !(centerX + rndX < 0 || centerY + rndY < 0 ||
521                                centerX + rndX >= width || centerY + rndY >= height))
522                            contents.add(Coord.get(centerX + rndX, centerY + rndY));
523                        if (surpassEdges || !(centerX + rndX < 0 || centerY - rndY < 0 ||
524                                centerX + rndX >= width || centerY - rndY >= height))
525                            contents.add(Coord.get(centerX + rndX, centerY - rndY));
526                    }
527                }
528            }
529        }
530        return contents;
531    }
532
533    /**
534     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Chebyshev measurement (making
535     * a square). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List of
536     * Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y less
537     * than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any Coords
538     * in the actual radius.
539     * @param centerX the center Coord x
540     * @param centerY the center Coord x
541     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
542     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
543     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
544     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
545     * @return a new List containing the points within radiusLength of the center
546
547     */
548    public static List<Coord> inSquare(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height)
549    {
550        return SQUARE.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, null);
551    }
552    /**
553     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Manhattan measurement (making
554     * a diamond). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List
555     * of Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y
556     * less than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any
557     * Coords in the actual radius.
558     * @param centerX the center Coord x
559     * @param centerY the center Coord x
560     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
561     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
562     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
563     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
564     * @return a new List containing the points within radiusLength of the center
565     */
566    public static List<Coord> inDiamond(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height)
567    {
568        return DIAMOND.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, null);
569    }
570    /**
571     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Euclidean measurement (making
572     * a circle). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List of
573     * Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y less
574     * than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any Coords
575     * in the actual radius.
576     * @param centerX the center Coord x
577     * @param centerY the center Coord x
578     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
579     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
580     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
581     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
582     * @return a new List containing the points within radiusLength of the center
583     */
584    public static List<Coord> inCircle(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height)
585    {
586        return CIRCLE.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, null);
587    }
588
589    /**
590     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Chebyshev measurement (making
591     * a square). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List of
592     * Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y less
593     * than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any Coords
594     * in the actual radius.
595     * @param centerX the center Coord x
596     * @param centerY the center Coord x
597     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
598     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
599     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
600     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
601     * @param buf the List of Coord to append points to; may be null to create a new List
602     * @return buf, after appending Coords to it, or a new List if buf was null
603     */
604    public static List<Coord> inSquare(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height, List<Coord> buf)
605    {
606        return SQUARE.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, buf);
607    }
608    /**
609     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Manhattan measurement (making
610     * a diamond). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List
611     * of Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y
612     * less than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any
613     * Coords in the actual radius.
614     * @param centerX the center Coord x
615     * @param centerY the center Coord x
616     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
617     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
618     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
619     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
620     * @param buf the List of Coord to append points to; may be null to create a new List
621     * @return buf, after appending Coords to it, or a new List if buf was null
622     */
623    public static List<Coord> inDiamond(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height, List<Coord> buf)
624    {
625        return DIAMOND.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, buf);
626    }
627    /**
628     * Gets a List of all Coord points within {@code radiusLength} of {@code center} using Euclidean measurement (making
629     * a circle). Appends Coords to {@code buf} if it is non-null, and returns either buf or a freshly-allocated List of
630     * Coord. If {@code surpassEdges} is false, which is the normal usage, this will not produce Coords with x or y less
631     * than 0 or greater than {@code width} or {@code height}; if surpassEdges is true, then it can produce any Coords
632     * in the actual radius.
633     * @param centerX the center Coord x
634     * @param centerY the center Coord x
635     * @param radiusLength the inclusive distance from (centerX,centerY) for Coords to use in the List
636     * @param surpassEdges usually should be false; if true, can produce Coords with negative x/y or past width/height
637     * @param width the width of the area this can place Coords (exclusive, not relative to center, usually map width)
638     * @param height the height of the area this can place Coords (exclusive, not relative to center, usually map height)
639     * @param buf the List of Coord to append points to; may be null to create a new List
640     * @return buf, after appending Coords to it, or a new List if buf was null
641     */
642    public static List<Coord> inCircle(int centerX, int centerY, int radiusLength, boolean surpassEdges, int width, int height, List<Coord> buf)
643    {
644        return CIRCLE.pointsInside(centerX, centerY, radiusLength, surpassEdges, width, height, buf);
645    }
646
647    /**
648     * Given an Iterable of Coord (such as a List or Set), a distance to expand outward by (using this Radius), and the
649     * bounding height and width of the map, gets a "thickened" group of Coord as a Set where each Coord in points has
650     * been expanded out by an amount no greater than distance. As an example, you could call this on a line generated
651     * by Bresenham, OrthoLine, or an LOS object's getLastPath() method, and expand the line into a thick "brush stroke"
652     * where this Radius affects the shape of the ends. This will never produce a Coord with negative x or y, a Coord
653     * with x greater than or equal to width, or a Coord with y greater than or equal to height.
654     * @param distance the distance, as measured by this Radius, to expand each Coord on points up to
655     * @param width the bounding width of the map (exclusive)
656     * @param height the bounding height of the map (exclusive)
657     * @param points an Iterable (such as a List or Set) of Coord that this will make a "thickened" version of
658     * @return a Set of Coord that covers a wider area than what points covers; each Coord will be unique (it's a Set)
659     */
660    public OrderedSet<Coord> expand(int distance, int width, int height, Iterable<Coord> points)
661    {
662        List<Coord> around = pointsInside(Coord.get(distance, distance), distance, false, width, height);
663        OrderedSet<Coord> expanded = new OrderedSet<>(around.size() * 16, 0.25f);
664        int tx, ty;
665        for(Coord pt : points)
666        {
667            for(Coord ar : around)
668            {
669                tx = pt.x + ar.x - distance;
670                ty = pt.y + ar.y - distance;
671                if(tx >= 0 && tx < width && ty >= 0 && ty < height)
672                    expanded.add(Coord.get(tx, ty));
673            }
674        }
675        return expanded;
676    }
677    /**
678     * Gets the appropriate {@link Measurement} to pass to a constructor if you already have a Radius.
679     * Matches SQUARE or CUBE to CHEBYSHEV, DIAMOND or OCTAHEDRON to MANHATTAN, and CIRCLE or SPHERE to EUCLIDEAN.
680     * 
681     * @see Measurement#matchingMeasurement(Radius) an equivalent method in Measurement
682     * @see Measurement#matchingRadius() a method to do the inverse of this and get a Radius from a Measurement
683     *
684     * @return a {@link Measurement} that matches this; SQUARE to CHEBYSHEV, DIAMOND to MANHATTAN, etc.
685     */
686    public Measurement matchingMeasurement() {
687        switch (this)
688        {
689            case CUBE:
690            case SQUARE:
691                return Measurement.CHEBYSHEV;
692            case DIAMOND:
693            case OCTAHEDRON:
694                return Measurement.MANHATTAN;
695            default:
696                return Measurement.EUCLIDEAN;
697        }
698    }
699
700}