001package squidpony.squidmath; 002 003 004import squidpony.annotation.Beta; 005 006import java.io.Serializable; 007import java.util.ArrayList; 008import java.util.List; 009 010/** 011 * Creates a field of particles that tend to form a neuron image type 012 * distribution. The distribution tends to reach towards the largest area of 013 * empty space, but features many nice branches and curls as well. 014 * If no points are added before the populate method is run, the center of the 015 * area is chosen as the single pre-populated point. 016 * <br> 017 * <a href="http://www.nolithius.com/game-development/neural-particle-deposition">Based on work by Nolithius</a> 018 * <br> 019 * Source code is available on <a href="https://github.com/Nolithius/neural-particle">GitHub</a>, 020 * as well as <a href="http://code.google.com/p/neural-particle/">Google Code (now archived)</a> 021 * <br> 022 * This class is marked Beta because no test or demo was ever written to use it. 023 * Who could be to blame for this omission... 024 * @author Eben Howard - http://squidpony.com - howard@squidpony.com 025 */ 026@Beta 027public class NeuralParticle implements Serializable{ 028 private static final long serialVersionUID = -3742942580678517149L; 029 030 private final IRNG rng; 031 private final int maxDistance, minDistance, width, height; 032 private final ArrayList<Coord> distribution = new ArrayList<>(); 033 034 public NeuralParticle(int width, int height, int maxDistance, IRNG rng) { 035 this.rng = rng; 036 this.maxDistance = maxDistance; 037 this.width = width; 038 this.height = height; 039 minDistance = 1; 040 } 041 042 /** 043 * Populates the field with given number of points. 044 * 045 * @param quantity the number of points to insert 046 */ 047 public void populate(int quantity) { 048 for (int i = 0; i < quantity; i++) { 049 add(createPoint()); 050 } 051 } 052 053 /** 054 * Returns a list of the current distribution. 055 * 056 * @return the distribution as a List of Coord 057 */ 058 public List<Coord> asList() { 059 return new ArrayList<>(distribution); 060 } 061 062 /** 063 * Returns an integer mapping of the current distribution. 064 * 065 * @param scale the value that active points will hold 066 * @return a 2D int array, with all elements equal to either 0 or scale 067 */ 068 public int[][] asIntMap(int scale) { 069 int[][] ret = new int[width][height]; 070 for (Coord p : distribution) { 071 ret[p.x][p.y] = scale; 072 } 073 return ret; 074 } 075 076 /** 077 * Adds a single specific point to the distribution. 078 * 079 * @param point the Coord, also called a pip here, to insert 080 */ 081 public void add(Coord point) { 082 distribution.add(point); 083 } 084 085 /** 086 * Creates a pip that falls within the required distance from the current 087 * distribution. Does not add the pip to the distribution. 088 * 089 * @return the created pip 090 */ 091 public Coord createPoint() { 092 Coord randomPoint = randomPoint(); 093 Coord nearestPoint = nearestPoint(randomPoint); 094 double pointDistance = randomPoint.distance(nearestPoint); 095 // Too close, toss 096 while (pointDistance < minDistance) { 097 randomPoint = randomPoint(); 098 nearestPoint = nearestPoint(randomPoint); 099 pointDistance = randomPoint.distance(nearestPoint); 100 } 101 // Adjust if we're too far 102 if (pointDistance > maxDistance) { 103 // Calculate unit vector 104 double unitX = (randomPoint.x - nearestPoint.x) / pointDistance; 105 double unitY = (randomPoint.y - nearestPoint.y) / pointDistance; 106 randomPoint = Coord.get( (int) (rng.between(minDistance, maxDistance + 1) * unitX + nearestPoint.x) 107 , (int) (rng.between(minDistance, maxDistance + 1) * unitY + nearestPoint.y)); 108 } 109 return randomPoint; 110 } 111 112 private Coord nearestPoint(Coord point) { 113 if (distribution.isEmpty()) { 114 Coord center = Coord.get(width / 2, height / 2); 115 distribution.add(center); 116 return center; 117 } 118 119 Coord nearestPoint = distribution.get(0); 120 double nearestDistance = point.distance(nearestPoint); 121 for (Coord candidatePoint : distribution) { 122 double candidateDistance = point.distance(candidatePoint); 123 if (candidateDistance > 0 && candidateDistance <= maxDistance) { 124 return candidatePoint; 125 } 126 127 if (candidateDistance < nearestDistance) { 128 nearestPoint = candidatePoint; 129 nearestDistance = candidateDistance; 130 } 131 } 132 return nearestPoint; 133 } 134 135 private Coord randomPoint() { 136 return Coord.get(rng.nextInt(width), rng.nextInt(height)); 137 } 138 139}