├── .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 |
11 |
12 |
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