├── .gitignore ├── .idea ├── dictionaries │ └── coderbot.xml ├── encodings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── Cargo.toml ├── Cave Generation ├── I73 Biome Generation ├── I73 Decoration List - Overworld and Sky ├── I73 Noise Generation ├── README.md ├── build.rs ├── i73.iml ├── profiles ├── b173 │ ├── biomes.json │ ├── climate.json │ ├── customized.json │ └── customized_.json └── b173_debug_biomes │ ├── biomes.json │ └── customized.json ├── src ├── bin │ └── main.rs ├── biome │ ├── climate.rs │ ├── mod.rs │ └── source.rs ├── config │ ├── biomes.rs │ ├── mod.rs │ └── settings │ │ ├── customized.rs │ │ ├── flat.rs │ │ └── mod.rs ├── decorator │ ├── clump │ │ ├── cactus.rs │ │ ├── mod.rs │ │ ├── plant.rs │ │ └── sugar_cane.rs │ ├── dungeon │ │ ├── loot.rs │ │ └── mod.rs │ ├── exposed.rs │ ├── lake.rs │ ├── large_tree │ │ ├── line.rs │ │ └── mod.rs │ ├── mod.rs │ ├── tree.rs │ └── vein.rs ├── distribution.rs ├── generator │ ├── mod.rs │ ├── nether_173.rs │ ├── overworld_173.rs │ └── sky_173.rs ├── lib.rs ├── matcher.rs ├── noise │ ├── mod.rs │ ├── octaves.rs │ ├── perlin.rs │ └── simplex.rs ├── noise_field │ ├── height.rs │ ├── mod.rs │ └── volume.rs ├── sample.rs ├── segmented.rs ├── structure │ ├── caves.rs │ └── mod.rs └── trig.rs └── test_data └── JavaSinTable.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | incr 4 | out 5 | .idea/workspace.xml 6 | -------------------------------------------------------------------------------- /.idea/dictionaries/coderbot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | biome 5 | overworld 6 | rainforest 7 | shrubland 8 | trilinear 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "i73" 3 | version = "0.1.0" 4 | authors = ["coderbot "] 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | cgmath = "0.16" 9 | hematite-nbt-serde = { git = "https://github.com/coderbot16/hematite_nbt.git" } 10 | serde = "0.9" 11 | serde_json = "0.9" 12 | serde_derive = "0.9" 13 | byteorder = "1.0" 14 | bit-vec = "0.4.4" 15 | nom = "3.2.1" 16 | rs25 = { path = "../rs25" } 17 | vocs = { path = "../vocs" } 18 | java-rand = "0.1.0" 19 | 20 | [profile.dev] 21 | opt-level = 3 -------------------------------------------------------------------------------- /Cave Generation: -------------------------------------------------------------------------------- 1 | [Over] CGen { carve: air, ocean: { flowing_water, still_water }, carvable: { stone, dirt, grass } } 2 | Rare { HalfNormal3 { max: 39 }, rarity: 15 } 3 | DepthPacked { min: 0, linear_start: 8, max: 126 } 4 | [Hell] CGen { carve: air, ocean: { flowing_lava, still_lava }, carvable: { netherrack, dirt, grass } } 5 | Rare { HalfNormal3 { max: 9 }, rarity: 5 } 6 | Linear { min: 0, max: 127 } 7 | BlobSizeFactor for Tunnel::normal is 2.0, from 1.0 8 | VerticalMultiplier is always 0.5, for circular and normal 9 | 10 | Todo: Grass pulldown in Overworld 11 | Todo: Lower lava in Overworld 12 | 13 | [Over] Ocean: { flowing_water, still_water } 14 | [Hell] Ocean: { flowing_lava, still_lava } 15 | [Over] HitGrassSurface flag 16 | [Over] Early distance check (not significant) 17 | [Over] HitGrass if B=grass 18 | [Over] Carvable: { stone, dirt, grass } 19 | [Hell] Carvable: { netherrack, dirt, grass } 20 | [Over] Carve: if y<10 { 21 | B->flowing_lava 22 | } else { 23 | B->air 24 | If HitGrass && Below==dirt { Below->grass } 25 | } 26 | [Hell] Carve: B->air 27 | [Over] Rare(15); Count: Ri(Ri(Ri(40) + 1) + 1) 28 | [Hell] Rare(5); Count: Ri(Ri(Ri(10) + 1) + 1) 29 | [Over] HeightDist: Linear(8, 128) 30 | [Hell] HeightDist: Linear(0, 128) 31 | [Over] BlobSize *= 1.0; VerticalMultiplier=1.0 32 | [Hell] BlobSize *= 2.0; VerticalMultiplier=0.5 33 | -------------------------------------------------------------------------------- /I73 Biome Generation: -------------------------------------------------------------------------------- 1 | # Biome Generation 2 | Minecraft biome generation is simple, but effective. It is designed so that in general, biomes placed next to each other will make at least a little sense. This is not true in some areas however (Tundras next to Savannas), but at least prevents things like rainforests from appearing next to taigas. 3 | 4 | ## Noise Generation 5 | Biome generation uses a modified Simplex noise generator. Instead of a `40.0` scale factor, Minecraft uses a `70.0` scale factor. It also uses a modified `Grad()` function: 6 | ```rust 7 | Vector2[] GRAD_VECTORS = [ 8 | ( 1, 1), 9 | (-1, 1), 10 | ( 1, -1), 11 | (-1, -1), 12 | ( 1, 0), 13 | (-1, 0), 14 | ( 1, 0), 15 | (-1, 0), 16 | ( 0, 1), 17 | ( 0, -1), 18 | ( 0, 1), 19 | ( 0, -1) 20 | ]; 21 | 22 | Function Grad(Hash: integer, X: real, Y: real) -> real { 23 | return Dot(GRAD_VECTORS[Hash % 12], (X, Y)); 24 | } 25 | 26 | Function Dot(A: Vector, B: Vector) -> real { 27 | return A.X * B.X + A.Y * B.Y; 28 | } 29 | ``` 30 | ## Biome Properties Calculation 31 | This function takes 3 inputs corresponding to each noise generator, producing a temperature and rain value. 32 | ```rust 33 | Function CalculateBiomeProperties(MixinIn: real, TIn: real, RIn: real) -> (Temperature: real, Rain: real) { 34 | Mixin = MixinIn * 1.1 + 0.5; 35 | 36 | TMixed = (TIn * 0.15 + 0.70) * 0.990 + Mixin * 0.010; 37 | TOut = Clamp(1.0 - (1.0 - TMixed)^2, 0.0, 1.0); 38 | 39 | RMixed = (RIn * 0.15 + 0.50) * 0.998 + Mixin * 0.002; 40 | ROut = Clamp(RMixed, 0.0, 1.0); 41 | 42 | return (TOut, ROut); 43 | } 44 | ``` 45 | 46 | ## Biome Lookup 47 | Internally, Minecraft uses a biome lookup table of resolution 64x64. This must be accounted for in biome lookup or biomes will be off very sligtly. 48 | ```rust 49 | Function LookupBiomeRounded(Temperature, Rain) -> Biome { 50 | TLookup = RoundIntoInteger(Temperature * 64) / 64; 51 | RLookup = RoundIntoInteger(Rain * 64) / 64; 52 | 53 | // You could use a lookup table instead for a big speedup. 54 | return BiomeLookup(TLookup, RLookup); 55 | } 56 | ``` 57 | 58 | ## Biomes 59 | There are 11 biomes, but only 10 are used. The unused biome, IceDesert, does not ever generate due to a bug. This table lists the bounding boxes in the lookup of each biome, and some biomes have multiple boxes due to having abnormal shapes. Ranges are expressed in Rust notation where `a..b` means `a <= x < b` and `a...b` means `a <= x <= b`. The `BiomeLookup` function matches the temperature and rainfall against this table, so it is not shown. 60 | 61 | When looking up in the following table, the rain is multiplied by the temperature (`Rain *= Temperature`). This makes areas with higher temperatures have higher rainfall. 62 | 63 | | Temperature | Rain | Biome | 64 | |--------------------|--------------------|------------------| 65 | | `0.00 .. 0.10` | `0.00 ... 1.00` | Tundra | 66 | | `0.10 .. 0.50` | `0.00 .. 0.20` | Tundra | 67 | | `0.10 .. 0.50` | `0.20 .. 0.50` | Taiga | 68 | | `0.10 .. 0.70` | `0.50 ... 1.00` | Swampland | 69 | | `0.50 .. 0.95` | `0.00 .. 0.20` | Savanna | 70 | | `0.50 .. 0.97` | `0.20 .. 0.35` | Shrubland | 71 | | `0.50 .. 0.97` | `0.35 .. 0.50` | Forest | 72 | | `0.70 .. 0.97` | `0.50 ... 1.00` | Forest | 73 | | `0.95 ... 1.00` | `0.00 .. 0.20` | Desert | 74 | | `0.97 ... 1.00` | `0.20 .. 0.45` | Plains | 75 | | `0.97 ... 1.00` | `0.45 .. 0.90` | Seasonal Forest | 76 | | `0.97 ... 1.00` | `0.90 ... 1.00` | Rainforest | 77 | 78 | -------------------------------------------------------------------------------- /I73 Decoration List - Overworld and Sky: -------------------------------------------------------------------------------- 1 | Rare(X) if rand.nextInt(X) == 0 {} 2 | Common(X) for(int i = 0; i < X; i++) {} 3 | Ri(X) rand.nextInt(X) 4 | Offs(X, Y, Z) (X + 8, Y, Z + 8) 5 | Uniform -> (Ri(16), Ri(128), Ri(16)) 6 | YRange(Low, High) -> (Ri(16), Ri(High - Low) + Low, Ri(16)) 7 | YCenter(T) -> (Ri(16), Ri(T) + Ri(T), Ri(16)) 8 | ::Cond -> Must be true before calling the gen 9 | 10 | Rare(4) Lake 11 | Position: Offs(Uniform) 12 | Block: water 13 | Rare(8) Lake 14 | Position: Offs(Ri(16), Ri(Ri(120) + 8), Ri(16)) 15 | Block: lava 16 | ::Cond { if Y < 64 || Ri(10) } 17 | Common(8) Dungeons 18 | Position: Offs(Uniform) 19 | Common(10) VeinClay 20 | Position: Uniform 21 | Size: 32 22 | Block: clay 23 | Common(20) Vein 24 | Position: Uniform 25 | Size: 32 26 | Block: dirt 27 | Common(10) Vein 28 | Position: Uniform 29 | Size: 32 30 | Block: gravel 31 | Common(20) Vein 32 | Position: Uniform 33 | Size: 16 34 | Block: coal_ore 35 | Common(20) Vein 36 | Position: YRange(0, 64) 37 | Size: 8 38 | Block: iron_ore 39 | Common(2) Vein 40 | Position: YRange(0, 32) 41 | Size: 8 42 | Block: gold_ore 43 | Common(8) Vein 44 | Position: YRange(0, 16) 45 | Size: 7 46 | Block: redstone_ore 47 | Common(1) Vein 48 | Position: YRange(0, 16) 49 | Size: 7 50 | Block: diamond_ore 51 | Common(1) Vein 52 | Position: YCenter(16) 53 | Size: 6 54 | Block: lapis_ore 55 | BiomeItems (TODO) 56 | Trees 57 | YellowFlowers (Sky: Shown below) 58 | TallGrass (Not in sky) 59 | DeadBush (Not in sky) 60 | [SKY] Common(2) Flowers 61 | [SKY] Position: Offs(Uniform) 62 | [SKY] Block: yellow_flower 63 | Rare(2) Flowers 64 | Position: Offs(Uniform) 65 | Block: red_flower 66 | Rare(4) Flowers 67 | Position: Offs(Uniform) 68 | Block: brown_mushroom 69 | Rare(8) Flowers 70 | Position: Offs(Uniform) 71 | Block: red_mushroom 72 | Common(10) Reed 73 | Position: Offs(Uniform) 74 | Rare(32) Pumpkin 75 | Position: Offs(Uniform) 76 | BiomeItems (TODO) 77 | Cactus 78 | Common(50) Liquid 79 | Position: Offs(Ri(16), Ri(Ri(120) + 8), Ri(16)) 80 | Block: water 81 | Common(20) Liquid 82 | Position: Offs(Ri(16), Ri(Ri(Ri(112) + 8) + 8), Ri(16)) 83 | Block: lava 84 | Common(1) Snow 85 | -------------------------------------------------------------------------------- /I73 Noise Generation: -------------------------------------------------------------------------------- 1 | # Minecraft Terrain Shape Generation 2 | Minecraft terrain shape generation is rather complex. There are 5 noise generators: 3 | 4 | * Biome Influence - determines how much the biome properties influence the noise. 5 | * Depth - Determines the general height values of the terrain. 6 | * Lower and Upper limit - Determines the lower and upper limit for the computed density. 7 | * Main - Determines the interpolation factor between the lower and upper limit noise values. 8 | 9 | ## Noise Generation 10 | Noise is generated at a resolution of `5x17x5` for a chunk of size `16x128x16`. Each noise value represents a end of one `4x8x4` section of blocks, and the start of another. Noise values are trilinearly interpolated between for each individual block coordinate. This is done to significantly increase the performance of world generation due to the amount of time that Perlin noise generation takes relative to other processes. In addition, it makes the noise more smooth. Both of these properties are documented on Notch's blog. 11 | ## Biome Chaos 12 | For each horizontal position in the chunk, a `BiomeChaos` value is calculated from the temperature and rainfall values. This is what makes hot and rainy values more chaotic that cold and dry biomes. This is also the only way that biomes influence the terrain's shape, in stark contrast to the world generation of modern Minecraft. 13 | ```rust 14 | GetBiomeChaos(Noise: real, Rain: real, Temperature: real) -> real { 15 | let Factor = 1.0 - Pow(1.0 - Rain * Temperature, 4); 16 | let Chaos = (Noise / 512.0 + 0.5) * Factor + 0.5; 17 | return Clamp(Chaos, 0.5, 1.5); 18 | } 19 | ``` 20 | ## Heightmap 21 | In addition, a height "center" is calculated from the noise. The height center determines the general border between ground and sky. In density computation, locations below the height center have increased density while those above have decreased density. The height center of each horizontal position forms a sort of heightmap. 22 | ```rust 23 | const DepthBase = 8.5; 24 | GetHeightCenter(Noise: real) -> real { 25 | let Depth = (Noise / 8000.0) * 3.0; 26 | if Depth < 0 { 27 | Depth *= 0.3; 28 | } 29 | 30 | let Deviation = Clamp(Abs(Depth) - 2, -2.0, 1.0); 31 | if Deviation < 0 { 32 | Deviation /= 1.4; 33 | } else { 34 | Deviation /= 2.0; 35 | } 36 | 37 | return DepthBase + Deviation * (DepthBase / 8.0); 38 | } 39 | ``` 40 | In this snippet, the final value of the `Deviation` variable has a range of approximately 12 blocks below to approximately 4 blocks above the `DepthBase`. The value of `DepthBase` is important in the calculation: `8.5 * 8` is a Y value of 68, slightly above sea level. 41 | 42 | ## Density 43 | 44 | Each noise value actually represents a density, where densities above 0.0 are solid blocks. 45 | ```rust 46 | const HeightStretch = 12.0; 47 | const LowerLimitScale = 512.0; 48 | const UpperLimitScale = 512.0; 49 | const MainScale = 20.0; 50 | const TaperBase = 13.0; 51 | 52 | GetDensityAt(Y: int, Chaos: real, HeightCenter: real, NoiseLower: real, NoiseUpper: real, NoiseMain: real) -> real { 53 | let DistanceAboveHeightCenter = Y - HeightCenter; 54 | if DistanceAboveHeightCenter < 0 { 55 | // Make areas below the height center more shallow 56 | DistanceAboveHeightCenter *= 4.0; 57 | } 58 | 59 | let Calmness = DistanceAboveHeightCenter * HeightStretch; 60 | 61 | let Lower = NoiseLower / LowerLimitScale; 62 | let Upper = NoiseUpper / UpperLimitScale; 63 | let Main = Clamp(NoiseMain / MainScale, 0.0, 1.0); 64 | let Density = Lerp(Main, Lower, Upper) - (Calmness / Chaos); 65 | 66 | let TaperFactor = (Max(Y, TaperBase) - TaperBase) / 3; 67 | Density *= 1.0 - TaperFactor; 68 | Density -= 10.0 * TaperFactor; 69 | 70 | return Density; 71 | } 72 | ``` 73 | The DistanceAboveHeightCenter variable is multiplied by 4 if the Y coordinate is below the height center. This creates flat lowlands and oceans, while still keeping steep hills. This is then factored into the Calmness variable. The Calmness variable is the effect the heightmap has on the density. Finally, the TaperFactor reduces the density significantly around Y=112. 74 | 75 | # Block Selection 76 | 77 | Block selection follows a simple algorithm: 78 | ```rust 79 | const SeaCoord = 63; 80 | SelectBlock(Density: real, Temperature: real, Y: int) -> Block { 81 | if Density > 0 { 82 | Stone 83 | } else if Y == SeaCoord && Temperature < 0.5 { 84 | Ice 85 | } else if Y <= SeaCoord { 86 | StationaryWater 87 | } else { 88 | Air 89 | } 90 | } 91 | ``` 92 | Effectively, a density of anything above 0 is stone. Otherwise, blocks below sea level become water. The rest of the blocks are air. If the temperature is below 0.5 (50%), then the top layer of water is frozen into ice. 93 | # Density Array Access 94 | The Density array is accessed using Trilinear interpolation: 95 | ```rust 96 | TrilinearInterpolate(Array: real[5][5][17], Piece: Vector3, Inner: Vector3) { 97 | Lerp(Inner.Z/4.0, 98 | Lerp(Inner.X/4.0 99 | Lerp(Inner.Y/8.0, 100 | Array[XPiece][ZPiece][YPiece], 101 | Array[XPiece][ZPiece][YPiece + 1] 102 | ), 103 | Lerp(Inner.Y/8.0 104 | Array[XPiece + 1][ZPiece][YPiece], 105 | Array[XPiece + 1][ZPiece][YPiece + 1] 106 | ) 107 | ), 108 | Lerp(Inner.X/4.0 109 | Lerp(Inner.Y/8.0 110 | Array[XPiece][ZPiece + 1][YPiece], 111 | Array[XPiece][ZPiece + 1][YPiece + 1], 112 | ), 113 | Lerp(Inner.Y/8.0 114 | Array[XPiece + 1][ZPiece + 1][YPiece], 115 | Array[XPiece + 1][ZPiece + 1][YPiece + 1], 116 | ) 117 | ) 118 | ) 119 | } 120 | ``` 121 | 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i73 2 | Minecraft Beta 1.7.3 compatible world generator 3 | 4 | ## What is i73? 5 | The goal of the i73 project is to create a generator with the capability of generating terrain 6 | that is practically identical to the default Beta 1.7.3 generator. This means that given the 7 | correct configuration, the produced terrain should be identical aside from non-deterministic 8 | things like Decorators. However, i73 will also have many features added over the default 9 | generator. 10 | 11 | ## Planned/Implemented Features 12 | 13 | ### Customizable generation settings 14 | i73 will support a level of configurability that is at least as good as the Customized world 15 | type in modern versions. Since many parts of the old world generation remain in modern versions, 16 | many of the same parameters exist in both versions. 17 | 18 | ### Exposed APIs for working with generation components 19 | Unlike the default generator, i73 will allow library users to access many generation components 20 | directly. This customization allows easy learning from the content of the code as well as the 21 | possibility of alternative implementations of Minecraft world generations building on the 22 | generation primitives expressed in i73. Some examples of primitives include the default noise 23 | generators, default decorators, caves, terrain shaping and painting, and biomes. 24 | 25 | ### Slick command line / IPC API along with a C interface 26 | In addition to the exposed generation primitives to Rust code, i73 will also provide an interface 27 | accessible through the command line, interprocess communication, and a C-compatible interface. 28 | This makes i73 accessible from practically any environment, and allows many use cases including 29 | biome correction, generating huge sections of the world (without Bukkit plugins/spawnpoint changing 30 | hacks), and generating chunk data for access in a server implementation. 31 | 32 | ### Compact + Extensible chunk storage system 33 | i73 internally uses a palette-based system for storing block data. Not only is this fast and space 34 | efficient, it is also in line with the 1.9 protocol chunk format meaning that chunk data generated 35 | either from the Rust API, IPC, or C interface can be sent over the wire directly. The use of 36 | palettes also allows the internal block type to be changed, which will make changing/removing the 37 | ID limit much easier than in the Notchian minecraft implementation. This also makes i73 very likely 38 | ready for the 1.13 Chunk Format teased by the Minecraft devs, which is said to use palettes. 39 | 40 | // TODO: Finish this README. Installation, status, etc. 41 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::env; 3 | use std::fs::File; 4 | use std::io::Write; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | let out_dir = env::var("OUT_DIR").unwrap(); 9 | let dest_path = Path::new(&out_dir).join("sin_table.rs"); 10 | let mut f = File::create(&dest_path).unwrap(); 11 | 12 | println!("cargo:rerun-if-changed=build.rs"); 13 | 14 | write!(f, "pub const SIN_TABLE: [u32; 16384] = [").unwrap(); 15 | 16 | for i in 0..16384 { 17 | if i % 16 == 0 { 18 | writeln!(f, "\t").unwrap(); 19 | } 20 | 21 | write!(f, "{:#010X}, ", trig(i).to_bits()).unwrap(); 22 | } 23 | 24 | writeln!(f, "];").unwrap(); 25 | } 26 | 27 | fn trig(index: u16) -> f32 { 28 | ((index as f64) * 3.141592653589793 * 2.0 / 65536.0).sin() as f32 29 | } -------------------------------------------------------------------------------- /i73.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /profiles/b173/biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "biomes": { 3 | "tundra": { 4 | "debug_name": "Tundra", 5 | "surface": { 6 | "top": "2:0", 7 | "fill": "3:0", 8 | "chain": [] 9 | } 10 | }, 11 | "taiga": { 12 | "debug_name": "Taiga", 13 | "surface": { 14 | "top": "2:0", 15 | "fill": "3:0", 16 | "chain": [] 17 | } 18 | }, 19 | "swampland": { 20 | "debug_name": "Swampland", 21 | "surface": { 22 | "top": "2:0", 23 | "fill": "3:0", 24 | "chain": [] 25 | } 26 | }, 27 | "savanna": { 28 | "debug_name": "Savanna", 29 | "surface": { 30 | "top": "2:0", 31 | "fill": "3:0", 32 | "chain": [] 33 | } 34 | }, 35 | "shrubland": { 36 | "debug_name": "Shrubland", 37 | "surface": { 38 | "top": "2:0", 39 | "fill": "3:0", 40 | "chain": [] 41 | } 42 | }, 43 | "forest": { 44 | "debug_name": "Forest", 45 | "surface": { 46 | "top": "2:0", 47 | "fill": "3:0", 48 | "chain": [] 49 | } 50 | }, 51 | "desert": { 52 | "debug_name": "Desert", 53 | "surface": { 54 | "top": "12:0", 55 | "fill": "12:0", 56 | "chain": [ 57 | { 58 | "block": "24:0", 59 | "max_depth": 3 60 | } 61 | ] 62 | } 63 | }, 64 | "plains": { 65 | "debug_name": "Plains", 66 | "surface": { 67 | "top": "2:0", 68 | "fill": "3:0", 69 | "chain": [] 70 | } 71 | }, 72 | "seasonal_forest": { 73 | "debug_name": "Seasonal Forest", 74 | "surface": { 75 | "top": "2:0", 76 | "fill": "3:0", 77 | "chain": [] 78 | } 79 | }, 80 | "rainforest": { 81 | "debug_name": "Rainforest", 82 | "surface": { 83 | "top": "2:0", 84 | "fill": "3:0", 85 | "chain": [] 86 | } 87 | }, 88 | "ice_desert": { 89 | "debug_name": "Ice Desert", 90 | "surface": { 91 | "top": "12:0", 92 | "fill": "12:0", 93 | "chain": [ 94 | { 95 | "block": "24:0", 96 | "max_depth": 3 97 | } 98 | ] 99 | } 100 | } 101 | }, 102 | "default": "plains", 103 | "grid": [ 104 | { "temperature": [0.00, 0.10], "rainfall": [0.00, 1.00], "biome": "tundra" }, 105 | 106 | { "temperature": [0.10, 0.50], "rainfall": [0.00, 0.20], "biome": "tundra" }, 107 | { "temperature": [0.10, 0.50], "rainfall": [0.20, 0.50], "biome": "taiga" }, 108 | { "temperature": [0.10, 0.70], "rainfall": [0.50, 1.00], "biome": "swampland" }, 109 | 110 | { "temperature": [0.50, 0.95], "rainfall": [0.00, 0.20], "biome": "savanna" }, 111 | { "temperature": [0.50, 0.97], "rainfall": [0.20, 0.35], "biome": "shrubland" }, 112 | { "temperature": [0.50, 0.97], "rainfall": [0.35, 0.50], "biome": "forest" }, 113 | { "temperature": [0.70, 0.97], "rainfall": [0.50, 1.00], "biome": "forest" }, 114 | 115 | { "temperature": [0.95, 1.00], "rainfall": [0.00, 0.20], "biome": "desert" }, 116 | { "temperature": [0.97, 1.00], "rainfall": [0.20, 0.45], "biome": "plains" }, 117 | { "temperature": [0.97, 1.00], "rainfall": [0.45, 0.90], "biome": "seasonal_forest" }, 118 | { "temperature": [0.97, 1.00], "rainfall": [0.90, 1.00], "biome": "rainforest" } 119 | ], 120 | "decorator_sets": { 121 | "lakes": [ 122 | { 123 | "decorator": "lake", 124 | "settings": { 125 | "blocks": { 126 | "liquid": "9:0", 127 | "carve": "0:0", 128 | "is_liquid": { 129 | "kind": "whitelist", 130 | "blocks": ["8:0", "9:0", "10:0", "11:0"] 131 | }, 132 | "is_solid": { 133 | "kind": "blacklist", 134 | "blocks": ["0:0", "8:0", "9:0", "10:0", "11:0"] 135 | }, 136 | "replaceable": { 137 | "kind": "whitelist", 138 | "blocks": [] 139 | } 140 | } 141 | }, 142 | "height_distribution": { 143 | "base": { 144 | "kind": "Linear", 145 | "min": 0, 146 | "max": 127 147 | } 148 | }, 149 | "count": { 150 | "base": { 151 | "kind": "Constant", 152 | "value": 1 153 | }, 154 | "chance": 4 155 | } 156 | } 157 | ] 158 | } 159 | } -------------------------------------------------------------------------------- /profiles/b173/climate.json: -------------------------------------------------------------------------------- 1 | { 2 | "temperature_fq": 0.25, 3 | "rainfall_fq": 0.33333333333333331, 4 | "mixin_fq": 0.58823529411764708, 5 | "temperature_mixin": 0.01, 6 | "rainfall_mixin": 0.002, 7 | "temperature_mean": 0.7, 8 | "temperature_coeff": 0.15, 9 | "rainfall_mean": 0.5, 10 | "rainfall_coeff": 0.15, 11 | "mixin_mean": 0.5, 12 | "mixin_coeff": 1.1 13 | } 14 | -------------------------------------------------------------------------------- /profiles/b173/customized.json: -------------------------------------------------------------------------------- 1 | { 2 | "coordinateScale": 684.412, 3 | "heightScale": 684.412, 4 | "lowerLimitScale": 512, 5 | "upperLimitScale": 512, 6 | "depthNoiseScaleX": 200, 7 | "depthNoiseScaleZ": 200, 8 | "depthNoiseScaleExponent": 0.5, 9 | "mainNoiseScaleX": 80, 10 | "mainNoiseScaleY": 160, 11 | "mainNoiseScaleZ": 80, 12 | "baseSize": 8.5, 13 | "stretchY": 12, 14 | "biomeDepthWeight": 1, 15 | "biomeDepthOffset": 0, 16 | "biomeScaleWeight": 1, 17 | "biomeScaleOffset": 0, 18 | "seaLevel": 64, 19 | "useCaves": true, 20 | "useDungeons": true, 21 | "dungeonChance": 8, 22 | "useStrongholds": true, 23 | "useVillages": true, 24 | "useMineShafts": true, 25 | "useTemples": true, 26 | "useMonuments": true, 27 | "useRavines": true, 28 | "useWaterLakes": true, 29 | "waterLakeChance": 4, 30 | "useLavaLakes": true, 31 | "lavaLakeChance": 80, 32 | "useLavaOceans": false, 33 | "fixedBiome": -1, 34 | "biomeSize": 4, 35 | "riverSize": 4, 36 | "dirtSize": 33, 37 | "dirtCount": 10, 38 | "dirtMinHeight": 0, 39 | "dirtMaxHeight": 256, 40 | "gravelSize": 33, 41 | "gravelCount": 8, 42 | "gravelMinHeight": 0, 43 | "gravelMaxHeight": 256, 44 | "graniteSize": 33, 45 | "graniteCount": 10, 46 | "graniteMinHeight": 0, 47 | "graniteMaxHeight": 80, 48 | "dioriteSize": 33, 49 | "dioriteCount": 10, 50 | "dioriteMinHeight": 0, 51 | "dioriteMaxHeight": 80, 52 | "andesiteSize": 33, 53 | "andesiteCount": 10, 54 | "andesiteMinHeight": 0, 55 | "andesiteMaxHeight": 80, 56 | "coalSize": 17, 57 | "coalCount": 20, 58 | "coalMinHeight": 0, 59 | "coalMaxHeight": 128, 60 | "ironSize": 9, 61 | "ironCount": 20, 62 | "ironMinHeight": 0, 63 | "ironMaxHeight": 64, 64 | "goldSize": 9, 65 | "goldCount": 2, 66 | "goldMinHeight": 0, 67 | "goldMaxHeight": 32, 68 | "redstoneSize": 8, 69 | "redstoneCount": 8, 70 | "redstoneMinHeight": 0, 71 | "redstoneMaxHeight": 16, 72 | "diamondSize": 8, 73 | "diamondCount": 1, 74 | "diamondMinHeight": 0, 75 | "diamondMaxHeight": 16, 76 | "lapisSize": 7, 77 | "lapisCount": 1, 78 | "lapisCenterHeight": 16, 79 | "lapisSpread": 16 80 | } -------------------------------------------------------------------------------- /profiles/b173/customized_.json: -------------------------------------------------------------------------------- 1 | {"coordinateScale":512.0,"heightScale":256.0,"lowerLimitScale":512.0,"upperLimitScale":128.0,"depthNoiseScaleX":200.0,"depthNoiseScaleZ":200.0,"depthNoiseScaleExponent":0.25,"mainNoiseScaleX":320.0,"mainNoiseScaleY":160.0,"mainNoiseScaleZ":320.0,"baseSize":5.0,"stretchY":12.0,"biomeDepthWeight":1.0,"biomeDepthOffset":0.0,"biomeScaleWeight":1.0,"biomeScaleOffset":0.0,"seaLevel":38,"useCaves":true,"useDungeons":true,"dungeonChance":15,"useStrongholds":true,"useVillages":true,"useMineShafts":true,"useTemples":true,"useMonuments":true,"useRavines":true,"useWaterLakes":true,"waterLakeChance":15,"useLavaLakes":true,"lavaLakeChance":80,"useLavaOceans":false,"fixedBiome":-1,"biomeSize":5,"riverSize":5,"dirtSize":33,"dirtCount":10,"dirtMinHeight":0,"dirtMaxHeight":256,"gravelSize":33,"gravelCount":8,"gravelMinHeight":0,"gravelMaxHeight":256,"graniteSize":33,"graniteCount":10,"graniteMinHeight":0,"graniteMaxHeight":80,"dioriteSize":33,"dioriteCount":10,"dioriteMinHeight":0,"dioriteMaxHeight":80,"andesiteSize":33,"andesiteCount":10,"andesiteMinHeight":0,"andesiteMaxHeight":80,"coalSize":17,"coalCount":20,"coalMinHeight":0,"coalMaxHeight":128,"ironSize":9,"ironCount":20,"ironMinHeight":0,"ironMaxHeight":64,"goldSize":9,"goldCount":2,"goldMinHeight":0,"goldMaxHeight":32,"redstoneSize":8,"redstoneCount":8,"redstoneMinHeight":0,"redstoneMaxHeight":16,"diamondSize":8,"diamondCount":1,"diamondMinHeight":0,"diamondMaxHeight":16,"lapisSize":9,"lapisCount":20,"lapisCenterHeight":96,"lapisSpread":32} -------------------------------------------------------------------------------- /profiles/b173_debug_biomes/biomes.json: -------------------------------------------------------------------------------- 1 | { 2 | "biomes": { 3 | "tundra": { 4 | "debug_name": "Tundra", 5 | "surface": { 6 | "top": "35:1", 7 | "fill": "3:0", 8 | "chain": [] 9 | } 10 | }, 11 | "taiga": { 12 | "debug_name": "Taiga", 13 | "surface": { 14 | "top": "35:2", 15 | "fill": "3:0", 16 | "chain": [] 17 | } 18 | }, 19 | "swampland": { 20 | "debug_name": "Swampland", 21 | "surface": { 22 | "top": "35:3", 23 | "fill": "3:0", 24 | "chain": [] 25 | } 26 | }, 27 | "savanna": { 28 | "debug_name": "Savanna", 29 | "surface": { 30 | "top": "35:4", 31 | "fill": "3:0", 32 | "chain": [] 33 | } 34 | }, 35 | "shrubland": { 36 | "debug_name": "Shrubland", 37 | "surface": { 38 | "top": "35:5", 39 | "fill": "3:0", 40 | "chain": [] 41 | } 42 | }, 43 | "forest": { 44 | "debug_name": "Forest", 45 | "surface": { 46 | "top": "35:6", 47 | "fill": "3:0", 48 | "chain": [] 49 | } 50 | }, 51 | "desert": { 52 | "debug_name": "Desert", 53 | "surface": { 54 | "top": "35:7", 55 | "fill": "12:0", 56 | "chain": [ 57 | { 58 | "block": "24:0", 59 | "max_depth": 3 60 | } 61 | ] 62 | } 63 | }, 64 | "plains": { 65 | "debug_name": "Plains", 66 | "surface": { 67 | "top": "35:8", 68 | "fill": "3:0", 69 | "chain": [] 70 | } 71 | }, 72 | "seasonal_forest": { 73 | "debug_name": "Seasonal Forest", 74 | "surface": { 75 | "top": "35:9", 76 | "fill": "3:0", 77 | "chain": [] 78 | } 79 | }, 80 | "rainforest": { 81 | "debug_name": "Rainforest", 82 | "surface": { 83 | "top": "92:0", 84 | "fill": "3:0", 85 | "chain": [] 86 | } 87 | }, 88 | "ice_desert": { 89 | "debug_name": "Ice Desert", 90 | "surface": { 91 | "top": "35:11", 92 | "fill": "12:0", 93 | "chain": [ 94 | { 95 | "block": "24:0", 96 | "max_depth": 3 97 | } 98 | ] 99 | } 100 | } 101 | }, 102 | "default": "plains", 103 | "grid": [ 104 | { "temperature": [0.00, 0.10], "rainfall": [0.00, 1.00], "biome": "tundra" }, 105 | 106 | { "temperature": [0.10, 0.50], "rainfall": [0.00, 0.20], "biome": "tundra" }, 107 | { "temperature": [0.10, 0.50], "rainfall": [0.20, 0.50], "biome": "taiga" }, 108 | { "temperature": [0.10, 0.70], "rainfall": [0.50, 1.00], "biome": "swampland" }, 109 | 110 | { "temperature": [0.50, 0.95], "rainfall": [0.00, 0.20], "biome": "savanna" }, 111 | { "temperature": [0.50, 0.97], "rainfall": [0.20, 0.35], "biome": "shrubland" }, 112 | { "temperature": [0.50, 0.97], "rainfall": [0.35, 0.50], "biome": "forest" }, 113 | { "temperature": [0.70, 0.97], "rainfall": [0.50, 1.00], "biome": "forest" }, 114 | 115 | { "temperature": [0.95, 1.00], "rainfall": [0.00, 0.20], "biome": "desert" }, 116 | { "temperature": [0.97, 1.00], "rainfall": [0.20, 0.45], "biome": "plains" }, 117 | { "temperature": [0.97, 1.00], "rainfall": [0.45, 0.90], "biome": "seasonal_forest" }, 118 | { "temperature": [0.97, 1.00], "rainfall": [0.90, 1.00], "biome": "rainforest" } 119 | ] 120 | } -------------------------------------------------------------------------------- /profiles/b173_debug_biomes/customized.json: -------------------------------------------------------------------------------- 1 | { 2 | "coordinateScale": 684.412, 3 | "heightScale": 684.412, 4 | "lowerLimitScale": 512, 5 | "upperLimitScale": 512, 6 | "depthNoiseScaleX": 200, 7 | "depthNoiseScaleZ": 200, 8 | "depthNoiseScaleExponent": 0.5, 9 | "mainNoiseScaleX": 80, 10 | "mainNoiseScaleY": 160, 11 | "mainNoiseScaleZ": 80, 12 | "baseSize": 8.5, 13 | "stretchY": 12, 14 | "biomeDepthWeight": 1, 15 | "biomeDepthOffset": 0, 16 | "biomeScaleWeight": 1, 17 | "biomeScaleOffset": 0, 18 | "seaLevel": 64, 19 | "useCaves": true, 20 | "useDungeons": true, 21 | "dungeonChance": 8, 22 | "useStrongholds": true, 23 | "useVillages": true, 24 | "useMineShafts": true, 25 | "useTemples": true, 26 | "useMonuments": true, 27 | "useRavines": true, 28 | "useWaterLakes": true, 29 | "waterLakeChance": 4, 30 | "useLavaLakes": true, 31 | "lavaLakeChance": 80, 32 | "useLavaOceans": false, 33 | "fixedBiome": -1, 34 | "biomeSize": 4, 35 | "riverSize": 4, 36 | "dirtSize": 33, 37 | "dirtCount": 10, 38 | "dirtMinHeight": 0, 39 | "dirtMaxHeight": 256, 40 | "gravelSize": 33, 41 | "gravelCount": 8, 42 | "gravelMinHeight": 0, 43 | "gravelMaxHeight": 256, 44 | "graniteSize": 33, 45 | "graniteCount": 10, 46 | "graniteMinHeight": 0, 47 | "graniteMaxHeight": 80, 48 | "dioriteSize": 33, 49 | "dioriteCount": 10, 50 | "dioriteMinHeight": 0, 51 | "dioriteMaxHeight": 80, 52 | "andesiteSize": 33, 53 | "andesiteCount": 10, 54 | "andesiteMinHeight": 0, 55 | "andesiteMaxHeight": 80, 56 | "coalSize": 17, 57 | "coalCount": 20, 58 | "coalMinHeight": 0, 59 | "coalMaxHeight": 128, 60 | "ironSize": 9, 61 | "ironCount": 20, 62 | "ironMinHeight": 0, 63 | "ironMaxHeight": 64, 64 | "goldSize": 9, 65 | "goldCount": 2, 66 | "goldMinHeight": 0, 67 | "goldMaxHeight": 32, 68 | "redstoneSize": 8, 69 | "redstoneCount": 8, 70 | "redstoneMinHeight": 0, 71 | "redstoneMaxHeight": 16, 72 | "diamondSize": 8, 73 | "diamondCount": 1, 74 | "diamondMinHeight": 0, 75 | "diamondMaxHeight": 16, 76 | "lapisSize": 7, 77 | "lapisCount": 1, 78 | "lapisCenterHeight": 16, 79 | "lapisSpread": 16 80 | } -------------------------------------------------------------------------------- /src/bin/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rs25; 2 | extern crate vocs; 3 | extern crate i73; 4 | extern crate java_rand; 5 | #[macro_use] 6 | extern crate serde_json; 7 | 8 | use std::path::PathBuf; 9 | use std::fs::File; 10 | use std::cmp::min; 11 | 12 | use i73::config::settings::customized::{Customized, Parts}; 13 | use i73::generator::Pass; 14 | use i73::generator::overworld_173::{self, Settings}; 15 | use i73::config::biomes::{BiomesConfig, DecoratorConfig}; 16 | use i73::biome::Lookup; 17 | use i73::structure; 18 | use i73::matcher::BlockMatcher; 19 | 20 | use vocs::indexed::ChunkIndexed; 21 | use vocs::world::world::World; 22 | use vocs::view::ColumnMut; 23 | use vocs::position::{GlobalColumnPosition, GlobalChunkPosition}; 24 | 25 | use rs25::level::manager::{ColumnSnapshot, ChunkSnapshot}; 26 | use rs25::level::region::RegionWriter; 27 | use rs25::level::anvil::ColumnRoot; 28 | 29 | fn main() { 30 | let profile_name = match ::std::env::args().skip(1).next() { 31 | Some(name) => name, 32 | None => { 33 | println!("Usage: i73 "); 34 | return; 35 | } 36 | }; 37 | 38 | let mut profile = PathBuf::new(); 39 | profile.push("profiles"); 40 | profile.push(&profile_name); 41 | 42 | println!("Using profile {}: {}", profile_name, profile.to_string_lossy()); 43 | 44 | let customized = serde_json::from_reader::(File::open(profile.join("customized.json")).unwrap()).unwrap(); 45 | let parts = Parts::from(customized); 46 | 47 | println!(" Tri Noise Settings: {:?}", parts.tri); 48 | println!(" Height Stretch: {:?}", parts.height_stretch); 49 | println!(" Height Settings: {:?}", parts.height); 50 | println!(" Biome Settings: {:?}", parts.biome); 51 | println!(" Structures: {:?}", parts.structures); 52 | println!(" Decorators: {:?}", parts.decorators); 53 | 54 | let mut settings = Settings::default(); 55 | 56 | settings.tri = parts.tri; 57 | settings.height = parts.height.into(); 58 | settings.field.height_stretch = parts.height_stretch; 59 | 60 | // TODO: Biome Settings 61 | 62 | let sea_block = if parts.ocean.top > 0 { 63 | settings.sea_coord = min(parts.ocean.top - 1, 255) as u8; 64 | 65 | if parts.ocean.lava { 11*16 } else { 9*16 } 66 | } else { 67 | 0*16 68 | }; 69 | 70 | settings.shape_blocks.ocean = sea_block; 71 | settings.paint_blocks.ocean = sea_block; 72 | 73 | // TODO: Structures and Decorators 74 | 75 | let biomes_config = serde_json::from_reader::(File::open(profile.join("biomes.json")).unwrap()).unwrap(); 76 | let grid = biomes_config.to_grid().unwrap(); 77 | 78 | let mut decorator_registry: ::std::collections::HashMap>> = ::std::collections::HashMap::new(); 79 | decorator_registry.insert("vein".into(), Box::new(::i73::decorator::vein::VeinDecoratorFactory::default())); 80 | decorator_registry.insert("seaside_vein".into(), Box::new(::i73::decorator::vein::SeasideVeinDecoratorFactory::default())); 81 | 82 | let gravel_config = DecoratorConfig { 83 | decorator: "vein".into(), 84 | settings: json!({ 85 | "blocks": { 86 | "replace": { 87 | "blacklist": false, 88 | "blocks": [16] 89 | }, 90 | "block": 208 91 | }, 92 | "size": 32 93 | }), 94 | height_distribution: ::i73::distribution::Chance { 95 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 96 | min: 0, 97 | max: 63 98 | }), 99 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 100 | chance: 1 101 | }, 102 | count: ::i73::distribution::Chance { 103 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 104 | min: 0, 105 | max: 9 106 | }), 107 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 108 | chance: 1 109 | } 110 | }; 111 | 112 | let mut decorators: Vec<::i73::decorator::Dispatcher, i73::distribution::Chance, u16>> = Vec::new(); 113 | 114 | decorators.push (::i73::decorator::Dispatcher { 115 | decorator: Box::new(::i73::decorator::lake::LakeDecorator { 116 | blocks: ::i73::decorator::lake::LakeBlocks { 117 | is_liquid: BlockMatcher::include([8*16, 9*16, 10*16, 11*16].iter()), 118 | is_solid: BlockMatcher::exclude([0*16, 8*16, 9*16, 10*16, 11*16].iter()), // TODO: All nonsolid blocks 119 | replacable: BlockMatcher::none(), // TODO 120 | liquid: 9*16, 121 | carve: 0*16, 122 | solidify: None 123 | }, 124 | settings: ::i73::decorator::lake::LakeSettings::default() 125 | }), 126 | height_distribution: ::i73::distribution::Chance { 127 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 128 | min: 0, 129 | max: 127 130 | }), 131 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 132 | chance: 1 133 | }, 134 | rarity: ::i73::distribution::Chance { 135 | base: ::i73::distribution::Baseline::Constant { value: 1 }, 136 | chance: 4, 137 | ordering: ::i73::distribution::ChanceOrdering::AlwaysGeneratePayload 138 | } 139 | }); 140 | 141 | decorators.push (::i73::decorator::Dispatcher { 142 | decorator: Box::new(::i73::decorator::vein::SeasideVeinDecorator { 143 | vein: ::i73::decorator::vein::VeinDecorator { 144 | blocks: ::i73::decorator::vein::VeinBlocks { 145 | replace: BlockMatcher::is(12*16), 146 | block: 82*16 147 | }, 148 | size: 32 149 | }, 150 | ocean: BlockMatcher::include([8*16, 9*16].iter()) 151 | }), 152 | height_distribution: ::i73::distribution::Chance { 153 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 154 | min: 0, 155 | max: 63 156 | }), 157 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 158 | chance: 1 159 | }, 160 | rarity: ::i73::distribution::Chance { 161 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 162 | min: 0, 163 | max: 9 164 | }), 165 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 166 | chance: 1 167 | } 168 | }); 169 | 170 | decorators.push (gravel_config.into_dispatcher(&decorator_registry).unwrap()); 171 | 172 | decorators.push (::i73::decorator::Dispatcher { 173 | decorator: Box::new(::i73::decorator::clump::Clump { 174 | iterations: 64, 175 | horizontal: 8, 176 | vertical: 4, 177 | decorator: ::i73::decorator::clump::plant::PlantDecorator { 178 | block: 31*16 + 1, 179 | base: BlockMatcher::include([2*16, 3*16, 60*16].into_iter()), 180 | replace: BlockMatcher::is(0*16) 181 | }, 182 | phantom: ::std::marker::PhantomData:: 183 | }), 184 | height_distribution: ::i73::distribution::Chance { 185 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 186 | min: 0, 187 | max: 127 188 | }), 189 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 190 | chance: 1 191 | }, 192 | rarity: ::i73::distribution::Chance { 193 | base: i73::distribution::Baseline::Linear(i73::distribution::Linear { 194 | min: 0, 195 | max: 90 196 | }), 197 | ordering: i73::distribution::ChanceOrdering::AlwaysGeneratePayload, 198 | chance: 1 199 | } 200 | }); 201 | 202 | /*use decorator::large_tree::{LargeTreeSettings, LargeTree}; 203 | let settings = LargeTreeSettings::default(); 204 | 205 | for i in 0..1 { 206 | let mut rng = Random::new(100 + i); 207 | let shape = settings.tree((0, 0, 0), &mut rng, None, 20); 208 | 209 | println!("{:?}", shape); 210 | 211 | let mut y = shape.foliage_max_y - 1; 212 | while y >= shape.foliage_min_y { 213 | let spread = shape.spread(y); 214 | 215 | println!("y: {}, spread: {}", y, spread); 216 | 217 | for _ in 0..shape.foliage_per_y { 218 | println!("{:?}", shape.foliage(y, spread, &mut rng)); 219 | } 220 | 221 | y -= 1; 222 | } 223 | }*/ 224 | 225 | let (shape, paint) = overworld_173::passes(8399452073110208023, settings, Lookup::generate(&grid)); 226 | 227 | let caves_generator = structure::caves::CavesGenerator { 228 | carve: 0*16, 229 | lower: 10*16, 230 | surface_block: 2*16, 231 | ocean: BlockMatcher::include([8*16, 9*16].iter()), 232 | carvable: BlockMatcher::include([1*16, 2*16, 3*16].iter()), 233 | surface_top: BlockMatcher::is(2*16), 234 | surface_fill: BlockMatcher::is(3*16), 235 | blob_size_multiplier: 1.0, 236 | vertical_multiplier: 1.0, 237 | lower_surface: 10 238 | }; 239 | let caves = structure::StructureGenerateNearby::new(8399452073110208023, 8, caves_generator); 240 | 241 | /*let shape = nether_173::passes(-160654125608861039, &nether_173::default_tri_settings(), nether_173::ShapeBlocks::default(), 31); 242 | 243 | let default_grid = biome::default_grid(); 244 | 245 | let mut fake_settings = Settings::default(); 246 | fake_settings.biome_lookup = biome::Lookup::filled(default_grid.lookup(biome::climate::Climate::new(0.5, 0.0))); 247 | fake_settings.sea_coord = 31; 248 | fake_settings.beach = None; 249 | fake_settings.max_bedrock_height = None; 250 | 251 | let (_, paint) = overworld_173::passes(-160654125608861039, fake_settings);*/ 252 | 253 | let mut world = World::>::new(); 254 | 255 | println!("Generating region (0, 0)"); 256 | let gen_start = ::std::time::Instant::now(); 257 | 258 | for x in 0..32 { 259 | println!("{}", x); 260 | for z in 0..32 { 261 | let column_position = GlobalColumnPosition::new(x, z); 262 | 263 | let mut column_chunks = [ 264 | ChunkIndexed::::new(4, 0), 265 | ChunkIndexed::::new(4, 0), 266 | ChunkIndexed::::new(4, 0), 267 | ChunkIndexed::::new(4, 0), 268 | ChunkIndexed::::new(4, 0), 269 | ChunkIndexed::::new(4, 0), 270 | ChunkIndexed::::new(4, 0), 271 | ChunkIndexed::::new(4, 0), 272 | ChunkIndexed::::new(4, 0), 273 | ChunkIndexed::::new(4, 0), 274 | ChunkIndexed::::new(4, 0), 275 | ChunkIndexed::::new(4, 0), 276 | ChunkIndexed::::new(4, 0), 277 | ChunkIndexed::::new(4, 0), 278 | ChunkIndexed::::new(4, 0), 279 | ChunkIndexed::::new(4, 0) 280 | ]; 281 | 282 | { 283 | let mut column: ColumnMut = ColumnMut::from_array(&mut column_chunks); 284 | 285 | shape.apply(&mut column, column_position); 286 | paint.apply(&mut column, column_position); 287 | caves.apply(&mut column, column_position); 288 | } 289 | 290 | world.set_column(column_position, column_chunks); 291 | } 292 | } 293 | 294 | { 295 | let end = ::std::time::Instant::now(); 296 | let time = end.duration_since(gen_start); 297 | 298 | let secs = time.as_secs(); 299 | let us = (secs * 1000000) + ((time.subsec_nanos() / 1000) as u64); 300 | 301 | println!("Generation done in {}us ({}us per column)", us, us / 1024); 302 | } 303 | 304 | println!("Decorating region (0, 0)"); 305 | let dec_start = ::std::time::Instant::now(); 306 | 307 | let mut decoration_rng = ::java_rand::Random::new(8399452073110208023); 308 | let coefficients = ( 309 | ((decoration_rng.next_i64() >> 1) << 1) + 1, 310 | ((decoration_rng.next_i64() >> 1) << 1) + 1 311 | ); 312 | 313 | for x in 0..31 { 314 | println!("{}", x); 315 | for z in 0..31 { 316 | let x_part = (x as i64).wrapping_mul(coefficients.0) as u64; 317 | let z_part = (z as i64).wrapping_mul(coefficients.1) as u64; 318 | decoration_rng = ::java_rand::Random::new((x_part.wrapping_add(z_part)) ^ 8399452073110208023); 319 | 320 | let mut quad = world.get_quad_mut(GlobalColumnPosition::new(x as i32, z as i32)).unwrap(); 321 | 322 | for dispatcher in &decorators { 323 | dispatcher.generate(&mut quad, &mut decoration_rng).unwrap(); 324 | } 325 | } 326 | } 327 | 328 | { 329 | let end = ::std::time::Instant::now(); 330 | let time = end.duration_since(dec_start); 331 | 332 | let secs = time.as_secs(); 333 | let us = (secs * 1000000) + ((time.subsec_nanos() / 1000) as u64); 334 | 335 | println!("Decoration done in {}us ({}us per column)", us, us / 1024); 336 | } 337 | 338 | use vocs::nibbles::{u4, ChunkNibbles, BulkNibbles}; 339 | use vocs::mask::ChunkMask; 340 | use vocs::sparse::SparseStorage; 341 | use vocs::mask::LayerMask; 342 | use vocs::component::*; 343 | use vocs::view::{SplitDirectional, Directional}; 344 | use rs25::dynamics::light::{SkyLightSources, Lighting, HeightMapBuilder}; 345 | use rs25::dynamics::queue::Queue; 346 | use vocs::position::{Offset, dir}; 347 | 348 | use vocs::world::shared::{NoPack, SharedWorld}; 349 | 350 | let mut sky_light = SharedWorld::>::new(); 351 | let mut incomplete = World::::new(); 352 | let mut heightmaps = ::std::collections::HashMap::<(i32, i32), Vec>::new(); // TODO: Better vocs integration. 353 | 354 | let mut lighting_info = SparseStorage::::with_default(u4::new(15)); 355 | lighting_info.set( 0 * 16, u4::new(0)); 356 | lighting_info.set( 8 * 16, u4::new(2)); 357 | lighting_info.set( 9 * 16, u4::new(2)); 358 | 359 | let empty_lighting = ChunkNibbles::default(); 360 | 361 | let mut queue = Queue::default(); 362 | 363 | println!("Performing initial sky lighting for region (0, 0)"); 364 | let lighting_start = ::std::time::Instant::now(); 365 | 366 | fn spill_out(chunk_position: GlobalChunkPosition, incomplete: &mut World, old_spills: vocs::view::Directional) { 367 | if let Some(up) = chunk_position.plus_y() { 368 | if !old_spills[dir::Up].is_filled(false) { 369 | incomplete.get_or_create_mut(up).layer_zx_mut(0).combine(&old_spills[dir::Up]); 370 | } 371 | } 372 | 373 | if let Some(down) = chunk_position.minus_y() { 374 | if !old_spills[dir::Down].is_filled(false) { 375 | incomplete.get_or_create_mut(down).layer_zx_mut(15).combine(&old_spills[dir::Down]); 376 | } 377 | } 378 | 379 | if let Some(plus_x) = chunk_position.plus_x() { 380 | if !old_spills[dir::PlusX].is_filled(false) { 381 | incomplete.get_or_create_mut(plus_x).layer_zy_mut(0).combine(&old_spills[dir::PlusX]); 382 | } 383 | } 384 | 385 | if let Some(minus_x) = chunk_position.minus_x() { 386 | if !old_spills[dir::MinusX].is_filled(false) { 387 | incomplete.get_or_create_mut(minus_x).layer_zy_mut(15).combine(&old_spills[dir::MinusX]); 388 | } 389 | } 390 | 391 | if let Some(plus_z) = chunk_position.plus_z() { 392 | if !old_spills[dir::PlusZ].is_filled(false) { 393 | incomplete.get_or_create_mut(plus_z).layer_yx_mut(0).combine(&old_spills[dir::PlusZ]); 394 | } 395 | } 396 | 397 | if let Some(minus_z) = chunk_position.minus_z() { 398 | if !old_spills[dir::MinusZ].is_filled(false) { 399 | incomplete.get_or_create_mut(minus_z).layer_yx_mut(15).combine(&old_spills[dir::MinusZ]); 400 | } 401 | } 402 | } 403 | 404 | for x in 0..32 { 405 | println!("{}", x); 406 | for z in 0..32 { 407 | let column_position = GlobalColumnPosition::new(x, z); 408 | 409 | let mut mask = LayerMask::default(); 410 | let mut heightmap = HeightMapBuilder::new(); 411 | let mut heightmap_sections = [None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]; 412 | 413 | for y in (0..16).rev() { 414 | let chunk_position = GlobalChunkPosition::from_column(column_position, y); 415 | 416 | let (blocks, palette) = world.get(chunk_position).unwrap().freeze(); 417 | 418 | let mut opacity = BulkNibbles::new(palette.len()); 419 | 420 | for (index, value) in palette.iter().enumerate() { 421 | opacity.set(index, value.map(|entry| lighting_info.get(entry as usize)).unwrap_or(lighting_info.default_value())); 422 | } 423 | 424 | let sources = SkyLightSources::build(blocks, &opacity, mask); 425 | 426 | let mut light_data = ChunkNibbles::default(); 427 | let neighbors = Directional::combine(SplitDirectional { 428 | minus_x: &empty_lighting, 429 | plus_x: &empty_lighting, 430 | minus_z: &empty_lighting, 431 | plus_z: &empty_lighting, 432 | down: &empty_lighting, 433 | up: &empty_lighting 434 | }); 435 | 436 | let sources = { 437 | let mut light = Lighting::new(&mut light_data, neighbors, sources, opacity); 438 | 439 | light.initial(blocks, &mut queue); 440 | light.finish(blocks, &mut queue); 441 | 442 | light.decompose().1 443 | }; 444 | 445 | heightmap_sections[y as usize] = Some(sources.clone()); 446 | mask = heightmap.add(sources); 447 | 448 | let old_spills = queue.reset_spills(); 449 | 450 | spill_out(chunk_position, &mut incomplete, old_spills); 451 | 452 | sky_light.set(chunk_position, NoPack(light_data)); 453 | } 454 | 455 | let heightmap = heightmap.build(); 456 | 457 | /*for (index, part) in heightmap_sections.iter().enumerate() { 458 | let part = part.as_ref().unwrap().clone(); 459 | 460 | assert_eq!(SkyLightSources::slice(&heightmap, index as u8), part); 461 | }*/ 462 | 463 | heightmaps.insert((x, z), heightmap.into_vec()); 464 | } 465 | } 466 | 467 | { 468 | let end = ::std::time::Instant::now(); 469 | let time = end.duration_since(lighting_start); 470 | 471 | let secs = time.as_secs(); 472 | let us = (secs * 1000000) + ((time.subsec_nanos() / 1000) as u64); 473 | 474 | println!("Initial sky lighting done in {}us ({}us per column)", us, us / 1024); 475 | } 476 | 477 | println!("Completing sky lighting for region (0, 0)"); 478 | let complete_lighting_start = ::std::time::Instant::now(); 479 | 480 | while incomplete.sectors().len() > 0 { 481 | let incomplete_front = ::std::mem::replace(&mut incomplete, World::new()); 482 | 483 | for (sector_position, mut sector) in incomplete_front.into_sectors() { 484 | println!("Completing sector @ {} - {} queued", sector_position, sector.count_sectors()); 485 | 486 | let block_sector = match world.get_sector(sector_position) { 487 | Some(sector) => sector, 488 | None => continue // No sense in lighting the void. 489 | }; 490 | 491 | println!("(not skipped)"); 492 | 493 | let light_sector = sky_light.get_or_create_sector_mut(sector_position); 494 | 495 | while let Some((position, incomplete)) = sector.pop_first() { 496 | use vocs::mask::Mask; 497 | println!("Completing chunk: {} / {} queued blocks", position, incomplete.count_ones()); 498 | 499 | 500 | let (blocks, palette) = block_sector[position].as_ref().unwrap().freeze(); 501 | 502 | let mut opacity = BulkNibbles::new(palette.len()); 503 | 504 | for (index, value) in palette.iter().enumerate() { 505 | opacity.set(index, value.map(|entry| lighting_info.get(entry as usize)).unwrap_or(lighting_info.default_value())); 506 | } 507 | 508 | let column_pos = GlobalColumnPosition::combine(sector_position, position.layer()); 509 | let heightmap = heightmaps.get(&(column_pos.x(), column_pos.z())).unwrap(); 510 | 511 | let sources = SkyLightSources::slice(&heightmap, position.y()); 512 | 513 | // TODO: cross-sector lighting 514 | 515 | let mut central = light_sector.get_or_create(position); 516 | let locks = SplitDirectional { 517 | up: position.offset(dir::Up).map(|position| light_sector[position].read()), 518 | down: position.offset(dir::Down).map(|position| light_sector[position].read()), 519 | plus_x: position.offset(dir::PlusX).map(|position| light_sector[position].read()), 520 | minus_x: position.offset(dir::MinusX).map(|position| light_sector[position].read()), 521 | plus_z: position.offset(dir::PlusZ).map(|position| light_sector[position].read()), 522 | minus_z: position.offset(dir::MinusZ).map(|position| light_sector[position].read()), 523 | }; 524 | 525 | let neighbors = SplitDirectional { 526 | up: locks.up.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting), 527 | down: locks.down.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting), 528 | plus_x: locks.plus_x.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting), 529 | minus_x: locks.minus_x.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting), 530 | plus_z: locks.plus_z.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting), 531 | minus_z: locks.minus_z.as_ref().and_then(|chunk| chunk.as_ref().map(|chunk| &chunk.0)).unwrap_or(&empty_lighting) 532 | }; 533 | 534 | let mut light = Lighting::new(&mut central, Directional::combine(neighbors), sources, opacity); 535 | 536 | queue.reset_from_mask(incomplete); 537 | light.finish(blocks, &mut queue); 538 | 539 | // TODO: Queue handling 540 | } 541 | } 542 | } 543 | 544 | { 545 | let end = ::std::time::Instant::now(); 546 | let time = end.duration_since(complete_lighting_start); 547 | 548 | let secs = time.as_secs(); 549 | let us = (secs * 1000000) + ((time.subsec_nanos() / 1000) as u64); 550 | 551 | println!("Sky lighting completion done in {}us ({}us per column)", us, us / 1024); 552 | } 553 | 554 | println!("Writing region (0, 0)"); 555 | let writing_start = ::std::time::Instant::now(); 556 | 557 | // use rs25::level::manager::{Manager, RegionPool}; 558 | // let pool = RegionPool::new(PathBuf::from("out/region/"), 512); 559 | // let mut manager = Manager::manage(pool); 560 | 561 | let file = File::create("out/region/r.0.0.mca").unwrap(); 562 | let mut writer = RegionWriter::start(file).unwrap(); 563 | 564 | for z in 0..32 { 565 | println!("{}", z); 566 | for x in 0..32 { 567 | let column_position = GlobalColumnPosition::new(x, z); 568 | 569 | let heightmap = heightmaps.remove(&(x, z)).unwrap(); 570 | 571 | let mut snapshot = ColumnSnapshot { 572 | chunks: vec![None; 16], 573 | last_update: 0, 574 | light_populated: true, 575 | terrain_populated: true, 576 | inhabited_time: 0, 577 | biomes: vec![0; 256], 578 | heightmap, 579 | entities: vec![], 580 | tile_entities: vec![], 581 | tile_ticks: vec![] 582 | }; 583 | 584 | for y in 0..16 { 585 | let chunk_position = GlobalChunkPosition::from_column(column_position, y); 586 | 587 | let chunk = world.get(chunk_position).unwrap(); 588 | 589 | if chunk.anvil_empty() { 590 | continue; 591 | } 592 | 593 | let sky_light = sky_light.remove(chunk_position).unwrap()/*_or_else(ChunkNibbles::default)*/; 594 | 595 | snapshot.chunks[y as usize] = Some(ChunkSnapshot { 596 | blocks: chunk.clone(), 597 | block_light: ChunkNibbles::default(), 598 | sky_light: sky_light.0 599 | }); 600 | }; 601 | 602 | let root = ColumnRoot::from(snapshot.to_column(x as i32, z as i32).unwrap()); 603 | 604 | writer.chunk(x as u8, z as u8, &root).unwrap(); 605 | } 606 | } 607 | 608 | writer.finish().unwrap(); 609 | 610 | { 611 | let end = ::std::time::Instant::now(); 612 | let time = end.duration_since(writing_start); 613 | 614 | let secs = time.as_secs(); 615 | let us = (secs * 1000000) + ((time.subsec_nanos() / 1000) as u64); 616 | 617 | println!("Writing done in {}us ({}us per column)", us, us / 1024); 618 | } 619 | } -------------------------------------------------------------------------------- /src/biome/climate.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Point2; 2 | use noise::octaves::SimplexOctaves; 3 | use java_rand::Random; 4 | use sample::Sample; 5 | 6 | #[derive(Serialize, Deserialize, Copy, Clone, Debug)] 7 | pub struct ClimateSettings { 8 | pub temperature_fq: f64, 9 | pub rainfall_fq: f64, 10 | pub mixin_fq: f64, 11 | pub temperature_mixin: f64, 12 | pub rainfall_mixin: f64, 13 | pub temperature_mean: f64, 14 | pub temperature_coeff: f64, 15 | pub rainfall_mean: f64, 16 | pub rainfall_coeff: f64, 17 | pub mixin_mean: f64, 18 | pub mixin_coeff: f64 19 | } 20 | 21 | impl Default for ClimateSettings { 22 | fn default() -> Self { 23 | ClimateSettings { 24 | temperature_fq: 0.25, 25 | rainfall_fq: 1.0/3.0, 26 | mixin_fq: 1.0/1.7, 27 | temperature_mixin: 0.010, 28 | rainfall_mixin: 0.002, 29 | temperature_mean: 0.7, 30 | temperature_coeff: 0.15, 31 | rainfall_mean: 0.5, 32 | rainfall_coeff: 0.15, 33 | mixin_mean: 0.5, 34 | mixin_coeff: 1.1 35 | } 36 | } 37 | } 38 | 39 | const TEMP_COEFF: i64 = 9871; 40 | const RAIN_COEFF: i64 = 39811; 41 | const MIXIN_COEFF: i64 = 543321; 42 | 43 | #[derive(Debug)] 44 | pub struct ClimateSource { 45 | temperature: SimplexOctaves, 46 | rainfall: SimplexOctaves, 47 | mixin: SimplexOctaves, 48 | settings: ClimateSettings, 49 | temp_keep: f64, 50 | rain_keep: f64 51 | } 52 | 53 | impl ClimateSource { 54 | pub fn new(seed: u64, settings: ClimateSettings) -> Self { 55 | let seed = seed as i64; 56 | let scale = (1 << 4) as f64; 57 | 58 | ClimateSource { 59 | temperature: SimplexOctaves::new(&mut Random::new(seed.wrapping_mul(TEMP_COEFF) as u64), 4, settings.temperature_fq, 0.5, (0.4 / scale, 0.4 / scale)), 60 | rainfall: SimplexOctaves::new(&mut Random::new(seed.wrapping_mul(RAIN_COEFF) as u64), 4, settings.rainfall_fq, 0.5, (0.8 / scale, 0.8 / scale)), 61 | mixin: SimplexOctaves::new(&mut Random::new(seed.wrapping_mul(MIXIN_COEFF) as u64), 2, settings.mixin_fq, 0.5, (4.0 / scale, 4.0 / scale)), 62 | settings, 63 | temp_keep: 1.0 - settings.temperature_mixin, 64 | rain_keep: 1.0 - settings.rainfall_mixin 65 | } 66 | } 67 | } 68 | 69 | impl Sample for ClimateSource { 70 | type Output = Climate; 71 | 72 | fn sample(&self, point: Point2) -> Self::Output { 73 | let mixin = self.mixin.sample(point) * self.settings.mixin_coeff + self.settings.mixin_mean; 74 | 75 | let temp = (self.temperature.sample(point) * self.settings.temperature_coeff + self.settings.temperature_mean) * self.temp_keep + mixin * self.settings.temperature_mixin; 76 | let rain = (self.rainfall.sample(point) * self.settings.rainfall_coeff + self.settings.rainfall_mean ) * self.rain_keep + mixin * self.settings.rainfall_mixin; 77 | 78 | let temp = 1.0 - (1.0 - temp).powi(2); 79 | 80 | Climate::new(temp, rain) 81 | } 82 | } 83 | 84 | #[derive(Debug, Default, Copy, Clone)] 85 | pub struct Climate { 86 | temperature: f64, 87 | rainfall: f64 88 | } 89 | 90 | impl Climate { 91 | /// Returns a Climate that represents Minecraft Alpha terrain. 92 | pub fn alpha() -> Self { 93 | Climate { 94 | temperature: 1.0, 95 | rainfall: 1.0 96 | } 97 | } 98 | 99 | pub fn new(temperature: f64, rainfall: f64) -> Self { 100 | Climate { 101 | temperature: temperature.max(0.0).min(1.0), 102 | rainfall: rainfall.max(0.0).min(1.0) 103 | } 104 | } 105 | 106 | pub fn freezing(&self) -> bool { 107 | self.temperature < 0.5 108 | } 109 | 110 | pub fn temperature(&self) -> f64 { 111 | self.temperature 112 | } 113 | 114 | pub fn rainfall(&self) -> f64 { 115 | self.rainfall 116 | } 117 | 118 | pub fn adjusted_rainfall(&self) -> f64 { 119 | self.temperature * self.rainfall 120 | } 121 | 122 | /// Returns a value between 0.0 and 1.0 that lowers/raises the chaos. 123 | /// Temperature and Rainfall at 100% results in 1.0, which is the 124 | /// influence factor for generators without biomes. 125 | /// This means that no biome is in fact signalling rainforest-like terrain. 126 | pub fn influence_factor(&self) -> f64 { 127 | 1.0 - f64::powi(1.0 - self.adjusted_rainfall(), 4) 128 | } 129 | } -------------------------------------------------------------------------------- /src/biome/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod climate; 2 | pub mod source; 3 | 4 | use biome::climate::Climate; 5 | use vocs::indexed::Target; 6 | use std::borrow::Cow; 7 | use segmented::Segmented; 8 | 9 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 10 | pub struct Biome where B: Target { 11 | pub surface: Surface, 12 | pub name: Cow<'static, str> 13 | } 14 | 15 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 16 | pub struct Surface where B: Target { 17 | pub top: B, 18 | pub fill: B, 19 | pub chain: Vec> 20 | } 21 | 22 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 23 | pub struct Followup where B: Target { 24 | pub block: B, 25 | pub max_depth: u32 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Grid(pub Segmented>>) where B: Target; 30 | impl Grid where B: Target { 31 | fn new_temperatures(biome: Biome) -> Segmented> { 32 | let mut temperatures = Segmented::new(biome.clone()); 33 | temperatures.add_boundary(1.0, biome.clone()); 34 | 35 | temperatures 36 | } 37 | 38 | pub fn new(default: Biome) -> Self { 39 | let temperatures = Self::new_temperatures(default); 40 | 41 | let mut grid = Segmented::new(temperatures.clone()); 42 | grid.add_boundary(1.0, temperatures.clone()); 43 | 44 | Grid(grid) 45 | } 46 | 47 | pub fn add(&mut self, temperature: (f64, f64), rainfall: (f64, f64), biome: Biome) { 48 | self.0.for_all_aligned(rainfall.0, rainfall.1, &|| Self::new_temperatures(biome.clone()), &|temperatures| { 49 | temperatures.for_all_aligned(temperature.0, temperature.1, &|| biome.clone(), &|existing| { 50 | *existing = biome.clone(); 51 | }) 52 | }) 53 | } 54 | 55 | pub fn lookup(&self, climate: Climate) -> &Biome { 56 | self.0.get(climate.adjusted_rainfall()).get(climate.temperature()) 57 | } 58 | } 59 | 60 | pub struct Lookup(Box<[Biome]>) where B: Target; 61 | impl Lookup where B: Target { 62 | pub fn filled(biome: &Biome) -> Self { 63 | let mut lookup = Vec::with_capacity(4096); 64 | 65 | for _ in 0..4096 { 66 | lookup.push(biome.clone()); 67 | } 68 | 69 | Lookup(lookup.into_boxed_slice()) 70 | } 71 | 72 | pub fn generate(grid: &Grid) -> Self { 73 | let mut lookup = Vec::with_capacity(4096); 74 | 75 | for index in 0..4096 { 76 | let (temperature, rainfall) = (index / 64, index % 64); 77 | 78 | let climate = Climate::new((temperature as f64) / 63.0, (rainfall as f64) / 63.0); 79 | 80 | lookup.push(grid.lookup(climate).clone()); 81 | } 82 | 83 | Lookup(lookup.into_boxed_slice()) 84 | } 85 | 86 | pub fn lookup_raw(&self, temperature: usize, rainfall: usize) -> &Biome { 87 | &self.0[temperature * 64 + rainfall] 88 | } 89 | 90 | pub fn lookup(&self, climate: Climate) -> &Biome { 91 | self.lookup_raw((climate.temperature() * 63.0) as usize, (climate.rainfall() * 63.0) as usize) 92 | } 93 | } -------------------------------------------------------------------------------- /src/biome/source.rs: -------------------------------------------------------------------------------- 1 | use vocs::position::{LayerPosition, GlobalColumnPosition}; 2 | use biome::{Biome, Lookup}; 3 | use biome::climate::{Climate, ClimateSource}; 4 | use vocs::indexed::{LayerIndexed, Target}; 5 | use cgmath::{Point2, Vector2}; 6 | use sample::Sample; 7 | 8 | pub struct BiomeSource where B: Target { 9 | climate: ClimateSource, 10 | lookup: Lookup 11 | } 12 | 13 | impl BiomeSource where B: Target { 14 | pub fn new(climate: ClimateSource, lookup: Lookup) -> Self { 15 | BiomeSource { climate, lookup } 16 | } 17 | 18 | pub fn layer(&self, chunk: GlobalColumnPosition) -> LayerIndexed> { 19 | let block = Point2::new ( 20 | (chunk.x() * 16) as f64, 21 | (chunk.z() * 16) as f64 22 | ); 23 | 24 | // TODO: Avoid the default lookup and clone. 25 | let mut layer = LayerIndexed::new(2, self.lookup.lookup(Climate::new(1.0, 1.0)).clone()); 26 | 27 | for z in 0..16 { 28 | for x in 0..16 { 29 | let position = LayerPosition::new(x, z); 30 | 31 | let climate = self.climate.sample(block + Vector2::new(x as f64, z as f64)); 32 | let biome = self.lookup.lookup(climate); 33 | 34 | layer.set_immediate(position, biome); 35 | } 36 | } 37 | 38 | layer 39 | } 40 | } -------------------------------------------------------------------------------- /src/config/biomes.rs: -------------------------------------------------------------------------------- 1 | use biome::{Grid, Biome, Surface, Followup}; 2 | use serde_json; 3 | use distribution::{Chance, Baseline}; 4 | use std::collections::HashMap; 5 | use std::num::ParseIntError; 6 | use std::borrow::Cow; 7 | use decorator::{Dispatcher, DecoratorFactory}; 8 | 9 | #[derive(Debug)] 10 | pub enum Error { 11 | ParseInt(ParseIntError), 12 | UnknownBiome(String) 13 | } 14 | 15 | impl From for Error { 16 | fn from(from: ParseIntError) -> Self { 17 | Error::ParseInt(from) 18 | } 19 | } 20 | 21 | #[derive(Debug, Serialize, Deserialize)] 22 | pub struct BiomesConfig { 23 | #[serde(default)] 24 | pub decorator_sets: HashMap>, 25 | pub biomes: HashMap, 26 | pub default: String, 27 | pub grid: Vec 28 | } 29 | 30 | impl BiomesConfig { 31 | pub fn to_grid(&self) -> Result, Error> { 32 | let mut translated = HashMap::with_capacity(self.biomes.capacity()); 33 | 34 | for (name, biome) in &self.biomes { 35 | translated.insert(name.clone(), biome.to_biome()?); 36 | } 37 | 38 | let default = translated.get(&self.default).ok_or_else(|| Error::UnknownBiome(self.default.clone()))?; 39 | 40 | let mut grid = Grid::new(default.clone()); 41 | 42 | for rect in &self.grid { 43 | let biome = translated.get(&rect.biome).ok_or_else(|| Error::UnknownBiome(rect.biome.clone()))?; 44 | 45 | grid.add(rect.temperature, rect.rainfall, biome.clone()); 46 | } 47 | 48 | Ok(grid) 49 | } 50 | } 51 | 52 | #[derive(Debug, Serialize, Deserialize)] 53 | pub struct BiomeConfig { 54 | pub debug_name: String, 55 | pub surface: SurfaceConfig, 56 | #[serde(default)] 57 | pub decorators: Vec 58 | } 59 | 60 | impl BiomeConfig { 61 | pub fn to_biome(&self) -> Result, ParseIntError> { 62 | Ok(Biome { 63 | name: Cow::Owned(self.debug_name.clone()), 64 | surface: self.surface.to_surface()? 65 | }) 66 | } 67 | } 68 | 69 | #[derive(Debug, Serialize, Deserialize)] 70 | pub struct SurfaceConfig { 71 | pub top: String, 72 | pub fill: String, 73 | pub chain: Vec 74 | } 75 | 76 | impl SurfaceConfig { 77 | pub fn to_surface(&self) -> Result, ParseIntError> { 78 | Ok(Surface { 79 | top: parse_id(&self.top)?, 80 | fill: parse_id(&self.fill)?, 81 | chain: self.chain.iter().map(FollowupConfig::to_followup).collect::>, ParseIntError>>()? 82 | }) 83 | } 84 | } 85 | 86 | #[derive(Debug, Serialize, Deserialize)] 87 | pub struct FollowupConfig { 88 | pub block: String, 89 | pub max_depth: u32 90 | } 91 | 92 | impl FollowupConfig { 93 | pub fn to_followup(&self) -> Result, ParseIntError> { 94 | Ok(Followup { 95 | block: parse_id(&self.block)?, 96 | max_depth: self.max_depth 97 | }) 98 | } 99 | } 100 | 101 | #[derive(Debug, Serialize, Deserialize)] 102 | pub struct DecoratorConfig { 103 | pub decorator: String, 104 | pub settings: serde_json::Value, 105 | pub height_distribution: Chance, 106 | pub count: Chance 107 | } 108 | 109 | impl DecoratorConfig { 110 | pub fn into_dispatcher(self, registry: &HashMap>>) -> Result, Chance, u16>, String> { 111 | let factory = registry.get(&self.decorator).ok_or_else(|| format!("unknown decorator kind: {}", self.decorator))?; 112 | 113 | let decorator =factory.configure(self.settings).map_err(|e| format!("{}", e))?; 114 | 115 | Ok(Dispatcher { 116 | decorator, 117 | height_distribution: self.height_distribution, 118 | rarity: self.count 119 | }) 120 | } 121 | } 122 | 123 | #[derive(Debug, Serialize, Deserialize)] 124 | pub struct RectConfig { 125 | pub temperature: (f64, f64), 126 | pub rainfall: (f64, f64), 127 | pub biome: String 128 | } 129 | 130 | pub fn parse_id(id: &str) -> Result { 131 | let mut split = id.split(':'); 132 | 133 | let primary = split.next().unwrap().parse::()?; 134 | let secondary = split.next().map(|s| s.parse::()); 135 | 136 | let secondary = match secondary { 137 | Some(secondary) => secondary?, 138 | None => 0 139 | }; 140 | 141 | Ok(primary * 16 + secondary) 142 | } -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod biomes; 2 | pub mod settings; -------------------------------------------------------------------------------- /src/config/settings/customized.rs: -------------------------------------------------------------------------------- 1 | use noise_field::volume::TriNoiseSettings; 2 | use noise_field::height::HeightSettings81; 3 | use cgmath::Vector3; 4 | 5 | #[derive(Debug, Serialize, Deserialize, PartialEq)] 6 | pub struct Customized { 7 | #[serde(rename="coordinateScale")] pub coordinate_scale: f64, 8 | #[serde(rename="heightScale")] pub height_scale: f64, 9 | 10 | #[serde(rename="lowerLimitScale")] pub lower_limit_scale: f64, 11 | #[serde(rename="upperLimitScale")] pub upper_limit_scale: f64, 12 | 13 | #[serde(rename="mainNoiseScaleX")] pub main_noise_scale_x: f64, 14 | #[serde(rename="mainNoiseScaleY")] pub main_noise_scale_y: f64, 15 | #[serde(rename="mainNoiseScaleZ")] pub main_noise_scale_z: f64, 16 | 17 | #[serde(rename="depthNoiseScaleX")] pub depth_noise_scale_x: f64, 18 | #[serde(rename="depthNoiseScaleZ")] pub depth_noise_scale_z: f64, 19 | #[serde(rename="depthNoiseScaleExponent")] pub depth_noise_scale_exponent: f64, // Unused. 20 | 21 | #[serde(rename="baseSize")] pub depth_base: f64, 22 | #[serde(rename="stretchY")] pub height_stretch: f64, 23 | 24 | #[serde(rename="biomeDepthWeight")] pub biome_depth_weight: f64, 25 | #[serde(rename="biomeDepthOffset")] pub biome_depth_offset: f64, 26 | #[serde(rename="biomeScaleWeight")] pub biome_scale_weight: f64, 27 | #[serde(rename="biomeScaleOffset")] pub biome_scale_offset: f64, 28 | 29 | #[serde(rename="seaLevel")] pub sea_level: i32, 30 | #[serde(rename="useCaves")] pub use_caves: bool, 31 | #[serde(rename="useDungeons")] pub use_dungeons: bool, 32 | #[serde(rename="dungeonChance")] pub dungeon_chance: i32, 33 | #[serde(rename="useStrongholds")] pub use_strongholds: bool, 34 | #[serde(rename="useVillages")] pub use_villages: bool, 35 | #[serde(rename="useMineShafts")] pub use_mineshafts: bool, 36 | #[serde(rename="useTemples")] pub use_temples: bool, 37 | #[serde(rename="useRavines")] pub use_ravines: bool, 38 | #[serde(rename="useWaterLakes")] pub use_water_lakes: bool, 39 | #[serde(rename="waterLakeChance")] pub water_lake_chance: i32, 40 | #[serde(rename="useLavaLakes")] pub use_lava_lakes: bool, 41 | #[serde(rename="lavaLakeChance")] pub lava_lake_chance: i32, 42 | #[serde(rename="useLavaOceans")] pub use_lava_oceans: bool, 43 | 44 | #[serde(rename="fixedBiome")] pub fixed_biome: i32, 45 | #[serde(rename="biomeSize")] pub biome_size: i32, 46 | #[serde(rename="riverSize")] pub river_size: i32, 47 | 48 | #[serde(rename="dirtSize")] pub dirt_size: i32, 49 | #[serde(rename="dirtCount")] pub dirt_count: i32, 50 | #[serde(rename="dirtMinHeight")] pub dirt_min_height: i32, 51 | #[serde(rename="dirtMaxHeight")] pub dirt_max_height: i32, 52 | 53 | #[serde(rename="gravelSize")] pub gravel_size: i32, 54 | #[serde(rename="gravelCount")] pub gravel_count: i32, 55 | #[serde(rename="gravelMinHeight")] pub gravel_min_height: i32, 56 | #[serde(rename="gravelMaxHeight")] pub gravel_max_height: i32, 57 | 58 | #[serde(rename="graniteSize")] pub granite_size: i32, 59 | #[serde(rename="graniteCount")] pub granite_count: i32, 60 | #[serde(rename="graniteMinHeight")] pub granite_min_height: i32, 61 | #[serde(rename="graniteMaxHeight")] pub granite_max_height: i32, 62 | 63 | #[serde(rename="dioriteSize")] pub diorite_size: i32, 64 | #[serde(rename="dioriteCount")] pub diorite_count: i32, 65 | #[serde(rename="dioriteMinHeight")] pub diorite_min_height: i32, 66 | #[serde(rename="dioriteMaxHeight")] pub diorite_max_height: i32, 67 | 68 | #[serde(rename="andesiteSize")] pub andesite_size: i32, 69 | #[serde(rename="andesiteCount")] pub andesite_count: i32, 70 | #[serde(rename="andesiteMinHeight")] pub andesite_min_height: i32, 71 | #[serde(rename="andesiteMaxHeight")] pub andesite_max_height: i32, 72 | 73 | #[serde(rename="coalSize")] pub coal_size: i32, 74 | #[serde(rename="coalCount")] pub coal_count: i32, 75 | #[serde(rename="coalMinHeight")] pub coal_min_height: i32, 76 | #[serde(rename="coalMaxHeight")] pub coal_max_height: i32, 77 | 78 | #[serde(rename="ironSize")] pub iron_size: i32, 79 | #[serde(rename="ironCount")] pub iron_count: i32, 80 | #[serde(rename="ironMinHeight")] pub iron_min_height: i32, 81 | #[serde(rename="ironMaxHeight")] pub iron_max_height: i32, 82 | 83 | #[serde(rename="goldSize")] pub gold_size: i32, 84 | #[serde(rename="goldCount")] pub gold_count: i32, 85 | #[serde(rename="goldMinHeight")] pub gold_min_height: i32, 86 | #[serde(rename="goldMaxHeight")] pub gold_max_height: i32, 87 | 88 | #[serde(rename="redstoneSize")] pub redstone_size: i32, 89 | #[serde(rename="redstoneCount")] pub redstone_count: i32, 90 | #[serde(rename="redstoneMinHeight")] pub redstone_min_height: i32, 91 | #[serde(rename="redstoneMaxHeight")] pub redstone_max_height: i32, 92 | 93 | #[serde(rename="diamondSize")] pub diamond_size: i32, 94 | #[serde(rename="diamondCount")] pub diamond_count: i32, 95 | #[serde(rename="diamondMinHeight")] pub diamond_min_height: i32, 96 | #[serde(rename="diamondMaxHeight")] pub diamond_max_height: i32, 97 | 98 | #[serde(rename="lapisSize")] pub lapis_size: i32, 99 | #[serde(rename="lapisCount")] pub lapis_count: i32, 100 | #[serde(rename="lapisCenterHeight")] pub lapis_center_height: i32, 101 | #[serde(rename="lapisSpread")] pub lapis_spread: i32 102 | } 103 | 104 | #[derive(Debug, PartialEq)] 105 | pub struct Parts { 106 | pub tri: TriNoiseSettings, 107 | pub height_stretch: f64, 108 | pub height: HeightSettings81, 109 | pub biome: BiomeSettings, 110 | pub ocean: Ocean, 111 | pub structures: Structures, 112 | pub decorators: Decorators 113 | } 114 | 115 | impl From for Parts { 116 | fn from(settings: Customized) -> Self { 117 | let h_scale = settings.coordinate_scale; 118 | let y_scale = settings.height_scale; 119 | 120 | Parts { 121 | tri: TriNoiseSettings { 122 | main_out_scale: 20.0, 123 | upper_out_scale: settings.upper_limit_scale, 124 | lower_out_scale: settings.lower_limit_scale, 125 | lower_scale: Vector3::new(h_scale, y_scale, h_scale ), 126 | upper_scale: Vector3::new(h_scale, y_scale, h_scale ), 127 | main_scale: Vector3::new(h_scale / settings.main_noise_scale_x, y_scale / settings.main_noise_scale_y, h_scale / settings.main_noise_scale_z), 128 | y_size: 17 129 | }, 130 | height_stretch: settings.height_stretch, 131 | height: HeightSettings81 { 132 | coord_scale: Vector3::new(settings.depth_noise_scale_x, 0.0, settings.depth_noise_scale_z), 133 | out_scale: 8000.0, 134 | base: settings.depth_base 135 | }, 136 | biome: BiomeSettings { 137 | depth_weight: settings.biome_depth_weight, 138 | depth_offset: settings.biome_depth_offset, 139 | scale_weight: settings.biome_scale_weight, 140 | scale_offset: settings.biome_scale_offset, 141 | fixed: settings.fixed_biome, 142 | biome_size: settings.biome_size, 143 | river_size: settings.river_size 144 | }, 145 | ocean: Ocean { 146 | top: settings.sea_level, 147 | lava: settings.use_lava_oceans 148 | }, 149 | structures: Structures { 150 | caves: settings.use_caves, 151 | strongholds: settings.use_strongholds, 152 | villages: settings.use_villages, 153 | mineshafts: settings.use_mineshafts, 154 | temples: settings.use_temples, 155 | ravines: settings.use_ravines 156 | }, 157 | decorators: Decorators { 158 | dungeon_chance: if settings.use_dungeons { Some(settings.dungeon_chance) } else { None }, 159 | water_lake_chance: if settings.use_water_lakes { Some(settings.water_lake_chance) } else { None }, 160 | lava_lake_chance: if settings.use_lava_lakes { Some(settings.lava_lake_chance) } else { None }, 161 | dirt: VeinSettings { 162 | size: settings.dirt_size, 163 | count: settings.dirt_count, 164 | min_y: settings.dirt_min_height, 165 | max_y: settings.dirt_max_height 166 | }, 167 | gravel: VeinSettings { 168 | size: settings.gravel_size, 169 | count: settings.gravel_count, 170 | min_y: settings.gravel_min_height, 171 | max_y: settings.gravel_max_height 172 | }, 173 | granite: VeinSettings { 174 | size: settings.granite_size, 175 | count: settings.granite_count, 176 | min_y: settings.granite_min_height, 177 | max_y: settings.granite_max_height 178 | }, 179 | diorite: VeinSettings { 180 | size: settings.diorite_size, 181 | count: settings.diorite_count, 182 | min_y: settings.diorite_min_height, 183 | max_y: settings.diorite_max_height 184 | }, 185 | andesite: VeinSettings { 186 | size: settings.andesite_size, 187 | count: settings.andesite_count, 188 | min_y: settings.andesite_min_height, 189 | max_y: settings.andesite_max_height 190 | }, 191 | coal: VeinSettings { 192 | size: settings.coal_size, 193 | count: settings.coal_count, 194 | min_y: settings.coal_min_height, 195 | max_y: settings.coal_max_height 196 | }, 197 | iron: VeinSettings { 198 | size: settings.iron_size, 199 | count: settings.iron_count, 200 | min_y: settings.iron_min_height, 201 | max_y: settings.iron_max_height 202 | }, 203 | redstone: VeinSettings { 204 | size: settings.redstone_size, 205 | count: settings.redstone_count, 206 | min_y: settings.redstone_min_height, 207 | max_y: settings.redstone_max_height 208 | }, 209 | diamond: VeinSettings { 210 | size: settings.diamond_size, 211 | count: settings.diamond_count, 212 | min_y: settings.diamond_min_height, 213 | max_y: settings.diamond_max_height 214 | }, 215 | lapis: VeinSettingsCentered { 216 | size: settings.lapis_size, 217 | count: settings.lapis_count, 218 | center_y: settings.lapis_center_height, 219 | spread: settings.lapis_spread 220 | } 221 | } 222 | } 223 | } 224 | } 225 | 226 | #[derive(Debug, PartialEq)] 227 | pub struct BiomeSettings { 228 | pub depth_weight: f64, 229 | pub depth_offset: f64, 230 | pub scale_weight: f64, 231 | pub scale_offset: f64, 232 | pub fixed: i32, 233 | pub biome_size: i32, 234 | pub river_size: i32 235 | } 236 | 237 | #[derive(Debug, PartialEq)] 238 | pub struct Ocean { 239 | pub top: i32, 240 | pub lava: bool 241 | } 242 | 243 | #[derive(Debug, PartialEq)] 244 | pub struct Structures { 245 | pub caves: bool, 246 | pub strongholds: bool, 247 | pub villages: bool, 248 | pub mineshafts: bool, 249 | pub temples: bool, 250 | pub ravines: bool 251 | } 252 | 253 | #[derive(Debug, PartialEq)] 254 | pub struct Decorators { 255 | pub dungeon_chance: Option, 256 | pub water_lake_chance: Option, 257 | pub lava_lake_chance: Option, 258 | pub dirt: VeinSettings, 259 | pub gravel: VeinSettings, 260 | pub granite: VeinSettings, 261 | pub diorite: VeinSettings, 262 | pub andesite: VeinSettings, 263 | pub coal: VeinSettings, 264 | pub iron: VeinSettings, 265 | pub redstone: VeinSettings, 266 | pub diamond: VeinSettings, 267 | pub lapis: VeinSettingsCentered 268 | } 269 | 270 | #[derive(Debug, PartialEq)] 271 | pub struct VeinSettings { 272 | pub size: i32, 273 | pub count: i32, 274 | pub min_y: i32, 275 | pub max_y: i32 276 | } 277 | 278 | #[derive(Debug, PartialEq)] 279 | pub struct VeinSettingsCentered { 280 | pub size: i32, 281 | pub count: i32, 282 | pub center_y: i32, 283 | pub spread: i32 284 | } -------------------------------------------------------------------------------- /src/config/settings/flat.rs: -------------------------------------------------------------------------------- 1 | use std::str::{self, FromStr}; 2 | use nom::{digit, IError}; 3 | use std::collections::HashMap; 4 | 5 | // V1: 1; 6 | // [x][:], ...; 7 | // 8 | 9 | // V2: 2; 10 | // [x][:], ...; 11 | // ; 12 | // [(=, ...)], ... 13 | 14 | // V3: 3; 15 | // [*][:][:], ...; 16 | // ; 17 | // [(=, ...)], ... 18 | 19 | #[derive(Debug, PartialEq)] 20 | pub struct FlatV1 { 21 | pub layers: Vec, 22 | pub biome: i64 23 | } 24 | 25 | impl FromStr for FlatV1 { 26 | type Err = IError; 27 | 28 | fn from_str(s: &str) -> Result { 29 | parse_flat_v1(s).to_full_result() 30 | } 31 | } 32 | 33 | #[derive(Debug, PartialEq)] 34 | pub struct LayerV1 { 35 | count: i64, 36 | id: i64, 37 | meta: i64 38 | } 39 | 40 | named!(parse_flat_v1<&str, FlatV1>, 41 | do_parse!( 42 | version: tag!("1;") >> 43 | layers: many1!(parse_layer_v1) >> 44 | biome: alt!(parse_biome_v1 | value!(1)) >> 45 | (FlatV1 { layers, biome }) 46 | ) 47 | ); 48 | 49 | named!(parse_biome_v1<&str, i64>, 50 | do_parse!( 51 | marker: tag!(";") >> 52 | biome: integer >> 53 | (biome) 54 | ) 55 | ); 56 | 57 | named!(parse_layer_v1<&str, LayerV1>, 58 | do_parse!( 59 | count: alt!(count_v1 | value!(1)) >> 60 | id: integer >> 61 | meta: alt!(meta_v1 | value!(0)) >> 62 | sep: alt!(tag!(",") | tag!("")) >> 63 | (LayerV1 { count, id, meta }) 64 | ) 65 | ); 66 | 67 | named!(count_v1<&str, i64>, 68 | do_parse!( 69 | count: integer >> 70 | marker: tag!("x") >> 71 | (count) 72 | ) 73 | ); 74 | 75 | named!(meta_v1<&str, i64>, 76 | do_parse!( 77 | marker: tag!(":") >> 78 | meta: integer >> 79 | (meta) 80 | ) 81 | ); 82 | 83 | named!(integer<&str, i64>, 84 | map_res!( 85 | digit, 86 | FromStr::from_str 87 | ) 88 | ); 89 | 90 | #[derive(Debug, PartialEq)] 91 | pub struct FlatV3 { 92 | pub layers: Vec, 93 | pub biome: i64, 94 | pub features: HashMap> 95 | } 96 | 97 | impl FlatV3 { 98 | pub fn add_feature(&mut self, name: &str) { 99 | self.features.insert(name.to_owned(), HashMap::new()); 100 | } 101 | } 102 | 103 | impl FromStr for FlatV3 { 104 | type Err = IError; 105 | 106 | fn from_str(s: &str) -> Result { 107 | parse_flat_v3(s).to_full_result() 108 | } 109 | } 110 | 111 | #[derive(Debug, PartialEq)] 112 | pub struct LayerV3 { 113 | count: i64, 114 | namespace: String, 115 | id: String, 116 | meta: i64 117 | } 118 | 119 | impl LayerV3 { 120 | fn from_parts(count: i64, parts: (&str, &str, i64)) -> Self { 121 | LayerV3 { 122 | count, 123 | namespace: parts.0.to_string(), 124 | id: parts.1.to_string(), 125 | meta: parts.2 126 | } 127 | } 128 | } 129 | 130 | fn features_to_map(input: Vec<(String, Vec<(String, String)>)>) -> HashMap> { 131 | let mut out = HashMap::with_capacity(input.len()); 132 | 133 | for feature in input { 134 | let mut options = HashMap::with_capacity(feature.1.len()); 135 | 136 | for option in feature.1 { 137 | options.insert(option.0, option.1); 138 | } 139 | 140 | out.insert(feature.0, options); 141 | } 142 | 143 | out 144 | } 145 | 146 | named!(parse_flat_v3<&str, FlatV3>, 147 | do_parse!( 148 | version: tag!("3;") >> 149 | layers: many1!(parse_layer_v3) >> 150 | biome: alt!(parse_biome_v1 | value!(1)) >> 151 | features: alt!( 152 | preceded!( tag!(";"), many_till!( call!(parse_feature_v3), call!(eof_thunk) )) | 153 | value!((Vec::new(), "")) 154 | ) >> 155 | (FlatV3 { layers, biome, features: features_to_map(features.0) }) 156 | ) 157 | ); 158 | 159 | named!(eof_thunk<&str, &str>, eof!()); 160 | 161 | named!(parse_layer_v3<&str, LayerV3>, 162 | do_parse!( 163 | count: alt!(count_v3 | value!(1)) >> 164 | block: alt!(parse_id_all | parse_name_meta | parse_namespaced | parse_id_name) >> 165 | (LayerV3::from_parts(count, block)) 166 | ) 167 | ); 168 | 169 | named!(parse_id_all<&str, (&str, &str, i64)>, 170 | do_parse!( 171 | namespace: take_till!(|c| c==':' || c==',' || c==';') >> 172 | sep0: tag!(":") >> 173 | id: take_till!(|c| c==':' || c==',' || c==';') >> 174 | sep1: tag!(":") >> 175 | meta: integer >> 176 | sep2: alt!(tag!(",") | tag!("")) >> 177 | ((namespace, id, meta)) 178 | ) 179 | ); 180 | 181 | named!(parse_name_meta<&str, (&str, &str, i64)>, 182 | do_parse!( 183 | id: take_till!(|c| c==':' || c==',' || c==';') >> 184 | sep0: tag!(":") >> 185 | meta: integer >> 186 | sep1: alt!(tag!(",") | tag!("")) >> 187 | (("minecraft", id, meta)) 188 | ) 189 | ); 190 | 191 | named!(parse_namespaced<&str, (&str, &str, i64)>, 192 | do_parse!( 193 | namespace: take_till!(|c| c==':' || c==',' || c==';') >> 194 | sep0: tag!(":") >> 195 | id: take_till!(|c| c==':' || c==',' || c==';') >> 196 | sep1: alt!(tag!(",") | tag!("")) >> 197 | ((namespace, id, 0)) 198 | ) 199 | ); 200 | 201 | named!(parse_id_name<&str, (&str, &str, i64)>, 202 | do_parse!( 203 | id: take_till!(|c| c==',' || c==';') >> 204 | sep1: alt!(tag!(",") | tag!("")) >> 205 | (("minecraft", id, 0)) 206 | ) 207 | ); 208 | 209 | // Parses a count of the form * 210 | named!(count_v3<&str, i64>, 211 | do_parse!( 212 | count: integer >> 213 | marker: tag!("*") >> 214 | (count) 215 | ) 216 | ); 217 | 218 | // Parses a feature of the format (, , ..) or just plain . 219 | named!(parse_feature_v3<&str, (String, Vec<(String, String)>)>, 220 | do_parse!( 221 | name: take_till!(|c| c==',' || c=='(' || c==';') >> 222 | options: parse_feature_options_v3 >> 223 | sep: alt!(eof!() | tag!(",") | tag!("")) >> 224 | (name.to_string(), options) 225 | ) 226 | ); 227 | 228 | // Parses an option list of the format (, , ..) 229 | named!(parse_feature_options_v3<&str, Vec<(String, String)>>, alt!( 230 | preceded!( 231 | alt!(eof!() | tag!(",")), 232 | value!(Vec::new()) 233 | ) | do_parse!( 234 | open: tag!("(") >> 235 | options: many_till!(call!(parse_option_v3), tag!(")") ) >> 236 | (options.0) 237 | ) 238 | )); 239 | 240 | // Parses an option of the format =, and ends at either a space, seperating kv pairs, or a ')', which ends the map. 241 | named!(parse_option_v3<&str, (String, String)>, 242 | do_parse!( 243 | key: take_until_and_consume!("=") >> 244 | value: take_till!(|c| c==' ' || c==')') >> 245 | sep: alt!(tag!(" ") | tag!("")) >> 246 | ((key.to_string(), value.to_string())) 247 | ) 248 | ); 249 | 250 | #[cfg(test)] 251 | mod test { 252 | use super::{FlatV1, LayerV1, FlatV3, LayerV3}; 253 | use std::collections::HashMap; 254 | 255 | #[test] 256 | fn test_default_v1() { 257 | let default = FlatV1 { 258 | layers: vec![ 259 | LayerV1 { count: 1, id: 7, meta: 0 }, 260 | LayerV1 { count: 2, id: 3, meta: 0 }, 261 | LayerV1 { count: 1, id: 2, meta: 0 } 262 | ], 263 | biome: 1 264 | }; 265 | 266 | assert_eq!(Ok(default), "1;7,2x3,2;1".parse::()) 267 | } 268 | 269 | #[test] 270 | fn test_glasscore_v3() { 271 | let mut features = HashMap::>::new(); 272 | features.insert("mineshaft".to_string(), { let mut map = HashMap::new(); map.insert("chance".to_string(), "0.04".to_string()); map }); 273 | 274 | let mut default = FlatV3 { 275 | layers: vec![ 276 | LayerV3 { count: 1, namespace: "minecraft".to_string(), id: "bedrock".to_string(), meta: 0 }, 277 | LayerV3 { count: 63, namespace: "minecraft".to_string(), id: "glass".to_string(), meta: 0 }, 278 | ], 279 | biome: 1, 280 | features 281 | }; 282 | 283 | default.add_feature("dungeon"); 284 | default.add_feature("decoration"); 285 | default.add_feature("lake"); 286 | default.add_feature("lava_lake"); 287 | default.add_feature("stronghold"); 288 | 289 | assert_eq!(Ok(default), "3;1*minecraft:bedrock,63*minecraft:glass;1;mineshaft(chance=0.04),dungeon,decoration,lake,lava_lake,stronghold".parse::()) 290 | } 291 | } -------------------------------------------------------------------------------- /src/config/settings/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod customized; 2 | pub mod flat; -------------------------------------------------------------------------------- /src/decorator/clump/cactus.rs: -------------------------------------------------------------------------------- 1 | use vocs::position::{QuadPosition, Offset, dir}; 2 | use vocs::view::QuadMut; 3 | use vocs::indexed::Target; 4 | use matcher::BlockMatcher; 5 | use decorator::{Decorator, Result}; 6 | use java_rand::Random; 7 | 8 | pub struct CactusDecorator where B: Target { 9 | pub blocks: CactusBlocks, 10 | pub settings: CactusSettings 11 | } 12 | 13 | impl Decorator for CactusDecorator where B: Target { 14 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 15 | if !self.blocks.replace.matches(quad.get(position)) { 16 | return Ok(()); 17 | } 18 | 19 | let height = rng.next_u32_bound(self.settings.add_height + 1); 20 | let height = self.settings.base_height + rng.next_u32_bound(height + 1); 21 | 22 | let mut position = position; 23 | 24 | for _ in 0..height { 25 | position = match position.offset(dir::Up) { 26 | Some(position) => position, 27 | None => return Ok(()) 28 | }; 29 | 30 | if self.blocks.check(quad, position) { 31 | quad.set_immediate(position, &self.blocks.block); 32 | } 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | pub struct CactusBlocks where B: Target { 40 | pub replace: BlockMatcher, // Air 41 | pub base: BlockMatcher, // Cactus / Sand 42 | pub solid: BlockMatcher, // any solid block 43 | pub block: B // Cactus 44 | } 45 | 46 | impl CactusBlocks where B: Target { 47 | pub fn check(&self, quad: &mut QuadMut, position: QuadPosition) -> bool { 48 | if !self.replace.matches(quad.get(position)) { 49 | return false 50 | } 51 | 52 | if let Some(minus_x) = position.offset(dir::MinusX) { 53 | if self.solid.matches(quad.get(minus_x)) { 54 | return false; 55 | } 56 | } 57 | 58 | if let Some(plus_x) = position.offset(dir::PlusX) { 59 | if self.solid.matches(quad.get(plus_x)) { 60 | return false; 61 | } 62 | } 63 | 64 | if let Some(minus_z) = position.offset(dir::MinusZ) { 65 | if self.solid.matches(quad.get(minus_z)) { 66 | return false; 67 | } 68 | } 69 | 70 | if let Some(plus_z) = position.offset(dir::PlusZ) { 71 | if self.solid.matches(quad.get(plus_z)) { 72 | return false; 73 | } 74 | } 75 | 76 | let below = match position.offset(dir::Down) { 77 | Some(below) => below, 78 | None => return false 79 | }; 80 | 81 | self.base.matches(quad.get(below)) 82 | } 83 | } 84 | 85 | pub struct CactusSettings { 86 | /// Base, minimum height of a cactus 87 | pub base_height: u32, 88 | /// Maximum height of a cactus when added to the base height. 89 | /// For example, with base=1 and add=2, the height of a cactus can be 1-3 blocks tall. 90 | pub add_height: u32 91 | } 92 | 93 | impl Default for CactusSettings { 94 | fn default() -> Self { 95 | CactusSettings { 96 | base_height: 1, 97 | add_height: 2 98 | } 99 | } 100 | } 101 | 102 | // Clump settings: 103 | // iterations = 10 104 | // horizontal_variation = 8 105 | // vertical_variation = 4 -------------------------------------------------------------------------------- /src/decorator/clump/mod.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::indexed::Target; 3 | use vocs::view::QuadMut; 4 | use vocs::position::{QuadPosition, Offset}; 5 | use super::{Decorator, Result}; 6 | 7 | pub mod cactus; 8 | pub mod plant; 9 | pub mod sugar_cane; 10 | 11 | /// Clumped generation. Places a number of objects with a varying distance from the center. 12 | pub struct Clump where D: Decorator, B: Target { 13 | pub iterations: u32, 14 | /// Horizontal variance. Must be 8 or below, or else spilling will occur. 15 | pub horizontal: u8, 16 | /// Vertical variance. 17 | pub vertical: u8, 18 | pub decorator: D, 19 | pub phantom: ::std::marker::PhantomData 20 | } 21 | 22 | impl Decorator for Clump where D: Decorator, B: Target { 23 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 24 | for _ in 0..self.iterations { 25 | let offset = ( 26 | rng.next_i32_bound(self.horizontal as i32) - rng.next_i32_bound(self.horizontal as i32), 27 | rng.next_i32_bound(self.vertical as i32) - rng.next_i32_bound(self.vertical as i32), 28 | rng.next_i32_bound(self.horizontal as i32) - rng.next_i32_bound(self.horizontal as i32) 29 | ); 30 | 31 | if (position.y() as i32) + offset.1 < 0 { 32 | continue; 33 | } 34 | 35 | let at = match position.offset (( 36 | offset.0 as i8, 37 | offset.1 as i8, 38 | offset.2 as i8 39 | )) { 40 | Some(at) => at, 41 | None => { 42 | panic!("out of bounds offsetting {:?} by {:?}", position, offset); 43 | } 44 | }; 45 | 46 | self.decorator.generate(quad, rng, at)?; 47 | } 48 | 49 | Ok(()) 50 | } 51 | } 52 | 53 | pub struct FlatClump where D: Decorator, B: Target { 54 | pub iterations: u32, 55 | /// Horizontal variance. Must be 8 or below, or else spilling will occur. 56 | pub horizontal: u8, 57 | pub decorator: D, 58 | pub phantom: ::std::marker::PhantomData 59 | } 60 | 61 | impl Decorator for FlatClump where D: Decorator, B: Target { 62 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 63 | for _ in 0..self.iterations { 64 | let offset = ( 65 | rng.next_i32_bound(self.horizontal as i32) - rng.next_i32_bound(self.horizontal as i32), 66 | rng.next_i32_bound(self.horizontal as i32) - rng.next_i32_bound(self.horizontal as i32) 67 | ); 68 | 69 | let at = position.offset (( 70 | offset.0 as i8, 71 | 0, 72 | offset.1 as i8 73 | )).unwrap(); 74 | 75 | self.decorator.generate(quad, rng, at)?; 76 | } 77 | 78 | Ok(()) 79 | } 80 | } -------------------------------------------------------------------------------- /src/decorator/clump/plant.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::indexed::Target; 3 | use vocs::view::QuadMut; 4 | use vocs::position::{QuadPosition, Offset, dir}; 5 | use decorator::{Decorator, Result}; 6 | use matcher::BlockMatcher; 7 | 8 | // Pumpkin: On grass, replacing air or {material:ground_cover} 9 | 10 | pub struct PlantDecorator where B: Target { 11 | pub block: B, 12 | pub base: BlockMatcher, 13 | pub replace: BlockMatcher 14 | } 15 | 16 | impl Decorator for PlantDecorator where B: Target { 17 | fn generate(&self, quad: &mut QuadMut, _: &mut Random, position: QuadPosition) -> Result { 18 | // TODO: Check if the block is above the heightmap (how?) 19 | 20 | if !self.replace.matches(quad.get(position)) { 21 | return Ok(()); 22 | } 23 | 24 | match position.offset(dir::Down) { 25 | Some(below) => if !self.base.matches(quad.get(below)) { 26 | return Ok(()) 27 | }, 28 | None => return Ok(()) 29 | } 30 | 31 | quad.set_immediate(position, &self.block); 32 | 33 | Ok(()) 34 | } 35 | } -------------------------------------------------------------------------------- /src/decorator/clump/sugar_cane.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::indexed::Target; 3 | use vocs::view::QuadMut; 4 | use vocs::position::{QuadPosition, Offset, dir}; 5 | use decorator::{Decorator, Result}; 6 | use matcher::BlockMatcher; 7 | 8 | pub struct SugarCaneDecorator where B: Target { 9 | pub block: B, 10 | pub base: BlockMatcher, 11 | pub liquid: BlockMatcher, 12 | pub replace: BlockMatcher, 13 | pub base_height: u32, 14 | pub add_height: u32 15 | } 16 | 17 | impl Decorator for SugarCaneDecorator where B: Target { 18 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 19 | if !self.replace.matches(quad.get(position)) { 20 | return Ok(()); 21 | } 22 | 23 | let below = match position.offset(dir::Down) { 24 | Some(below) => below, 25 | None => return Ok(()) 26 | }; 27 | 28 | if *quad.get(below) != self.block { 29 | if !self.base.matches(quad.get(below)) { 30 | return Ok(()) 31 | } 32 | 33 | let mut valid = false; 34 | 35 | if let Some(minus_x) = below.offset(dir::MinusX) { 36 | if self.liquid.matches(quad.get(minus_x)) { 37 | valid = true; 38 | } 39 | } 40 | 41 | if let Some(plus_x) = below.offset(dir::PlusX) { 42 | if self.liquid.matches(quad.get(plus_x)) { 43 | valid = true; 44 | } 45 | } 46 | 47 | if let Some(minus_z) = below.offset(dir::MinusZ) { 48 | if self.liquid.matches(quad.get(minus_z)) { 49 | valid = true; 50 | } 51 | } 52 | 53 | if let Some(plus_z) = below.offset(dir::PlusZ) { 54 | if self.liquid.matches(quad.get(plus_z)) { 55 | valid = true; 56 | } 57 | } 58 | 59 | if !valid { 60 | return Ok(()); 61 | } 62 | } 63 | 64 | let height = rng.next_u32_bound(self.add_height + 1); 65 | let height = self.base_height + rng.next_u32_bound(height + 1); 66 | 67 | let mut position = position; 68 | 69 | for _ in 0..height { 70 | if !self.replace.matches(quad.get(position)) { 71 | return Ok(()); 72 | } 73 | 74 | quad.set_immediate(position, &self.block); 75 | 76 | if let Some(at) = position.offset(dir::Up) { 77 | position = at; 78 | } else { 79 | return Ok(()); 80 | } 81 | } 82 | 83 | Ok(()) 84 | } 85 | } -------------------------------------------------------------------------------- /src/decorator/dungeon/loot.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | 3 | // TODO 4 | #[derive(Debug, Copy, Clone)] 5 | pub enum Item { 6 | Saddle, 7 | IronIngot, 8 | Bread, 9 | Wheat, 10 | Gunpowder, 11 | String, 12 | Bucket, 13 | GoldenApple, 14 | Redstone, 15 | GoldRecord, 16 | GreenRecord, 17 | InkSac 18 | } 19 | 20 | #[derive(Debug)] 21 | pub struct Stack { 22 | item: Item, 23 | size: u32 24 | } 25 | 26 | pub struct SimpleLootTable { 27 | pools: Vec 28 | } 29 | 30 | impl SimpleLootTable { 31 | pub fn get_item(&self, rng: &mut Random) -> Option { 32 | if self.pools.len() != 0 { 33 | self.pools[rng.next_u32_bound(self.pools.len() as u32) as usize].get_item(rng) 34 | } else { 35 | None 36 | } 37 | } 38 | } 39 | 40 | impl Default for SimpleLootTable { 41 | fn default() -> Self { 42 | SimpleLootTable { 43 | pools: vec![ 44 | Pool::Common { item: Item::Saddle, base_size: 1, add_size: None }, 45 | Pool::Common { item: Item::IronIngot, base_size: 1, add_size: Some(3) }, 46 | Pool::Common { item: Item::Bread, base_size: 1, add_size: None }, 47 | Pool::Common { item: Item::Wheat, base_size: 1, add_size: Some(3) }, 48 | Pool::Common { item: Item::Gunpowder, base_size: 1, add_size: Some(3) }, 49 | Pool::Common { item: Item::String, base_size: 1, add_size: Some(3) }, 50 | Pool::Common { item: Item::Bucket, base_size: 1, add_size: None }, 51 | Pool::rare ( Pool::single(Item::GoldenApple), 100), 52 | Pool::rare ( Pool::Common { item: Item::Redstone, base_size: 1, add_size: Some(3) }, 2), 53 | Pool::rare ( Pool::Table(records()), 10), 54 | Pool::Common { item: Item::InkSac, base_size: 1, add_size: None } 55 | ] 56 | } 57 | } 58 | } 59 | 60 | fn records() -> SimpleLootTable { 61 | SimpleLootTable { 62 | pools: vec![ 63 | Pool::single(Item::GoldRecord), 64 | Pool::single(Item::GreenRecord) 65 | ] 66 | } 67 | } 68 | 69 | enum Pool { 70 | /// Creates an item with a stack size of base + rng(add + 1) 71 | Common { item: Item, base_size: u32, add_size: Option }, 72 | Decide { item: Box, other: Option>, chance: u32 }, 73 | Table (SimpleLootTable) 74 | } 75 | 76 | impl Pool { 77 | fn single(item: Item) -> Self { 78 | Pool::Common { item, base_size: 1, add_size: None } 79 | } 80 | 81 | fn rare(item: Pool, chance: u32) -> Self { 82 | Pool::Decide { item: Box::new(item), other: None, chance } 83 | } 84 | 85 | fn get_item(&self, rng: &mut Random) -> Option { 86 | match *self { 87 | Pool::Common { ref item, base_size, add_size } => Some(Stack { 88 | item: *item, 89 | size: base_size + add_size.map(|i| rng.next_u32_bound(i + 1)).unwrap_or(0) 90 | }), 91 | Pool::Decide { ref item, ref other, chance } => if rng.next_u32_bound(chance) == 0 { 92 | item.get_item(rng) 93 | } else { 94 | other.as_ref().and_then(|o| o.get_item(rng)) 95 | }, 96 | Pool::Table ( ref table ) => table.get_item(rng) 97 | } 98 | } 99 | } 100 | 101 | pub enum SpawnerMob { 102 | Skeleton, 103 | Zombie, 104 | Spider 105 | } 106 | 107 | impl SpawnerMob { 108 | pub fn select(rng: &mut Random) -> Self { 109 | match rng.next_u32_bound(4) { 110 | 0 => SpawnerMob::Skeleton, 111 | 1 | 2 => SpawnerMob::Zombie, 112 | 3 => SpawnerMob::Spider, 113 | _ => unreachable!() 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/decorator/dungeon/mod.rs: -------------------------------------------------------------------------------- 1 | mod loot; -------------------------------------------------------------------------------- /src/decorator/exposed.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::indexed::Target; 3 | use vocs::view::QuadMut; 4 | use vocs::position::{QuadPosition, Offset, dir}; 5 | use decorator::{Decorator, Result}; 6 | use matcher::BlockMatcher; 7 | 8 | pub struct ExposedDecorator where B: Target { 9 | pub block: B, 10 | pub stone: BlockMatcher, 11 | pub empty: BlockMatcher 12 | } 13 | 14 | impl Decorator for ExposedDecorator where B: Target { 15 | fn generate(&self, quad: &mut QuadMut, _: &mut Random, position: QuadPosition) -> Result { 16 | if !self.stone.matches(quad.get(position)) { 17 | return Ok(()); 18 | } 19 | 20 | match position.offset(dir::Down) { 21 | Some(below) => if !self.stone.matches(quad.get(below)) { 22 | return Ok(()) 23 | }, 24 | None => return Ok(()) 25 | } 26 | 27 | match position.offset(dir::Up) { 28 | Some(above) => if !self.stone.matches(quad.get(above)) { 29 | return Ok(()) 30 | }, 31 | None => return Ok(()) 32 | } 33 | 34 | let mut stone = 0; 35 | let mut empty = 0; 36 | 37 | if let Some(position) = position.offset(dir::MinusX) { 38 | let block = quad.get(position); 39 | 40 | if self.stone.matches(block) { stone += 1; } 41 | if self.empty.matches(block) { empty += 1; } 42 | } else { 43 | empty += 1; 44 | } 45 | 46 | if let Some(position) = position.offset(dir::PlusX) { 47 | let block = quad.get(position); 48 | 49 | if self.stone.matches(block) { stone += 1; } 50 | if self.empty.matches(block) { empty += 1; } 51 | } else { 52 | empty += 1; 53 | } 54 | 55 | if let Some(position) = position.offset(dir::MinusZ) { 56 | let block = quad.get(position); 57 | 58 | if self.stone.matches(block) { stone += 1; } 59 | if self.empty.matches(block) { empty += 1; } 60 | } else { 61 | empty += 1; 62 | } 63 | 64 | if let Some(position) = position.offset(dir::PlusZ) { 65 | let block = quad.get(position); 66 | 67 | if self.stone.matches(block) { stone += 1; } 68 | if self.empty.matches(block) { empty += 1; } 69 | } else { 70 | empty += 1; 71 | } 72 | 73 | if stone == 3 && empty == 1 { 74 | quad.set_immediate(position, &self.block); 75 | } 76 | 77 | Ok(()) 78 | } 79 | } -------------------------------------------------------------------------------- /src/decorator/lake.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::indexed::Target; 3 | use matcher::BlockMatcher; 4 | use vocs::position::{ChunkPosition, ColumnPosition, QuadPosition}; 5 | use vocs::view::QuadMut; 6 | use vocs::mask::ChunkMask; 7 | use vocs::component::*; 8 | use super::{Decorator, Result}; 9 | 10 | // Since lakes are always 16x8x16, they will never escape the Quad. 11 | 12 | pub struct LakeDecorator where B: Target { 13 | pub blocks: LakeBlocks, 14 | pub settings: LakeSettings 15 | } 16 | 17 | impl Decorator for LakeDecorator where B: Target { 18 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 19 | let mut lower = position.to_centered().unwrap(); 20 | 21 | while lower.y() > 0 && quad.get(QuadPosition::new(lower.x(), lower.y(), lower.z())) == &self.blocks.carve { 22 | lower = ColumnPosition::new(lower.x(), lower.y() - 1, lower.z()); 23 | } 24 | 25 | // Trying to access blocks below Y=0 returns air, which would cause lake generation to fail. 26 | if lower.y() < 4 { 27 | return Ok(()); 28 | } 29 | 30 | lower = ColumnPosition::new(lower.x(), lower.y() - 4, lower.z()); 31 | 32 | let mut lake = Lake::new(self.settings.surface); 33 | 34 | lake.fill(LakeBlobs::new(rng, &self.settings)); 35 | lake.update_border(); 36 | 37 | if !self.blocks.check_border(&lake, quad, lower) { 38 | return Ok(()); 39 | } 40 | 41 | self.blocks.fill_and_carve(&lake, quad, lower); 42 | 43 | Ok(()) 44 | } 45 | } 46 | 47 | pub struct LakeBlocks where B: Target { 48 | pub is_liquid: BlockMatcher, 49 | pub is_solid: BlockMatcher, 50 | pub replacable: BlockMatcher, 51 | pub liquid: B, 52 | pub carve: B, 53 | pub solidify: Option 54 | } 55 | 56 | impl LakeBlocks where B: Target { 57 | pub fn check_border(&self, lake: &Lake, quad: &mut QuadMut, lower: ColumnPosition) -> bool { 58 | 59 | for x in 0..16 { 60 | for z in 0..16 { 61 | for y in 0..lake.surface { 62 | let at = QuadPosition::new(lower.x() + x, lower.y() + y, lower.z() + z); 63 | let block = quad.get(at); 64 | 65 | if lake.get(border(x, y, z)) && *block != self.liquid && !self.is_solid.matches(block) { 66 | return false; 67 | } 68 | } 69 | 70 | for y in lake.surface..8 { 71 | let at = QuadPosition::new(lower.x() + x, lower.y() + y, lower.z() + z); 72 | 73 | if lake.get(border(x, y, z)) && self.is_liquid.matches(quad.get(at)) { 74 | return false; 75 | } 76 | } 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | 83 | pub fn fill_and_carve(&self, lake: &Lake, quad: &mut QuadMut, lower: ColumnPosition) { 84 | quad.ensure_available(self.liquid.clone()); 85 | quad.ensure_available(self.carve.clone()); 86 | 87 | let (mut blocks, palette) = quad.freeze_palette(); 88 | 89 | let liquid = palette.reverse_lookup(&self.liquid).unwrap(); 90 | let carve = palette.reverse_lookup(&self.carve).unwrap(); 91 | 92 | for x in 0..16 { 93 | for z in 0..16 { 94 | for y in 0..lake.surface { 95 | let at = QuadPosition::new(lower.x() + x, lower.y() + y, lower.z() + z); 96 | 97 | if lake.get(volume(x, y, z)) { 98 | blocks.set(at, &liquid); 99 | } 100 | } 101 | 102 | for y in lake.surface..8 { 103 | let at = QuadPosition::new(lower.x() + x, lower.y() + y, lower.z() + z); 104 | 105 | if lake.get(volume(x, y, z)) { 106 | blocks.set(at, &carve); 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | // TODO: grow_grass, solidify_border 114 | } 115 | 116 | pub struct LakeSettings { 117 | pub surface: u8, 118 | pub min_blobs: u32, 119 | pub add_blobs: u32 120 | } 121 | 122 | impl Default for LakeSettings { 123 | fn default() -> Self { 124 | LakeSettings { 125 | surface: 4, 126 | min_blobs: 4, 127 | add_blobs: 3 128 | } 129 | } 130 | } 131 | 132 | pub struct LakeBlobs<'r> { 133 | remaining_blobs: u32, 134 | rng: &'r mut Random 135 | } 136 | 137 | impl<'r> LakeBlobs<'r> { 138 | pub fn new(rng: &'r mut Random, settings: &LakeSettings) -> Self { 139 | let remaining_blobs = settings.min_blobs + rng.next_u32_bound(settings.add_blobs + 1); 140 | 141 | LakeBlobs { 142 | remaining_blobs, 143 | rng 144 | } 145 | } 146 | } 147 | 148 | impl<'r> Iterator for LakeBlobs<'r> { 149 | type Item = Blob; 150 | 151 | fn next(&mut self) -> Option { 152 | if self.remaining_blobs <= 0 { 153 | return None; 154 | } 155 | 156 | self.remaining_blobs -= 1; 157 | 158 | let diameter = ( 159 | self.rng.next_f64() * 6.0 + 3.0, 160 | self.rng.next_f64() * 4.0 + 2.0, 161 | self.rng.next_f64() * 6.0 + 3.0 162 | ); 163 | 164 | let radius = ( 165 | diameter.0 / 2.0, 166 | diameter.1 / 2.0, 167 | diameter.2 / 2.0 168 | ); 169 | 170 | let center = ( 171 | self.rng.next_f64() * (16.0 - diameter.0 - 2.0) + 1.0 + radius.0, 172 | self.rng.next_f64() * ( 8.0 - diameter.1 - 4.0) + 2.0 + radius.1, 173 | self.rng.next_f64() * (16.0 - diameter.2 - 2.0) + 1.0 + radius.2 174 | ); 175 | 176 | Some(Blob { radius, center }) 177 | } 178 | } 179 | 180 | #[derive(Debug)] 181 | pub struct Blob { 182 | pub center: (f64, f64, f64), 183 | pub radius: (f64, f64, f64) 184 | } 185 | 186 | pub fn volume(x: u8, y: u8, z: u8) -> ChunkPosition { 187 | ChunkPosition::new(x, y % 8, z) 188 | } 189 | 190 | pub fn border(x: u8, y: u8, z: u8) -> ChunkPosition { 191 | ChunkPosition::new(x, (y % 8) + 8, z) 192 | } 193 | 194 | /// Uses a ChunkMask to store both the volume and the border blocks. 195 | /// Lakes are 16x8x16. A ChunkMask is 16x16x16. 196 | /// For compactness, these two masks are stacked on top of each other. 197 | pub struct Lake { 198 | shape: ChunkMask, 199 | surface: u8 200 | } 201 | 202 | impl Lake { 203 | pub fn new(surface: u8) -> Self { 204 | Lake { 205 | shape: ChunkMask::default(), 206 | surface 207 | } 208 | } 209 | 210 | pub fn clear(&mut self) { 211 | self.shape.fill(false) 212 | } 213 | 214 | pub fn set_or(&mut self, at: ChunkPosition, value: bool) { 215 | use vocs::mask::Mask; 216 | self.shape.set_or(at, value) 217 | } 218 | 219 | pub fn set(&mut self, at: ChunkPosition, value: bool) { 220 | self.shape.set(at, value) 221 | } 222 | 223 | pub fn get(&self, at: ChunkPosition) -> bool { 224 | self.shape[at] 225 | } 226 | 227 | pub fn fill(&mut self, blobs: LakeBlobs) { 228 | for blob in blobs { 229 | self.add_blob(blob); 230 | } 231 | } 232 | 233 | pub fn add_blob(&mut self, blob: Blob) { 234 | // TODO: Reduce size of possible bounding box. 235 | for x in 1..15 { 236 | for y in 1..7 { 237 | for z in 1..15 { 238 | let axis_distances = ( 239 | (x as f64 - blob.center.0) / blob.radius.0, 240 | (y as f64 - blob.center.1) / blob.radius.1, 241 | (z as f64 - blob.center.2) / blob.radius.2, 242 | ); 243 | 244 | let distance_squared = 245 | axis_distances.0 * axis_distances.0 + 246 | axis_distances.1 * axis_distances.1 + 247 | axis_distances.2 * axis_distances.2; 248 | 249 | self.set_or(volume(x, y, z), distance_squared < 1.0); 250 | } 251 | } 252 | } 253 | } 254 | 255 | pub fn update_border(&mut self) { 256 | // Main volume 257 | for x in 1..15 { 258 | for y in 1..7 { 259 | for z in 1..15 { 260 | let is_border = !self.get(volume(x, y, z)) && ( 261 | self.get(volume(x + 1, y, z )) || 262 | self.get(volume(x - 1, y, z )) || 263 | self.get(volume(x, y + 1, z )) || 264 | self.get(volume(x, y - 1, z )) || 265 | self.get(volume(x, y, z + 1)) || 266 | self.get(volume(x, y, z - 1)) 267 | ); 268 | 269 | self.set(border(x, y, z), is_border); 270 | } 271 | } 272 | } 273 | 274 | // Top and bottom face 275 | for x in 1..15 { 276 | for z in 1..15 { 277 | let bottom = self.get(volume(x, 1, z)); 278 | let top = self.get(volume(x, 7 - 1, z)); 279 | 280 | self.set(border(x, 0, z), bottom); 281 | self.set(border(x, 7, z), top ); 282 | } 283 | } 284 | 285 | // Z=0 / Z=Max faces 286 | for x in 1..15 { 287 | for y in 1..7 { 288 | let min = self.get(volume(x, y, 1 )); 289 | let max = self.get(volume(x, y, 15 - 1)); 290 | 291 | self.set(border(x, y, 0), min); 292 | self.set(border(x, y, 15), max); 293 | } 294 | } 295 | 296 | // X=0 / X=Max faces 297 | for z in 1..15 { 298 | for y in 1..7 { 299 | let min = self.get(volume(1, y, z)); 300 | let max = self.get(volume(15 - 1, y, z)); 301 | 302 | self.set(border(0, y, z), min); 303 | self.set(border(15, y, z), max); 304 | } 305 | } 306 | 307 | // Skip the edge/corner cases (literally) as they cannot possibly fulfill any of the criteria. 308 | // TODO: Not clearing these may lead to corruption. 309 | } 310 | } -------------------------------------------------------------------------------- /src/decorator/large_tree/line.rs: -------------------------------------------------------------------------------- 1 | use vocs::position::{QuadPosition, Dir, Axis}; 2 | use vocs::view::{QuadBlocks, QuadAssociation}; 3 | use std::cmp; 4 | 5 | // TODO: This should be close enough, but is unverified. 6 | 7 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] 8 | pub struct Line { 9 | pub from: QuadPosition, 10 | pub to: QuadPosition 11 | } 12 | 13 | impl Line { 14 | /// Offset that needs to be applied to `from` to get `to`. 15 | pub fn offset(&self) -> (i8, i8, i8) { 16 | ( 17 | (self.to.x() as i8) - (self.from.x() as i8), 18 | (self.to.y() as i8) - (self.from.y() as i8), 19 | (self.to.z() as i8) - (self.from.z() as i8) 20 | ) 21 | } 22 | 23 | pub fn trace(&self) -> LineTracer { 24 | let diff = self.offset(); 25 | 26 | let max = cmp::max ( 27 | diff.0.abs(), 28 | cmp::max ( 29 | diff.1.abs(), 30 | diff.2.abs() 31 | ) 32 | ); 33 | 34 | let equal = (diff.0 == max, diff.1 == max, diff.2 == max); 35 | let mask = (equal.0 as u8) | ((equal.1 as u8) << 1) | ((equal.2 as u8) << 2); 36 | 37 | let axis = match mask.trailing_zeros() & 3 { 38 | 0 => Axis::X, 39 | 1 => Axis::Y, 40 | 2 => Axis::Z, 41 | _ => unreachable!() 42 | }; 43 | 44 | LineTracer { 45 | steps: max as u32, 46 | iterations: 0, 47 | velocity: ( 48 | (diff.0 as f64) / (max as f64), 49 | (diff.1 as f64) / (max as f64), 50 | (diff.2 as f64) / (max as f64) 51 | ), 52 | position: ( 53 | self.from.x() as f64, 54 | self.from.y() as f64, 55 | self.from.z() as f64 56 | ), 57 | direction: if max > 0 { axis.plus() } else { axis.minus() } 58 | } 59 | } 60 | 61 | pub fn draw(&self, blocks: &mut QuadBlocks, association: &mut QuadAssociation) { 62 | for position in self.trace() { 63 | blocks.set(position, association); 64 | } 65 | } 66 | } 67 | 68 | pub struct LineTracer { 69 | velocity: (f64, f64, f64), 70 | position: (f64, f64, f64), 71 | steps: u32, 72 | iterations: u32, 73 | direction: Dir 74 | } 75 | 76 | impl Iterator for LineTracer { 77 | type Item = QuadPosition; 78 | 79 | fn next(&mut self) -> Option { 80 | if self.iterations >= self.steps { 81 | return None; 82 | } 83 | 84 | let mut position = [ 85 | self.position.0 + self.velocity.0, 86 | self.position.1 + self.velocity.1, 87 | self.position.2 + self.velocity.2 88 | ]; 89 | 90 | let primary_offset = (self.iterations as i32) * ( if self.direction.plus() { 1 } else { -1 }); 91 | 92 | position[self.direction.axis() as usize] = self.position.0 + (primary_offset as f64); 93 | 94 | let position = QuadPosition::new( 95 | (position[0] + 0.5).floor() as u8, 96 | (position[1] + 0.5).floor() as u8, 97 | (position[2] + 0.5).floor() as u8 98 | ); 99 | 100 | self.iterations += 1; 101 | 102 | Some(position) 103 | } 104 | } -------------------------------------------------------------------------------- /src/decorator/large_tree/mod.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use std::cmp::min; 3 | 4 | mod line; 5 | 6 | const TAU: f64 = 2.0 * 3.14159; 7 | 8 | /// A foliage cluster. "Balloon" oaks in Minecraft are simply a large tree generating a single foliage cluster at the top of the very short trunk. 9 | #[derive(Debug)] 10 | pub struct Foilage { 11 | /// Location of the leaf cluster, and the endpoint of the branch line. The Y is at the bottom of the cluster. 12 | cluster: (i32, i32, i32), 13 | /// Y coordinate of the block above the top of this foliage cluster. 14 | foliage_top_y: i32, 15 | /// Y coordinate of the start of the branch line. The X and Z coordinate are always equal to the orgin of the tree. 16 | branch_y: i32 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct LargeTreeSettings { 21 | /// Makes the branches shorter or longer than the default. 22 | branch_scale: f64, 23 | /// For every 1 block the branch is long, this multiplier determines how many blocks it will go down on the trunk. 24 | branch_slope: f64, 25 | /// Default height of the leaves of the foliage clusters, from top to bottom. 26 | /// When added to the Y of the cluster, represents the coordinate of the top layer of the leaf cluster. 27 | foliage_height: i32, 28 | /// Factor in determining the amount of foliage clusters generated on each Y level of the big tree. 29 | foliage_density: f64, 30 | /// Added to the foliage_per_y value before conversion to i32. 31 | base_foliage_per_y: f64, 32 | /// How tall the trunk is in comparison to the total height. Should be 0.0 to 1.0. 33 | trunk_height_scale: f64, 34 | /// Minimum height of the tree. 35 | min_height: i32, 36 | /// Maximum height that can be added to the minimum. Max height of the tree = min_height + add_height. 37 | add_height: i32 38 | } 39 | 40 | impl Default for LargeTreeSettings { 41 | fn default() -> Self { 42 | LargeTreeSettings { 43 | branch_scale: 1.0, 44 | branch_slope: 0.381, 45 | foliage_height: 4, 46 | foliage_density: 1.0, 47 | base_foliage_per_y: 1.382, 48 | trunk_height_scale: 0.618, 49 | min_height: 5, 50 | add_height: 11 51 | } 52 | } 53 | } 54 | 55 | impl LargeTreeSettings { 56 | pub fn tree(&self, orgin: (i32, i32, i32), rng: &mut Random, preset_height: Option, max_height: i32) -> LargeTree { 57 | let height = min(preset_height.unwrap_or_else(|| self.min_height + rng.next_i32_bound(self.add_height + 1)), max_height); 58 | let height_f32 = height as f32; 59 | let height_f64 = height as f64; 60 | 61 | let spread_center = height_f32 / 2.0; 62 | let spread_center_sq = spread_center.powi(2); 63 | 64 | let trunk_height = min((height_f64 * self.trunk_height_scale) as i32, height - 1); 65 | let trunk_top = orgin.1 + trunk_height; 66 | 67 | let foliage_per_y = ((self.foliage_density * height_f64 / 13.0).powi(2) + self.base_foliage_per_y).max(1.0) as i32; 68 | let foliage_max_y = orgin.1 + height - self.foliage_height; 69 | let foliage_min_y = orgin.1 + (height_f32 * 0.3).ceil() as i32 - 1; 70 | 71 | LargeTree { 72 | orgin, 73 | spread_center, 74 | spread_center_sq, 75 | height, 76 | trunk_top, 77 | foliage_per_y, 78 | foliage_max_y, 79 | foliage_min_y, 80 | branch_scale: self.branch_scale, 81 | branch_slope: self.branch_slope, 82 | foliage_height: self.foliage_height 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug)] 88 | pub struct LargeTree { 89 | /// Coordinate pointing to the bottom log of the trunk. 90 | pub orgin: (i32, i32, i32), 91 | /// The place where most of the foliage should be centered around. At this point, the foliage should spread out the most. 92 | /// If outside the range of the foliage range, then it will instead spread out the foliage the most at the bottom/top. 93 | pub spread_center: f32, 94 | /// A squared version of the spread_center. 95 | pub spread_center_sq: f32, 96 | /// Maximum length from bottom to top of the tree. 97 | pub height: i32, 98 | /// Y coordinate of the uppermost block of the trunk. 99 | pub trunk_top: i32, 100 | /// Foilage per Y layer. At least 1. 101 | pub foliage_per_y: i32, 102 | /// Maximum Y value for foliage layers to spawn (Inclusive). 103 | /// A single foliage cluster is found at this value, centered on the trunk. 104 | /// For the purposes of iterating through the generated foliage layers, this can be considered Exclusive. 105 | pub foliage_max_y: i32, 106 | /// Minimum Y value for foliage layers to spawn (Inclusive) 107 | pub foliage_min_y: i32, 108 | /// Makes the branches shorter or longer than the default. 109 | pub branch_scale: f64, 110 | /// For every 1 block the branch is long, this multiplier determines how many blocks it will go down on the trunk. 111 | pub branch_slope: f64, 112 | /// Default height of the leaves of the foliage clusters, from top to bottom. 113 | pub foliage_height: i32 114 | } 115 | 116 | impl LargeTree { 117 | /// Computes the spread at a given Y value. This is computed using the spread_center and spread_center_sq. 118 | pub fn spread(&self, y: i32) -> f64 { 119 | let distance_from_center = self.spread_center - (y - self.orgin.1 + 1) as f32; 120 | ((self.spread_center_sq - distance_from_center.powi(2)).sqrt() * 0.5) as f64 121 | } 122 | 123 | // TODO: Replace this with an iterator implementation? 124 | /// Gets the foliage at a given Y level. The caller is responsible for ordering the calls, managing the Y value, and creating the random number generator. 125 | pub fn foliage(&self, y: i32, spread: f64, rng: &mut Random) -> Foilage { 126 | let branch_factor = self.branch_scale * spread * (rng.next_f32() as f64 + 0.328); 127 | let angle = (rng.next_f32() as f64) * TAU; 128 | 129 | let cluster = ( 130 | (branch_factor * angle.sin() + (self.orgin.0 as f64) + 0.5).floor() as i32, 131 | y, 132 | (branch_factor * angle.cos() + (self.orgin.2 as f64) + 0.5).floor() as i32 133 | ); 134 | 135 | let foliage_top_y = y + self.foliage_height; 136 | 137 | let trunk_distance = ( 138 | (self.orgin.0 - cluster.0) as f64, 139 | (self.orgin.2 - cluster.2) as f64 140 | ); 141 | 142 | let branch_length = (trunk_distance.0 * trunk_distance.0 + trunk_distance.1 * trunk_distance.1).sqrt(); 143 | 144 | // Determine how low to place the branch start Y, controlled by branch_slope. Longer branches have lower starts on the trunk. 145 | let slope = branch_length * self.branch_slope; 146 | 147 | // Make sure the starting Y value for the branch is not above the trunk. 148 | // Interestingly, it does not check whether the branch starts below the trunk. 149 | let branch_y = ((y as f64) - slope).min(self.trunk_top as f64) as i32; 150 | 151 | // TODO: CheckLine from Cluster to TopY, and from BranchY to Cluster. 152 | Foilage { cluster, foliage_top_y, branch_y } 153 | } 154 | } -------------------------------------------------------------------------------- /src/decorator/mod.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use vocs::view::QuadMut; 3 | use vocs::position::{ColumnPosition, QuadPosition}; 4 | use vocs::indexed::Target; 5 | use distribution::Distribution; 6 | use serde_json; 7 | 8 | pub mod dungeon; 9 | pub mod vein; 10 | pub mod clump; 11 | pub mod large_tree; 12 | pub mod lake; 13 | pub mod tree; 14 | pub mod exposed; 15 | 16 | // TODO: MultiDispatcher 17 | 18 | #[derive(Debug, Eq, PartialEq, Copy, Clone)] 19 | pub struct Spilled(pub QuadPosition); 20 | pub type Result = ::std::result::Result<(), Spilled>; 21 | 22 | pub struct Dispatcher where H: Distribution, R: Distribution, B: Target { 23 | pub height_distribution: H, 24 | pub rarity: R, 25 | pub decorator: Box> 26 | } 27 | 28 | impl Dispatcher where H: Distribution, R: Distribution, B: Target { 29 | pub fn generate(&self, quad: &mut QuadMut, rng: &mut Random) -> Result { 30 | for _ in 0..self.rarity.next(rng) { 31 | let at = ColumnPosition::new( 32 | rng.next_u32_bound(16) as u8, 33 | self.height_distribution.next(rng) as u8, 34 | rng.next_u32_bound(16) as u8 35 | ); 36 | 37 | self.decorator.generate(quad, rng, QuadPosition::from_centered(at))?; 38 | } 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | pub trait Decorator where B: Target { 45 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result; 46 | } 47 | 48 | pub trait DecoratorFactory where B: Target { 49 | fn configure(&self, config: serde_json::Value) -> serde_json::Result>>; 50 | } -------------------------------------------------------------------------------- /src/decorator/tree.rs: -------------------------------------------------------------------------------- 1 | use matcher::BlockMatcher; 2 | use vocs::indexed::Target; 3 | use vocs::view::QuadMut; 4 | use vocs::position::{QuadPosition, Offset, dir}; 5 | use decorator::{Decorator, Result}; 6 | use java_rand::Random; 7 | 8 | pub struct TreeDecorator where B: Target { 9 | blocks: TreeBlocks, 10 | settings: TreeSettings 11 | } 12 | 13 | impl Decorator for TreeDecorator where B: Target { 14 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 15 | let tree = self.settings.tree(rng, position); 16 | 17 | if tree.leaves_max_y > 128 { 18 | return Ok(()); 19 | } 20 | 21 | let below = match position.offset(dir::Down) { 22 | Some(below) => below, 23 | None => return Ok(()) 24 | }; 25 | 26 | if !self.blocks.soil.matches(quad.get(below)) { 27 | return Ok(()); 28 | } 29 | 30 | // TODO: Check bounding box 31 | 32 | quad.set_immediate(below, &self.blocks.new_soil); 33 | 34 | quad.ensure_available(self.blocks.log.clone()); 35 | quad.ensure_available(self.blocks.foliage.clone()); 36 | 37 | let (mut blocks, palette) = quad.freeze_palette(); 38 | 39 | let log = palette.reverse_lookup(&self.blocks.log).unwrap(); 40 | let foliage = palette.reverse_lookup(&self.blocks.foliage).unwrap(); 41 | 42 | for y in tree.leaves_min_y..tree.leaves_max_y { 43 | let radius = tree.foliage_radius(y) as i32; 44 | 45 | for z_offset in -radius..radius { 46 | for x_offset in -radius..radius { 47 | if z_offset.abs() != radius || x_offset.abs() != radius || rng.next_u32_bound(self.settings.foliage_corner_chance) != 0 && y < tree.trunk_top { 48 | 49 | let position = match position.offset((x_offset as i8, 0, z_offset as i8)) { 50 | Some(position) => position, 51 | None => continue 52 | }; 53 | 54 | let position = QuadPosition::new(position.x(), y as u8, position.z()); 55 | 56 | blocks.set(position, &foliage); 57 | } 58 | } 59 | } 60 | } 61 | 62 | for y in position.y()..(tree.trunk_top as u8) { 63 | let position = QuadPosition::new(position.x(), y, position.z()); 64 | 65 | if self.blocks.replace.matches(blocks.get(position, &palette)) { 66 | blocks.set(position, &log); 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | } 73 | 74 | impl Default for TreeDecorator { 75 | fn default() -> Self { 76 | TreeDecorator { 77 | blocks: TreeBlocks::default(), 78 | settings: TreeSettings::default() 79 | } 80 | } 81 | } 82 | 83 | struct TreeBlocks where B: Target { 84 | log: B, 85 | foliage: B, 86 | replace: BlockMatcher, 87 | soil: BlockMatcher, 88 | new_soil: B 89 | } 90 | 91 | impl Default for TreeBlocks { 92 | fn default() -> Self { 93 | TreeBlocks { 94 | log: 17*16, 95 | foliage: 18*16, 96 | replace: BlockMatcher::include([0*16, 18*16].iter()), 97 | soil: BlockMatcher::include([2*16, 3*16].iter()), 98 | new_soil: 3*16 99 | } 100 | } 101 | } 102 | 103 | struct TreeSettings { 104 | min_trunk_height: u32, 105 | add_trunk_height: u32, 106 | foliage_layers_on_trunk: u32, 107 | foliage_layers_off_trunk: u32, 108 | foliage_slope: u32, 109 | foliage_radius_base: u32, 110 | foliage_corner_chance: u32 111 | } 112 | 113 | impl TreeSettings { 114 | fn tree(&self, rng: &mut Random, orgin: QuadPosition) -> Tree { 115 | let trunk_height = self.min_trunk_height + rng.next_u32_bound(self.add_trunk_height + 1); 116 | let trunk_top = (orgin.y() as u32) + trunk_height; 117 | 118 | Tree { 119 | orgin, 120 | full_height: trunk_height + self.foliage_layers_off_trunk, 121 | trunk_height, 122 | trunk_top, 123 | leaves_min_y: trunk_top - self.foliage_layers_on_trunk, 124 | leaves_max_y: trunk_top + self.foliage_layers_off_trunk, 125 | leaves_slope: self.foliage_slope, 126 | leaves_radius_base: self.foliage_radius_base 127 | } 128 | } 129 | } 130 | 131 | impl Default for TreeSettings { 132 | fn default() -> Self { 133 | TreeSettings { 134 | min_trunk_height: 4, 135 | add_trunk_height: 2, 136 | foliage_layers_on_trunk: 3, 137 | foliage_layers_off_trunk: 1, 138 | foliage_slope: 2, 139 | foliage_radius_base: 1, 140 | foliage_corner_chance: 2 141 | } 142 | } 143 | } 144 | 145 | struct Tree { 146 | orgin: QuadPosition, 147 | /// Trunk Height + number of foliage layers above the trunk 148 | full_height: u32, 149 | /// Height of the trunk. Can be considered the length of the line that defines the trunk. 150 | trunk_height: u32, 151 | /// Coordinates of the block above the last block of the trunk. 152 | trunk_top: u32, 153 | /// Minimum Y value for foliage layers (Inclusive). 154 | leaves_min_y: u32, 155 | /// Maximum Y value for foliage layers (Exclusive). 156 | leaves_max_y: u32, 157 | /// Slope value of the radius for each layer. Flattens or widens the tree. 158 | leaves_slope: u32, 159 | /// Base value for the radius. 160 | leaves_radius_base: u32 161 | } 162 | 163 | impl Tree { 164 | /// Radius of the foliage at a given location. 0 is just the trunk. 165 | fn foliage_radius(&self, y: u32) -> u32 { 166 | (self.leaves_radius_base + self.trunk_top - y) / self.leaves_slope 167 | } 168 | 169 | /// Radius of the bounding box for the foliage at a given level. 0 for just checking the trunk. 170 | fn bounding_radius(&self, y: u32) -> u32 { 171 | if y == (self.orgin.y() as u32) { 172 | 0 173 | } else if y > self.trunk_top { 174 | 2 175 | } else { 176 | 1 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /src/decorator/vein.rs: -------------------------------------------------------------------------------- 1 | use vocs::indexed::Target; 2 | use matcher::BlockMatcher; 3 | use vocs::position::{QuadPosition, Offset}; 4 | use vocs::view::QuadMut; 5 | use super::{Decorator, DecoratorFactory, Result}; 6 | use java_rand::Random; 7 | use trig; 8 | use serde_json; 9 | 10 | // TODO: Is this really 3.141593? 11 | /// For when you don't have the time to type out all the digits of π or Math.PI. 12 | const NOTCHIAN_PI: f32 = 3.1415927; 13 | 14 | /// The radius is in the range `[0.0, 0.5+size/RADIUS_DIVISOR]` 15 | const RADIUS_DIVISOR: f64 = 16.0; 16 | /// The length is `size/LENGTH_DIVISOR` 17 | const LENGTH_DIVISOR: f32 = 8.0; 18 | 19 | #[derive(Default)] 20 | pub struct VeinDecoratorFactory(::std::marker::PhantomData); 21 | impl DecoratorFactory for VeinDecoratorFactory where B: 'static + Target + ::serde::Deserialize { 22 | fn configure(&self, config: serde_json::Value) -> serde_json::Result>> { 23 | Ok(Box::new(serde_json::from_value::>(config)?)) 24 | } 25 | } 26 | 27 | #[derive(Default)] 28 | pub struct SeasideVeinDecoratorFactory(::std::marker::PhantomData); 29 | impl DecoratorFactory for SeasideVeinDecoratorFactory where B: 'static + Target + ::serde::Deserialize { 30 | fn configure(&self, config: serde_json::Value) -> serde_json::Result>> { 31 | Ok(Box::new(serde_json::from_value::>(config)?)) 32 | } 33 | } 34 | 35 | #[derive(Serialize, Deserialize, Debug, Clone)] 36 | pub struct SeasideVeinDecorator where B: Target { 37 | pub vein: VeinDecorator, 38 | pub ocean: BlockMatcher 39 | } 40 | 41 | impl Decorator for SeasideVeinDecorator where B: Target { 42 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 43 | if !self.ocean.matches(quad.get(position.offset((-8, 0, -8)).unwrap())) { 44 | return Ok(()); 45 | } 46 | 47 | self.vein.generate(quad, rng, position) 48 | } 49 | } 50 | 51 | #[derive(Serialize, Deserialize, Debug, Clone)] 52 | pub struct VeinDecorator where B: Target { 53 | pub blocks: VeinBlocks, 54 | pub size: u32 55 | } 56 | 57 | impl Decorator for VeinDecorator where B: Target { 58 | fn generate(&self, quad: &mut QuadMut, rng: &mut Random, position: QuadPosition) -> Result { 59 | let vein = Vein::create(self.size, (position.x() as i32, position.y() as i32, position.z() as i32), rng); 60 | self.blocks.generate(&vein, quad, rng) 61 | } 62 | } 63 | 64 | #[derive(Serialize, Deserialize, Debug, Clone)] 65 | pub struct VeinBlocks where B: Target { 66 | pub replace: BlockMatcher, 67 | pub block: B 68 | } 69 | 70 | impl VeinBlocks where B: Target { 71 | pub fn generate(&self, vein: &Vein, quad: &mut QuadMut, rng: &mut Random) -> Result { 72 | quad.ensure_available(self.block.clone()); 73 | 74 | let (mut blocks, palette) = quad.freeze_palette(); 75 | 76 | let block = palette.reverse_lookup(&self.block).unwrap(); 77 | 78 | for index in 0..(vein.size+1) { 79 | let blob = vein.blob(index, rng); 80 | 81 | for y in blob.lower.1..(blob.upper.1 + 1) { 82 | for z in blob.lower.2..(blob.upper.2 + 1) { 83 | for x in blob.lower.2..(blob.upper.2 + 1) { 84 | let at = QuadPosition::new(x as u8, y as u8, z as u8); // TODO 85 | 86 | if blob.distance_squared((x, y, z)) < 1.0 && self.replace.matches(blocks.get(at, &palette)) { 87 | blocks.set(at, &block); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | Ok(()) 95 | } 96 | } 97 | 98 | #[derive(Debug)] 99 | pub struct Vein { 100 | /// Size of the vein. Controls iterations, radius of the spheroids, and length of the line. 101 | size: u32, 102 | /// Size as a f64, to avoid excessive casting. 103 | size_f64: f64, 104 | /// Size as a f32, to avoid excessive casting. 105 | size_f32: f32, 106 | /// Start point of the line, but not neccesarily the minimum on the Y axis. 107 | from: (f64, f64, f64), 108 | /// End point of the line, but not neccesarily the maximum on the Y axis. 109 | to: (f64, f64, f64) 110 | } 111 | 112 | impl Vein { 113 | pub fn create(size: u32, base: (i32, i32, i32), rng: &mut Random) -> Self { 114 | let size_f32 = size as f32; 115 | 116 | let angle = rng.next_f32() * NOTCHIAN_PI; 117 | let x_size = trig::sin(angle) * size_f32 / LENGTH_DIVISOR; 118 | let z_size = trig::cos(angle) * size_f32 / LENGTH_DIVISOR; 119 | 120 | let from = ( 121 | (base.0 as f32 + x_size) as f64, 122 | (base.1 + 2 + rng.next_i32_bound(3)) as f64, 123 | (base.2 as f32 + z_size) as f64 124 | ); 125 | 126 | let to = ( 127 | (base.0 as f32 - x_size) as f64, 128 | (base.1 + 2 + rng.next_i32_bound(3)) as f64, 129 | (base.2 as f32 - z_size) as f64 130 | ); 131 | 132 | Vein { size, size_f64: size as f64, size_f32, from, to } 133 | } 134 | 135 | pub fn blob(&self, index: u32, rng: &mut Random) -> Blob { 136 | let index_f64 = index as f64; 137 | let index_f32 = index as f32; 138 | 139 | let center = ( 140 | lerp_fraction(index_f64, self.size_f64, self.from.0, self.to.0), 141 | lerp_fraction(index_f64, self.size_f64, self.from.1, self.to.1), 142 | lerp_fraction(index_f64, self.size_f64, self.from.2, self.to.2) 143 | ); 144 | 145 | let radius_multiplier = rng.next_f64() * self.size_f64 / RADIUS_DIVISOR; 146 | 147 | // The sin function varies the diameter over time, so that larger diameters are closer to the center. 148 | let diameter = (trig::sin(index_f32 * NOTCHIAN_PI / self.size_f32) + 1.0f32) as f64 * radius_multiplier + 1.0; 149 | let radius = diameter / 2.0; 150 | 151 | // TODO: i32 casts can overflow. 152 | let lower = ( 153 | (center.0 - radius).floor() as i32, 154 | (center.1 - radius).floor() as i32, 155 | (center.2 - radius).floor() as i32 156 | ); 157 | 158 | let upper = ( 159 | (center.0 + radius).floor() as i32, 160 | (center.1 + radius).floor() as i32, 161 | (center.2 + radius).floor() as i32 162 | ); 163 | 164 | Blob { center, radius, lower, upper } 165 | } 166 | } 167 | 168 | #[derive(Debug)] 169 | pub struct Blob { 170 | center: (f64, f64, f64), 171 | radius: f64, 172 | lower: (i32, i32, i32), 173 | upper: (i32, i32, i32) 174 | } 175 | 176 | impl Blob { 177 | pub fn distance_squared(&self, at: (i32, i32, i32)) -> f64 { 178 | let dist_x_sq = ((at.0 as f64 + 0.5 - self.center.0) / self.radius).powi(2); 179 | let dist_y_sq = ((at.1 as f64 + 0.5 - self.center.1) / self.radius).powi(2); 180 | let dist_z_sq = ((at.2 as f64 + 0.5 - self.center.2) / self.radius).powi(2); 181 | 182 | dist_x_sq + dist_y_sq + dist_z_sq 183 | } 184 | } 185 | 186 | /// Preforms linear interpolation using a fraction expressed as `index/size`. 187 | /// Used instead of standard lerp() to preserve operation order. 188 | fn lerp_fraction(index: f64, size: f64, a: f64, b: f64) -> f64 { 189 | a + (b - a) * index / size 190 | } -------------------------------------------------------------------------------- /src/distribution.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | 3 | /// A random distribution. 4 | pub trait Distribution { 5 | fn next(&self, rng: &mut Random) -> u32; 6 | } 7 | 8 | fn default_chance() -> u32 { 9 | 1 10 | } 11 | 12 | fn default_ordering() -> ChanceOrdering { 13 | ChanceOrdering::AlwaysGeneratePayload 14 | } 15 | 16 | #[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)] 17 | pub enum ChanceOrdering { 18 | AlwaysGeneratePayload, 19 | CheckChanceBeforePayload 20 | } 21 | 22 | #[derive(Debug, Serialize, Deserialize)] 23 | pub struct Chance where D: Distribution { 24 | /// Chance for this distribution to return its value instead of 0. 25 | /// Represented as probability = 1 / chance. 26 | /// A chance of "1" does not call the Chance RNG, and acts as if it passed. 27 | #[serde(default = "default_chance")] 28 | pub chance: u32, 29 | #[serde(default = "default_ordering")] 30 | pub ordering: ChanceOrdering, 31 | pub base: D 32 | } 33 | 34 | impl Distribution for Chance where D: Distribution { 35 | fn next(&self, rng: &mut Random) -> u32 { 36 | match self.ordering { 37 | ChanceOrdering::AlwaysGeneratePayload => { 38 | let payload = self.base.next(rng); 39 | 40 | if self.chance <= 1 { 41 | payload 42 | } else if rng.next_u32_bound(self.chance) == 0 { 43 | payload 44 | } else { 45 | 0 46 | } 47 | }, 48 | ChanceOrdering::CheckChanceBeforePayload => { 49 | if self.chance <= 1 { 50 | self.base.next(rng) 51 | } else if rng.next_u32_bound(self.chance) == 0 { 52 | self.base.next(rng) 53 | } else { 54 | 0 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | /// Baseline distribution. This should be general enough to fit most use cases. 62 | #[derive(Debug, Serialize, Deserialize)] 63 | #[serde(tag = "kind")] 64 | pub enum Baseline { 65 | Constant { value: u32 }, 66 | Linear(Linear), 67 | Packed2(Packed2), 68 | Packed3(Packed3), 69 | Centered(Centered) 70 | } 71 | 72 | impl Distribution for Baseline { 73 | fn next(&self, rng: &mut Random) -> u32 { 74 | match *self { 75 | Baseline::Constant { value } => value, 76 | Baseline::Linear(ref linear) => linear.next(rng), 77 | Baseline::Packed2(ref packed2) => packed2.next(rng), 78 | Baseline::Packed3(ref packed3) => packed3.next(rng), 79 | Baseline::Centered(ref centered) => centered.next(rng) 80 | } 81 | } 82 | } 83 | 84 | impl Distribution for u32 { 85 | fn next(&self, _: &mut Random) -> u32 { 86 | *self 87 | } 88 | } 89 | 90 | /// Plain old linear distribution, with a minimum and maximum. 91 | #[derive(Debug, Serialize, Deserialize)] 92 | pub struct Linear { 93 | pub min: u32, 94 | pub max: u32 95 | } 96 | 97 | impl Distribution for Linear { 98 | fn next(&self, rng: &mut Random) -> u32 { 99 | self.min + rng.next_u32_bound(self.max - self.min + 1) 100 | } 101 | } 102 | 103 | /// Distribution that packs more values to the minimum value. This is based on 2 RNG iterations. 104 | #[derive(Debug, Serialize, Deserialize)] 105 | pub struct Packed2 { 106 | pub min: u32, 107 | /// Minimum height passed to the second RNG call (the linear call). 108 | pub linear_start: u32, 109 | pub max: u32 110 | } 111 | 112 | impl Distribution for Packed2 { 113 | fn next(&self, rng: &mut Random) -> u32 { 114 | let initial = rng.next_u32_bound(self.max - self.linear_start + 2); 115 | 116 | self.min + rng.next_u32_bound(initial + self.linear_start - self.min) 117 | } 118 | } 119 | 120 | /// Distribution that packs more values to the minimum value. This is based on 3 RNG iterations, and is more extreme. 121 | /// The average is around `(max+1)/8 - 1`, a simplified form of `(max+1)/2³ - 1`. 122 | #[derive(Debug, Serialize, Deserialize)] 123 | pub struct Packed3 { 124 | pub max: u32 125 | } 126 | 127 | impl Distribution for Packed3 { 128 | fn next(&self, rng: &mut Random) -> u32 { 129 | let result = rng.next_u32_bound(self.max + 1); 130 | let result = rng.next_u32_bound(result + 1); 131 | rng.next_u32_bound(result + 1) 132 | } 133 | } 134 | 135 | /// Distribution centered around a certain point, with a maximum variance. 136 | #[derive(Debug, Serialize, Deserialize)] 137 | pub struct Centered { 138 | pub center: u32, 139 | pub radius: u32 140 | } 141 | 142 | impl Distribution for Centered { 143 | fn next(&self, rng: &mut Random) -> u32 { 144 | rng.next_u32_bound(self.radius) + rng.next_u32_bound(self.radius) + self.center - self.radius 145 | } 146 | } -------------------------------------------------------------------------------- /src/generator/mod.rs: -------------------------------------------------------------------------------- 1 | use vocs::indexed::Target; 2 | use vocs::view::ColumnMut; 3 | use vocs::position::GlobalColumnPosition; 4 | 5 | pub mod overworld_173; 6 | pub mod nether_173; 7 | pub mod sky_173; 8 | 9 | pub trait Pass where B: Target { 10 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition); 11 | } -------------------------------------------------------------------------------- /src/generator/nether_173.rs: -------------------------------------------------------------------------------- 1 | use vocs::indexed::Target; 2 | use vocs::position::{ColumnPosition,GlobalColumnPosition}; 3 | use vocs::view::ColumnMut; 4 | use generator::Pass; 5 | use noise_field::volume::{self, TriNoiseSource, TriNoiseSettings, trilinear128}; 6 | use cgmath::{Vector2, Vector3}; 7 | use java_rand::Random; 8 | 9 | const NOTCH_PI_F64: f64 = 3.1415926535897931; 10 | 11 | pub fn default_tri_settings() -> TriNoiseSettings { 12 | TriNoiseSettings { 13 | main_out_scale: 20.0, 14 | upper_out_scale: 512.0, 15 | lower_out_scale: 512.0, 16 | lower_scale: Vector3::new(684.412, 2053.236, 684.412 ), 17 | upper_scale: Vector3::new(684.412, 2053.236, 684.412 ), 18 | main_scale: Vector3::new(684.412 / 80.0, 2053.236 / 60.0, 684.412 / 80.0), 19 | y_size: 33 20 | } 21 | } 22 | 23 | pub fn passes(seed: u64, tri_settings: &TriNoiseSettings, blocks: ShapeBlocks, sea_coord: u8) -> ShapePass where B: Target { 24 | let mut rng = Random::new(seed); 25 | 26 | let tri = TriNoiseSource::new(&mut rng, tri_settings); 27 | 28 | ShapePass { 29 | blocks, 30 | tri, 31 | reduction: generate_reduction_table(17), 32 | sea_coord 33 | } 34 | } 35 | 36 | pub struct ShapeBlocks where B: Target { 37 | pub solid: B, 38 | pub air: B, 39 | pub ocean: B 40 | } 41 | 42 | impl Default for ShapeBlocks { 43 | fn default() -> Self { 44 | ShapeBlocks { 45 | solid: 87 * 16, 46 | air: 0 * 16, 47 | ocean: 11 * 16 48 | } 49 | } 50 | } 51 | 52 | pub struct ShapePass where B: Target { 53 | blocks: ShapeBlocks, 54 | tri: TriNoiseSource, 55 | reduction: Vec, 56 | sea_coord: u8 57 | } 58 | 59 | impl Pass for ShapePass where B: Target { 60 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition) { 61 | let offset = Vector2::new( 62 | (chunk.x() as f64) * 4.0, 63 | (chunk.z() as f64) * 4.0 64 | ); 65 | 66 | let mut field = [[[0f64; 5]; 17]; 5]; 67 | 68 | for x in 0..5 { 69 | for z in 0..5 { 70 | for y in 0..17 { 71 | let mut value = self.tri.sample(Vector3::new(offset.x + x as f64, y as f64, offset.y + z as f64), y); 72 | 73 | value -= self.reduction[y]; 74 | value = volume::reduce_upper(value, y as f64, 4.0, 10.0, 17.0); 75 | 76 | field[x][y][z] = value; 77 | } 78 | } 79 | } 80 | 81 | target.ensure_available(self.blocks.air.clone()); 82 | target.ensure_available(self.blocks.solid.clone()); 83 | target.ensure_available(self.blocks.ocean.clone()); 84 | 85 | let (mut blocks, palette) = target.freeze_palette(); 86 | 87 | let air = palette.reverse_lookup(&self.blocks.air).unwrap(); 88 | let solid = palette.reverse_lookup(&self.blocks.solid).unwrap(); 89 | let ocean = palette.reverse_lookup(&self.blocks.ocean).unwrap(); 90 | 91 | for i in 0..32768 { 92 | let position = ColumnPosition::from_yzx(i); 93 | let altitude = position.y(); 94 | 95 | let block = if trilinear128(&field, position) > 0.0 { 96 | &solid 97 | } else if altitude <= self.sea_coord { 98 | &ocean 99 | } else { 100 | &air 101 | }; 102 | 103 | blocks.set(position, block); 104 | } 105 | } 106 | } 107 | 108 | pub fn generate_reduction_table(y_size: usize) -> Vec { 109 | let mut data = Vec::with_capacity(y_size); 110 | let y_size_f64 = y_size as f64; 111 | 112 | for index in 0..y_size { 113 | let index_f64 = index as f64; 114 | 115 | let mut value = ((index_f64 * NOTCH_PI_F64 * 6.0) / y_size_f64).cos() * 2.0; 116 | 117 | value = volume::reduce_cubic(value, y_size_f64 - 1.0 - index_f64); 118 | value = volume::reduce_cubic(value, index_f64); 119 | 120 | data.push(value); 121 | } 122 | 123 | data 124 | } -------------------------------------------------------------------------------- /src/generator/overworld_173.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use noise::octaves::PerlinOctaves; 3 | use biome::climate::{ClimateSettings, ClimateSource}; 4 | use biome::source::BiomeSource; 5 | use biome::{Lookup, Surface}; 6 | use noise_field::height::{HeightSettings, HeightSource}; 7 | use noise_field::volume::{TriNoiseSettings, TriNoiseSource, FieldSettings, trilinear128}; 8 | use generator::Pass; 9 | use vocs::position::{ColumnPosition, LayerPosition, GlobalColumnPosition}; 10 | use vocs::indexed::Target; 11 | use vocs::view::{ColumnMut, ColumnBlocks, ColumnPalettes, ColumnAssociation}; 12 | use matcher::BlockMatcher; 13 | use sample::Sample; 14 | use cgmath::{Point2, Vector2, Vector3}; 15 | use noise_field::height::lerp_to_layer; 16 | 17 | pub struct Settings where B: Target { 18 | pub shape_blocks: ShapeBlocks, 19 | pub paint_blocks: PaintBlocks, 20 | pub tri: TriNoiseSettings, 21 | pub height: HeightSettings, 22 | pub field: FieldSettings, 23 | pub sea_coord: u8, 24 | pub beach: Option<(u8, u8)>, 25 | pub max_bedrock_height: Option, 26 | pub climate: ClimateSettings 27 | } 28 | 29 | impl Default for Settings { 30 | fn default() -> Self { 31 | Settings { 32 | shape_blocks: ShapeBlocks::default(), 33 | paint_blocks: PaintBlocks::default(), 34 | tri: TriNoiseSettings::default(), 35 | height: HeightSettings::default(), 36 | field: FieldSettings::default(), 37 | sea_coord: 63, 38 | beach: Some((59, 65)), 39 | max_bedrock_height: Some(5), 40 | climate: ClimateSettings::default() 41 | } 42 | } 43 | } 44 | 45 | pub fn passes(seed: u64, settings: Settings, biome_lookup: Lookup) -> (ShapePass, PaintPass) where B: Target { 46 | let mut rng = Random::new(seed); 47 | 48 | let tri = TriNoiseSource::new(&mut rng, &settings.tri); 49 | 50 | // TODO: The PerlinOctaves implementation currently does not support noise on arbitrary Y coordinates. 51 | // Oddly, this "feature" is what causes the sharp walls in beach/biome surfaces. 52 | // It is a mystery why the feature exists in the first place. 53 | 54 | let sand = PerlinOctaves::new(&mut rng.clone(), 4, Vector3::new(1.0 / 32.0, 1.0 / 32.0, 1.0)); // Vertical, Z = 0.0 55 | let gravel = PerlinOctaves::new(&mut rng, 4, Vector3::new(1.0 / 32.0, 1.0, 1.0 / 32.0)); // Horizontal 56 | let thickness = PerlinOctaves::new(&mut rng, 4, Vector3::new(1.0 / 16.0, 1.0 / 16.0, 1.0 / 16.0)); // Vertical, Z = 0.0 57 | 58 | let height = HeightSource::new(&mut rng, &settings.height); 59 | let field = settings.field; 60 | let climate = ClimateSource::new(seed, settings.climate); 61 | 62 | ( 63 | ShapePass { 64 | climate, 65 | blocks: settings.shape_blocks, 66 | tri, 67 | height, 68 | field, 69 | sea_coord: settings.sea_coord 70 | }, 71 | PaintPass { 72 | biomes: BiomeSource::new(ClimateSource::new(seed, settings.climate), biome_lookup), 73 | blocks: settings.paint_blocks, 74 | sand, 75 | gravel, 76 | thickness, 77 | sea_coord: settings.sea_coord, 78 | beach: settings.beach, 79 | max_bedrock_height: settings.max_bedrock_height 80 | } 81 | ) 82 | } 83 | 84 | pub struct ShapeBlocks where B: Target { 85 | pub solid: B, 86 | pub ocean: B, 87 | pub ice: B, 88 | pub air: B 89 | } 90 | 91 | impl Default for ShapeBlocks { 92 | fn default() -> Self { 93 | ShapeBlocks { 94 | solid: 1 * 16, 95 | ocean: 9 * 16, 96 | ice: 79 * 16, 97 | air: 0 * 16 98 | } 99 | } 100 | } 101 | 102 | pub struct ShapePass where B: Target { 103 | climate: ClimateSource, 104 | blocks: ShapeBlocks, 105 | tri: TriNoiseSource, 106 | height: HeightSource, 107 | field: FieldSettings, 108 | sea_coord: u8 109 | } 110 | 111 | impl Pass for ShapePass where B: Target { 112 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition) { 113 | let offset = Point2::new( 114 | (chunk.x() as f64) * 4.0, 115 | (chunk.z() as f64) * 4.0 116 | ); 117 | 118 | let block_offset = ( 119 | (chunk.x() as f64) * 16.0, 120 | (chunk.z() as f64) * 16.0 121 | ); 122 | 123 | let climate_chunk = self.climate.chunk(block_offset); 124 | 125 | let mut field = [[[0f64; 5]; 17]; 5]; 126 | 127 | for x in 0..5 { 128 | for z in 0..5 { 129 | let layer = lerp_to_layer(Vector2::new(x as u8, z as u8)); 130 | 131 | let climate = climate_chunk.get(layer); 132 | let height = self.height.sample(offset + Vector2::new(x as f64, z as f64), climate); 133 | 134 | for y in 0..17 { 135 | let tri = self.tri.sample(Vector3::new(offset.x + x as f64, y as f64, offset.y + z as f64), y); 136 | 137 | field[x][y][z] = self.field.compute_noise_value(y as f64, height, tri); 138 | } 139 | } 140 | } 141 | 142 | target.ensure_available(self.blocks.air.clone()); 143 | target.ensure_available(self.blocks.solid.clone()); 144 | target.ensure_available(self.blocks.ocean.clone()); 145 | target.ensure_available(self.blocks.ice.clone()); 146 | 147 | let (mut blocks, palette) = target.freeze_palette(); 148 | 149 | let air = palette.reverse_lookup(&self.blocks.air).unwrap(); 150 | let solid = palette.reverse_lookup(&self.blocks.solid).unwrap(); 151 | let ocean = palette.reverse_lookup(&self.blocks.ocean).unwrap(); 152 | let ice = palette.reverse_lookup(&self.blocks.ice).unwrap(); 153 | 154 | for i in 0..32768 { 155 | let position = ColumnPosition::from_yzx(i); 156 | let altitude = position.y(); 157 | 158 | let block = if trilinear128(&field, position) > 0.0 { 159 | &solid 160 | } else if altitude == self.sea_coord && climate_chunk.get(position.layer()).freezing() { 161 | &ice 162 | } else if altitude <= self.sea_coord { 163 | &ocean 164 | } else { 165 | &air 166 | }; 167 | 168 | blocks.set(position, block); 169 | } 170 | } 171 | } 172 | 173 | pub struct PaintBlocks where B: Target { 174 | pub reset: BlockMatcher, 175 | pub ignore: BlockMatcher, 176 | pub air: B, 177 | pub stone: B, 178 | pub ocean: B, 179 | pub gravel: B, 180 | pub sand: B, 181 | pub sandstone: B, 182 | pub bedrock: B 183 | } 184 | 185 | impl Default for PaintBlocks { 186 | fn default() -> Self { 187 | PaintBlocks { 188 | reset: BlockMatcher::is(0 * 16), 189 | ignore: BlockMatcher::is_not(1 * 16), 190 | air: 0 * 16, 191 | stone: 1 * 16, 192 | ocean: 9 * 16, 193 | gravel: 13 * 16, 194 | sand: 12 * 16, 195 | sandstone: 24 * 16, 196 | bedrock: 7 * 16 197 | } 198 | } 199 | } 200 | 201 | pub struct PaintAssociations { 202 | ocean: ColumnAssociation, 203 | bedrock: ColumnAssociation 204 | } 205 | 206 | struct SurfaceAssociations { 207 | pub top: ColumnAssociation, 208 | pub fill: ColumnAssociation, 209 | pub chain: Vec 210 | } 211 | 212 | impl SurfaceAssociations { 213 | fn lookup(surface: &Surface, palette: &ColumnPalettes) -> Self where B: Target { 214 | let mut chain = Vec::new(); 215 | 216 | for followup in &surface.chain { 217 | chain.push( 218 | FollowupAssociation { 219 | block: palette.reverse_lookup(&followup.block).unwrap(), 220 | max_depth: followup.max_depth 221 | } 222 | ) 223 | } 224 | 225 | SurfaceAssociations { 226 | top: palette.reverse_lookup(&surface.top).unwrap(), 227 | fill: palette.reverse_lookup(&surface.fill).unwrap(), 228 | chain 229 | } 230 | } 231 | } 232 | 233 | struct FollowupAssociation { 234 | pub block: ColumnAssociation, 235 | pub max_depth: u32 236 | } 237 | 238 | pub struct PaintPass where B: Target { 239 | biomes: BiomeSource, 240 | blocks: PaintBlocks, 241 | sand: PerlinOctaves, 242 | gravel: PerlinOctaves, 243 | thickness: PerlinOctaves, 244 | sea_coord: u8, 245 | beach: Option<(u8, u8)>, 246 | max_bedrock_height: Option 247 | } 248 | 249 | impl PaintPass where B: Target { 250 | fn paint_stack(&self, rng: &mut Random, blocks: &mut ColumnBlocks, palette: &ColumnPalettes, associations: &PaintAssociations, x: u8, z: u8, surface: &SurfaceAssociations, beach: &SurfaceAssociations, basin: &SurfaceAssociations, thickness: i32) { 251 | let reset_remaining = match thickness { 252 | -1 => None, 253 | x if x <= 0 => Some(0), 254 | thickness => Some(thickness as u32) 255 | }; 256 | 257 | let mut remaining = None; 258 | let mut followup_index: Option = None; 259 | 260 | let mut current_surface = if thickness <= 0 {basin} else {surface}; 261 | 262 | for y in (0..128).rev() { 263 | let position = ColumnPosition::new(x, y, z); 264 | 265 | if let Some(chance) = self.max_bedrock_height { 266 | if (y as u32) <= rng.next_u32_bound(chance as u32) { 267 | blocks.set(position, &associations.bedrock); 268 | continue; 269 | } 270 | } 271 | 272 | let existing_block = blocks.get(position, &palette); 273 | 274 | if self.blocks.reset.matches(existing_block) { 275 | remaining = None; 276 | continue 277 | } else if self.blocks.ignore.matches(existing_block) { 278 | continue 279 | } 280 | 281 | match remaining { 282 | Some(0) => (), 283 | Some(ref mut remaining) => { 284 | let block = match followup_index { 285 | Some(index) => ¤t_surface.chain[index].block, 286 | None => ¤t_surface.fill 287 | }; 288 | 289 | blocks.set(position, block); 290 | 291 | *remaining -= 1; 292 | if *remaining == 0 { 293 | // TODO: Don't increment the index if it is already out of bounds. 294 | let new_index = followup_index.map(|index| index + 1).unwrap_or(0); 295 | 296 | if new_index < current_surface.chain.len() { 297 | *remaining = rng.next_u32_bound(current_surface.chain[new_index].max_depth + 1) 298 | } 299 | 300 | followup_index = Some(new_index); 301 | } 302 | }, 303 | None => { 304 | if thickness <= 0 { 305 | current_surface = basin; 306 | } else if let Some(beach_range) = self.beach { 307 | if y >= beach_range.0 && y <= beach_range.1 { 308 | current_surface = beach; 309 | } 310 | } 311 | 312 | blocks.set(position, if y >= self.sea_coord {¤t_surface.top} else {¤t_surface.fill}); 313 | 314 | if y <= self.sea_coord && blocks.get(position, palette) == &self.blocks.air { 315 | blocks.set(position, &associations.ocean); 316 | } 317 | 318 | remaining = reset_remaining; 319 | followup_index = None; 320 | } 321 | } 322 | 323 | 324 | } 325 | } 326 | } 327 | 328 | impl Pass for PaintPass where B: Target { 329 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition) { 330 | let block = ((chunk.x() * 16) as f64, (chunk.z() * 16) as f64); 331 | let seed = (chunk.x() as i64).wrapping_mul(341873128712).wrapping_add((chunk.z() as i64).wrapping_mul(132897987541)); 332 | let mut rng = Random::new(seed as u64); 333 | 334 | let biome_layer = self.biomes.layer(chunk); 335 | let (biomes, biome_palette) = biome_layer.freeze(); 336 | 337 | let sand_vertical = self. sand.vertical_ref(block.1, 16); 338 | let thickness_vertical = self.thickness.vertical_ref(block.1, 16); 339 | 340 | let vertical_offset = Vector3::new(block.0 as f64, block.1 as f64, 0.0); 341 | let horizontal_offset = Point2::new(block.0 as f64, block.1 as f64); 342 | 343 | target.ensure_available(self.blocks.air.clone()); 344 | target.ensure_available(self.blocks.stone.clone()); 345 | target.ensure_available(self.blocks.ocean.clone()); 346 | target.ensure_available(self.blocks.gravel.clone()); 347 | target.ensure_available(self.blocks.sand.clone()); 348 | target.ensure_available(self.blocks.sandstone.clone()); 349 | target.ensure_available(self.blocks.bedrock.clone()); 350 | 351 | for surface in biome_palette.iter().filter_map(Option::as_ref).map(|biome| &biome.surface) { 352 | target.ensure_available(surface.top.clone()); 353 | target.ensure_available(surface.fill.clone()); 354 | 355 | for followup in &surface.chain { 356 | target.ensure_available(followup.block.clone()); 357 | } 358 | } 359 | 360 | let (mut blocks, palette) = target.freeze_palette(); 361 | 362 | let mut surfaces = Vec::new(); 363 | 364 | for entry in biome_palette { 365 | surfaces.push( 366 | entry.as_ref().map(|biome| SurfaceAssociations::lookup(&biome.surface, &palette)) 367 | ); 368 | } 369 | 370 | let associations = PaintAssociations { 371 | ocean: palette.reverse_lookup(&self.blocks.ocean).unwrap(), 372 | bedrock: palette.reverse_lookup(&self.blocks.bedrock).unwrap() 373 | }; 374 | 375 | let gravel_beach = SurfaceAssociations { 376 | top: palette.reverse_lookup(&self.blocks.air).unwrap(), 377 | fill: palette.reverse_lookup(&self.blocks.gravel).unwrap(), 378 | chain: vec![] 379 | }; 380 | 381 | let sand_beach = SurfaceAssociations { 382 | top: palette.reverse_lookup(&self.blocks.sand).unwrap(), 383 | fill: palette.reverse_lookup(&self.blocks.sand).unwrap(), 384 | chain: vec![ 385 | FollowupAssociation { 386 | block: palette.reverse_lookup(&self.blocks.sandstone).unwrap(), 387 | max_depth: 3 388 | } 389 | ] 390 | }; 391 | 392 | let basin = SurfaceAssociations { 393 | top: palette.reverse_lookup(&self.blocks.air).unwrap(), 394 | fill: palette.reverse_lookup(&self.blocks.stone).unwrap(), 395 | chain: vec![] 396 | }; 397 | 398 | for z in 0..16 { 399 | for x in 0..16 { 400 | let position = LayerPosition::new(x, z); 401 | 402 | // TODO: BeachSelector 403 | 404 | let (sand_variation, gravel_variation, thickness_variation) = (rng.next_f64() * 0.2, rng.next_f64() * 0.2, rng.next_f64() * 0.25); 405 | 406 | let sand = sand_vertical.generate_override( vertical_offset + Vector3::new(x as f64, z as f64, 0.0), z as usize) + sand_variation > 0.0; 407 | let gravel = self.gravel.sample (horizontal_offset + Vector2::new(x as f64, z as f64 ) ) + gravel_variation > 3.0; 408 | let thickness = (thickness_vertical.generate_override( vertical_offset + Vector3::new(x as f64, z as f64, 0.0), z as usize) / 3.0 + 3.0 + thickness_variation) as i32; 409 | 410 | let surface = surfaces[biomes.get(position) as usize].as_ref().unwrap(); 411 | 412 | let beach = if sand { 413 | &sand_beach 414 | } else if gravel { 415 | &gravel_beach 416 | } else { 417 | surface 418 | }; 419 | 420 | self.paint_stack(&mut rng, &mut blocks, &palette, &associations, x, z, surface, beach, &basin, thickness); 421 | } 422 | } 423 | } 424 | } -------------------------------------------------------------------------------- /src/generator/sky_173.rs: -------------------------------------------------------------------------------- 1 | use vocs::indexed::Target; 2 | use vocs::position::{ColumnPosition, GlobalColumnPosition}; 3 | use vocs::view::ColumnMut; 4 | use generator::Pass; 5 | use noise_field::volume::{self, TriNoiseSource, TriNoiseSettings}; 6 | use cgmath::{Vector2, Vector3}; 7 | use java_rand::Random; 8 | 9 | pub fn default_tri_settings() -> TriNoiseSettings { 10 | TriNoiseSettings { 11 | main_out_scale: 20.0, 12 | upper_out_scale: 512.0, 13 | lower_out_scale: 512.0, 14 | lower_scale: Vector3::new(1368.824, 684.412, 1368.824 ), 15 | upper_scale: Vector3::new(1368.824, 684.412, 1368.824 ), 16 | main_scale: Vector3::new(1368.824 / 80.0, 684.412 / 160.0, 1368.824 / 80.0), 17 | y_size: 33 18 | } 19 | } 20 | 21 | pub fn passes(seed: u64, tri_settings: &TriNoiseSettings, blocks: ShapeBlocks) -> ShapePass where B: Target { 22 | let mut rng = Random::new(seed); 23 | 24 | let tri = TriNoiseSource::new(&mut rng, tri_settings); 25 | 26 | ShapePass { 27 | blocks, 28 | tri 29 | } 30 | } 31 | 32 | pub struct ShapeBlocks where B: Target { 33 | pub solid: B, 34 | pub air: B 35 | } 36 | 37 | impl Default for ShapeBlocks { 38 | fn default() -> Self { 39 | ShapeBlocks { 40 | solid: 1 * 16, 41 | air: 0 * 16 42 | } 43 | } 44 | } 45 | 46 | pub struct ShapePass where B: Target { 47 | blocks: ShapeBlocks, 48 | tri: TriNoiseSource 49 | } 50 | 51 | impl Pass for ShapePass where B: Target { 52 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition) { 53 | let offset = Vector2::new( 54 | (chunk.x() as f64) * 2.0, 55 | (chunk.z() as f64) * 2.0 56 | ); 57 | 58 | let mut field = [[[0f64; 3]; 33]; 3]; 59 | 60 | for x in 0..3 { 61 | for z in 0..3 { 62 | for y in 0..33 { 63 | let mut value = self.tri.sample(Vector3::new(offset.x + x as f64, y as f64, offset.y + z as f64), y) - 8.0; 64 | 65 | value = volume::reduce_upper(value, y as f64, 32.0, 30.0, 33.0); 66 | value = volume::reduce_lower(value, y as f64, 8.0, 30.0 ); 67 | 68 | field[x][y][z] = value; 69 | } 70 | } 71 | } 72 | 73 | target.ensure_available(self.blocks.air.clone()); 74 | target.ensure_available(self.blocks.solid.clone()); 75 | 76 | let (mut blocks, palette) = target.freeze_palette(); 77 | 78 | let air = palette.reverse_lookup(&self.blocks.air).unwrap(); 79 | let solid = palette.reverse_lookup(&self.blocks.solid).unwrap(); 80 | 81 | for i in 0..32768 { 82 | let position = ColumnPosition::from_yzx(i); 83 | 84 | let block = if trilinear128(&field, position) > 0.0 { 85 | &solid 86 | } else { 87 | &air 88 | }; 89 | 90 | blocks.set(position, block); 91 | } 92 | } 93 | } 94 | 95 | pub fn trilinear128(array: &[[[f64; 3]; 33]; 3], position: ColumnPosition) -> f64 { 96 | debug_assert!(position.y() < 128, "trilinear128 only supports Y values below 128"); 97 | 98 | let inner = ( 99 | ((position.x() % 8) as f64) / 8.0, 100 | ((position.y() % 4) as f64) / 4.0, 101 | ((position.z() % 8) as f64) / 8.0 102 | ); 103 | 104 | let indices = ( 105 | (position.x() / 8) as usize, 106 | (position.y() / 4) as usize, 107 | (position.z() / 8) as usize 108 | ); 109 | 110 | lerp(inner.2, 111 | lerp(inner.0, 112 | lerp(inner.1, 113 | array[indices.0 ][indices.1 ][indices.2 ], 114 | array[indices.0 ][indices.1 + 1][indices.2 ], 115 | ), 116 | lerp(inner.1, 117 | array[indices.0 + 1][indices.1 ][indices.2 ], 118 | array[indices.0 + 1][indices.1 + 1][indices.2 ], 119 | ) 120 | ), 121 | lerp(inner.0, 122 | lerp(inner.1, 123 | array[indices.0 ][indices.1 ][indices.2 + 1], 124 | array[indices.0 ][indices.1 + 1][indices.2 + 1], 125 | ), 126 | lerp(inner.1, 127 | array[indices.0 + 1][indices.1 ][indices.2 + 1], 128 | array[indices.0 + 1][indices.1 + 1][indices.2 + 1], 129 | ) 130 | ) 131 | ) 132 | } 133 | 134 | fn lerp(t: f64, a: f64, b: f64) -> f64 { 135 | a + t * (b - a) 136 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | 4 | extern crate serde; 5 | #[macro_use] 6 | extern crate serde_derive; 7 | extern crate serde_json; 8 | extern crate nbt_serde; 9 | extern crate byteorder; 10 | extern crate bit_vec; 11 | extern crate vocs; 12 | extern crate cgmath; 13 | extern crate java_rand; 14 | 15 | pub mod noise; 16 | pub mod biome; 17 | pub mod sample; 18 | pub mod noise_field; 19 | pub mod decorator; 20 | pub mod trig; 21 | pub mod structure; 22 | pub mod generator; 23 | pub mod distribution; 24 | pub mod segmented; 25 | pub mod config; 26 | pub mod matcher; 27 | 28 | #[cfg(test)] 29 | mod test { 30 | use std::fs::File; 31 | use std::io::Read; 32 | 33 | pub fn read_u64s(name: &str) -> Vec { 34 | let file = File::open(format!("test_data/{}.txt", name)).unwrap(); 35 | let mut data = Vec::new(); 36 | 37 | let mut term = 0u64; 38 | for byte in file.bytes() { 39 | let c = (byte.unwrap() as char).to_lowercase().next().unwrap(); 40 | 41 | if c.is_whitespace() { 42 | data.push(term); 43 | 44 | term = 0; 45 | } else { 46 | term <<= 4; 47 | term |= if c >= '0' && c <= '9' { 48 | (c as u64) - ('0' as u64) 49 | } else if c >= 'a' && c <= 'f' { 50 | (c as u64) - ('a' as u64) + 10 51 | } else { 52 | panic!("Bad hex character {}", c); 53 | }; 54 | } 55 | } 56 | 57 | data.push(term); 58 | 59 | data 60 | } 61 | 62 | pub fn read_u32s(name: &str) -> Vec { 63 | read_u64s(name).iter().map(|&v| v as u32).collect::>() 64 | } 65 | 66 | pub fn read_f32s(name: &str) -> Vec { 67 | read_u64s(name).iter().map(|&v| f32::from_bits(v as u32)).collect::>() 68 | } 69 | 70 | pub fn read_f64s(name: &str) -> Vec { 71 | read_u64s(name).iter().map(|&v| f64::from_bits(v)).collect::>() 72 | } 73 | } -------------------------------------------------------------------------------- /src/matcher.rs: -------------------------------------------------------------------------------- 1 | //! Types for matching against specific block. 2 | //! TODO: Replace with sparse bit array in `vocs`. 3 | //! Generic types are not configurable and are a band aid. 4 | //! A component-based solution, in comparison, would be much more configurable. 5 | 6 | use vocs::indexed::Target; 7 | use std::collections::HashSet; 8 | use std::iter::{IntoIterator, FromIterator, Iterator}; 9 | 10 | #[derive(Debug, Clone, Deserialize, Serialize)] 11 | pub struct BlockMatcher where B: Target { 12 | pub blocks: HashSet, 13 | pub blacklist: bool 14 | } 15 | 16 | impl BlockMatcher where B: Target { 17 | pub fn all() -> Self { 18 | BlockMatcher { 19 | blocks: HashSet::new(), 20 | blacklist: true 21 | } 22 | } 23 | 24 | pub fn none() -> Self { 25 | BlockMatcher { 26 | blocks: HashSet::new(), 27 | blacklist: false 28 | } 29 | } 30 | 31 | pub fn is(block: B) -> Self { 32 | let mut blocks = HashSet::with_capacity(1); 33 | blocks.insert(block); 34 | 35 | BlockMatcher { 36 | blocks, 37 | blacklist: false 38 | } 39 | } 40 | 41 | pub fn is_not(block: B) -> Self { 42 | let mut blocks = HashSet::with_capacity(1); 43 | blocks.insert(block); 44 | 45 | BlockMatcher { 46 | blocks, 47 | blacklist: true 48 | } 49 | } 50 | 51 | pub fn include<'a, I>(blocks: I) -> Self where I: IntoIterator, B: 'a { 52 | BlockMatcher { 53 | blocks: HashSet::from_iter(blocks.into_iter().map(|x| x.clone())), 54 | blacklist: false 55 | } 56 | } 57 | 58 | pub fn exclude<'a, I>(blocks: I) -> Self where I: IntoIterator, B: 'a{ 59 | BlockMatcher { 60 | blocks: HashSet::from_iter(blocks.into_iter().map(|x| x.clone())), 61 | blacklist: true 62 | } 63 | } 64 | 65 | pub fn matches(&self, block: &B) -> bool { 66 | // NotPresent, Whitelist => 0 ^ 0 => 0 67 | // NotPresent, Blacklist => 0 ^ 1 => 1 68 | // Contains, Whitelist => 1 ^ 0 => 1 69 | // Contains, Blacklist => 1 ^ 1 => 0 70 | self.blocks.contains(block) ^ self.blacklist 71 | } 72 | } -------------------------------------------------------------------------------- /src/noise/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod perlin; 2 | pub mod simplex; 3 | pub mod octaves; 4 | 5 | use java_rand::Random; 6 | use cgmath::Vector3; 7 | 8 | pub struct Permutations { 9 | offset: Vector3, 10 | permutations: [u8; 256] 11 | } 12 | 13 | impl Permutations { 14 | pub fn new(rng: &mut Random) -> Self { 15 | let mut p = Permutations { 16 | offset: Vector3::new(rng.next_f64() * 256.0, rng.next_f64() * 256.0, rng.next_f64() * 256.0), 17 | permutations: [0; 256] 18 | }; 19 | 20 | // Fill array with 0..256 21 | for (i, x) in p.permutations.iter_mut().enumerate() { 22 | *x = i as u8; 23 | }; 24 | 25 | for i in 0..256 { 26 | let rand = rng.next_u32_bound(256 - i) + i; 27 | p.permutations.swap(i as usize, rand as usize); 28 | }; 29 | 30 | p 31 | } 32 | 33 | fn hash(&self, i: u16) -> u16 { 34 | self.permutations[(i as usize) & 0xFF] as u16 35 | } 36 | } 37 | 38 | impl ::std::fmt::Debug for Permutations { 39 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 40 | write!(f, "Permutations {{ offset: ({}, {}, {}), permutations: {:?} }}", self.offset.x, self.offset.y, self.offset.z, &self.permutations[..]) 41 | } 42 | } -------------------------------------------------------------------------------- /src/noise/octaves.rs: -------------------------------------------------------------------------------- 1 | // Note: The values returned by these functions may be off by up to ±0.00000000000001 units compared to Notchian implementations due to moving around FP operations. 2 | // The simplex implementation may also suffer from some inaccuracy due to it doing the *70 and *amplitude multiplications seperately. 3 | 4 | use cgmath::{Point2, Vector2, Vector3}; 5 | use sample::{Sample, Layer}; 6 | use noise::simplex::Simplex; 7 | use noise::perlin::Perlin; 8 | use java_rand::Random; 9 | 10 | #[derive(Debug)] 11 | pub struct SimplexOctaves(Vec); 12 | impl SimplexOctaves { 13 | pub fn new(rng: &mut Random, octaves: usize, fq: f64, persistence: f64, scale: (f64, f64)) -> Self { 14 | let mut octaves = Vec::with_capacity(octaves); 15 | 16 | let scale = (scale.0 / 1.5, scale.1 / 1.5); 17 | let mut frequency = 1.0; 18 | let mut amplitude = 1.0; 19 | 20 | for _ in 0..octaves.capacity() { 21 | octaves.push(Simplex::from_rng(rng, Vector2::new(scale.0 * frequency, scale.1 * frequency), 0.55 / amplitude)); 22 | 23 | frequency *= fq; 24 | amplitude *= persistence; 25 | } 26 | 27 | SimplexOctaves(octaves) 28 | } 29 | } 30 | 31 | impl Sample for SimplexOctaves { 32 | type Output = f64; 33 | 34 | fn sample(&self, point: Point2) -> Self::Output { 35 | let mut result = 0.0; 36 | 37 | for octave in &self.0 { 38 | result += octave.sample(point) 39 | } 40 | 41 | result 42 | } 43 | 44 | fn chunk(&self, chunk: (f64, f64)) -> Layer { 45 | let mut result = Layer::fill(0.0); 46 | 47 | for octave in &self.0 { 48 | result += octave.chunk(chunk); 49 | } 50 | 51 | result 52 | } 53 | } 54 | 55 | #[derive(Debug)] 56 | pub struct PerlinOctaves(Vec); 57 | impl PerlinOctaves { 58 | pub fn new(rng: &mut Random, octaves: usize, scale: Vector3) -> Self { 59 | let mut octaves = Vec::with_capacity(octaves); 60 | 61 | let mut frequency = 1.0; 62 | 63 | for _ in 0..octaves.capacity() { 64 | octaves.push(Perlin::from_rng(rng, scale * frequency, 1.0 / frequency)); 65 | 66 | frequency /= 2.0; 67 | } 68 | 69 | PerlinOctaves(octaves) 70 | } 71 | 72 | fn generate_y_tables(&self, y_start: f64, y_count: usize) -> Vec { 73 | let mut tables = vec![0.0; y_count * self.0.len()]; 74 | 75 | for (perlin, chunk) in self.0.iter().zip(tables.chunks_mut(y_count)) { 76 | perlin.generate_y_table(y_start, chunk); 77 | } 78 | 79 | tables 80 | } 81 | 82 | pub fn vertical_ref(&self, y_start: f64, y_count: usize) -> PerlinOctavesVerticalRef { 83 | PerlinOctavesVerticalRef { 84 | octaves: &self.0, 85 | tables: self.generate_y_tables(y_start, y_count), 86 | y_count 87 | } 88 | } 89 | 90 | pub fn into_vertical(self, y_start: f64, y_count: usize) -> PerlinOctavesVertical { 91 | let tables = self.generate_y_tables(y_start, y_count); 92 | 93 | PerlinOctavesVertical { octaves: self.0, tables, y_count } 94 | } 95 | 96 | pub fn generate(&self, point: Vector3) -> f64 { 97 | self.0.iter().fold(0.0, |result, perlin| result + perlin.generate(point)) 98 | } 99 | } 100 | 101 | impl Sample for PerlinOctaves { 102 | type Output = f64; 103 | 104 | fn sample(&self, point: Point2) -> Self::Output { 105 | self.0.iter().fold(0.0, |result, perlin| result + perlin.sample(point)) 106 | } 107 | 108 | fn chunk(&self, chunk: (f64, f64)) -> Layer { 109 | self.0.iter().fold(Layer::fill(0.0), |result, perlin| result + perlin.chunk(chunk)) 110 | } 111 | } 112 | 113 | pub struct PerlinOctavesVerticalRef<'a> { 114 | octaves: &'a [Perlin], 115 | tables: Vec, 116 | y_count: usize 117 | } 118 | 119 | impl<'a> PerlinOctavesVerticalRef<'a> { 120 | pub fn generate_override(&self, point: Vector3, index: usize) -> f64 { 121 | let mut result = 0.0; 122 | 123 | for (perlin, table) in self.octaves.iter().zip(self.tables.chunks(self.y_count)) { 124 | result += perlin.generate_override(point, table[index]) 125 | } 126 | 127 | result 128 | } 129 | } 130 | 131 | 132 | pub struct PerlinOctavesVertical { 133 | octaves: Vec, 134 | tables: Vec, 135 | y_count: usize 136 | } 137 | 138 | impl PerlinOctavesVertical { 139 | pub fn new(rng: &mut Random, octaves: usize, scale: Vector3, y_start: f64, y_count: usize) -> Self { 140 | PerlinOctaves::new(rng, octaves, scale).into_vertical(y_start, y_count) 141 | } 142 | 143 | pub fn generate_override(&self, point: Vector3, index: usize) -> f64 { 144 | let mut result = 0.0; 145 | 146 | for (perlin, table) in self.octaves.iter().zip(self.tables.chunks(self.y_count)) { 147 | result += perlin.generate_override(point, table[index]) 148 | } 149 | 150 | result 151 | } 152 | } -------------------------------------------------------------------------------- /src/noise/perlin.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use cgmath::{Point2, Vector2, Vector3}; 3 | use noise::Permutations; 4 | use sample::Sample; 5 | 6 | const GRAD_TABLE: [(f64, f64, f64); 16] = [ 7 | ( 1.0, 1.0, 0.0), 8 | (-1.0, 1.0, 0.0), 9 | ( 1.0, -1.0, 0.0), 10 | (-1.0, -1.0, 0.0), 11 | ( 1.0, 0.0, 1.0), 12 | (-1.0, 0.0, 1.0), 13 | ( 1.0, 0.0, -1.0), 14 | (-1.0, 0.0, -1.0), 15 | ( 0.0, 1.0, 1.0), 16 | ( 0.0, -1.0, 1.0), 17 | ( 0.0, 1.0, -1.0), 18 | ( 0.0, -1.0, -1.0), 19 | ( 1.0, 1.0, 0.0), 20 | ( 0.0, -1.0, 1.0), 21 | (-1.0, 1.0, 0.0), 22 | ( 0.0, -1.0, -1.0) 23 | ]; 24 | 25 | /// Returns the dot product of the vector with a pseudorandomly selected gradient vector. 26 | fn grad(t: u16, vec: Vector3) -> f64 { 27 | let gradient = GRAD_TABLE[(t & 0xF) as usize]; 28 | gradient.0 * vec.x + gradient.1 * vec.y + gradient.2 * vec.z 29 | } 30 | 31 | /// Convienience method to call grad with Y=0. 32 | fn grad2d(t: u16, vec: Vector2) -> f64 { 33 | grad(t, Vector3::new(vec.x, 0.0, vec.y)) 34 | } 35 | 36 | /// Upper noise coordinate where farlands appear. 37 | const FARLANDS_UPPER: f64 = 2147483647.0; 38 | 39 | /// Lower noise coordinate where farlands appear. 40 | const FARLANDS_LOWER: f64 = -2147483648.0; 41 | 42 | /// Imitates a combination of floor() and Java float to integer rounding. 43 | /// Returns the floor of a number capped in between the lower and upper signed 32-bit integer limit. 44 | /// Makes sure the farlands remain. 45 | // TODO: Verify farlands. 46 | fn floor_capped(t: f64) -> f64 { 47 | t.floor().max(FARLANDS_LOWER).min(FARLANDS_UPPER) 48 | } 49 | 50 | fn fade(t: f64) -> f64 { 51 | t * t * t * (t * (t * 6.0 - 15.0) + 10.0) 52 | } 53 | 54 | /// Preforms linear interpolation between A and B using T as a factor. 0.0 = A, 1.0 = B, 0.5 = (A + B)/2. 55 | fn lerp(t: f64, a: f64, b: f64) -> f64 { 56 | a + t * (b - a) 57 | } 58 | 59 | /// Perlin noise generator. Can be sampled in 2 or 3 dimensions. 60 | #[derive(Debug)] 61 | pub struct Perlin { 62 | p: Permutations, 63 | scale: Vector3, 64 | amplitude: f64 65 | } 66 | 67 | impl Perlin { 68 | pub fn new(p: Permutations, scale: Vector3, amplitude: f64) -> Self { 69 | Perlin { p, scale, amplitude } 70 | } 71 | 72 | pub fn from_rng(rng: &mut Random, scale: Vector3, amplitude: f64) -> Self { 73 | Perlin { p: Permutations::new(rng), scale, amplitude } 74 | } 75 | 76 | fn hash(&self, i: u16) -> u16 { 77 | self.p.hash(i) 78 | } 79 | 80 | // TODO: Merge generate and generate_override. 81 | 82 | pub fn generate(&self, loc: Vector3) -> f64 { 83 | let loc = Vector3::new(loc.x * self.scale.x, loc.y * self.scale.y, loc.z * self.scale.z) + self.p.offset; 84 | 85 | // TODO: Make sure we still get the far lands. 86 | let floored = loc.map(floor_capped); 87 | 88 | // Perform modulo before conversion to u16 because Rust currently has a bug that results in UB from out of bounds float to integer casts. 89 | // TODO: This is broken for negative coords. 90 | let p = Vector3::new( 91 | (floored.x % 256.0) as u16, 92 | (floored.y % 256.0) as u16, 93 | (floored.z % 256.0) as u16 94 | ); 95 | 96 | // Find the position of the point within the cell. 97 | let loc = loc - floored; 98 | 99 | // Use the fade function to reduce unnatural looking artifacts from direct linear interpolation. 100 | let faded = loc.map(fade); 101 | 102 | let a = self.hash(p.x) + p.y; 103 | let aa = self.hash(a) + p.z; 104 | let ab = self.hash(a + 1) + p.z; 105 | 106 | let b = self.hash(p.x + 1) + p.y; 107 | let ba = self.hash(b) + p.z; 108 | let bb = self.hash(b + 1) + p.z; 109 | 110 | lerp(faded.z, 111 | lerp(faded.y, 112 | lerp(faded.x, 113 | grad(self.hash(aa ), loc ), 114 | grad(self.hash(ba ), loc - Vector3::new(1.0, 0.0, 0.0)) 115 | ), 116 | lerp(faded.x, 117 | grad(self.hash(ab ), loc - Vector3::new(0.0, 1.0, 0.0)), 118 | grad(self.hash(bb ), loc - Vector3::new(1.0, 1.0, 0.0)) 119 | ) 120 | ), 121 | lerp(faded.y, 122 | lerp(faded.x, 123 | grad(self.hash(aa + 1), loc - Vector3::new(0.0, 0.0, 1.0)), 124 | grad(self.hash(ba + 1), loc - Vector3::new(1.0, 0.0, 1.0)) 125 | ), 126 | lerp(faded.x, 127 | grad(self.hash(ab + 1), loc - Vector3::new(0.0, 1.0, 1.0)), 128 | grad(self.hash(bb + 1), loc - Vector3::new(1.0, 1.0, 1.0)) 129 | ) 130 | ) 131 | ) * self.amplitude 132 | } 133 | 134 | pub fn generate_override(&self, loc: Vector3, actual_y: f64) -> f64 { 135 | let loc = Vector3::new(loc.x * self.scale.x, loc.y * self.scale.y, loc.z * self.scale.z) + self.p.offset; 136 | 137 | // TODO: Make sure we still get the far lands. 138 | let floored = loc.map(floor_capped); 139 | // TODO: This is broken for negative coords. 140 | let p = Vector3::new( 141 | (floored.x % 256.0) as u16, 142 | (floored.y % 256.0) as u16, 143 | (floored.z % 256.0) as u16 144 | ); 145 | let p = p.map(|x| x & 255); 146 | 147 | let mut loc = loc - floored; 148 | let faded = loc.map(fade); 149 | loc.y = actual_y; 150 | 151 | let a = self.hash(p.x) + p.y; 152 | let aa = self.hash(a) + p.z; 153 | let ab = self.hash(a + 1) + p.z; 154 | 155 | let b = self.hash(p.x + 1) + p.y; 156 | let ba = self.hash(b) + p.z; 157 | let bb = self.hash(b + 1) + p.z; 158 | 159 | lerp(faded.z, 160 | lerp(faded.y, 161 | lerp(faded.x, 162 | grad(self.hash(aa + 0), loc), 163 | grad(self.hash(ba + 0), loc - Vector3::new(1.0, 0.0, 0.0)) 164 | ), 165 | lerp(faded.x, 166 | grad(self.hash(ab + 0), loc - Vector3::new(0.0, 1.0, 0.0)), 167 | grad(self.hash(bb + 0), loc - Vector3::new(1.0, 1.0, 0.0)) 168 | ) 169 | ), 170 | lerp(faded.y, 171 | lerp(faded.x, 172 | grad(self.hash(aa + 1), loc - Vector3::new(0.0, 0.0, 1.0)), 173 | grad(self.hash(ba + 1), loc - Vector3::new(1.0, 0.0, 1.0)) 174 | ), 175 | lerp(faded.x, 176 | grad(self.hash(ab + 1), loc - Vector3::new(0.0, 1.0, 1.0)), 177 | grad(self.hash(bb + 1), loc - Vector3::new(1.0, 1.0, 1.0)) 178 | ) 179 | ) 180 | ) * self.amplitude 181 | } 182 | 183 | pub fn generate_y_table(&self, start: f64, table: &mut [f64]) { 184 | let mut actual_y = 0.0; 185 | let mut last_p = 65535; 186 | 187 | for (offset, entry) in table.iter_mut().enumerate() { 188 | let y = (start + (offset as f64)) * self.scale.y + self.p.offset.y; 189 | let floored = floor_capped(y); 190 | let p = (floored % 256.0) as u16; 191 | let y = y - floored; 192 | 193 | if p != last_p { 194 | actual_y = y; 195 | } 196 | 197 | last_p = p; 198 | 199 | *entry = actual_y; 200 | } 201 | } 202 | } 203 | 204 | impl Sample for Perlin { 205 | type Output = f64; 206 | 207 | fn sample(&self, loc: Point2) -> f64 { 208 | let loc = Vector2::new(loc.x * self.scale.x, loc.y * self.scale.z) + Vector2::new(self.p.offset.x, self.p.offset.z); 209 | 210 | // TODO: This is broken for negative coords? 211 | let floored = loc.map(floor_capped); 212 | let p = Vector2::new( 213 | (floored.x % 256.0) as u16, 214 | (floored.y % 256.0) as u16 215 | ); 216 | let p = p.map(|x| x & 255); 217 | 218 | let loc = loc - floored; 219 | let faded = loc.map(fade); 220 | 221 | let aa = self.hash(self.hash(p.x )) + p.y; 222 | let ba = self.hash(self.hash(p.x + 1)) + p.y; 223 | 224 | lerp(faded.y, 225 | lerp(faded.x, 226 | grad2d(self.hash(aa ), loc), 227 | grad2d(self.hash(ba ), loc - Vector2::new(1.0, 0.0)) 228 | ), 229 | lerp(faded.x, 230 | grad2d(self.hash(aa + 1), loc - Vector2::new(0.0, 1.0)), 231 | grad2d(self.hash(ba + 1), loc - Vector2::new(1.0, 1.0)) 232 | ) 233 | ) * self.amplitude 234 | } 235 | } -------------------------------------------------------------------------------- /src/noise/simplex.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use cgmath::{Point2, Vector2}; 3 | use noise::Permutations; 4 | use sample::Sample; 5 | 6 | const GRAD_TABLE: [(f64, f64); 12] = [ 7 | ( 1.0, 1.0), 8 | (-1.0, 1.0), 9 | ( 1.0, -1.0), 10 | (-1.0, -1.0), 11 | ( 1.0, 0.0), 12 | (-1.0, 0.0), 13 | ( 1.0, 0.0), 14 | (-1.0, 0.0), 15 | ( 0.0, 1.0), 16 | ( 0.0, -1.0), 17 | ( 0.0, 1.0), 18 | ( 0.0, -1.0) 19 | ]; 20 | 21 | fn grad(hash: u16, x: f64, y: f64) -> f64 { 22 | let gradient = GRAD_TABLE[hash as usize % 12]; 23 | gradient.0 * x + gradient.1 * y 24 | } 25 | 26 | const SQRT_THREE: f64 = 1.7320508075688772935; 27 | 28 | const F2: f64 = 0.5 * (SQRT_THREE - 1.0); 29 | const G2: f64 = (3.0 - SQRT_THREE) / 6.0; 30 | 31 | // We can only implement Simplex noise up to 2D or we will run into patent issues. 32 | #[derive(Debug)] 33 | pub struct Simplex { 34 | p: Permutations, 35 | scale: Vector2, 36 | amplitude: f64 37 | } 38 | 39 | impl Simplex { 40 | pub fn new(p: Permutations, scale: Vector2, amplitude: f64) -> Self { 41 | Simplex { p, scale, amplitude } 42 | } 43 | 44 | pub fn from_rng(rng: &mut Random, scale: Vector2, amplitude: f64) -> Self { 45 | Simplex { p: Permutations::new(rng), scale, amplitude } 46 | } 47 | 48 | fn hash(&self, i: u16) -> u16 { 49 | self.p.hash(i) 50 | } 51 | } 52 | 53 | impl Sample for Simplex { 54 | type Output = f64; 55 | 56 | fn sample(&self, point: Point2) -> f64 { 57 | let point = Point2::new(point.x * self.scale.x, point.y * self.scale.y) + Vector2::new(self.p.offset.x, self.p.offset.y); 58 | 59 | let s = (point.x + point.y) * F2; 60 | let fx = (point.x + s).floor(); 61 | let fy = (point.y + s).floor(); 62 | let t = (fx + fy) * G2; 63 | 64 | let x0 = point.x - (fx - t); 65 | let y0 = point.y - (fy - t); 66 | 67 | let bias = if x0 > y0 {Vector2::new(1, 0)} else {Vector2::new(0, 1)}; 68 | 69 | let x1 = x0 - (bias.x as f64) + G2; 70 | let y1 = y0 - (bias.y as f64) + G2; 71 | let x2 = x0 - 1.0 + G2 * 2.0; 72 | let y2 = y0 - 1.0 + G2 * 2.0; 73 | 74 | // TODO: This is broken for negative coords. 75 | let x_i = ((fx % 256.0) as u16) % 255; 76 | let y_i = ((fy % 256.0) as u16) % 255; 77 | 78 | let t0 = f64::max(0.5 - x0*x0 - y0*y0, 0.0); 79 | let n0 = f64::powi(t0, 4) * grad(self.hash(x_i + self.hash(y_i)), x0, y0); 80 | 81 | let t1 = f64::max(0.5 - x1*x1 - y1*y1, 0.0); 82 | let n1 = f64::powi(t1, 4) * grad(self.hash(x_i + bias.x + self.hash(y_i + bias.y)), x1, y1); 83 | 84 | let t2 = f64::max(0.5 - x2*x2 - y2*y2, 0.0); 85 | let n2 = f64::powi(t2, 4) * grad(self.hash(x_i + 1 + self.hash(y_i + 1)), x2, y2); 86 | 87 | (70.0 * self.amplitude) * (n0 + n1 + n2) 88 | } 89 | } -------------------------------------------------------------------------------- /src/noise_field/height.rs: -------------------------------------------------------------------------------- 1 | use noise::octaves::PerlinOctaves; 2 | use cgmath::{Point2, Vector2, Vector3}; 3 | use java_rand::Random; 4 | use biome::climate::Climate; 5 | use sample::Sample; 6 | use vocs::position::LayerPosition; 7 | 8 | #[derive(Debug, Copy, Clone, PartialEq)] 9 | pub struct Height { 10 | pub center: f64, 11 | pub chaos: f64 12 | } 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub struct HeightSettings { 16 | biome_influence_coord_scale: Vector3, 17 | biome_influence_scale: f64, 18 | depth_coord_scale: Vector3, 19 | depth_scale: f64, 20 | depth_base: f64 21 | } 22 | 23 | impl Default for HeightSettings { 24 | fn default() -> Self { 25 | HeightSettings { 26 | biome_influence_coord_scale: Vector3::new(1.121, 0.0, 1.121), 27 | biome_influence_scale: 512.0, 28 | depth_coord_scale: Vector3::new(200.0, 0.0, 200.0), 29 | depth_scale: 8000.0, 30 | depth_base: 8.5 31 | } 32 | } 33 | } 34 | 35 | impl From for HeightSettings { 36 | fn from(settings: HeightSettings81) -> Self { 37 | HeightSettings { 38 | biome_influence_coord_scale: Vector3::new(1.121, 0.0, 1.121), 39 | biome_influence_scale: 512.0, 40 | depth_coord_scale: settings.coord_scale, 41 | depth_scale: settings.out_scale, 42 | depth_base: settings.base 43 | } 44 | } 45 | } 46 | 47 | pub struct HeightSource { 48 | biome_influence: PerlinOctaves, 49 | depth: PerlinOctaves, 50 | biome_influence_scale: f64, 51 | depth_scale: f64, 52 | depth_base: f64 53 | } 54 | 55 | impl HeightSource { 56 | pub fn new(rng: &mut Random, settings: &HeightSettings) -> Self { 57 | HeightSource { 58 | biome_influence: PerlinOctaves::new(rng, 10, settings.biome_influence_coord_scale), 59 | depth: PerlinOctaves::new(rng, 16, settings.depth_coord_scale), 60 | biome_influence_scale: settings.biome_influence_scale, 61 | depth_scale: settings.depth_scale, 62 | depth_base: settings.depth_base 63 | } 64 | } 65 | 66 | pub fn sample(&self, point: Point2, climate: Climate) -> Height { 67 | let scaled_noise = self.biome_influence.sample(point) / self.biome_influence_scale; 68 | 69 | let chaos = (climate.influence_factor() * (scaled_noise + 0.5)).max(0.0).min(1.0) + 0.5; 70 | 71 | let mut depth = self.depth.sample(point) / self.depth_scale; 72 | 73 | if depth < 0.0 { 74 | depth *= 0.3 75 | } 76 | 77 | depth = depth.abs().min(1.0) * 3.0 - 2.0; 78 | depth /= if depth < 0.0 {1.4} else {2.0}; 79 | 80 | Height { 81 | center: self.depth_base + depth * (self.depth_base / 8.0), 82 | chaos: if depth < 0.0 {0.5} else {chaos} 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug, PartialEq)] 88 | pub struct HeightSettings81 { 89 | pub coord_scale: Vector3, 90 | pub out_scale: f64, 91 | pub base: f64 92 | } 93 | 94 | impl HeightSettings81 { 95 | pub fn with_biome_influence(self, biome_influence_coord_scale: Vector3, biome_influence_scale: f64) -> HeightSettings { 96 | HeightSettings { 97 | biome_influence_coord_scale, 98 | biome_influence_scale, 99 | depth_coord_scale: self.coord_scale, 100 | depth_scale: self.out_scale, 101 | depth_base: self.base 102 | } 103 | } 104 | } 105 | 106 | impl Default for HeightSettings81 { 107 | fn default() -> Self { 108 | HeightSettings81 { 109 | coord_scale: Vector3::new(200.0, 0.0, 200.0), 110 | out_scale: 8000.0, 111 | base: 8.5 112 | } 113 | } 114 | } 115 | 116 | pub struct HeightSource81 { 117 | depth: PerlinOctaves, 118 | out_scale: f64, 119 | base: f64 120 | } 121 | 122 | impl HeightSource81 { 123 | pub fn new(rng: &mut Random, settings: &HeightSettings81) -> Self { 124 | HeightSource81 { 125 | depth: PerlinOctaves::new(rng, 16, settings.coord_scale), 126 | out_scale: settings.out_scale, 127 | base: settings.base 128 | } 129 | } 130 | 131 | pub fn sample(&self, point: Point2, biome_height_center: f64, biome_chaos: f64) -> Height { 132 | let mut depth = self.depth.sample(point) / self.out_scale; 133 | 134 | if depth < 0.0 { 135 | depth *= 0.3 136 | } 137 | 138 | depth = depth.abs().min(1.0) * 3.0 - 2.0; 139 | depth /= if depth < 0.0 { 1.4 } else { 2.0 }; 140 | 141 | depth = depth * 0.2 + biome_height_center; 142 | 143 | Height { 144 | center: self.base + depth * (self.base / 8.0), 145 | chaos: biome_chaos 146 | } 147 | } 148 | } 149 | 150 | /*pub struct BiomeDigestor { 151 | /// Each cell has a weight assigned. The highest weight is at the center, a max of ~22.36 152 | weights: [[f32; 5]; 5] 153 | } 154 | 155 | impl BiomeDigestor { 156 | pub fn new() -> Self { 157 | let mut weights = [[0.0; 5]; 5]; 158 | 159 | for x in 0..5 { 160 | for z in 0..5 { 161 | // Add 0.2 to prevent a divide by 0, when X/Z are centered. 162 | 163 | let x_relative = (x as i32) - 2; 164 | let z_relative = (z as i32) - 2; 165 | 166 | let distance_squared = (x_relative*x_relative + z_relative*z_relative) as f32 + 0.2; 167 | 168 | weights[x][z] = 10.0 / ((distance_squared as f64).sqrt() as f32); 169 | } 170 | } 171 | 172 | BiomeDigestor { weights } 173 | } 174 | }*/ 175 | 176 | /// Converts form lerp coords (5x5) to layer coords (16x16). 177 | /// ``` 178 | /// 0 => 1 179 | /// 1 => 4 180 | /// 2 => 7 181 | /// 3 => 10 182 | /// 4 => 13 183 | /// ``` 184 | pub fn lerp_to_layer(lerp: Vector2) -> LayerPosition { 185 | LayerPosition::new( 186 | lerp.x*3 + 1, 187 | lerp.y*3 + 1 188 | ) 189 | } -------------------------------------------------------------------------------- /src/noise_field/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod height; 2 | pub mod volume; -------------------------------------------------------------------------------- /src/noise_field/volume.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use noise::octaves::PerlinOctavesVertical; 3 | use cgmath::Vector3; 4 | use noise_field::height::Height; 5 | use vocs::position::ColumnPosition; 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub struct TriNoiseSettings { 9 | pub main_out_scale: f64, 10 | pub upper_out_scale: f64, 11 | pub lower_out_scale: f64, 12 | pub lower_scale: Vector3, 13 | pub upper_scale: Vector3, 14 | pub main_scale: Vector3, 15 | pub y_size: usize 16 | } 17 | 18 | impl Default for TriNoiseSettings { 19 | fn default() -> Self { 20 | TriNoiseSettings { 21 | main_out_scale: 20.0, 22 | upper_out_scale: 512.0, 23 | lower_out_scale: 512.0, 24 | lower_scale: Vector3::new(684.412, 684.412, 684.412 ), 25 | upper_scale: Vector3::new(684.412, 684.412, 684.412 ), 26 | main_scale: Vector3::new(684.412 / 80.0, 684.412 / 160.0, 684.412 / 80.0), 27 | y_size: 17 28 | } 29 | } 30 | } 31 | 32 | pub struct TriNoiseSource { 33 | lower: PerlinOctavesVertical, 34 | upper: PerlinOctavesVertical, 35 | main: PerlinOctavesVertical, 36 | main_out_scale: f64, 37 | upper_out_scale: f64, 38 | lower_out_scale: f64 39 | } 40 | 41 | impl TriNoiseSource { 42 | pub fn new(rng: &mut Random, settings: &TriNoiseSettings) -> Self { 43 | TriNoiseSource { 44 | lower: PerlinOctavesVertical::new(rng, 16, settings.lower_scale, 0.0, settings.y_size), 45 | upper: PerlinOctavesVertical::new(rng, 16, settings.upper_scale, 0.0, settings.y_size), 46 | main: PerlinOctavesVertical::new(rng, 8, settings. main_scale, 0.0, settings.y_size), 47 | main_out_scale: settings. main_out_scale, 48 | upper_out_scale: settings.upper_out_scale, 49 | lower_out_scale: settings.lower_out_scale 50 | } 51 | } 52 | 53 | pub fn sample(&self, point: Vector3, index: usize) -> f64 { 54 | let lower = self.lower.generate_override(point, index) / self.lower_out_scale; 55 | let upper = self.upper.generate_override(point, index) / self.upper_out_scale; 56 | let main = self. main.generate_override(point, index) / self. main_out_scale + 0.5; 57 | 58 | lerp(main.max(0.0).min(1.0), lower, upper) 59 | } 60 | } 61 | 62 | #[derive(Debug)] 63 | pub struct FieldSettings { 64 | pub seabed_stretch : f64, 65 | pub ground_stretch: f64, 66 | pub taper_control: f64, 67 | pub height_stretch: f64 68 | } 69 | 70 | impl FieldSettings { 71 | pub fn with_height_stretch(height_stretch: f64) -> Self { 72 | let mut default = Self::default(); 73 | 74 | default.height_stretch = height_stretch; 75 | 76 | default 77 | } 78 | } 79 | 80 | impl Default for FieldSettings { 81 | fn default() -> Self { 82 | FieldSettings { 83 | seabed_stretch: 4.0, 84 | ground_stretch: 1.0, 85 | taper_control: 4.0, 86 | height_stretch: 12.0 87 | } 88 | } 89 | } 90 | 91 | impl FieldSettings { 92 | // TODO: Replace with FieldSource. 93 | pub fn compute_noise_value(&self, y: f64, height: Height, tri_noise: f64) -> f64 { 94 | let distance = y - height.center; 95 | let distance = distance * if distance < 0.0 {self.seabed_stretch} else {self.ground_stretch}; 96 | 97 | let reduction = distance * self.height_stretch / height.chaos; 98 | let value = tri_noise - reduction; 99 | 100 | reduce_upper(value, y, self.taper_control, 10.0, 17.0) 101 | } 102 | } 103 | 104 | fn lerp(t: f64, a: f64, b: f64) -> f64 { 105 | a + t * (b - a) 106 | } 107 | 108 | pub fn reduce_upper(value: f64, y: f64, control: f64, min: f64, max_y: f64) -> f64 { 109 | let threshold = max_y - control; 110 | let divisor = control - 1.0; 111 | let factor = (y.max(threshold) - threshold) / divisor; 112 | 113 | reduce(value, factor, min) 114 | } 115 | 116 | pub fn reduce_lower(value: f64, y: f64, control: f64, min: f64) -> f64 { 117 | let divisor = control - 1.0; 118 | let factor = (control - y.min(control)) / divisor; 119 | 120 | reduce(value, factor, min) 121 | } 122 | 123 | pub fn reduce_cubic(value: f64, distance: f64) -> f64 { 124 | let factor = 4.0 - distance.min(4.0); 125 | value - 10.0 * factor.powi(3) 126 | } 127 | 128 | pub fn reduce(value: f64, factor: f64, min: f64) -> f64 { 129 | value * (1.0 - factor) - min * factor 130 | } 131 | 132 | pub fn trilinear128(array: &[[[f64; 5]; 17]; 5], position: ColumnPosition) -> f64 { 133 | debug_assert!(position.y() < 128, "trilinear128 only supports Y values below 128"); 134 | 135 | let inner = ( 136 | ((position.x() % 4) as f64) / 4.0, 137 | ((position.y() % 8) as f64) / 8.0, 138 | ((position.z() % 4) as f64) / 4.0 139 | ); 140 | 141 | let indices = ( 142 | (position.x() / 4) as usize, 143 | (position.y() / 8) as usize, 144 | (position.z() / 4) as usize 145 | ); 146 | 147 | lerp(inner.2, 148 | lerp(inner.0, 149 | lerp(inner.1, 150 | array[indices.0 ][indices.1 ][indices.2 ], 151 | array[indices.0 ][indices.1 + 1][indices.2 ], 152 | ), 153 | lerp(inner.1, 154 | array[indices.0 + 1][indices.1 ][indices.2 ], 155 | array[indices.0 + 1][indices.1 + 1][indices.2 ], 156 | ) 157 | ), 158 | lerp(inner.0, 159 | lerp(inner.1, 160 | array[indices.0 ][indices.1 ][indices.2 + 1], 161 | array[indices.0 ][indices.1 + 1][indices.2 + 1], 162 | ), 163 | lerp(inner.1, 164 | array[indices.0 + 1][indices.1 ][indices.2 + 1], 165 | array[indices.0 + 1][indices.1 + 1][indices.2 + 1], 166 | ) 167 | ) 168 | ) 169 | } -------------------------------------------------------------------------------- /src/sample.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, AddAssign}; 2 | use cgmath::{Point2, Vector2}; 3 | use vocs::position::LayerPosition; 4 | 5 | pub struct Layer([T; 256]) where T: Copy; 6 | impl Layer where T: Copy { 7 | pub fn fill(fill: T) -> Self { 8 | Layer([fill; 256]) 9 | } 10 | 11 | pub fn get(&self, position: LayerPosition) -> T { 12 | self.0[position.zx() as usize] 13 | } 14 | 15 | fn set(&mut self, position: LayerPosition, value: T) { 16 | self.0[position.zx() as usize] = value; 17 | } 18 | } 19 | 20 | impl Add for Layer where T: Copy + AddAssign { 21 | type Output = Self; 22 | 23 | fn add(mut self, rhs: Self) -> Self::Output { 24 | for x in 0..256 { 25 | // TODO: Iterators. 26 | self.0[x] += rhs.0[x]; 27 | } 28 | 29 | self 30 | } 31 | } 32 | 33 | impl AddAssign for Layer where T: Copy + AddAssign { 34 | fn add_assign(&mut self, rhs: Self) { 35 | for x in 0..256 { 36 | // TODO: Iterators. 37 | self.0[x] += rhs.0[x]; 38 | } 39 | } 40 | } 41 | 42 | pub trait Sample { 43 | type Output: Default + Copy; 44 | 45 | /// Coordinates are in block space 46 | fn sample(&self, point: Point2) -> Self::Output; 47 | 48 | /// An optimized version of this function is usually provided by the implementor. 49 | fn chunk(&self, chunk: (f64, f64)) -> Layer { 50 | let mut out = Layer::fill(Self::Output::default()); 51 | let chunk = Point2::new(chunk.0, chunk.1); 52 | 53 | for index in 0..=255 { 54 | let position = LayerPosition::from_zx(index); 55 | let point = chunk + Vector2::new(position.x() as f64, position.z() as f64); 56 | 57 | out.set(position, self.sample(point)); 58 | } 59 | 60 | out 61 | } 62 | } -------------------------------------------------------------------------------- /src/segmented.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct Segmented { 3 | segments: Vec>, 4 | out: T 5 | } 6 | 7 | impl Segmented { 8 | pub fn new(default: T) -> Self { 9 | Segmented { 10 | segments: Vec::new(), 11 | out: default 12 | } 13 | } 14 | 15 | pub fn default_out(&self) -> &T { 16 | &self.out 17 | } 18 | 19 | fn segment(&self, at: f64, always_inclusive: bool) -> Option<(&Segment, usize)> { 20 | let last_idx = if self.segments.len() > 0 { Some(self.segments.len() - 1) } else { None }; 21 | 22 | for (index, segment) in self.segments.iter().enumerate() { 23 | if (at < segment.upper) || ((Some(index) == last_idx || always_inclusive) && at == segment.upper) { 24 | return Some((segment, index)); 25 | } 26 | } 27 | 28 | None 29 | } 30 | 31 | fn segment_mut(&mut self, at: f64, always_inclusive: bool) -> Option<(&mut Segment, usize)> { 32 | let last_idx = if self.segments.len() > 0 { Some(self.segments.len() - 1) } else { None }; 33 | 34 | for (index, segment) in self.segments.iter_mut().enumerate() { 35 | if (at < segment.upper) || ((Some(index) == last_idx || always_inclusive) && at == segment.upper) { 36 | return Some((segment, index)); 37 | } 38 | } 39 | 40 | None 41 | } 42 | 43 | pub fn add_boundary(&mut self, upper: f64, value: T) { 44 | if let Some((seg_upper, index)) = self.segment(upper, true).map(|(seg, index)| (seg.upper, index)) { 45 | if seg_upper == upper { 46 | self.segment_mut(upper, true).unwrap().0.value = value; 47 | } else { 48 | self.segments.insert(index, Segment { upper, value }); 49 | } 50 | } else { 51 | let len = self.segments.len(); 52 | self.segments.insert(len, Segment { upper, value }); 53 | } 54 | } 55 | 56 | pub fn get(&self, at: f64) -> &T { 57 | self.segment(at, false).map(|seg| &seg.0.value).unwrap_or(&self.out) 58 | } 59 | } 60 | 61 | impl Segmented where T: Clone { 62 | pub fn for_all_aligned(&mut self, lower: f64, upper: f64, above: &A, on: &F) where A: Fn() -> T, F: Fn(&mut T) { 63 | self.align(lower, upper, above); 64 | 65 | let mut last_upper = None; 66 | 67 | for segment in &mut self.segments { 68 | let above = last_upper.map(|last_upper| lower <= last_upper).unwrap_or(true); 69 | 70 | if above && lower < segment.upper && segment.upper <= upper { 71 | on(&mut segment.value) 72 | } 73 | 74 | last_upper = Some(segment.upper); 75 | } 76 | } 77 | 78 | /// Makes sure that 1 segment has an upper equal to the lower and 1 segment has an upper equal to the upper. 79 | pub fn align(&mut self, lower: f64, upper: f64, above: &F) where F: Fn() -> T { 80 | let split_index = self.segment(lower, true).map(|(_, index)| index).unwrap_or(self.segments.len() - 1); 81 | self.split(split_index, lower, above); 82 | 83 | let end_index = self.segment(upper, true).map(|(_, index)| index).unwrap_or(self.segments.len() - 1); 84 | self.split(end_index, upper, above); 85 | } 86 | 87 | fn split(&mut self, index: usize, new_boundary: f64, above: &F) where F: Fn() -> T { 88 | if self.segments[index].upper == new_boundary { 89 | return; 90 | } 91 | 92 | let before = self.segments[index].upper > new_boundary; 93 | 94 | let (index, value) = if before { 95 | (index, self.segments[index].value.clone()) 96 | } else { 97 | (index + 1, above()) 98 | }; 99 | 100 | self.segments.insert(index, Segment { upper: new_boundary, value }) 101 | } 102 | } 103 | 104 | impl Segmented> where T: Clone { 105 | // TODO: insert a rect 106 | } 107 | 108 | #[derive(Clone, Debug)] 109 | struct Segment { 110 | upper: f64, 111 | value: T 112 | } -------------------------------------------------------------------------------- /src/structure/caves.rs: -------------------------------------------------------------------------------- 1 | use java_rand::Random; 2 | use trig; 3 | use std::cmp::{min, max}; 4 | use distribution::{Distribution, Chance, Linear, Packed2, Packed3, ChanceOrdering}; 5 | use structure::StructureGenerator; 6 | use vocs::indexed::Target; 7 | use vocs::view::{ColumnMut, ColumnBlocks, ColumnPalettes, ColumnAssociation}; 8 | use vocs::position::{ColumnPosition, GlobalColumnPosition}; 9 | use matcher::BlockMatcher; 10 | 11 | const NOTCH_PI: f32 = 3.141593; 12 | const PI_DIV_2: f32 = 1.570796; 13 | const MIN_H_SIZE: f64 = 1.5; 14 | 15 | /// Make many chunks not spawn cave starts at all, otherwise the world would look like swiss cheese. 16 | /// Note that caves starting in other chunks can still carve through this chunk. 17 | /// Offsets the fact that a single cave start can branch many times. 18 | /// Also make most chunks that do contain caves contain few, but have the potential to contain many. 19 | pub static RARITY: Chance = Chance { 20 | base: Packed3 { max: 39 }, 21 | chance: 15, 22 | ordering: ChanceOrdering::AlwaysGeneratePayload 23 | }; 24 | 25 | /// Allow caves at high altitudes, but make most of them spawn underground. 26 | pub static HEIGHT: Packed2 = Packed2 { min: 0, linear_start: 8, max: 126 }; 27 | 28 | /// More chunks will have cave starts, but they will have less in each one. 29 | /// Results in less caves overall, since a chunk is 3x more likely to have cave starts, 30 | /// but will have a maximum that is 4x less. 31 | pub static RARITY_NETHER: Chance = Chance { 32 | base: Packed3 { max: 9 }, 33 | chance: 5, 34 | ordering: ChanceOrdering::AlwaysGeneratePayload 35 | }; 36 | 37 | /// Since the Nether has a high amount of solid blocks from bottom to top, caves spawn uniformly. 38 | pub static HEIGHT_NETHER: Linear = Linear { 39 | min: 0, 40 | max: 127 41 | }; 42 | 43 | /// Mimics Java rounding rules and avoids UB from float casts. 44 | fn floor_capped(t: f64) -> i32 { 45 | t.floor().max(-2147483648.0).min(2147483647.0) as i32 46 | } 47 | 48 | struct CavesAssociations { 49 | carve: ColumnAssociation, 50 | lower: ColumnAssociation, 51 | surface: ColumnAssociation 52 | } 53 | 54 | // Overworld: CavesGenerator { carve: air, ocean: [ flowing_water, still_water ], carvable: [ stone, dirt, grass ], blob_size_multiplier: 1.0, vertical_multiplier: 1.0 } 55 | // Nether: CavesGenerator { carve: air, ocean: [ flowing_lava, still_lava ], carvable: [ netherrack, dirt, grass ], blob_size_multiplier: 2.0, vertical_multiplier: 0.5} 56 | 57 | pub struct CavesGenerator where B: Target { 58 | pub carve: B, 59 | pub lower: B, 60 | pub surface_block: B, 61 | pub ocean: BlockMatcher, 62 | pub surface_top: BlockMatcher, 63 | pub surface_fill: BlockMatcher, 64 | pub carvable: BlockMatcher, 65 | pub blob_size_multiplier: f32, 66 | pub vertical_multiplier: f64, 67 | pub lower_surface: u8 68 | } 69 | 70 | impl CavesGenerator where B: Target { 71 | fn carve_blob(&self, blob: Blob, associations: &CavesAssociations, blocks: &mut ColumnBlocks, palette: &ColumnPalettes, chunk: GlobalColumnPosition) { 72 | let chunk_block = ((chunk.x() * 16) as f64, (chunk.z() * 16) as f64); 73 | 74 | // Try to make sure that we don't carve into the ocean. 75 | // However, this misses chunk boundaries - there is no easy way to fix this. 76 | 77 | for z in blob.lower.2..blob.upper.2 { 78 | for x in blob.lower.0..blob.upper.0 { 79 | let mut y = (blob.upper.1 + 1) as i32; 80 | 81 | while y >= (blob.lower.1 - 1) as i32 { 82 | if y < 0 || y >= 128 { 83 | y -= 1; 84 | continue; 85 | } 86 | 87 | let block = ColumnPosition::new(x, y as u8, z); 88 | 89 | if self.ocean.matches(blocks.get(block, palette)) { 90 | return; 91 | } 92 | 93 | // Optimization: Only check the edges. 94 | if y != (blob.lower.1 - 1) as i32 95 | && x != blob.lower.0 && x != blob.upper.0 - 1 96 | && z != blob.lower.2 && z != blob.upper.2 - 1 { 97 | // If it ain't on any of the other 5 sides, check the bottom and skip the interior of the volume. 98 | y = blob.lower.1 as i32; 99 | } 100 | 101 | y -= 1; 102 | } 103 | } 104 | } 105 | 106 | // TODO: FloorY 107 | // block.1 > (-0.7) * blob.size.vertical + blob.center.1 - 0.5 108 | 109 | for z in blob.lower.2..blob.upper.2 { 110 | for x in blob.lower.0..blob.upper.0 { 111 | let mut hit_surface_top = false; 112 | 113 | // Need to go downwards so that the grass gets pulled down. 114 | for y in (blob.lower.1..blob.upper.1).rev() { 115 | let position = ColumnPosition::new(x, y, z); 116 | 117 | let block = (x as f64, y as f64, z as f64); 118 | 119 | let scaled = ( 120 | (block.0 + chunk_block.0 + 0.5 - blob.center.0) / blob.size.horizontal, 121 | (block.1 + 0.5 - blob.center.1) / blob.size.vertical, 122 | (block.2 + chunk_block.1 + 0.5 - blob.center.2) / blob.size.horizontal 123 | ); 124 | 125 | // TODO: Grass pulldown sometimes is inconsistent? 126 | 127 | // Test if the block is within the blob region. Additionally, the y > -0.7 check makes the floors flat. 128 | if scaled.1 > -0.7 && scaled.0 * scaled.0 + scaled.1 * scaled.1 + scaled.2 * scaled.2 < 1.0 { 129 | let block = blocks.get(position, palette); 130 | 131 | if self.surface_top.matches(block) { 132 | hit_surface_top = true; 133 | } 134 | 135 | if !self.carvable.matches(block) { 136 | continue; 137 | } 138 | 139 | if y < self.lower_surface { 140 | blocks.set(position, &associations.lower); 141 | } else { 142 | blocks.set(position, &associations.carve); 143 | 144 | if y > 0 && hit_surface_top { 145 | let below = ColumnPosition::new(x, y - 1, z); 146 | 147 | if self.surface_fill.matches(blocks.get(below, palette)) { 148 | blocks.set(below, &associations.surface); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | fn carve_tunnel(&self, mut tunnel: Tunnel, caves: &mut Caves, associations: &CavesAssociations, blocks: &mut ColumnBlocks, palette: &ColumnPalettes, chunk: GlobalColumnPosition, from: GlobalColumnPosition, radius: u32) { 159 | loop { 160 | let outcome = tunnel.step(self.vertical_multiplier); 161 | 162 | match outcome { 163 | Outcome::Split => { 164 | let (a, b) = tunnel.split(caves); 165 | 166 | self.carve_tunnel(a, caves, associations, blocks, palette, chunk, from, radius); 167 | self.carve_tunnel(b, caves, associations, blocks, palette, chunk, from, radius); 168 | 169 | return 170 | }, 171 | Outcome::Constrict => (), 172 | Outcome::Unreachable => return, 173 | Outcome::OutOfChunk => (), 174 | Outcome::Carve(blob) => self.carve_blob(blob, associations, blocks, palette, chunk), 175 | Outcome::Done => return 176 | } 177 | } 178 | } 179 | } 180 | 181 | impl StructureGenerator for CavesGenerator where B: Target { 182 | fn generate(&self, random: Random, column: &mut ColumnMut, chunk: GlobalColumnPosition, from: GlobalColumnPosition, radius: u32) { 183 | let mut caves = Caves::for_chunk(random, chunk, from, radius, self.blob_size_multiplier); 184 | 185 | column.ensure_available(self.carve.clone()); 186 | column.ensure_available(self.lower.clone()); 187 | column.ensure_available(self.surface_block.clone()); 188 | 189 | let (mut blocks, palette) = column.freeze_palette(); 190 | 191 | let associations = CavesAssociations { 192 | carve: palette.reverse_lookup(&self.carve).unwrap(), 193 | lower: palette.reverse_lookup(&self.lower).unwrap(), 194 | surface: palette.reverse_lookup(&self.surface_block).unwrap() 195 | }; 196 | 197 | while let Some(start) = caves.next() { 198 | match start { 199 | Start::Tunnel(tunnel) => self.carve_tunnel(tunnel, &mut caves, &associations, &mut blocks, &palette, chunk, from, radius), 200 | Start::Circular(Some(blob)) => self.carve_blob(blob, &associations, &mut blocks, &palette, chunk), 201 | Start::Circular(None) => () 202 | }; 203 | } 204 | } 205 | } 206 | 207 | #[derive(Debug)] 208 | pub struct Caves { 209 | state: Random, 210 | chunk: GlobalColumnPosition, 211 | from: GlobalColumnPosition, 212 | remaining: u32, 213 | max_chunk_radius: u32, 214 | blob_size_multiplier: f32, 215 | extra: Option<(u32, (f64, f64, f64))> 216 | } 217 | 218 | impl Caves { 219 | pub fn for_chunk(mut state: Random, chunk: GlobalColumnPosition, from: GlobalColumnPosition, radius: u32, blob_size_multiplier: f32) -> Caves { 220 | let remaining = RARITY.next(&mut state); 221 | 222 | Caves { state, chunk, from, remaining, extra: None, max_chunk_radius: radius, blob_size_multiplier } 223 | } 224 | } 225 | 226 | impl Iterator for Caves { 227 | type Item = Start; 228 | 229 | fn next(&mut self) -> Option { 230 | if self.remaining == 0 { 231 | return None; 232 | } 233 | 234 | self.remaining -= 1; 235 | 236 | if let &mut Some((ref mut extra, orgin)) = &mut self.extra { 237 | if *extra > 0 { 238 | *extra -= 1; 239 | 240 | return Some(Start::normal(&mut self.state, self.chunk, orgin, self.max_chunk_radius, self.blob_size_multiplier)); 241 | } 242 | } 243 | 244 | self.extra = None; 245 | 246 | let x = self.state.next_i32_bound(16); 247 | let mut y = self.state.next_u32_bound(120); 248 | y = self.state.next_u32_bound(y + 8); 249 | let z = self.state.next_i32_bound(16); 250 | 251 | let orgin = ( 252 | (self.from.x() * 16 + x) as f64, 253 | y as f64, 254 | (self.from.z() * 16 + z) as f64 255 | ); 256 | 257 | if self.state.next_u32_bound(4) == 0 { 258 | let circular = Start::circular(&mut self.state, self.chunk, orgin, self.max_chunk_radius); 259 | let extra = 1 + self.state.next_u32_bound(4); 260 | 261 | self.remaining += extra; 262 | self.extra = Some((extra, orgin)); 263 | 264 | Some(circular) 265 | } else { 266 | Some(Start::normal(&mut self.state, self.chunk, orgin, self.max_chunk_radius, self.blob_size_multiplier)) 267 | } 268 | } 269 | } 270 | 271 | #[derive(Debug)] 272 | pub enum Start { 273 | Circular(Option), 274 | Tunnel(Tunnel) 275 | } 276 | 277 | impl Start { 278 | fn normal(rng: &mut Random, chunk: GlobalColumnPosition, block: (f64, f64, f64), max_chunk_radius: u32, blob_size_multiplier: f32) -> Self { 279 | Start::Tunnel(Tunnel::normal(rng, chunk, block, max_chunk_radius, blob_size_multiplier)) 280 | } 281 | 282 | fn circular(rng: &mut Random, chunk: GlobalColumnPosition, block: (f64, f64, f64), max_chunk_radius: u32) -> Self { 283 | let blob_size_factor = 1.0 + rng.next_f32() * 6.0; 284 | let mut state = Random::new(rng.next_u64()); 285 | 286 | let mut size = SystemSize::new(&mut state, 0, max_chunk_radius); 287 | size.current = size.max / 2; 288 | 289 | let size = BlobSize::from_horizontal( 290 | MIN_H_SIZE + (trig::sin(size.current as f32 * NOTCH_PI / size.max as f32) * blob_size_factor) as f64, 291 | 0.5 292 | ); 293 | 294 | let position = Position::new(chunk, (block.0 + 1.0, block.1, block.2)); 295 | 296 | Start::Circular(if position.out_of_chunk(&size) { 297 | None 298 | } else { 299 | Some(position.blob(size)) 300 | }) 301 | } 302 | } 303 | 304 | #[derive(Debug)] 305 | pub struct Tunnel { 306 | state: Random, 307 | position: Position, 308 | size: SystemSize, 309 | split: Option, 310 | /// 0.92 = Steep, 0.7 = Normal 311 | pitch_keep: f32, 312 | blob_size_factor: f32 313 | } 314 | 315 | impl Tunnel { 316 | fn normal(rng: &mut Random, chunk: GlobalColumnPosition, block: (f64, f64, f64), max_chunk_radius: u32, blob_size_multiplier: f32) -> Self { 317 | let position = Position::with_angles(chunk, block, rng.next_f32() * NOTCH_PI * 2.0, (rng.next_f32() - 0.5) / 4.0); 318 | let blob_size_factor = (rng.next_f32() * 2.0 + rng.next_f32()) * blob_size_multiplier; 319 | 320 | let mut state = Random::new(rng.next_u64()); 321 | 322 | let size = SystemSize::new(&mut state, 0, max_chunk_radius); 323 | 324 | Tunnel { 325 | position, 326 | size, 327 | split: size.split(&mut state, blob_size_factor), 328 | pitch_keep: if state.next_u32_bound(6) == 0 { 0.92 } else { 0.7 }, 329 | blob_size_factor, 330 | state 331 | } 332 | } 333 | 334 | fn split_off(&mut self, rng: &mut Random, yaw_offset: f32) -> Tunnel { 335 | let position = Position::with_angles(self.position.chunk, self.position.block, self.position.yaw + yaw_offset, self.position.pitch / 3.0); 336 | let blob_size_factor = self.state.next_f32() * 0.5 + 0.5; 337 | 338 | let mut state = Random::new(rng.next_u64()); 339 | 340 | let size = self.size; 341 | 342 | Tunnel { 343 | position, 344 | size, 345 | split: size.split(&mut state, blob_size_factor), 346 | pitch_keep: if state.next_u32_bound(6) == 0 { 0.92 } else { 0.7 }, 347 | blob_size_factor, 348 | state 349 | } 350 | } 351 | 352 | fn split(&mut self, caves: &mut Caves) -> (Tunnel, Tunnel) { 353 | // https://bugs.mojang.com/browse/MC-7196 354 | // First bug resulting in chunk cliffs, that we have to recreate 355 | // The tunnel splitting calls back to the root RNG, causing discontinuities if is_chunk_unreachable() returns true before the cave splits. 356 | // If the is_chunk_unreachable optimization is disabled, this issue doesn't occur. 357 | // It also wrecks the nice, clean iterator implementation, as we have to pass the RNG down. Ugh. 358 | 359 | (self.split_off(&mut caves.state, -PI_DIV_2), self.split_off(&mut caves.state, PI_DIV_2)) 360 | } 361 | 362 | fn is_chunk_unreachable(&self) -> bool { 363 | // https://bugs.mojang.com/browse/MC-7200 364 | // Second bug resulting in chunk cliffs, that we have to recreate. 365 | // Using addition/subtraction with distance squared math is invalid. 366 | 367 | let remaining = (self.size.max - self.size.current) as f64; 368 | 369 | // Conservative buffer distance that accounts for the size of each carved part. 370 | let buffer = (self.blob_size_factor * 2.0 + 16.0) as f64; 371 | 372 | // Invalid: Subtraction from distance squared. 373 | self.position.distance_from_chunk_squared() - remaining * remaining > buffer * buffer 374 | } 375 | 376 | fn next_blob_size(&self) -> f64 { 377 | MIN_H_SIZE + (trig::sin(self.size.current as f32 * NOTCH_PI / self.size.max as f32) * self.blob_size_factor) as f64 378 | } 379 | 380 | pub fn step(&mut self, vertical_multiplier: f64) -> Outcome { 381 | if self.size.done() { 382 | return Outcome::Done; 383 | } 384 | 385 | self.position.step(&mut self.state, self.pitch_keep); 386 | 387 | if self.size.should_split(self.split) { 388 | return Outcome::Split; 389 | } 390 | 391 | if self.state.next_u32_bound(4) == 0 { 392 | self.size.step(); 393 | return Outcome::Constrict; 394 | } 395 | 396 | if self.is_chunk_unreachable() { 397 | return Outcome::Unreachable; 398 | } 399 | 400 | let size = BlobSize::from_horizontal(self.next_blob_size(), vertical_multiplier); 401 | 402 | if self.position.out_of_chunk(&size) { 403 | self.size.step(); 404 | return Outcome::OutOfChunk; 405 | } 406 | 407 | let blob = self.position.blob(size); 408 | 409 | self.size.step(); 410 | 411 | Outcome::Carve(blob) 412 | } 413 | } 414 | 415 | #[derive(Debug, Copy, Clone)] 416 | struct SystemSize { 417 | current: u32, 418 | max: u32 419 | } 420 | 421 | impl SystemSize { 422 | fn new(rng: &mut Random, current: u32, max_chunk_radius: u32) -> Self { 423 | let max_block_radius = max_chunk_radius * 16 - 16; 424 | let max = max_block_radius - rng.next_u32_bound(max_block_radius / 4); 425 | 426 | SystemSize { current, max } 427 | } 428 | 429 | pub fn step(&mut self) { 430 | self.current += 1; 431 | } 432 | 433 | pub fn done(&self) -> bool { 434 | self.current >= self.max 435 | } 436 | 437 | pub fn should_split(&self, split_threshold: Option) -> bool { 438 | Some(self.current) == split_threshold 439 | } 440 | 441 | /// Returns the point where the tunnel will split into 2. Returns None if it won't split. 442 | fn split(&self, rng: &mut Random, blob_size_factor: f32) -> Option { 443 | let split = rng.next_u32_bound(self.max / 2) + self.max / 4; 444 | 445 | if blob_size_factor > 1.0 {Some(split)} else {None} 446 | } 447 | } 448 | 449 | #[derive(Debug, Copy, Clone)] 450 | struct Position { 451 | /// Position of the chunk being carved 452 | chunk: GlobalColumnPosition, 453 | /// Block position of the center of the generated chunk. 454 | chunk_center: (f64, f64), 455 | /// Absolute block position in the world 456 | block: (f64, f64, f64), 457 | /// Horizontal angle 458 | yaw: f32, 459 | /// Vertical angle 460 | pitch: f32, 461 | /// Rate of change for the horizontal angle 462 | yaw_velocity: f32, 463 | /// Rate of change for the vertical angle 464 | pitch_velocity: f32 465 | } 466 | 467 | impl Position { 468 | fn new(chunk: GlobalColumnPosition, block: (f64, f64, f64)) -> Self { 469 | Position::with_angles(chunk, block, 0.0, 0.0) 470 | } 471 | 472 | fn with_angles(chunk: GlobalColumnPosition, block: (f64, f64, f64), yaw: f32, pitch: f32) -> Self { 473 | Position { 474 | chunk, 475 | chunk_center: ((chunk.x() * 16 + 8) as f64, (chunk.z() * 16 + 8) as f64), 476 | block, 477 | yaw, 478 | pitch, 479 | yaw_velocity: 0.0, 480 | pitch_velocity: 0.0 481 | } 482 | } 483 | 484 | fn step(&mut self, rng: &mut Random, pitch_keep: f32) { 485 | let cos_pitch = trig::cos(self.pitch); 486 | 487 | self.block.0 += (trig::cos(self.yaw) * cos_pitch) as f64; 488 | self.block.1 += trig::sin(self.pitch) as f64; 489 | self.block.2 += (trig::sin(self.yaw) * cos_pitch) as f64; 490 | 491 | self.pitch *= pitch_keep; 492 | self.pitch += self.pitch_velocity * 0.1; 493 | self.yaw += self.yaw_velocity * 0.1; 494 | 495 | self.pitch_velocity *= 0.9; 496 | self.yaw_velocity *= 0.75; 497 | self.pitch_velocity += (rng.next_f32() - rng.next_f32()) * rng.next_f32() * 2.0; 498 | self.yaw_velocity += (rng.next_f32() - rng.next_f32()) * rng.next_f32() * 4.0; 499 | } 500 | 501 | fn distance_from_chunk_squared(&self) -> f64 { 502 | let distance_x = self.block.0 - self.chunk_center.0; 503 | let distance_z = self.block.2 - self.chunk_center.1; 504 | 505 | distance_x * distance_x + distance_z * distance_z 506 | } 507 | 508 | fn out_of_chunk(&self, blob: &BlobSize) -> bool { 509 | let horizontal_diameter = blob.horizontal_diameter(); 510 | 511 | self.block.0 < self.chunk_center.0 - 16.0 - horizontal_diameter || 512 | self.block.2 < self.chunk_center.1 - 16.0 - horizontal_diameter || 513 | self.block.0 > self.chunk_center.0 + 16.0 + horizontal_diameter || 514 | self.block.2 > self.chunk_center.1 + 16.0 + horizontal_diameter 515 | } 516 | 517 | fn blob(&self, size: BlobSize) -> Blob { 518 | let lower = ( 519 | floor_capped(self.block.0 - size.horizontal) - self.chunk.x() * 16 - 1, 520 | floor_capped(self.block.1 - size.vertical) - 1, 521 | floor_capped(self.block.2 - size.horizontal) - self.chunk.z() * 16 - 1 522 | ); 523 | 524 | let upper = ( 525 | floor_capped(self.block.0 + size.horizontal) - self.chunk.x() * 16 + 1, 526 | floor_capped(self.block.1 + size.vertical) + 1, 527 | floor_capped(self.block.2 + size.horizontal) - self.chunk.z() * 16 + 1 528 | ); 529 | 530 | Blob { 531 | center: self.block, 532 | size, 533 | lower: ( 534 | min(max(lower.0, 0), 16) as u8, 535 | min(max(lower.1, 1), 255) as u8, 536 | min(max(lower.2, 0), 16) as u8 537 | ), 538 | upper: ( 539 | min(max(upper.0, 0), 16) as u8, 540 | min(max(upper.1, 0), 120) as u8, 541 | min(max(upper.2, 0), 16) as u8 542 | ) 543 | } 544 | } 545 | } 546 | 547 | #[derive(Debug)] 548 | pub enum Outcome { 549 | Split, 550 | Constrict, 551 | Unreachable, 552 | OutOfChunk, 553 | Carve(Blob), 554 | Done 555 | } 556 | 557 | impl Outcome { 558 | pub fn continues(&self) -> bool { 559 | match *self { 560 | Outcome::Split => false, 561 | Outcome::Constrict => true, 562 | Outcome::Unreachable => false, 563 | Outcome::OutOfChunk => true, 564 | Outcome::Carve(_) => true, 565 | Outcome::Done => false 566 | } 567 | } 568 | } 569 | 570 | #[derive(Debug, Copy, Clone)] 571 | pub struct BlobSize { 572 | /// Radius on the X/Z axis 573 | pub horizontal: f64, 574 | /// Radius on the Y axis 575 | pub vertical: f64 576 | } 577 | 578 | impl BlobSize { 579 | fn from_horizontal(horizontal: f64, vertical_multiplier: f64) -> Self { 580 | BlobSize { 581 | horizontal, 582 | vertical: horizontal * vertical_multiplier 583 | } 584 | } 585 | 586 | fn horizontal_diameter(&self) -> f64 { 587 | self.horizontal * 2.0 588 | } 589 | } 590 | 591 | #[derive(Debug)] 592 | pub struct Blob { 593 | /// Center of the blob 594 | pub center: (f64, f64, f64), 595 | /// Size of the blob 596 | pub size: BlobSize, 597 | /// Lower bounds of the feasible region, in chunk coordinates: [0,16), [0,128), [0,16) 598 | pub lower: (u8, u8, u8), 599 | /// Upper bounds of the feasible region, in chunk coordiantes: [0,16), [0,128), [0,16) 600 | pub upper: (u8, u8, u8) 601 | } -------------------------------------------------------------------------------- /src/structure/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod caves; 2 | // TODO: Should mineshafts/villages be implemented?: pub mod organized; 3 | 4 | use java_rand::Random; 5 | use vocs::view::ColumnMut; 6 | use vocs::indexed::Target; 7 | use vocs::position::GlobalColumnPosition; 8 | use std::marker::PhantomData; 9 | use generator::Pass; 10 | 11 | pub struct StructureGenerateNearby where T: StructureGenerator, B: Target { 12 | seed_coefficients: (i64, i64), 13 | radius: u32, 14 | diameter: u32, 15 | world_seed: u64, 16 | generator: T, 17 | phantom: PhantomData 18 | } 19 | 20 | impl StructureGenerateNearby where T: StructureGenerator, B: Target { 21 | pub fn new(world_seed: u64, radius: u32, generator: T) -> Self { 22 | let mut rng = Random::new(world_seed); 23 | 24 | StructureGenerateNearby { 25 | seed_coefficients: ( 26 | ((rng.next_i64() >> 1) << 1) + 1, 27 | ((rng.next_i64() >> 1) << 1) + 1 28 | ), 29 | radius, 30 | diameter: radius * 2, 31 | world_seed, 32 | generator, 33 | phantom: PhantomData 34 | } 35 | } 36 | } 37 | 38 | impl Pass for StructureGenerateNearby where T: StructureGenerator, B: Target { 39 | fn apply(&self, target: &mut ColumnMut, chunk: GlobalColumnPosition) { 40 | let radius = self.radius as i32; 41 | 42 | for x in (0..self.diameter).map(|x| chunk.x() + (x as i32) - radius) { 43 | for z in (0..self.diameter).map(|z| chunk.z() + (z as i32) - radius) { 44 | let x_part = (x as i64).wrapping_mul(self.seed_coefficients.0) as u64; 45 | let z_part = (z as i64).wrapping_mul(self.seed_coefficients.1) as u64; 46 | 47 | let seed = (x_part.wrapping_add(z_part)) ^ self.world_seed; 48 | let from = GlobalColumnPosition::new(x, z); 49 | 50 | self.generator.generate(Random::new(seed), target, chunk, from, self.radius); 51 | } 52 | } 53 | } 54 | } 55 | 56 | pub trait StructureGenerator where B: Target { 57 | fn generate(&self, random: Random, column: &mut ColumnMut, chunk_pos: GlobalColumnPosition, from: GlobalColumnPosition, radius: u32); 58 | } -------------------------------------------------------------------------------- /src/trig.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/sin_table.rs")); 2 | 3 | pub fn sin(f: f32) -> f32 { 4 | sin_index(((f * 10430.38) as i32) as u32) 5 | } 6 | 7 | pub fn cos(f: f32) -> f32 { 8 | sin_index(((f * 10430.38 + 16384.0) as i32) as u32) 9 | } 10 | 11 | fn sin_index(idx: u32) -> f32 { 12 | let idx = idx & 0xFFFF; 13 | 14 | let neg = (idx & 0x8000) << 16; 15 | let idx2 = idx & 0x7FFF; 16 | let invert = (idx & 0x4000) >> 14; 17 | 18 | let full_invert = 0u32.wrapping_sub(invert); 19 | let sub_from = (invert << 15) + invert; 20 | let idx3 = ::std::cmp::min(sub_from.wrapping_add(idx2 ^ full_invert), 16383); 21 | 22 | let wierd = (idx == 32768) as u32; 23 | 24 | let raw = (SIN_TABLE[idx3 as usize] ^ neg).wrapping_add(wierd * 0xA50D3132); 25 | 26 | f32::from_bits(raw) 27 | } 28 | 29 | #[cfg(test)] 30 | mod test { 31 | #[test] 32 | fn test_sin() { 33 | let java = ::test::read_u32s("JavaSinTable"); 34 | 35 | assert_eq!(java.len(), 65536); 36 | 37 | for index in 0..65536usize { 38 | let r = super::sin_index(index as u32).to_bits(); 39 | let j = java[index]; 40 | 41 | if r != j { 42 | panic!("trig::test_sin: mismatch @ index {}: {} (R) != {} (J)", index, r, j); 43 | } 44 | } 45 | } 46 | } --------------------------------------------------------------------------------