mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'zesterer/worldgen' into 'master'
Worldgen improvements See merge request veloren/veloren!457
This commit is contained in:
@ -170,7 +170,7 @@ void main() {
|
|||||||
hsva_color.y *= 1.45;
|
hsva_color.y *= 1.45;
|
||||||
hsva_color.z *= 0.85;
|
hsva_color.z *= 0.85;
|
||||||
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
|
//hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0);
|
||||||
vec4 final_color = fxaa_color;
|
vec4 final_color = fxaa_color;
|
||||||
//vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
|
//vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a);
|
||||||
|
|
||||||
tgt_color = vec4(final_color.rgb, 1);
|
tgt_color = vec4(final_color.rgb, 1);
|
||||||
|
@ -47,4 +47,4 @@ void main() {
|
|||||||
proj_mat *
|
proj_mat *
|
||||||
view_mat *
|
view_mat *
|
||||||
vec4(f_pos, 1);
|
vec4(f_pos, 1);
|
||||||
}
|
}
|
||||||
|
BIN
assets/world/module/human/balcony_upstairs.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/balcony_upstairs.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/chimney_roof.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/chimney_roof.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/corner_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/corner_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/corner_roof.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/corner_roof.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/corner_upstairs.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/corner_upstairs.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/door_big.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/door_big.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/door_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/door_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/floor_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/floor_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/floor_roof.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/floor_roof.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/floor_upstairs.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/floor_upstairs.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/stair_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/stair_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/wall_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/wall_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/wall_roof.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/wall_roof.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/wall_upstairs.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/wall_upstairs.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/window_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/window_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/human/window_upstairs.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/human/window_upstairs.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/corner_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/corner_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/corner_mid.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/corner_mid.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/corner_top.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/corner_top.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/edge_ground.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/edge_ground.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/edge_mid.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/edge_mid.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/edge_top.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/edge_top.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/end_top.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/end_top.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/module/wall/single_top.vox
(Stored with Git LFS)
Normal file
BIN
assets/world/module/wall/single_top.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/world/structure/human/mage_tower.vox
(Stored with Git LFS)
BIN
assets/world/structure/human/mage_tower.vox
(Stored with Git LFS)
Binary file not shown.
1
server-cli/.gitignore
vendored
Normal file
1
server-cli/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
settings.ron
|
@ -104,8 +104,8 @@ impl<V: BaseVol<Vox = Block> + ReadVol + Debug, S: VolSize + Clone>
|
|||||||
.map(|vox| block_shadow_density(vox.kind()))
|
.map(|vox| block_shadow_density(vox.kind()))
|
||||||
.unwrap_or((0.0, 0.0));
|
.unwrap_or((0.0, 0.0));
|
||||||
|
|
||||||
neighbour_light[0][i][j] =
|
neighbour_light[0][i][j] = (neighbour_light[0][i][j] * (1.0 - density))
|
||||||
(neighbour_light[0][i][j] * (1.0 - density)).max(cap);
|
.max(cap.min(neighbour_light[1][i][j]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,11 +608,9 @@ impl Terrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translucent
|
// Terrain sprites
|
||||||
for (pos, chunk) in &self.chunks {
|
for (pos, chunk) in &self.chunks {
|
||||||
if chunk.visible {
|
if chunk.visible {
|
||||||
renderer.render_fluid_chunk(&chunk.fluid_model, globals, &chunk.locals, lights);
|
|
||||||
|
|
||||||
const SPRITE_RENDER_DISTANCE: f32 = 128.0;
|
const SPRITE_RENDER_DISTANCE: f32 = 128.0;
|
||||||
|
|
||||||
let chunk_center = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
let chunk_center = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
||||||
@ -632,5 +630,12 @@ impl Terrain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translucent
|
||||||
|
for (_, chunk) in &self.chunks {
|
||||||
|
if chunk.visible {
|
||||||
|
renderer.render_fluid_chunk(&chunk.fluid_model, globals, &chunk.locals, lights);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ mod natural;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
column::{ColumnGen, ColumnSample, StructureData},
|
column::{ColumnGen, ColumnSample, StructureData},
|
||||||
|
generator::{Generator, TownGen},
|
||||||
util::{HashCache, RandomField, Sampler, SamplerMut},
|
util::{HashCache, RandomField, Sampler, SamplerMut},
|
||||||
World, CONFIG,
|
World, CONFIG,
|
||||||
};
|
};
|
||||||
@ -136,6 +137,7 @@ impl<'a> BlockGen<'a> {
|
|||||||
column_gen,
|
column_gen,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let sample = &z_cache?.sample;
|
||||||
let &ColumnSample {
|
let &ColumnSample {
|
||||||
alt,
|
alt,
|
||||||
chaos,
|
chaos,
|
||||||
@ -155,8 +157,10 @@ impl<'a> BlockGen<'a> {
|
|||||||
cliff_hill,
|
cliff_hill,
|
||||||
close_cliffs,
|
close_cliffs,
|
||||||
temp,
|
temp,
|
||||||
|
|
||||||
|
chunk,
|
||||||
..
|
..
|
||||||
} = &z_cache?.sample;
|
} = sample;
|
||||||
|
|
||||||
let structures = &z_cache?.structures;
|
let structures = &z_cache?.structures;
|
||||||
|
|
||||||
@ -168,31 +172,21 @@ impl<'a> BlockGen<'a> {
|
|||||||
(true, alt, CONFIG.sea_level /*water_level*/)
|
(true, alt, CONFIG.sea_level /*water_level*/)
|
||||||
} else {
|
} else {
|
||||||
// Apply warping
|
// Apply warping
|
||||||
let warp = (world
|
let warp = (world.sim().gen_ctx.warp_nz.get(wposf.div(48.0)) as f32)
|
||||||
.sim()
|
|
||||||
.gen_ctx
|
|
||||||
.warp_nz
|
|
||||||
.get((wposf.div(Vec3::new(150.0, 150.0, 150.0))).into_array())
|
|
||||||
as f32)
|
|
||||||
.mul((chaos - 0.1).max(0.0))
|
.mul((chaos - 0.1).max(0.0))
|
||||||
.mul(96.0);
|
.mul(48.0)
|
||||||
|
+ (world.sim().gen_ctx.warp_nz.get(wposf.div(15.0)) as f32)
|
||||||
|
.mul((chaos - 0.1).max(0.0))
|
||||||
|
.mul(24.0);
|
||||||
|
|
||||||
let height = if (wposf.z as f32) < alt + warp - 10.0 {
|
let height = if (wposf.z as f32) < alt + warp - 10.0 {
|
||||||
// Shortcut cliffs
|
// Shortcut cliffs
|
||||||
alt + warp
|
alt + warp
|
||||||
} else {
|
} else {
|
||||||
let turb = Vec2::new(
|
let turb = Vec2::new(
|
||||||
world
|
world.sim().gen_ctx.fast_turb_x_nz.get(wposf.div(25.0)) as f32,
|
||||||
.sim()
|
world.sim().gen_ctx.fast_turb_y_nz.get(wposf.div(25.0)) as f32,
|
||||||
.gen_ctx
|
) * 8.0;
|
||||||
.turb_x_nz
|
|
||||||
.get((wposf.div(48.0)).into_array()) as f32,
|
|
||||||
world
|
|
||||||
.sim()
|
|
||||||
.gen_ctx
|
|
||||||
.turb_y_nz
|
|
||||||
.get((wposf.div(48.0)).into_array()) as f32,
|
|
||||||
) * 12.0;
|
|
||||||
|
|
||||||
let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb;
|
let wpos_turb = Vec2::from(wpos).map(|e: i32| e as f32) + turb;
|
||||||
let cliff_height = Self::get_cliff_height(
|
let cliff_height = Self::get_cliff_height(
|
||||||
@ -352,6 +346,14 @@ impl<'a> BlockGen<'a> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Structures (like towns)
|
||||||
|
let block = chunk
|
||||||
|
.structures
|
||||||
|
.town
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|town| TownGen.get((town, wpos, sample, height)))
|
||||||
|
.or(block);
|
||||||
|
|
||||||
let block = structures
|
let block = structures
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|st| {
|
.find_map(|st| {
|
||||||
@ -406,11 +408,24 @@ impl<'a> ZCache<'a> {
|
|||||||
.max(self.sample.water_level)
|
.max(self.sample.water_level)
|
||||||
.max(CONFIG.sea_level + 2.0);
|
.max(CONFIG.sea_level + 2.0);
|
||||||
|
|
||||||
|
// Structures
|
||||||
|
let (min, max) = self
|
||||||
|
.sample
|
||||||
|
.chunk
|
||||||
|
.structures
|
||||||
|
.town
|
||||||
|
.as_ref()
|
||||||
|
.map(|town| {
|
||||||
|
let (town_min, town_max) = TownGen.get_z_limits(town, self.wpos, &self.sample);
|
||||||
|
(town_min.min(min), town_max.max(max))
|
||||||
|
})
|
||||||
|
.unwrap_or((min, max));
|
||||||
|
|
||||||
(min, max)
|
(min, max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SamplerMut for BlockGen<'a> {
|
impl<'a> SamplerMut<'static> for BlockGen<'a> {
|
||||||
type Index = Vec3<i32>;
|
type Index = Vec3<i32>;
|
||||||
type Sample = Option<Block>;
|
type Sample = Option<Block>;
|
||||||
|
|
||||||
@ -499,7 +514,7 @@ impl StructureInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_from_structure(
|
pub fn block_from_structure(
|
||||||
sblock: StructureBlock,
|
sblock: StructureBlock,
|
||||||
default_kind: BlockKind,
|
default_kind: BlockKind,
|
||||||
pos: Vec3<i32>,
|
pos: Vec3<i32>,
|
||||||
|
@ -30,6 +30,7 @@ pub fn structure_gen<'a>(
|
|||||||
if (st_sample.tree_density as f64) < random_seed
|
if (st_sample.tree_density as f64) < random_seed
|
||||||
|| st_sample.alt < st_sample.water_level
|
|| st_sample.alt < st_sample.water_level
|
||||||
|| st_sample.spawn_rate < 0.5
|
|| st_sample.spawn_rate < 0.5
|
||||||
|
|| !st_sample.spawn_rules.trees
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
all::ForestKind,
|
all::ForestKind,
|
||||||
block::StructureMeta,
|
block::StructureMeta,
|
||||||
sim::{LocationInfo, SimChunk},
|
generator::{Generator, SpawnRules, TownGen},
|
||||||
|
sim::{LocationInfo, SimChunk, WorldSim},
|
||||||
util::{RandomPerm, Sampler, UnitChooser},
|
util::{RandomPerm, Sampler, UnitChooser},
|
||||||
World, CONFIG,
|
World, CONFIG,
|
||||||
};
|
};
|
||||||
@ -20,7 +21,7 @@ use std::{
|
|||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
pub struct ColumnGen<'a> {
|
pub struct ColumnGen<'a> {
|
||||||
world: &'a World,
|
pub sim: &'a WorldSim,
|
||||||
}
|
}
|
||||||
|
|
||||||
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
||||||
@ -55,14 +56,13 @@ lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ColumnGen<'a> {
|
impl<'a> ColumnGen<'a> {
|
||||||
pub fn new(world: &'a World) -> Self {
|
pub fn new(sim: &'a WorldSim) -> Self {
|
||||||
Self { world }
|
Self { sim }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_local_structure(&self, wpos: Vec2<i32>) -> Option<StructureData> {
|
fn get_local_structure(&self, wpos: Vec2<i32>) -> Option<StructureData> {
|
||||||
let (pos, seed) = self
|
let (pos, seed) = self
|
||||||
.world
|
.sim
|
||||||
.sim()
|
|
||||||
.gen_ctx
|
.gen_ctx
|
||||||
.region_gen
|
.region_gen
|
||||||
.get(wpos)
|
.get(wpos)
|
||||||
@ -74,7 +74,7 @@ impl<'a> ColumnGen<'a> {
|
|||||||
let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
let chunk_pos = pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
||||||
e / sz as i32
|
e / sz as i32
|
||||||
});
|
});
|
||||||
let chunk = self.world.sim().get(chunk_pos)?;
|
let chunk = self.sim.get(chunk_pos)?;
|
||||||
|
|
||||||
if seed % 5 == 2
|
if seed % 5 == 2
|
||||||
&& chunk.temp > CONFIG.desert_temp
|
&& chunk.temp > CONFIG.desert_temp
|
||||||
@ -102,8 +102,7 @@ impl<'a> ColumnGen<'a> {
|
|||||||
|
|
||||||
fn gen_close_structures(&self, wpos: Vec2<i32>) -> [Option<StructureData>; 9] {
|
fn gen_close_structures(&self, wpos: Vec2<i32>) -> [Option<StructureData>; 9] {
|
||||||
let mut metas = [None; 9];
|
let mut metas = [None; 9];
|
||||||
self.world
|
self.sim
|
||||||
.sim()
|
|
||||||
.gen_ctx
|
.gen_ctx
|
||||||
.structure_gen
|
.structure_gen
|
||||||
.get(wpos)
|
.get(wpos)
|
||||||
@ -121,7 +120,7 @@ impl<'a> ColumnGen<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Sampler for ColumnGen<'a> {
|
impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||||
type Index = Vec2<i32>;
|
type Index = Vec2<i32>;
|
||||||
type Sample = Option<ColumnSample<'a>>;
|
type Sample = Option<ColumnSample<'a>>;
|
||||||
|
|
||||||
@ -131,7 +130,7 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
e / sz as i32
|
e / sz as i32
|
||||||
});
|
});
|
||||||
|
|
||||||
let sim = self.world.sim();
|
let sim = &self.sim;
|
||||||
|
|
||||||
let turb = Vec2::new(
|
let turb = Vec2::new(
|
||||||
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
|
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
|
||||||
@ -142,7 +141,6 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
|
let alt_base = sim.get_interpolated(wpos, |chunk| chunk.alt_base)?;
|
||||||
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
|
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
|
||||||
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
|
let temp = sim.get_interpolated(wpos, |chunk| chunk.temp)?;
|
||||||
let dryness = sim.get_interpolated(wpos, |chunk| chunk.dryness)?;
|
|
||||||
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
|
let humidity = sim.get_interpolated(wpos, |chunk| chunk.humidity)?;
|
||||||
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
|
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
|
||||||
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
|
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
|
||||||
@ -175,8 +173,16 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
.small_nz
|
.small_nz
|
||||||
.get((wposf_turb.div(150.0)).into_array()) as f32)
|
.get((wposf_turb.div(150.0)).into_array()) as f32)
|
||||||
.abs()
|
.abs()
|
||||||
.mul(chaos.max(0.15))
|
.mul(chaos.max(0.025))
|
||||||
.mul(64.0);
|
.mul(64.0)
|
||||||
|
+ (sim
|
||||||
|
.gen_ctx
|
||||||
|
.small_nz
|
||||||
|
.get((wposf_turb.div(450.0)).into_array()) as f32)
|
||||||
|
.abs()
|
||||||
|
.mul(1.0 - chaos)
|
||||||
|
.mul(1.0 - humidity)
|
||||||
|
.mul(96.0);
|
||||||
|
|
||||||
let is_cliffs = sim_chunk.is_cliffs;
|
let is_cliffs = sim_chunk.is_cliffs;
|
||||||
let near_cliffs = sim_chunk.near_cliffs;
|
let near_cliffs = sim_chunk.near_cliffs;
|
||||||
@ -376,6 +382,7 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
.add((marble_small - 0.5) * 0.5),
|
.add((marble_small - 0.5) * 0.5),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
// Work out if we're on a path or near a town
|
// Work out if we're on a path or near a town
|
||||||
let dist_to_path = match &sim_chunk.location {
|
let dist_to_path = match &sim_chunk.location {
|
||||||
Some(loc) => {
|
Some(loc) => {
|
||||||
@ -412,9 +419,11 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
} else {
|
} else {
|
||||||
(alt, ground)
|
(alt, ground)
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
// Cities
|
// Cities
|
||||||
// TODO: In a later MR
|
// TODO: In a later MR
|
||||||
|
/*
|
||||||
let building = match &sim_chunk.location {
|
let building = match &sim_chunk.location {
|
||||||
Some(loc) => {
|
Some(loc) => {
|
||||||
let loc = &sim.locations[loc.loc_idx];
|
let loc = &sim.locations[loc.loc_idx];
|
||||||
@ -433,6 +442,7 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let alt = alt + building;
|
let alt = alt + building;
|
||||||
|
*/
|
||||||
|
|
||||||
// Caves
|
// Caves
|
||||||
let cave_at = |wposf: Vec2<f64>| {
|
let cave_at = |wposf: Vec2<f64>| {
|
||||||
@ -507,6 +517,14 @@ impl<'a> Sampler for ColumnGen<'a> {
|
|||||||
temp,
|
temp,
|
||||||
spawn_rate,
|
spawn_rate,
|
||||||
location: sim_chunk.location.as_ref(),
|
location: sim_chunk.location.as_ref(),
|
||||||
|
|
||||||
|
chunk: sim_chunk,
|
||||||
|
spawn_rules: sim_chunk
|
||||||
|
.structures
|
||||||
|
.town
|
||||||
|
.as_ref()
|
||||||
|
.map(|town| TownGen.spawn_rules(town, wpos))
|
||||||
|
.unwrap_or(SpawnRules::default()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -534,6 +552,9 @@ pub struct ColumnSample<'a> {
|
|||||||
pub temp: f32,
|
pub temp: f32,
|
||||||
pub spawn_rate: f32,
|
pub spawn_rate: f32,
|
||||||
pub location: Option<&'a LocationInfo>,
|
pub location: Option<&'a LocationInfo>,
|
||||||
|
|
||||||
|
pub chunk: &'a SimChunk,
|
||||||
|
pub spawn_rules: SpawnRules,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
34
world/src/generator/mod.rs
Normal file
34
world/src/generator/mod.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
mod town;
|
||||||
|
|
||||||
|
// Reexports
|
||||||
|
pub use self::town::{TownGen, TownState};
|
||||||
|
|
||||||
|
use crate::{column::ColumnSample, util::Sampler};
|
||||||
|
use common::terrain::Block;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct SpawnRules {
|
||||||
|
pub trees: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpawnRules {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { trees: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpawnRules {
|
||||||
|
pub fn and(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
trees: self.trees && other.trees,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Generator<'a, T: 'a>:
|
||||||
|
Sampler<'a, Index = (&'a T, Vec3<i32>, &'a ColumnSample<'a>, f32), Sample = Option<Block>>
|
||||||
|
{
|
||||||
|
fn get_z_limits(&self, state: &'a T, wpos: Vec2<i32>, sample: &ColumnSample) -> (f32, f32);
|
||||||
|
fn spawn_rules(&self, town: &'a TownState, wpos: Vec2<i32>) -> SpawnRules;
|
||||||
|
}
|
655
world/src/generator/town/mod.rs
Normal file
655
world/src/generator/town/mod.rs
Normal file
@ -0,0 +1,655 @@
|
|||||||
|
mod util;
|
||||||
|
mod vol;
|
||||||
|
|
||||||
|
use super::{Generator, SpawnRules};
|
||||||
|
use crate::{
|
||||||
|
block::block_from_structure,
|
||||||
|
column::{ColumnGen, ColumnSample},
|
||||||
|
sim::WorldSim,
|
||||||
|
util::{seed_expan, Grid, Sampler, UnitChooser},
|
||||||
|
};
|
||||||
|
use common::{
|
||||||
|
assets,
|
||||||
|
terrain::{Block, BlockKind, Structure},
|
||||||
|
vol::{ReadVol, Vox, WriteVol},
|
||||||
|
};
|
||||||
|
use hashbrown::HashSet;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use rand_chacha::ChaChaRng;
|
||||||
|
use std::{ops::Add, sync::Arc};
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
use self::vol::{CellKind, ColumnKind, Module, TownCell, TownColumn, TownVol};
|
||||||
|
|
||||||
|
const CELL_SIZE: i32 = 9;
|
||||||
|
const CELL_HEIGHT: i32 = 9;
|
||||||
|
|
||||||
|
pub struct TownGen;
|
||||||
|
|
||||||
|
impl<'a> Sampler<'a> for TownGen {
|
||||||
|
type Index = (&'a TownState, Vec3<i32>, &'a ColumnSample<'a>, f32);
|
||||||
|
type Sample = Option<Block>;
|
||||||
|
|
||||||
|
fn get(&self, (town, wpos, sample, height): Self::Index) -> Self::Sample {
|
||||||
|
let cell_pos = (wpos - town.center)
|
||||||
|
.map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| {
|
||||||
|
e.div_euclid(sz)
|
||||||
|
})
|
||||||
|
.add(Vec3::from(town.vol.size() / 2));
|
||||||
|
let inner_pos = (wpos - town.center)
|
||||||
|
.map2(Vec3::new(CELL_SIZE, CELL_SIZE, CELL_HEIGHT), |e, sz| {
|
||||||
|
e.rem_euclid(sz)
|
||||||
|
});
|
||||||
|
|
||||||
|
let cell = town.vol.get(cell_pos).ok()?;
|
||||||
|
|
||||||
|
match (modules_from_kind(&cell.kind), &cell.module) {
|
||||||
|
(Some(module_list), Some(module)) => {
|
||||||
|
let transform = [
|
||||||
|
(Vec2::new(0, 0), Vec2::unit_x(), Vec2::unit_y()),
|
||||||
|
(Vec2::new(0, 1), -Vec2::unit_y(), Vec2::unit_x()),
|
||||||
|
(Vec2::new(1, 1), -Vec2::unit_x(), -Vec2::unit_y()),
|
||||||
|
(Vec2::new(1, 0), Vec2::unit_y(), -Vec2::unit_x()),
|
||||||
|
];
|
||||||
|
|
||||||
|
module_list[module.vol_idx]
|
||||||
|
.0
|
||||||
|
.get(
|
||||||
|
Vec3::from(
|
||||||
|
transform[module.dir].0 * (CELL_SIZE - 1)
|
||||||
|
+ transform[module.dir].1 * inner_pos.x
|
||||||
|
+ transform[module.dir].2 * inner_pos.y,
|
||||||
|
) + Vec3::unit_z() * inner_pos.z,
|
||||||
|
)
|
||||||
|
.ok()
|
||||||
|
.and_then(|sb| {
|
||||||
|
block_from_structure(*sb, BlockKind::Normal, wpos, wpos.into(), 0, sample)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => match cell.kind {
|
||||||
|
CellKind::Empty => None,
|
||||||
|
CellKind::Park => None,
|
||||||
|
CellKind::Rock => Some(Block::new(BlockKind::Normal, Rgb::broadcast(100))),
|
||||||
|
CellKind::Wall => Some(Block::new(BlockKind::Normal, Rgb::broadcast(175))),
|
||||||
|
CellKind::Road => {
|
||||||
|
if (wpos.z as f32) < height - 1.0 {
|
||||||
|
Some(Block::new(
|
||||||
|
BlockKind::Normal,
|
||||||
|
Lerp::lerp(
|
||||||
|
Rgb::new(150.0, 140.0, 50.0),
|
||||||
|
Rgb::new(100.0, 95.0, 30.0),
|
||||||
|
sample.marble_small,
|
||||||
|
)
|
||||||
|
.map(|e| e as u8),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some(Block::empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CellKind::House(idx) => Some(Block::new(BlockKind::Normal, town.houses[idx].color)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Generator<'a, TownState> for TownGen {
|
||||||
|
fn get_z_limits(
|
||||||
|
&self,
|
||||||
|
town: &'a TownState,
|
||||||
|
wpos: Vec2<i32>,
|
||||||
|
sample: &ColumnSample,
|
||||||
|
) -> (f32, f32) {
|
||||||
|
(sample.alt - 32.0, sample.alt + 75.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_rules(&self, town: &'a TownState, wpos: Vec2<i32>) -> SpawnRules {
|
||||||
|
SpawnRules { trees: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct House {
|
||||||
|
color: Rgb<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TownState {
|
||||||
|
center: Vec3<i32>,
|
||||||
|
radius: i32,
|
||||||
|
vol: TownVol,
|
||||||
|
houses: Vec<House>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TownState {
|
||||||
|
pub fn generate(center: Vec2<i32>, gen: &mut ColumnGen, rng: &mut impl Rng) -> Option<Self> {
|
||||||
|
let radius = rng.gen_range(18, 20) * 9;
|
||||||
|
let size = Vec2::broadcast(radius * 2 / 9 - 2);
|
||||||
|
|
||||||
|
let alt = gen.get(center).map(|sample| sample.alt).unwrap_or(0.0) as i32;
|
||||||
|
|
||||||
|
let mut vol = TownVol::generate_from(
|
||||||
|
size,
|
||||||
|
|pos| {
|
||||||
|
let wpos = center + (pos - size / 2) * CELL_SIZE + CELL_SIZE / 2;
|
||||||
|
let rel_alt = gen.get(wpos).map(|sample| sample.alt).unwrap_or(0.0) as i32
|
||||||
|
+ CELL_HEIGHT / 2
|
||||||
|
- alt;
|
||||||
|
|
||||||
|
let col = TownColumn {
|
||||||
|
ground: rel_alt.div_euclid(CELL_HEIGHT),
|
||||||
|
kind: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
(col.ground, col)
|
||||||
|
},
|
||||||
|
|(col, pos)| {
|
||||||
|
if pos.z >= col.ground {
|
||||||
|
TownCell::empty()
|
||||||
|
} else {
|
||||||
|
TownCell::from(CellKind::Rock)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generation passes
|
||||||
|
vol.setup(rng);
|
||||||
|
vol.gen_roads(rng, 30);
|
||||||
|
vol.gen_parks(rng, 3);
|
||||||
|
vol.emplace_columns();
|
||||||
|
let houses = vol.gen_houses(rng, 50);
|
||||||
|
vol.gen_walls(rng);
|
||||||
|
vol.resolve_modules(rng);
|
||||||
|
vol.cull_unused();
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
center: Vec3::new(center.x, center.y, alt),
|
||||||
|
radius,
|
||||||
|
vol,
|
||||||
|
houses,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center(&self) -> Vec3<i32> {
|
||||||
|
self.center
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn radius(&self) -> i32 {
|
||||||
|
self.radius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TownVol {
|
||||||
|
fn floodfill(
|
||||||
|
&self,
|
||||||
|
limit: Option<usize>,
|
||||||
|
mut opens: HashSet<Vec2<i32>>,
|
||||||
|
mut f: impl FnMut(Vec2<i32>, &TownColumn) -> bool,
|
||||||
|
) -> HashSet<Vec2<i32>> {
|
||||||
|
let mut closed = HashSet::new();
|
||||||
|
|
||||||
|
while opens.len() > 0 {
|
||||||
|
let mut new_opens = HashSet::new();
|
||||||
|
|
||||||
|
'search: for open in opens.iter() {
|
||||||
|
for i in -1..2 {
|
||||||
|
for j in -1..2 {
|
||||||
|
let pos = *open + Vec2::new(i, j);
|
||||||
|
|
||||||
|
if let Some(col) = self.col(pos) {
|
||||||
|
if !closed.contains(&pos) && !opens.contains(&pos) && f(pos, col) {
|
||||||
|
match limit {
|
||||||
|
Some(limit)
|
||||||
|
if limit
|
||||||
|
<= new_opens.len() + closed.len() + opens.len() =>
|
||||||
|
{
|
||||||
|
break 'search
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
new_opens.insert(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closed = closed.union(&opens).copied().collect();
|
||||||
|
opens = new_opens;
|
||||||
|
}
|
||||||
|
|
||||||
|
closed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self, rng: &mut impl Rng) {
|
||||||
|
// Place a single road tile at first
|
||||||
|
let root_road = self
|
||||||
|
.size()
|
||||||
|
.map(|sz| (sz / 8) * 2 + rng.gen_range(0, sz / 4) * 2);
|
||||||
|
self.set_col_kind(root_road, Some(ColumnKind::Road));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_roads(&mut self, rng: &mut impl Rng, n: usize) {
|
||||||
|
const ATTEMPTS: usize = 5;
|
||||||
|
|
||||||
|
let mut junctions = HashSet::new();
|
||||||
|
junctions.insert(self.choose_column(rng, |_, col| col.is_road()).unwrap());
|
||||||
|
|
||||||
|
for road in 0..n {
|
||||||
|
for _ in 0..ATTEMPTS {
|
||||||
|
let start = *junctions.iter().choose(rng).unwrap();
|
||||||
|
//let start = self.choose_column(rng, |pos, col| pos.map(|e| e % 2 == 0).reduce_and() && col.is_road()).unwrap();
|
||||||
|
let dir = util::gen_dir(rng);
|
||||||
|
|
||||||
|
// If the direction we want to paint a path in is obstructed, abandon this attempt
|
||||||
|
if self
|
||||||
|
.col(start + dir)
|
||||||
|
.map(|col| !col.is_empty())
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How long should this road be?
|
||||||
|
let len = rng.gen_range(1, 10) * 2 + 1;
|
||||||
|
|
||||||
|
// Paint the road until we hit an obstacle
|
||||||
|
let success = (1..len)
|
||||||
|
.map(|i| start + dir * i)
|
||||||
|
.try_for_each(|pos| {
|
||||||
|
if self.col(pos).map(|col| col.is_empty()).unwrap_or(false) {
|
||||||
|
self.set_col_kind(pos, Some(ColumnKind::Road));
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
junctions.insert(pos);
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if success {
|
||||||
|
junctions.insert(start + dir * (len - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_parks(&mut self, rng: &mut impl Rng, n: usize) {
|
||||||
|
const ATTEMPTS: usize = 5;
|
||||||
|
|
||||||
|
for _ in 0..n {
|
||||||
|
for _ in 0..ATTEMPTS {
|
||||||
|
let start = self
|
||||||
|
.choose_column(rng, |pos, col| {
|
||||||
|
col.is_empty()
|
||||||
|
&& (0..4).any(|i| {
|
||||||
|
self.col(pos + util::dir(i))
|
||||||
|
.map(|col| col.is_road())
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut park =
|
||||||
|
self.floodfill(Some(16), [start].iter().copied().collect(), |_, col| {
|
||||||
|
col.is_empty()
|
||||||
|
});
|
||||||
|
|
||||||
|
if park.len() < 4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for cell in park {
|
||||||
|
self.set_col_kind(cell, Some(ColumnKind::Internal));
|
||||||
|
let col = self.col(cell).unwrap();
|
||||||
|
let ground = col.ground;
|
||||||
|
for z in 0..2 {
|
||||||
|
self.set(Vec3::new(cell.x, cell.y, ground + z), CellKind::Park.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_walls(&mut self, rng: &mut impl Rng) {
|
||||||
|
let mut outer = HashSet::new();
|
||||||
|
for i in 0..self.size().x {
|
||||||
|
outer.insert(Vec2::new(i, 0));
|
||||||
|
outer.insert(Vec2::new(i, self.size().y - 1));
|
||||||
|
}
|
||||||
|
for j in 0..self.size().y {
|
||||||
|
outer.insert(Vec2::new(0, j));
|
||||||
|
outer.insert(Vec2::new(self.size().x - 1, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outer = self.floodfill(None, outer, |_, col| col.is_empty());
|
||||||
|
|
||||||
|
let mut walls = HashSet::new();
|
||||||
|
let inner = self.floodfill(
|
||||||
|
None,
|
||||||
|
[self.size() / 2].iter().copied().collect(),
|
||||||
|
|pos, _| {
|
||||||
|
if outer.contains(&pos) {
|
||||||
|
walls.insert(pos);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
while let Some(wall) = walls
|
||||||
|
.iter()
|
||||||
|
.filter(|pos| {
|
||||||
|
let lateral_count = (0..4)
|
||||||
|
.filter(|i| walls.contains(&(**pos + util::dir(*i))))
|
||||||
|
.count();
|
||||||
|
let max_quadrant_count = (0..4)
|
||||||
|
.map(|i| {
|
||||||
|
let units = util::unit(i);
|
||||||
|
(0..2)
|
||||||
|
.map(|i| (0..2).map(move |j| (i, j)))
|
||||||
|
.flatten()
|
||||||
|
.filter(|(i, j)| walls.contains(&(**pos + units.0 * *i + units.1 * *j)))
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
lateral_count < 2 || (lateral_count == 2 && max_quadrant_count == 4)
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
let wall = *wall;
|
||||||
|
walls.remove(&wall);
|
||||||
|
}
|
||||||
|
|
||||||
|
for wall in walls.iter() {
|
||||||
|
let col = self.col(*wall).unwrap();
|
||||||
|
let ground = col.ground;
|
||||||
|
for z in -1..3 {
|
||||||
|
self.set(Vec3::new(wall.x, wall.y, ground + z), CellKind::Wall.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emplace_columns(&mut self) {
|
||||||
|
for i in 0..self.size().x {
|
||||||
|
for j in 0..self.size().y {
|
||||||
|
let col = self.col(Vec2::new(i, j)).unwrap();
|
||||||
|
let ground = col.ground;
|
||||||
|
|
||||||
|
match col.kind {
|
||||||
|
None => {}
|
||||||
|
Some(ColumnKind::Internal) => {}
|
||||||
|
Some(ColumnKind::External) => {}
|
||||||
|
Some(ColumnKind::Road) => {
|
||||||
|
for z in -1..2 {
|
||||||
|
self.set(Vec3::new(i, j, ground + z), CellKind::Road.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_houses(&mut self, rng: &mut impl Rng, n: usize) -> Vec<House> {
|
||||||
|
const ATTEMPTS: usize = 10;
|
||||||
|
|
||||||
|
let mut houses = Vec::new();
|
||||||
|
for _ in 0..n {
|
||||||
|
for _ in 0..ATTEMPTS {
|
||||||
|
let entrance = {
|
||||||
|
let start = self.choose_cell(rng, |_, cell| cell.is_road()).unwrap();
|
||||||
|
let dir = Vec3::from(util::gen_dir(rng));
|
||||||
|
|
||||||
|
if self
|
||||||
|
.get(start + dir)
|
||||||
|
.map(|col| !col.is_empty())
|
||||||
|
.unwrap_or(true)
|
||||||
|
|| self
|
||||||
|
.get(start + dir - Vec3::unit_z())
|
||||||
|
.map(|col| !col.is_foundation())
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
start + dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut cells: HashSet<_> = Some(entrance).into_iter().collect();
|
||||||
|
|
||||||
|
let mut energy = 1000;
|
||||||
|
while energy > 0 {
|
||||||
|
energy -= 1;
|
||||||
|
|
||||||
|
let parent = *cells.iter().choose(rng).unwrap();
|
||||||
|
let dir = util::UNITS_3D
|
||||||
|
.choose_weighted(rng, |pos| 1 + pos.z.max(0))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.get(parent + dir)
|
||||||
|
.map(|cell| cell.is_empty())
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& self
|
||||||
|
.get(parent + dir - Vec3::unit_z())
|
||||||
|
.map(|cell| {
|
||||||
|
cell.is_foundation()
|
||||||
|
|| cells.contains(&(parent + dir - Vec3::unit_z()))
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
cells.insert(parent + dir);
|
||||||
|
energy -= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove cells that are too isolated
|
||||||
|
loop {
|
||||||
|
let cells_copy = cells.clone();
|
||||||
|
|
||||||
|
let mut any_removed = false;
|
||||||
|
cells.retain(|pos| {
|
||||||
|
let neighbour_count = (0..6)
|
||||||
|
.filter(|i| {
|
||||||
|
let neighbour = pos + util::dir_3d(*i);
|
||||||
|
cells_copy.contains(&neighbour)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if neighbour_count < 3 {
|
||||||
|
any_removed = true;
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !any_removed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rid of houses that are too small
|
||||||
|
if cells.len() < 6 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for cell in cells {
|
||||||
|
self.set(cell, CellKind::House(houses.len()).into());
|
||||||
|
self.set_col_kind(Vec2::from(cell), Some(ColumnKind::Internal));
|
||||||
|
}
|
||||||
|
|
||||||
|
houses.push(House {
|
||||||
|
color: Rgb::new(rng.gen(), rng.gen(), rng.gen()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
houses
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cull_unused(&mut self) {
|
||||||
|
for x in 0..self.size().x {
|
||||||
|
for y in 0..self.size().y {
|
||||||
|
for z in self.col_range(Vec2::new(x, y)).unwrap().rev() {
|
||||||
|
let pos = Vec3::new(x, y, z);
|
||||||
|
|
||||||
|
// Remove foundations that don't have anything on top of them
|
||||||
|
if self.get(pos).unwrap().is_foundation()
|
||||||
|
&& self
|
||||||
|
.get(pos + Vec3::unit_z())
|
||||||
|
.map(TownCell::is_space)
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
self.set(pos, TownCell::empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_modules(&mut self, rng: &mut impl Rng) {
|
||||||
|
fn classify(cell: &TownCell, this_cell: &TownCell) -> ModuleKind {
|
||||||
|
match (&cell.kind, &this_cell.kind) {
|
||||||
|
(CellKind::House(a), CellKind::House(b)) if a == b => ModuleKind::This,
|
||||||
|
(CellKind::Wall, CellKind::Wall) => ModuleKind::This,
|
||||||
|
_ => ModuleKind::That,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in 0..self.size().x {
|
||||||
|
for y in 0..self.size().y {
|
||||||
|
for z in self.col_range(Vec2::new(x, y)).unwrap() {
|
||||||
|
let pos = Vec3::new(x, y, z);
|
||||||
|
let this_cell = if let Ok(this_cell) = self.get(pos) {
|
||||||
|
this_cell
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut signature = [ModuleKind::That; 6];
|
||||||
|
for i in 0..6 {
|
||||||
|
signature[i] = self
|
||||||
|
.get(pos + util::dir_3d(i))
|
||||||
|
.map(|cell| classify(cell, this_cell))
|
||||||
|
.unwrap_or(ModuleKind::That);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_list = if let Some(modules) = modules_from_kind(&this_cell.kind) {
|
||||||
|
modules
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let module = module_list
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, module)| {
|
||||||
|
let perms = [[0, 1, 2, 3], [3, 0, 1, 2], [2, 3, 0, 1], [1, 2, 3, 0]];
|
||||||
|
|
||||||
|
let mut rotated_signature = [ModuleKind::That; 6];
|
||||||
|
for (dir, perm) in perms.iter().enumerate() {
|
||||||
|
rotated_signature[perm[0]] = signature[0];
|
||||||
|
rotated_signature[perm[1]] = signature[1];
|
||||||
|
rotated_signature[perm[2]] = signature[2];
|
||||||
|
rotated_signature[perm[3]] = signature[3];
|
||||||
|
rotated_signature[4] = signature[4];
|
||||||
|
rotated_signature[5] = signature[5];
|
||||||
|
|
||||||
|
if &module.1[0..6] == &rotated_signature[0..6] {
|
||||||
|
return Some(Module { vol_idx: i, dir });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.choose(rng);
|
||||||
|
|
||||||
|
if let Some(module) = module {
|
||||||
|
let kind = this_cell.kind.clone();
|
||||||
|
self.set(
|
||||||
|
pos,
|
||||||
|
TownCell {
|
||||||
|
kind,
|
||||||
|
module: Some(module),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub enum ModuleKind {
|
||||||
|
This,
|
||||||
|
That,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module(name: &str, sig: [ModuleKind; 6]) -> (Arc<Structure>, [ModuleKind; 6]) {
|
||||||
|
(
|
||||||
|
assets::load(&format!("world.module.{}", name)).unwrap(),
|
||||||
|
sig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modules_from_kind(kind: &CellKind) -> Option<&'static [(Arc<Structure>, [ModuleKind; 6])]> {
|
||||||
|
match kind {
|
||||||
|
CellKind::House(_) => Some(&HOUSE_MODULES),
|
||||||
|
CellKind::Wall => Some(&WALL_MODULES),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref HOUSE_MODULES: Vec<(Arc<Structure>, [ModuleKind; 6])> = {
|
||||||
|
use ModuleKind::*;
|
||||||
|
vec![
|
||||||
|
module("human.floor_ground", [This, This, This, This, This, That]),
|
||||||
|
module("human.stair_ground", [This, This, This, This, This, That]),
|
||||||
|
module("human.corner_ground", [This, This, That, That, This, That]),
|
||||||
|
module("human.wall_ground", [This, This, This, That, This, That]),
|
||||||
|
module("human.door_ground", [This, This, This, That, This, That]),
|
||||||
|
module("human.window_ground", [This, This, This, That, This, That]),
|
||||||
|
module("human.floor_roof", [This, This, This, This, That, This]),
|
||||||
|
module("human.corner_roof", [This, This, That, That, That, This]),
|
||||||
|
module("human.chimney_roof", [This, This, That, That, That, This]),
|
||||||
|
module("human.wall_roof", [This, This, This, That, That, This]),
|
||||||
|
module("human.floor_upstairs", [This, This, This, This, This, This]),
|
||||||
|
module(
|
||||||
|
"human.balcony_upstairs",
|
||||||
|
[This, This, This, This, This, This],
|
||||||
|
),
|
||||||
|
module(
|
||||||
|
"human.corner_upstairs",
|
||||||
|
[This, This, That, That, This, This],
|
||||||
|
),
|
||||||
|
module("human.wall_upstairs", [This, This, This, That, This, This]),
|
||||||
|
module(
|
||||||
|
"human.window_upstairs",
|
||||||
|
[This, This, This, That, This, This],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
pub static ref WALL_MODULES: Vec<(Arc<Structure>, [ModuleKind; 6])> = {
|
||||||
|
use ModuleKind::*;
|
||||||
|
vec![
|
||||||
|
module("wall.edge_ground", [This, That, This, That, This, That]),
|
||||||
|
module("wall.edge_mid", [This, That, This, That, This, This]),
|
||||||
|
module("wall.edge_top", [This, That, This, That, That, This]),
|
||||||
|
module("wall.corner_ground", [This, This, That, That, This, That]),
|
||||||
|
module("wall.corner_mid", [This, This, That, That, This, This]),
|
||||||
|
module("wall.corner_top", [This, This, That, That, That, This]),
|
||||||
|
module("wall.end_top", [That, This, That, That, That, This]),
|
||||||
|
module("wall.single_top", [That, That, That, That, That, This]),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
42
world/src/generator/town/util.rs
Normal file
42
world/src/generator/town/util.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use rand::prelude::*;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
pub const UNITS: [Vec2<i32>; 4] = [
|
||||||
|
Vec2 { x: 1, y: 0 },
|
||||||
|
Vec2 { x: 0, y: 1 },
|
||||||
|
Vec2 { x: -1, y: 0 },
|
||||||
|
Vec2 { x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn dir(i: usize) -> Vec2<i32> {
|
||||||
|
UNITS[i % 4]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unit(i: usize) -> (Vec2<i32>, Vec2<i32>) {
|
||||||
|
(UNITS[i % 4], UNITS[(i + 1) % 4])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_unit(rng: &mut impl Rng) -> (Vec2<i32>, Vec2<i32>) {
|
||||||
|
unit(rng.gen_range(0, 4))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_dir(rng: &mut impl Rng) -> Vec2<i32> {
|
||||||
|
UNITS[rng.gen_range(0, 4)]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const UNITS_3D: [Vec3<i32>; 6] = [
|
||||||
|
Vec3 { x: 1, y: 0, z: 0 },
|
||||||
|
Vec3 { x: 0, y: 1, z: 0 },
|
||||||
|
Vec3 { x: -1, y: 0, z: 0 },
|
||||||
|
Vec3 { x: 0, y: -1, z: 0 },
|
||||||
|
Vec3 { x: 0, y: 0, z: 1 },
|
||||||
|
Vec3 { x: 0, y: 0, z: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn dir_3d(i: usize) -> Vec3<i32> {
|
||||||
|
UNITS_3D[i % 6]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_dir_3d(rng: &mut impl Rng) -> Vec3<i32> {
|
||||||
|
UNITS_3D[rng.gen_range(0, 6)]
|
||||||
|
}
|
227
world/src/generator/town/vol.rs
Normal file
227
world/src/generator/town/vol.rs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
use crate::util::Grid;
|
||||||
|
use common::vol::{BaseVol, ReadVol, Vox, WriteVol};
|
||||||
|
use rand::prelude::*;
|
||||||
|
use std::ops::Range;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ColumnKind {
|
||||||
|
Road,
|
||||||
|
Wall,
|
||||||
|
Internal,
|
||||||
|
External, // Outside the boundary wall
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct TownColumn {
|
||||||
|
pub ground: i32,
|
||||||
|
pub kind: Option<ColumnKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TownColumn {
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.kind.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_road(&self) -> bool {
|
||||||
|
self.kind
|
||||||
|
.as_ref()
|
||||||
|
.map(|kind| match kind {
|
||||||
|
ColumnKind::Road => true,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Module {
|
||||||
|
pub vol_idx: usize,
|
||||||
|
pub dir: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum CellKind {
|
||||||
|
Empty,
|
||||||
|
Park,
|
||||||
|
Rock,
|
||||||
|
Road,
|
||||||
|
Wall,
|
||||||
|
House(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TownCell {
|
||||||
|
pub kind: CellKind,
|
||||||
|
pub module: Option<Module>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TownCell {
|
||||||
|
pub fn is_road(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Road => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_space(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Empty => true,
|
||||||
|
CellKind::Park => true,
|
||||||
|
CellKind::Road => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_foundation(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Rock => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vox for TownCell {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
kind: CellKind::Empty,
|
||||||
|
module: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
CellKind::Empty => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CellKind> for TownCell {
|
||||||
|
fn from(kind: CellKind) -> Self {
|
||||||
|
Self { kind, module: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TownError {
|
||||||
|
OutOfBounds,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HEIGHT: usize = 24;
|
||||||
|
const UNDERGROUND_DEPTH: i32 = 5;
|
||||||
|
|
||||||
|
type GridItem = (i32, TownColumn, Vec<TownCell>);
|
||||||
|
|
||||||
|
pub struct TownVol {
|
||||||
|
grid: Grid<GridItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TownVol {
|
||||||
|
pub fn generate_from(
|
||||||
|
size: Vec2<i32>,
|
||||||
|
mut f: impl FnMut(Vec2<i32>) -> (i32, TownColumn),
|
||||||
|
mut g: impl FnMut((&TownColumn, Vec3<i32>)) -> TownCell,
|
||||||
|
) -> Self {
|
||||||
|
let mut this = Self {
|
||||||
|
grid: Grid::new(
|
||||||
|
(0, TownColumn::default(), vec![TownCell::empty(); HEIGHT]),
|
||||||
|
size,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (pos, (base, col, cells)) in this.grid.iter_mut() {
|
||||||
|
let column = f(pos);
|
||||||
|
*base = column.0;
|
||||||
|
*col = column.1;
|
||||||
|
for z in 0..HEIGHT {
|
||||||
|
cells[z] = g((
|
||||||
|
col,
|
||||||
|
Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + z as i32),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Vec2<i32> {
|
||||||
|
self.grid.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_col_kind(&mut self, pos: Vec2<i32>, kind: Option<ColumnKind>) {
|
||||||
|
self.grid.get_mut(pos).map(|col| col.1.kind = kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col(&self, pos: Vec2<i32>) -> Option<&TownColumn> {
|
||||||
|
self.grid.get(pos).map(|col| &col.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn col_range(&self, pos: Vec2<i32>) -> Option<Range<i32>> {
|
||||||
|
self.grid.get(pos).map(|col| {
|
||||||
|
let lower = col.0 - UNDERGROUND_DEPTH;
|
||||||
|
lower..lower + HEIGHT as i32
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn choose_column(
|
||||||
|
&self,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
mut f: impl FnMut(Vec2<i32>, &TownColumn) -> bool,
|
||||||
|
) -> Option<Vec2<i32>> {
|
||||||
|
self.grid
|
||||||
|
.iter()
|
||||||
|
.filter(|(pos, col)| f(*pos, &col.1))
|
||||||
|
.choose(rng)
|
||||||
|
.map(|(pos, _)| pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn choose_cell(
|
||||||
|
&self,
|
||||||
|
rng: &mut impl Rng,
|
||||||
|
mut f: impl FnMut(Vec3<i32>, &TownCell) -> bool,
|
||||||
|
) -> Option<Vec3<i32>> {
|
||||||
|
self.grid
|
||||||
|
.iter()
|
||||||
|
.map(|(pos, (base, _, cells))| {
|
||||||
|
cells.iter().enumerate().map(move |(i, cell)| {
|
||||||
|
(
|
||||||
|
Vec3::new(pos.x, pos.y, *base - UNDERGROUND_DEPTH + i as i32),
|
||||||
|
cell,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.filter(|(pos, cell)| f(*pos, *cell))
|
||||||
|
.choose(rng)
|
||||||
|
.map(|(pos, _)| pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BaseVol for TownVol {
|
||||||
|
type Vox = TownCell;
|
||||||
|
type Err = TownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadVol for TownVol {
|
||||||
|
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, Self::Err> {
|
||||||
|
match self.grid.get(Vec2::from(pos)) {
|
||||||
|
Some((base, _, cells)) => cells
|
||||||
|
.get((pos.z + UNDERGROUND_DEPTH - *base) as usize)
|
||||||
|
.ok_or(TownError::OutOfBounds),
|
||||||
|
None => Err(TownError::OutOfBounds),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WriteVol for TownVol {
|
||||||
|
fn set(&mut self, pos: Vec3<i32>, vox: Self::Vox) -> Result<(), Self::Err> {
|
||||||
|
match self.grid.get_mut(Vec2::from(pos)) {
|
||||||
|
Some((base, _, cells)) => cells
|
||||||
|
.get_mut((pos.z + UNDERGROUND_DEPTH - *base) as usize)
|
||||||
|
.map(|cell| *cell = vox)
|
||||||
|
.ok_or(TownError::OutOfBounds),
|
||||||
|
None => Err(TownError::OutOfBounds),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,13 +3,15 @@
|
|||||||
const_generics,
|
const_generics,
|
||||||
euclidean_division,
|
euclidean_division,
|
||||||
bind_by_move_pattern_guards,
|
bind_by_move_pattern_guards,
|
||||||
option_flattening
|
option_flattening,
|
||||||
|
label_break_value
|
||||||
)]
|
)]
|
||||||
|
|
||||||
mod all;
|
mod all;
|
||||||
mod block;
|
mod block;
|
||||||
mod column;
|
mod column;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
pub mod generator;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
@ -56,11 +58,11 @@ impl World {
|
|||||||
pub fn sample_columns(
|
pub fn sample_columns(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
|
) -> impl Sampler<Index = Vec2<i32>, Sample = Option<ColumnSample>> + '_ {
|
||||||
ColumnGen::new(self)
|
ColumnGen::new(&self.sim)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_blocks(&self) -> BlockGen {
|
pub fn sample_blocks(&self) -> BlockGen {
|
||||||
BlockGen::new(self, ColumnGen::new(self))
|
BlockGen::new(self, ColumnGen::new(&self.sim))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_chunk(&self, chunk_pos: Vec2<i32>) -> (TerrainChunk, ChunkSupplement) {
|
pub fn generate_chunk(&self, chunk_pos: Vec2<i32>) -> (TerrainChunk, ChunkSupplement) {
|
||||||
|
@ -11,7 +11,9 @@ use self::util::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
all::ForestKind,
|
all::ForestKind,
|
||||||
util::{seed_expan, Sampler, StructureGen2d},
|
column::ColumnGen,
|
||||||
|
generator::TownState,
|
||||||
|
util::{seed_expan, FastNoise, Sampler, StructureGen2d},
|
||||||
CONFIG,
|
CONFIG,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
@ -24,8 +26,10 @@ use noise::{
|
|||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use rand_chacha::ChaChaRng;
|
use rand_chacha::ChaChaRng;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
f32,
|
f32,
|
||||||
ops::{Add, Div, Mul, Neg, Sub},
|
ops::{Add, Div, Mul, Neg, Sub},
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
@ -73,7 +77,7 @@ pub(crate) struct GenCtx {
|
|||||||
pub small_nz: BasicMulti,
|
pub small_nz: BasicMulti,
|
||||||
pub rock_nz: HybridMulti,
|
pub rock_nz: HybridMulti,
|
||||||
pub cliff_nz: HybridMulti,
|
pub cliff_nz: HybridMulti,
|
||||||
pub warp_nz: BasicMulti,
|
pub warp_nz: FastNoise,
|
||||||
pub tree_nz: BasicMulti,
|
pub tree_nz: BasicMulti,
|
||||||
|
|
||||||
pub cave_0_nz: SuperSimplex,
|
pub cave_0_nz: SuperSimplex,
|
||||||
@ -82,6 +86,11 @@ pub(crate) struct GenCtx {
|
|||||||
pub structure_gen: StructureGen2d,
|
pub structure_gen: StructureGen2d,
|
||||||
pub region_gen: StructureGen2d,
|
pub region_gen: StructureGen2d,
|
||||||
pub cliff_gen: StructureGen2d,
|
pub cliff_gen: StructureGen2d,
|
||||||
|
|
||||||
|
pub fast_turb_x_nz: FastNoise,
|
||||||
|
pub fast_turb_y_nz: FastNoise,
|
||||||
|
|
||||||
|
pub town_gen: StructureGen2d,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WorldSim {
|
pub struct WorldSim {
|
||||||
@ -115,7 +124,7 @@ impl WorldSim {
|
|||||||
small_nz: BasicMulti::new().set_octaves(2).set_seed(gen_seed()),
|
small_nz: BasicMulti::new().set_octaves(2).set_seed(gen_seed()),
|
||||||
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()),
|
rock_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()),
|
||||||
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()),
|
cliff_nz: HybridMulti::new().set_persistence(0.3).set_seed(gen_seed()),
|
||||||
warp_nz: BasicMulti::new().set_octaves(3).set_seed(gen_seed()),
|
warp_nz: FastNoise::new(gen_seed()), //BasicMulti::new().set_octaves(3).set_seed(gen_seed()),
|
||||||
tree_nz: BasicMulti::new()
|
tree_nz: BasicMulti::new()
|
||||||
.set_octaves(12)
|
.set_octaves(12)
|
||||||
.set_persistence(0.75)
|
.set_persistence(0.75)
|
||||||
@ -133,6 +142,11 @@ impl WorldSim {
|
|||||||
// .set_octaves(6)
|
// .set_octaves(6)
|
||||||
// .set_persistence(0.5)
|
// .set_persistence(0.5)
|
||||||
.set_seed(gen_seed()),
|
.set_seed(gen_seed()),
|
||||||
|
|
||||||
|
fast_turb_x_nz: FastNoise::new(gen_seed()),
|
||||||
|
fast_turb_y_nz: FastNoise::new(gen_seed()),
|
||||||
|
|
||||||
|
town_gen: StructureGen2d::new(gen_seed(), 2048, 1024),
|
||||||
};
|
};
|
||||||
|
|
||||||
// "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied value is
|
// "Base" of the chunk, to be multiplied by CONFIG.mountain_scale (multiplied value is
|
||||||
@ -202,7 +216,7 @@ impl WorldSim {
|
|||||||
// maximal at 0. Also to be multiplied by CONFIG.mountain_scale.
|
// maximal at 0. Also to be multiplied by CONFIG.mountain_scale.
|
||||||
let alt_main = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32)
|
let alt_main = (gen_ctx.alt_nz.get((wposf.div(2_000.0)).into_array()) as f32)
|
||||||
.abs()
|
.abs()
|
||||||
.powf(1.45);
|
.powf(1.35);
|
||||||
|
|
||||||
(0.0 + alt_main
|
(0.0 + alt_main
|
||||||
+ (gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32)
|
+ (gen_ctx.small_nz.get((wposf.div(300.0)).into_array()) as f32)
|
||||||
@ -410,6 +424,40 @@ impl WorldSim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stage 2 - towns!
|
||||||
|
let mut maybe_towns = HashMap::new();
|
||||||
|
for i in 0..WORLD_SIZE.x {
|
||||||
|
for j in 0..WORLD_SIZE.y {
|
||||||
|
let chunk_pos = Vec2::new(i as i32, j as i32);
|
||||||
|
let wpos = chunk_pos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
||||||
|
e * sz as i32 + sz as i32 / 2
|
||||||
|
});
|
||||||
|
|
||||||
|
let near_towns = self.gen_ctx.town_gen.get(wpos);
|
||||||
|
let town = near_towns
|
||||||
|
.iter()
|
||||||
|
.min_by_key(|(pos, seed)| wpos.distance_squared(*pos));
|
||||||
|
|
||||||
|
if let Some((pos, _)) = town {
|
||||||
|
let maybe_town = maybe_towns
|
||||||
|
.entry(*pos)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
TownState::generate(*pos, &mut ColumnGen::new(self), &mut rng)
|
||||||
|
.map(|t| Arc::new(t))
|
||||||
|
})
|
||||||
|
.as_mut()
|
||||||
|
// Only care if we're close to the town
|
||||||
|
.filter(|town| {
|
||||||
|
Vec2::from(town.center()).distance_squared(wpos)
|
||||||
|
< town.radius().add(64).pow(2)
|
||||||
|
})
|
||||||
|
.cloned();
|
||||||
|
|
||||||
|
self.get_mut(chunk_pos).unwrap().structures.town = maybe_town;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.rng = rng;
|
self.rng = rng;
|
||||||
self.locations = locations;
|
self.locations = locations;
|
||||||
}
|
}
|
||||||
@ -425,6 +473,12 @@ impl WorldSim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_wpos(&self, wpos: Vec2<i32>) -> Option<&SimChunk> {
|
||||||
|
self.get(wpos.map2(Vec2::from(TerrainChunkSize::SIZE), |e, sz: u32| {
|
||||||
|
e / sz as i32
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
|
pub fn get_mut(&mut self, chunk_pos: Vec2<i32>) -> Option<&mut SimChunk> {
|
||||||
if chunk_pos
|
if chunk_pos
|
||||||
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
|
.map2(WORLD_SIZE, |e, sz| e >= 0 && e < sz as i32)
|
||||||
@ -491,7 +545,6 @@ pub struct SimChunk {
|
|||||||
pub alt_base: f32,
|
pub alt_base: f32,
|
||||||
pub alt: f32,
|
pub alt: f32,
|
||||||
pub temp: f32,
|
pub temp: f32,
|
||||||
pub dryness: f32,
|
|
||||||
pub humidity: f32,
|
pub humidity: f32,
|
||||||
pub rockiness: f32,
|
pub rockiness: f32,
|
||||||
pub is_cliffs: bool,
|
pub is_cliffs: bool,
|
||||||
@ -500,6 +553,8 @@ pub struct SimChunk {
|
|||||||
pub forest_kind: ForestKind,
|
pub forest_kind: ForestKind,
|
||||||
pub spawn_rate: f32,
|
pub spawn_rate: f32,
|
||||||
pub location: Option<LocationInfo>,
|
pub location: Option<LocationInfo>,
|
||||||
|
|
||||||
|
pub structures: Structures,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
@ -516,27 +571,16 @@ pub struct LocationInfo {
|
|||||||
pub near: Vec<RegionInfo>,
|
pub near: Vec<RegionInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Structures {
|
||||||
|
pub town: Option<Arc<TownState>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl SimChunk {
|
impl SimChunk {
|
||||||
fn generate(posi: usize, gen_ctx: &mut GenCtx, gen_cdf: &GenCdf) -> Self {
|
fn generate(posi: usize, gen_ctx: &mut GenCtx, gen_cdf: &GenCdf) -> Self {
|
||||||
let pos = uniform_idx_as_vec2(posi);
|
let pos = uniform_idx_as_vec2(posi);
|
||||||
let wposf = (pos * TerrainChunkSize::SIZE.map(|e| e as i32)).map(|e| e as f64);
|
let wposf = (pos * TerrainChunkSize::SIZE.map(|e| e as i32)).map(|e| e as f64);
|
||||||
|
|
||||||
// FIXME: Currently unused, but should represent fresh groundwater level.
|
|
||||||
// Should be correlated a little with humidity, somewhat negatively with altitude,
|
|
||||||
// and very negatively with difference in temperature from zero.
|
|
||||||
let dryness = gen_ctx.dry_nz.get(
|
|
||||||
(wposf
|
|
||||||
.add(Vec2::new(
|
|
||||||
gen_ctx
|
|
||||||
.dry_nz
|
|
||||||
.get((wposf.add(10000.0).div(500.0)).into_array())
|
|
||||||
* 150.0,
|
|
||||||
gen_ctx.dry_nz.get((wposf.add(0.0).div(500.0)).into_array()) * 150.0,
|
|
||||||
))
|
|
||||||
.div(2_000.0))
|
|
||||||
.into_array(),
|
|
||||||
) as f32;
|
|
||||||
|
|
||||||
let (_, alt_base) = gen_cdf.alt_base[posi];
|
let (_, alt_base) = gen_cdf.alt_base[posi];
|
||||||
let map_edge_factor = map_edge_factor(posi);
|
let map_edge_factor = map_edge_factor(posi);
|
||||||
let (_, chaos) = gen_cdf.chaos[posi];
|
let (_, chaos) = gen_cdf.chaos[posi];
|
||||||
@ -606,17 +650,13 @@ impl SimChunk {
|
|||||||
alt_base,
|
alt_base,
|
||||||
alt,
|
alt,
|
||||||
temp,
|
temp,
|
||||||
dryness,
|
|
||||||
humidity,
|
humidity,
|
||||||
rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
|
rockiness: (gen_ctx.rock_nz.get((wposf.div(1024.0)).into_array()) as f32)
|
||||||
.sub(0.1)
|
.sub(0.1)
|
||||||
.mul(1.3)
|
.mul(1.3)
|
||||||
.max(0.0),
|
.max(0.0),
|
||||||
is_cliffs: cliff > 0.5
|
is_cliffs: cliff > 0.5 && alt > CONFIG.sea_level + 5.0,
|
||||||
&& dryness > 0.05
|
near_cliffs: cliff > 0.2,
|
||||||
&& alt > CONFIG.sea_level + 5.0
|
|
||||||
&& dryness.abs() > 0.075,
|
|
||||||
near_cliffs: cliff > 0.25,
|
|
||||||
tree_density,
|
tree_density,
|
||||||
forest_kind: if temp > 0.0 {
|
forest_kind: if temp > 0.0 {
|
||||||
if temp > CONFIG.desert_temp {
|
if temp > CONFIG.desert_temp {
|
||||||
@ -678,6 +718,8 @@ impl SimChunk {
|
|||||||
},
|
},
|
||||||
spawn_rate: 1.0,
|
spawn_rate: 1.0,
|
||||||
location: None,
|
location: None,
|
||||||
|
|
||||||
|
structures: Structures { town: None },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
world/src/util/fast_noise.rs
Normal file
49
world/src/util/fast_noise.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use super::{RandomField, Sampler};
|
||||||
|
use std::f32;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
|
pub struct FastNoise {
|
||||||
|
noise: RandomField,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FastNoise {
|
||||||
|
pub const fn new(seed: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
noise: RandomField::new(seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn noise_at(&self, pos: Vec3<i32>) -> f32 {
|
||||||
|
(self.noise.get(pos) % 4096) as f32 / 4096.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sampler<'static> for FastNoise {
|
||||||
|
type Index = Vec3<f64>;
|
||||||
|
type Sample = f32;
|
||||||
|
|
||||||
|
fn get(&self, pos: Self::Index) -> Self::Sample {
|
||||||
|
let near_pos = pos.map(|e| e.floor() as i32);
|
||||||
|
|
||||||
|
let v000 = self.noise_at(near_pos + Vec3::new(0, 0, 0));
|
||||||
|
let v100 = self.noise_at(near_pos + Vec3::new(1, 0, 0));
|
||||||
|
let v010 = self.noise_at(near_pos + Vec3::new(0, 1, 0));
|
||||||
|
let v110 = self.noise_at(near_pos + Vec3::new(1, 1, 0));
|
||||||
|
let v001 = self.noise_at(near_pos + Vec3::new(0, 0, 1));
|
||||||
|
let v101 = self.noise_at(near_pos + Vec3::new(1, 0, 1));
|
||||||
|
let v011 = self.noise_at(near_pos + Vec3::new(0, 1, 1));
|
||||||
|
let v111 = self.noise_at(near_pos + Vec3::new(1, 1, 1));
|
||||||
|
|
||||||
|
let factor = pos.map(|e| 0.5 - (e.fract() as f32 * f32::consts::PI).cos() * 0.5);
|
||||||
|
|
||||||
|
let x00 = Lerp::lerp(v000, v100, factor.x);
|
||||||
|
let x10 = Lerp::lerp(v010, v110, factor.x);
|
||||||
|
let x01 = Lerp::lerp(v001, v101, factor.x);
|
||||||
|
let x11 = Lerp::lerp(v011, v111, factor.x);
|
||||||
|
|
||||||
|
let y0 = Lerp::lerp(x00, x10, factor.y);
|
||||||
|
let y1 = Lerp::lerp(x01, x11, factor.y);
|
||||||
|
|
||||||
|
Lerp::lerp(y0, y1, factor.z) * 2.0 - 1.0
|
||||||
|
}
|
||||||
|
}
|
74
world/src/util/grid.rs
Normal file
74
world/src/util/grid.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use vek::*;
|
||||||
|
|
||||||
|
pub struct Grid<T> {
|
||||||
|
cells: Vec<T>,
|
||||||
|
size: Vec2<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Grid<T> {
|
||||||
|
pub fn new(default_cell: T, size: Vec2<i32>) -> Self {
|
||||||
|
Self {
|
||||||
|
cells: vec![default_cell; size.product() as usize],
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idx(&self, pos: Vec2<i32>) -> Option<usize> {
|
||||||
|
if pos.map2(self.size, |e, sz| e >= 0 && e < sz).reduce_and() {
|
||||||
|
Some((pos.y * self.size.x + pos.x) as usize)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Vec2<i32> {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, pos: Vec2<i32>) -> Option<&T> {
|
||||||
|
self.cells.get(self.idx(pos)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, pos: Vec2<i32>) -> Option<&mut T> {
|
||||||
|
let idx = self.idx(pos)?;
|
||||||
|
self.cells.get_mut(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, pos: Vec2<i32>, cell: T) -> Option<()> {
|
||||||
|
let idx = self.idx(pos)?;
|
||||||
|
self.cells.get_mut(idx).map(|c| *c = cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (Vec2<i32>, &T)> + '_ {
|
||||||
|
let w = self.size.x;
|
||||||
|
self.cells
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Vec2<i32>, &mut T)> + '_ {
|
||||||
|
let w = self.size.x;
|
||||||
|
self.cells
|
||||||
|
.iter_mut()
|
||||||
|
.enumerate()
|
||||||
|
.map(move |(i, cell)| (Vec2::new(i as i32 % w, i as i32 / w), cell))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_area(
|
||||||
|
&self,
|
||||||
|
pos: Vec2<i32>,
|
||||||
|
size: Vec2<i32>,
|
||||||
|
) -> impl Iterator<Item = Option<(Vec2<i32>, &T)>> + '_ {
|
||||||
|
(0..size.x)
|
||||||
|
.map(move |x| {
|
||||||
|
(0..size.y).map(move |y| {
|
||||||
|
Some((
|
||||||
|
pos + Vec2::new(x, y),
|
||||||
|
&self.cells[self.idx(pos + Vec2::new(x, y))?],
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
pub mod fast_noise;
|
||||||
|
pub mod grid;
|
||||||
pub mod hash_cache;
|
pub mod hash_cache;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
@ -7,6 +9,8 @@ pub mod unit_chooser;
|
|||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
fast_noise::FastNoise,
|
||||||
|
grid::Grid,
|
||||||
hash_cache::HashCache,
|
hash_cache::HashCache,
|
||||||
random::{RandomField, RandomPerm},
|
random::{RandomField, RandomPerm},
|
||||||
sampler::{Sampler, SamplerMut},
|
sampler::{Sampler, SamplerMut},
|
||||||
|
@ -11,26 +11,26 @@ impl RandomField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler for RandomField {
|
impl Sampler<'static> for RandomField {
|
||||||
type Index = Vec3<i32>;
|
type Index = Vec3<i32>;
|
||||||
type Sample = u32;
|
type Sample = u32;
|
||||||
|
|
||||||
fn get(&self, pos: Self::Index) -> Self::Sample {
|
fn get(&self, pos: Self::Index) -> Self::Sample {
|
||||||
let pos = pos.map(|e| (e * 13 + (1 << 31)) as u32);
|
let pos = pos.map(|e| u32::from_le_bytes(e.to_le_bytes()));
|
||||||
|
|
||||||
let mut a = self.seed;
|
let mut a = self.seed;
|
||||||
a = (a ^ 61) ^ (a >> 16);
|
a = (a ^ 61) ^ (a >> 16);
|
||||||
a = a + (a << 3);
|
a = a.wrapping_add(a << 3);
|
||||||
a = a ^ pos.x;
|
a = a ^ pos.x;
|
||||||
a = a ^ (a >> 4);
|
a = a ^ (a >> 4);
|
||||||
a = a * 0x27d4eb2d;
|
a = a.wrapping_mul(0x27d4eb2d);
|
||||||
a = a ^ (a >> 15);
|
a = a ^ (a >> 15);
|
||||||
a = a ^ pos.y;
|
a = a ^ pos.y;
|
||||||
a = (a ^ 61) ^ (a >> 16);
|
a = (a ^ 61) ^ (a >> 16);
|
||||||
a = a + (a << 3);
|
a = a.wrapping_add(a << 3);
|
||||||
a = a ^ (a >> 4);
|
a = a ^ (a >> 4);
|
||||||
a = a ^ pos.z;
|
a = a ^ pos.z;
|
||||||
a = a * 0x27d4eb2d;
|
a = a.wrapping_mul(0x27d4eb2d);
|
||||||
a = a ^ (a >> 15);
|
a = a ^ (a >> 15);
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
@ -46,17 +46,19 @@ impl RandomPerm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler for RandomPerm {
|
impl Sampler<'static> for RandomPerm {
|
||||||
type Index = u32;
|
type Index = u32;
|
||||||
type Sample = u32;
|
type Sample = u32;
|
||||||
|
|
||||||
fn get(&self, perm: Self::Index) -> Self::Sample {
|
fn get(&self, perm: Self::Index) -> Self::Sample {
|
||||||
let mut a = perm;
|
let a = self
|
||||||
a = (a ^ 61) ^ (a >> 16);
|
.seed
|
||||||
a = a + (a << 3);
|
.wrapping_mul(3471)
|
||||||
a = a ^ (a >> 4);
|
.wrapping_add(perm)
|
||||||
a = a * 0x27d4eb2d;
|
.wrapping_add(0x3BE7172B)
|
||||||
a = a ^ (a >> 15);
|
.wrapping_mul(perm)
|
||||||
a
|
.wrapping_add(0x172A3BE1);
|
||||||
|
let b = a.wrapping_mul(a);
|
||||||
|
b ^ (a >> 17) ^ b >> 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
pub trait Sampler: Sized {
|
pub trait Sampler<'a>: Sized {
|
||||||
type Index;
|
type Index: 'a;
|
||||||
type Sample;
|
type Sample: 'a;
|
||||||
|
|
||||||
fn get(&self, index: Self::Index) -> Self::Sample;
|
fn get(&self, index: Self::Index) -> Self::Sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SamplerMut: Sized {
|
pub trait SamplerMut<'a>: Sized {
|
||||||
type Index;
|
type Index: 'a;
|
||||||
type Sample;
|
type Sample: 'a;
|
||||||
|
|
||||||
fn get(&mut self, index: Self::Index) -> Self::Sample;
|
fn get(&mut self, index: Self::Index) -> Self::Sample;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ impl StructureGen2d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler for StructureGen2d {
|
impl Sampler<'static> for StructureGen2d {
|
||||||
type Index = Vec2<i32>;
|
type Index = Vec2<i32>;
|
||||||
type Sample = [(Vec2<i32>, u32); 9];
|
type Sample = [(Vec2<i32>, u32); 9];
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ impl UnitChooser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler for UnitChooser {
|
impl Sampler<'static> for UnitChooser {
|
||||||
type Index = u32;
|
type Index = u32;
|
||||||
type Sample = (Vec2<i32>, Vec2<i32>);
|
type Sample = (Vec2<i32>, Vec2<i32>);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user