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}