///|
priv enum Block {
Air
Grass
Dirt
Stone
Sand
Water
Wood
Leaves
Bedrock
Planks
Brick
Cobblestone
Gravel
Clay
Snow
} derive(Eq)
///|
priv enum Biome {
Ocean
Plains
Forest
Hills
Mountains
Desert
Mesa
River
Snow
Karst
} derive(Eq)
///|
priv struct ColumnInfo {
height : Int
biome : Biome
ridge : Double
moist : Double
coast : Double
}
///|
priv struct RawColumn {
height : Double
biome : Biome
ridge : Double
moist : Double
coast : Double
}
///|
priv struct Chunk {
blocks : Array[Block]
modified : Bool
}
///|
priv struct World {
chunk_size : Int
height : Int
section_height : Int
sea_level : Int
chunks : @hashmap.HashMap[String, Chunk]
mut chunk_keys : Array[String]
height_cache : @hashmap.HashMap[String, Array[Int]]
mut height_keys : Array[String]
}
///|
fn World::new(height : Int, chunk_size : Int, section_height : Int) -> World {
let sea_level = 240
{
chunk_size,
height,
section_height,
sea_level,
chunks: @hashmap.new(capacity=64),
chunk_keys: [],
height_cache: @hashmap.new(capacity=64),
height_keys: [],
}
}
///|
fn chunk_key(cx : Int, cz : Int) -> String {
"\{cx},\{cz}"
}
///|
fn chunk_key_3(cx : Int, cy : Int, cz : Int) -> String {
"\{cx},\{cy},\{cz}"
}
///|
fn div_floor(a : Int, b : Int) -> Int {
let mut q = a / b
let r = a % b
if r != 0 && (r > 0) != (b > 0) {
q = q - 1
}
q
}
///|
fn mod_floor(a : Int, b : Int) -> Int {
a - div_floor(a, b) * b
}
///|
fn lerp(a : Double, b : Double, t : Double) -> Double {
a + (b - a) * t
}
///|
fn smoothstep(t : Double) -> Double {
t * t * (3.0 - 2.0 * t)
}
///|
fn clamp01(v : Double) -> Double {
if v < 0.0 {
0.0
} else if v > 1.0 {
1.0
} else {
v
}
}
///|
fn hash2(x : Int, z : Int) -> Double {
let mut n = x * 374761393 + z * 668265263 + 0x9E3779B9
n = (n ^ (n >> 13)) * 1274126177
n = n ^ (n >> 16)
let v = n & 0x7fffffff
v.to_double() / 2147483647.0
}
///|
fn value_noise(x : Double, z : Double) -> Double {
let x0 = x.floor().to_int()
let z0 = z.floor().to_int()
let x1 = x0 + 1
let z1 = z0 + 1
let fx = x - x0.to_double()
let fz = z - z0.to_double()
let u = smoothstep(fx)
let v = smoothstep(fz)
let n00 = hash2(x0, z0)
let n10 = hash2(x1, z0)
let n01 = hash2(x0, z1)
let n11 = hash2(x1, z1)
let nx0 = lerp(n00, n10, u)
let nx1 = lerp(n01, n11, u)
lerp(nx0, nx1, v)
}
///|
fn fbm(x : Double, z : Double) -> Double {
let mut sum = 0.0
let mut amp = 1.0
let mut freq = 1.0
let mut norm = 0.0
for i = 0; i < 4; i = i + 1 {
sum = sum + value_noise(x * freq, z * freq) * amp
norm = norm + amp
amp = amp * 0.5
freq = freq * 2.0
}
sum / norm
}
///|
fn ridged(x : Double, z : Double) -> Double {
let n = fbm(x, z)
let r = 1.0 - (2.0 * n - 1.0).abs()
r * r
}
///|
fn warp_coords(x : Double, z : Double) -> (Double, Double) {
let warp_x = fbm(x * 0.0015, z * 0.0015)
let warp_z = fbm(x * 0.0015 + 17.3, z * 0.0015 - 11.7)
let wx = x + (warp_x - 0.5) * 48.0
let wz = z + (warp_z - 0.5) * 48.0
(wx, wz)
}
///|
fn terrace(height : Double, step : Double) -> Double {
let base = (height / step).floor() * step
let frac = height - base
base + frac * 0.2
}
///|
fn biome_from(
temp : Double,
moist : Double,
ridge : Double,
land : Double,
) -> Biome {
if land < 0.16 {
Biome::Ocean
} else if temp > 0.7 && moist < 0.34 {
if ridge > 0.55 {
Biome::Mesa
} else {
Biome::Desert
}
} else if ridge > 0.62 {
Biome::Hills
} else if moist > 0.62 {
Biome::Forest
} else {
Biome::Plains
}
}
///|
fn tree_peak(gx : Int, gz : Int, radius : Int) -> (Bool, Double) {
let noise = hash2(gx, gz)
for dz = -radius; dz <= radius; dz = dz + 1 {
for dx = -radius; dx <= radius; dx = dx + 1 {
if dx == 0 && dz == 0 {
continue
}
let other = hash2(gx + dx, gz + dz)
if other >= noise {
return (false, noise)
}
}
}
(true, noise)
}
///|
fn World::chunk_index(self : World, lx : Int, y : Int, lz : Int) -> Int {
lx + self.chunk_size * (y + self.section_height * lz)
}
///|
///|
///|
fn World::prune_chunks(
self : World,
keep : @hashmap.HashMap[String, Bool],
) -> Unit {
let next_keys : Array[String] = []
for i = 0; i < self.chunk_keys.length(); i = i + 1 {
let key = self.chunk_keys[i]
match self.chunks.get(key) {
Some(chunk) =>
if chunk.modified || keep.contains(key) {
next_keys.push(key)
} else {
self.chunks.remove(key)
}
None => ()
}
}
self.chunk_keys = next_keys
}
///|
fn World::prune_height_cache(
self : World,
keep : @hashmap.HashMap[String, Bool],
) -> Unit {
let next_keys : Array[String] = []
for i = 0; i < self.height_keys.length(); i = i + 1 {
let key = self.height_keys[i]
match self.height_cache.get(key) {
Some(_) =>
if keep.contains(key) {
next_keys.push(key)
} else {
self.height_cache.remove(key)
}
None => ()
}
}
self.height_keys = next_keys
}
///|
fn World::get(self : World, x : Int, y : Int, z : Int) -> Block {
if y < 0 {
Block::Bedrock
} else if y >= self.height {
Block::Air
} else {
let cx = div_floor(x, self.chunk_size)
let cz = div_floor(z, self.chunk_size)
let cy = div_floor(y, self.section_height)
let lx = mod_floor(x, self.chunk_size)
let lz = mod_floor(z, self.chunk_size)
let ly = mod_floor(y, self.section_height)
let key = chunk_key_3(cx, cy, cz)
match self.chunks.get(key) {
Some(chunk) => chunk.blocks[self.chunk_index(lx, ly, lz)]
None => {
let chunk = generate_chunk_section(self, cx, cy, cz)
let blocks = chunk.blocks
self.chunks.set(key, chunk)
self.chunk_keys.push(key)
blocks[self.chunk_index(lx, ly, lz)]
}
}
}
}
///|
fn block_is_air(block : Block) -> Bool {
match block {
Block::Air => true
_ => false
}
}
///|
fn block_is_transparent(block : Block) -> Bool {
match block {
Block::Water => true
_ => false
}
}
///|
fn block_is_cutout(block : Block) -> Bool {
match block {
Block::Leaves => true
_ => false
}
}
///|
fn block_is_see_through(block : Block) -> Bool {
block_is_transparent(block) || block_is_cutout(block)
}
///|
fn block_is_water(block : Block) -> Bool {
match block {
Block::Water => true
_ => false
}
}
///|
fn block_is_solid(block : Block) -> Bool {
match block {
Block::Air => false
Block::Water => false
_ => true
}
}
///|
fn block_is_replaceable(block : Block) -> Bool {
match block {
Block::Air => true
Block::Water => true
_ => false
}
}
///|
///|
fn World::set(self : World, x : Int, y : Int, z : Int, block : Block) -> Bool {
if y <= 0 || y >= self.height {
false
} else {
let cx = div_floor(x, self.chunk_size)
let cz = div_floor(z, self.chunk_size)
let cy = div_floor(y, self.section_height)
let lx = mod_floor(x, self.chunk_size)
let lz = mod_floor(z, self.chunk_size)
let ly = mod_floor(y, self.section_height)
let key = chunk_key_3(cx, cy, cz)
let mut existed = false
let blocks = match self.chunks.get(key) {
Some(chunk) => {
existed = true
chunk.blocks
}
None => generate_chunk_section(self, cx, cy, cz).blocks
}
blocks[self.chunk_index(lx, ly, lz)] = block
if !existed {
self.chunk_keys.push(key)
}
self.chunks.set(key, { blocks, modified: true })
true
}
}
///|
fn World::get_range(
self : World,
x : Int,
y : Int,
z : Int,
y_min : Int,
y_max : Int,
) -> Block {
if y < y_min || y >= y_max {
Block::Air
} else {
self.get(x, y, z)
}
}
///|
fn World::column_raw_info(self : World, x : Int, z : Int) -> RawColumn {
let fx = x.to_double()
let fz = z.to_double()
let (wx, wz) = warp_coords(fx, fz)
let continent = fbm(wx * 0.0011, wz * 0.0011)
let continentalness = continent * 2.0 - 1.0
let land = clamp01((continentalness + 0.3) / 0.55)
let hills_raw = fbm(wx * 0.018, wz * 0.018)
let detail_raw = fbm(wx * 0.085, wz * 0.085)
let hills = (smoothstep(hills_raw) + hills_raw) * 0.5
let detail = (smoothstep(detail_raw) + detail_raw) * 0.5
let ridge = ridged(wx * 0.0065, wz * 0.0065)
let peaks = ridged(wx * 0.012, wz * 0.012)
let mega = ridged(wx * 0.0008 + 2.5, wz * 0.0008 - 5.4)
let mega_mask = clamp01((mega - 0.78) / 0.16)
let mountain_chain = ridged(wx * 0.0019 + 7.4, wz * 0.0019 - 3.1)
let chain_mask = clamp01((mountain_chain - 0.55) / 0.35)
let cliff = ridged(wx * 0.017, wz * 0.017)
let cliff_mask = clamp01((cliff - 0.82) / 0.2)
let cliff_strength = cliff_mask * cliff_mask
let coast = clamp01((0.36 - land) / 0.18)
let coast_noise = fbm(wx * 0.022 + 19.2, wz * 0.022 - 7.1)
let coast_cliff = clamp01((coast_noise - 0.8) / 0.2) * coast
let raw_scale = self.height.to_double() / 384.0
let relief_scale = if raw_scale < 2.0 { raw_scale } else { 2.0 }
let detail_scale = if raw_scale < 1.0 { raw_scale } else { 1.0 }
let temp = fbm(wx * 0.0015 + 5.2, wz * 0.0015 - 9.7)
let moist = fbm(wx * 0.0018 - 7.3, wz * 0.0018 + 2.4)
let karst_noise = fbm(wx * 0.008 + 21.4, wz * 0.008 - 17.2)
let karst_mask = clamp01((karst_noise - 0.55) / 0.25)
let river_noise = fbm(wx * 0.002 + 12.4, wz * 0.002 - 8.2)
let river_shape = 1.0 - (river_noise * 2.0 - 1.0).abs()
let river_mask = clamp01((river_shape - 0.78) / 0.12)
let mut biome = biome_from(temp, moist, ridge, land)
let sea = self.sea_level.to_double()
let macro_base = fbm(wx * 0.00000105 + 12.3, wz * 0.00000105 - 9.4)
let macro_slope = fbm(wx * 0.00000035 - 5.7, wz * 0.00000035 + 3.9)
let macro_shift = (macro_base * 2.0 - 1.0) * (self.height.to_double() * 0.16) +
(macro_slope * 2.0 - 1.0) * (self.height.to_double() * 0.06)
let macro_bias = self.height.to_double() * 0.16
let base_ocean = sea - 130.0 * relief_scale + macro_shift * 0.45
let base_land = sea + 65.0 * relief_scale + macro_shift + macro_bias
let bump_scale = lerp(
1.0,
1.8,
clamp01((base_land - sea) / (self.height.to_double() * 0.6)),
)
let land_height = base_land +
(continent - 0.5) * 14.0 * relief_scale +
(hills - 0.5) * 1.2 * detail_scale * bump_scale +
(detail - 0.5) * 0.35 * detail_scale * bump_scale
let mut height = lerp(base_ocean, land_height, land)
height = height +
chain_mask * 14.0 * relief_scale +
chain_mask * peaks * 2.25 * relief_scale
height = height +
mega_mask * 30.0 * relief_scale +
mega_mask * peaks * 3.0 * relief_scale
height = height + ridge * 0.7 * detail_scale * bump_scale
if cliff_strength > 0.0 {
height = lerp(height, land_height, cliff_strength * 0.3)
}
let highland_limit = sea + 60.0 * relief_scale
if karst_mask > 0.45 && land > 0.3 && height < highland_limit {
let karst_scale = clamp01((highland_limit - height) / highland_limit)
height = terrace(
height + karst_mask * 6.0 * detail_scale * karst_scale,
2.8,
)
let sink = fbm(wx * 0.05 + 3.1, wz * 0.05 - 6.7)
if sink > 0.74 {
height = height -
(sink - 0.74) * 10.0 * karst_mask * detail_scale * karst_scale
}
}
if river_mask > 0.0 && land > 0.22 && height < sea + 80.0 * relief_scale {
let river_depth = river_mask *
(8.0 + (1.0 - chain_mask) * 4.0) *
detail_scale
height = height - river_depth
}
match biome {
Biome::Ocean => height = height - (8.0 + land * 5.0) * relief_scale
Biome::Mesa => {
height = height +
ridge * 6.0 * detail_scale +
(hills - 0.5) * 3.0 * detail_scale
height = terrace(height, 4.0)
}
Biome::Desert =>
height = height +
(hills - 0.5) * 0.5 * detail_scale * bump_scale +
(detail - 0.5) * 0.22 * detail_scale * bump_scale
Biome::Forest =>
height = height +
(hills - 0.5) * 0.75 * detail_scale * bump_scale +
(detail - 0.5) * 0.32 * detail_scale * bump_scale
Biome::Plains => height = lerp(height, sea + 5.0 * relief_scale, 0.75)
Biome::Hills =>
height = height +
ridge * 1.2 * detail_scale * bump_scale +
(hills - 0.5) * 0.6 * detail_scale * bump_scale
Biome::Mountains =>
height = height + ridge * 2.4 * relief_scale + peaks * 1.5 * relief_scale
Biome::River => height = height
Biome::Snow => height = height
Biome::Karst => height = height
}
if land > 0.2 && river_mask > 0.55 && ridge < 0.7 {
biome = Biome::River
}
if chain_mask > 0.55 && land > 0.3 {
biome = Biome::Mountains
}
if mega_mask > 0.55 && land > 0.3 {
biome = Biome::Mountains
}
if karst_mask > 0.68 && land > 0.32 && temp > 0.4 && temp < 0.75 {
biome = Biome::Karst
}
if height > sea + 46.0 * relief_scale {
biome = Biome::Snow
}
if coast > 0.0 {
let beach_height = sea + 1.5 * relief_scale
height = lerp(height, beach_height, coast * 0.5)
if coast_cliff > 0.45 {
height = height + coast_cliff * 3.5 * relief_scale
}
}
let lake = fbm(wx * 0.01 + 13.7, wz * 0.01 - 4.4)
if land > 0.2 && lake > 0.72 && height < sea + 40.0 * relief_scale {
let lake_scale = clamp01(
(sea + 40.0 * relief_scale - height) / (40.0 * relief_scale),
)
height = height - (lake - 0.72) * 10.0 * detail_scale * lake_scale
}
if land > 0.25 && height < sea + 2.0 {
let lift = clamp01((land - 0.25) / 0.2)
height = lerp(height, sea + 2.0, lift)
} else if land < 0.2 && height > sea - 2.0 {
let sink = clamp01((0.2 - land) / 0.2)
height = lerp(height, sea - 2.0, sink)
}
let mut h = height
let max_h = self.height.to_double()
if h < 0.0 {
h = 0.0
} else if h > max_h {
h = max_h
}
{ height: h, biome, ridge, moist, coast }
}
///|
fn World::column_info(self : World, x : Int, z : Int) -> ColumnInfo {
let raw = self.column_raw_info(x, z)
let h = self.height_for(x, z)
{
height: h,
biome: raw.biome,
ridge: raw.ridge,
moist: raw.moist,
coast: raw.coast,
}
}
///|
fn World::height_at(self : World, x : Int, z : Int) -> Int {
self.height_for(x, z)
}
///|
fn World::height_for(self : World, x : Int, z : Int) -> Int {
let cx = div_floor(x, self.chunk_size)
let cz = div_floor(z, self.chunk_size)
let lx = mod_floor(x, self.chunk_size)
let lz = mod_floor(z, self.chunk_size)
let key = chunk_key(cx, cz)
let heights = match self.height_cache.get(key) {
Some(map) => map
None => {
let map = self.build_height_map(cx, cz)
self.height_cache.set(key, map)
self.height_keys.push(key)
map
}
}
heights[lx + self.chunk_size * lz]
}
///|
fn World::build_height_map(self : World, cx : Int, cz : Int) -> Array[Int] {
let size = self.chunk_size + 2
let raw = Array::make(size * size, 0.0)
let x0 = cx * self.chunk_size - 1
let z0 = cz * self.chunk_size - 1
for lz = 0; lz < size; lz = lz + 1 {
for lx = 0; lx < size; lx = lx + 1 {
let gx = x0 + lx
let gz = z0 + lz
let info = self.column_raw_info(gx, gz)
raw[lx + size * lz] = info.height
}
}
let max_slope = 2.5
let heights = raw
for _iter = 0; _iter < 5; _iter = _iter + 1 {
for lz = 1; lz < size - 1; lz = lz + 1 {
for lx = 1; lx < size - 1; lx = lx + 1 {
let idx = lx + size * lz
let n = heights[idx - size]
let s = heights[idx + size]
let w = heights[idx - 1]
let e = heights[idx + 1]
let mut min_allowed = n - max_slope
let mut max_allowed = n + max_slope
let s_min = s - max_slope
let s_max = s + max_slope
let w_min = w - max_slope
let w_max = w + max_slope
let e_min = e - max_slope
let e_max = e + max_slope
if s_min > min_allowed {
min_allowed = s_min
}
if w_min > min_allowed {
min_allowed = w_min
}
if e_min > min_allowed {
min_allowed = e_min
}
if s_max < max_allowed {
max_allowed = s_max
}
if w_max < max_allowed {
max_allowed = w_max
}
if e_max < max_allowed {
max_allowed = e_max
}
let mut h = heights[idx]
if min_allowed > max_allowed {
h = (min_allowed + max_allowed) * 0.5
} else if h < min_allowed {
h = min_allowed
} else if h > max_allowed {
h = max_allowed
}
heights[idx] = h
}
}
}
let out = Array::make(self.chunk_size * self.chunk_size, 0)
for lz = 0; lz < self.chunk_size; lz = lz + 1 {
for lx = 0; lx < self.chunk_size; lx = lx + 1 {
let idx = lx + 1 + size * (lz + 1)
let mut h = heights[idx].floor().to_int()
if h < 4 {
h = 4
} else if h > self.height - 3 {
h = self.height - 3
}
out[lx + self.chunk_size * lz] = h
}
}
out
}
///|
fn surface_blocks(
world : World,
gx : Int,
gz : Int,
info : ColumnInfo,
) -> (Block, Block) {
let h = info.height
let biome = info.biome
let ridge = info.ridge
let moist = info.moist
let coast = info.coast
let raw_scale = world.height.to_double() / 384.0
let relief_scale = if raw_scale < 2.0 { raw_scale } else { 2.0 }
let scaled30 = (30.0 * relief_scale).to_int()
let scaled28 = (28.0 * relief_scale).to_int()
let scaled48 = (48.0 * relief_scale).to_int()
let mut top_block = match biome {
Biome::Ocean => Block::Sand
Biome::Desert => Block::Sand
Biome::Mesa => Block::Sand
Biome::Mountains => Block::Stone
Biome::Hills => Block::Grass
Biome::Forest => Block::Grass
Biome::Plains => Block::Grass
Biome::River => Block::Sand
Biome::Snow => Block::Snow
Biome::Karst => Block::Stone
}
let mut mid_block = match biome {
Biome::Ocean => Block::Sand
Biome::Desert => Block::Sand
Biome::Mesa => Block::Sand
Biome::Mountains => Block::Stone
Biome::Hills => Block::Dirt
Biome::Forest => Block::Dirt
Biome::Plains => Block::Dirt
Biome::River => Block::Clay
Biome::Snow => Block::Stone
Biome::Karst => Block::Stone
}
let is_shore = h <= world.sea_level + 1 && coast > 0.2
if is_shore {
let shore_strength = clamp01((coast - 0.2) / 0.4)
if shore_strength > 0.0 {
top_block = Block::Sand
mid_block = Block::Sand
}
}
let sea = world.sea_level
if biome == Biome::Mountains {
if h > sea + scaled30 {
top_block = Block::Stone
mid_block = Block::Stone
} else {
top_block = Block::Grass
mid_block = Block::Dirt
}
}
if biome != Biome::Snow && ridge > 0.88 && h > sea + scaled28 {
top_block = Block::Stone
mid_block = Block::Stone
}
let snow_line = sea + scaled48
if h >= snow_line {
top_block = Block::Snow
mid_block = Block::Stone
}
let surface_noise = fbm(
gx.to_double() * 0.06 + 2.1,
gz.to_double() * 0.06 - 3.7,
)
let is_stony = match top_block {
Block::Stone => true
_ => false
}
let allow_soil = biome != Biome::Snow && h > world.sea_level + 2
if is_stony && allow_soil {
let karst_limit = if biome == Biome::Karst { 0.62 } else { 0.45 }
if surface_noise > karst_limit && moist > 0.3 {
top_block = Block::Grass
mid_block = Block::Dirt
} else if surface_noise > 0.38 {
top_block = Block::Dirt
mid_block = Block::Dirt
}
}
if h <= sea {
let base = if biome == Biome::River { Block::Clay } else { Block::Sand }
top_block = base
mid_block = base
} else if h <= sea + 2 {
top_block = Block::Sand
mid_block = Block::Sand
}
(top_block, mid_block)
}
///|
fn bedrock_height(world : World, gx : Int, gz : Int, info : ColumnInfo) -> Int {
let (wx, wz) = warp_coords(gx.to_double(), gz.to_double())
let plate = fbm(wx * 0.0006 + 11.7, wz * 0.0006 - 4.2)
let plate_shift = (plate - 0.5) * 80.0
let sea = world.sea_level.to_double()
let base_floor = sea - 140.0 + plate_shift
let depth_limit = 120.0
let surface = info.height.to_double()
let mut bedrock = base_floor
let max_floor = surface - depth_limit
if max_floor > bedrock {
bedrock = max_floor
}
let mut y = bedrock.floor().to_int()
let max_allowed = info.height - 5
if y > max_allowed {
y = max_allowed
}
if y < 0 {
y = 0
}
y
}
///|
fn generate_chunk_section(world : World, cx : Int, cy : Int, cz : Int) -> Chunk {
let total = world.chunk_size * world.section_height * world.chunk_size
let blocks = Array::make(total, Block::Air)
let y0 = cy * world.section_height
let mut y1 = y0 + world.section_height
if y1 > world.height {
y1 = world.height
}
for lz = 0; lz < world.chunk_size; lz = lz + 1 {
for lx = 0; lx < world.chunk_size; lx = lx + 1 {
let gx = cx * world.chunk_size + lx
let gz = cz * world.chunk_size + lz
let info = world.column_info(gx, gz)
let h = info.height
let biome = info.biome
let ridge = info.ridge
let moist = info.moist
let (top_block, mid_block) = surface_blocks(world, gx, gz, info)
let bedrock_y = bedrock_height(world, gx, gz, info)
for y = y0; y < y1; y = y + 1 {
let ly = y - y0
let idx = world.chunk_index(lx, ly, lz)
if y <= bedrock_y {
blocks[idx] = Block::Bedrock
} else if y < h - 4 {
blocks[idx] = Block::Stone
} else if y < h - 1 {
blocks[idx] = mid_block
} else if y < h {
blocks[idx] = top_block
} else if y < world.sea_level {
blocks[idx] = Block::Water
}
}
if h > world.sea_level + 1 {
let forest = fbm(
gx.to_double() * 0.01 + 2.7,
gz.to_double() * 0.01 - 4.1,
)
let radius_bias = if forest > 0.65 {
-1
} else if forest < 0.35 {
1
} else {
0
}
let tree_radius = match biome {
Biome::Forest => 2
Biome::Plains => 3
Biome::Hills => 3
Biome::Mountains => 4
Biome::Ocean => 4
Biome::Desert => 4
Biome::Mesa => 4
Biome::River => 4
Biome::Snow => 4
Biome::Karst => 4
}
let tree_radius = if tree_radius + radius_bias < 2 {
2
} else {
tree_radius + radius_bias
}
let (is_peak, tree_noise) = tree_peak(gx, gz, tree_radius)
let mut tree_threshold = match biome {
Biome::Ocean => 1.2
Biome::Forest => 0.7
Biome::Plains => 0.78
Biome::Hills => 0.8
Biome::Mountains => 0.88
Biome::Desert => 1.1
Biome::Mesa => 1.05
Biome::River => 1.2
Biome::Snow => 1.2
Biome::Karst => 1.0
}
let density_boost = (forest - 0.5) * 0.16
tree_threshold = tree_threshold - moist * 0.08 - density_boost
let can_tree = match top_block {
Block::Grass => true
_ => false
}
if can_tree &&
is_peak &&
ridge < 0.75 &&
tree_noise > tree_threshold &&
lx >= 2 &&
lz >= 2 &&
lx <= world.chunk_size - 3 &&
lz <= world.chunk_size - 3 {
let trunk = 3 + (tree_noise * 4.0 + moist * 2.0).to_int()
for t = 0; t < trunk; t = t + 1 {
let y = h + t
if y >= y0 && y < y1 {
let idx = world.chunk_index(lx, y - y0, lz)
blocks[idx] = Block::Wood
}
}
let leaf_start = h + trunk - 2
for dy = -2; dy <= 1; dy = dy + 1 {
for dz = -2; dz <= 2; dz = dz + 1 {
for dx = -2; dx <= 2; dx = dx + 1 {
let dist = dx * dx + dz * dz + dy * dy
if dist <= 6 {
let y = leaf_start + dy
let lx2 = lx + dx
let lz2 = lz + dz
if y >= y0 &&
y < y1 &&
lx2 >= 0 &&
lx2 < world.chunk_size &&
lz2 >= 0 &&
lz2 < world.chunk_size {
let idx = world.chunk_index(lx2, y - y0, lz2)
if block_is_air(blocks[idx]) {
blocks[idx] = Block::Leaves
}
}
}
}
}
}
let top_y = h + trunk
for dz = -1; dz <= 1; dz = dz + 1 {
for dx = -1; dx <= 1; dx = dx + 1 {
let dist = dx * dx + dz * dz
if dist <= 2 {
let lx2 = lx + dx
let lz2 = lz + dz
let y = top_y
if y >= y0 &&
y < y1 &&
lx2 >= 0 &&
lx2 < world.chunk_size &&
lz2 >= 0 &&
lz2 < world.chunk_size {
let idx = world.chunk_index(lx2, y - y0, lz2)
if block_is_air(blocks[idx]) {
blocks[idx] = Block::Leaves
}
}
}
}
}
}
}
}
}
{ blocks, modified: false }
}
///|
priv struct RayHit {
x : Int
y : Int
z : Int
prev_x : Int
prev_y : Int
prev_z : Int
}
///|
fn raycast(
world : World,
origin : Vec3,
dir : Vec3,
max_dist : Double,
) -> RayHit? {
let dir_n = dir.normalize()
if dir_n.length() <= 0.000001 {
return None
}
let mut x = origin.x.floor().to_int()
let mut y = origin.y.floor().to_int()
let mut z = origin.z.floor().to_int()
let start_block = world.get(x, y, z)
if !block_is_air(start_block) && !block_is_replaceable(start_block) {
return Some({ x, y, z, prev_x: x, prev_y: y, prev_z: z })
}
let step_x = if dir_n.x > 0.0 { 1 } else if dir_n.x < 0.0 { -1 } else { 0 }
let step_y = if dir_n.y > 0.0 { 1 } else if dir_n.y < 0.0 { -1 } else { 0 }
let step_z = if dir_n.z > 0.0 { 1 } else if dir_n.z < 0.0 { -1 } else { 0 }
let mut t_max_x = if step_x != 0 {
let next = if step_x > 0 { (x + 1).to_double() } else { x.to_double() }
(next - origin.x) / dir_n.x
} else {
1.0e9
}
let mut t_max_y = if step_y != 0 {
let next = if step_y > 0 { (y + 1).to_double() } else { y.to_double() }
(next - origin.y) / dir_n.y
} else {
1.0e9
}
let mut t_max_z = if step_z != 0 {
let next = if step_z > 0 { (z + 1).to_double() } else { z.to_double() }
(next - origin.z) / dir_n.z
} else {
1.0e9
}
let t_delta_x = if step_x != 0 { 1.0 / dir_n.x.abs() } else { 1.0e9 }
let t_delta_y = if step_y != 0 { 1.0 / dir_n.y.abs() } else { 1.0e9 }
let t_delta_z = if step_z != 0 { 1.0 / dir_n.z.abs() } else { 1.0e9 }
let mut prev_x = x
let mut prev_y = y
let mut prev_z = z
let mut t = 0.0
while t <= max_dist {
if t_max_x < t_max_y {
if t_max_x < t_max_z {
prev_x = x
prev_y = y
prev_z = z
x = x + step_x
t = t_max_x
t_max_x = t_max_x + t_delta_x
} else {
prev_x = x
prev_y = y
prev_z = z
z = z + step_z
t = t_max_z
t_max_z = t_max_z + t_delta_z
}
} else if t_max_y < t_max_z {
prev_x = x
prev_y = y
prev_z = z
y = y + step_y
t = t_max_y
t_max_y = t_max_y + t_delta_y
} else {
prev_x = x
prev_y = y
prev_z = z
z = z + step_z
t = t_max_z
t_max_z = t_max_z + t_delta_z
}
if !block_is_air(world.get(x, y, z)) {
return Some({ x, y, z, prev_x, prev_y, prev_z })
}
}
None
}
///|
fn raycast_solid(
world : World,
origin : Vec3,
dir : Vec3,
max_dist : Double,
) -> RayHit? {
let dir_n = dir.normalize()
if dir_n.length() <= 0.000001 {
return None
}
let mut x = origin.x.floor().to_int()
let mut y = origin.y.floor().to_int()
let mut z = origin.z.floor().to_int()
let start_block = world.get(x, y, z)
if !block_is_air(start_block) && !block_is_replaceable(start_block) {
return Some({ x, y, z, prev_x: x, prev_y: y, prev_z: z })
}
let step_x = if dir_n.x > 0.0 { 1 } else if dir_n.x < 0.0 { -1 } else { 0 }
let step_y = if dir_n.y > 0.0 { 1 } else if dir_n.y < 0.0 { -1 } else { 0 }
let step_z = if dir_n.z > 0.0 { 1 } else if dir_n.z < 0.0 { -1 } else { 0 }
let mut t_max_x = if step_x != 0 {
let next = if step_x > 0 { (x + 1).to_double() } else { x.to_double() }
(next - origin.x) / dir_n.x
} else {
1.0e9
}
let mut t_max_y = if step_y != 0 {
let next = if step_y > 0 { (y + 1).to_double() } else { y.to_double() }
(next - origin.y) / dir_n.y
} else {
1.0e9
}
let mut t_max_z = if step_z != 0 {
let next = if step_z > 0 { (z + 1).to_double() } else { z.to_double() }
(next - origin.z) / dir_n.z
} else {
1.0e9
}
let t_delta_x = if step_x != 0 { 1.0 / dir_n.x.abs() } else { 1.0e9 }
let t_delta_y = if step_y != 0 { 1.0 / dir_n.y.abs() } else { 1.0e9 }
let t_delta_z = if step_z != 0 { 1.0 / dir_n.z.abs() } else { 1.0e9 }
let mut prev_x = x
let mut prev_y = y
let mut prev_z = z
let mut t = 0.0
while t <= max_dist {
if t_max_x < t_max_y {
if t_max_x < t_max_z {
prev_x = x
prev_y = y
prev_z = z
x = x + step_x
t = t_max_x
t_max_x = t_max_x + t_delta_x
} else {
prev_x = x
prev_y = y
prev_z = z
z = z + step_z
t = t_max_z
t_max_z = t_max_z + t_delta_z
}
} else if t_max_y < t_max_z {
prev_x = x
prev_y = y
prev_z = z
y = y + step_y
t = t_max_y
t_max_y = t_max_y + t_delta_y
} else {
prev_x = x
prev_y = y
prev_z = z
z = z + step_z
t = t_max_z
t_max_z = t_max_z + t_delta_z
}
let block = world.get(x, y, z)
if !block_is_air(block) && !block_is_replaceable(block) {
return Some({ x, y, z, prev_x, prev_y, prev_z })
}
}
None
}