├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── examples ├── cellular-radius2.png ├── cellular.png ├── dungeon-maze.png ├── dungeon-shapes.png ├── dungeon-simple.png ├── dungeon-tiny.png ├── dungeon.png ├── noise+cellular.png └── noise.png └── src └── com └── github └── czyzby └── noise4j ├── Noise4J.gwt.xml ├── array ├── Array2D.java ├── Int2dArray.java └── Object2dArray.java └── map ├── Grid.java └── generator ├── AbstractGenerator.java ├── Generator.java ├── cellular └── CellularAutomataGenerator.java ├── noise └── NoiseGenerator.java ├── room ├── AbstractRoomGenerator.java ├── RoomType.java └── dungeon │ └── DungeonGenerator.java └── util └── Generators.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .classpath 3 | .project 4 | .settings/ 5 | .gradle/ 6 | build/ 7 | gradle.properties 8 | TODO 9 | 10 | #Idea 11 | .idea/ 12 | *.iml 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Noise4J 2 | 3 | Simple map generators based on various procedural content generation tutorials. 4 | 5 | I really did not want to enforce any kind of map&tile system that you would have to modify, extend or even copy to your own implementation. The generators work on a simple 1D float array with a lightweight wrapper, that basically allows to use it as a 2D array. Result number ranges are never enforced - you can generate map with `[0,1]` values range (like in the examples), as well as `[0,1000]` - whatever suits you bests. `Grid` class also comes with some common math operations, so you can manually modify the values if you feel the need to. 6 | 7 | ## Dependency 8 | Gradle dependency: 9 | ```Groovy 10 | compile 'com.github.czyzby:noise4j:0.1.0' 11 | ``` 12 | 13 | ### LibGDX 14 | While `Noise4J` was created with `LibGDX` games in mind, it has no external dependencies. It's GWT- and Java 6-compatible, so including it in `LibGDX` projects is pretty straightforward. Start with adding the mentioned Gradle dependency to the core project: 15 | ```Groovy 16 | compile 'com.github.czyzby:noise4j:0.1.0' 17 | ``` 18 | 19 | You need to inherit `Noise4J` GWT module in your `GdxDefinition`, otherwise GWT compiler will not recognize the classes: 20 | ```XML 21 | 22 | ``` 23 | Also, don't forget to also include the sources dependency in GWT project: `compile 'com.github.czyzby:noise4j:0.1.0:sources'`. 24 | 25 | `Noise4J` does not use reflection, so its files usually do not need to be registered in any additional way. 26 | 27 | ## Noise generator 28 | 29 | LibGDX usage example: 30 | 31 | ```Java 32 | public class Example extends ApplicationAdapter { 33 | private SpriteBatch batch; 34 | private Texture texture; 35 | 36 | @Override 37 | public void create() { 38 | final Pixmap map = new Pixmap(512, 512, Format.RGBA8888); 39 | final Grid grid = new Grid(512); 40 | 41 | final NoiseGenerator noiseGenerator = new NoiseGenerator(); 42 | noiseStage(grid, noiseGenerator, 32, 0.6f); 43 | noiseStage(grid, noiseGenerator, 16, 0.2f); 44 | noiseStage(grid, noiseGenerator, 8, 0.1f); 45 | noiseStage(grid, noiseGenerator, 4, 0.1f); 46 | noiseStage(grid, noiseGenerator, 1, 0.05f); 47 | 48 | final Color color = new Color(); 49 | for (int x = 0; x < grid.getWidth(); x++) { 50 | for (int y = 0; y < grid.getHeight(); y++) { 51 | final float cell = grid.get(x, y); 52 | color.set(cell, cell, cell, 1f); 53 | map.drawPixel(x, y, Color.rgba8888(color)); 54 | } 55 | } 56 | 57 | texture = new Texture(map); 58 | batch = new SpriteBatch(); 59 | map.dispose(); 60 | } 61 | 62 | private static void noiseStage(final Grid grid, final NoiseGenerator noiseGenerator, final int radius, 63 | final float modifier) { 64 | noiseGenerator.setRadius(radius); 65 | noiseGenerator.setModifier(modifier); 66 | // Seed ensures randomness, can be saved if you feel the need to 67 | // generate the same map in the future. 68 | noiseGenerator.setSeed(Generators.rollSeed()); 69 | noiseGenerator.generate(grid); 70 | } 71 | 72 | @Override 73 | public void render() { 74 | Gdx.gl.glClearColor(0f, 0f, 0f, 1f); 75 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 76 | batch.begin(); 77 | batch.draw(texture, 0f, 0f); 78 | batch.end(); 79 | } 80 | 81 | @Override 82 | public void dispose() { 83 | texture.dispose(); 84 | batch.dispose(); 85 | } 86 | } 87 | ``` 88 | ![NoiseGenerator](https://github.com/czyzby/noise4j/blob/master/examples/noise.png "NoiseGenerator") 89 | 90 | ## Cellular automata generator 91 | 92 | LibGDX usage example: 93 | 94 | ```Java 95 | public class Example extends ApplicationAdapter { 96 | private SpriteBatch batch; 97 | private Texture texture; 98 | 99 | @Override 100 | public void create() { 101 | final Pixmap map = new Pixmap(512, 512, Format.RGBA8888); 102 | final Grid grid = new Grid(512); 103 | 104 | final CellularAutomataGenerator cellularGenerator = new CellularAutomataGenerator(); 105 | cellularGenerator.setAliveChance(0.5f); 106 | cellularGenerator.setIterationsAmount(4); 107 | cellularGenerator.generate(grid); 108 | 109 | final Color color = new Color(); 110 | for (int x = 0; x < grid.getWidth(); x++) { 111 | for (int y = 0; y < grid.getHeight(); y++) { 112 | final float cell = grid.get(x, y); 113 | color.set(cell, cell, cell, 1f); 114 | map.drawPixel(x, y, Color.rgba8888(color)); 115 | } 116 | } 117 | 118 | texture = new Texture(map); 119 | batch = new SpriteBatch(); 120 | map.dispose(); 121 | } 122 | 123 | @Override 124 | public void render() { 125 | Gdx.gl.glClearColor(0f, 0f, 0f, 1f); 126 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 127 | batch.begin(); 128 | batch.draw(texture, 0f, 0f); 129 | batch.end(); 130 | } 131 | 132 | @Override 133 | public void dispose() { 134 | texture.dispose(); 135 | batch.dispose(); 136 | } 137 | } 138 | ``` 139 | ![CellularAutomataGenerator](https://github.com/czyzby/noise4j/blob/master/examples/cellular.png "CellularAutomataGenerator") 140 | 141 | 142 | Bigger radius: 143 | ```Java 144 | final CellularAutomataGenerator cellularGenerator = new CellularAutomataGenerator(); 145 | cellularGenerator.setAliveChance(0.5f); 146 | cellularGenerator.setRadius(2); 147 | cellularGenerator.setBirthLimit(13); 148 | cellularGenerator.setDeathLimit(9); 149 | cellularGenerator.setIterationsAmount(6); 150 | cellularGenerator.generate(grid); 151 | ``` 152 | ![CellularAutomataGenerator](https://github.com/czyzby/noise4j/blob/master/examples/cellular-radius2.png "CellularAutomataGenerator") 153 | Use more iterations for a smoother map. Keep in mind that using a big radius can make the algorithm significantly slower. 154 | 155 | 156 | ## Dungeon generator 157 | LibGDX usage example: 158 | 159 | ```Java 160 | public class Example extends ApplicationAdapter { 161 | private SpriteBatch batch; 162 | private Texture texture; 163 | 164 | @Override 165 | public void create() { 166 | final Pixmap map = new Pixmap(512, 512, Format.RGBA8888); 167 | final Grid grid = new Grid(512); // This algorithm likes odd-sized maps, although it works either way. 168 | 169 | final DungeonGenerator dungeonGenerator = new DungeonGenerator(); 170 | dungeonGenerator.setRoomGenerationAttempts(500); 171 | dungeonGenerator.setMaxRoomSize(75); 172 | dungeonGenerator.setTolerance(10); // Max difference between width and height. 173 | dungeonGenerator.setMinRoomSize(9); 174 | dungeonGenerator.generate(grid); 175 | 176 | final Color color = new Color(); 177 | for (int x = 0; x < grid.getWidth(); x++) { 178 | for (int y = 0; y < grid.getHeight(); y++) { 179 | final float cell = 1f - grid.get(x, y); 180 | color.set(cell, cell, cell, 1f); 181 | map.drawPixel(x, y, Color.rgba8888(color)); 182 | } 183 | } 184 | 185 | texture = new Texture(map); 186 | batch = new SpriteBatch(); 187 | map.dispose(); 188 | } 189 | 190 | @Override 191 | public void render() { 192 | Gdx.gl.glClearColor(0f, 0f, 0f, 1f); 193 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 194 | batch.begin(); 195 | batch.draw(texture, 0f, 0f); 196 | batch.end(); 197 | } 198 | 199 | @Override 200 | public void dispose() { 201 | texture.dispose(); 202 | batch.dispose(); 203 | } 204 | } 205 | ``` 206 | ![DungeonGenerator](https://github.com/czyzby/noise4j/blob/master/examples/dungeon.png "DungeonGenerator") 207 | 208 | Different room types are also supported. While you can implement `RoomType` and fully control how rooms are "carved" in the walls, you can also use some default proposed room types with `dungeonGenerator.addRoomTypes(DefaultRoomType.values());`: 209 | 210 | ![DungeonGenerator](https://github.com/czyzby/noise4j/blob/master/examples/dungeon-shapes.png "DungeonGenerator") 211 | 212 | This dungeon can be also used to create "perfect" mazes (as in: with one way to solve them): 213 | 214 | ```Java 215 | final DungeonGenerator dungeonGenerator = new DungeonGenerator(); 216 | dungeonGenerator.setRoomGenerationAttempts(200); 217 | dungeonGenerator.setMaxRoomSize(25); 218 | dungeonGenerator.setTolerance(6); 219 | dungeonGenerator.setMinRoomSize(9); 220 | dungeonGenerator.setWindingChance(0.5f); // More chaotic! 221 | dungeonGenerator.setDeadEndRemovalIterations(5); // Introducing dead ends. 222 | dungeonGenerator.setRandomConnectorChance(0f); // One way to solve the maze. 223 | dungeonGenerator.generate(grid); 224 | ``` 225 | ![DungeonGenerator](https://github.com/czyzby/noise4j/blob/master/examples/dungeon-maze.png "DungeonGenerator") 226 | 227 | While these might seem chaotic (or even somewhat unplayable) at first sight, the generator is highly flexible - and no one is forcing you to create such huge maps(/rooms) either. This is a simple set-up for a rogue-like (256x256px, scaled x2): 228 | 229 | ![DungeonGenerator](https://github.com/czyzby/noise4j/blob/master/examples/dungeon-simple.png "DungeonGenerator") 230 | 231 | In case you're wondering about the settings - it's simply `new DungeonGenerator().generate(grid)`. This is what comes out when you use the default setup, which features relatively small rooms. The one below, on the other hand, has custom min/max room sizes and rooms amount: 232 | 233 | ![DungeonGenerator](https://github.com/czyzby/noise4j/blob/master/examples/dungeon-tiny.png "DungeonGenerator") 234 | 235 | ### Combined 236 | 237 | `NoiseGenerator` and `CellularAutomataGenerator` combined can generate maps similar to this: 238 | 239 | ![NoiseGenerator + CellularAutomataGenerator](https://github.com/czyzby/noise4j/blob/master/examples/noise%2Bcellular.png "NoiseGenerator + CellularAutomataGenerator") 240 | 241 | On this scale, this might look somewhat like some cavern system, but you don't have to generate maps that big (or with the same parameters, for that matter). Note that here each tile is represented by a single pixel - given than the map's size is 512x512 tiles, with a relatively small tile image size at 16x16px, this map would still need 67108864 (8192x8192) pixels to be drawn. With a smaller map and appropriate tiles, such map could be just as easily used to represent islands, for example. 242 | 243 | ## What can I do with the Grid... 244 | 245 | By default, **noise generator** adds `[0, modifier]` to each cell on each generation, smoothing values between the regions that you define. It ends up with "realistic" maps with smooth transitions between different regions. 246 | 247 | **Cellular generator** sees cells as alive (`cell >= marker`) or dead (`cell < marker`); on each iteration, it can kill (`cell -= marker`) a cell with too few living neighbors or bring back to live (`cell += marker`) a dead cell with enough neighbors. It creates cave-like patterns. 248 | 249 | **Dungeon generator** spawns multiple rooms and maze-like corridors between them, converting other cells to walls. Corridors and room floor values can be customized; wall value has to be higher than the other two (but `Grid` provides methods like `replace` or `negate`, so you can use pretty much any setup you need). As you can guess, it generates dungeons. 250 | 251 | You'll usually end up creating a few `Grids` and merging them with your custom algorithms, depending on your needs. 252 | 253 | ### Usage idea: islands 254 | - *Grid 1*: use cellular generator with a higher radius (2-3). (Find sensible birth and death limits! The higher the radius, the higher the limits.) 255 | - *Grid 2*: use noise generator with a few stages, with modifiers summing up to `1f`. This will be the height map. 256 | - *Grid 3*: use noise generator with a few stages, with modifiers summing up to `1f`. This will be the moisture map. 257 | - Combine grids: create an instance of your tiled map. If the cell is alive(/dead) in the first grid, tile of your map becomes water - if it is also close to the ground, it can become shallow water. If the cell is not water, check height and moisture values to determine tile type. For example, desert/canyon can be low and dry, forest can be medium-high and wet, swamp - low and wet, grass - medium all the way, etc. Trigger the generators' parameters for the most realistic maps with smooth terrain transitions. 258 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'eclipse' 3 | apply plugin: 'maven' 4 | apply plugin: 'signing' 5 | 6 | sourceSets.main.java.srcDirs = [ "src/" ] 7 | sourceCompatibility = 1.6 8 | 9 | ext { 10 | libVersion = '0.1.0' 11 | isSnapshot = '' 12 | } 13 | 14 | group = "com.github.czyzby" 15 | archivesBaseName = "noise4j" 16 | version = "$libVersion$isSnapshot" 17 | 18 | configurations { 19 | deployerJars 20 | } 21 | 22 | repositories { 23 | mavenLocal() 24 | mavenCentral() 25 | jcenter() 26 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 27 | } 28 | 29 | jar { 30 | from project.sourceSets.main.allSource 31 | from project.sourceSets.main.output 32 | baseName = 'noise4j' 33 | } 34 | 35 | task javadocJar(type: Jar) { 36 | classifier = 'javadoc' 37 | from javadoc 38 | } 39 | 40 | task sourcesJar(type: Jar) { 41 | classifier = 'sources' 42 | from sourceSets.main.allSource 43 | } 44 | 45 | artifacts { 46 | archives javadocJar, sourcesJar 47 | } 48 | 49 | signing { 50 | sign configurations.archives 51 | } 52 | 53 | dependencies { 54 | deployerJars "org.apache.maven.wagon:wagon-ssh:2.2" 55 | deployerJars "org.apache.maven.wagon:wagon-http:2.2" 56 | } 57 | 58 | uploadArchives { 59 | repositories { 60 | mavenDeployer { 61 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 62 | 63 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 64 | authentication(userName: ossrhUsername, password: ossrhPassword) 65 | } 66 | 67 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 68 | authentication(userName: ossrhUsername, password: ossrhPassword) 69 | } 70 | 71 | pom.project { 72 | name 'Noise4j' 73 | packaging 'jar' 74 | description 'Simple map generator.' 75 | url 'http://github.com/czyzby/noise4j' 76 | 77 | licenses { 78 | license { 79 | name 'The Apache License, Version 2.0' 80 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 81 | } 82 | } 83 | 84 | scm { 85 | connection 'scm:git:git@github.com:czyzby/noise4j.git' 86 | developerConnection 'scm:git:git@github.com:czyzby/noise4j.git' 87 | url 'http://github.com/czyzby/noise4j/' 88 | } 89 | 90 | developers { 91 | developer { 92 | id 'mj' 93 | name 'MJ' 94 | email 'john.hervicc@gmail.com' 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/cellular-radius2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/cellular-radius2.png -------------------------------------------------------------------------------- /examples/cellular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/cellular.png -------------------------------------------------------------------------------- /examples/dungeon-maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/dungeon-maze.png -------------------------------------------------------------------------------- /examples/dungeon-shapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/dungeon-shapes.png -------------------------------------------------------------------------------- /examples/dungeon-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/dungeon-simple.png -------------------------------------------------------------------------------- /examples/dungeon-tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/dungeon-tiny.png -------------------------------------------------------------------------------- /examples/dungeon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/dungeon.png -------------------------------------------------------------------------------- /examples/noise+cellular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/noise+cellular.png -------------------------------------------------------------------------------- /examples/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/czyzby/noise4j/440388c298783266f938be7789556e61c8f14460/examples/noise.png -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/Noise4J.gwt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/array/Array2D.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.array; 2 | 3 | /** Base class for containers that wrap around a single 1D array, treating it as a 2D array. 4 | * 5 | * @author MJ */ 6 | public abstract class Array2D { 7 | protected final int width; 8 | protected final int height; 9 | 10 | /** @param size amount of columns and rows. */ 11 | public Array2D(final int size) { 12 | this(size, size); 13 | } 14 | 15 | /** @param width amount of columns. 16 | * @param height amount of rows. */ 17 | public Array2D(final int width, final int height) { 18 | this.width = width; 19 | this.height = height; 20 | } 21 | 22 | /** @return amount of columns. */ 23 | public int getWidth() { 24 | return width; 25 | } 26 | 27 | /** @return amount of rows. */ 28 | public int getHeight() { 29 | return height; 30 | } 31 | 32 | /** @param x column index. 33 | * @param y row index. 34 | * @return true if the coordinates are valid and can be safely used with getter methods. */ 35 | public boolean isIndexValid(final int x, final int y) { 36 | return x >= 0 && x < width && y >= 0 && y < height; 37 | } 38 | 39 | /** @param x column index. 40 | * @param y row index. 41 | * @return actual array index of the cell. */ 42 | public int toIndex(final int x, final int y) { 43 | return x + y * width; 44 | } 45 | 46 | /** @param index actual array index of a cell. 47 | * @return column index. */ 48 | public int toX(final int index) { 49 | return index % width; 50 | } 51 | 52 | /** @param index actual array index of a cell. 53 | * @return row index. */ 54 | public int toY(final int index) { 55 | return index / width; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/array/Int2dArray.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.array; 2 | 3 | /** A simple {@link Array2D} extension storing a 1D primitive int array, treating it as a 2D array. 4 | * 5 | * @author MJ */ 6 | public class Int2dArray extends Array2D { 7 | private final int[] array; 8 | 9 | public Int2dArray(final int size) { 10 | this(size, size); 11 | } 12 | 13 | public Int2dArray(final int width, final int height) { 14 | super(width, height); 15 | array = new int[width * height]; 16 | } 17 | 18 | /** @param x column index. 19 | * @param y row index. 20 | * @return cell value with the selected index. */ 21 | public int get(final int x, final int y) { 22 | return array[toIndex(x, y)]; 23 | } 24 | 25 | /** @param x column index. 26 | * @param y row index. 27 | * @param value will become the value stored in the selected cell. */ 28 | public void set(final int x, final int y, final int value) { 29 | array[toIndex(x, y)] = value; 30 | } 31 | 32 | /** @param value will replace all cells' values. 33 | * @return this, for chaining. */ 34 | public Int2dArray set(final int value) { 35 | for (int index = 0, length = array.length; index < length; index++) { 36 | array[index] = value; 37 | } 38 | return this; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/array/Object2dArray.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.array; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | 6 | /** Stores a 1D object array of objects, treating it as a 2D array. 7 | * 8 | * @author MJ 9 | * @param class of stored objects. */ 10 | public abstract class Object2dArray extends Array2D implements Iterable { 11 | private final Type[] array; 12 | 13 | /** @param size amount of columns and rows. */ 14 | public Object2dArray(final int size) { 15 | super(size, size); 16 | this.array = getArray(size); 17 | } 18 | 19 | /** @param width amount of columns. 20 | * @param height amount of rows. */ 21 | public Object2dArray(final int width, final int height) { 22 | super(width, height); 23 | this.array = getArray(width * height); 24 | } 25 | 26 | /** @param array will be wrapped. Has to be valid - it cannot be too small (and it should not be too big either). 27 | * @param size amount of columns and rows. */ 28 | public Object2dArray(final Type[] array, final int size) { 29 | this(array, size, size); 30 | } 31 | 32 | /** @param array will be wrapped. Has to be valid - it cannot be too small (and it should not be too big either). 33 | * @param width amount of columns. 34 | * @param height amount of rows. */ 35 | public Object2dArray(final Type[] array, final int width, final int height) { 36 | super(width, height); 37 | if (array.length < width * height) { 38 | throw new IllegalArgumentException( 39 | "Passed array is too small. Expected length: " + width * height + ", received: " + array.length); 40 | } 41 | this.array = array; 42 | } 43 | 44 | /** @param width amount of columns. 45 | * @param height amount of rows. 46 | * @return a new {@link Object2dArray} wrapping around a simple object array. Note that if you use this method to 47 | * create an array, {@link #getArray()} will throw a {@link ClassCastException}. As long as you use safe 48 | * methods - like {@link #iterator()}, {@link #get(int, int)}, {@link #set(int, int, Object)} or 49 | * {@link #getObjectArray()} - this is perfectly OK to use this factory method. 50 | * @param class of stored objects. */ 51 | public static Object2dArray newNotTyped(final int width, final int height) { 52 | return new Object2dArray(width, height) { 53 | @Override 54 | @SuppressWarnings("unchecked") 55 | protected Type[] getArray(final int size) { 56 | return (Type[]) new Object[size]; 57 | } 58 | }; 59 | } 60 | 61 | /** @param size size of the array to create. 62 | * @return a new instance of typed array. */ 63 | protected abstract Type[] getArray(int size); 64 | 65 | /** @return direct reference to internal object array. 66 | * @see #toIndex(int, int) */ 67 | public Type[] getArray() { 68 | return array; 69 | } 70 | 71 | /** @return direct reference to internal object array with stripped type. This method never throws 72 | * {@link ClassCastException}, even if the array was created with a simple, not-typed object array. 73 | * @see #toIndex(int, int) */ 74 | public Object[] getObjectArray() { 75 | return array; 76 | } 77 | 78 | /** @param x valid column index. 79 | * @param y valid row index. 80 | * @return value stored in the selected cell. 81 | * @see #isIndexValid(int, int) */ 82 | public Type get(final int x, final int y) { 83 | return array[toIndex(x, y)]; 84 | } 85 | 86 | /** @param x column index. 87 | * @param y row index. 88 | * @return value stored in the selected cell or null if cell coordinates are invalid. */ 89 | public Type getOrNull(final int x, final int y) { 90 | return getOrElse(x, y, null); 91 | } 92 | 93 | /** @param x column index. 94 | * @param y row index. 95 | * @param alternative will be returned if index is invalid. 96 | * @return value stored in the selected cell or the passed alternative. Can be null if alternative object is null or 97 | * value stored in the cell is null. */ 98 | public Type getOrElse(final int x, final int y, final Type alternative) { 99 | if (isIndexValid(x, y)) { 100 | return get(x, y); 101 | } 102 | return alternative; 103 | } 104 | 105 | /** @param x valid column index. 106 | * @param y valid row index. 107 | * @param value will be stored in the selected cell. */ 108 | public void set(final int x, final int y, final Type value) { 109 | array[toIndex(x, y)] = value; 110 | } 111 | 112 | /** Swaps two cells' values. 113 | * 114 | * @param x column index of first value. 115 | * @param y column index of first value. 116 | * @param x2 column index of second value. 117 | * @param y2 row index of second value. */ 118 | public void swap(final int x, final int y, final int x2, final int y2) { 119 | final Type first = get(x, y); 120 | set(x, y, get(x2, y2)); 121 | set(x2, y2, first); 122 | } 123 | 124 | /** @param value will be stored in all array's cells. */ 125 | public void fill(final Type value) { 126 | for (int index = 0, length = width * height; index < length; index++) { 127 | array[index] = value; 128 | } 129 | } 130 | 131 | @Override 132 | public Iterator iterator() { 133 | return new ArrayIterator(); 134 | } 135 | 136 | /** Allows to iterate over wrapped array. {@link #remove()} method clears the value of the current cell, replacing 137 | * it with null, but it does not modify the size of the array. 138 | * 139 | * @author MJ */ 140 | protected class ArrayIterator implements Iterator { 141 | private final int size = width * height; // Does not have to match array.length. 142 | private int index; 143 | 144 | @Override 145 | public boolean hasNext() { 146 | return index < size; 147 | } 148 | 149 | @Override 150 | public Type next() { 151 | if (index >= size) { 152 | throw new NoSuchElementException(); 153 | } 154 | return array[index++]; 155 | } 156 | 157 | @Override 158 | public void remove() { 159 | if (index == 0) { 160 | throw new IllegalStateException("#next() has to be called before using #remove() method."); 161 | } 162 | array[index - 1] = null; 163 | } 164 | 165 | /** Allows to reuse the iterator. Begins iteration from the first cell. */ 166 | public void reset() { 167 | index = 0; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/Grid.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map; 2 | 3 | import java.util.Arrays; 4 | 5 | import com.github.czyzby.noise4j.array.Array2D; 6 | 7 | /** A float array wrapper. Allows to use a single 1D float array as a 2D array. 8 | * 9 | * @author MJ */ 10 | public class Grid extends Array2D { 11 | private final float[] grid; 12 | 13 | /** @param size amount of columns and rows. */ 14 | public Grid(final int size) { 15 | this(size, size); 16 | } 17 | 18 | /** @param width amount of columns. 19 | * @param height amount of rows. */ 20 | public Grid(final int width, final int height) { 21 | this(new float[width * height], width, height); 22 | } 23 | 24 | /** @param initialValue all cells will start with this value. 25 | * @param width amount of columns. 26 | * @param height amount of rows. */ 27 | public Grid(final float initialValue, final int width, final int height) { 28 | this(new float[width * height], width, height); 29 | set(initialValue); 30 | } 31 | 32 | /** @param grid array that will internally used by the grid. Its size has to be equal width multiplied by height. 33 | * @param width amount of columns. 34 | * @param height amount of rows. */ 35 | public Grid(final float[] grid, final int width, final int height) { 36 | super(width, height); 37 | this.grid = grid; 38 | if (grid.length != width * height) { 39 | throw new IllegalArgumentException("Array with length: " + grid.length 40 | + " is too small or too big to store a grid with " + width + " columns and " + height + " rows."); 41 | } 42 | } 43 | 44 | /** @return direct reference to the stored array. Use in extreme cases, try to use getters instead. */ 45 | public float[] getArray() { 46 | return grid; 47 | } 48 | 49 | /** @param x column index. 50 | * @param y row index. 51 | * @return value stored in the chosen cell. */ 52 | public float get(final int x, final int y) { 53 | return grid[toIndex(x, y)]; 54 | } 55 | 56 | /** @param x column index. 57 | * @param y row index. 58 | * @param value will be set as the value in the chosen cell. 59 | * @return value (parameter), for chaining. */ 60 | public float set(final int x, final int y, final float value) { 61 | return grid[toIndex(x, y)] = value; 62 | } 63 | 64 | /** @param x column index. 65 | * @param y row index. 66 | * @param value will be added to the current value stored in the chosen cell. 67 | * @return current cell value after adding the passed parameter. */ 68 | public float add(final int x, final int y, final float value) { 69 | return grid[toIndex(x, y)] += value; 70 | } 71 | 72 | /** @param x column index. 73 | * @param y row index. 74 | * @param value will be subtracted from the current value stored in the chosen cell. 75 | * @return current cell value after subtracting the passed parameter. */ 76 | public float subtract(final int x, final int y, final float value) { 77 | return grid[toIndex(x, y)] -= value; 78 | } 79 | 80 | /** @param x column index. 81 | * @param y row index. 82 | * @param value will be multiplied by the current value stored in the chosen cell. 83 | * @return current cell value after multiplying by the passed parameter. */ 84 | public float multiply(final int x, final int y, final float value) { 85 | return grid[toIndex(x, y)] *= value; 86 | } 87 | 88 | /** @param x column index. 89 | * @param y row index. 90 | * @param value will divide the current value stored in the chosen cell. 91 | * @return current cell value after dividing by the passed parameter. */ 92 | public float divide(final int x, final int y, final float value) { 93 | return grid[toIndex(x, y)] /= value; 94 | } 95 | 96 | /** @param x column index. 97 | * @param y row index. 98 | * @param mod will be used to perform modulo operation on the current cell value. 99 | * @return current cell value after modulo operation. */ 100 | public float modulo(final int x, final int y, final float mod) { 101 | return grid[toIndex(x, y)] %= mod; 102 | } 103 | 104 | /** Iterates over the whole grid. 105 | * 106 | * @param cellConsumer will consume each cell. If returns true, further iteration will be cancelled. */ 107 | public void forEach(final CellConsumer cellConsumer) { 108 | iterate(cellConsumer, 0, grid.length); 109 | } 110 | 111 | /** Iterates over the grid from a starting point. 112 | * 113 | * @param cellConsumer will consume each cell. If returns true, further iteration will be cancelled. 114 | * @param fromX first cell column index. Min is 0. 115 | * @param fromY first cell row index. Min is 0. */ 116 | public void forEach(final CellConsumer cellConsumer, final int fromX, final int fromY) { 117 | iterate(cellConsumer, toIndex(fromX, fromY), grid.length); 118 | } 119 | 120 | /** Iterates over chosen cells range in the grid. 121 | * 122 | * @param cellConsumer will consume each cell. If returns true, further iteration will be cancelled. 123 | * @param fromX first cell column index. Min is 0. 124 | * @param fromY first cell row index. Min is 0. 125 | * @param toX last cell column index (excluded). Max is {@link #getWidth()}. 126 | * @param toY last cell row index (excluded). Max is {@link #getHeight()}. */ 127 | public void forEach(final CellConsumer cellConsumer, final int fromX, final int fromY, final int toX, 128 | final int toY) { 129 | iterate(cellConsumer, toIndex(fromX, fromY), toIndex(toX, toY)); 130 | } 131 | 132 | /** @param cellConsumer will consume each cell. If returns true, further iteration will be cancelled. 133 | * @param fromIndex actual array index, begins the iteration. Min i 0. 134 | * @param toIndex actual array index (excluded); iterations ends with this value -1. Max is length of array. */ 135 | protected void iterate(final CellConsumer cellConsumer, final int fromIndex, final int toIndex) { 136 | for (int index = fromIndex; index < toIndex; index++) { 137 | if (cellConsumer.consume(this, toX(index), toY(index), grid[index])) { 138 | break; 139 | } 140 | } 141 | } 142 | 143 | /** @param grid its values will replace this grid's values. */ 144 | public void set(final Grid grid) { 145 | validateGrid(grid); 146 | System.arraycopy(grid.grid, 0, this.grid, 0, this.grid.length); 147 | } 148 | 149 | /** @param grid its values will be added to this grid's values. */ 150 | public void add(final Grid grid) { 151 | validateGrid(grid); 152 | for (int index = 0, length = this.grid.length; index < length; index++) { 153 | this.grid[index] += grid.grid[index]; 154 | } 155 | } 156 | 157 | /** @param grid its values will be subtracted from this grid's values. */ 158 | public void subtract(final Grid grid) { 159 | validateGrid(grid); 160 | for (int index = 0, length = this.grid.length; index < length; index++) { 161 | this.grid[index] -= grid.grid[index]; 162 | } 163 | } 164 | 165 | /** @param grid its values will multiply this grid's values. */ 166 | public void multiply(final Grid grid) { 167 | validateGrid(grid); 168 | for (int index = 0, length = this.grid.length; index < length; index++) { 169 | this.grid[index] *= grid.grid[index]; 170 | } 171 | } 172 | 173 | /** @param grid its values will be used to divide this grid's values. */ 174 | public void divide(final Grid grid) { 175 | validateGrid(grid); 176 | for (int index = 0, length = this.grid.length; index < length; index++) { 177 | this.grid[index] /= grid.grid[index]; 178 | } 179 | } 180 | 181 | /** @param grid will be validated. 182 | * @throws IllegalStateException if sizes do not match. */ 183 | protected void validateGrid(final Grid grid) { 184 | if (grid.width != width || grid.height != height) { 185 | throw new IllegalStateException("Grid's sizes do not match. Unable to perform operation."); 186 | } 187 | } 188 | 189 | /** Sets all values in the map. 190 | * 191 | * @param value will be set. 192 | * @return this, for chaining. */ 193 | public Grid set(final float value) { 194 | for (int index = 0, length = grid.length; index < length; index++) { 195 | grid[index] = value; 196 | } 197 | return this; 198 | } 199 | 200 | /** Sets all values in the map. 201 | * 202 | * @param value will be set. 203 | * @return this, for chaining. 204 | * @see #set(float) */ 205 | public Grid fill(final float value) { 206 | return set(value); 207 | } 208 | 209 | /** Sets all values in the selected column. 210 | * 211 | * @param x column index. 212 | * @param value will be set. 213 | * @return this, for chaining. */ 214 | public Grid fillColumn(final int x, final float value) { 215 | for (int y = 0; y < height; y++) { 216 | grid[toIndex(x, y)] = value; 217 | } 218 | return this; 219 | } 220 | 221 | /** Sets all values in the selected row. 222 | * 223 | * @param y row index. 224 | * @param value will be set. 225 | * @return this, for chaining. */ 226 | public Grid fillRow(final int y, final float value) { 227 | for (int x = 0; x < width; x++) { 228 | grid[toIndex(x, y)] = value; 229 | } 230 | return this; 231 | } 232 | 233 | /** Increases all values in the map. 234 | * 235 | * @param value will be added. 236 | * @return this, for chaining. */ 237 | public Grid add(final float value) { 238 | for (int index = 0, length = grid.length; index < length; index++) { 239 | grid[index] += value; 240 | } 241 | return this; 242 | } 243 | 244 | /** Decreases all values in the map. 245 | * 246 | * @param value will be subtracted. 247 | * @return this, for chaining. */ 248 | public Grid subtract(final float value) { 249 | for (int index = 0, length = grid.length; index < length; index++) { 250 | grid[index] -= value; 251 | } 252 | return this; 253 | } 254 | 255 | /** Multiplies all values in the map. 256 | * 257 | * @param value will be used. 258 | * @return this, for chaining. */ 259 | public Grid multiply(final float value) { 260 | for (int index = 0, length = grid.length; index < length; index++) { 261 | grid[index] *= value; 262 | } 263 | return this; 264 | } 265 | 266 | /** Divides all values in the map. 267 | * 268 | * @param value will be used. 269 | * @return this, for chaining. */ 270 | public Grid divide(final float value) { 271 | for (int index = 0, length = grid.length; index < length; index++) { 272 | grid[index] /= value; 273 | } 274 | return this; 275 | } 276 | 277 | /** Performs modulo operation on all values in the map. 278 | * 279 | * @param modulo will be used. 280 | * @return this, for chaining. */ 281 | public Grid modulo(final float modulo) { 282 | for (int index = 0, length = grid.length; index < length; index++) { 283 | grid[index] %= modulo; 284 | } 285 | return this; 286 | } 287 | 288 | /** Negates all values in the map. 289 | * 290 | * @return this, for chaining. */ 291 | public Grid negate() { 292 | for (int index = 0, length = grid.length; index < length; index++) { 293 | grid[index] = -grid[index]; 294 | } 295 | return this; 296 | } 297 | 298 | /** @param min all values lower than this value will be converted to this value. 299 | * @param max all values higher than this value will be converted to this value. 300 | * @return this, for chaining. */ 301 | public Grid clamp(final float min, final float max) { 302 | for (int index = 0, length = grid.length; index < length; index++) { 303 | final float value = grid[index]; 304 | grid[index] = value > max ? max : value < min ? min : value; 305 | } 306 | return this; 307 | } 308 | 309 | /** @param value cells storing this value will be replaced. 310 | * @param withValue this value will replace the affected cells. 311 | * @return this, for chaining. */ 312 | public Grid replace(final float value, final float withValue) { 313 | for (int index = 0, length = grid.length; index < length; index++) { 314 | if (Float.compare(grid[index], value) == 0) { 315 | grid[index] = withValue; 316 | } 317 | } 318 | return this; 319 | } 320 | 321 | /** Increases value in each cell by 1. 322 | * 323 | * @return this, for chaining. */ 324 | public Grid increment() { 325 | for (int index = 0, length = grid.length; index < length; index++) { 326 | grid[index]++; 327 | } 328 | return this; 329 | } 330 | 331 | /** Decreases value in each cell by 1. 332 | * 333 | * @return this, for chaining. */ 334 | public Grid decrement() { 335 | for (int index = 0, length = grid.length; index < length; index++) { 336 | grid[index]--; 337 | } 338 | return this; 339 | } 340 | 341 | @Override 342 | public boolean equals(final Object object) { 343 | return object == this || object instanceof Grid && ((Grid) object).width == width // If width is equal 344 | && Arrays.equals(((Grid) object).grid, grid); // and arrays are the same size, height is equal. 345 | } 346 | 347 | @Override 348 | public int hashCode() { 349 | return Arrays.hashCode(grid); 350 | } 351 | 352 | /** {@link #clone()} alternative with casted result. Cloning is not supported on GWT. 353 | * 354 | * @return a new instance of the grid with same size and values. */ 355 | public Grid copy() { 356 | final float[] copy = new float[grid.length]; 357 | System.arraycopy(grid, 0, copy, 0, copy.length); 358 | return new Grid(copy, width, height); 359 | } 360 | 361 | @Override 362 | public String toString() { 363 | final StringBuilder logger = new StringBuilder(); 364 | forEach(new CellConsumer() { 365 | @Override 366 | public boolean consume(final Grid grid, final int x, final int y, final float value) { 367 | logger.append('[').append(x).append(',').append(y).append('|').append(value).append(']'); 368 | if (x == grid.width - 1) { 369 | logger.append('\n'); 370 | } else { 371 | logger.append(' '); 372 | } 373 | return CONTINUE; 374 | } 375 | }); 376 | return logger.toString(); 377 | } 378 | 379 | /** Allows to perform an action on {@link Grid}'s cells. 380 | * 381 | * @author MJ */ 382 | public static interface CellConsumer { 383 | /** Should be returned by {@link #consume(Grid, int, int, float)} method for code clarity. */ 384 | boolean BREAK = true, CONTINUE = false; 385 | 386 | /** @param grid contains the cell. 387 | * @param x column index of the current cell. 388 | * @param y row index of the current cell. 389 | * @param value value stored in the current cell. Should match {@link Grid#get(int, int)} method result invoked 390 | * with passed coordinates (x and y). 391 | * @return if true and {@link CellConsumer} is used to iterate over the {@link Grid} using an iteration method 392 | * like {@link Grid#forEach(CellConsumer)}, further iteration will be cancelled. 393 | * @see #BREAK 394 | * @see #CONTINUE */ 395 | public boolean consume(Grid grid, int x, int y, float value); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/generator/AbstractGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map.generator; 2 | 3 | import com.github.czyzby.noise4j.map.Grid; 4 | 5 | /** Abstract base for map generators. Manages current {@link GenerationMode}. 6 | * 7 | * @author MJ */ 8 | public abstract class AbstractGenerator implements Generator { 9 | private GenerationMode mode = GenerationMode.ADD; 10 | 11 | @Override 12 | public GenerationMode getMode() { 13 | return mode; 14 | } 15 | 16 | @Override 17 | public void setMode(final GenerationMode mode) { 18 | if (mode == null) { 19 | throw new IllegalArgumentException("Generation mode cannot be null."); 20 | } 21 | this.mode = mode; 22 | } 23 | 24 | /** @param grid processed grid. 25 | * @param x column index. 26 | * @param y row index. 27 | * @param value will modify current cell value. */ 28 | protected void modifyCell(final Grid grid, final int x, final int y, final float value) { 29 | mode.modify(grid, x, y, value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/generator/Generator.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map.generator; 2 | 3 | import com.github.czyzby.noise4j.map.Grid; 4 | 5 | /** Common interface for all map generators. 6 | * 7 | * @author MJ */ 8 | public interface Generator { 9 | /** @param grid all (or most) of its cells will be affected, usually by adding or subtracting their current cell 10 | * value. */ 11 | void generate(Grid grid); 12 | 13 | /** @return current generation mode, deciding how values modify current grid's cells. */ 14 | GenerationMode getMode(); 15 | 16 | /** @param mode decides how values modify current grid's cells. */ 17 | void setMode(GenerationMode mode); 18 | 19 | /** Decides how values modify current grid's cells. 20 | * 21 | * @author MJ */ 22 | public static enum GenerationMode { 23 | /** Adds value to the current cell's value. Default. */ 24 | ADD { 25 | @Override 26 | public void modify(final Grid grid, final int x, final int y, final float value) { 27 | grid.add(x, y, value); 28 | } 29 | }, 30 | /** Subtracts value from the current cell's value. */ 31 | SUBTRACT { 32 | @Override 33 | public void modify(final Grid grid, final int x, final int y, final float value) { 34 | grid.subtract(x, y, value); 35 | } 36 | }, 37 | /** Multiplies current cell's value. */ 38 | MULTIPLY { 39 | @Override 40 | public void modify(final Grid grid, final int x, final int y, final float value) { 41 | grid.multiply(x, y, value); 42 | } 43 | }, 44 | /** Divides current cell's value. */ 45 | DIVIDE { 46 | @Override 47 | public void modify(final Grid grid, final int x, final int y, final float value) { 48 | grid.divide(x, y, value); 49 | } 50 | }, 51 | /** Replaces current cell's value. */ 52 | REPLACE { 53 | @Override 54 | public void modify(final Grid grid, final int x, final int y, final float value) { 55 | grid.set(x, y, value); 56 | } 57 | }; 58 | 59 | /** @param grid contains a cell. 60 | * @param x cell column index. 61 | * @param y cell row index. 62 | * @param value will modify current cell value. */ 63 | public abstract void modify(Grid grid, int x, int y, float value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/generator/cellular/CellularAutomataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map.generator.cellular; 2 | 3 | import java.util.Random; 4 | 5 | import com.github.czyzby.noise4j.map.Grid; 6 | import com.github.czyzby.noise4j.map.Grid.CellConsumer; 7 | import com.github.czyzby.noise4j.map.generator.AbstractGenerator; 8 | import com.github.czyzby.noise4j.map.generator.util.Generators; 9 | 10 | /** Contains a marker - a single float value; every cell below this value is considered dead, the others are alive. 11 | * During each iteration, if a living cell has too few living neighbors, it will die (marker will be subtracted from its 12 | * value). If a dead cell has enough living neighbors, it will become alive (marker will modify its current value 13 | * according to {@link #getMode()} - by default, marker will be added). This usually results in a cave-like map. The 14 | * more iterations, the smoother the map is. 15 | * 16 | *

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 roomTypes = new ArrayList(); 17 | private int minRoomSize = 3; 18 | private int maxRoomSize = 7; 19 | private int tolerance = 2; 20 | private int maxRoomsAmount; 21 | 22 | /** @return direct reference to internal list of accepted room types. */ 23 | public List getRoomTypes() { 24 | return roomTypes; 25 | } 26 | 27 | /** @param roomType determines how the room is carved. Room type is chosen at random during room generating. Note 28 | * that you can add a single room type multiple times to make it more likely for the type to be 29 | * chosen. */ 30 | public void addRoomType(final RoomType roomType) { 31 | roomTypes.add(roomType); 32 | } 33 | 34 | /** @param roomType determines how the room is carved. Room type is chosen at random during room generating. Note 35 | * that you can add a single room type multiple times to make it more likely for the type to be chosen. 36 | * @param times this is how many times the room will be added to the list. The bigger this value, the more likely 37 | * this room time gets rolled. */ 38 | public void addRoomType(final RoomType roomType, final int times) { 39 | for (int index = 0; index < times; index++) { 40 | addRoomType(roomType); 41 | } 42 | } 43 | 44 | /** @param roomTypes determine how the rooms are carved. Room type is chosen at random during room generating. Note 45 | * that you can add a single room type multiple times to make it more likely for the type to be 46 | * chosen. */ 47 | public void addRoomTypes(final RoomType... roomTypes) { 48 | for (final RoomType roomType : roomTypes) { 49 | addRoomType(roomType); 50 | } 51 | } 52 | 53 | /** @param grid contains the room. 54 | * @param room was just spawned. Should fill its values in the grid. 55 | * @param value value used to fill the room. */ 56 | protected void carveRoom(final Grid grid, final Room room, final float value) { 57 | if (roomTypes.isEmpty()) { // No types specified: carving whole room: 58 | room.fill(grid, value); 59 | } else { 60 | int index = Generators.randomIndex(roomTypes); 61 | final int originalIndex = index; 62 | RoomType type = roomTypes.get(index); 63 | while (!type.isValid(room)) { 64 | index = (index + 1) % roomTypes.size(); 65 | if (index == originalIndex) { // No valid types found for the room: 66 | room.fill(grid, value); 67 | return; 68 | } 69 | type = roomTypes.get(index); 70 | } 71 | type.carve(room, grid, value); 72 | } 73 | } 74 | 75 | /** @param grid will be used to generate bounds of the room. 76 | * @return a new random-sized room within grid's bounds. */ 77 | protected Room getRandomRoom(final Grid grid) { 78 | final int width = randomSize(); 79 | final int height = randomSize(width); 80 | if (width > grid.getWidth() || height > grid.getHeight()) { 81 | throw new IllegalStateException( 82 | "maxRoomSize is higher than grid's size, which resulted in spawning a room bigger than the whole map. Set maxRoomSize to a lower value."); 83 | } 84 | final Random random = Generators.getRandom(); 85 | final int x = normalizePosition(random.nextInt(grid.getWidth() - width)); 86 | final int y = normalizePosition(random.nextInt(grid.getHeight() - height)); 87 | return new Room(x, y, width, height); 88 | } 89 | 90 | /** @param position row or column index. 91 | * @return validated and normalized position. */ 92 | protected int normalizePosition(final int position) { 93 | return position; 94 | } 95 | 96 | /** @param size random room size value. 97 | * @return validated and normalized room size. */ 98 | protected int normalizeSize(final int size) { 99 | return size; 100 | } 101 | 102 | /** @return random odd room size within {@link #minRoomSize} and {@link #maxRoomSize} range. */ 103 | protected int randomSize() { 104 | return normalizeSize(minRoomSize == maxRoomSize ? minRoomSize : Generators.randomInt(minRoomSize, maxRoomSize)); 105 | } 106 | 107 | /** @param bound second size variable. 108 | * @return random odd room size within {@link #minRoomSize} and {@link #maxRoomSize} range, respecting 109 | * {@link #tolerance} */ 110 | protected int randomSize(final int bound) { 111 | final int size = Generators.randomInt(Math.max(minRoomSize, bound - tolerance), 112 | Math.min(maxRoomSize, bound + tolerance)); 113 | return normalizeSize(size); 114 | } 115 | 116 | /** @return minimum room's width and height. */ 117 | public int getMinRoomSize() { 118 | return minRoomSize; 119 | } 120 | 121 | /** @param minRoomSize minimum room's width and height. Some algorithms might require this value to be odd - even 122 | * values might be normalized or ignored. */ 123 | public void setMinRoomSize(final int minRoomSize) { 124 | if (minRoomSize <= 0 || minRoomSize > maxRoomSize) { 125 | throw new IllegalArgumentException("minRoomSize cannot be bigger than max or lower than 1."); 126 | } 127 | this.minRoomSize = minRoomSize; 128 | } 129 | 130 | /** @return maximum room's width and height. */ 131 | public int getMaxRoomSize() { 132 | return maxRoomSize; 133 | } 134 | 135 | /** @param maxRoomSize maximum room's width and height. Some algorithms might require this value to be odd - even 136 | * values might be normalized or ignored. */ 137 | public void setMaxRoomSize(final int maxRoomSize) { 138 | if (maxRoomSize <= 0 || minRoomSize > maxRoomSize) { 139 | throw new IllegalArgumentException("maxRoomSize cannot be lower than min or 1."); 140 | } 141 | this.maxRoomSize = maxRoomSize; 142 | } 143 | 144 | /** @param tolerance maximum difference between room's width and height. The bigger the tolerance, the more 145 | * rectangular the rooms can be. */ 146 | public void setTolerance(final int tolerance) { 147 | this.tolerance = tolerance; 148 | } 149 | 150 | /** @return maximum difference between room's width and height. */ 151 | public int getTolerance() { 152 | return tolerance; 153 | } 154 | 155 | /** @return maximum amount of generated rooms. If 0 or negative, there is no limit. */ 156 | public int getMaxRoomsAmount() { 157 | return maxRoomsAmount; 158 | } 159 | 160 | /** @param maxRoomsAmount maximum amount of generated rooms. While achieving a certain amount of rooms is not always 161 | * possible (due to collisions or simply the map being too small to store chosen amount), you can limit 162 | * the maximum amount of rooms that will be generated. If set to negative or zero (the default value), 163 | * there is no limit. */ 164 | public void setMaxRoomsAmount(final int maxRoomsAmount) { 165 | this.maxRoomsAmount = maxRoomsAmount; 166 | } 167 | 168 | /** Basic rectangle class. Contains position and size of a single room. Provides simple, common math operations. 169 | * 170 | * @author MJ */ 171 | public static class Room { 172 | private final int x, y; 173 | private final int width, height; 174 | 175 | public Room(final int x, final int y, final int width, final int height) { 176 | this.x = x; 177 | this.y = y; 178 | this.width = width; 179 | this.height = height; 180 | } 181 | 182 | /** @param room another room instance. 183 | * @return true if the two rooms overlap with each other. */ 184 | public boolean overlaps(final Room room) { 185 | return x < room.x + room.width && x + width > room.x && y < room.y + room.height && y + height > room.y; 186 | } 187 | 188 | /** @param grid its cells will be modified. 189 | * @param value will be used to fill all cells contained by the room. */ 190 | public void fill(final Grid grid, final float value) { 191 | for (int x = this.x, sizeX = this.x + width; x < sizeX; x++) { 192 | for (int y = this.y, sizeY = this.y + height; y < sizeY; y++) { 193 | grid.set(x, y, value); 194 | } 195 | } 196 | } 197 | 198 | /** @param grid its cells will be modified. 199 | * @param value will be used to fill all cells contained by the room. */ 200 | public void fill(final Int2dArray grid, final int value) { 201 | for (int x = this.x, sizeX = this.x + width; x < sizeX; x++) { 202 | for (int y = this.y, sizeY = this.y + height; y < sizeY; y++) { 203 | grid.set(x, y, value); 204 | } 205 | } 206 | } 207 | 208 | /** @param x column index. 209 | * @param y row index. 210 | * @return true if the passed position is on the bounds of the room. */ 211 | public boolean isBorder(final int x, final int y) { 212 | return this.x == x || this.y == y || this.x + width - 1 == x || this.y + height - 1 == y; 213 | } 214 | 215 | /** @return column index of the room's start. */ 216 | public int getX() { 217 | return x; 218 | } 219 | 220 | /** @return row index of the room's start. */ 221 | public int getY() { 222 | return y; 223 | } 224 | 225 | /** @return amount of columns taken by the room. */ 226 | public int getWidth() { 227 | return width; 228 | } 229 | 230 | /** @return amount of rows taken by the room. */ 231 | public int getHeight() { 232 | return height; 233 | } 234 | 235 | @Override // Auto-generated. 236 | public String toString() { 237 | return "Room [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/generator/room/RoomType.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map.generator.room; 2 | 3 | import com.github.czyzby.noise4j.map.Grid; 4 | import com.github.czyzby.noise4j.map.generator.room.AbstractRoomGenerator.Room; 5 | 6 | /** Represents a single room type. Determines how the room is carved in the map. 7 | *

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 rooms = new ArrayList(); 74 | private final List directions = new ArrayList(); 75 | private Int2dArray regions; 76 | private int currentRegion; 77 | private int lastRoomRegion; 78 | 79 | /** Not thread-safe. Uses static generator instance. Since this method provides only basic settings, creating or 80 | * obtaining an instance of the generator is generally preferred. 81 | * 82 | * @param grid will be used to generate the dungeon. 83 | * @param roomGenerationAttempts see {@link #getRoomGenerationAttempts()}. */ 84 | public static void generate(final Grid grid, final int roomGenerationAttempts) { 85 | final DungeonGenerator generator = getInstance(); 86 | generator.setRoomGenerationAttempts(roomGenerationAttempts); 87 | generator.generate(grid); 88 | } 89 | 90 | /** @return static instance of the generator. Not thread-safe. */ 91 | public static DungeonGenerator getInstance() { 92 | if (INSTANCE == null) { 93 | INSTANCE = new DungeonGenerator(); 94 | } 95 | return INSTANCE; 96 | } 97 | 98 | @Override 99 | public void generate(final Grid grid) { 100 | validateRoomSizes(); 101 | reset(); 102 | // Mirroring grid with a 2D int array - each non-wall mirrored cell will contain region index: 103 | regions = new Int2dArray(grid.getWidth(), grid.getHeight()); 104 | // Filling grid with wall tiles: 105 | grid.set(wallThreshold); 106 | // Generating rooms: 107 | spawnRooms(grid, roomGenerationAttempts == 0 ? getDefaultRoomsAmount(grid) : roomGenerationAttempts); 108 | // Generating corridors: 109 | spawnCorridors(grid); 110 | // Joining spawned rooms and corridor regions: 111 | joinRegions(grid); 112 | // Removing corridors leading to nowhere: 113 | removeDeadEnds(grid); 114 | reset(); // Removing all unnecessary references. 115 | } 116 | 117 | /** Resets control variables. */ 118 | protected void reset() { 119 | currentRegion = lastRoomRegion = -1; 120 | rooms.clear(); 121 | directions.clear(); 122 | regions = null; 123 | } 124 | 125 | /** Increases current region index. */ 126 | protected void nextRegion() { 127 | currentRegion++; 128 | } 129 | 130 | /** @throws IllegalStateException if room sizes are not odd. */ 131 | protected void validateRoomSizes() { 132 | if (getMinRoomSize() % 2 == 0 || getMaxRoomSize() % 2 == 0) { 133 | throw new IllegalStateException("Min and max room sizes have to be odd."); 134 | } 135 | } 136 | 137 | /** @param grid will contain generated dungeon. 138 | * @return maximum amount of placed rooms. Used if {@link #roomGenerationAttempts} is not set. */ 139 | private int getDefaultRoomsAmount(final Grid grid) { 140 | return grid.getWidth() / getMaxRoomSize() * (grid.getHeight() / getMaxRoomSize()); 141 | } 142 | 143 | /** @param grid is being generated. 144 | * @param attempts amount of attempts of placing rooms before the generator gives up. */ 145 | protected void spawnRooms(final Grid grid, final int attempts) { 146 | for (int index = 0, maxRoomsAmount = getMaxRoomsAmount(); index < attempts; index++) { 147 | final Room newRoom = getRandomRoom(grid); 148 | if (!overlapsAny(newRoom)) { 149 | rooms.add(newRoom); 150 | carveRoom(grid, newRoom, floorThreshold); 151 | nextRegion(); 152 | newRoom.fill(regions, currentRegion); // Assigning region values to all cells. 153 | } 154 | if (maxRoomsAmount > 0 && rooms.size() >= maxRoomsAmount) { 155 | break; 156 | } 157 | } 158 | lastRoomRegion = currentRegion; 159 | } 160 | 161 | /** @param room validated room. 162 | * @return true if passed room overlaps with any of the current rooms. */ 163 | protected boolean overlapsAny(final Room room) { 164 | for (final Room currentRoom : rooms) { 165 | if (currentRoom.overlaps(room)) { 166 | return true; 167 | } 168 | } 169 | return false; 170 | } 171 | 172 | /** @param grid will contain mazes spawned on the non-grid cells. */ 173 | protected void spawnCorridors(final Grid grid) { 174 | for (int x = 1, width = grid.getWidth(); x < width; x += 2) { 175 | for (int y = 1, height = grid.getHeight(); y < height; y += 2) { 176 | if (isCarveable(grid, x, y)) { 177 | carveMaze(grid, new Point(x, y)); 178 | } 179 | } 180 | } 181 | } 182 | 183 | /** @param grid contains the cell. 184 | * @param x column index. 185 | * @param y row index. 186 | * @return true if the selected cell is a wall. */ 187 | protected boolean isWall(final Grid grid, final int x, final int y) { 188 | return grid.isIndexValid(x, y) && grid.get(x, y) >= wallThreshold; 189 | } 190 | 191 | /** @param grid contains the point. 192 | * @param point will start carving maze at this point. Stops when it reaches a dead end. */ 193 | protected void carveMaze(final Grid grid, final Point point) { 194 | nextRegion(); 195 | Direction lastDirection = null; 196 | while (true) { 197 | // Carving current point: 198 | carveCorridor(grid, point); 199 | regions.set(point.x, point.y, currentRegion); 200 | 201 | directions.clear(); 202 | // Checking neighbors - getting possible carving directions: 203 | for (final Direction direction : Direction.values()) { 204 | if (isCarveable(point, grid, direction)) { 205 | directions.add(direction); 206 | } 207 | } 208 | if (directions.isEmpty()) { 209 | return; 210 | } 211 | Direction carvingDirection; 212 | // Getting actual carving direction: 213 | if (lastDirection != null && directions.contains(lastDirection) 214 | && Generators.randomPercent() > windingChance) { 215 | carvingDirection = lastDirection; 216 | } else { 217 | carvingDirection = Generators.randomElement(directions); 218 | } 219 | lastDirection = carvingDirection; 220 | // Carving "ignored" even-indexed corridor cell: 221 | carvingDirection.next(point); 222 | carveCorridor(grid, point); 223 | regions.set(point.x, point.y, currentRegion); 224 | // Switching to next odd-index cell, repeating until no viable neighbors left: 225 | carvingDirection.next(point); 226 | } 227 | } 228 | 229 | /** @param grid contains the point. 230 | * @param point a point representing a part of a corridor. Should set its value in the grid. */ 231 | protected void carveCorridor(final Grid grid, final Point point) { 232 | grid.set(point.x, point.y, corridorThreshold); 233 | } 234 | 235 | /** @param point part of the corridor. 236 | * @param grid contains the point. 237 | * @param direction possible carving direction. 238 | * @return true if can carve in the selected direction. */ 239 | protected boolean isCarveable(final Point point, final Grid grid, final Direction direction) { 240 | final int x = direction.nextX(point.x, 2); // Omitting 1 field, checking the next odd one. 241 | final int y = direction.nextY(point.y, 2); 242 | // Checking if index within grid bounds and not in a region yet: 243 | return isCarveable(grid, x, y); 244 | } 245 | 246 | /** @param grid contains the point. 247 | * @param x column index. 248 | * @param y row index. 249 | * @return true if the point can be a corridor. */ 250 | protected boolean isCarveable(final Grid grid, final int x, final int y) { 251 | return isWall(grid, x, y) 252 | // Diagonal: 253 | && isWall(grid, x + 1, y + 1) && isWall(grid, x - 1, y + 1) && isWall(grid, x + 1, y - 1) 254 | && isWall(grid, x - 1, y - 1); 255 | } 256 | 257 | /** @param grid contains unconnected room and corridor regions. */ 258 | protected void joinRegions(final Grid grid) { // DRAGON. 259 | nextRegion(); 260 | // Working on boxed primitives, because lawl, Java generics and collections. 261 | final Map> connectorsToRegions = findConnectors(grid); 262 | final List connectors = new ArrayList(connectorsToRegions.keySet()); 263 | final Integer[] merged = new Integer[currentRegion]; // Keeps track of merged regions. 264 | final Set unjoined = new HashSet(); // Keeps track of unconnected regions. 265 | for (int index = 0; index < currentRegion; index++) { 266 | // All regions point to themselves at first: 267 | merged[index] = index; 268 | // All regions start unjoined: 269 | unjoined.add(index); 270 | } 271 | Generators.shuffle(connectors); 272 | final Set tempSet = new HashSet(); 273 | // Looping until all regions point to one source: 274 | for (final Iterator connectorIterator = connectors.iterator(); connectorIterator.hasNext() 275 | && unjoined.size() > 1;) { 276 | final Point connector = connectorIterator.next(); 277 | // These are the regions that the connector originally pointed to - we need to convert them to the "new", 278 | // merged region indexes: 279 | final Set regions = connectorsToRegions.get(connector); 280 | tempSet.clear(); 281 | for (final Integer region : regions) { 282 | tempSet.add(merged[region]); 283 | } 284 | if (tempSet.size() <= 1) { // All connector's regions point to the same region group... 285 | if (Generators.randomPercent() < randomConnectorChance) { 286 | // This connector is not actually needed, but it got lucky - carving: 287 | carveConnector(grid, connector.x, connector.y); 288 | } 289 | continue; 290 | } 291 | carveConnector(grid, connector.x, connector.y); 292 | regions.clear(); 293 | regions.addAll(tempSet); 294 | final Iterator regionsIterator = regions.iterator(); 295 | // Using first region as our "source": 296 | final Integer source = regionsIterator.next(); // Safe, has at least 2 regions. 297 | // Using the rest of the region as destinations - they will point to the source region in merged array: 298 | final Integer[] destinations = getDestinations(regions, regionsIterator, merged); 299 | // Changing merged status - all regions that currently point to destinations will now point to source: 300 | for (int regionIndex = 0; regionIndex < currentRegion; regionIndex++) { 301 | for (final Integer destination : destinations) { 302 | if (merged[regionIndex].equals(destination)) { 303 | // This region was previously connected with one of our joined regions (or itself). Now pointing 304 | // to our source. 305 | merged[regionIndex] = source; 306 | } 307 | } 308 | } 309 | // Removing destinations - which were clearly joined - from unjoined regions: 310 | for (final Integer destination : destinations) { 311 | unjoined.remove(destination); 312 | } 313 | } 314 | } 315 | 316 | /** Should change selected point's value. Note that connector might connect both two corridors and two rooms - 317 | * spawning a door (for example) might not be always desired; check cell neighbors first. 318 | * 319 | * @param grid contains the point. 320 | * @param x column index. 321 | * @param y row index. */ 322 | protected void carveConnector(final Grid grid, final int x, final int y) { 323 | grid.set(x, y, corridorThreshold); 324 | regions.set(x, y, lastRoomRegion + 1); // Treating connector as corridor. 325 | } 326 | 327 | /** @param grid contains unconnected room and corridor regions. 328 | * @return map of points that are neighbors to at least 2 different regions mapped to set of IDs of their 329 | * neighbors. */ 330 | protected Map> findConnectors(final Grid grid) { 331 | final Map> connectorsToRegions = new HashMap>(); 332 | for (int x = 1, width = grid.getWidth() - 1; x < width; x++) { 333 | for (int y = 1, height = grid.getHeight() - 1; y < height; y++) { 334 | addConnector(grid, connectorsToRegions, x, y); 335 | } 336 | } 337 | return connectorsToRegions; 338 | } 339 | 340 | /** @param grid contains the regions. 341 | * @param connectorsToRegions map of possible connectors to the collection of regions that are their neighbors. 342 | * @param x column index of possible connector. 343 | * @param y row index of possible connector. */ 344 | protected void addConnector(final Grid grid, final Map> connectorsToRegions, final int x, 345 | final int y) { 346 | if (isWall(grid, x, y)) { 347 | final Set regions = new HashSet(4, 1f); 348 | for (final Direction direction : Direction.values()) { 349 | final int region = getRegion(direction.nextX(x), direction.nextY(y)); 350 | if (region >= 0 && !isWall(grid, direction.nextX(x), direction.nextY(y))) { 351 | regions.add(region); 352 | } 353 | } 354 | if (regions.size() > 1) { // At least 2 regions. 355 | connectorsToRegions.put(new Point(x, y), regions); 356 | } 357 | } 358 | } 359 | 360 | /** @param regions all regions of a connector. 361 | * @param regionsIterator regions' iterator. Should have one value skipped (source). 362 | * @param merged contains mapping of regions to the IDs of their supergroups. 363 | * @return regions marked as destinations. 364 | * @see #joinRegions(Grid) */ 365 | protected Integer[] getDestinations(final Set regions, final Iterator regionsIterator, 366 | final Integer[] merged) { 367 | final Integer[] destinations = new Integer[regions.size() - 1]; 368 | int index = 0; 369 | while (regionsIterator.hasNext()) { 370 | destinations[index++] = merged[regionsIterator.next()]; 371 | } 372 | return destinations; 373 | } 374 | 375 | /** @param x column index. 376 | * @param y row index. 377 | * @return region index of the cell. -1 if not in a region. */ 378 | protected int getRegion(final int x, final int y) { 379 | if (regions.isIndexValid(x, y)) { 380 | return regions.get(x, y); 381 | } 382 | return -1; 383 | } 384 | 385 | /** @param grid will have its cells with 3 or 4 wall neighbors removed. */ 386 | protected void removeDeadEnds(final Grid grid) { 387 | if (deadEndRemovalIterations <= 0) { 388 | return; // The user wants us to leave all dead ends. No need to waste time searching for them. 389 | } 390 | final List deadEnds = new LinkedList(); 391 | for (int x = 0, width = grid.getWidth(); x < width; x++) { 392 | for (int y = 0, height = grid.getHeight(); y < height; y++) { 393 | if (isDeadEnd(grid, x, y)) { 394 | deadEnds.add(new Point(x, y)); 395 | } 396 | } 397 | } 398 | // Removing dead ends until there are none left or we've done enough iterations: 399 | for (int index = 0; index < deadEndRemovalIterations && !deadEnds.isEmpty(); index++) { 400 | for (final Iterator iterator = deadEnds.iterator(); iterator.hasNext();) { 401 | final Point deadEnd = iterator.next(); 402 | // Closing dead end: 403 | grid.set(deadEnd.x, deadEnd.y, wallThreshold); 404 | // Checking dead end neighbors - one (and only one) of them can be a dead end too: 405 | if (!findDeadEndNeighbor(grid, deadEnd)) { 406 | // No dead end neighbors found - removing dead end from list: 407 | iterator.remove(); 408 | } // else { Point becomes its neighbor - will be removed on next iteration (or never). } 409 | } 410 | } 411 | } 412 | 413 | /** @param grid contains the cell. 414 | * @param deadEnd a currently closed dead end that can possibly have a single dead end neighbor. 415 | * @return true if dead end neighbor present. */ 416 | private boolean findDeadEndNeighbor(final Grid grid, final Point deadEnd) { 417 | for (final Direction direction : Direction.values()) { 418 | if (isDeadEnd(grid, direction.nextX(deadEnd.x), direction.nextY(deadEnd.y))) { 419 | // Setting dead end as its neighbor: 420 | deadEnd.x = direction.nextX(deadEnd.x); 421 | deadEnd.y = direction.nextY(deadEnd.y); 422 | return true; 423 | } 424 | } 425 | return false; 426 | } 427 | 428 | /** @param grid contains the cell. 429 | * @param x column index. 430 | * @param y row index. 431 | * @return true if the cell has at least 3 wall neighbors. */ 432 | protected boolean isDeadEnd(final Grid grid, final int x, final int y) { 433 | if (grid.isIndexValid(x, y) && !isWall(grid, x, y) && isCorridor(x, y)) { 434 | int wallNeighbors = 0; 435 | int nextX; 436 | int nextY; 437 | for (final Direction direction : Direction.values()) { 438 | nextX = direction.nextX(x); 439 | nextY = direction.nextY(y); 440 | if (grid.isIndexValid(nextX, nextY) && isWall(grid, nextX, nextY)) { 441 | wallNeighbors++; 442 | } 443 | } 444 | return wallNeighbors >= 3; 445 | } 446 | return false; 447 | } 448 | 449 | /** @param x column index. 450 | * @param y row index. 451 | * @return true if selected cell is a corridor. Works only if the cell is not a wall. This check works if all rooms 452 | * and corridors are already spawned. */ 453 | protected boolean isCorridor(final int x, final int y) { 454 | return getRegion(x, y) > lastRoomRegion; 455 | } 456 | 457 | @Override // Room position has to be odd. 458 | protected int normalizePosition(final int position) { 459 | if (position == 0) { 460 | return 1; 461 | } 462 | return position % 2 == 0 ? position - 1 : position; 463 | } 464 | 465 | @Override // Room size has to be odd. 466 | protected int normalizeSize(int size) { 467 | if (size % 2 != 1) { 468 | return Generators.getRandom().nextBoolean() ? --size : ++size; 469 | } 470 | return size; 471 | } 472 | 473 | /** @return amount of attempts of placing a new room during dungeon generation. */ 474 | public int getRoomGenerationAttempts() { 475 | return roomGenerationAttempts; 476 | } 477 | 478 | /** @param roomGenerationAttempts amount of attempts of placing a new room during dungeon generation. Changing this 479 | * value allows to modify density of the rooms. */ 480 | public void setRoomGenerationAttempts(final int roomGenerationAttempts) { 481 | this.roomGenerationAttempts = roomGenerationAttempts; 482 | } 483 | 484 | /** @return cells are considered walls if they are equal to or bigger than this value. */ 485 | public float getWallThreshold() { 486 | return wallThreshold; 487 | } 488 | 489 | /** @param wallThreshold cells are considered walls if they are equal to or bigger than this value. Should be higher 490 | * than {@link #getCorridorThreshold()} and {@link #getFloorThreshold()}. */ 491 | public void setWallThreshold(final float wallThreshold) { 492 | this.wallThreshold = wallThreshold; 493 | } 494 | 495 | /** @return cells are considered room floor if they are equal this value. */ 496 | public float getFloorThreshold() { 497 | return floorThreshold; 498 | } 499 | 500 | /** @param floorThreshold cells are considered room floor if they are equal this value. Should be lower than 501 | * {@link #getWallThreshold()}. */ 502 | public void setFloorThreshold(final float floorThreshold) { 503 | this.floorThreshold = floorThreshold; 504 | } 505 | 506 | /** @return cells are considered corridors if they are equal this value. */ 507 | public float getCorridorThreshold() { 508 | return corridorThreshold; 509 | } 510 | 511 | /** @param corridorThreshold cells are considered corridors if they are equal this value. Should be lower than 512 | * {@link #getWallThreshold()}. */ 513 | public void setCorridorThreshold(final float corridorThreshold) { 514 | this.corridorThreshold = corridorThreshold; 515 | } 516 | 517 | /** @return chance to wind the currently generated corridor in range of 0 to 1. */ 518 | public float getWindingChance() { 519 | return windingChance; 520 | } 521 | 522 | /** @param windingChance chance to wind the currently generated corridor in range of 0 to 1. Anything below (or 523 | * equal to) 0 results in winding the corridor only if cannot continue carving in the same direction (not 524 | * the best idea to make a playable game). Anything above or equal to 1 results in completely random 525 | * corridor carving, resulting in highly chaotic mazes. The higher the value, the more chaotic the 526 | * result. */ 527 | public void setWindingChance(final float windingChance) { 528 | this.windingChance = windingChance; 529 | } 530 | 531 | /** @return chance of a random carved cell between two regions (rooms and corridors) in range of 0 to 1. */ 532 | public float getRandomConnectorChance() { 533 | return randomConnectorChance; 534 | } 535 | 536 | /** @param randomConnectorChance chance of a random carved cell between two regions (rooms and corridors) in range 537 | * of 0 to 1. The higher this value, the more passages will appear between the rooms and different 538 | * corridors. Setting this value to 0 (or lower) will result in a "perfect" dungeon, which basically has 539 | * only one way of solving (as in getting from 1 point to another, treating rooms as one cell). This 540 | * might make the dungeon crawling linear, so this is usually not desired; however, using too high value 541 | * might result in broken dungeons with a lot of unnecessary passages and doors. */ 542 | public void setRandomConnectorChance(final float randomConnectorChance) { 543 | this.randomConnectorChance = randomConnectorChance; 544 | } 545 | 546 | /** @return amount of iterations performed to remove all dead ends in the corridors. */ 547 | public int getDeadEndRemovalIterations() { 548 | return deadEndRemovalIterations; 549 | } 550 | 551 | /** @param deadEndRemovalIterations amount of iterations performed to remove all dead ends in the corridors. Each 552 | * iteration removes only the last dead end's tile, not the whole branch. This should not cause 553 | * significant performance penalties, but if you do want to leave some dead ends in the final dungeon or 554 | * need the generation to be blazing fast, you can limit this value. Note that also generating more rooms 555 | * - {@link #setRoomGenerationAttempts(int)}, creating more passages - 556 | * {@link #setRandomConnectorChance(float)} or making the dungeon more random - 557 | * {@link #setWindingChance(float)} - might decrease the amount of dead ends (or overall corridors 558 | * amount). */ 559 | public void setDeadEndRemovalIterations(final int deadEndRemovalIterations) { 560 | this.deadEndRemovalIterations = deadEndRemovalIterations; 561 | } 562 | 563 | /** A simple container class, storing 2 values. 564 | * 565 | * @author MJ */ // Avoids extra dependencies and classes not available on Android/GWT. 566 | protected static class Point { 567 | // Note that its interface makes this class effectively final if used externally, but it can be mutated 568 | // internally for object reuse. 569 | private int x, y; 570 | 571 | public Point(final int x, final int y) { 572 | this.x = x; 573 | this.y = y; 574 | } 575 | 576 | /** @param point another point. 577 | * @return true if the passed point is a direct (non-diagonal) neighbor of this point. */ 578 | public boolean isNeighbor(final Point point) { 579 | final int xDifference = Math.abs(x - point.x); 580 | if (xDifference == 0) { 581 | return Math.abs(y - point.y) == 1; 582 | } else if (xDifference == 1) { 583 | return Math.abs(y - point.y) == 0; 584 | } 585 | return false; 586 | } 587 | 588 | /** @return column index. */ 589 | public int x() { 590 | return x; 591 | } 592 | 593 | /** @return row index. */ 594 | public int y() { 595 | return y; 596 | } 597 | 598 | @Override 599 | public boolean equals(final Object object) { 600 | return this == object || object instanceof Point && ((Point) object).x == x && ((Point) object).y == y; 601 | } 602 | 603 | @Override 604 | public int hashCode() { 605 | return x + y * 653; 606 | } 607 | 608 | @Override 609 | public String toString() { 610 | return "[" + x + "," + y + "]"; 611 | } 612 | } 613 | 614 | /** Contains all possible corridor carving directions. 615 | * 616 | * @author MJ */ 617 | protected static enum Direction { 618 | UP { 619 | @Override 620 | public void next(final Point point) { 621 | point.y++; 622 | } 623 | 624 | @Override 625 | public int nextY(final int y, final int amount) { 626 | return y + amount; 627 | } 628 | }, 629 | DOWN { 630 | @Override 631 | public void next(final Point point) { 632 | point.y--; 633 | } 634 | 635 | @Override 636 | public int nextY(final int y, final int amount) { 637 | return y - amount; 638 | } 639 | }, 640 | LEFT { 641 | @Override 642 | public void next(final Point point) { 643 | point.x--; 644 | } 645 | 646 | @Override 647 | public int nextX(final int x, final int amount) { 648 | return x - amount; 649 | } 650 | }, 651 | RIGHT { 652 | @Override 653 | public void next(final Point point) { 654 | point.x++; 655 | } 656 | 657 | @Override 658 | public int nextX(final int x, final int amount) { 659 | return x + amount; 660 | } 661 | }; 662 | 663 | /** @param point a point in the grid. Its coordinates will be modified to represent the next cell in the chosen 664 | * direction. */ 665 | public abstract void next(Point point); 666 | 667 | /** @param x current column index. 668 | * @return column index of the next cell. */ 669 | public int nextX(final int x) { 670 | return nextX(x, 1); 671 | } 672 | 673 | /** @param y current row index. 674 | * @return row index of the next cell. */ 675 | public int nextY(final int y) { 676 | return nextY(y, 1); 677 | } 678 | 679 | /** @param x current column index. 680 | * @param amount distance from the selected cell. 681 | * @return column index of the selected cell. */ 682 | public int nextX(final int x, final int amount) { 683 | return x; 684 | } 685 | 686 | /** @param y current row index. 687 | * @param amount distance from the selected cell. 688 | * @return row index of the selected cell. */ 689 | public int nextY(final int y, final int amount) { 690 | return y; 691 | } 692 | } 693 | } 694 | -------------------------------------------------------------------------------- /src/com/github/czyzby/noise4j/map/generator/util/Generators.java: -------------------------------------------------------------------------------- 1 | package com.github.czyzby.noise4j.map.generator.util; 2 | 3 | import java.math.BigInteger; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | /** Utilities for map generators. 8 | * 9 | *

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 | *

16 | * 17 | *
 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 | *
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 type of stored elements. */ 106 | public static Type randomElement(final List list) { 107 | return list.get(randomIndex(list)); 108 | } 109 | 110 | /** @param list a list of elements. Cannot be null or empty. 111 | * @return random index of an element stored in the list. */ 112 | public static int randomIndex(final List list) { 113 | return getRandom().nextInt(list.size()); 114 | } 115 | 116 | /** @return a random float in range of 0f (inclusive) to 1f (exclusive). */ 117 | public static float randomPercent() { 118 | return getRandom().nextFloat(); 119 | } 120 | 121 | /** GWT-compatible collection shuffling method. Use only for lists with quick random access; use 122 | * {@link java.util.Collections#shuffle(List)} if not targeting GWT. 123 | * 124 | * @param list its elements will be shuffled. 125 | * @return passed list, for chaining. 126 | * @param type of elements stored in the list. */ 127 | public static List shuffle(final List list) { 128 | final Random random = getRandom(); 129 | int swap; 130 | for (int i = list.size(); i > 1; i--) { 131 | swap = random.nextInt(i); 132 | list.set(swap, list.set(i - 1, list.get(swap))); 133 | } 134 | return list; 135 | } 136 | 137 | /** Allows to calculate common generators' functions. By implementing this interface, you can replace these common 138 | * functions with a more efficient solutions - for example, a look-up table. In LibGDX applications, MathUtils class 139 | * can be used for these operations. 140 | * 141 | *

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 | --------------------------------------------------------------------------------