17 | * Since this generator creates pretty much boolean-based maps (sets each cell as dead or alive), this generator is
18 | * usually used first to create the general layout of the map - like a caverns system or islands.
19 | *
20 | * @author MJ */
21 | public class CellularAutomataGenerator extends AbstractGenerator implements CellConsumer {
22 | private static CellularAutomataGenerator INSTANCE;
23 |
24 | private boolean initiate = true;
25 | private float marker = 1f;
26 | private float aliveChance = 0.5f;
27 | private int iterationsAmount = 3;
28 | private int birthLimit = 4;
29 | private int deathLimit = 3;
30 | private int radius = 1;
31 | private Grid temporaryGrid;
32 |
33 | /** Not thread-safe. Uses static generator instance. Since this method provides only basic settings, creating or
34 | * obtaining an instance of the generator is generally preferred.
35 | *
36 | * @param grid its cells will be affected.
37 | * @param iterationsAmount {@link #setIterationsAmount(int)} */
38 | public static void generate(final Grid grid, final int iterationsAmount) {
39 | generate(grid, iterationsAmount, 1f, true);
40 | }
41 |
42 | /** Not thread-safe. Uses static generator instance. Since this method provides only basic settings, creating or
43 | * obtaining an instance of the generator is generally preferred.
44 | *
45 | * @param grid its cells will be affected.
46 | * @param iterationsAmount {@link #setIterationsAmount(int)}
47 | * @param marker {@link #setMarker(float)}
48 | * @param initiate {@link #setInitiate(boolean)} */
49 | public static void generate(final Grid grid, final int iterationsAmount, final float marker,
50 | final boolean initiate) {
51 | final CellularAutomataGenerator generator = getInstance();
52 | generator.setIterationsAmount(iterationsAmount);
53 | generator.setMarker(marker);
54 | generator.setInitiate(initiate);
55 | generator.generate(grid);
56 | }
57 |
58 | /** @return static instance of the generator. Not thread-safe. */
59 | public static CellularAutomataGenerator getInstance() {
60 | if (INSTANCE == null) {
61 | INSTANCE = new CellularAutomataGenerator();
62 | }
63 | return INSTANCE;
64 | }
65 |
66 | /** @return amount of generation iterations. */
67 | public int getIterationsAmount() {
68 | return iterationsAmount;
69 | }
70 |
71 | /** @param iterationsAmount amount of generation iterations. The more iterations, the smoother the result. */
72 | public void setIterationsAmount(final int iterationsAmount) {
73 | this.iterationsAmount = iterationsAmount;
74 | }
75 |
76 | /** @return if {@link #isInitiating()} returns true, some alive cells will be spawned before the generation. This is
77 | * the change of the cell becoming alive before generating. */
78 | public float getAliveChance() {
79 | return aliveChance;
80 | }
81 |
82 | /** @param aliveChance if {@link #isInitiating()} returns true, some alive cells will be spawned before the
83 | * generation. This is the change of the cell becoming alive before generating. In range from 0 to 1. */
84 | public void setAliveChance(final float aliveChance) {
85 | this.aliveChance = aliveChance;
86 | }
87 |
88 | /** @return if true, some alive cells will be spawned before the generation. */
89 | public boolean isInitiating() {
90 | return initiate;
91 | }
92 |
93 | /** @param initiate if true, some alive cells will be spawned before the generation. The others will be killed, if
94 | * their value is higher than {@link #getMarker()}. */
95 | public void setInitiate(final boolean initiate) {
96 | this.initiate = initiate;
97 | }
98 |
99 | /** @return determines how far the cells can be from a cell to be considered neighbors. */
100 | public int getRadius() {
101 | return radius;
102 | }
103 |
104 | /** @param radius determines how far the cells can be from a cell to be considered neighbors. Defaults to 1 - only
105 | * direct cell neighbors (sides + corners) are counted. */
106 | public void setRadius(final int radius) {
107 | this.radius = radius;
108 | }
109 |
110 | /** @return if cell is equal to or greater than this value, it is considered alive. When the cell dies, this value
111 | * is subtracted from it. If the cell becomes alive, this value modifies the current cell value according to
112 | * current mode. */
113 | public float getMarker() {
114 | return marker;
115 | }
116 |
117 | /** @param marker if cell is equal to or greater than this value, it is considered alive. When the cell dies, this
118 | * value is subtracted from it. If the cell becomes alive, this value modifies the current cell value
119 | * according to current mode.
120 | * @see #setMode(com.github.czyzby.noise4j.map.generator.Generator.GenerationMode) */
121 | public void setMarker(final float marker) {
122 | this.marker = marker;
123 | }
124 |
125 | /** @return dead cell becomes alive if it has more alive neighbors than this value. */
126 | public int getBirthLimit() {
127 | return birthLimit;
128 | }
129 |
130 | /** @param birthLimit dead cell becomes alive if it has more alive neighbors than this value. The lesser this value
131 | * is, the more alive cells will be present. */
132 | public void setBirthLimit(final int birthLimit) {
133 | this.birthLimit = birthLimit;
134 | }
135 |
136 | /** @return living cell dies if it has less alive neighbors than this value. */
137 | public int getDeathLimit() {
138 | return deathLimit;
139 | }
140 |
141 | /** @param deathLimit living cell dies if it has less alive neighbors than this value. The higher this value is, the
142 | * less smooth the map becomes. */
143 | public void setDeathLimit(final int deathLimit) {
144 | this.deathLimit = deathLimit;
145 | }
146 |
147 | @Override
148 | public void generate(final Grid grid) {
149 | if (initiate) {
150 | spawnLivingCells(grid);
151 | }
152 | // Grid is copied to keep the correct living neighbors count. Otherwise it would change during iterations.
153 | temporaryGrid = grid.copy();
154 | for (int iterationIndex = 0; iterationIndex < iterationsAmount; iterationIndex++) {
155 | grid.forEach(this);
156 | grid.set(temporaryGrid);
157 | }
158 | }
159 |
160 | /** @param grid some of its cells will become alive, according to the current chance settings. The others will die,
161 | * if they were already alive.
162 | * @see #getAliveChance() */
163 | protected void spawnLivingCells(final Grid grid) {
164 | initiate(grid, aliveChance, marker);
165 | }
166 |
167 | /** @param grid unlike in case of noise algorithm, for example, cellular automata generator does not use an initial
168 | * random seed (yet) which can be easily used to recreate the same map over and over. Unless you use a
169 | * custom way of initiating the grid using a seed, the easiest solution to recreate exactly the same map
170 | * is saving both generator settings and initial cell values before first iteration. By manually calling
171 | * this method, you can copy the cell values before iterations begin; since the map is already initiated,
172 | * it makes sense to turn off automatic initiation with {@link #setInitiate(boolean)} method.
173 | * @param generator its settings will be used. */
174 | public static void initiate(final Grid grid, final CellularAutomataGenerator generator) {
175 | initiate(grid, generator.getAliveChance(), generator.getMarker());
176 | }
177 |
178 | /** @param grid unlike in case of noise algorithm, for example, cellular automata generator does not use an initial
179 | * random seed (yet) which can be easily used to recreate the same map over and over. Unless you use a
180 | * custom way of initiating the grid using a seed, the easiest solution to recreate exactly the same map
181 | * is saving both generator settings and initial cell values before first iteration. By manually calling
182 | * this method, you can copy the cell values before iterations begin; since the map is already initiated,
183 | * it makes sense to turn off automatic initiation with {@link #setInitiate(boolean)} method.
184 | * @param aliveChance see {@link #setAliveChance(float)}.
185 | * @param marker see {@link #setMarker(float)}. If value is already above the marker and rolled as alive, its value
186 | * will not be changed. If cell's value is above the marker and it is rolled as dead, marker will be
187 | * subtracted from its value. */
188 | public static void initiate(final Grid grid, final float aliveChance, final float marker) {
189 | final Random random = Generators.getRandom();
190 | final float[] array = grid.getArray();
191 | for (int index = 0, length = array.length; index < length; index++) {
192 | if (random.nextFloat() > aliveChance) {
193 | if (array[index] < marker) {
194 | grid.add(grid.toX(index), grid.toY(index), marker);
195 | }
196 | } else if (array[index] >= marker) { // Is alive - killing it.
197 | grid.subtract(grid.toX(index), grid.toY(index), marker);
198 | }
199 | }
200 | }
201 |
202 | /** @return temporary grid, copied to preserve to correct amounts of living neighbors during iterations. */
203 | protected Grid getTemporaryGrid() {
204 | return temporaryGrid;
205 | }
206 |
207 | @Override
208 | public boolean consume(final Grid grid, final int x, final int y, final float value) {
209 | final int livingNeighbors = countLivingNeighbors(grid, x, y);
210 | if (isAlive(value)) {
211 | if (shouldDie(livingNeighbors)) {
212 | setDead(x, y);
213 | }
214 | } else if (shouldBeBorn(livingNeighbors)) {
215 | setAlive(x, y);
216 | }
217 | return CONTINUE;
218 | }
219 |
220 | /** Makes the cell alive in temporary cached grid copy.
221 | *
222 | * @param x column index of temporary grid.
223 | * @param y row index of temporary grid. */
224 | protected void setAlive(final int x, final int y) {
225 | modifyCell(temporaryGrid, x, y, marker);
226 | }
227 |
228 | /** Kills the cell in temporary cached grid copy.
229 | *
230 | * @param x column index of temporary grid.
231 | * @param y row index of temporary grid. */
232 | protected void setDead(final int x, final int y) {
233 | temporaryGrid.subtract(x, y, marker);
234 | }
235 |
236 | /** @param aliveNeighbors amount of alive tile's neighbors.
237 | * @return true if tile has less alive neighbors than the current death limit. */
238 | protected boolean shouldDie(final int aliveNeighbors) {
239 | return aliveNeighbors < deathLimit;
240 | }
241 |
242 | /** @param aliveNeighbors amount of alive tile's neighbors.
243 | * @return true if tile has more alive neighbors than the current birth limit. */
244 | protected boolean shouldBeBorn(final int aliveNeighbors) {
245 | return aliveNeighbors > birthLimit;
246 | }
247 |
248 | /** @param value current cell's value.
249 | * @return true if the cell is currently considered alive. */
250 | protected boolean isAlive(final float value) {
251 | return value >= marker;
252 | }
253 |
254 | /** @param grid processed grid.
255 | * @param x column index of a cell.
256 | * @param y row index of a cell.
257 | * @return amount of neighbor cells that are considered alive. */
258 | protected int countLivingNeighbors(final Grid grid, final int x, final int y) {
259 | int count = 0;
260 | for (int xOffset = -radius; xOffset <= radius; xOffset++) {
261 | for (int yOffset = -radius; yOffset <= radius; yOffset++) {
262 | if (xOffset == 0 && yOffset == 0) {
263 | continue;// Same tile.
264 | }
265 | final int neighborX = x + xOffset;
266 | final int neighborY = y + yOffset;
267 | if (grid.isIndexValid(neighborX, neighborY) && isAlive(grid.get(neighborX, neighborY))) {
268 | count++;
269 | }
270 | }
271 | }
272 | return count;
273 | }
274 | }
275 |
--------------------------------------------------------------------------------
/src/com/github/czyzby/noise4j/map/generator/noise/NoiseGenerator.java:
--------------------------------------------------------------------------------
1 | package com.github.czyzby.noise4j.map.generator.noise;
2 |
3 | import com.github.czyzby.noise4j.map.Grid;
4 | import com.github.czyzby.noise4j.map.Grid.CellConsumer;
5 | import com.github.czyzby.noise4j.map.generator.AbstractGenerator;
6 | import com.github.czyzby.noise4j.map.generator.util.Generators;
7 |
8 | /** Divides grid into equal regions. Assigns semi-random value to each region using a noise function. Interpolates the
9 | * value according to neighbor regions' values. Unless regions are too small, this usually results in a smooth map with
10 | * logical region transitions. During map generation, you usually trigger the {@link #getRadius()} and
11 | * {@link #getModifier()}, invoking generation multiple times - each time with lower modifier. This allows to generate a
12 | * map with logical transitions, while keeping the map interesting thanks to further iterations with lower radius.
13 | *
14 | * @author MJ */
15 | public class NoiseGenerator extends AbstractGenerator implements CellConsumer {
16 | private static NoiseGenerator INSTANCE;
17 |
18 | private NoiseAlgorithmProvider algorithmProvider = new DefaultNoiseAlgorithmProvider();
19 | private int radius;
20 | private float modifier;
21 | private int seed;
22 |
23 | /** Not thread-safe. Uses static generator instance. Since this method provides only basic settings, creating or
24 | * obtaining an instance of the generator is generally preferred.
25 | *
26 | * @param grid will contain generated values.
27 | * @param radius {@link #setRadius(int)}
28 | * @param modifier {@link #setModifier(float)} */
29 | public static void generate(final Grid grid, final int radius, final float modifier) {
30 | generate(grid, radius, modifier, Generators.rollSeed());
31 | }
32 |
33 | /** Not thread-safe. Uses static generator instance. Since this method provides only basic settings, creating or
34 | * obtaining an instance of the generator is generally preferred.
35 | *
36 | * @param grid will contain generated values.
37 | * @param radius {@link #setRadius(int)}
38 | * @param modifier {@link #setModifier(float)}
39 | * @param seed {@link #setSeed(int)} */
40 | private static void generate(final Grid grid, final int radius, final float modifier, final int seed) {
41 | final NoiseGenerator generator = getInstance();
42 | generator.setRadius(radius);
43 | generator.setModifier(modifier);
44 | generator.setSeed(seed);
45 | generator.generate(grid);
46 | }
47 |
48 | /** @return static instance of the generator. Not thread-safe. */
49 | public static NoiseGenerator getInstance() {
50 | if (INSTANCE == null) {
51 | INSTANCE = new NoiseGenerator();
52 | }
53 | return INSTANCE;
54 | }
55 |
56 | /** @return size of a single generation region. The bigger, the more smooth the map seems. Setting it to one
57 | * effectively turns the map into semi-random noise with no smoothing whatsoever. */
58 | public int getRadius() {
59 | return radius;
60 | }
61 |
62 | /** @param radius size of a single generation region. The bigger, the more smooth the map seems. Setting it to one
63 | * effectively turns the map into semi-random noise with no smoothing whatsoever. */
64 | public void setRadius(final int radius) {
65 | this.radius = radius;
66 | }
67 |
68 | /** @return prime number. Random seed used by the noise function. */
69 | public int getSeed() {
70 | return seed;
71 | }
72 |
73 | /** @param seed prime number. Random seed used by the noise function. */
74 | public void setSeed(final int seed) {
75 | this.seed = seed;
76 | }
77 |
78 | /** @return relevance of the generation stage. Each grid cell will be increased with a semi-random value on scale
79 | * from 0 to this modifier. */
80 | public float getModifier() {
81 | return modifier;
82 | }
83 |
84 | /** @param modifier relevance of the generation stage. Each grid cell will be increased with a semi-random value on
85 | * scale from 0 to this modifier. */
86 | public void setModifier(final float modifier) {
87 | this.modifier = modifier;
88 | }
89 |
90 | /** @param algorithmProvider handles interpolation and noise math.
91 | * @see DefaultNoiseAlgorithmProvider */
92 | public void setAlgorithmProvider(final NoiseAlgorithmProvider algorithmProvider) {
93 | this.algorithmProvider = algorithmProvider;
94 | }
95 |
96 | @Override
97 | public void generate(final Grid grid) {
98 | if (seed == 0) {
99 | setSeed(Generators.rollSeed());
100 | }
101 | grid.forEach(this);
102 | }
103 |
104 | @Override
105 | public boolean consume(final Grid grid, final int x, final int y, final float value) {
106 | // Region index:
107 | final int regionX = x / radius;
108 | final int regionY = y / radius;
109 | // Distance from the start of the region:
110 | final float factorialX = x / (float) radius - regionX;
111 | final float factorialY = y / (float) radius - regionY;
112 | // Generated noises. Top and left noises are handled (already interpolated) by the other neighbors.
113 | final float noiseCenter = algorithmProvider.smoothNoise(this, regionX, regionY);
114 | final float noiseRight = algorithmProvider.smoothNoise(this, regionX + 1, regionY);
115 | final float noiseBottom = algorithmProvider.smoothNoise(this, regionX, regionY + 1);
116 | final float noiseBottomRight = algorithmProvider.smoothNoise(this, regionX + 1, regionY + 1);
117 | // Noise interpolations:
118 | final float topInterpolation = algorithmProvider.interpolate(noiseCenter, noiseRight, factorialX);
119 | final float bottomInterpolation = algorithmProvider.interpolate(noiseBottom, noiseBottomRight, factorialX);
120 | final float finalInterpolation = algorithmProvider.interpolate(topInterpolation, bottomInterpolation,
121 | factorialY);
122 | // Modifying current cell value according to the generation mode:
123 | modifyCell(grid, x, y, (finalInterpolation + 1f) / 2f * modifier);
124 | return CONTINUE;
125 | }
126 |
127 | /** Interface providing functions necessary for map generation.
128 | *
129 | * @author MJ */
130 | public interface NoiseAlgorithmProvider {
131 | /** Semi-random function. Consumes two parameters, always returning the same result for the same set of numbers.
132 | *
133 | * @param generator its settings should be honored. Random seed is usually used for the noise calculation.
134 | * @param x position on the X axis.
135 | * @param y position on the Y axis.
136 | * @return noise value. */
137 | float noise(NoiseGenerator generator, int x, int y);
138 |
139 | /** Semi-random function. Consumes two parameters, always returning the same result for the same set of numbers.
140 | * Noise is dependent on the neighbors, ensuring that drastic changes are rather rare.
141 | *
142 | * @param generator its settings should be honored. Random seed is usually used for the noise calculation.
143 | * @param x position on the X axis.
144 | * @param y position on the Y axis.
145 | * @return smoothed noise value. */
146 | float smoothNoise(NoiseGenerator generator, int x, int y);
147 |
148 | /** @param start range start.
149 | * @param end range end.
150 | * @param factorial [0,1), distance of the current point from the start.
151 | * @return interpolated current value, [start,end]. */
152 | float interpolate(float start, float end, float factorial);
153 | }
154 |
155 | /** Uses a custom noise function and cos interpolation.
156 | *
157 | * @author MJ */
158 | public static class DefaultNoiseAlgorithmProvider implements NoiseAlgorithmProvider {
159 | private static final float PI = (float) Math.PI;
160 |
161 | // HERE BE DRAGONS. AND MAGIC NUMBERS.
162 | @Override
163 | public float noise(final NoiseGenerator generator, final int x, final int y) {
164 | final int n = x + generator.getSeed() + y * generator.getSeed();
165 | return 1.0f - (n * (n * n * 15731 + 789221) + 1376312589 & 0x7fffffff) / 1073741824.0f;
166 | }
167 |
168 | @Override
169 | public float smoothNoise(final NoiseGenerator generator, final int x, final int y) {
170 | return // Corners:
171 | (noise(generator, x - 1, y - 1) + noise(generator, x + 1, y - 1) + noise(generator, x - 1, y + 1)
172 | + noise(generator, x + 1, y + 1)) / 16f
173 | // Sides:
174 | + (noise(generator, x - 1, y) + noise(generator, x + 1, y) + noise(generator, x, y - 1)
175 | + noise(generator, x, y + 1)) / 8f
176 | // Center:
177 | + noise(generator, x, y) / 4f;
178 | }
179 |
180 | @Override
181 | public float interpolate(final float start, final float end, final float factorial) {
182 | final float modificator = (1f - Generators.getCalculator().cos(factorial * PI)) * 0.5f;
183 | return start * (1f - modificator) + end * modificator;
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/com/github/czyzby/noise4j/map/generator/room/AbstractRoomGenerator.java:
--------------------------------------------------------------------------------
1 | package com.github.czyzby.noise4j.map.generator.room;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Random;
6 |
7 | import com.github.czyzby.noise4j.array.Int2dArray;
8 | import com.github.czyzby.noise4j.map.Grid;
9 | import com.github.czyzby.noise4j.map.generator.AbstractGenerator;
10 | import com.github.czyzby.noise4j.map.generator.util.Generators;
11 |
12 | /** Abstract base for room-generating algorithms.
13 | *
14 | * @author MJ */
15 | public abstract class AbstractRoomGenerator extends AbstractGenerator {
16 | private final List
8 | * Note that even through some room types create non-rectangle rooms, room collisions during map generating are still
9 | * checked with room's original rectangle bounds to simplify calculations. So, for example, two
10 | * {@link DefaultRoomType#DIAMOND} rooms cannot be places next to each other as long as their rectangles overlap, even
11 | * though their cells would be completely separated. However, corridors (or roads) are using actual tile states rather
12 | * than rooms' bounds, so - for example - diamond rooms can have corridors around them, even though they would normally
13 | * overlap with a plain square room.
14 | *
15 | * @author MJ
16 | * @see Interceptor */
17 | public interface RoomType {
18 | /** @param room should be filled.
19 | * @param grid should contain the room in its selected position.
20 | * @param value value with which the room should be filled. */
21 | void carve(Room room, Grid grid, float value);
22 |
23 | /** @param room is about to be filled.
24 | * @return true if this type can handle this room. Returns false if the room has invalid properties and cannot be
25 | * properly created with this type. */
26 | boolean isValid(Room room);
27 |
28 | /** Wraps around an existing type, allowing to slightly modify its behavior. A common usage can be changing of tile
29 | * value in carve method to a custom one, allowing different room types to use different tile sets, for example.
30 | * Extend this class and override isValid method if you want to add custom conditions.
31 | *
32 | * @author MJ */
33 | public static class Interceptor implements RoomType {
34 | protected final RoomType type;
35 | protected final float value;
36 |
37 | /** @param type wrapped type. Will delegate method calls to this type. Cannot be null.
38 | * @param value custom value passed to carving method. */
39 | public Interceptor(final RoomType type, final float value) {
40 | this.type = type;
41 | this.value = value;
42 | }
43 |
44 | @Override
45 | public void carve(final Room room, final Grid grid, final float value) {
46 | type.carve(room, grid, this.value);
47 | }
48 |
49 | @Override
50 | public boolean isValid(final Room room) {
51 | return type.isValid(room);
52 | }
53 | }
54 |
55 | /** Contains default implementations of {@link RoomType}.
56 | *
57 | * @author MJ */
58 | public static enum DefaultRoomType implements RoomType {
59 | /** Fills all of room's cells. Default behavior if room types are not used. Works with any room size. */
60 | SQUARE {
61 | @Override
62 | public void carve(final Room room, final Grid grid, final float value) {
63 | room.fill(grid, value);
64 | }
65 | },
66 | /** Uses a very simple algorithm to round room's corners. Works best for about 5 to 25 room size. */
67 | ROUNDED {
68 | @Override
69 | public void carve(final Room room, final Grid grid, final float value) {
70 | final int halfSize = (room.getWidth() + room.getHeight()) / 2;
71 | final int maxDistanceFromCenter = halfSize * 9 / 10;
72 | for (int x = 0, width = room.getWidth(); x < width; x++) {
73 | for (int y = 0, height = room.getHeight(); y < height; y++) {
74 | final int distanceFromCenter = Math.abs(x - width / 2) + Math.abs(y - height / 2);
75 | if (distanceFromCenter < maxDistanceFromCenter) {
76 | grid.set(x + room.getX(), y + room.getY(), value);
77 | }
78 | }
79 | }
80 | }
81 | },
82 | /** Instead of carving a simple rectangle, forms a rectangle with four "towers" in rooms' corners. Works with
83 | * pretty much any room size, but requires the room to be at least 7x7 squares big (and small/wide rooms do look
84 | * like bones instead of castles). */
85 | CASTLE {
86 | public static final int MIN_SIZE = 7, MIN_TOWER = 3;
87 |
88 | @Override
89 | public void carve(final Room room, final Grid grid, final float value) {
90 | final int size = Math.min(room.getWidth(), room.getHeight());
91 | final int towerSize = Math.max((size - 1) / 4, MIN_TOWER);
92 | final int offset = Math.max(towerSize / 4, towerSize == MIN_TOWER ? 1 : 2);
93 | // Main room:
94 | for (int x = offset, width = room.getWidth() - offset; x < width; x++) {
95 | for (int y = offset, height = room.getHeight() - offset; y < height; y++) {
96 | grid.set(x + room.getX(), y + room.getY(), value);
97 | }
98 | }
99 | // Towers:
100 | for (int x = 0, width = towerSize; x < width; x++) {
101 | for (int y = 0, height = towerSize; y < height; y++) {
102 | grid.set(x + room.getX(), y + room.getY(), value);
103 | }
104 | }
105 | for (int x = room.getWidth() - towerSize, width = room.getWidth(); x < width; x++) {
106 | for (int y = 0, height = towerSize; y < height; y++) {
107 | grid.set(x + room.getX(), y + room.getY(), value);
108 | }
109 | }
110 | for (int x = 0, width = towerSize; x < width; x++) {
111 | for (int y = room.getHeight() - towerSize, height = room.getHeight(); y < height; y++) {
112 | grid.set(x + room.getX(), y + room.getY(), value);
113 | }
114 | }
115 | for (int x = room.getWidth() - towerSize, width = room.getWidth(); x < width; x++) {
116 | for (int y = room.getHeight() - towerSize, height = room.getHeight(); y < height; y++) {
117 | grid.set(x + room.getX(), y + room.getY(), value);
118 | }
119 | }
120 | }
121 |
122 | @Override
123 | public boolean isValid(final Room room) {
124 | return room.getWidth() >= MIN_SIZE && room.getHeight() >= MIN_SIZE;
125 | }
126 | },
127 | /** Forms a pyramid-like structures. Can handle only square rooms with side size bigger than 2. Works best on
128 | * odd room sizes. Since equal width and height rooms can be relatively rare if you use a big tolerance, it is a
129 | * good idea to add this type multiple times to the possible types list to even its chances. */
130 | DIAMOND {
131 | @Override
132 | public void carve(final Room room, final Grid grid, final float value) {
133 | final int halfSize = room.getWidth() / 2;
134 | for (int x = 0, width = room.getWidth(); x < width; x++) {
135 | for (int y = 0, height = room.getHeight(); y < height; y++) {
136 | final int distanceFromCenter = Math.abs(x - halfSize) + Math.abs(y - halfSize);
137 | if (distanceFromCenter <= halfSize) {
138 | grid.set(x + room.getX(), y + room.getY(), value);
139 | }
140 | }
141 | }
142 | }
143 |
144 | @Override
145 | public boolean isValid(final Room room) {
146 | return room.getWidth() > 2 && room.getWidth() == room.getHeight();
147 | }
148 | },
149 | /** Forms a cross-shaped room, dividing the room into 9 (usually) equal parts and removing the corner ones.
150 | * Requires the room to have at least 3x3 size. Works best with square rooms. */
151 | CROSS {
152 | public static final int MIN_SIZE = 3;
153 |
154 | @Override
155 | public void carve(final Room room, final Grid grid, final float value) {
156 | final int offsetX = room.getWidth() / 3;
157 | final int offsetY = room.getHeight() / 3;
158 | for (int x = 0, width = room.getWidth(); x < width; x++) {
159 | for (int y = offsetY, height = room.getHeight() - offsetY; y < height; y++) {
160 | grid.set(x + room.getX(), y + room.getY(), value);
161 | }
162 | }
163 | for (int x = offsetX, width = room.getWidth() - offsetX; x < width; x++) {
164 | for (int y = 0, height = room.getHeight(); y < height; y++) {
165 | grid.set(x + room.getX(), y + room.getY(), value);
166 | }
167 | }
168 | }
169 |
170 | @Override
171 | public boolean isValid(final Room room) {
172 | return room.getWidth() >= MIN_SIZE && room.getHeight() >= MIN_SIZE;
173 | }
174 | };
175 |
176 | @Override
177 | public boolean isValid(final Room room) {
178 | return true;
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/com/github/czyzby/noise4j/map/generator/room/dungeon/DungeonGenerator.java:
--------------------------------------------------------------------------------
1 | package com.github.czyzby.noise4j.map.generator.room.dungeon;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.HashSet;
6 | import java.util.Iterator;
7 | import java.util.LinkedList;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.Set;
11 |
12 | import com.github.czyzby.noise4j.array.Int2dArray;
13 | import com.github.czyzby.noise4j.map.Grid;
14 | import com.github.czyzby.noise4j.map.generator.room.AbstractRoomGenerator;
15 | import com.github.czyzby.noise4j.map.generator.util.Generators;
16 |
17 | /** Generates a set of rooms with a maze-like system of corridors connecting them. This particular implementation
18 | * requires the map and rooms to have odd sizes - if the passed map is not odd, last row and column might be filled with
19 | * corridors.
20 | *
21 | * This algorithm fills the whole map. Even if the map was not empty before, {@link #generate(Grid)} method will
22 | * override the previous cell settings - it's better to modify already generated dungeon rather than pass non-empty grid
23 | * to this generator.
24 | *
25 | * @author MJ */
26 | /* Algorithm was based on implementation from journal.stuffwithstuff.com, translated to Java and improved (especially
27 | * the dead end removal and region joining parts):
28 | *
29 | * 0. Reset the grid. Set all cells as walls.
30 | *
31 | * 1. Generate rooms. Spawn random rooms across the map, honoring min/max size settings and width&height difference
32 | * tolerance.
33 | *
34 | * 1.1 Fill each room's cell with floor value.
35 | *
36 | * 1.2 Set each room's cell "region" value. Region value is common for all cells of currently generated part of the
37 | * dungeon, be a room or corridors set.
38 | *
39 | * 2. Generate corridors. Corridor regions cannot be connected to the rooms and each other (yet).
40 | *
41 | * 2.1 Fill each corridor cell with specified corridor value. Assign cell to corridor region.
42 | *
43 | * 3. Join regions. Every non-wall cell should be accessible from any other non-wall cell.
44 | *
45 | * 3.1 Find every possible connector (wall with non-wall neighbors from at least 2 separate regions). Shuffle them.
46 | *
47 | * 3.2 Until all regions are merged, iterate over connectors. If they separate two unconnected regions, replace the wall
48 | * with a corridor.
49 | *
50 | * 3.3 If a connector is neighbor of already connected regions, discard it - unless it passes a random test, in which
51 | * case replace it with a corridor to make the dungeon not perfect.
52 | *
53 | * 4. Remove dead ends.
54 | *
55 | * 4.1 Iterate over the whole map, locate all current dead ends.
56 | *
57 | * 4.2 Iterate over dead ends list until the desired dead end removal iterations amount is achieved or the dead end list
58 | * is cleared. Remove dead end from the list after it is converted into a wall and it has no dead end neighbors;
59 | * otherwise leave on the list and modify its coordinates to match the dead end neighbor. */
60 | public class DungeonGenerator extends AbstractRoomGenerator {
61 | private static DungeonGenerator INSTANCE;
62 |
63 | // Settings.
64 | private int roomGenerationAttempts;
65 | private float wallThreshold = 1f;
66 | private float floorThreshold = 0.5f;
67 | private float corridorThreshold;
68 | private float windingChance = 0.15f;
69 | private float randomConnectorChance = 0.01f;
70 | private int deadEndRemovalIterations = Integer.MAX_VALUE;
71 |
72 | // Control variables.
73 | private final List
10 | * When used in LibGDX applications, it is a good idea to replace {@link Random} and {@link Calculator} instances with
11 | * values and methods of {@code MathUtils} class, which provides both more efficient random implementation and sin/cos
12 | * look-up tables. See {@link #setRandom(Random)} and {@link #setCalculator(Calculator)}. This should be done before
13 | * using any generators - see example below:
14 | *
15 | *
142 | * Default implementation uses {@link Math} methods.
143 | *
144 | * @author MJ
145 | * @see Generators#setCalculator(Calculator) */
146 | public static interface Calculator {
147 | /** @param radians angle in radians.
148 | * @return sin value.
149 | * @see Math#sin(double) */
150 | float sin(float radians);
151 |
152 | /** @param radians angle in radians.
153 | * @return cos value.
154 | * @see Math#cos(double) */
155 | float cos(float radians);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
16 | *
17 | *
31 | *
32 | * @author MJ */
33 | public class Generators {
34 | /** Length of generated random seeds with {@link #rollSeed()}. Depending on the algorithm, this value might vary -
35 | * in which case {@link #rollSeed(int)} method should be used instead. */
36 | public static final int DEFAULT_SEED_BIT_LENGTH = 16;
37 |
38 | private static Random RANDOM;
39 | private static Calculator CALCULATOR;
40 |
41 | private Generators() {
42 | }
43 |
44 | /** @return {@link Random} instance shared by the generators. Not thread-safe, unless modified with
45 | * {@link #setRandom(Random)}. */
46 | public static Random getRandom() {
47 | if (RANDOM == null) {
48 | RANDOM = new Random();
49 | }
50 | return RANDOM;
51 | }
52 |
53 | /** @param random will be available through {@link #getRandom()} instance. This method allows to provide a
54 | * thread-safe, secure or specialized random instance. In LibGDX applications, you'd generally want to
55 | * use MathUtils.random instance. */
56 | public static void setRandom(final Random random) {
57 | RANDOM = random;
58 | }
59 |
60 | /** @return static instance of immutable, thread-safe {@link Calculator}, providing common math functions. */
61 | public static Calculator getCalculator() {
62 | if (CALCULATOR == null) {
63 | CALCULATOR = new Calculator() {
64 | @Override
65 | public float sin(final float radians) {
66 | return (float) Math.sin(radians);
67 | }
68 |
69 | @Override
70 | public float cos(final float radians) {
71 | return (float) Math.cos(radians);
72 | }
73 | };
74 | }
75 | return CALCULATOR;
76 | }
77 |
78 | /** @param calculator instance of immutable, thread-safe {@link Calculator}, providing common math functions. */
79 | public static void setCalculator(final Calculator calculator) {
80 | CALCULATOR = calculator;
81 | }
82 |
83 | /** @return a random probable prime with {@link #DEFAULT_SEED_BIT_LENGTH} bits.
84 | * @see BigInteger#probablePrime(int, Random) */
85 | public static int rollSeed() {
86 | return rollSeed(DEFAULT_SEED_BIT_LENGTH);
87 | }
88 |
89 | /** @param seedBitLength bits lenths of the generated seed.
90 | * @return a random probable prime.
91 | * @see BigInteger#probablePrime(int, Random) */
92 | public static int rollSeed(final int seedBitLength) {
93 | return BigInteger.probablePrime(seedBitLength, getRandom()).intValue();
94 | }
95 |
96 | /** @param min minimum possible random value.
97 | * @param max maximum possible random value.
98 | * @return random value in the specified range. */
99 | public static int randomInt(final int min, final int max) {
100 | return min + getRandom().nextInt(max - min + 1);
101 | }
102 |
103 | /** @param list a list of elements. Cannot be null or empty.
104 | * @return random list element.
105 | * @param
18 | * Generators.setRandom(MathUtils.random);
19 | * Generators.setCalculator(new Calculator() {
20 | * public float sin(float radians) {
21 | * return MathUtils.sin(radians);
22 | * }
23 | *
24 | * public float cos(float radians) {
25 | * return MathUtils.cos(radians);
26 | * }
27 | * });
28 | *
29 | *
30 | *