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}