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