001package squidpony.squidmath;
002
003/**
004 * Generates procedural shapes based on a mask that determines what values can exist at a location.
005 * Currently, this generates spaceship-like shapes, which <a href="https://i.imgur.com/O4q1a2I.png">look like this</a>.
006 * The technique used here is derived from <a href="https://github.com/zfedoran/pixel-sprite-generator">this repo</a>,
007 * which is an adaptation of
008 * <a href="http://web.archive.org/web/20080228054410/http://www.davebollinger.com/works/pixelspaceships/"> Dave
009 * Bollinger's work</a>.
010 * Created by Tommy Ettinger on 10/12/2017.
011 */
012public class MaskedShapeGenerator {
013    public static final int[][] spaceship = {
014            {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
015            {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0},
016            {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0},
017            {0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0},
018            {0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 0},
019            {0, 1, 3, 3, 3, 3, 2, 2, 2, 3, 1, 0}
020    };
021    public static final GreasedRegion potentialBody = new GreasedRegion(spaceship, 1), // can be body or empty
022            potentialSolid = new GreasedRegion(spaceship, 2, 4), // can be solid or body, never empty
023            alwaysSolid = new GreasedRegion(spaceship, 3); // must be solid
024    public Starfish32RNG randomness = new Starfish32RNG(1234567890, 987654321);
025    public final GreasedRegion randomRegion = new GreasedRegion(randomness, 6, 12);
026    private final GreasedRegion workingBody = new GreasedRegion(6, 12),
027            workingSolid = new GreasedRegion(6, 12),
028            workingShade = new GreasedRegion(6, 12),
029            workingShine = new GreasedRegion(6, 12);
030
031    public MaskedShapeGenerator()
032    {
033    }
034
035    /**
036     * Returns a modified version of changing where 0 represents empty space, 1 represents border, and 2 represents
037     * "body." Only a 12x12 area will be changed by this call, with its minimum x and y determined by xPos and yPos.
038     * The seed will change each time this runs, producing different shapes each time.
039     * The technique used here is derived from https://github.com/zfedoran/pixel-sprite-generator .
040     * @param changing an int array that will be altered if possible
041     * @param xPos the minimum x to modify; the maximum will be xPos + 12, exclusive
042     * @param yPos the minimum y to modify; the maximum will be yPos + 12, exclusive
043     * @return changing, after modifications
044     */
045    public int[][] generateInto(int[][] changing, int xPos, int yPos)
046    {
047        int w = workingBody.width, h = workingBody.height, ys = (h + 63) >> 6;
048        if(changing.length < w * 2 || changing[0].length < h
049                || xPos + w * 2 >= changing.length || yPos + h >= changing[0].length)
050            return changing;
051        randomRegion.refill(randomness, 0.75, w, h);
052        workingSolid.remake(potentialSolid);
053        workingBody.remake(potentialBody).or(potentialSolid).andNot(alwaysSolid).and(randomRegion);
054        workingSolid.andNot(workingBody).or(randomRegion.remake(workingBody).fringe());
055        for (int x = 0, o = w*2-1; x < w; x++, o--) {
056            for (int y = 0; y < h; y++) {
057                changing[xPos + x][yPos + y] = ((workingBody.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 2 : 0)
058                        | ((workingSolid.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0);
059            }
060            System.arraycopy(changing[xPos + x], yPos, changing[xPos + o], yPos, h);
061        }
062        return changing;
063    }
064
065    /**
066     * Returns an int array (12x12) where 0 represents empty space, 1 represents border, and 2 represents "body."
067     * The seed will change each time this runs, producing different shapes each time.
068     * The technique used here is derived from https://github.com/zfedoran/pixel-sprite-generator .
069     * @return an int array with the randomly generated shape.
070     */
071    public int[][] generate()
072    {
073        return generateInto(new int[workingSolid.width][workingSolid.height], 0, 0);
074    }
075    /**
076     * Returns an int array (12x12) where 0 represents empty space, 1 represents border, and 2 represents "body."
077     * Will use the specified seed for this generation.
078     * The technique used here is derived from https://github.com/zfedoran/pixel-sprite-generator .
079     * @param seed a long to use as the seed for this random shape.
080     * @return an int array with the randomly generated shape.
081     */
082    public int[][] generate(long seed)
083    {
084        randomness.setState(seed);
085        return generateInto(new int[workingSolid.width][workingSolid.height], 0, 0);
086    }
087
088    /**
089     * Returns a modified version of changing where 0 represents empty space, 1 represents border, 2 represents shaded
090     * "body,", 3 represents normal body, and 4 represents lit body. Only a 12x12 area will be changed by this call,
091     * with its minimum x and y determined by xPos and yPos.
092     * The seed will change each time this runs, producing different shapes each time.
093     * The technique used here is derived from https://github.com/zfedoran/pixel-sprite-generator .
094     * @param changing an int array that will be altered if possible
095     * @param xPos the minimum x to modify; the maximum will be xPos + 12, exclusive
096     * @param yPos the minimum y to modify; the maximum will be yPos + 12, exclusive
097     * @return changing, after modifications
098     */
099    public int[][] generateIntoShaded(int[][] changing, int xPos, int yPos)
100    {
101        int w = workingBody.width, h = workingBody.height, ys = (h + 63) >> 6;
102        if(changing.length < w * 2 || changing[0].length < h
103                || xPos + w * 2 >= changing.length || yPos + h >= changing[0].length)
104            return changing;
105        randomRegion.refill(randomness, 0.75, w, h);
106        workingSolid.remake(potentialSolid);
107        workingBody.remake(potentialBody).or(potentialSolid).andNot(alwaysSolid).and(randomRegion);
108        workingSolid.andNot(workingBody).or(randomRegion.remake(workingBody).fringe());
109        workingShade.remake(workingBody).neighborDown().not().and(workingBody);
110        workingShine.remake(workingBody).neighborUp().not().and(workingBody).andNot(workingShade);
111        workingBody.andNot(workingShade).andNot(workingShine);
112        for (int x = 0, o = w*2-1; x < w; x++, o--) {
113            for (int y = 0; y < h; y++) {
114                changing[xPos + x][yPos + y] = ((workingShine.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 4 : 0)
115                        | ((workingBody.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 3 : 0)
116                        | ((workingShade.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 2 : 0)
117                        | ((workingSolid.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0);
118            }
119            System.arraycopy(changing[xPos + x], yPos, changing[xPos + o], yPos, h);
120        }
121        return changing;
122    }
123
124    /**
125     * Returns a modified version of changing where 0 represents empty space, 1 represents border, 2 represents shaded
126     * "body,", 3 represents normal body, and 4 represents lit body. Only a 12x12 area will be changed by this call,
127     * with its minimum x and y determined by xPos and yPos. Ensures that borders drawn around the shape cover all cells
128     * that are 8-way adjacent to any cells in the shape.
129     * The seed will change each time this runs, producing different shapes each time.
130     * The technique used here is derived from https://github.com/zfedoran/pixel-sprite-generator .
131     * @param changing an int array that will be altered if possible
132     * @param xPos the minimum x to modify; the maximum will be xPos + 12, exclusive
133     * @param yPos the minimum y to modify; the maximum will be yPos + 12, exclusive
134     * @return changing, after modifications
135     */
136    public int[][] generateIntoShaded8way(int[][] changing, int xPos, int yPos)
137    {
138        int w = workingBody.width, h = workingBody.height, ys = (h + 63) >> 6;
139        if(changing.length < w * 2 || changing[0].length < h
140                || xPos + w * 2 >= changing.length || yPos + h >= changing[0].length)
141            return changing;
142        randomRegion.refill(randomness, 0.75, w, h);
143        workingSolid.remake(potentialSolid);
144        workingBody.remake(potentialBody).or(potentialSolid).andNot(alwaysSolid).and(randomRegion);
145        workingSolid.andNot(workingBody).or(randomRegion.remake(workingBody).fringe8way());
146        workingShade.remake(workingBody).neighborDown().not().and(workingBody);
147        workingShine.remake(workingBody).neighborUp().not().and(workingBody).andNot(workingShade);
148        workingBody.andNot(workingShade).andNot(workingShine);
149        for (int x = 0, o = w*2-1; x < w; x++, o--) {
150            for (int y = 0; y < h; y++) {
151                changing[xPos + x][yPos + y] = ((workingShine.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 4 : 0)
152                        | ((workingBody.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 3 : 0)
153                        | ((workingShade.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 2 : 0)
154                        | ((workingSolid.data[x * ys + (y >> 6)] & (1L << (y & 63))) != 0 ? 1 : 0);
155            }
156            System.arraycopy(changing[xPos + x], yPos, changing[xPos + o], yPos, h);
157        }
158        return changing;
159    }
160
161}