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}