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!1768
This commit is contained in:
commit
52cfc2f301
@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- New enemies in 5 lower dungeons
|
||||
- Added on join event in plugins
|
||||
- Item stacking and splitting
|
||||
- Procedural trees (currently only oaks and pines are procedural)
|
||||
- Cliffs on steep slopes
|
||||
- Giant tree sites
|
||||
|
||||
### Changed
|
||||
|
||||
@ -63,6 +66,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Items can be requested from the counterparty's inventory during trade.
|
||||
- Savanna grasses restricted to savanna, cacti to desert.
|
||||
- Fireworks recursively shoot more fireworks.
|
||||
- Improved static light rendering and illumination
|
||||
- Improved the tree spawning model to allow for overlapping forests
|
||||
- Changed sunlight (and, in general, static light) propagation through blocks to allow for more material properties
|
||||
|
||||
### Removed
|
||||
|
||||
|
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -5884,6 +5884,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"bitvec",
|
||||
"criterion",
|
||||
"enum-iterator",
|
||||
"fxhash",
|
||||
"hashbrown 0.9.1",
|
||||
"image",
|
||||
@ -5899,6 +5900,8 @@ dependencies = [
|
||||
"rayon",
|
||||
"ron",
|
||||
"serde",
|
||||
"structopt",
|
||||
"svg_fmt",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"vek 0.14.1",
|
||||
|
BIN
assets/voxygen/element/map/excl.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/map/excl.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/map/tree.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/map/tree.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
assets/voxygen/element/map/tree_hover.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/map/tree_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -9,9 +9,11 @@
|
||||
"hud.map.difficulty": "Difficulty",
|
||||
"hud.map.towns": "Towns",
|
||||
"hud.map.castles": "Castles",
|
||||
"hud.map.dungeons": "Dungeons",
|
||||
"hud.map.dungeons": "Dungeons",
|
||||
"hud.map.caves": "Caves",
|
||||
"hud.map.cave": "Cave",
|
||||
"hud.map.trees": "Giant Trees",
|
||||
"hud.map.tree": "Giant Tree",
|
||||
"hud.map.town": "Town",
|
||||
"hud.map.castle": "Castle",
|
||||
"hud.map.dungeon": "Dungeon",
|
||||
|
@ -1,5 +1,7 @@
|
||||
#version 330 core
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
@ -50,6 +52,7 @@ uniform u_locals {
|
||||
mat4 model_mat;
|
||||
vec4 highlight_col;
|
||||
vec4 model_light;
|
||||
vec4 model_glow;
|
||||
ivec4 atlas_offs;
|
||||
vec3 model_pos;
|
||||
// bit 0 - is player
|
||||
@ -181,7 +184,10 @@ void main() {
|
||||
|
||||
float ao = f_ao * sqrt(f_ao);//0.25 + f_ao * 0.75; ///*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15;
|
||||
|
||||
vec3 glow = pow(model_light.y, 3) * 4 * GLOW_COLOR;
|
||||
float glow_mag = length(model_glow.xyz);
|
||||
vec3 glow = pow(model_glow.w, 2) * 4
|
||||
* glow_light(f_pos)
|
||||
* (max(dot(f_norm, model_glow.xyz / glow_mag) * 0.5 + 0.5, 0.0) + max(1.0 - glow_mag, 0.0));
|
||||
emitted_light += glow;
|
||||
|
||||
reflected_light *= ao;
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
|
||||
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
|
||||
@ -28,6 +30,7 @@ uniform u_locals {
|
||||
mat4 model_mat;
|
||||
vec4 highlight_col;
|
||||
vec4 model_light;
|
||||
vec4 model_glow;
|
||||
ivec4 atlas_offs;
|
||||
vec3 model_pos;
|
||||
// bit 0 - is player
|
||||
|
@ -139,6 +139,10 @@ float lights_at(vec3 wpos, vec3 wnorm, vec3 /*cam_to_frag*/view_dir, vec3 mu, ve
|
||||
vec3 difference = light_pos - wpos;
|
||||
float distance_2 = dot(difference, difference);
|
||||
|
||||
if (distance_2 > 10000.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// float strength = attenuation_strength(difference);// pow(attenuation_strength(difference), 0.6);
|
||||
// NOTE: This normalizes strength to 0.25 at the center of the point source.
|
||||
float strength = 1.0 / (4 + distance_2);
|
||||
@ -183,7 +187,12 @@ float lights_at(vec3 wpos, vec3 wnorm, vec3 /*cam_to_frag*/view_dir, vec3 mu, ve
|
||||
vec3 direct_light = PI * color * strength * square_factor * light_reflection_factor(/*direct_norm_dir*/wnorm, /*cam_to_frag*/view_dir, direct_light_dir, k_d, k_s, alpha, voxel_norm, voxel_lighting);
|
||||
float computed_shadow = ShadowCalculationPoint(i, -difference, wnorm, wpos/*, light_distance*/);
|
||||
// directed_light += is_direct ? max(computed_shadow, /*LIGHT_AMBIANCE*/0.0) * direct_light * square_factor : vec3(0.0);
|
||||
directed_light += is_direct ? mix(LIGHT_AMBIANCE, 1.0, computed_shadow) * direct_light * square_factor : vec3(0.0);
|
||||
#ifdef FIGURE_SHADER
|
||||
vec3 ambiance = color * 0.5 / distance_2; // Non-physical hack, but it's pretty subtle and *damn* does it make shadows on characters look better
|
||||
#else
|
||||
vec3 ambiance = vec3(0.0);
|
||||
#endif
|
||||
directed_light += (is_direct ? mix(LIGHT_AMBIANCE, 1.0, computed_shadow) * direct_light * square_factor : vec3(0.0)) + ambiance;
|
||||
// directed_light += (is_direct ? 1.0 : LIGHT_AMBIANCE) * max(computed_shadow, /*LIGHT_AMBIANCE*/0.0) * direct_light * square_factor;// : vec3(0.0);
|
||||
// directed_light += mix(LIGHT_AMBIANCE, 1.0, computed_shadow) * direct_light * square_factor;
|
||||
// ambient_light += is_direct ? vec3(0.0) : vec3(0.0); // direct_light * square_factor * LIGHT_AMBIANCE;
|
||||
|
@ -26,6 +26,11 @@ float hash_fast(uvec3 q)
|
||||
return float(n) * (1.0/float(0xffffffffU));
|
||||
}
|
||||
|
||||
// 2D, but using shifted 2D textures
|
||||
float noise_2d(vec2 pos) {
|
||||
return texture(t_noise, pos).x;
|
||||
}
|
||||
|
||||
// 3D, but using shifted 2D textures
|
||||
float noise_3d(vec3 pos) {
|
||||
pos.z *= 15.0;
|
||||
|
@ -44,8 +44,15 @@ const float UNDERWATER_MIST_DIST = 100.0;
|
||||
|
||||
const float PERSISTENT_AMBIANCE = 1.0 / 32.0;// 1.0 / 80; // 1.0 / 512; // 0.00125 // 0.1;// 0.025; // 0.1;
|
||||
|
||||
// Glow from static light sources
|
||||
// Allowed to be > 1 due to HDR
|
||||
const vec3 GLOW_COLOR = vec3(2, 1.30, 0.1);
|
||||
const vec3 GLOW_COLOR = vec3(3.0, 0.9, 0.05);
|
||||
|
||||
// Calculate glow from static light sources, + some noise for flickering.
|
||||
// TODO: Optionally disable the flickering for performance?
|
||||
vec3 glow_light(vec3 pos) {
|
||||
return GLOW_COLOR * (1.0 + (noise_3d(vec3(pos.xy * 0.005, tick.x * 0.5)) - 0.5) * 1.0);
|
||||
}
|
||||
|
||||
//vec3 get_sun_dir(float time_of_day) {
|
||||
// const float TIME_FACTOR = (PI * 2.0) / (3600.0 * 24.0);
|
||||
|
@ -1,6 +1,8 @@
|
||||
#version 330 core
|
||||
// #extension ARB_texture_storage : enable
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
@ -40,6 +42,7 @@ uniform u_locals {
|
||||
mat4 model_mat;
|
||||
vec4 highlight_col;
|
||||
vec4 model_light;
|
||||
vec4 model_glow;
|
||||
ivec4 atlas_offs;
|
||||
vec3 model_pos;
|
||||
// bit 0 - is player
|
||||
|
@ -640,7 +640,7 @@ void main() {
|
||||
// f_col = f_col + (hash(vec4(floor(vec3(focus_pos.xy + splay(v_pos_orig), f_pos.z)) * 3.0 - round(f_norm) * 0.5, 0)) - 0.5) * 0.05; // Small-scale noise
|
||||
vec3 surf_color;
|
||||
#if (FLUID_MODE == FLUID_MODE_SHINY)
|
||||
if (f_col_raw.b > max(f_col_raw.r, f_col_raw.g) * 2.0 && dot(vec3(0, 0, 1), f_norm) > 0.9) {
|
||||
if (length(f_col_raw - vec3(0.02, 0.06, 0.22)) < 0.025 && dot(vec3(0, 0, 1), f_norm) > 0.9) {
|
||||
vec3 water_color = (1.0 - MU_WATER) * MU_SCATTER;
|
||||
|
||||
vec3 reflect_ray = cam_to_frag * vec3(1, 1, -1);
|
||||
|
@ -275,7 +275,7 @@ void main() {
|
||||
vec3(0, 0, -2)
|
||||
) + vec3(sin(lifetime), sin(lifetime + 0.7), sin(lifetime * 0.5)) * 2.0,
|
||||
vec3(4),
|
||||
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.5 + rand6 * 0.5) * 0.6, 0), 1),
|
||||
vec4(vec3(0.2 + rand7 * 0.2, 0.2 + (0.25 + rand6 * 0.5) * 0.3, 0) * (0.75 + rand1 * 0.5), 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), rand9 * 3 + lifetime * 5)
|
||||
);
|
||||
} else if (inst_mode == SNOW) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
#version 330 core
|
||||
|
||||
#define FIGURE_SHADER
|
||||
|
||||
#include <constants.glsl>
|
||||
|
||||
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
|
||||
@ -26,6 +28,7 @@ uniform u_locals {
|
||||
mat4 model_mat;
|
||||
vec4 highlight_col;
|
||||
vec4 model_light;
|
||||
vec4 model_glow;
|
||||
ivec4 atlas_offs;
|
||||
vec3 model_pos;
|
||||
int flags;
|
||||
|
@ -174,7 +174,7 @@ void main() {
|
||||
reflected_light += point_light; */
|
||||
|
||||
// float ao = /*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15;
|
||||
vec3 glow = pow(f_inst_light.y, 3) * 4 * GLOW_COLOR;
|
||||
vec3 glow = pow(f_inst_light.y, 3) * 4 * glow_light(f_pos);
|
||||
emitted_light += glow;
|
||||
|
||||
float ao = f_ao;
|
||||
|
@ -266,8 +266,7 @@ void main() {
|
||||
max_light *= f_light;
|
||||
|
||||
// TODO: Apply AO after this
|
||||
vec3 glow = GLOW_COLOR * (pow(f_glow, 6) * 8 + pow(f_glow, 2) * 0.5);
|
||||
emitted_light += glow;
|
||||
vec3 glow = glow_light(f_pos) * (pow(f_glow, 6) * 5 + pow(f_glow, 1.5) * 2);
|
||||
reflected_light += glow;
|
||||
|
||||
max_light += lights_at(f_pos, f_norm, view_dir, mu, cam_attenuation, fluid_alt, k_a, k_d, k_s, alpha, f_norm, 1.0, emitted_light, reflected_light);
|
||||
|
BIN
assets/voxygen/voxel/sprite/lantern/lantern-orange.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/sprite/lantern/lantern-orange.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -2824,4 +2824,16 @@ SapphireSmall: Some((
|
||||
],
|
||||
wind_sway: 0.0,
|
||||
)),
|
||||
|
||||
// Lantern
|
||||
Lantern: Some((
|
||||
variations: [
|
||||
(
|
||||
model: "voxygen.voxel.sprite.lantern.lantern-orange",
|
||||
offset: (-2.5, -2.5, 0.0),
|
||||
lod_axes: (0.0, 0.0, 0.0),
|
||||
),
|
||||
],
|
||||
wind_sway: 0.0,
|
||||
)),
|
||||
)
|
||||
|
@ -138,4 +138,5 @@ pub enum SiteKind {
|
||||
Dungeon { difficulty: u32 },
|
||||
Castle,
|
||||
Cave,
|
||||
Tree,
|
||||
}
|
||||
|
@ -32,6 +32,13 @@ impl<T> FromIterator<T> for Path<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoIterator for Path<T> {
|
||||
type IntoIter = std::vec::IntoIter<T>;
|
||||
type Item = T;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter { self.nodes.into_iter() }
|
||||
}
|
||||
|
||||
impl<T> Path<T> {
|
||||
pub fn is_empty(&self) -> bool { self.nodes.is_empty() }
|
||||
|
||||
|
@ -1,73 +1,187 @@
|
||||
use std::{
|
||||
cmp::{Eq, PartialEq},
|
||||
cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
|
||||
fmt, hash,
|
||||
marker::PhantomData,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
// NOTE: We use u64 to make sure we are consistent across all machines. We
|
||||
// assume that usize fits into 8 bytes.
|
||||
pub struct Id<T>(u64, PhantomData<T>);
|
||||
pub struct Id<T> {
|
||||
idx: u32,
|
||||
gen: u32,
|
||||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Id<T> {
|
||||
pub fn id(&self) -> u64 { self.0 }
|
||||
pub fn id(&self) -> u64 { self.idx as u64 | ((self.gen as u64) << 32) }
|
||||
}
|
||||
|
||||
impl<T> Copy for Id<T> {}
|
||||
impl<T> Clone for Id<T> {
|
||||
fn clone(&self) -> Self { Self(self.0, PhantomData) }
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
idx: self.idx,
|
||||
gen: self.gen,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> Eq for Id<T> {}
|
||||
impl<T> PartialEq for Id<T> {
|
||||
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
|
||||
fn eq(&self, other: &Self) -> bool { self.idx == other.idx && self.gen == other.gen }
|
||||
}
|
||||
impl<T> Ord for Id<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering { (self.idx, self.gen).cmp(&(other.idx, other.gen)) }
|
||||
}
|
||||
impl<T> PartialOrd for Id<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
|
||||
}
|
||||
impl<T> fmt::Debug for Id<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Id<{}>({})", std::any::type_name::<T>(), self.0)
|
||||
write!(
|
||||
f,
|
||||
"Id<{}>({}, {})",
|
||||
std::any::type_name::<T>(),
|
||||
self.idx,
|
||||
self.gen
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> hash::Hash for Id<T> {
|
||||
fn hash<H: hash::Hasher>(&self, h: &mut H) { self.0.hash(h); }
|
||||
fn hash<H: hash::Hasher>(&self, h: &mut H) {
|
||||
self.idx.hash(h);
|
||||
self.gen.hash(h);
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry<T> {
|
||||
gen: u32,
|
||||
item: Option<T>,
|
||||
}
|
||||
|
||||
pub struct Store<T> {
|
||||
items: Vec<T>,
|
||||
entries: Vec<Entry<T>>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<T> Default for Store<T> {
|
||||
fn default() -> Self { Self { items: Vec::new() } }
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Store<T> {
|
||||
pub fn is_empty(&self) -> bool { self.len == 0 }
|
||||
|
||||
pub fn len(&self) -> usize { self.len }
|
||||
|
||||
pub fn contains(&self, id: Id<T>) -> bool {
|
||||
self.entries
|
||||
.get(id.idx as usize)
|
||||
.map(|e| e.gen == id.gen)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn get(&self, id: Id<T>) -> &T {
|
||||
// NOTE: Safe conversion, because it came from usize.
|
||||
self.items.get(id.0 as usize).unwrap()
|
||||
let entry = self.entries.get(id.idx as usize).unwrap();
|
||||
if entry.gen == id.gen {
|
||||
entry.item.as_ref().unwrap()
|
||||
} else {
|
||||
panic!("Stale ID used to access store entry");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: Id<T>) -> &mut T {
|
||||
// NOTE: Safe conversion, because it came from usize.
|
||||
self.items.get_mut(id.0 as usize).unwrap()
|
||||
let entry = self.entries.get_mut(id.idx as usize).unwrap();
|
||||
if entry.gen == id.gen {
|
||||
entry.item.as_mut().unwrap()
|
||||
} else {
|
||||
panic!("Stale ID used to access store entry");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> impl Iterator<Item = Id<T>> {
|
||||
(0..self.items.len()).map(|i| Id(i as u64, PhantomData))
|
||||
pub fn ids(&self) -> impl Iterator<Item = Id<T>> + '_ { self.iter().map(|(id, _)| id) }
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &T> + '_ { self.iter().map(|(_, item)| item) }
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> + '_ {
|
||||
self.iter_mut().map(|(_, item)| item)
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &T> { self.items.iter() }
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> + '_ {
|
||||
self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, entry)| {
|
||||
Some(Id {
|
||||
idx: idx as u32,
|
||||
gen: entry.gen,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
.zip(entry.item.as_ref())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> { self.items.iter_mut() }
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Id<T>, &T)> { self.ids().zip(self.values()) }
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> {
|
||||
self.ids().zip(self.values_mut())
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Id<T>, &mut T)> + '_ {
|
||||
self.entries
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.filter_map(move |(idx, entry)| {
|
||||
Some(Id {
|
||||
idx: idx as u32,
|
||||
gen: entry.gen,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
.zip(entry.item.as_mut())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, item: T) -> Id<T> {
|
||||
// NOTE: Assumes usize fits into 8 bytes.
|
||||
let id = Id(self.items.len() as u64, PhantomData);
|
||||
self.items.push(item);
|
||||
id
|
||||
if self.len < self.entries.len() {
|
||||
// TODO: Make this more efficient with a lookahead system
|
||||
let (idx, entry) = self
|
||||
.entries
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.find(|(_, e)| e.item.is_none())
|
||||
.unwrap();
|
||||
entry.item = Some(item);
|
||||
assert!(entry.gen < u32::MAX);
|
||||
entry.gen += 1;
|
||||
Id {
|
||||
idx: idx as u32,
|
||||
gen: entry.gen,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
} else {
|
||||
assert!(self.entries.len() < (u32::MAX - 1) as usize);
|
||||
let id = Id {
|
||||
idx: self.entries.len() as u32,
|
||||
gen: 0,
|
||||
phantom: PhantomData,
|
||||
};
|
||||
self.entries.push(Entry {
|
||||
gen: 0,
|
||||
item: Some(item),
|
||||
});
|
||||
self.len += 1;
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: Id<T>) -> Option<T> {
|
||||
if let Some(item) = self
|
||||
.entries
|
||||
.get_mut(id.idx as usize)
|
||||
.and_then(|e| if e.gen == id.gen { e.item.take() } else { None })
|
||||
{
|
||||
self.len -= 1;
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,10 +189,24 @@ impl Block {
|
||||
| SpriteKind::RubySmall
|
||||
| SpriteKind::EmeraldSmall
|
||||
| SpriteKind::SapphireSmall => Some(3),
|
||||
SpriteKind::Lantern => Some(24),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// minimum block, attenuation
|
||||
#[inline]
|
||||
pub fn get_max_sunlight(&self) -> (u8, u8) {
|
||||
match self.kind() {
|
||||
BlockKind::Water => (1, 1),
|
||||
BlockKind::Leaves => (9, 255),
|
||||
BlockKind::Wood => (6, 2),
|
||||
BlockKind::Snow => (6, 2),
|
||||
_ if self.is_opaque() => (0, 255),
|
||||
_ => (0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_solid(&self) -> bool {
|
||||
self.get_sprite()
|
||||
|
@ -142,6 +142,7 @@ make_case_elim!(
|
||||
Seagrass = 0x73,
|
||||
RedAlgae = 0x74,
|
||||
UnderwaterVent = 0x75,
|
||||
Lantern = 0x76,
|
||||
}
|
||||
);
|
||||
|
||||
@ -201,6 +202,7 @@ impl SpriteKind {
|
||||
| SpriteKind::DropGate => 1.0,
|
||||
// TODO: Figure out if this should be solid or not.
|
||||
SpriteKind::Shelf => 1.0,
|
||||
SpriteKind::Lantern => 0.9,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@ -289,6 +291,7 @@ impl SpriteKind {
|
||||
| SpriteKind::Bowl
|
||||
| SpriteKind::VialEmpty
|
||||
| SpriteKind::FireBowlGround
|
||||
| SpriteKind::Lantern
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -30,11 +30,15 @@ make_case_elim!(
|
||||
Hollow = 13,
|
||||
Liana = 14,
|
||||
Normal(color: Rgb<u8>) = 15,
|
||||
Log = 16,
|
||||
Block(kind: BlockKind, color: Rgb<u8>) = 17,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StructureError {}
|
||||
pub enum StructureError {
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Structure {
|
||||
@ -44,7 +48,6 @@ pub struct Structure {
|
||||
|
||||
struct BaseStructure {
|
||||
vol: Dyna<StructureBlock, ()>,
|
||||
empty: StructureBlock,
|
||||
default_kind: BlockKind,
|
||||
}
|
||||
|
||||
@ -109,7 +112,7 @@ impl ReadVol for Structure {
|
||||
fn get(&self, pos: Vec3<i32>) -> Result<&Self::Vox, StructureError> {
|
||||
match self.base.vol.get(pos + self.center) {
|
||||
Ok(block) => Ok(block),
|
||||
Err(DynaError::OutOfBounds) => Ok(&self.base.empty),
|
||||
Err(DynaError::OutOfBounds) => Err(StructureError::OutOfBounds),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,13 +168,11 @@ impl assets::Compound for BaseStructure {
|
||||
|
||||
Ok(BaseStructure {
|
||||
vol,
|
||||
empty: StructureBlock::None,
|
||||
default_kind: BlockKind::Misc,
|
||||
})
|
||||
} else {
|
||||
Ok(BaseStructure {
|
||||
vol: Dyna::filled(Vec3::zero(), StructureBlock::None, ()),
|
||||
empty: StructureBlock::None,
|
||||
default_kind: BlockKind::Misc,
|
||||
})
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ impl<V: RectRasterableVol> VolGrid2d<V> {
|
||||
#[inline(always)]
|
||||
pub fn chunk_key<P: Into<Vec2<i32>>>(pos: P) -> Vec2<i32> {
|
||||
pos.into()
|
||||
.map2(V::RECT_SIZE, |e, sz: u32| e >> (sz - 1).count_ones())
|
||||
.map2(V::RECT_SIZE, |e, sz: u32| e.div_euclid(sz as i32))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -2,7 +2,7 @@ pub use ::vek::{
|
||||
bezier::repr_simd::*, geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*,
|
||||
quaternion::repr_simd::*, transform::repr_simd::*, transition::*, vec::repr_simd::*,
|
||||
};
|
||||
/* pub use ::vek::{
|
||||
/*pub use ::vek::{
|
||||
bezier::repr_c::*, geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*,
|
||||
quaternion::repr_c::*, transform::repr_c::*, transition::*, vec::repr_c::*,
|
||||
}; */
|
||||
};*/
|
||||
|
@ -364,6 +364,9 @@ image_ids! {
|
||||
mmap_site_cave_bg: "voxygen.element.map.cave_bg",
|
||||
mmap_site_cave_hover: "voxygen.element.map.cave_hover",
|
||||
mmap_site_cave: "voxygen.element.map.cave",
|
||||
mmap_site_excl: "voxygen.element.map.excl",
|
||||
mmap_site_tree: "voxygen.element.map.tree",
|
||||
mmap_site_tree_hover: "voxygen.element.map.tree_hover",
|
||||
|
||||
// Window Parts
|
||||
window_3: "voxygen.element.frames.window_3",
|
||||
|
@ -49,6 +49,12 @@ widget_ids! {
|
||||
show_dungeons_img,
|
||||
show_dungeons_box,
|
||||
show_dungeons_text,
|
||||
show_caves_img,
|
||||
show_caves_box,
|
||||
show_caves_text,
|
||||
show_trees_img,
|
||||
show_trees_box,
|
||||
show_trees_text,
|
||||
show_difficulty_img,
|
||||
show_difficulty_box,
|
||||
show_difficulty_text,
|
||||
@ -57,9 +63,6 @@ widget_ids! {
|
||||
drag_ico,
|
||||
zoom_txt,
|
||||
zoom_ico,
|
||||
show_caves_img,
|
||||
show_caves_box,
|
||||
show_caves_text,
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,6 +120,7 @@ pub enum Event {
|
||||
ShowCastles(bool),
|
||||
ShowDungeons(bool),
|
||||
ShowCaves(bool),
|
||||
ShowTrees(bool),
|
||||
Close,
|
||||
}
|
||||
|
||||
@ -143,6 +147,7 @@ impl<'a> Widget for Map<'a> {
|
||||
let show_dungeons = self.global_state.settings.gameplay.map_show_dungeons;
|
||||
let show_castles = self.global_state.settings.gameplay.map_show_castles;
|
||||
let show_caves = self.global_state.settings.gameplay.map_show_caves;
|
||||
let show_trees = self.global_state.settings.gameplay.map_show_trees;
|
||||
let mut events = Vec::new();
|
||||
let i18n = &self.localized_strings;
|
||||
// Tooltips
|
||||
@ -471,6 +476,40 @@ impl<'a> Widget for Map<'a> {
|
||||
.graphics_for(state.ids.show_caves_box)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_caves_text, ui);
|
||||
// Trees
|
||||
Image::new(self.imgs.mmap_site_tree)
|
||||
.down_from(state.ids.show_caves_img, 10.0)
|
||||
.w_h(20.0, 20.0)
|
||||
.set(state.ids.show_trees_img, ui);
|
||||
if Button::image(if show_trees {
|
||||
self.imgs.checkbox_checked
|
||||
} else {
|
||||
self.imgs.checkbox
|
||||
})
|
||||
.w_h(18.0, 18.0)
|
||||
.hover_image(if show_trees {
|
||||
self.imgs.checkbox_checked_mo
|
||||
} else {
|
||||
self.imgs.checkbox_mo
|
||||
})
|
||||
.press_image(if show_trees {
|
||||
self.imgs.checkbox_checked
|
||||
} else {
|
||||
self.imgs.checkbox_press
|
||||
})
|
||||
.right_from(state.ids.show_trees_img, 10.0)
|
||||
.set(state.ids.show_trees_box, ui)
|
||||
.was_clicked()
|
||||
{
|
||||
events.push(Event::ShowTrees(!show_trees));
|
||||
}
|
||||
Text::new(i18n.get("hud.map.trees"))
|
||||
.right_from(state.ids.show_trees_box, 10.0)
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.graphics_for(state.ids.show_trees_box)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.show_trees_text, ui);
|
||||
// Map icons
|
||||
if state.ids.mmap_site_icons.len() < self.client.sites().len() {
|
||||
state.update(|state| {
|
||||
@ -512,6 +551,7 @@ impl<'a> Widget for Map<'a> {
|
||||
SiteKind::Dungeon { .. } => i18n.get("hud.map.dungeon"),
|
||||
SiteKind::Castle => i18n.get("hud.map.castle"),
|
||||
SiteKind::Cave => i18n.get("hud.map.cave"),
|
||||
SiteKind::Tree => i18n.get("hud.map.tree"),
|
||||
});
|
||||
let (difficulty, desc) = match &site.kind {
|
||||
SiteKind::Town => (0, i18n.get("hud.map.town").to_string()),
|
||||
@ -522,12 +562,14 @@ impl<'a> Widget for Map<'a> {
|
||||
),
|
||||
SiteKind::Castle => (0, i18n.get("hud.map.castle").to_string()),
|
||||
SiteKind::Cave => (0, i18n.get("hud.map.cave").to_string()),
|
||||
SiteKind::Tree => (0, i18n.get("hud.map.tree").to_string()),
|
||||
};
|
||||
let site_btn = Button::image(match &site.kind {
|
||||
SiteKind::Town => self.imgs.mmap_site_town,
|
||||
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon,
|
||||
SiteKind::Castle => self.imgs.mmap_site_castle,
|
||||
SiteKind::Cave => self.imgs.mmap_site_cave,
|
||||
SiteKind::Tree => self.imgs.mmap_site_tree,
|
||||
})
|
||||
.x_y_position_relative_to(
|
||||
state.ids.grid,
|
||||
@ -540,6 +582,7 @@ impl<'a> Widget for Map<'a> {
|
||||
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon_hover,
|
||||
SiteKind::Castle => self.imgs.mmap_site_castle_hover,
|
||||
SiteKind::Cave => self.imgs.mmap_site_cave_hover,
|
||||
SiteKind::Tree => self.imgs.mmap_site_tree_hover,
|
||||
})
|
||||
.image_color(UI_HIGHLIGHT_0)
|
||||
.with_tooltip(
|
||||
@ -560,6 +603,7 @@ impl<'a> Widget for Map<'a> {
|
||||
_ => TEXT_COLOR,
|
||||
},
|
||||
SiteKind::Cave => TEXT_COLOR,
|
||||
SiteKind::Tree => TEXT_COLOR,
|
||||
},
|
||||
);
|
||||
// Only display sites that are toggled on
|
||||
@ -584,6 +628,11 @@ impl<'a> Widget for Map<'a> {
|
||||
site_btn.set(state.ids.mmap_site_icons[i], ui);
|
||||
}
|
||||
},
|
||||
SiteKind::Tree => {
|
||||
if show_trees {
|
||||
site_btn.set(state.ids.mmap_site_icons[i], ui);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Difficulty from 0-6
|
||||
@ -640,6 +689,11 @@ impl<'a> Widget for Map<'a> {
|
||||
dif_img.set(state.ids.site_difs[i], ui)
|
||||
}
|
||||
},
|
||||
SiteKind::Tree => {
|
||||
if show_trees {
|
||||
dif_img.set(state.ids.site_difs[i], ui)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -303,6 +303,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon_bg,
|
||||
SiteKind::Castle => self.imgs.mmap_site_castle_bg,
|
||||
SiteKind::Cave => self.imgs.mmap_site_cave_bg,
|
||||
SiteKind::Tree => self.imgs.mmap_site_tree,
|
||||
})
|
||||
.x_y_position_relative_to(
|
||||
state.ids.grid,
|
||||
@ -323,6 +324,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
_ => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
||||
},
|
||||
SiteKind::Cave => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
||||
SiteKind::Tree => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
||||
}))
|
||||
.parent(state.ids.grid)
|
||||
.set(state.ids.mmap_site_icons_bgs[i], ui);
|
||||
@ -331,6 +333,7 @@ impl<'a> Widget for MiniMap<'a> {
|
||||
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon,
|
||||
SiteKind::Castle => self.imgs.mmap_site_castle,
|
||||
SiteKind::Cave => self.imgs.mmap_site_cave,
|
||||
SiteKind::Tree => self.imgs.mmap_site_tree,
|
||||
})
|
||||
.middle_of(state.ids.mmap_site_icons_bgs[i])
|
||||
.w_h(20.0, 20.0)
|
||||
|
@ -362,6 +362,7 @@ pub enum Event {
|
||||
MapShowDungeons(bool),
|
||||
MapShowCastles(bool),
|
||||
MapShowCaves(bool),
|
||||
MapShowTrees(bool),
|
||||
AdjustWindowSize([u16; 2]),
|
||||
ChangeFullscreenMode(FullScreenSettings),
|
||||
ToggleParticlesEnabled(bool),
|
||||
@ -2642,6 +2643,9 @@ impl Hud {
|
||||
map::Event::ShowCaves(map_show_caves) => {
|
||||
events.push(Event::MapShowCaves(map_show_caves));
|
||||
},
|
||||
map::Event::ShowTrees(map_show_trees) => {
|
||||
events.push(Event::MapShowTrees(map_show_trees));
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -30,8 +30,8 @@ enum FaceKind {
|
||||
Fluid,
|
||||
}
|
||||
|
||||
const SUNLIGHT: u8 = 24;
|
||||
const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
|
||||
pub const SUNLIGHT: u8 = 24;
|
||||
pub const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
|
||||
|
||||
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
|
||||
is_sunlight: bool,
|
||||
@ -69,25 +69,24 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
|
||||
if is_sunlight {
|
||||
for x in 0..outer.size().w {
|
||||
for y in 0..outer.size().h {
|
||||
let z = outer.size().d - 1;
|
||||
let is_air = vol_cached
|
||||
.get(outer.min + Vec3::new(x, y, z))
|
||||
.ok()
|
||||
.map_or(false, |b| b.is_air());
|
||||
let mut light = SUNLIGHT;
|
||||
for z in (0..outer.size().d).rev() {
|
||||
let (min_light, attenuation) = vol_cached
|
||||
.get(outer.min + Vec3::new(x, y, z))
|
||||
.map_or((0, 0), |b| b.get_max_sunlight());
|
||||
|
||||
light_map[lm_idx(x, y, z)] = if is_air {
|
||||
if vol_cached
|
||||
.get(outer.min + Vec3::new(x, y, z - 1))
|
||||
.ok()
|
||||
.map_or(false, |b| b.is_air())
|
||||
{
|
||||
light_map[lm_idx(x, y, z - 1)] = SUNLIGHT;
|
||||
if light > min_light {
|
||||
light = light.saturating_sub(attenuation).max(min_light);
|
||||
}
|
||||
|
||||
light_map[lm_idx(x, y, z)] = light;
|
||||
|
||||
if light == 0 {
|
||||
break;
|
||||
} else {
|
||||
prop_que.push_back((x as u8, y as u8, z as u16));
|
||||
}
|
||||
SUNLIGHT
|
||||
} else {
|
||||
OPAQUE
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,46 +127,26 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
|
||||
let pos = Vec3::new(pos.0 as i32, pos.1 as i32, pos.2 as i32);
|
||||
let light = light_map[lm_idx(pos.x, pos.y, pos.z)];
|
||||
|
||||
// If ray propagate downwards at full strength
|
||||
if is_sunlight && light == SUNLIGHT {
|
||||
// Down is special cased and we know up is a ray
|
||||
// Special cased ray propagation
|
||||
let pos = Vec3::new(pos.x, pos.y, pos.z - 1);
|
||||
let (is_air, is_liquid) = vol_cached
|
||||
.get(outer.min + pos)
|
||||
.ok()
|
||||
.map_or((false, false), |b| (b.is_air(), b.is_liquid()));
|
||||
light_map[lm_idx(pos.x, pos.y, pos.z)] = if is_air {
|
||||
prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
|
||||
SUNLIGHT
|
||||
} else if is_liquid {
|
||||
prop_que.push_back((pos.x as u8, pos.y as u8, pos.z as u16));
|
||||
SUNLIGHT - 1
|
||||
} else {
|
||||
OPAQUE
|
||||
}
|
||||
} else {
|
||||
// Up
|
||||
// Bounds checking
|
||||
if pos.z + 1 < outer.size().d {
|
||||
propagate(
|
||||
light,
|
||||
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z + 1)).unwrap(),
|
||||
Vec3::new(pos.x, pos.y, pos.z + 1),
|
||||
&mut prop_que,
|
||||
&mut vol_cached,
|
||||
)
|
||||
}
|
||||
// Down
|
||||
if pos.z > 0 {
|
||||
propagate(
|
||||
light,
|
||||
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z - 1)).unwrap(),
|
||||
Vec3::new(pos.x, pos.y, pos.z - 1),
|
||||
&mut prop_que,
|
||||
&mut vol_cached,
|
||||
)
|
||||
}
|
||||
// Up
|
||||
// Bounds checking
|
||||
if pos.z + 1 < outer.size().d {
|
||||
propagate(
|
||||
light,
|
||||
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z + 1)).unwrap(),
|
||||
Vec3::new(pos.x, pos.y, pos.z + 1),
|
||||
&mut prop_que,
|
||||
&mut vol_cached,
|
||||
)
|
||||
}
|
||||
// Down
|
||||
if pos.z > 0 {
|
||||
propagate(
|
||||
light,
|
||||
light_map.get_mut(lm_idx(pos.x, pos.y, pos.z - 1)).unwrap(),
|
||||
Vec3::new(pos.x, pos.y, pos.z - 1),
|
||||
&mut prop_que,
|
||||
&mut vol_cached,
|
||||
)
|
||||
}
|
||||
// The XY directions
|
||||
if pos.y + 1 < outer.size().h {
|
||||
@ -401,7 +380,13 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static>
|
||||
let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z);
|
||||
let draw_delta = Vec3::new(1, 1, z_start);
|
||||
|
||||
let get_light = |_: &mut (), pos: Vec3<i32>| light(pos + range.min);
|
||||
let get_light = |_: &mut (), pos: Vec3<i32>| {
|
||||
if flat_get(pos).is_opaque() {
|
||||
0.0
|
||||
} else {
|
||||
light(pos + range.min)
|
||||
}
|
||||
};
|
||||
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
|
||||
let get_color =
|
||||
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or(Rgb::zero());
|
||||
|
@ -14,6 +14,7 @@ gfx_defines! {
|
||||
model_mat: [[f32; 4]; 4] = "model_mat",
|
||||
highlight_col: [f32; 4] = "highlight_col",
|
||||
model_light: [f32; 4] = "model_light",
|
||||
model_glow: [f32; 4] = "model_glow",
|
||||
atlas_offs: [i32; 4] = "atlas_offs",
|
||||
model_pos: [f32; 3] = "model_pos",
|
||||
flags: u32 = "flags",
|
||||
@ -60,7 +61,7 @@ impl Locals {
|
||||
atlas_offs: Vec2<i32>,
|
||||
is_player: bool,
|
||||
light: f32,
|
||||
glow: f32,
|
||||
glow: (Vec3<f32>, f32),
|
||||
) -> Self {
|
||||
let mut flags = 0;
|
||||
flags |= is_player as u32;
|
||||
@ -70,7 +71,8 @@ impl Locals {
|
||||
highlight_col: [col.r, col.g, col.b, 1.0],
|
||||
model_pos: pos.into_array(),
|
||||
atlas_offs: Vec4::from(atlas_offs).into_array(),
|
||||
model_light: [light, glow, 1.0, 1.0],
|
||||
model_light: [light, 1.0, 1.0, 1.0],
|
||||
model_glow: [glow.0.x, glow.0.y, glow.0.z, glow.1],
|
||||
flags,
|
||||
}
|
||||
}
|
||||
@ -85,7 +87,7 @@ impl Default for Locals {
|
||||
Vec2::default(),
|
||||
false,
|
||||
1.0,
|
||||
0.0,
|
||||
(Vec3::zero(), 0.0),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -4602,7 +4602,7 @@ pub struct FigureStateMeta {
|
||||
last_pos: Option<anim::vek::Vec3<f32>>,
|
||||
avg_vel: anim::vek::Vec3<f32>,
|
||||
last_light: f32,
|
||||
last_glow: f32,
|
||||
last_glow: (Vec3<f32>, f32),
|
||||
acc_vel: f32,
|
||||
}
|
||||
|
||||
@ -4649,7 +4649,7 @@ impl<S: Skeleton> FigureState<S> {
|
||||
last_pos: None,
|
||||
avg_vel: anim::vek::Vec3::zero(),
|
||||
last_light: 1.0,
|
||||
last_glow: 0.0,
|
||||
last_glow: (Vec3::zero(), 0.0),
|
||||
acc_vel: 0.0,
|
||||
},
|
||||
skeleton,
|
||||
@ -4743,14 +4743,15 @@ impl<S: Skeleton> FigureState<S> {
|
||||
let s = Lerp::lerp(s_10, s_11, (wpos.x.fract() - 0.5).abs() * 2.0);
|
||||
*/
|
||||
|
||||
Vec2::new(t.light_at_wpos(wposi), t.glow_at_wpos(wposi)).into_tuple()
|
||||
(t.light_at_wpos(wposi), t.glow_normal_at_wpos(wpos))
|
||||
})
|
||||
.unwrap_or((1.0, 0.0));
|
||||
.unwrap_or((1.0, (Vec3::zero(), 0.0)));
|
||||
// Fade between light and glow levels
|
||||
// TODO: Making this temporal rather than spatial is a bit dumb but it's a very
|
||||
// subtle difference
|
||||
self.last_light = vek::Lerp::lerp(self.last_light, light, 16.0 * dt);
|
||||
self.last_glow = vek::Lerp::lerp(self.last_glow, glow, 16.0 * dt);
|
||||
self.last_glow.0 = vek::Lerp::lerp(self.last_glow.0, glow.0, 16.0 * dt);
|
||||
self.last_glow.1 = vek::Lerp::lerp(self.last_glow.1, glow.1, 16.0 * dt);
|
||||
|
||||
let locals = FigureLocals::new(
|
||||
mat,
|
||||
|
@ -2,7 +2,7 @@ use core::{iter, mem};
|
||||
use hashbrown::HashMap;
|
||||
use num::traits::Float;
|
||||
pub use vek::{geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*, vec::repr_simd::*};
|
||||
// pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*,
|
||||
//pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*,
|
||||
// vec::repr_c::*};
|
||||
|
||||
pub fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] {
|
||||
|
@ -571,7 +571,7 @@ impl ParticleMgr {
|
||||
cond: |_| true,
|
||||
},
|
||||
BlockParticles {
|
||||
blocks: |boi| &boi.reeds,
|
||||
blocks: |boi| &boi.fireflies,
|
||||
range: 6,
|
||||
rate: 0.004,
|
||||
lifetime: 40.0,
|
||||
|
@ -3,7 +3,7 @@ mod watcher;
|
||||
pub use self::watcher::BlocksOfInterest;
|
||||
|
||||
use crate::{
|
||||
mesh::{greedy::GreedyMesh, Meshable},
|
||||
mesh::{greedy::GreedyMesh, terrain::SUNLIGHT, Meshable},
|
||||
render::{
|
||||
ColLightFmt, ColLightInfo, Consts, FluidPipeline, GlobalModel, Instances, Mesh, Model,
|
||||
RenderError, Renderer, ShadowPipeline, SpriteInstance, SpriteLocals, SpritePipeline,
|
||||
@ -508,6 +508,52 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn glow_normal_at_wpos(&self, wpos: Vec3<f32>) -> (Vec3<f32>, f32) {
|
||||
let wpos_chunk = wpos.xy().map2(TerrainChunk::RECT_SIZE, |e: f32, sz| {
|
||||
(e as i32).div_euclid(sz as i32)
|
||||
});
|
||||
|
||||
const AMBIANCE: f32 = 0.15; // 0-1, the proportion of light that should illuminate the rear of an object
|
||||
|
||||
let (bias, total) = Spiral2d::new()
|
||||
.take(9)
|
||||
.map(|rpos| {
|
||||
let chunk_pos = wpos_chunk + rpos;
|
||||
self.chunks
|
||||
.get(&chunk_pos)
|
||||
.map(|c| c.blocks_of_interest.lights.iter())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(move |(lpos, level)| {
|
||||
(
|
||||
Vec3::<i32>::from(
|
||||
chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32),
|
||||
) + *lpos,
|
||||
level,
|
||||
)
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.fold(
|
||||
(Vec3::broadcast(0.001), 0.0),
|
||||
|(bias, total), (lpos, level)| {
|
||||
let rpos = lpos.map(|e| e as f32 + 0.5) - wpos;
|
||||
let level = (*level as f32 - rpos.magnitude()).max(0.0) / SUNLIGHT as f32;
|
||||
(
|
||||
bias + rpos.try_normalized().unwrap_or_else(Vec3::zero) * level,
|
||||
total + level,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let bias_factor = bias.magnitude() * (1.0 - AMBIANCE) / total.max(0.001);
|
||||
|
||||
(
|
||||
bias.try_normalized().unwrap_or_else(Vec3::zero) * bias_factor.powf(0.5),
|
||||
self.glow_at_wpos(wpos.map(|e| e.floor() as i32)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Maintain terrain data. To be called once per tick.
|
||||
#[allow(clippy::for_loops_over_fallibles)] // TODO: Pending review in #587
|
||||
#[allow(clippy::len_zero)] // TODO: Pending review in #587
|
||||
@ -596,13 +642,23 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
// be meshed
|
||||
span!(guard, "Add chunks with modified blocks to mesh todo list");
|
||||
// TODO: would be useful if modified blocks were grouped by chunk
|
||||
for pos in scene_data
|
||||
.state
|
||||
.terrain_changes()
|
||||
.modified_blocks
|
||||
.iter()
|
||||
.map(|(p, _)| *p)
|
||||
{
|
||||
for (&pos, &_block) in scene_data.state.terrain_changes().modified_blocks.iter() {
|
||||
// TODO: Be cleverer about this to avoid remeshing all neighbours. There are a
|
||||
// few things that can create an 'effect at a distance'. These are
|
||||
// as follows:
|
||||
// - A glowing block is added or removed, thereby causing a lighting
|
||||
// recalculation proportional to its glow radius.
|
||||
// - An opaque block that was blocking sunlight from entering a cavity is
|
||||
// removed (or added) thereby
|
||||
// changing the way that sunlight propagates into the cavity.
|
||||
//
|
||||
// We can and should be cleverer about this, but it's non-trivial. For now, just
|
||||
// conservatively assume that the lighting in all neighbouring
|
||||
// chunks is invalidated. Thankfully, this doesn't need to happen often
|
||||
// because block modification is unusual in Veloren.
|
||||
// let block_effect_radius = block.get_glow().unwrap_or(0).max(1);
|
||||
let block_effect_radius = crate::mesh::terrain::MAX_LIGHT_DIST;
|
||||
|
||||
// Handle block changes on chunk borders
|
||||
// Remesh all neighbours because we have complex lighting now
|
||||
// TODO: if lighting is on the server this can be updated to only remesh when
|
||||
@ -610,7 +666,7 @@ impl<V: RectRasterableVol> Terrain<V> {
|
||||
// change was on the border
|
||||
for x in -1..2 {
|
||||
for y in -1..2 {
|
||||
let neighbour_pos = pos + Vec3::new(x, y, 0);
|
||||
let neighbour_pos = pos + Vec3::new(x, y, 0) * block_effect_radius;
|
||||
let neighbour_chunk_pos = scene_data.state.terrain().pos_key(neighbour_pos);
|
||||
|
||||
// Only remesh if this chunk has all its neighbors
|
||||
|
@ -15,6 +15,7 @@ pub struct BlocksOfInterest {
|
||||
pub smokers: Vec<Vec3<i32>>,
|
||||
pub beehives: Vec<Vec3<i32>>,
|
||||
pub reeds: Vec<Vec3<i32>>,
|
||||
pub fireflies: Vec<Vec3<i32>>,
|
||||
pub flowers: Vec<Vec3<i32>>,
|
||||
pub fire_bowls: Vec<Vec3<i32>>,
|
||||
pub snow: Vec<Vec3<i32>>,
|
||||
@ -39,6 +40,7 @@ impl BlocksOfInterest {
|
||||
let mut smokers = Vec::new();
|
||||
let mut beehives = Vec::new();
|
||||
let mut reeds = Vec::new();
|
||||
let mut fireflies = Vec::new();
|
||||
let mut flowers = Vec::new();
|
||||
let mut interactables = Vec::new();
|
||||
let mut lights = Vec::new();
|
||||
@ -94,10 +96,12 @@ impl BlocksOfInterest {
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
Some(SpriteKind::Reed) => {
|
||||
reeds.push(pos);
|
||||
fireflies.push(pos);
|
||||
if thread_rng().gen_range(0..12) == 0 {
|
||||
frogs.push(pos)
|
||||
frogs.push(pos);
|
||||
}
|
||||
},
|
||||
Some(SpriteKind::CaveMushroom) => fireflies.push(pos),
|
||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||
@ -123,6 +127,7 @@ impl BlocksOfInterest {
|
||||
smokers,
|
||||
beehives,
|
||||
reeds,
|
||||
fireflies,
|
||||
flowers,
|
||||
interactables,
|
||||
lights,
|
||||
|
@ -1266,6 +1266,10 @@ impl PlayState for SessionState {
|
||||
global_state.settings.gameplay.map_show_caves = map_show_caves;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::MapShowTrees(map_show_trees) => {
|
||||
global_state.settings.gameplay.map_show_trees = map_show_trees;
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
HudEvent::ChangeGamma(new_gamma) => {
|
||||
global_state.settings.graphics.gamma = new_gamma;
|
||||
global_state.settings.save_to_file_warn();
|
||||
|
@ -463,6 +463,7 @@ pub struct GameplaySettings {
|
||||
pub map_show_castles: bool,
|
||||
pub loading_tips: bool,
|
||||
pub map_show_caves: bool,
|
||||
pub map_show_trees: bool,
|
||||
pub minimap_show: bool,
|
||||
pub minimap_face_north: bool,
|
||||
}
|
||||
@ -502,6 +503,7 @@ impl Default for GameplaySettings {
|
||||
map_show_castles: true,
|
||||
loading_tips: true,
|
||||
map_show_caves: true,
|
||||
map_show_trees: true,
|
||||
minimap_show: true,
|
||||
minimap_face_north: false,
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ common = { package = "veloren-common", path = "../common" }
|
||||
common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||
bincode = "1.3.1"
|
||||
bitvec = "0.21.0"
|
||||
enum-iterator = "0.6"
|
||||
fxhash = "0.2.1"
|
||||
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
||||
itertools = "0.10"
|
||||
@ -36,3 +37,9 @@ ron = { version = "0.6", default-features = false }
|
||||
criterion = "0.3"
|
||||
tracing-subscriber = { version = "0.2.3", default-features = false, features = ["fmt", "chrono", "ansi", "smallvec"] }
|
||||
minifb = "0.19.1"
|
||||
svg_fmt = "0.4"
|
||||
structopt = "0.3"
|
||||
|
||||
[[bench]]
|
||||
harness = false
|
||||
name = "tree"
|
||||
|
41
world/benches/tree.rs
Normal file
41
world/benches/tree.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use rand::prelude::*;
|
||||
use veloren_world::layer::tree::{ProceduralTree, TreeConfig};
|
||||
|
||||
fn tree(c: &mut Criterion) {
|
||||
c.bench_function("generate", |b| {
|
||||
let mut i = 0;
|
||||
b.iter(|| {
|
||||
i += 1;
|
||||
black_box(ProceduralTree::generate(
|
||||
TreeConfig::oak(&mut thread_rng(), 1.0),
|
||||
&mut thread_rng(),
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("sample", |b| {
|
||||
let mut i = 0;
|
||||
b.iter_batched(
|
||||
|| {
|
||||
i += 1;
|
||||
ProceduralTree::generate(TreeConfig::oak(&mut thread_rng(), 1.0), &mut thread_rng())
|
||||
},
|
||||
|tree| {
|
||||
let bounds = tree.get_bounds();
|
||||
for x in (bounds.min.x as i32..bounds.max.x as i32).step_by(3) {
|
||||
for y in (bounds.min.y as i32..bounds.max.y as i32).step_by(3) {
|
||||
for z in (bounds.min.z as i32..bounds.max.z as i32).step_by(3) {
|
||||
let pos = (x as f32, y as f32, z as f32).into();
|
||||
black_box(tree.is_branch_or_leaves_at(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, tree);
|
||||
criterion_main!(benches);
|
34
world/examples/site.rs
Normal file
34
world/examples/site.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use svg_fmt::*;
|
||||
use veloren_world::site2::test_site;
|
||||
|
||||
fn main() {
|
||||
let site = test_site();
|
||||
let size = site.bounds().size();
|
||||
println!("{}", BeginSvg {
|
||||
w: size.w as f32,
|
||||
h: size.h as f32
|
||||
});
|
||||
|
||||
for plot in site.plots() {
|
||||
let bounds = plot.find_bounds();
|
||||
println!("{}", Rectangle {
|
||||
x: bounds.min.x as f32,
|
||||
y: bounds.min.y as f32,
|
||||
w: bounds.size().w as f32,
|
||||
h: bounds.size().h as f32,
|
||||
style: Style {
|
||||
fill: Fill::Color(Color {
|
||||
r: 50,
|
||||
g: 50,
|
||||
b: 50
|
||||
}),
|
||||
stroke: Stroke::Color(Color { r: 0, g: 0, b: 0 }, 1.0),
|
||||
opacity: 1.0,
|
||||
stroke_opacity: 1.0,
|
||||
},
|
||||
border_radius: 0.0,
|
||||
});
|
||||
}
|
||||
|
||||
println!("{}", EndSvg);
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
use crate::util::math::close;
|
||||
use enum_iterator::IntoEnumIterator;
|
||||
use std::ops::Range;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, IntoEnumIterator)]
|
||||
pub enum ForestKind {
|
||||
Palm,
|
||||
Acacia,
|
||||
@ -7,5 +12,82 @@ pub enum ForestKind {
|
||||
Pine,
|
||||
Birch,
|
||||
Mangrove,
|
||||
Giant,
|
||||
Swamp,
|
||||
}
|
||||
|
||||
pub struct Environment {
|
||||
pub humid: f32,
|
||||
pub temp: f32,
|
||||
pub near_water: f32,
|
||||
}
|
||||
|
||||
impl ForestKind {
|
||||
pub fn humid_range(&self) -> Range<f32> {
|
||||
match self {
|
||||
ForestKind::Palm => 0.25..1.4,
|
||||
ForestKind::Acacia => 0.05..0.55,
|
||||
ForestKind::Baobab => 0.2..0.6,
|
||||
ForestKind::Oak => 0.35..1.5,
|
||||
ForestKind::Pine => 0.2..1.4,
|
||||
ForestKind::Birch => 0.0..0.6,
|
||||
ForestKind::Mangrove => 0.65..1.3,
|
||||
ForestKind::Swamp => 0.5..1.1,
|
||||
_ => 0.0..0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn temp_range(&self) -> Range<f32> {
|
||||
match self {
|
||||
ForestKind::Palm => 0.4..1.6,
|
||||
ForestKind::Acacia => 0.3..1.6,
|
||||
ForestKind::Baobab => 0.4..0.9,
|
||||
ForestKind::Oak => -0.35..0.6,
|
||||
ForestKind::Pine => -1.8..-0.2,
|
||||
ForestKind::Birch => -0.7..0.25,
|
||||
ForestKind::Mangrove => 0.4..1.6,
|
||||
ForestKind::Swamp => -0.6..0.8,
|
||||
_ => 0.0..0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn near_water_range(&self) -> Option<Range<f32>> {
|
||||
match self {
|
||||
ForestKind::Palm => Some(0.35..1.8),
|
||||
ForestKind::Swamp => Some(0.5..1.8),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The relative rate at which this tree appears under ideal conditions
|
||||
pub fn ideal_proclivity(&self) -> f32 {
|
||||
match self {
|
||||
ForestKind::Palm => 0.4,
|
||||
ForestKind::Acacia => 0.6,
|
||||
ForestKind::Baobab => 0.2,
|
||||
ForestKind::Oak => 1.0,
|
||||
ForestKind::Pine => 1.0,
|
||||
ForestKind::Birch => 0.65,
|
||||
ForestKind::Mangrove => 1.0,
|
||||
ForestKind::Swamp => 1.0,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proclivity(&self, env: &Environment) -> f32 {
|
||||
self.ideal_proclivity()
|
||||
* close(env.humid, self.humid_range())
|
||||
* close(env.temp, self.temp_range())
|
||||
* self.near_water_range().map_or(1.0, |near_water_range| {
|
||||
close(env.near_water, near_water_range)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeAttr {
|
||||
pub pos: Vec2<i32>,
|
||||
pub seed: u32,
|
||||
pub scale: f32,
|
||||
pub forest_kind: ForestKind,
|
||||
pub inhabited: bool,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
column::{ColumnGen, ColumnSample},
|
||||
util::{RandomField, Sampler, SmallCache},
|
||||
util::{FastNoise, RandomField, Sampler, SmallCache},
|
||||
IndexRef,
|
||||
};
|
||||
use common::terrain::{
|
||||
@ -70,6 +70,8 @@ impl<'a> BlockGen<'a> {
|
||||
// humidity,
|
||||
stone_col,
|
||||
snow_cover,
|
||||
cliff_offset,
|
||||
cliff_height,
|
||||
..
|
||||
} = sample;
|
||||
|
||||
@ -118,7 +120,28 @@ impl<'a> BlockGen<'a> {
|
||||
.map(|e| (e * 255.0) as u8);
|
||||
|
||||
if stone_factor >= 0.5 {
|
||||
Some(Block::new(BlockKind::Rock, col))
|
||||
if wposf.z as f32 > height - cliff_offset.max(0.0) {
|
||||
if cliff_offset.max(0.0)
|
||||
> cliff_height
|
||||
- (FastNoise::new(37).get(wposf / Vec3::new(6.0, 6.0, 10.0)) * 0.5
|
||||
+ 0.5)
|
||||
* (height - grass_depth - wposf.z as f32)
|
||||
.mul(0.25)
|
||||
.clamped(0.0, 8.0)
|
||||
{
|
||||
Some(Block::empty())
|
||||
} else {
|
||||
let col = Lerp::lerp(
|
||||
col.map(|e| e as f32),
|
||||
col.map(|e| e as f32) * 0.7,
|
||||
(wposf.z as f32 - basement * 0.3).div(2.0).sin() * 0.5 + 0.5,
|
||||
)
|
||||
.map(|e| e as u8);
|
||||
Some(Block::new(BlockKind::Rock, col))
|
||||
}
|
||||
} else {
|
||||
Some(Block::new(BlockKind::Rock, col))
|
||||
}
|
||||
} else {
|
||||
Some(Block::new(BlockKind::Earth, col))
|
||||
}
|
||||
@ -183,7 +206,9 @@ pub struct ZCache<'a> {
|
||||
|
||||
impl<'a> ZCache<'a> {
|
||||
pub fn get_z_limits(&self) -> (f32, f32) {
|
||||
let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0);
|
||||
let min = self.sample.alt
|
||||
- (self.sample.chaos.min(1.0) * 16.0)
|
||||
- self.sample.cliff_offset.max(0.0);
|
||||
let min = min - 4.0;
|
||||
|
||||
let rocks = if self.sample.rock > 0.0 { 12.0 } else { 0.0 };
|
||||
@ -220,6 +245,7 @@ pub fn block_from_structure(
|
||||
sample.surface_color.map(|e| (e * 255.0) as u8),
|
||||
)),
|
||||
StructureBlock::Normal(color) => Some(Block::new(BlockKind::Misc, color)),
|
||||
StructureBlock::Block(kind, color) => Some(Block::new(kind, color)),
|
||||
StructureBlock::Water => Some(Block::water(SpriteKind::Empty)),
|
||||
// TODO: If/when liquid supports other colors again, revisit this.
|
||||
StructureBlock::GreenSludge => Some(Block::water(SpriteKind::Empty)),
|
||||
@ -249,6 +275,7 @@ pub fn block_from_structure(
|
||||
Some(with_sprite(SpriteKind::Chest))
|
||||
}
|
||||
},
|
||||
StructureBlock::Log => Some(Block::new(BlockKind::Wood, Rgb::new(60, 30, 0))),
|
||||
// We interpolate all these BlockKinds as needed.
|
||||
StructureBlock::TemperateLeaves
|
||||
| StructureBlock::PineLeaves
|
||||
|
@ -2,7 +2,8 @@ use crate::{
|
||||
block::ZCache,
|
||||
column::ColumnSample,
|
||||
index::IndexRef,
|
||||
sim::{SimChunk, WorldSim as Land},
|
||||
land::Land,
|
||||
sim::{SimChunk, WorldSim},
|
||||
util::Grid,
|
||||
};
|
||||
use common::{
|
||||
@ -17,7 +18,7 @@ pub struct CanvasInfo<'a> {
|
||||
pub(crate) wpos: Vec2<i32>,
|
||||
pub(crate) column_grid: &'a Grid<Option<ZCache<'a>>>,
|
||||
pub(crate) column_grid_border: i32,
|
||||
pub(crate) land: &'a Land,
|
||||
pub(crate) chunks: &'a WorldSim,
|
||||
pub(crate) index: IndexRef<'a>,
|
||||
pub(crate) chunk: &'a SimChunk,
|
||||
}
|
||||
@ -45,7 +46,9 @@ impl<'a> CanvasInfo<'a> {
|
||||
|
||||
pub fn chunk(&self) -> &'a SimChunk { self.chunk }
|
||||
|
||||
pub fn land(&self) -> &'a Land { self.land }
|
||||
pub fn chunks(&self) -> &'a WorldSim { self.chunks }
|
||||
|
||||
pub fn land(&self) -> Land<'_> { Land::from_sim(self.chunks) }
|
||||
}
|
||||
|
||||
pub struct Canvas<'a> {
|
||||
@ -60,8 +63,12 @@ impl<'a> Canvas<'a> {
|
||||
/// inner `CanvasInfo` such that it may be used independently.
|
||||
pub fn info(&mut self) -> CanvasInfo<'a> { self.info }
|
||||
|
||||
pub fn get(&mut self, pos: Vec3<i32>) -> Option<Block> {
|
||||
self.chunk.get(pos - self.wpos()).ok().copied()
|
||||
pub fn get(&mut self, pos: Vec3<i32>) -> Block {
|
||||
self.chunk
|
||||
.get(pos - self.wpos())
|
||||
.ok()
|
||||
.copied()
|
||||
.unwrap_or_else(Block::empty)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, pos: Vec3<i32>, block: Block) {
|
||||
|
@ -6,9 +6,10 @@ use self::{Occupation::*, Stock::*};
|
||||
use crate::{
|
||||
config::CONFIG,
|
||||
sim::WorldSim,
|
||||
site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite},
|
||||
site::{namegen::NameGen, Castle, Dungeon, Settlement, Site as WorldSite, Tree},
|
||||
site2,
|
||||
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
|
||||
Index,
|
||||
Index, Land,
|
||||
};
|
||||
use common::{
|
||||
astar::Astar,
|
||||
@ -105,8 +106,10 @@ impl Civs {
|
||||
|
||||
for _ in 0..initial_civ_count * 3 {
|
||||
attempt(5, || {
|
||||
let (kind, size) = match ctx.rng.gen_range(0..8) {
|
||||
0 => (SiteKind::Castle, 3),
|
||||
let (kind, size) = match ctx.rng.gen_range(0..64) {
|
||||
0..=4 => (SiteKind::Castle, 3),
|
||||
// 5..=28 => (SiteKind::Refactor, 6),
|
||||
29..=31 => (SiteKind::Tree, 4),
|
||||
_ => (SiteKind::Dungeon, 0),
|
||||
};
|
||||
let loc = find_site_loc(&mut ctx, None, size)?;
|
||||
@ -142,14 +145,14 @@ impl Civs {
|
||||
|
||||
// Flatten ground around sites
|
||||
for site in this.sites.values() {
|
||||
let radius = 48i32;
|
||||
|
||||
let wpos = site.center * TerrainChunkSize::RECT_SIZE.map(|e: u32| e as i32);
|
||||
|
||||
let flatten_radius = match &site.kind {
|
||||
SiteKind::Settlement => 10.0,
|
||||
SiteKind::Dungeon => 2.0,
|
||||
SiteKind::Castle => 5.0,
|
||||
let (radius, flatten_radius) = match &site.kind {
|
||||
SiteKind::Settlement => (32i32, 10.0),
|
||||
SiteKind::Dungeon => (8i32, 2.0),
|
||||
SiteKind::Castle => (16i32, 5.0),
|
||||
SiteKind::Refactor => (0i32, 0.0),
|
||||
SiteKind::Tree => (12i32, 8.0),
|
||||
};
|
||||
|
||||
let (raise, raise_dist): (f32, i32) = match &site.kind {
|
||||
@ -187,7 +190,6 @@ impl Civs {
|
||||
chunk.alt += diff;
|
||||
chunk.basement += diff;
|
||||
chunk.rockiness = 0.0;
|
||||
chunk.warp_factor = 0.0;
|
||||
chunk.surface_veg *= 1.0 - factor * rng.gen_range(0.25..0.9);
|
||||
});
|
||||
}
|
||||
@ -215,6 +217,14 @@ impl Civs {
|
||||
SiteKind::Castle => {
|
||||
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
|
||||
},
|
||||
SiteKind::Refactor => WorldSite::refactor(site2::Site::generate(
|
||||
&Land::from_sim(&ctx.sim),
|
||||
&mut rng,
|
||||
wpos,
|
||||
)),
|
||||
SiteKind::Tree => {
|
||||
WorldSite::tree(Tree::generate(wpos, &Land::from_sim(&ctx.sim), &mut rng))
|
||||
},
|
||||
});
|
||||
sim_site.site_tmp = Some(site);
|
||||
let site_ref = &index.sites[site];
|
||||
@ -694,7 +704,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
||||
/// Return true if a position is suitable for walking on
|
||||
fn loc_suitable_for_walking(sim: &WorldSim, loc: Vec2<i32>) -> bool {
|
||||
if let Some(chunk) = sim.get(loc) {
|
||||
!chunk.river.is_ocean() && !chunk.river.is_lake()
|
||||
!chunk.river.is_ocean() && !chunk.river.is_lake() && !chunk.near_cliffs()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@ -893,6 +903,8 @@ pub enum SiteKind {
|
||||
Settlement,
|
||||
Dungeon,
|
||||
Castle,
|
||||
Refactor,
|
||||
Tree,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
|
@ -69,10 +69,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
|
||||
let sim = &self.sim;
|
||||
|
||||
let _turb = Vec2::new(
|
||||
sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
|
||||
sim.gen_ctx.turb_y_nz.get((wposf.div(48.0)).into_array()) as f32,
|
||||
) * 12.0;
|
||||
// let turb = Vec2::new(
|
||||
// sim.gen_ctx.turb_x_nz.get((wposf.div(48.0)).into_array()) as f32,
|
||||
// sim.gen_ctx.turb_y_nz.get((wposf.div(48.0)).into_array()) as f32,
|
||||
// ) * 12.0;
|
||||
let wposf_turb = wposf; // + turb.map(|e| e as f64);
|
||||
|
||||
let chaos = sim.get_interpolated(wpos, |chunk| chunk.chaos)?;
|
||||
@ -81,9 +81,13 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
let rockiness = sim.get_interpolated(wpos, |chunk| chunk.rockiness)?;
|
||||
let tree_density = sim.get_interpolated(wpos, |chunk| chunk.tree_density)?;
|
||||
let spawn_rate = sim.get_interpolated(wpos, |chunk| chunk.spawn_rate)?;
|
||||
let near_water =
|
||||
sim.get_interpolated(
|
||||
wpos,
|
||||
|chunk| if chunk.river.near_water() { 1.0 } else { 0.0 },
|
||||
)?;
|
||||
let alt = sim.get_interpolated_monotone(wpos, |chunk| chunk.alt)?;
|
||||
let surface_veg = sim.get_interpolated_monotone(wpos, |chunk| chunk.surface_veg)?;
|
||||
let chunk_warp_factor = sim.get_interpolated_monotone(wpos, |chunk| chunk.warp_factor)?;
|
||||
let sim_chunk = sim.get(chunk_pos)?;
|
||||
let neighbor_coef = TerrainChunkSize::RECT_SIZE.map(|e| e as f64);
|
||||
let my_chunk_idx = vec2_as_uniform_idx(self.sim.map_size_lg(), chunk_pos);
|
||||
@ -261,6 +265,27 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
)
|
||||
});
|
||||
|
||||
// Cliffs
|
||||
let cliff_factor = (alt
|
||||
+ self.sim.gen_ctx.hill_nz.get(wposf.div(64.0).into_array()) as f32 * 8.0
|
||||
+ self.sim.gen_ctx.hill_nz.get(wposf.div(350.0).into_array()) as f32 * 128.0)
|
||||
.rem_euclid(200.0)
|
||||
/ 64.0
|
||||
- 1.0;
|
||||
let cliff_scale =
|
||||
((self.sim.gen_ctx.hill_nz.get(wposf.div(128.0).into_array()) as f32 * 1.5 + 0.75)
|
||||
+ self.sim.gen_ctx.hill_nz.get(wposf.div(48.0).into_array()) as f32 * 0.1)
|
||||
.clamped(0.0, 1.0)
|
||||
.powf(2.0);
|
||||
let cliff_height = sim.get_interpolated(wpos, |chunk| chunk.cliff_height)? * cliff_scale;
|
||||
let cliff = if cliff_factor < 0.0 {
|
||||
cliff_factor.abs().powf(1.5)
|
||||
} else {
|
||||
0.0
|
||||
} * (1.0 - near_water * 3.0).max(0.0).powi(2);
|
||||
let cliff_offset = cliff * cliff_height;
|
||||
let alt = alt + (cliff - 0.5) * cliff_height;
|
||||
|
||||
// Find the average distance to each neighboring body of water.
|
||||
let mut river_count = 0.0f64;
|
||||
let mut overlap_count = 0.0f64;
|
||||
@ -436,7 +461,7 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.unwrap_or(CONFIG.sea_level);
|
||||
|
||||
let river_gouge = 0.5;
|
||||
let (_in_water, water_dist, alt_, water_level, riverless_alt, warp_factor) = if let Some(
|
||||
let (_in_water, water_dist, alt_, water_level, _riverless_alt, warp_factor) = if let Some(
|
||||
(max_border_river_pos, river_chunk, max_border_river, max_border_river_dist),
|
||||
) =
|
||||
max_river
|
||||
@ -701,13 +726,12 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
1.0,
|
||||
)
|
||||
};
|
||||
let warp_factor = warp_factor * chunk_warp_factor;
|
||||
// NOTE: To disable warp, uncomment this line.
|
||||
// let warp_factor = 0.0;
|
||||
|
||||
let riverless_alt_delta = Lerp::lerp(0.0, riverless_alt_delta, warp_factor);
|
||||
let riverless_alt = alt + riverless_alt_delta; //riverless_alt + riverless_alt_delta;
|
||||
let alt = alt_ + riverless_alt_delta;
|
||||
let riverless_alt = riverless_alt + riverless_alt_delta;
|
||||
let basement =
|
||||
alt + sim.get_interpolated_monotone(wpos, |chunk| chunk.basement.sub(chunk.alt))?;
|
||||
|
||||
@ -982,6 +1006,10 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
.map(|wd| Lerp::lerp(sub_surface_color, ground, (wd / 3.0).clamped(0.0, 1.0)))
|
||||
.unwrap_or(ground);
|
||||
|
||||
// Ground under thick trees should be receive less sunlight and so often become
|
||||
// dirt
|
||||
let ground = Lerp::lerp(ground, sub_surface_color, marble_mid * tree_density);
|
||||
|
||||
let near_ocean = max_river.and_then(|(_, _, river_data, _)| {
|
||||
if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean))
|
||||
&& alt <= water_level.max(CONFIG.sea_level + 5.0)
|
||||
@ -1047,6 +1075,8 @@ impl<'a> Sampler<'a> for ColumnGen<'a> {
|
||||
path,
|
||||
cave,
|
||||
snow_cover,
|
||||
cliff_offset,
|
||||
cliff_height,
|
||||
|
||||
chunk: sim_chunk,
|
||||
})
|
||||
@ -1077,6 +1107,8 @@ pub struct ColumnSample<'a> {
|
||||
pub path: Option<(f32, Vec2<f32>, Path, Vec2<f32>)>,
|
||||
pub cave: Option<(f32, Vec2<f32>, Cave, Vec2<f32>)>,
|
||||
pub snow_cover: bool,
|
||||
pub cliff_offset: f32,
|
||||
pub cliff_height: f32,
|
||||
|
||||
pub chunk: &'a SimChunk,
|
||||
}
|
||||
|
35
world/src/land.rs
Normal file
35
world/src/land.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use crate::sim;
|
||||
use common::{terrain::TerrainChunkSize, vol::RectVolSize};
|
||||
use vek::*;
|
||||
|
||||
/// A wrapper type that may contain a reference to a generated world. If not,
|
||||
/// default values will be provided.
|
||||
pub struct Land<'a> {
|
||||
sim: Option<&'a sim::WorldSim>,
|
||||
}
|
||||
|
||||
impl<'a> Land<'a> {
|
||||
pub fn empty() -> Self { Self { sim: None } }
|
||||
|
||||
pub fn from_sim(sim: &'a sim::WorldSim) -> Self { Self { sim: Some(sim) } }
|
||||
|
||||
pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
|
||||
self.sim
|
||||
.and_then(|sim| sim.get_alt_approx(wpos))
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn get_gradient_approx(&self, wpos: Vec2<i32>) -> f32 {
|
||||
self.sim
|
||||
.and_then(|sim| {
|
||||
sim.get_gradient_approx(
|
||||
wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)),
|
||||
)
|
||||
})
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn get_chunk_at(&self, wpos: Vec2<i32>) -> Option<&sim::SimChunk> {
|
||||
self.sim.and_then(|sim| sim.get_wpos(wpos))
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ pub use self::{scatter::apply_scatter_to, tree::apply_trees_to};
|
||||
|
||||
use crate::{
|
||||
column::ColumnSample,
|
||||
util::{RandomField, Sampler},
|
||||
util::{FastNoise, RandomField, Sampler},
|
||||
Canvas, IndexRef,
|
||||
};
|
||||
use common::{
|
||||
@ -22,7 +22,7 @@ use rand::prelude::*;
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
f32,
|
||||
ops::{Mul, Sub},
|
||||
ops::{Mul, Range, Sub},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
@ -99,7 +99,7 @@ pub fn apply_paths_to(canvas: &mut Canvas) {
|
||||
let head_space = path.head_space(path_dist);
|
||||
for z in inset..inset + head_space {
|
||||
let pos = Vec3::new(wpos2d.x, wpos2d.y, surface_z + z);
|
||||
if canvas.get(pos).unwrap().kind() != BlockKind::Water {
|
||||
if canvas.get(pos).kind() != BlockKind::Water {
|
||||
let _ = canvas.set(pos, EMPTY_AIR);
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ pub fn apply_paths_to(canvas: &mut Canvas) {
|
||||
pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
|
||||
let info = canvas.info();
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
let surface_z = col.riverless_alt.floor() as i32;
|
||||
let surface_z = col.alt.floor() as i32;
|
||||
|
||||
if let Some((cave_dist, _, cave, _)) =
|
||||
col.cave.filter(|(dist, _, cave, _)| *dist < cave.width)
|
||||
@ -135,11 +135,7 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
|
||||
{
|
||||
// If the block a little above is liquid, we should stop carving out the cave in
|
||||
// order to leave a ceiling, and not floating water
|
||||
if canvas
|
||||
.get(Vec3::new(wpos2d.x, wpos2d.y, z + 2))
|
||||
.map(|b| b.is_liquid())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if canvas.get(Vec3::new(wpos2d.x, wpos2d.y, z + 2)).is_liquid() {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -164,14 +160,20 @@ pub fn apply_caves_to(canvas: &mut Canvas, rng: &mut impl Rng) {
|
||||
)
|
||||
.mul(45.0) as i32;
|
||||
|
||||
for z in cave_roof - stalagtites..cave_roof {
|
||||
canvas.set(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, z),
|
||||
Block::new(
|
||||
BlockKind::WeakRock,
|
||||
info.index().colors.layer.stalagtite.into(),
|
||||
),
|
||||
);
|
||||
// Generate stalagtites if there's something for them to hold on to
|
||||
if canvas
|
||||
.get(Vec3::new(wpos2d.x, wpos2d.y, cave_roof))
|
||||
.is_filled()
|
||||
{
|
||||
for z in cave_roof - stalagtites..cave_roof {
|
||||
canvas.set(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, z),
|
||||
Block::new(
|
||||
BlockKind::WeakRock,
|
||||
info.index().colors.layer.stalagtite.into(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let cave_depth = (col.alt - cave.alt).max(0.0);
|
||||
@ -302,3 +304,78 @@ pub fn apply_caves_supplement<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn apply_coral_to(canvas: &mut Canvas) {
|
||||
let info = canvas.info();
|
||||
|
||||
if !info.chunk.river.near_water() {
|
||||
return; // Don't bother with coral for a chunk nowhere near water
|
||||
}
|
||||
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
const CORAL_DEPTH: Range<f32> = 14.0..32.0;
|
||||
const CORAL_HEIGHT: f32 = 14.0;
|
||||
const CORAL_DEPTH_FADEOUT: f32 = 5.0;
|
||||
const CORAL_SCALE: f32 = 10.0;
|
||||
|
||||
let water_depth = col.water_level - col.alt;
|
||||
|
||||
if !CORAL_DEPTH.contains(&water_depth) {
|
||||
return; // Avoid coral entirely for this column if we're outside coral depths
|
||||
}
|
||||
|
||||
for z in col.alt.floor() as i32..(col.alt + CORAL_HEIGHT) as i32 {
|
||||
let wpos = Vec3::new(wpos2d.x, wpos2d.y, z);
|
||||
|
||||
let coral_factor = Lerp::lerp(
|
||||
1.0,
|
||||
0.0,
|
||||
// Fade coral out due to incorrect depth
|
||||
((water_depth.clamped(CORAL_DEPTH.start, CORAL_DEPTH.end) - water_depth).abs()
|
||||
/ CORAL_DEPTH_FADEOUT)
|
||||
.min(1.0),
|
||||
) * Lerp::lerp(
|
||||
1.0,
|
||||
0.0,
|
||||
// Fade coral out due to incorrect altitude above the seabed
|
||||
((z as f32 - col.alt) / CORAL_HEIGHT).powi(2),
|
||||
) * FastNoise::new(info.index.seed + 7)
|
||||
.get(wpos.map(|e| e as f64) / 32.0)
|
||||
.sub(0.2)
|
||||
.mul(100.0)
|
||||
.clamped(0.0, 1.0);
|
||||
|
||||
let nz = Vec3::iota().map(|e: u32| FastNoise::new(info.index.seed + e * 177));
|
||||
|
||||
let wpos_warped = wpos.map(|e| e as f32)
|
||||
+ nz.map(|nz| {
|
||||
nz.get(wpos.map(|e| e as f64) / CORAL_SCALE as f64) * CORAL_SCALE * 0.3
|
||||
});
|
||||
|
||||
// let is_coral = FastNoise2d::new(info.index.seed + 17)
|
||||
// .get(wpos_warped.xy().map(|e| e as f64) / CORAL_SCALE)
|
||||
// .sub(1.0 - coral_factor)
|
||||
// .max(0.0)
|
||||
// .div(coral_factor) > 0.5;
|
||||
|
||||
let is_coral = [
|
||||
FastNoise::new(info.index.seed),
|
||||
FastNoise::new(info.index.seed + 177),
|
||||
]
|
||||
.iter()
|
||||
.all(|nz| {
|
||||
nz.get(wpos_warped.map(|e| e as f64) / CORAL_SCALE as f64)
|
||||
.abs()
|
||||
< coral_factor * 0.3
|
||||
});
|
||||
|
||||
if is_coral {
|
||||
let _ = canvas.set(
|
||||
wpos,
|
||||
Block::new(BlockKind::WeakRock, Rgb::new(170, 220, 210)),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -579,15 +579,13 @@ pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
|
||||
.find(|z| {
|
||||
canvas
|
||||
.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))
|
||||
.map(|b| b.is_solid())
|
||||
.unwrap_or(false)
|
||||
.is_solid()
|
||||
})
|
||||
.and_then(|solid_start| {
|
||||
(1..8).map(|z| solid_start + z).find(|z| {
|
||||
canvas
|
||||
!canvas
|
||||
.get(Vec3::new(wpos2d.x, wpos2d.y, alt + z))
|
||||
.map(|b| !b.is_solid())
|
||||
.unwrap_or(true)
|
||||
.is_solid()
|
||||
})
|
||||
})
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
all::ForestKind,
|
||||
all::*,
|
||||
block::block_from_structure,
|
||||
column::ColumnGen,
|
||||
util::{RandomPerm, Sampler, UnitChooser},
|
||||
@ -7,12 +7,16 @@ use crate::{
|
||||
};
|
||||
use common::{
|
||||
assets::AssetHandle,
|
||||
terrain::{Block, BlockKind, Structure, StructuresGroup},
|
||||
terrain::{
|
||||
structure::{Structure, StructureBlock, StructuresGroup},
|
||||
Block, BlockKind, SpriteKind,
|
||||
},
|
||||
vol::ReadVol,
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use std::f32;
|
||||
use rand::prelude::*;
|
||||
use std::{f32, ops::Range};
|
||||
use vek::*;
|
||||
|
||||
lazy_static! {
|
||||
@ -36,10 +40,16 @@ static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
|
||||
static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F);
|
||||
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
|
||||
// TODO: Get rid of this
|
||||
enum TreeModel {
|
||||
Structure(Structure),
|
||||
Procedural(ProceduralTree, StructureBlock),
|
||||
}
|
||||
|
||||
struct Tree {
|
||||
pos: Vec3<i32>,
|
||||
model: Structure,
|
||||
model: TreeModel,
|
||||
seed: u32,
|
||||
units: (Vec2<i32>, Vec2<i32>),
|
||||
}
|
||||
@ -48,11 +58,18 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
|
||||
let info = canvas.info();
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
let trees = info.land().get_near_trees(wpos2d);
|
||||
let trees = info.chunks().get_near_trees(wpos2d);
|
||||
|
||||
for (tree_wpos, seed) in trees {
|
||||
let tree = if let Some(tree) = tree_cache.entry(tree_wpos).or_insert_with(|| {
|
||||
let col = ColumnGen::new(info.land()).get((tree_wpos, info.index()))?;
|
||||
for TreeAttr {
|
||||
pos,
|
||||
seed,
|
||||
scale,
|
||||
forest_kind,
|
||||
inhabited,
|
||||
} in trees
|
||||
{
|
||||
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
|
||||
let col = ColumnGen::new(info.chunks()).get((pos, info.index()))?;
|
||||
|
||||
let is_quirky = QUIRKY_RAND.chance(seed, 1.0 / 500.0);
|
||||
|
||||
@ -72,8 +89,8 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
}
|
||||
|
||||
Some(Tree {
|
||||
pos: Vec3::new(tree_wpos.x, tree_wpos.y, col.alt as i32),
|
||||
model: {
|
||||
pos: Vec3::new(pos.x, pos.y, col.alt as i32),
|
||||
model: 'model: {
|
||||
let models: AssetHandle<_> = if is_quirky {
|
||||
if col.temp > CONFIG.desert_temp {
|
||||
*QUIRKY_DRY
|
||||
@ -81,7 +98,7 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
*QUIRKY
|
||||
}
|
||||
} else {
|
||||
match col.forest_kind {
|
||||
match forest_kind {
|
||||
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => {
|
||||
*OAK_STUMPS
|
||||
},
|
||||
@ -91,17 +108,51 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
ForestKind::Palm => *PALMS,
|
||||
ForestKind::Acacia => *ACACIAS,
|
||||
ForestKind::Baobab => *BAOBABS,
|
||||
ForestKind::Oak => *OAKS,
|
||||
ForestKind::Pine => *PINES,
|
||||
// ForestKind::Oak => *OAKS,
|
||||
ForestKind::Oak => {
|
||||
break 'model TreeModel::Procedural(
|
||||
ProceduralTree::generate(
|
||||
TreeConfig::oak(&mut RandomPerm::new(seed), scale),
|
||||
&mut RandomPerm::new(seed),
|
||||
),
|
||||
StructureBlock::TemperateLeaves,
|
||||
);
|
||||
},
|
||||
//ForestKind::Pine => *PINES,
|
||||
ForestKind::Pine => {
|
||||
break 'model TreeModel::Procedural(
|
||||
ProceduralTree::generate(
|
||||
TreeConfig::pine(&mut RandomPerm::new(seed), scale),
|
||||
&mut RandomPerm::new(seed),
|
||||
),
|
||||
StructureBlock::PineLeaves,
|
||||
);
|
||||
},
|
||||
ForestKind::Birch => *BIRCHES,
|
||||
ForestKind::Mangrove => *MANGROVE_TREES,
|
||||
ForestKind::Swamp => *SWAMP_TREES,
|
||||
ForestKind::Giant => {
|
||||
break 'model TreeModel::Procedural(
|
||||
ProceduralTree::generate(
|
||||
TreeConfig::giant(
|
||||
&mut RandomPerm::new(seed),
|
||||
scale,
|
||||
inhabited,
|
||||
),
|
||||
&mut RandomPerm::new(seed),
|
||||
),
|
||||
StructureBlock::TemperateLeaves,
|
||||
);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let models = models.read();
|
||||
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize % models.len()]
|
||||
.clone()
|
||||
TreeModel::Structure(
|
||||
models[(MODEL_RAND.get(seed.wrapping_mul(17)) / 13) as usize
|
||||
% models.len()]
|
||||
.clone(),
|
||||
)
|
||||
},
|
||||
seed,
|
||||
units: UNIT_CHOOSER.get(seed),
|
||||
@ -112,9 +163,22 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
continue;
|
||||
};
|
||||
|
||||
let bounds = tree.model.get_bounds();
|
||||
let bounds = match &tree.model {
|
||||
TreeModel::Structure(s) => s.get_bounds(),
|
||||
TreeModel::Procedural(t, _) => t.get_bounds().map(|e| e as i32),
|
||||
};
|
||||
|
||||
let rpos2d = (wpos2d - tree.pos.xy())
|
||||
.map2(Vec2::new(tree.units.0, tree.units.1), |p, unit| unit * p)
|
||||
.sum();
|
||||
if !Aabr::from(bounds).contains_point(rpos2d) {
|
||||
// Skip this column
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut is_top = true;
|
||||
let mut is_leaf_top = true;
|
||||
let mut last_block = Block::empty();
|
||||
for z in (bounds.min.z..bounds.max.z).rev() {
|
||||
let wpos = Vec3::new(wpos2d.x, wpos2d.y, tree.pos.z + z);
|
||||
let model_pos = Vec3::from(
|
||||
@ -127,11 +191,22 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
) + Vec3::unit_z() * (wpos.z - tree.pos.z);
|
||||
block_from_structure(
|
||||
info.index(),
|
||||
if let Some(block) = tree.model.get(model_pos).ok().copied() {
|
||||
if let Some(block) = match &tree.model {
|
||||
TreeModel::Structure(s) => s.get(model_pos).ok().copied(),
|
||||
TreeModel::Procedural(t, leaf_block) => Some(
|
||||
match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) {
|
||||
(_, _, true, _) => {
|
||||
StructureBlock::Block(BlockKind::Wood, Rgb::new(110, 68, 22))
|
||||
},
|
||||
(_, _, _, true) => StructureBlock::None,
|
||||
(true, _, _, _) => StructureBlock::Log,
|
||||
(_, true, _, _) => *leaf_block,
|
||||
_ => StructureBlock::None,
|
||||
},
|
||||
),
|
||||
} {
|
||||
block
|
||||
} else {
|
||||
// If we hit an inaccessible block, we're probably outside the model bounds.
|
||||
// Skip this column.
|
||||
break;
|
||||
},
|
||||
wpos,
|
||||
@ -141,8 +216,16 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
Block::air,
|
||||
)
|
||||
.map(|block| {
|
||||
// Add a snow covering to the block above under certain circumstances
|
||||
if col.snow_cover
|
||||
// Add lights to the tree
|
||||
if inhabited
|
||||
&& last_block.is_air()
|
||||
&& block.kind() == BlockKind::Wood
|
||||
&& dynamic_rng.gen_range(0..256) == 0
|
||||
{
|
||||
canvas.set(wpos + Vec3::unit_z(), Block::air(SpriteKind::Lantern));
|
||||
// Add a snow covering to the block above under certain
|
||||
// circumstances
|
||||
} else if col.snow_cover
|
||||
&& ((block.kind() == BlockKind::Leaves && is_leaf_top)
|
||||
|| (is_top && block.is_filled()))
|
||||
{
|
||||
@ -154,11 +237,420 @@ pub fn apply_trees_to(canvas: &mut Canvas) {
|
||||
canvas.set(wpos, block);
|
||||
is_leaf_top = false;
|
||||
is_top = false;
|
||||
last_block = block;
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
if last_block.kind() == BlockKind::Wood && dynamic_rng.gen_range(0..512) == 0 {
|
||||
canvas.set(wpos, Block::air(SpriteKind::Beehive));
|
||||
}
|
||||
|
||||
is_leaf_top = true;
|
||||
last_block = Block::empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// A type that specifies the generation properties of a tree.
|
||||
pub struct TreeConfig {
|
||||
/// Length of trunk, also scales other branches.
|
||||
pub trunk_len: f32,
|
||||
/// Radius of trunk, also scales other branches.
|
||||
pub trunk_radius: f32,
|
||||
// The scale that child branch lengths should be compared to their parents.
|
||||
pub branch_child_len: f32,
|
||||
// The scale that child branch radii should be compared to their parents.
|
||||
pub branch_child_radius: f32,
|
||||
/// The range of radii that leaf-emitting branches might have.
|
||||
pub leaf_radius: Range<f32>,
|
||||
/// 0 - 1 (0 = chaotic, 1 = straight).
|
||||
pub straightness: f32,
|
||||
/// Maximum number of branch layers (not including trunk).
|
||||
pub max_depth: usize,
|
||||
/// The number of branches that form from each branch.
|
||||
pub splits: Range<f32>,
|
||||
/// The range of proportions along a branch at which a split into another
|
||||
/// branch might occur. This value is clamped between 0 and 1, but a
|
||||
/// wider range may bias the results towards branch ends.
|
||||
pub split_range: Range<f32>,
|
||||
/// The bias applied to the length of branches based on the proportion along
|
||||
/// their parent that they eminate from. -1.0 = negative bias (branches
|
||||
/// at ends are longer, branches at the start are shorter) 0.0 = no bias
|
||||
/// (branches do not change their length with regard to parent branch
|
||||
/// proportion) 1.0 = positive bias (branches at ends are shorter,
|
||||
/// branches at the start are longer)
|
||||
pub branch_len_bias: f32,
|
||||
/// The scale of leaves in the vertical plane. Less than 1.0 implies a
|
||||
/// flattening of the leaves.
|
||||
pub leaf_vertical_scale: f32,
|
||||
/// How evenly spaced (vs random) sub-branches are along their parent.
|
||||
pub proportionality: f32,
|
||||
/// Whether the tree is inhabited (adds various features and effects)
|
||||
pub inhabited: bool,
|
||||
}
|
||||
|
||||
impl TreeConfig {
|
||||
pub fn oak(rng: &mut impl Rng, scale: f32) -> Self {
|
||||
let scale = scale * (0.8 + rng.gen::<f32>().powi(4) * 0.75);
|
||||
let log_scale = 1.0 + scale.log2().max(0.0);
|
||||
|
||||
Self {
|
||||
trunk_len: 9.0 * scale,
|
||||
trunk_radius: 2.0 * scale,
|
||||
branch_child_len: 0.9,
|
||||
branch_child_radius: 0.75,
|
||||
leaf_radius: 2.5 * log_scale..3.25 * log_scale,
|
||||
straightness: 0.45,
|
||||
max_depth: 4,
|
||||
splits: 2.25..3.25,
|
||||
split_range: 0.75..1.5,
|
||||
branch_len_bias: 0.0,
|
||||
leaf_vertical_scale: 1.0,
|
||||
proportionality: 0.0,
|
||||
inhabited: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pine(rng: &mut impl Rng, scale: f32) -> Self {
|
||||
let scale = scale * (1.0 + rng.gen::<f32>().powi(4) * 0.5);
|
||||
let log_scale = 1.0 + scale.log2().max(0.0);
|
||||
|
||||
Self {
|
||||
trunk_len: 32.0 * scale,
|
||||
trunk_radius: 1.25 * scale,
|
||||
branch_child_len: 0.35 / scale,
|
||||
branch_child_radius: 0.0,
|
||||
leaf_radius: 2.5 * log_scale..2.75 * log_scale,
|
||||
straightness: 0.0,
|
||||
max_depth: 1,
|
||||
splits: 40.0..50.0,
|
||||
split_range: 0.165..1.2,
|
||||
branch_len_bias: 0.75,
|
||||
leaf_vertical_scale: 0.3,
|
||||
proportionality: 1.0,
|
||||
inhabited: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn giant(_rng: &mut impl Rng, scale: f32, inhabited: bool) -> Self {
|
||||
let log_scale = 1.0 + scale.log2().max(0.0);
|
||||
|
||||
Self {
|
||||
trunk_len: 11.0 * scale,
|
||||
trunk_radius: 6.0 * scale,
|
||||
branch_child_len: 0.9,
|
||||
branch_child_radius: 0.75,
|
||||
leaf_radius: 2.5 * scale..3.75 * scale,
|
||||
straightness: 0.36,
|
||||
max_depth: (7.0 + log_scale) as usize,
|
||||
splits: 1.5..2.5,
|
||||
split_range: 1.0..1.1,
|
||||
branch_len_bias: 0.0,
|
||||
leaf_vertical_scale: 0.6,
|
||||
proportionality: 0.0,
|
||||
inhabited,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename this to `Tree` when the name conflict is gone
|
||||
pub struct ProceduralTree {
|
||||
branches: Vec<Branch>,
|
||||
trunk_idx: usize,
|
||||
}
|
||||
|
||||
impl ProceduralTree {
|
||||
/// Generate a new tree using the given configuration and seed.
|
||||
pub fn generate(config: TreeConfig, rng: &mut impl Rng) -> Self {
|
||||
let mut this = Self {
|
||||
branches: Vec::new(),
|
||||
trunk_idx: 0, // Gets replaced later
|
||||
};
|
||||
|
||||
// Add the tree trunk (and sub-branches) recursively
|
||||
let (trunk_idx, _) = this.add_branch(
|
||||
&config,
|
||||
// Our trunk starts at the origin...
|
||||
Vec3::zero(),
|
||||
// ...and has a roughly upward direction
|
||||
Vec3::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0), 10.0).normalized(),
|
||||
config.trunk_len,
|
||||
config.trunk_radius,
|
||||
0,
|
||||
None,
|
||||
rng,
|
||||
);
|
||||
this.trunk_idx = trunk_idx;
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
// Recursively add a branch (with sub-branches) to the tree's branch graph,
|
||||
// returning the index and AABB of the branch. This AABB gets propagated
|
||||
// down to the parent and is used later during sampling to cull the branches to
|
||||
// be sampled.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn add_branch(
|
||||
&mut self,
|
||||
config: &TreeConfig,
|
||||
start: Vec3<f32>,
|
||||
dir: Vec3<f32>,
|
||||
branch_len: f32,
|
||||
branch_radius: f32,
|
||||
depth: usize,
|
||||
sibling_idx: Option<usize>,
|
||||
rng: &mut impl Rng,
|
||||
) -> (usize, Aabb<f32>) {
|
||||
let end = start + dir * branch_len;
|
||||
let line = LineSegment3 { start, end };
|
||||
let wood_radius = branch_radius;
|
||||
let leaf_radius = if depth == config.max_depth {
|
||||
rng.gen_range(config.leaf_radius.clone())
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let has_stairs = config.inhabited
|
||||
&& depth < config.max_depth
|
||||
&& branch_radius > 6.5
|
||||
&& start.xy().distance(end.xy()) < (start.z - end.z).abs() * 1.5;
|
||||
let bark_radius = if has_stairs { 5.0 } else { 0.0 } + wood_radius * 0.25;
|
||||
|
||||
// The AABB that covers this branch, along with wood and leaves that eminate
|
||||
// from it
|
||||
let mut aabb = Aabb {
|
||||
min: Vec3::partial_min(start, end) - (wood_radius + bark_radius).max(leaf_radius),
|
||||
max: Vec3::partial_max(start, end) + (wood_radius + bark_radius).max(leaf_radius),
|
||||
};
|
||||
|
||||
let mut child_idx = None;
|
||||
// Don't add child branches if we're already enough layers into the tree
|
||||
if depth < config.max_depth {
|
||||
let x_axis = dir
|
||||
.cross(Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)))
|
||||
.normalized();
|
||||
let y_axis = dir.cross(x_axis).normalized();
|
||||
let screw_shift = rng.gen_range(0.0..f32::consts::TAU);
|
||||
|
||||
let splits = rng.gen_range(config.splits.clone()).round() as usize;
|
||||
for i in 0..splits {
|
||||
let dist = Lerp::lerp(
|
||||
rng.gen_range(0.0..1.0),
|
||||
i as f32 / (splits - 1) as f32,
|
||||
config.proportionality,
|
||||
);
|
||||
|
||||
const PHI: f32 = 0.618;
|
||||
const RAD_PER_BRANCH: f32 = f32::consts::TAU * PHI;
|
||||
let screw = (screw_shift + i as f32 * RAD_PER_BRANCH).sin() * x_axis
|
||||
+ (screw_shift + i as f32 * RAD_PER_BRANCH).cos() * y_axis;
|
||||
|
||||
// Choose a point close to the branch to act as the target direction for the
|
||||
// branch to grow in let split_factor =
|
||||
// rng.gen_range(config.split_range.start, config.split_range.end).clamped(0.0,
|
||||
// 1.0);
|
||||
let split_factor =
|
||||
Lerp::lerp(config.split_range.start, config.split_range.end, dist);
|
||||
let tgt = Lerp::lerp_unclamped(start, end, split_factor)
|
||||
+ Lerp::lerp(
|
||||
Vec3::<f32>::zero().map(|_| rng.gen_range(-1.0..1.0)),
|
||||
screw,
|
||||
config.proportionality,
|
||||
);
|
||||
// Start the branch at the closest point to the target
|
||||
let branch_start = line.projected_point(tgt);
|
||||
// Now, interpolate between the target direction and the parent branch's
|
||||
// direction to find a direction
|
||||
let branch_dir =
|
||||
Lerp::lerp(tgt - branch_start, dir, config.straightness).normalized();
|
||||
|
||||
let (branch_idx, branch_aabb) = self.add_branch(
|
||||
config,
|
||||
branch_start,
|
||||
branch_dir,
|
||||
branch_len
|
||||
* config.branch_child_len
|
||||
* (1.0
|
||||
- (split_factor - 0.5)
|
||||
* 2.0
|
||||
* config.branch_len_bias.clamped(-1.0, 1.0)),
|
||||
branch_radius * config.branch_child_radius,
|
||||
depth + 1,
|
||||
child_idx,
|
||||
rng,
|
||||
);
|
||||
child_idx = Some(branch_idx);
|
||||
// Parent branches AABBs include the AABBs of child branches to allow for
|
||||
// culling during sampling
|
||||
aabb.expand_to_contain(branch_aabb);
|
||||
}
|
||||
}
|
||||
|
||||
let idx = self.branches.len(); // Compute the index that this branch is going to have
|
||||
self.branches.push(Branch {
|
||||
line,
|
||||
wood_radius,
|
||||
leaf_radius,
|
||||
leaf_vertical_scale: config.leaf_vertical_scale,
|
||||
aabb,
|
||||
sibling_idx,
|
||||
child_idx,
|
||||
has_stairs,
|
||||
});
|
||||
|
||||
(idx, aabb)
|
||||
}
|
||||
|
||||
/// Get the bounding box that covers the tree (all branches and leaves)
|
||||
pub fn get_bounds(&self) -> Aabb<f32> { self.branches[self.trunk_idx].aabb }
|
||||
|
||||
// Recursively search for branches or leaves by walking the tree's branch graph.
|
||||
fn is_branch_or_leaves_at_inner(
|
||||
&self,
|
||||
pos: Vec3<f32>,
|
||||
parent: &Branch,
|
||||
branch_idx: usize,
|
||||
) -> (bool, bool, bool, bool) {
|
||||
let branch = &self.branches[branch_idx];
|
||||
// Always probe the sibling branch, since our AABB doesn't include its bounds
|
||||
// (it's not one of our children)
|
||||
let branch_or_leaves = branch
|
||||
.sibling_idx
|
||||
.map(|idx| Vec4::<bool>::from(self.is_branch_or_leaves_at_inner(pos, parent, idx)))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Only continue probing this sub-graph of the tree if the sample position falls
|
||||
// within its AABB
|
||||
if branch.aabb.contains_point(pos) {
|
||||
// Probe this branch
|
||||
let (this, _d2) = branch.is_branch_or_leaves_at(pos, parent);
|
||||
|
||||
let siblings = branch_or_leaves | Vec4::from(this);
|
||||
|
||||
// Probe the children of this branch
|
||||
let children = branch
|
||||
.child_idx
|
||||
.map(|idx| Vec4::<bool>::from(self.is_branch_or_leaves_at_inner(pos, branch, idx)))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Only allow empties for children if there is no solid at the current depth
|
||||
(siblings | children).into_tuple()
|
||||
} else {
|
||||
branch_or_leaves.into_tuple()
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine whether there are either branches or leaves at the given
|
||||
/// position in the tree.
|
||||
#[inline(always)]
|
||||
pub fn is_branch_or_leaves_at(&self, pos: Vec3<f32>) -> (bool, bool, bool, bool) {
|
||||
let (log, leaf, platform, air) =
|
||||
self.is_branch_or_leaves_at_inner(pos, &self.branches[self.trunk_idx], self.trunk_idx);
|
||||
(log /* & !air */, leaf & !air, platform & !air, air)
|
||||
}
|
||||
}
|
||||
|
||||
// Branches are arranged in a graph shape. Each branch points to both its first
|
||||
// child (if any) and also to the next branch in the list of child branches
|
||||
// associated with the parent. This means that the entire tree is laid out in a
|
||||
// walkable graph where each branch refers only to two other branches. As a
|
||||
// result, walking the tree is simply a case of performing double recursion.
|
||||
struct Branch {
|
||||
line: LineSegment3<f32>,
|
||||
wood_radius: f32,
|
||||
leaf_radius: f32,
|
||||
leaf_vertical_scale: f32,
|
||||
aabb: Aabb<f32>,
|
||||
|
||||
sibling_idx: Option<usize>,
|
||||
child_idx: Option<usize>,
|
||||
|
||||
has_stairs: bool,
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
/// Determine whether there are either branches or leaves at the given
|
||||
/// position in the branch.
|
||||
/// (branch, leaves, stairs, forced_air)
|
||||
pub fn is_branch_or_leaves_at(
|
||||
&self,
|
||||
pos: Vec3<f32>,
|
||||
parent: &Branch,
|
||||
) -> ((bool, bool, bool, bool), f32) {
|
||||
// fn finvsqrt(x: f32) -> f32 {
|
||||
// let y = f32::from_bits(0x5f375a86 - (x.to_bits() >> 1));
|
||||
// y * (1.5 - ( x * 0.5 * y * y ))
|
||||
// }
|
||||
|
||||
fn length_factor(line: LineSegment3<f32>, p: Vec3<f32>) -> f32 {
|
||||
let len_sq = line.start.distance_squared(line.end);
|
||||
if len_sq < 0.001 {
|
||||
0.0
|
||||
} else {
|
||||
(p - line.start).dot(line.end - line.start) / len_sq
|
||||
}
|
||||
}
|
||||
|
||||
// fn smooth(a: f32, b: f32, k: f32) -> f32 {
|
||||
// // let h = (0.5 + 0.5 * (b - a) / k).clamped(0.0, 1.0);
|
||||
// // Lerp::lerp(b, a, h) - k * h * (1.0 - h)
|
||||
|
||||
// let h = (k-(a-b).abs()).max(0.0);
|
||||
// a.min(b) - h * h * 0.25 / k
|
||||
// }
|
||||
|
||||
let p = self.line.projected_point(pos);
|
||||
let d2 = p.distance_squared(pos);
|
||||
|
||||
let length_factor = length_factor(self.line, pos);
|
||||
let wood_radius = Lerp::lerp(parent.wood_radius, self.wood_radius, length_factor);
|
||||
|
||||
let mask = if d2 < wood_radius.powi(2) {
|
||||
(true, false, false, false) // Wood
|
||||
} else if {
|
||||
let diff = (p - pos) / Vec3::new(1.0, 1.0, self.leaf_vertical_scale);
|
||||
diff.magnitude_squared() < self.leaf_radius.powi(2)
|
||||
} {
|
||||
(false, true, false, false) // Leaves
|
||||
} else {
|
||||
let stair_width = 5.0;
|
||||
let stair_thickness = 2.0;
|
||||
let stair_space = 5.0;
|
||||
if self.has_stairs {
|
||||
let (platform, air) = if pos.z >= self.line.start.z.min(self.line.end.z) - 1.0
|
||||
&& pos.z
|
||||
<= self.line.start.z.max(self.line.end.z) + stair_thickness + stair_space
|
||||
&& d2 < (wood_radius + stair_width).powi(2)
|
||||
{
|
||||
let rpos = pos.xy() - p;
|
||||
let stretch = 32.0;
|
||||
let stair_section =
|
||||
((rpos.x as f32).atan2(rpos.y as f32) / (f32::consts::PI * 2.0) * stretch
|
||||
+ pos.z)
|
||||
.rem_euclid(stretch);
|
||||
(
|
||||
stair_section < stair_thickness,
|
||||
stair_section >= stair_thickness
|
||||
&& stair_section < stair_thickness + stair_space,
|
||||
) // Stairs
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
|
||||
let platform = platform
|
||||
|| (self.has_stairs
|
||||
&& self.wood_radius > 4.0
|
||||
&& !air
|
||||
&& d2 < (wood_radius + 10.0).powi(2)
|
||||
&& pos.z % 48.0 < stair_thickness);
|
||||
|
||||
(false, false, platform, air)
|
||||
} else {
|
||||
(false, false, false, false)
|
||||
}
|
||||
};
|
||||
|
||||
(mask, d2)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
#![deny(unsafe_code)]
|
||||
#![allow(incomplete_features)]
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![allow(
|
||||
clippy::option_map_unit_fn,
|
||||
clippy::blocks_in_if_conditions,
|
||||
clippy::too_many_arguments
|
||||
)]
|
||||
#![deny(clippy::clone_on_ref_ptr)]
|
||||
#![feature(
|
||||
arbitrary_enum_discriminant,
|
||||
@ -9,7 +13,8 @@
|
||||
const_panic,
|
||||
label_break_value,
|
||||
or_patterns,
|
||||
array_value_iter
|
||||
array_value_iter,
|
||||
array_map
|
||||
)]
|
||||
|
||||
mod all;
|
||||
@ -19,17 +24,20 @@ pub mod civ;
|
||||
mod column;
|
||||
pub mod config;
|
||||
pub mod index;
|
||||
pub mod land;
|
||||
pub mod layer;
|
||||
pub mod pathfinding;
|
||||
pub mod sim;
|
||||
pub mod sim2;
|
||||
pub mod site;
|
||||
pub mod site2;
|
||||
pub mod util;
|
||||
|
||||
// Reexports
|
||||
pub use crate::{
|
||||
canvas::{Canvas, CanvasInfo},
|
||||
config::CONFIG,
|
||||
land::Land,
|
||||
};
|
||||
pub use block::BlockGen;
|
||||
pub use column::ColumnSample;
|
||||
@ -117,6 +125,8 @@ impl World {
|
||||
},
|
||||
},
|
||||
civ::SiteKind::Castle => world_msg::SiteKind::Castle,
|
||||
civ::SiteKind::Refactor => world_msg::SiteKind::Town,
|
||||
civ::SiteKind::Tree => world_msg::SiteKind::Tree,
|
||||
},
|
||||
wpos: site.center * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||
}
|
||||
@ -278,22 +288,24 @@ impl World {
|
||||
wpos: chunk_pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32),
|
||||
column_grid: &zcache_grid,
|
||||
column_grid_border: grid_border,
|
||||
land: &self.sim,
|
||||
chunks: &self.sim,
|
||||
index,
|
||||
chunk: sim_chunk,
|
||||
},
|
||||
chunk: &mut chunk,
|
||||
};
|
||||
|
||||
layer::apply_trees_to(&mut canvas);
|
||||
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
|
||||
layer::apply_caves_to(&mut canvas, &mut dynamic_rng);
|
||||
layer::apply_trees_to(&mut canvas, &mut dynamic_rng);
|
||||
layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
|
||||
layer::apply_paths_to(&mut canvas);
|
||||
// layer::apply_coral_to(&mut canvas);
|
||||
|
||||
// Apply site generation
|
||||
sim_chunk.sites.iter().for_each(|site| {
|
||||
index.sites[*site].apply_to(index, chunk_wpos2d, sample_get, &mut chunk)
|
||||
});
|
||||
sim_chunk
|
||||
.sites
|
||||
.iter()
|
||||
.for_each(|site| index.sites[*site].apply_to(&mut canvas, &mut dynamic_rng));
|
||||
|
||||
let gen_entity_pos = |dynamic_rng: &mut rand::rngs::ThreadRng| {
|
||||
let lpos2d = TerrainChunkSize::RECT_SIZE
|
||||
|
@ -133,7 +133,7 @@ pub fn sample_pos(
|
||||
let humidity = humidity.min(1.0).max(0.0);
|
||||
let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5;
|
||||
let wpos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32);
|
||||
let column_rgb = samples
|
||||
let column_rgb_alt = samples
|
||||
.and_then(|samples| {
|
||||
chunk_idx
|
||||
.and_then(|chunk_idx| samples.get(chunk_idx))
|
||||
@ -146,7 +146,7 @@ pub fn sample_pos(
|
||||
let basement = sample.basement;
|
||||
let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement);
|
||||
let wposz = if is_basement { basement } else { alt };
|
||||
if is_basement && wposz < alt - grass_depth {
|
||||
let rgb = if is_basement && wposz < alt - grass_depth {
|
||||
Lerp::lerp(
|
||||
sample.sub_surface_color,
|
||||
sample.stone_col.map(|e| e as f32 / 255.0),
|
||||
@ -160,11 +160,17 @@ pub fn sample_pos(
|
||||
((wposz as f32 - (alt - grass_depth)) / grass_depth).sqrt(),
|
||||
)
|
||||
.map(|e| e as f64)
|
||||
}
|
||||
};
|
||||
|
||||
(rgb, alt)
|
||||
});
|
||||
|
||||
let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
|
||||
let alt = if is_basement { basement } else { alt };
|
||||
let alt = if is_basement {
|
||||
basement
|
||||
} else {
|
||||
column_rgb_alt.map_or(alt, |(_, alt)| alt)
|
||||
};
|
||||
|
||||
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;
|
||||
let true_alt = (alt as f64 - focus.z) / gain as f64;
|
||||
@ -183,7 +189,7 @@ pub fn sample_pos(
|
||||
if is_shaded { 1.0 } else { alt },
|
||||
if is_shaded || is_humidity { 1.0 } else { 0.0 },
|
||||
);
|
||||
let column_rgb = column_rgb.unwrap_or(default_rgb);
|
||||
let column_rgb = column_rgb_alt.map(|(rgb, _)| rgb).unwrap_or(default_rgb);
|
||||
let mut connections = [None; 8];
|
||||
let mut has_connections = false;
|
||||
// TODO: Support non-river connections.
|
||||
|
@ -23,20 +23,22 @@ pub use self::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
all::ForestKind,
|
||||
all::{Environment, ForestKind, TreeAttr},
|
||||
block::BlockGen,
|
||||
civ::Place,
|
||||
column::ColumnGen,
|
||||
site::Site,
|
||||
util::{
|
||||
seed_expan, FastNoise, RandomField, RandomPerm, Sampler, StructureGen2d, LOCALITY,
|
||||
NEIGHBORS,
|
||||
seed_expan, DHashSet, FastNoise, FastNoise2d, RandomField, Sampler, StructureGen2d,
|
||||
CARDINALS, LOCALITY, NEIGHBORS,
|
||||
},
|
||||
IndexRef, CONFIG,
|
||||
};
|
||||
use common::{
|
||||
assets::{self, AssetExt},
|
||||
grid::Grid,
|
||||
lottery::Lottery,
|
||||
spiral::Spiral2d,
|
||||
store::Id,
|
||||
terrain::{
|
||||
map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,
|
||||
@ -45,6 +47,7 @@ use common::{
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use common_net::msg::WorldMapMsg;
|
||||
use enum_iterator::IntoEnumIterator;
|
||||
use noise::{
|
||||
BasicMulti, Billow, Fbm, HybridMulti, MultiFractal, NoiseFn, RangeFunction, RidgedMulti,
|
||||
Seedable, SuperSimplex, Worley,
|
||||
@ -114,6 +117,7 @@ pub(crate) struct GenCtx {
|
||||
pub cave_1_nz: SuperSimplex,
|
||||
|
||||
pub structure_gen: StructureGen2d,
|
||||
pub big_structure_gen: StructureGen2d,
|
||||
pub region_gen: StructureGen2d,
|
||||
|
||||
pub fast_turb_x_nz: FastNoise,
|
||||
@ -516,7 +520,8 @@ impl WorldSim {
|
||||
cave_0_nz: SuperSimplex::new().set_seed(rng.gen()),
|
||||
cave_1_nz: SuperSimplex::new().set_seed(rng.gen()),
|
||||
|
||||
structure_gen: StructureGen2d::new(rng.gen(), 32, 16),
|
||||
structure_gen: StructureGen2d::new(rng.gen(), 24, 10),
|
||||
big_structure_gen: StructureGen2d::new(rng.gen(), 768, 512),
|
||||
region_gen: StructureGen2d::new(rng.gen(), 400, 96),
|
||||
humid_nz: Billow::new()
|
||||
.set_octaves(9)
|
||||
@ -1397,6 +1402,8 @@ impl WorldSim {
|
||||
rng,
|
||||
};
|
||||
|
||||
this.generate_cliffs();
|
||||
|
||||
if opts.seed_elements {
|
||||
this.seed_elements();
|
||||
}
|
||||
@ -1509,6 +1516,50 @@ impl WorldSim {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_cliffs(&mut self) {
|
||||
let mut rng = self.rng.clone();
|
||||
|
||||
for _ in 0..self.get_size().product() / 10 {
|
||||
let mut pos = self.get_size().map(|e| rng.gen_range(0..e) as i32);
|
||||
|
||||
let mut cliffs = DHashSet::default();
|
||||
let mut cliff_path = Vec::new();
|
||||
|
||||
for _ in 0..64 {
|
||||
if self.get_gradient_approx(pos).map_or(false, |g| g > 1.5) {
|
||||
if !cliffs.insert(pos) {
|
||||
break;
|
||||
}
|
||||
cliff_path.push((pos, 0.0));
|
||||
|
||||
pos += CARDINALS
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by_key(|rpos| {
|
||||
self.get_gradient_approx(pos + rpos)
|
||||
.map_or(0, |g| (g * 1000.0) as i32)
|
||||
})
|
||||
.unwrap(); // Can't fail
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for cliff in cliffs {
|
||||
Spiral2d::new()
|
||||
.take((4usize * 2 + 1).pow(2))
|
||||
.for_each(|rpos| {
|
||||
let dist = rpos.map(|e| e as f32).magnitude();
|
||||
if let Some(c) = self.get_mut(cliff + rpos) {
|
||||
let warp = 1.0 / (1.0 + dist);
|
||||
c.tree_density *= 1.0 - warp;
|
||||
c.cliff_height = Lerp::lerp(44.0, 0.0, -1.0 + dist / 3.5);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare the world for simulation
|
||||
pub fn seed_elements(&mut self) {
|
||||
let mut rng = self.rng.clone();
|
||||
@ -1995,9 +2046,58 @@ impl WorldSim {
|
||||
/// Return an iterator over candidate tree positions (note that only some of
|
||||
/// these will become trees since environmental parameters may forbid
|
||||
/// them spawning).
|
||||
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = (Vec2<i32>, u32)> + '_ {
|
||||
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
|
||||
// Deterministic based on wpos
|
||||
std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
|
||||
let normal_trees = std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
|
||||
.filter_map(move |(pos, seed)| {
|
||||
let chunk = self.get_wpos(pos)?;
|
||||
let env = Environment {
|
||||
humid: chunk.humidity,
|
||||
temp: chunk.temp,
|
||||
near_water: if chunk.river.is_lake() || chunk.river.near_river() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
};
|
||||
Some(TreeAttr {
|
||||
pos,
|
||||
seed,
|
||||
scale: 1.0,
|
||||
forest_kind: *Lottery::from(
|
||||
ForestKind::into_enum_iter()
|
||||
.enumerate()
|
||||
.map(|(i, fk)| {
|
||||
const CLUSTER_SIZE: f64 = 48.0;
|
||||
let nz = (FastNoise2d::new(i as u32 * 37)
|
||||
.get(pos.map(|e| e as f64) / CLUSTER_SIZE)
|
||||
+ 1.0)
|
||||
/ 2.0;
|
||||
(fk.proclivity(&env) * nz, Some(fk))
|
||||
})
|
||||
.chain(std::iter::once((0.001, None)))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.choose_seeded(seed)
|
||||
.as_ref()?,
|
||||
inhabited: false,
|
||||
})
|
||||
});
|
||||
|
||||
// // For testing
|
||||
// let giant_trees =
|
||||
// std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos))
|
||||
// // Don't even consider trees if we aren't close
|
||||
// .filter(move |(pos, _)| pos.distance_squared(wpos) < 512i32.pow(2))
|
||||
// .map(move |(pos, seed)| TreeAttr {
|
||||
// pos,
|
||||
// seed,
|
||||
// scale: 5.0,
|
||||
// forest_kind: ForestKind::Giant,
|
||||
// inhabited: (seed / 13) % 2 == 0,
|
||||
// });
|
||||
|
||||
normal_trees //.chain(giant_trees)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2016,7 +2116,6 @@ pub struct SimChunk {
|
||||
pub forest_kind: ForestKind,
|
||||
pub spawn_rate: f32,
|
||||
pub river: RiverData,
|
||||
pub warp_factor: f32,
|
||||
pub surface_veg: f32,
|
||||
|
||||
pub sites: Vec<Id<Site>>,
|
||||
@ -2024,6 +2123,7 @@ pub struct SimChunk {
|
||||
|
||||
pub path: (Way, Path),
|
||||
pub cave: (Way, Cave),
|
||||
pub cliff_height: f32,
|
||||
|
||||
pub contains_waypoint: bool,
|
||||
}
|
||||
@ -2232,91 +2332,30 @@ impl SimChunk {
|
||||
},
|
||||
tree_density,
|
||||
forest_kind: {
|
||||
// Whittaker diagram
|
||||
let candidates = [
|
||||
// A smaller prevalence means that the range of values this tree appears in
|
||||
// will shrink compared to neighbouring trees in the
|
||||
// topology of the Whittaker diagram.
|
||||
// Humidity, temperature, near_water, each with prevalence
|
||||
(
|
||||
ForestKind::Palm,
|
||||
(CONFIG.desert_hum, 1.5),
|
||||
((CONFIG.tropical_temp + CONFIG.desert_temp) / 2.0, 1.25),
|
||||
(1.0, 2.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Acacia,
|
||||
(0.0, 1.5),
|
||||
(CONFIG.tropical_temp, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Baobab,
|
||||
(0.0, 1.5),
|
||||
(CONFIG.tropical_temp, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Oak,
|
||||
(CONFIG.forest_hum, 1.5),
|
||||
(0.0, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Mangrove,
|
||||
(CONFIG.jungle_hum, 0.5),
|
||||
(CONFIG.tropical_temp, 0.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Pine,
|
||||
(CONFIG.forest_hum, 1.25),
|
||||
(CONFIG.snow_temp, 2.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Birch,
|
||||
(CONFIG.desert_hum, 1.5),
|
||||
(CONFIG.temperate_temp, 1.5),
|
||||
(0.0, 1.0),
|
||||
),
|
||||
(
|
||||
ForestKind::Swamp,
|
||||
((CONFIG.forest_hum + CONFIG.jungle_hum) / 2.0, 2.0),
|
||||
((CONFIG.temperate_temp + CONFIG.snow_temp) / 2.0, 2.0),
|
||||
(1.0, 2.5),
|
||||
),
|
||||
];
|
||||
let env = Environment {
|
||||
humid: humidity,
|
||||
temp,
|
||||
near_water: if river.is_lake() || river.near_river() {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
};
|
||||
|
||||
candidates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by_key(|(i, (_, (h, h_prev), (t, t_prev), (w, w_prev)))| {
|
||||
let rand = RandomPerm::new(*i as u32 * 1000);
|
||||
let noise =
|
||||
Vec3::iota().map(|e| (rand.get(e) & 0xFF) as f32 / 255.0 - 0.5) * 2.0;
|
||||
(Vec3::new(
|
||||
(*h - humidity) / *h_prev,
|
||||
(*t - temp) / *t_prev,
|
||||
(*w - if river.near_water() { 1.0 } else { 0.0 }) / *w_prev,
|
||||
)
|
||||
.add(noise * 0.1)
|
||||
.map(|e| e * e)
|
||||
.sum()
|
||||
* 10000.0) as i32
|
||||
})
|
||||
.map(|(_, c)| c.0)
|
||||
ForestKind::into_enum_iter()
|
||||
.max_by_key(|fk| (fk.proclivity(&env) * 10000.0) as u32)
|
||||
.unwrap() // Can't fail
|
||||
},
|
||||
spawn_rate: 1.0,
|
||||
river,
|
||||
warp_factor: 1.0,
|
||||
surface_veg: 1.0,
|
||||
|
||||
sites: Vec::new(),
|
||||
place: None,
|
||||
path: Default::default(),
|
||||
cave: Default::default(),
|
||||
cliff_height: 0.0,
|
||||
|
||||
contains_waypoint: false,
|
||||
}
|
||||
}
|
||||
@ -2348,4 +2387,6 @@ impl SimChunk {
|
||||
BiomeKind::Grassland
|
||||
}
|
||||
}
|
||||
|
||||
pub fn near_cliffs(&self) -> bool { self.cliff_height > 0.0 }
|
||||
}
|
||||
|
@ -88,7 +88,8 @@ pub fn simulate(index: &mut Index, world: &mut WorldSim) {
|
||||
}
|
||||
|
||||
pub fn tick(index: &mut Index, _world: &mut WorldSim, dt: f32) {
|
||||
for site in index.sites.ids() {
|
||||
let site_ids = index.sites.ids().collect::<Vec<_>>();
|
||||
for site in site_ids {
|
||||
tick_site_economy(index, site, dt);
|
||||
}
|
||||
|
||||
|
@ -4,19 +4,16 @@ mod dungeon;
|
||||
pub mod economy;
|
||||
pub mod namegen;
|
||||
mod settlement;
|
||||
mod tree;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
block_mask::BlockMask, castle::Castle, dungeon::Dungeon, economy::Economy,
|
||||
settlement::Settlement,
|
||||
settlement::Settlement, tree::Tree,
|
||||
};
|
||||
|
||||
use crate::{column::ColumnSample, IndexRef};
|
||||
use common::{
|
||||
generation::ChunkSupplement,
|
||||
terrain::Block,
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
|
||||
};
|
||||
use crate::{column::ColumnSample, site2, Canvas};
|
||||
use common::generation::ChunkSupplement;
|
||||
use rand::Rng;
|
||||
use serde::Deserialize;
|
||||
use vek::*;
|
||||
@ -45,6 +42,8 @@ pub enum SiteKind {
|
||||
Settlement(Settlement),
|
||||
Dungeon(Dungeon),
|
||||
Castle(Castle),
|
||||
Refactor(site2::Site),
|
||||
Tree(tree::Tree),
|
||||
}
|
||||
|
||||
impl Site {
|
||||
@ -69,11 +68,27 @@ impl Site {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refactor(s: site2::Site) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Refactor(s),
|
||||
economy: Economy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tree(t: tree::Tree) -> Self {
|
||||
Self {
|
||||
kind: SiteKind::Tree(t),
|
||||
economy: Economy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn radius(&self) -> f32 {
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.radius(),
|
||||
SiteKind::Dungeon(d) => d.radius(),
|
||||
SiteKind::Castle(c) => c.radius(),
|
||||
SiteKind::Refactor(s) => s.radius(),
|
||||
SiteKind::Tree(t) => t.radius(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +97,8 @@ impl Site {
|
||||
SiteKind::Settlement(s) => s.get_origin(),
|
||||
SiteKind::Dungeon(d) => d.get_origin(),
|
||||
SiteKind::Castle(c) => c.get_origin(),
|
||||
SiteKind::Refactor(s) => s.origin,
|
||||
SiteKind::Tree(t) => t.origin,
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +107,8 @@ impl Site {
|
||||
SiteKind::Settlement(s) => s.spawn_rules(wpos),
|
||||
SiteKind::Dungeon(d) => d.spawn_rules(wpos),
|
||||
SiteKind::Castle(c) => c.spawn_rules(wpos),
|
||||
SiteKind::Refactor(s) => s.spawn_rules(wpos),
|
||||
SiteKind::Tree(t) => t.spawn_rules(wpos),
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,20 +117,20 @@ impl Site {
|
||||
SiteKind::Settlement(s) => s.name(),
|
||||
SiteKind::Dungeon(d) => d.name(),
|
||||
SiteKind::Castle(c) => c.name(),
|
||||
SiteKind::Refactor(_) => "Town",
|
||||
SiteKind::Tree(_) => "Giant Tree",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_to<'a>(
|
||||
&'a self,
|
||||
index: IndexRef,
|
||||
wpos2d: Vec2<i32>,
|
||||
get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
) {
|
||||
pub fn apply_to<'a>(&'a self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
|
||||
let info = canvas.info();
|
||||
let get_col = |wpos| info.col(wpos + info.wpos);
|
||||
match &self.kind {
|
||||
SiteKind::Settlement(s) => s.apply_to(index, wpos2d, get_column, vol),
|
||||
SiteKind::Dungeon(d) => d.apply_to(index, wpos2d, get_column, vol),
|
||||
SiteKind::Castle(c) => c.apply_to(index, wpos2d, get_column, vol),
|
||||
SiteKind::Settlement(s) => s.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Dungeon(d) => d.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Castle(c) => c.apply_to(canvas.index, canvas.wpos, get_col, canvas.chunk),
|
||||
SiteKind::Refactor(s) => s.render(canvas, dynamic_rng),
|
||||
SiteKind::Tree(t) => t.render(canvas, dynamic_rng),
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +148,8 @@ impl Site {
|
||||
},
|
||||
SiteKind::Dungeon(d) => d.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Castle(c) => c.apply_supplement(dynamic_rng, wpos2d, get_column, supplement),
|
||||
SiteKind::Refactor(_) => {},
|
||||
SiteKind::Tree(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
89
world/src/site/tree.rs
Normal file
89
world/src/site/tree.rs
Normal file
@ -0,0 +1,89 @@
|
||||
use crate::{
|
||||
layer::tree::{ProceduralTree, TreeConfig},
|
||||
site::SpawnRules,
|
||||
util::{FastNoise, Sampler},
|
||||
Canvas, Land,
|
||||
};
|
||||
use common::terrain::{Block, BlockKind};
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
// Temporary, do trees through the new site system later
|
||||
pub struct Tree {
|
||||
pub origin: Vec2<i32>,
|
||||
alt: i32,
|
||||
seed: u32,
|
||||
tree: ProceduralTree,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
pub fn generate(origin: Vec2<i32>, land: &Land, rng: &mut impl Rng) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
alt: land.get_alt_approx(origin) as i32,
|
||||
seed: rng.gen(),
|
||||
tree: {
|
||||
let config = TreeConfig::giant(rng, 4.0, false);
|
||||
ProceduralTree::generate(config, rng)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn radius(&self) -> f32 { 512.0 }
|
||||
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
let trunk_radius = 48i32;
|
||||
SpawnRules {
|
||||
trees: wpos.distance_squared(self.origin) > trunk_radius.pow(2),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
|
||||
let nz = FastNoise::new(self.seed);
|
||||
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
let rpos2d = wpos2d - self.origin;
|
||||
let bounds = self.tree.get_bounds().map(|e| e as i32);
|
||||
|
||||
if !Aabr::from(bounds).contains_point(rpos2d) {
|
||||
// Skip this column
|
||||
return;
|
||||
}
|
||||
|
||||
let mut above = true;
|
||||
for z in (bounds.min.z..bounds.max.z + 1).rev() {
|
||||
let wpos = wpos2d.with_z(self.alt + z);
|
||||
let rposf = (wpos - self.origin.with_z(self.alt)).map(|e| e as f32 + 0.5);
|
||||
|
||||
let (branch, leaves, _, _) = self.tree.is_branch_or_leaves_at(rposf);
|
||||
|
||||
if branch || leaves {
|
||||
if above && col.snow_cover {
|
||||
canvas.set(
|
||||
wpos + Vec3::unit_z(),
|
||||
Block::new(BlockKind::Snow, Rgb::new(255, 255, 255)),
|
||||
);
|
||||
}
|
||||
|
||||
if leaves {
|
||||
let dark = Rgb::new(10, 70, 50).map(|e| e as f32);
|
||||
let light = Rgb::new(80, 140, 10).map(|e| e as f32);
|
||||
let leaf_col = Lerp::lerp(
|
||||
dark,
|
||||
light,
|
||||
nz.get(rposf.map(|e| e as f64) * 0.05) * 0.5 + 0.5,
|
||||
);
|
||||
canvas.set(
|
||||
wpos,
|
||||
Block::new(BlockKind::Leaves, leaf_col.map(|e| e as u8)),
|
||||
);
|
||||
} else if branch {
|
||||
canvas.set(wpos, Block::new(BlockKind::Wood, Rgb::new(80, 32, 0)));
|
||||
}
|
||||
|
||||
above = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
135
world/src/site2/gen.rs
Normal file
135
world/src/site2/gen.rs
Normal file
@ -0,0 +1,135 @@
|
||||
use super::*;
|
||||
use crate::util::{RandomField, Sampler};
|
||||
use common::{
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind},
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
pub enum Primitive {
|
||||
Empty, // Placeholder
|
||||
|
||||
// Shapes
|
||||
Aabb(Aabb<i32>),
|
||||
Pyramid { aabb: Aabb<i32>, inset: i32 },
|
||||
|
||||
// Combinators
|
||||
And(Id<Primitive>, Id<Primitive>),
|
||||
Or(Id<Primitive>, Id<Primitive>),
|
||||
Xor(Id<Primitive>, Id<Primitive>),
|
||||
}
|
||||
|
||||
pub enum Fill {
|
||||
Block(Block),
|
||||
Brick(BlockKind, Rgb<u8>, u8),
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
fn contains_at(&self, tree: &Store<Primitive>, prim: Id<Primitive>, pos: Vec3<i32>) -> bool {
|
||||
// Custom closure because vek's impl of `contains_point` is inclusive :(
|
||||
let aabb_contains = |aabb: Aabb<i32>, pos: Vec3<i32>| {
|
||||
(aabb.min.x..aabb.max.x).contains(&pos.x)
|
||||
&& (aabb.min.y..aabb.max.y).contains(&pos.y)
|
||||
&& (aabb.min.z..aabb.max.z).contains(&pos.z)
|
||||
};
|
||||
|
||||
match &tree[prim] {
|
||||
Primitive::Empty => false,
|
||||
|
||||
Primitive::Aabb(aabb) => aabb_contains(*aabb, pos),
|
||||
Primitive::Pyramid { aabb, inset } => {
|
||||
let inset = (*inset).max(aabb.size().reduce_min());
|
||||
let inner = Aabr {
|
||||
min: aabb.min.xy() - 1 + inset,
|
||||
max: aabb.max.xy() - inset,
|
||||
};
|
||||
aabb_contains(*aabb, pos)
|
||||
&& (inner.projected_point(pos.xy()) - pos.xy())
|
||||
.map(|e| e.abs())
|
||||
.reduce_max() as f32
|
||||
/ (inset as f32)
|
||||
< 1.0
|
||||
- ((pos.z - aabb.min.z) as f32 + 0.5) / (aabb.max.z - aabb.min.z) as f32
|
||||
},
|
||||
|
||||
Primitive::And(a, b) => {
|
||||
self.contains_at(tree, *a, pos) && self.contains_at(tree, *b, pos)
|
||||
},
|
||||
Primitive::Or(a, b) => {
|
||||
self.contains_at(tree, *a, pos) || self.contains_at(tree, *b, pos)
|
||||
},
|
||||
Primitive::Xor(a, b) => {
|
||||
self.contains_at(tree, *a, pos) ^ self.contains_at(tree, *b, pos)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_at(
|
||||
&self,
|
||||
tree: &Store<Primitive>,
|
||||
prim: Id<Primitive>,
|
||||
pos: Vec3<i32>,
|
||||
) -> Option<Block> {
|
||||
if self.contains_at(tree, prim, pos) {
|
||||
match self {
|
||||
Fill::Block(block) => Some(*block),
|
||||
Fill::Brick(bk, col, range) => Some(Block::new(
|
||||
*bk,
|
||||
*col + (RandomField::new(13)
|
||||
.get((pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1))
|
||||
% *range as u32) as u8,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bounds_inner(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Option<Aabb<i32>> {
|
||||
fn or_zip_with<T, F: FnOnce(T, T) -> T>(a: Option<T>, b: Option<T>, f: F) -> Option<T> {
|
||||
match (a, b) {
|
||||
(Some(a), Some(b)) => Some(f(a, b)),
|
||||
(Some(a), _) => Some(a),
|
||||
(_, b) => b,
|
||||
}
|
||||
}
|
||||
|
||||
Some(match &tree[prim] {
|
||||
Primitive::Empty => return None,
|
||||
Primitive::Aabb(aabb) => *aabb,
|
||||
Primitive::Pyramid { aabb, .. } => *aabb,
|
||||
Primitive::And(a, b) => or_zip_with(
|
||||
self.get_bounds_inner(tree, *a),
|
||||
self.get_bounds_inner(tree, *b),
|
||||
|a, b| a.intersection(b),
|
||||
)?,
|
||||
Primitive::Or(a, b) | Primitive::Xor(a, b) => or_zip_with(
|
||||
self.get_bounds_inner(tree, *a),
|
||||
self.get_bounds_inner(tree, *b),
|
||||
|a, b| a.union(b),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_bounds(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Aabb<i32> {
|
||||
self.get_bounds_inner(tree, prim)
|
||||
.unwrap_or_else(|| Aabb::new_empty(Vec3::zero()))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Structure {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
|
||||
&self,
|
||||
site: &Site,
|
||||
prim: F,
|
||||
fill: G,
|
||||
);
|
||||
|
||||
// Generate a primitive tree and fills for this structure
|
||||
fn render_collect(&self, site: &Site) -> (Store<Primitive>, Vec<(Id<Primitive>, Fill)>) {
|
||||
let mut tree = Store::default();
|
||||
let mut fills = Vec::new();
|
||||
self.render(site, |p| tree.insert(p), |p, f| fills.push((p, f)));
|
||||
(tree, fills)
|
||||
}
|
||||
}
|
708
world/src/site2/mod.rs
Normal file
708
world/src/site2/mod.rs
Normal file
@ -0,0 +1,708 @@
|
||||
mod gen;
|
||||
mod plot;
|
||||
mod tile;
|
||||
|
||||
use self::{
|
||||
gen::{Fill, Primitive, Structure},
|
||||
plot::{Plot, PlotKind},
|
||||
tile::{HazardKind, Ori, Tile, TileGrid, TileKind, TILE_SIZE},
|
||||
};
|
||||
use crate::{
|
||||
site::SpawnRules,
|
||||
util::{attempt, DHashSet, Grid, CARDINALS, SQUARE_4, SQUARE_9},
|
||||
Canvas, Land,
|
||||
};
|
||||
use common::{
|
||||
astar::Astar,
|
||||
lottery::Lottery,
|
||||
spiral::Spiral2d,
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use hashbrown::hash_map::DefaultHashBuilder;
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaChaRng;
|
||||
use std::ops::Range;
|
||||
use vek::*;
|
||||
|
||||
/// Seed a new RNG from an old RNG, thereby making the old RNG indepedent of
|
||||
/// changing use of the new RNG. The practical effect of this is to reduce the
|
||||
/// extent to which changes to child generation algorithm produce a 'butterfly
|
||||
/// effect' on their parent generators, meaning that generators will be less
|
||||
/// likely to produce entirely different outcomes if some detail of a generation
|
||||
/// algorithm changes slightly. This is generally good and makes worldgen code
|
||||
/// easier to maintain and less liable to breaking changes.
|
||||
fn reseed(rng: &mut impl Rng) -> impl Rng { ChaChaRng::from_seed(rng.gen::<[u8; 32]>()) }
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Site {
|
||||
pub(crate) origin: Vec2<i32>,
|
||||
tiles: TileGrid,
|
||||
plots: Store<Plot>,
|
||||
plazas: Vec<Id<Plot>>,
|
||||
roads: Vec<Id<Plot>>,
|
||||
}
|
||||
|
||||
impl Site {
|
||||
pub fn radius(&self) -> f32 {
|
||||
((self
|
||||
.tiles
|
||||
.bounds
|
||||
.min
|
||||
.map(|e| e.abs())
|
||||
.reduce_max()
|
||||
.max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max())
|
||||
+ 1)
|
||||
* tile::TILE_SIZE as i32) as f32
|
||||
}
|
||||
|
||||
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
||||
SpawnRules {
|
||||
trees: SQUARE_9.iter().all(|&rpos| {
|
||||
self.wpos_tile(wpos + rpos * tile::TILE_SIZE as i32)
|
||||
.is_empty()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> Aabr<i32> {
|
||||
let border = 1;
|
||||
Aabr {
|
||||
min: self.origin + self.tile_wpos(self.tiles.bounds.min - border),
|
||||
max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + border),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plot(&self, id: Id<Plot>) -> &Plot { &self.plots[id] }
|
||||
|
||||
pub fn plots(&self) -> impl Iterator<Item = &Plot> + '_ { self.plots.values() }
|
||||
|
||||
pub fn create_plot(&mut self, plot: Plot) -> Id<Plot> { self.plots.insert(plot) }
|
||||
|
||||
pub fn blit_aabr(&mut self, aabr: Aabr<i32>, tile: Tile) {
|
||||
for y in 0..aabr.size().h {
|
||||
for x in 0..aabr.size().w {
|
||||
self.tiles.set(aabr.min + Vec2::new(x, y), tile.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_road(
|
||||
&mut self,
|
||||
land: &Land,
|
||||
rng: &mut impl Rng,
|
||||
a: Vec2<i32>,
|
||||
b: Vec2<i32>,
|
||||
w: u16,
|
||||
) -> Option<Id<Plot>> {
|
||||
const MAX_ITERS: usize = 4096;
|
||||
let range = -(w as i32) / 2..w as i32 - (w as i32 + 1) / 2;
|
||||
let heuristic = |tile: &Vec2<i32>| {
|
||||
let mut max_cost = (tile.distance_squared(b) as f32).sqrt();
|
||||
for y in range.clone() {
|
||||
for x in range.clone() {
|
||||
if self.tiles.get(*tile + Vec2::new(x, y)).is_obstacle() {
|
||||
max_cost = max_cost.max(1000.0);
|
||||
} else if !self.tiles.get(*tile + Vec2::new(x, y)).is_empty() {
|
||||
max_cost = max_cost.max(25.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
max_cost
|
||||
};
|
||||
let path = Astar::new(MAX_ITERS, a, &heuristic, DefaultHashBuilder::default())
|
||||
.poll(
|
||||
MAX_ITERS,
|
||||
&heuristic,
|
||||
|tile| {
|
||||
let tile = *tile;
|
||||
CARDINALS.iter().map(move |dir| tile + *dir)
|
||||
},
|
||||
|a, b| {
|
||||
let alt_a = land.get_alt_approx(self.tile_center_wpos(*a));
|
||||
let alt_b = land.get_alt_approx(self.tile_center_wpos(*b));
|
||||
(alt_a - alt_b).abs() / TILE_SIZE as f32
|
||||
},
|
||||
|tile| *tile == b,
|
||||
)
|
||||
.into_path()?;
|
||||
|
||||
let plot = self.create_plot(Plot {
|
||||
kind: PlotKind::Road(path.clone()),
|
||||
root_tile: a,
|
||||
tiles: path.clone().into_iter().collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
|
||||
self.roads.push(plot);
|
||||
|
||||
for (i, &tile) in path.iter().enumerate() {
|
||||
for y in range.clone() {
|
||||
for x in range.clone() {
|
||||
let tile = tile + Vec2::new(x, y);
|
||||
if self.tiles.get(tile).is_empty() {
|
||||
self.tiles.set(tile, Tile {
|
||||
kind: TileKind::Road {
|
||||
a: i.saturating_sub(1) as u16,
|
||||
b: (i + 1).min(path.len() - 1) as u16,
|
||||
w,
|
||||
},
|
||||
plot: Some(plot),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(plot)
|
||||
}
|
||||
|
||||
pub fn find_aabr(
|
||||
&mut self,
|
||||
search_pos: Vec2<i32>,
|
||||
area_range: Range<u32>,
|
||||
min_dims: Extent2<u32>,
|
||||
) -> Option<(Aabr<i32>, Vec2<i32>)> {
|
||||
self.tiles.find_near(search_pos, |center, _| {
|
||||
self.tiles
|
||||
.grow_aabr(center, area_range.clone(), min_dims)
|
||||
.ok()
|
||||
.filter(|aabr| {
|
||||
(aabr.min.x..aabr.max.x)
|
||||
.any(|x| self.tiles.get(Vec2::new(x, aabr.min.y - 1)).is_road())
|
||||
|| (aabr.min.x..aabr.max.x)
|
||||
.any(|x| self.tiles.get(Vec2::new(x, aabr.max.y)).is_road())
|
||||
|| (aabr.min.y..aabr.max.y)
|
||||
.any(|y| self.tiles.get(Vec2::new(aabr.min.x - 1, y)).is_road())
|
||||
|| (aabr.min.y..aabr.max.y)
|
||||
.any(|y| self.tiles.get(Vec2::new(aabr.max.x, y)).is_road())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_roadside_aabr(
|
||||
&mut self,
|
||||
rng: &mut impl Rng,
|
||||
area_range: Range<u32>,
|
||||
min_dims: Extent2<u32>,
|
||||
) -> Option<(Aabr<i32>, Vec2<i32>)> {
|
||||
let dir = Vec2::<f32>::zero()
|
||||
.map(|_| rng.gen_range(-1.0..1.0))
|
||||
.normalized();
|
||||
let search_pos = if rng.gen() {
|
||||
self.plot(*self.plazas.choose(rng)?).root_tile
|
||||
+ (dir * 4.0).map(|e: f32| e.round() as i32)
|
||||
} else if let PlotKind::Road(path) = &self.plot(*self.roads.choose(rng)?).kind {
|
||||
*path.nodes().choose(rng)? + (dir * 1.0).map(|e: f32| e.round() as i32)
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
self.find_aabr(search_pos, area_range, min_dims)
|
||||
}
|
||||
|
||||
pub fn make_plaza(&mut self, land: &Land, rng: &mut impl Rng) -> Id<Plot> {
|
||||
let pos = attempt(32, || {
|
||||
self.plazas
|
||||
.choose(rng)
|
||||
.map(|&p| {
|
||||
self.plot(p).root_tile
|
||||
+ (Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0))
|
||||
.normalized()
|
||||
* 24.0)
|
||||
.map(|e| e as i32)
|
||||
})
|
||||
.filter(|tile| !self.tiles.get(*tile).is_obstacle())
|
||||
.filter(|&tile| {
|
||||
self.plazas
|
||||
.iter()
|
||||
.all(|&p| self.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2))
|
||||
&& rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(Vec2::zero);
|
||||
|
||||
let aabr = Aabr {
|
||||
min: pos + Vec2::broadcast(-3),
|
||||
max: pos + Vec2::broadcast(4),
|
||||
};
|
||||
let plaza = self.create_plot(Plot {
|
||||
kind: PlotKind::Plaza,
|
||||
root_tile: pos,
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
self.plazas.push(plaza);
|
||||
self.blit_aabr(aabr, Tile {
|
||||
kind: TileKind::Plaza,
|
||||
plot: Some(plaza),
|
||||
});
|
||||
|
||||
let mut already_pathed = vec![plaza];
|
||||
// One major, one minor road
|
||||
for width in (1..=2).rev() {
|
||||
if let Some(&p) = self
|
||||
.plazas
|
||||
.iter()
|
||||
.filter(|p| !already_pathed.contains(p))
|
||||
.min_by_key(|&&p| self.plot(p).root_tile.distance_squared(pos))
|
||||
{
|
||||
self.create_road(land, rng, self.plot(p).root_tile, pos, width);
|
||||
already_pathed.push(p);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
plaza
|
||||
}
|
||||
|
||||
pub fn demarcate_obstacles(&mut self, land: &Land) {
|
||||
const SEARCH_RADIUS: u32 = 96;
|
||||
|
||||
Spiral2d::new()
|
||||
.take((SEARCH_RADIUS * 2 + 1).pow(2) as usize)
|
||||
.for_each(|tile| {
|
||||
if let Some(kind) = wpos_is_hazard(land, self.tile_wpos(tile)) {
|
||||
for &rpos in &SQUARE_4 {
|
||||
// `get_mut` doesn't increase generation bounds
|
||||
self.tiles
|
||||
.get_mut(tile - rpos - 1)
|
||||
.map(|tile| tile.kind = TileKind::Hazard(kind));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
|
||||
let mut rng = reseed(rng);
|
||||
|
||||
let mut site = Site {
|
||||
origin,
|
||||
..Site::default()
|
||||
};
|
||||
|
||||
site.demarcate_obstacles(land);
|
||||
|
||||
site.make_plaza(land, &mut rng);
|
||||
|
||||
let build_chance = Lottery::from(vec![(64.0, 1), (5.0, 2), (8.0, 3), (0.75, 4)]);
|
||||
|
||||
let mut castles = 0;
|
||||
|
||||
for _ in 0..120 {
|
||||
match *build_chance.choose_seeded(rng.gen()) {
|
||||
// House
|
||||
1 => {
|
||||
let size = (2.0 + rng.gen::<f32>().powf(8.0) * 3.0).round() as u32;
|
||||
if let Some((aabr, door_tile)) = attempt(32, || {
|
||||
site.find_roadside_aabr(
|
||||
&mut rng,
|
||||
4..(size + 1).pow(2),
|
||||
Extent2::broadcast(size),
|
||||
)
|
||||
}) {
|
||||
let plot = site.create_plot(Plot {
|
||||
kind: PlotKind::House(plot::House::generate(
|
||||
land,
|
||||
&mut reseed(&mut rng),
|
||||
&site,
|
||||
door_tile,
|
||||
aabr,
|
||||
)),
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
|
||||
site.blit_aabr(aabr, Tile {
|
||||
kind: TileKind::Building,
|
||||
plot: Some(plot),
|
||||
});
|
||||
} else {
|
||||
site.make_plaza(land, &mut rng);
|
||||
}
|
||||
},
|
||||
// Guard tower
|
||||
2 => {
|
||||
if let Some((aabr, entrance_tile)) = attempt(10, || {
|
||||
site.find_roadside_aabr(&mut rng, 4..4, Extent2::new(2, 2))
|
||||
}) {
|
||||
let plot = site.create_plot(Plot {
|
||||
kind: PlotKind::Castle(plot::Castle::generate(
|
||||
land,
|
||||
&mut rng,
|
||||
&site,
|
||||
entrance_tile,
|
||||
aabr,
|
||||
)),
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
|
||||
site.blit_aabr(aabr, Tile {
|
||||
kind: TileKind::Castle,
|
||||
plot: Some(plot),
|
||||
});
|
||||
}
|
||||
},
|
||||
// Field
|
||||
3 => {
|
||||
attempt(10, || {
|
||||
let search_pos = attempt(16, || {
|
||||
let tile =
|
||||
(Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0))
|
||||
.normalized()
|
||||
* rng.gen_range(16.0..48.0))
|
||||
.map(|e| e as i32);
|
||||
|
||||
Some(tile).filter(|_| {
|
||||
site.plazas.iter().all(|&p| {
|
||||
site.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2)
|
||||
}) && rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max()
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(Vec2::zero);
|
||||
|
||||
site.tiles.find_near(search_pos, |center, _| {
|
||||
site.tiles.grow_organic(&mut rng, center, 12..64).ok()
|
||||
})
|
||||
})
|
||||
.map(|(tiles, _)| {
|
||||
for tile in tiles {
|
||||
site.tiles.set(tile, Tile {
|
||||
kind: TileKind::Field,
|
||||
plot: None,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
// Castle
|
||||
4 if castles < 1 => {
|
||||
if let Some((aabr, entrance_tile)) = attempt(10, || {
|
||||
site.find_roadside_aabr(&mut rng, 16 * 16..18 * 18, Extent2::new(16, 16))
|
||||
}) {
|
||||
let plot = site.create_plot(Plot {
|
||||
kind: PlotKind::Castle(plot::Castle::generate(
|
||||
land,
|
||||
&mut rng,
|
||||
&site,
|
||||
entrance_tile,
|
||||
aabr,
|
||||
)),
|
||||
root_tile: aabr.center(),
|
||||
tiles: aabr_tiles(aabr).collect(),
|
||||
seed: rng.gen(),
|
||||
});
|
||||
|
||||
// Walls
|
||||
site.blit_aabr(aabr, Tile {
|
||||
kind: TileKind::Wall(Ori::North),
|
||||
plot: Some(plot),
|
||||
});
|
||||
|
||||
let tower = Tile {
|
||||
kind: TileKind::Tower,
|
||||
plot: Some(plot),
|
||||
};
|
||||
site.tiles
|
||||
.set(Vec2::new(aabr.min.x, aabr.min.y), tower.clone());
|
||||
site.tiles
|
||||
.set(Vec2::new(aabr.max.x - 1, aabr.min.y), tower.clone());
|
||||
site.tiles
|
||||
.set(Vec2::new(aabr.min.x, aabr.max.y - 1), tower.clone());
|
||||
site.tiles
|
||||
.set(Vec2::new(aabr.max.x - 1, aabr.max.y - 1), tower.clone());
|
||||
|
||||
// Courtyard
|
||||
site.blit_aabr(
|
||||
Aabr {
|
||||
min: aabr.min + 1,
|
||||
max: aabr.max - 1,
|
||||
},
|
||||
Tile {
|
||||
kind: TileKind::Road { a: 0, b: 0, w: 0 },
|
||||
plot: Some(plot),
|
||||
},
|
||||
);
|
||||
|
||||
// Keep
|
||||
site.blit_aabr(
|
||||
Aabr {
|
||||
min: aabr.center() - 3,
|
||||
max: aabr.center() + 3,
|
||||
},
|
||||
Tile {
|
||||
kind: TileKind::Wall(Ori::North),
|
||||
plot: Some(plot),
|
||||
},
|
||||
);
|
||||
site.tiles.set(
|
||||
Vec2::new(aabr.center().x + 2, aabr.center().y + 2),
|
||||
tower.clone(),
|
||||
);
|
||||
site.tiles.set(
|
||||
Vec2::new(aabr.center().x + 2, aabr.center().y - 3),
|
||||
tower.clone(),
|
||||
);
|
||||
site.tiles.set(
|
||||
Vec2::new(aabr.center().x - 3, aabr.center().y + 2),
|
||||
tower.clone(),
|
||||
);
|
||||
site.tiles.set(
|
||||
Vec2::new(aabr.center().x - 3, aabr.center().y - 3),
|
||||
tower.clone(),
|
||||
);
|
||||
|
||||
site.blit_aabr(
|
||||
Aabr {
|
||||
min: aabr.center() - 2,
|
||||
max: aabr.center() + 2,
|
||||
},
|
||||
Tile {
|
||||
kind: TileKind::Keep(tile::KeepKind::Middle),
|
||||
plot: Some(plot),
|
||||
},
|
||||
);
|
||||
|
||||
castles += 1;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
site
|
||||
}
|
||||
|
||||
pub fn wpos_tile_pos(&self, wpos2d: Vec2<i32>) -> Vec2<i32> {
|
||||
(wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))
|
||||
}
|
||||
|
||||
pub fn wpos_tile(&self, wpos2d: Vec2<i32>) -> &Tile {
|
||||
self.tiles.get(self.wpos_tile_pos(wpos2d))
|
||||
}
|
||||
|
||||
pub fn tile_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> {
|
||||
self.origin + tile * tile::TILE_SIZE as i32
|
||||
}
|
||||
|
||||
pub fn tile_center_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> {
|
||||
self.origin + tile * tile::TILE_SIZE as i32 + tile::TILE_SIZE as i32 / 2
|
||||
}
|
||||
|
||||
pub fn render_tile(&self, canvas: &mut Canvas, _dynamic_rng: &mut impl Rng, tpos: Vec2<i32>) {
|
||||
let tile = self.tiles.get(tpos);
|
||||
let twpos = self.tile_wpos(tpos);
|
||||
let border = TILE_SIZE as i32;
|
||||
let cols = (-border..TILE_SIZE as i32 + border)
|
||||
.map(|y| {
|
||||
(-border..TILE_SIZE as i32 + border)
|
||||
.map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))
|
||||
})
|
||||
.flatten();
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match &tile.kind {
|
||||
TileKind::Plaza => {
|
||||
let near_roads = CARDINALS.iter().filter_map(|rpos| {
|
||||
if self.tiles.get(tpos + rpos) == tile {
|
||||
Some(Aabr {
|
||||
min: self.tile_wpos(tpos).map(|e| e as f32),
|
||||
max: self.tile_wpos(tpos + 1).map(|e| e as f32),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
cols.for_each(|(wpos2d, _offs)| {
|
||||
let wpos2df = wpos2d.map(|e| e as f32);
|
||||
let dist = near_roads
|
||||
.clone()
|
||||
.map(|aabr| aabr.distance_to_point(wpos2df))
|
||||
.min_by_key(|d| (*d * 100.0) as i32);
|
||||
|
||||
if dist.map_or(false, |d| d <= 1.5) {
|
||||
let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32);
|
||||
(-8..6).for_each(|z| {
|
||||
canvas.map(Vec3::new(wpos2d.x, wpos2d.y, alt + z), |b| {
|
||||
if z >= 0 {
|
||||
if b.is_filled() {
|
||||
Block::empty()
|
||||
} else {
|
||||
b.with_sprite(SpriteKind::Empty)
|
||||
}
|
||||
} else {
|
||||
Block::new(BlockKind::Rock, Rgb::new(55, 45, 50))
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
|
||||
canvas.foreach_col(|canvas, wpos2d, col| {
|
||||
|
||||
let tpos = self.wpos_tile_pos(wpos2d);
|
||||
let near_roads = SQUARE_9
|
||||
.iter()
|
||||
.filter_map(|rpos| {
|
||||
let tile = self.tiles.get(tpos + rpos);
|
||||
if let TileKind::Road { a, b, w } = &tile.kind {
|
||||
if let Some(PlotKind::Road(path)) = tile.plot.map(|p| &self.plot(p).kind) {
|
||||
Some((LineSegment2 {
|
||||
start: self.tile_center_wpos(path.nodes()[*a as usize]).map(|e| e as f32),
|
||||
end: self.tile_center_wpos(path.nodes()[*b as usize]).map(|e| e as f32),
|
||||
}, *w))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let wpos2df = wpos2d.map(|e| e as f32);
|
||||
let dist = near_roads
|
||||
.map(|(line, w)| (line.distance_to_point(wpos2df) - w as f32 * 2.0).max(0.0))
|
||||
.min_by_key(|d| (*d * 100.0) as i32);
|
||||
|
||||
if dist.map_or(false, |d| d <= 0.75) {
|
||||
let alt = canvas.col(wpos2d).map_or(0, |col| col.alt as i32);
|
||||
(-6..4).for_each(|z| canvas.map(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, alt + z),
|
||||
|b| if z >= 0 {
|
||||
if b.is_filled() {
|
||||
Block::empty()
|
||||
} else {
|
||||
b.with_sprite(SpriteKind::Empty)
|
||||
}
|
||||
} else {
|
||||
Block::new(BlockKind::Rock, Rgb::new(55, 45, 50))
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
let tile = self.wpos_tile(wpos2d);
|
||||
let seed = tile.plot.map_or(0, |p| self.plot(p).seed);
|
||||
#[allow(clippy::single_match)]
|
||||
match tile.kind {
|
||||
TileKind::Field /*| TileKind::Road*/ => (-4..5).for_each(|z| canvas.map(
|
||||
Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z),
|
||||
|b| if [
|
||||
BlockKind::Grass,
|
||||
BlockKind::Earth,
|
||||
BlockKind::Sand,
|
||||
BlockKind::Snow,
|
||||
BlockKind::Rock,
|
||||
]
|
||||
.contains(&b.kind()) {
|
||||
match tile.kind {
|
||||
TileKind::Field => Block::new(BlockKind::Earth, Rgb::new(40, 5 + (seed % 32) as u8, 0)),
|
||||
TileKind::Road { .. } => Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
b.with_sprite(SpriteKind::Empty)
|
||||
},
|
||||
)),
|
||||
// TileKind::Building => {
|
||||
// let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt);
|
||||
// for z in base_alt - 12..base_alt + 16 {
|
||||
// canvas.set(
|
||||
// Vec3::new(wpos2d.x, wpos2d.y, z),
|
||||
// Block::new(BlockKind::Wood, Rgb::new(180, 90 + (seed % 64) as u8, 120))
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// TileKind::Castle | TileKind::Wall => {
|
||||
// let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt);
|
||||
// for z in base_alt - 12..base_alt + if tile.kind == TileKind::Wall { 24 } else { 40 } {
|
||||
// canvas.set(
|
||||
// Vec3::new(wpos2d.x, wpos2d.y, z),
|
||||
// Block::new(BlockKind::Wood, Rgb::new(40, 40, 55))
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
_ => {},
|
||||
}
|
||||
});
|
||||
|
||||
let tile_aabr = Aabr {
|
||||
min: self.wpos_tile_pos(canvas.wpos()) - 1,
|
||||
max: self
|
||||
.wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2)
|
||||
+ 3, // Round up, uninclusive, border
|
||||
};
|
||||
|
||||
// Don't double-generate the same plot per chunk!
|
||||
let mut plots = DHashSet::default();
|
||||
|
||||
for y in tile_aabr.min.y..tile_aabr.max.y {
|
||||
for x in tile_aabr.min.x..tile_aabr.max.x {
|
||||
self.render_tile(canvas, dynamic_rng, Vec2::new(x, y));
|
||||
|
||||
if let Some(plot) = self.tiles.get(Vec2::new(x, y)).plot {
|
||||
plots.insert(plot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut plots_to_render = plots.into_iter().collect::<Vec<_>>();
|
||||
plots_to_render.sort_unstable();
|
||||
|
||||
for plot in plots_to_render {
|
||||
let (prim_tree, fills) = match &self.plots[plot].kind {
|
||||
PlotKind::House(house) => house.render_collect(self),
|
||||
PlotKind::Castle(castle) => castle.render_collect(self),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
for (prim, fill) in fills {
|
||||
let aabb = fill.get_bounds(&prim_tree, prim);
|
||||
|
||||
for x in aabb.min.x..aabb.max.x {
|
||||
for y in aabb.min.y..aabb.max.y {
|
||||
for z in aabb.min.z..aabb.max.z {
|
||||
let pos = Vec3::new(x, y, z);
|
||||
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos) {
|
||||
canvas.set(pos, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_site() -> Site { Site::generate(&Land::empty(), &mut thread_rng(), Vec2::zero()) }
|
||||
|
||||
fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {
|
||||
if land
|
||||
.get_chunk_at(wpos)
|
||||
.map_or(true, |c| c.river.near_water())
|
||||
{
|
||||
Some(HazardKind::Water)
|
||||
} else if let Some(gradient) = Some(land.get_gradient_approx(wpos)).filter(|g| *g > 0.8) {
|
||||
Some(HazardKind::Hill { gradient })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn aabr_tiles(aabr: Aabr<i32>) -> impl Iterator<Item = Vec2<i32>> {
|
||||
(0..aabr.size().h)
|
||||
.map(move |y| (0..aabr.size().w).map(move |x| aabr.min + Vec2::new(x, y)))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub struct Plaza {}
|
33
world/src/site2/plot.rs
Normal file
33
world/src/site2/plot.rs
Normal file
@ -0,0 +1,33 @@
|
||||
mod castle;
|
||||
mod house;
|
||||
|
||||
pub use self::{castle::Castle, house::House};
|
||||
|
||||
use super::*;
|
||||
use crate::util::DHashSet;
|
||||
use common::path::Path;
|
||||
use vek::*;
|
||||
|
||||
pub struct Plot {
|
||||
pub(crate) kind: PlotKind,
|
||||
pub(crate) root_tile: Vec2<i32>,
|
||||
pub(crate) tiles: DHashSet<Vec2<i32>>,
|
||||
pub(crate) seed: u32,
|
||||
}
|
||||
|
||||
impl Plot {
|
||||
pub fn find_bounds(&self) -> Aabr<i32> {
|
||||
self.tiles
|
||||
.iter()
|
||||
.fold(Aabr::new_empty(self.root_tile), |b, t| {
|
||||
b.expanded_to_contain_point(*t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PlotKind {
|
||||
House(House),
|
||||
Plaza,
|
||||
Castle(Castle),
|
||||
Road(Path<Vec2<i32>>),
|
||||
}
|
226
world/src/site2/plot/castle.rs
Normal file
226
world/src/site2/plot/castle.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use super::*;
|
||||
use crate::Land;
|
||||
use common::terrain::{Block, BlockKind};
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub struct Castle {
|
||||
_entrance_tile: Vec2<i32>,
|
||||
tile_aabr: Aabr<i32>,
|
||||
_bounds: Aabr<i32>,
|
||||
alt: i32,
|
||||
}
|
||||
|
||||
impl Castle {
|
||||
pub fn generate(
|
||||
land: &Land,
|
||||
_rng: &mut impl Rng,
|
||||
site: &Site,
|
||||
entrance_tile: Vec2<i32>,
|
||||
tile_aabr: Aabr<i32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
_entrance_tile: entrance_tile,
|
||||
tile_aabr,
|
||||
_bounds: Aabr {
|
||||
min: site.tile_wpos(tile_aabr.min),
|
||||
max: site.tile_wpos(tile_aabr.max),
|
||||
},
|
||||
alt: land.get_alt_approx(site.tile_center_wpos(entrance_tile)) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Structure for Castle {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
|
||||
&self,
|
||||
site: &Site,
|
||||
mut prim: F,
|
||||
mut fill: G,
|
||||
) {
|
||||
let wall_height = 24;
|
||||
let _thickness = 12;
|
||||
let parapet_height = 2;
|
||||
let parapet_width = 1;
|
||||
let _downwards = 40;
|
||||
|
||||
let tower_height = 12;
|
||||
|
||||
let keep_levels = 3;
|
||||
let keep_level_height = 8;
|
||||
let _keep_height = wall_height + keep_levels * keep_level_height + 1;
|
||||
for x in 0..self.tile_aabr.size().w {
|
||||
for y in 0..self.tile_aabr.size().h {
|
||||
let tile_pos = self.tile_aabr.min + Vec2::new(x, y);
|
||||
let _wpos_center = site.tile_center_wpos(tile_pos);
|
||||
let wpos = site.tile_wpos(tile_pos);
|
||||
let ori = if x == 0 || x == self.tile_aabr.size().w - 1 {
|
||||
Vec2::new(1, 0)
|
||||
} else {
|
||||
Vec2::new(0, 1)
|
||||
};
|
||||
let ori_tower_x = if x == 0 {
|
||||
Vec2::new(1, 0)
|
||||
} else {
|
||||
Vec2::new(0, 0)
|
||||
};
|
||||
let ori_tower_y = if y == 0 {
|
||||
Vec2::new(0, 1)
|
||||
} else {
|
||||
Vec2::new(0, 0)
|
||||
};
|
||||
let ori_tower = ori_tower_x + ori_tower_y;
|
||||
match site.tiles.get(tile_pos).kind.clone() {
|
||||
TileKind::Wall(_ori) => {
|
||||
let wall = prim(Primitive::Aabb(Aabb {
|
||||
min: wpos.with_z(self.alt),
|
||||
max: (wpos + 6).with_z(self.alt + wall_height + parapet_height),
|
||||
}));
|
||||
let cut_path = prim(Primitive::Aabb(Aabb {
|
||||
min: (wpos + (parapet_width * ori) as Vec2<i32>)
|
||||
.with_z(self.alt + wall_height),
|
||||
max: (wpos
|
||||
+ (6 - parapet_width) * ori as Vec2<i32>
|
||||
+ 6 * ori.yx() as Vec2<i32>)
|
||||
.with_z(self.alt + wall_height + parapet_height),
|
||||
}));
|
||||
let cut_sides1 = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(wpos.x, wpos.y, self.alt + wall_height + 1),
|
||||
max: Vec3::new(
|
||||
wpos.x + 6 * ori.x + ori.y,
|
||||
wpos.y + 6 * ori.y + ori.x,
|
||||
self.alt + wall_height + parapet_height,
|
||||
),
|
||||
}));
|
||||
let pillar_start = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(wpos.x, wpos.y - 1, self.alt),
|
||||
max: Vec3::new(wpos.x + 1, wpos.y + 7, self.alt + wall_height),
|
||||
}));
|
||||
let pillar_end = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(wpos.x + 5, wpos.y - 1, self.alt),
|
||||
max: Vec3::new(wpos.x + 6, wpos.y + 7, self.alt + wall_height),
|
||||
}));
|
||||
let pillars = prim(Primitive::Or(pillar_start, pillar_end));
|
||||
fill(
|
||||
prim(Primitive::Or(wall, pillars)),
|
||||
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(33, 33, 33))),
|
||||
);
|
||||
fill(cut_path, Fill::Block(Block::empty()));
|
||||
fill(cut_sides1, Fill::Block(Block::empty()));
|
||||
},
|
||||
TileKind::Tower => {
|
||||
let tower_lower = prim(Primitive::Aabb(Aabb {
|
||||
min: wpos.with_z(self.alt),
|
||||
max: (wpos + 6).with_z(self.alt + wall_height + tower_height),
|
||||
}));
|
||||
let tower_lower_inner_x = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(
|
||||
wpos.x + ori_tower.x,
|
||||
wpos.y + parapet_width,
|
||||
self.alt + wall_height,
|
||||
),
|
||||
max: Vec3::new(
|
||||
wpos.x + 6 + ori_tower.x - 1,
|
||||
wpos.y + 6 - parapet_width,
|
||||
self.alt + wall_height + tower_height / 3,
|
||||
),
|
||||
}));
|
||||
let tower_lower_inner_y = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(
|
||||
wpos.x + parapet_width,
|
||||
wpos.y + ori_tower.y,
|
||||
self.alt + wall_height,
|
||||
),
|
||||
max: Vec3::new(
|
||||
wpos.x + 6 - parapet_width,
|
||||
wpos.y + 6 + ori_tower.y - 1,
|
||||
self.alt + wall_height + tower_height / 3,
|
||||
),
|
||||
}));
|
||||
let tower_lower_inner =
|
||||
prim(Primitive::Or(tower_lower_inner_x, tower_lower_inner_y));
|
||||
fill(
|
||||
prim(Primitive::Xor(tower_lower, tower_lower_inner)),
|
||||
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(33, 33, 33))),
|
||||
);
|
||||
let tower_upper = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(
|
||||
wpos.x - 1,
|
||||
wpos.y - 1,
|
||||
self.alt + wall_height + tower_height - 3i32,
|
||||
),
|
||||
max: Vec3::new(
|
||||
wpos.x + 7,
|
||||
wpos.y + 7,
|
||||
self.alt + wall_height + tower_height - 1i32,
|
||||
),
|
||||
}));
|
||||
let tower_upper2 = prim(Primitive::Aabb(Aabb {
|
||||
min: Vec3::new(
|
||||
wpos.x - 2,
|
||||
wpos.y - 2,
|
||||
self.alt + wall_height + tower_height - 1i32,
|
||||
),
|
||||
max: Vec3::new(
|
||||
wpos.x + 8,
|
||||
wpos.y + 8,
|
||||
self.alt + wall_height + tower_height,
|
||||
),
|
||||
}));
|
||||
|
||||
fill(
|
||||
prim(Primitive::Or(tower_upper, tower_upper2)),
|
||||
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(33, 33, 33))),
|
||||
);
|
||||
|
||||
let roof_lip = 1;
|
||||
let roof_height = 8 / 2 + roof_lip + 1;
|
||||
|
||||
// Roof
|
||||
fill(
|
||||
prim(Primitive::Pyramid {
|
||||
aabb: Aabb {
|
||||
min: (wpos - 2 - roof_lip)
|
||||
.with_z(self.alt + wall_height + tower_height),
|
||||
max: (wpos + 8 + roof_lip).with_z(
|
||||
self.alt + wall_height + tower_height + roof_height,
|
||||
),
|
||||
},
|
||||
inset: roof_height,
|
||||
}),
|
||||
Fill::Block(Block::new(BlockKind::Wood, Rgb::new(116, 20, 20))),
|
||||
);
|
||||
},
|
||||
TileKind::Keep(kind) => {
|
||||
match kind {
|
||||
tile::KeepKind::Middle => {
|
||||
for i in 0..keep_levels + 1 {
|
||||
let height = keep_level_height * i;
|
||||
fill(
|
||||
prim(Primitive::Aabb(Aabb {
|
||||
min: wpos.with_z(self.alt + height),
|
||||
max: (wpos + 6).with_z(self.alt + height + 1),
|
||||
})),
|
||||
Fill::Block(Block::new(
|
||||
BlockKind::Rock,
|
||||
Rgb::new(89, 44, 14),
|
||||
)),
|
||||
);
|
||||
}
|
||||
},
|
||||
tile::KeepKind::Corner => {},
|
||||
tile::KeepKind::Wall(_ori) => {
|
||||
for i in 0..keep_levels + 1 {
|
||||
let _height = keep_level_height * i;
|
||||
// TODO clamp value in case of big heights
|
||||
let _window_height = keep_level_height - 3;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
188
world/src/site2/plot/house.rs
Normal file
188
world/src/site2/plot/house.rs
Normal file
@ -0,0 +1,188 @@
|
||||
use super::*;
|
||||
use crate::Land;
|
||||
use common::terrain::{Block, BlockKind, SpriteKind};
|
||||
use rand::prelude::*;
|
||||
use vek::*;
|
||||
|
||||
pub struct House {
|
||||
_door_tile: Vec2<i32>,
|
||||
tile_aabr: Aabr<i32>,
|
||||
bounds: Aabr<i32>,
|
||||
alt: i32,
|
||||
levels: u32,
|
||||
roof_color: Rgb<u8>,
|
||||
}
|
||||
|
||||
impl House {
|
||||
pub fn generate(
|
||||
land: &Land,
|
||||
rng: &mut impl Rng,
|
||||
site: &Site,
|
||||
door_tile: Vec2<i32>,
|
||||
tile_aabr: Aabr<i32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
_door_tile: door_tile,
|
||||
tile_aabr,
|
||||
bounds: Aabr {
|
||||
min: site.tile_wpos(tile_aabr.min),
|
||||
max: site.tile_wpos(tile_aabr.max),
|
||||
},
|
||||
alt: land.get_alt_approx(site.tile_center_wpos(door_tile)) as i32 + 2,
|
||||
levels: rng.gen_range(1..2 + (tile_aabr.max - tile_aabr.min).product() / 6) as u32,
|
||||
roof_color: {
|
||||
let colors = [
|
||||
Rgb::new(21, 43, 48),
|
||||
Rgb::new(11, 23, 38),
|
||||
Rgb::new(45, 28, 21),
|
||||
Rgb::new(10, 55, 40),
|
||||
Rgb::new(5, 35, 15),
|
||||
Rgb::new(40, 5, 11),
|
||||
Rgb::new(55, 45, 11),
|
||||
];
|
||||
*colors.choose(rng).unwrap()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Structure for House {
|
||||
fn render<F: FnMut(Primitive) -> Id<Primitive>, G: FnMut(Id<Primitive>, Fill)>(
|
||||
&self,
|
||||
site: &Site,
|
||||
mut prim: F,
|
||||
mut fill: G,
|
||||
) {
|
||||
let storey = 5;
|
||||
let roof = storey * self.levels as i32;
|
||||
let foundations = 12;
|
||||
|
||||
// Walls
|
||||
let inner = prim(Primitive::Aabb(Aabb {
|
||||
min: (self.bounds.min + 1).with_z(self.alt),
|
||||
max: self.bounds.max.with_z(self.alt + roof),
|
||||
}));
|
||||
let outer = prim(Primitive::Aabb(Aabb {
|
||||
min: self.bounds.min.with_z(self.alt - foundations),
|
||||
max: (self.bounds.max + 1).with_z(self.alt + roof),
|
||||
}));
|
||||
fill(
|
||||
outer,
|
||||
Fill::Brick(BlockKind::Rock, Rgb::new(80, 75, 85), 24),
|
||||
);
|
||||
fill(inner, Fill::Block(Block::empty()));
|
||||
let walls = prim(Primitive::Xor(outer, inner));
|
||||
|
||||
// wall pillars
|
||||
let mut pillars_y = prim(Primitive::Empty);
|
||||
for x in self.tile_aabr.min.x..self.tile_aabr.max.x + 2 {
|
||||
let pillar = prim(Primitive::Aabb(Aabb {
|
||||
min: site
|
||||
.tile_wpos(Vec2::new(x, self.tile_aabr.min.y))
|
||||
.with_z(self.alt),
|
||||
max: (site.tile_wpos(Vec2::new(x, self.tile_aabr.max.y + 1)) + Vec2::unit_x())
|
||||
.with_z(self.alt + roof),
|
||||
}));
|
||||
pillars_y = prim(Primitive::Or(pillars_y, pillar));
|
||||
}
|
||||
let mut pillars_x = prim(Primitive::Empty);
|
||||
for y in self.tile_aabr.min.y..self.tile_aabr.max.y + 2 {
|
||||
let pillar = prim(Primitive::Aabb(Aabb {
|
||||
min: site
|
||||
.tile_wpos(Vec2::new(self.tile_aabr.min.x, y))
|
||||
.with_z(self.alt),
|
||||
max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x + 1, y)) + Vec2::unit_y())
|
||||
.with_z(self.alt + roof),
|
||||
}));
|
||||
pillars_x = prim(Primitive::Or(pillars_x, pillar));
|
||||
}
|
||||
let pillars = prim(Primitive::And(pillars_x, pillars_y));
|
||||
fill(
|
||||
pillars,
|
||||
Fill::Block(Block::new(BlockKind::Wood, Rgb::new(55, 25, 8))),
|
||||
);
|
||||
|
||||
// For each storey...
|
||||
for i in 0..self.levels + 1 {
|
||||
let height = storey * i as i32;
|
||||
let window_height = storey - 3;
|
||||
|
||||
// Windows x axis
|
||||
{
|
||||
let mut windows = prim(Primitive::Empty);
|
||||
for y in self.tile_aabr.min.y..self.tile_aabr.max.y {
|
||||
let window = prim(Primitive::Aabb(Aabb {
|
||||
min: (site.tile_wpos(Vec2::new(self.tile_aabr.min.x, y))
|
||||
+ Vec2::unit_y() * 2)
|
||||
.with_z(self.alt + height + 2),
|
||||
max: (site.tile_wpos(Vec2::new(self.tile_aabr.max.x, y + 1))
|
||||
+ Vec2::new(1, -1))
|
||||
.with_z(self.alt + height + 2 + window_height),
|
||||
}));
|
||||
windows = prim(Primitive::Or(windows, window));
|
||||
}
|
||||
fill(
|
||||
prim(Primitive::And(walls, windows)),
|
||||
Fill::Block(Block::air(SpriteKind::Window1).with_ori(2).unwrap()),
|
||||
);
|
||||
}
|
||||
// Windows y axis
|
||||
{
|
||||
let mut windows = prim(Primitive::Empty);
|
||||
for x in self.tile_aabr.min.x..self.tile_aabr.max.x {
|
||||
let window = prim(Primitive::Aabb(Aabb {
|
||||
min: (site.tile_wpos(Vec2::new(x, self.tile_aabr.min.y))
|
||||
+ Vec2::unit_x() * 2)
|
||||
.with_z(self.alt + height + 2),
|
||||
max: (site.tile_wpos(Vec2::new(x + 1, self.tile_aabr.max.y))
|
||||
+ Vec2::new(-1, 1))
|
||||
.with_z(self.alt + height + 2 + window_height),
|
||||
}));
|
||||
windows = prim(Primitive::Or(windows, window));
|
||||
}
|
||||
fill(
|
||||
prim(Primitive::And(walls, windows)),
|
||||
Fill::Block(Block::air(SpriteKind::Window1).with_ori(0).unwrap()),
|
||||
);
|
||||
}
|
||||
|
||||
// Floor
|
||||
fill(
|
||||
prim(Primitive::Aabb(Aabb {
|
||||
min: (self.bounds.min + 1).with_z(self.alt + height),
|
||||
max: self.bounds.max.with_z(self.alt + height + 1),
|
||||
})),
|
||||
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(89, 44, 14))),
|
||||
);
|
||||
}
|
||||
|
||||
let roof_lip = 2;
|
||||
let roof_height = (self.bounds.min - self.bounds.max)
|
||||
.map(|e| e.abs())
|
||||
.reduce_min()
|
||||
/ 2
|
||||
+ roof_lip
|
||||
+ 1;
|
||||
|
||||
// Roof
|
||||
fill(
|
||||
prim(Primitive::Pyramid {
|
||||
aabb: Aabb {
|
||||
min: (self.bounds.min - roof_lip).with_z(self.alt + roof),
|
||||
max: (self.bounds.max + 1 + roof_lip).with_z(self.alt + roof + roof_height),
|
||||
},
|
||||
inset: roof_height,
|
||||
}),
|
||||
Fill::Block(Block::new(BlockKind::Wood, self.roof_color)),
|
||||
);
|
||||
|
||||
// Foundations
|
||||
fill(
|
||||
prim(Primitive::Aabb(Aabb {
|
||||
min: (self.bounds.min - 1).with_z(self.alt - foundations),
|
||||
max: (self.bounds.max + 2).with_z(self.alt + 1),
|
||||
})),
|
||||
Fill::Block(Block::new(BlockKind::Rock, Rgb::new(31, 33, 32))),
|
||||
);
|
||||
}
|
||||
}
|
243
world/src/site2/tile.rs
Normal file
243
world/src/site2/tile.rs
Normal file
@ -0,0 +1,243 @@
|
||||
use super::*;
|
||||
use crate::util::DHashSet;
|
||||
use common::spiral::Spiral2d;
|
||||
use std::ops::Range;
|
||||
|
||||
pub const TILE_SIZE: u32 = 6;
|
||||
pub const ZONE_SIZE: u32 = 16;
|
||||
pub const ZONE_RADIUS: u32 = 16;
|
||||
pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS;
|
||||
#[allow(dead_code)]
|
||||
pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS;
|
||||
|
||||
pub struct TileGrid {
|
||||
pub(crate) bounds: Aabr<i32>, // Inclusive
|
||||
zones: Grid<Option<Grid<Option<Tile>>>>,
|
||||
}
|
||||
|
||||
impl Default for TileGrid {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bounds: Aabr::new_empty(Vec2::zero()),
|
||||
zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TileGrid {
|
||||
pub fn get(&self, tpos: Vec2<i32>) -> &Tile {
|
||||
static EMPTY: Tile = Tile::empty();
|
||||
|
||||
let tpos = tpos + TILE_RADIUS as i32;
|
||||
self.zones
|
||||
.get(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
|
||||
.and_then(|zone| {
|
||||
zone.as_ref()?
|
||||
.get(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
|
||||
})
|
||||
.and_then(|tile| tile.as_ref())
|
||||
.unwrap_or(&EMPTY)
|
||||
}
|
||||
|
||||
// WILL NOT EXPAND BOUNDS!
|
||||
pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
|
||||
let tpos = tpos + TILE_RADIUS as i32;
|
||||
self.zones
|
||||
.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32)))
|
||||
.and_then(|zone| {
|
||||
zone.get_or_insert_with(|| {
|
||||
Grid::populate_from(Vec2::broadcast(ZONE_SIZE as i32), |_| None)
|
||||
})
|
||||
.get_mut(tpos.map(|e| e.rem_euclid(ZONE_SIZE as i32)))
|
||||
.map(|tile| tile.get_or_insert_with(Tile::empty))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set(&mut self, tpos: Vec2<i32>, tile: Tile) -> Option<Tile> {
|
||||
self.bounds.expand_to_contain_point(tpos);
|
||||
self.get_mut(tpos).map(|t| std::mem::replace(t, tile))
|
||||
}
|
||||
|
||||
pub fn find_near<R>(
|
||||
&self,
|
||||
tpos: Vec2<i32>,
|
||||
mut f: impl FnMut(Vec2<i32>, &Tile) -> Option<R>,
|
||||
) -> Option<(R, Vec2<i32>)> {
|
||||
const MAX_SEARCH_RADIUS_BLOCKS: u32 = 70;
|
||||
const MAX_SEARCH_CELLS: u32 = ((MAX_SEARCH_RADIUS_BLOCKS / TILE_SIZE) * 2 + 1).pow(2);
|
||||
Spiral2d::new()
|
||||
.take(MAX_SEARCH_CELLS as usize)
|
||||
.map(|r| tpos + r)
|
||||
.find_map(|tpos| (&mut f)(tpos, self.get(tpos)).zip(Some(tpos)))
|
||||
}
|
||||
|
||||
pub fn grow_aabr(
|
||||
&self,
|
||||
center: Vec2<i32>,
|
||||
area_range: Range<u32>,
|
||||
min_dims: Extent2<u32>,
|
||||
) -> Result<Aabr<i32>, Aabr<i32>> {
|
||||
let mut aabr = Aabr {
|
||||
min: center,
|
||||
max: center + 1,
|
||||
};
|
||||
|
||||
if !self.get(center).is_empty() {
|
||||
return Err(aabr);
|
||||
};
|
||||
|
||||
let mut last_growth = 0;
|
||||
for i in 0..32 {
|
||||
if i - last_growth >= 4
|
||||
|| aabr.size().product()
|
||||
+ if i % 2 == 0 {
|
||||
aabr.size().h
|
||||
} else {
|
||||
aabr.size().w
|
||||
}
|
||||
> area_range.end as i32
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
// `center.sum()` to avoid biasing certain directions
|
||||
match (i + center.sum().abs()) % 4 {
|
||||
0 if (aabr.min.y..aabr.max.y + 1)
|
||||
.all(|y| self.get(Vec2::new(aabr.max.x, y)).is_empty()) =>
|
||||
{
|
||||
aabr.max.x += 1;
|
||||
last_growth = i;
|
||||
}
|
||||
1 if (aabr.min.x..aabr.max.x + 1)
|
||||
.all(|x| self.get(Vec2::new(x, aabr.max.y)).is_empty()) =>
|
||||
{
|
||||
aabr.max.y += 1;
|
||||
last_growth = i;
|
||||
}
|
||||
2 if (aabr.min.y..aabr.max.y + 1)
|
||||
.all(|y| self.get(Vec2::new(aabr.min.x - 1, y)).is_empty()) =>
|
||||
{
|
||||
aabr.min.x -= 1;
|
||||
last_growth = i;
|
||||
}
|
||||
3 if (aabr.min.x..aabr.max.x + 1)
|
||||
.all(|x| self.get(Vec2::new(x, aabr.min.y - 1)).is_empty()) =>
|
||||
{
|
||||
aabr.min.y -= 1;
|
||||
last_growth = i;
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aabr.size().product() as u32 >= area_range.start
|
||||
&& aabr.size().w as u32 >= min_dims.w
|
||||
&& aabr.size().h as u32 >= min_dims.h
|
||||
{
|
||||
Ok(aabr)
|
||||
} else {
|
||||
Err(aabr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grow_organic(
|
||||
&self,
|
||||
rng: &mut impl Rng,
|
||||
center: Vec2<i32>,
|
||||
area_range: Range<u32>,
|
||||
) -> Result<DHashSet<Vec2<i32>>, DHashSet<Vec2<i32>>> {
|
||||
let mut tiles = DHashSet::default();
|
||||
let mut open = Vec::new();
|
||||
|
||||
tiles.insert(center);
|
||||
open.push(center);
|
||||
|
||||
while tiles.len() < area_range.end as usize && !open.is_empty() {
|
||||
let tile = open.remove(rng.gen_range(0..open.len()));
|
||||
|
||||
for &rpos in CARDINALS.iter() {
|
||||
let neighbor = tile + rpos;
|
||||
|
||||
if self.get(neighbor).is_empty() && !tiles.contains(&neighbor) {
|
||||
tiles.insert(neighbor);
|
||||
open.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if tiles.len() >= area_range.start as usize {
|
||||
Ok(tiles)
|
||||
} else {
|
||||
Err(tiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum TileKind {
|
||||
Empty,
|
||||
Hazard(HazardKind),
|
||||
Field,
|
||||
Plaza,
|
||||
Road { a: u16, b: u16, w: u16 },
|
||||
Building,
|
||||
Castle,
|
||||
Wall(Ori),
|
||||
Tower,
|
||||
Keep(KeepKind),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Tile {
|
||||
pub(crate) kind: TileKind,
|
||||
pub(crate) plot: Option<Id<Plot>>,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
kind: TileKind::Empty,
|
||||
plot: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a tile that is not associated with any plot.
|
||||
pub const fn free(kind: TileKind) -> Self { Self { kind, plot: None } }
|
||||
|
||||
pub fn is_empty(&self) -> bool { self.kind == TileKind::Empty }
|
||||
|
||||
pub fn is_road(&self) -> bool { matches!(self.kind, TileKind::Road { .. }) }
|
||||
|
||||
pub fn is_obstacle(&self) -> bool {
|
||||
matches!(
|
||||
self.kind,
|
||||
TileKind::Hazard(_) | TileKind::Building | TileKind::Castle | TileKind::Wall(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum HazardKind {
|
||||
Water,
|
||||
Hill { gradient: f32 },
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum KeepKind {
|
||||
Middle,
|
||||
Corner,
|
||||
Wall(Ori),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum Ori {
|
||||
North = 0,
|
||||
East = 1,
|
||||
South = 2,
|
||||
West = 3,
|
||||
}
|
||||
|
||||
impl Ori {
|
||||
pub fn dir(self) -> Vec2<i32> { CARDINALS[self as u8 as usize] }
|
||||
}
|
@ -24,6 +24,30 @@ impl Sampler<'static> for FastNoise {
|
||||
type Sample = f32;
|
||||
|
||||
fn get(&self, pos: Self::Index) -> Self::Sample {
|
||||
// let align_pos = pos.map(|e| e.floor());
|
||||
// let near_pos = align_pos.map(|e| e 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 - align_pos).map(|e| e as f32);
|
||||
|
||||
// let v00 = v000 + factor.z * (v001 - v000);
|
||||
// let v10 = v010 + factor.z * (v011 - v010);
|
||||
// let v01 = v100 + factor.z * (v101 - v100);
|
||||
// let v11 = v110 + factor.z * (v111 - v110);
|
||||
|
||||
// let v0 = v00 + factor.y * (v01 - v00);
|
||||
// let v1 = v10 + factor.y * (v11 - v10);
|
||||
|
||||
// (v0 + factor.x * (v1 - v0)) * 2.0 - 1.0
|
||||
|
||||
let near_pos = pos.map(|e| e.floor() as i32);
|
||||
|
||||
let v000 = self.noise_at(near_pos + Vec3::new(0, 0, 0));
|
||||
@ -51,3 +75,44 @@ impl Sampler<'static> for FastNoise {
|
||||
(y0 + factor.z * (y1 - y0)) * 2.0 - 1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FastNoise2d {
|
||||
noise: RandomField,
|
||||
}
|
||||
|
||||
impl FastNoise2d {
|
||||
pub const fn new(seed: u32) -> Self {
|
||||
Self {
|
||||
noise: RandomField::new(seed),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::excessive_precision)] // TODO: Pending review in #587
|
||||
fn noise_at(&self, pos: Vec2<i32>) -> f32 {
|
||||
(self.noise.get(Vec3::new(pos.x, pos.y, 0)) & 4095) as f32 * 0.000244140625
|
||||
}
|
||||
}
|
||||
|
||||
impl Sampler<'static> for FastNoise2d {
|
||||
type Index = Vec2<f64>;
|
||||
type Sample = f32;
|
||||
|
||||
fn get(&self, pos: Self::Index) -> Self::Sample {
|
||||
let near_pos = pos.map(|e| e.floor() as i32);
|
||||
|
||||
let v00 = self.noise_at(near_pos + Vec2::new(0, 0));
|
||||
let v10 = self.noise_at(near_pos + Vec2::new(1, 0));
|
||||
let v01 = self.noise_at(near_pos + Vec2::new(0, 1));
|
||||
let v11 = self.noise_at(near_pos + Vec2::new(1, 1));
|
||||
|
||||
let factor = pos.map(|e| {
|
||||
let f = e.fract().add(1.0).fract() as f32;
|
||||
f.powi(2) * (3.0 - 2.0 * f)
|
||||
});
|
||||
|
||||
let v0 = v00 + factor.y * (v10 - v00);
|
||||
let v1 = v01 + factor.y * (v11 - v01);
|
||||
|
||||
(v0 + factor.x * (v1 - v0)) * 2.0 - 1.0
|
||||
}
|
||||
}
|
||||
|
12
world/src/util/math.rs
Normal file
12
world/src/util/math.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use std::ops::Range;
|
||||
use vek::*;
|
||||
|
||||
/// Return a value between 0 and 1 corresponding to how close to the centre of
|
||||
/// `range` `x` is. The exact function used is left unspecified, but it shall
|
||||
/// have the shape of a bell-like curve. This function is required to return `0`
|
||||
/// (or a value extremely close to `0`) when `x` is outside of `range`.
|
||||
pub fn close(x: f32, range: Range<f32>) -> f32 {
|
||||
let mean = (range.start + range.end) / 2.0;
|
||||
let width = (range.end - range.start) / 2.0;
|
||||
(1.0 - ((x - mean) / width).clamped(-1.0, 1.0).powi(2)).powi(2)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub mod fast_noise;
|
||||
pub mod map_vec;
|
||||
pub mod math;
|
||||
pub mod random;
|
||||
pub mod sampler;
|
||||
pub mod seed_expan;
|
||||
@ -9,7 +10,7 @@ pub mod unit_chooser;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
fast_noise::FastNoise,
|
||||
fast_noise::{FastNoise, FastNoise2d},
|
||||
map_vec::MapVec,
|
||||
random::{RandomField, RandomPerm},
|
||||
sampler::{Sampler, SamplerMut},
|
||||
@ -81,3 +82,22 @@ pub const CARDINAL_LOCALITY: [Vec2<i32>; 5] = [
|
||||
Vec2::new(0, -1),
|
||||
Vec2::new(-1, 0),
|
||||
];
|
||||
|
||||
pub const SQUARE_4: [Vec2<i32>; 4] = [
|
||||
Vec2::new(0, 0),
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(1, 1),
|
||||
];
|
||||
|
||||
pub const SQUARE_9: [Vec2<i32>; 9] = [
|
||||
Vec2::new(-1, -1),
|
||||
Vec2::new(0, -1),
|
||||
Vec2::new(1, -1),
|
||||
Vec2::new(-1, 0),
|
||||
Vec2::new(0, 0),
|
||||
Vec2::new(1, 0),
|
||||
Vec2::new(-1, 1),
|
||||
Vec2::new(0, 1),
|
||||
Vec2::new(1, 1),
|
||||
];
|
||||
|
@ -64,7 +64,7 @@ impl Sampler<'static> for RandomPerm {
|
||||
// `RandomPerm` is not high-quality but it is at least fast and deterministic.
|
||||
impl RngCore for RandomPerm {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.seed = self.get(self.seed);
|
||||
self.seed = self.get(self.seed) ^ 0xA7535839;
|
||||
self.seed
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user