Merge branch 'treeco/lod-models' into 'master'

Overhaul of LOD models

See merge request veloren/veloren!4390
This commit is contained in:
Isse 2024-03-23 18:54:17 +00:00
commit e22cbab7ce
29 changed files with 383 additions and 335 deletions

View File

@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allow moving and resizing the chat with left and right mouse button respectively. - Allow moving and resizing the chat with left and right mouse button respectively.
- Missing plugins are requested from the server and cached locally. - Missing plugins are requested from the server and cached locally.
- Support for adding spots in plugins. - Support for adding spots in plugins.
- Added real colours to LOD trees and rooftops, unique models for most tree kinds, and models for several buildings
### Changed ### Changed

1
Cargo.lock generated
View File

@ -7316,6 +7316,7 @@ version = "0.15.0"
dependencies = [ dependencies = [
"assets_manager", "assets_manager",
"backtrace", "backtrace",
"bitflags 2.5.0",
"bytemuck", "bytemuck",
"chrono", "chrono",
"chumsky", "chumsky",

BIN
assets/voxygen/lod/acacia.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/arena.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/baobab.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/birch.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/dead.obj (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/lod/desert_houses.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/frostpine.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/giant_tree.obj (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/lod/haniwa.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/house.obj (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/lod/mangrove.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/oak.obj (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/lod/palm.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/lod/pine.obj (Stored with Git LFS)

Binary file not shown.

BIN
assets/voxygen/lod/redwood.obj (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -25,8 +25,7 @@ layout(location = 3) in vec3 model_pos;
layout(location = 4) flat in uint f_flags; layout(location = 4) flat in uint f_flags;
const uint FLAG_SNOW_COVERED = 1; const uint FLAG_SNOW_COVERED = 1;
const uint FLAG_IS_BUILDING = 2; const uint FLAG_GLOW = 2;
const uint FLAG_IS_GIANT_TREE = 4;
layout(location = 0) out vec4 tgt_color; layout(location = 0) out vec4 tgt_color;
layout(location = 1) out uvec4 tgt_mat; layout(location = 1) out uvec4 tgt_mat;
@ -84,13 +83,6 @@ void main() {
vec3 k_d = vec3(1.0); vec3 k_d = vec3(1.0);
vec3 k_s = vec3(R_s); vec3 k_s = vec3(R_s);
// Tree trunks
if ((f_flags & FLAG_IS_GIANT_TREE) > 0u) {
if (dot(abs(model_pos.xyz) * vec3(1.0, 1.0, 2.0), vec3(1)) < 430.0) { surf_color = vec3(0.05, 0.02, 0.0); }
} else {
if (model_pos.z < 25.0 && dot(abs(model_pos.xy), vec2(1)) < 6.0) { surf_color = vec3(0.05, 0.02, 0.0); }
}
vec3 voxel_norm = f_norm; vec3 voxel_norm = f_norm;
float my_alt = f_pos.z + focus_off.z; float my_alt = f_pos.z + focus_off.z;
float f_ao = 1.0; float f_ao = 1.0;
@ -138,13 +130,8 @@ void main() {
reflected_light *= f_ao; reflected_light *= f_ao;
vec3 glow = vec3(0); vec3 glow = vec3(0);
if ((f_flags & FLAG_IS_BUILDING) > 0u && abs(f_norm.z) < 0.1) { if ((f_flags & FLAG_GLOW) > 0u) {
ivec3 wpos = ivec3((f_pos.xyz + focus_off.xyz) * 0.2); glow += vec3(1, 0.7, 0.3) * 2;
if (((wpos.x & wpos.y & wpos.z) & 1) == 1) {
glow += vec3(1, 0.7, 0.3) * 2;
} else {
reflected_light += vec3(1, 0.7, 0.3) * 0.9;
}
} }
vec3 side_color = surf_color; vec3 side_color = surf_color;

View File

@ -20,9 +20,13 @@
layout(location = 0) in vec3 v_pos; layout(location = 0) in vec3 v_pos;
layout(location = 1) in vec3 v_norm; layout(location = 1) in vec3 v_norm;
layout(location = 2) in vec3 v_col; layout(location = 2) in vec3 v_col;
layout(location = 3) in vec3 inst_pos; layout(location = 3) in uint v_flags;
layout(location = 4) in uvec3 inst_col; layout(location = 4) in vec3 inst_pos;
layout(location = 5) in uint inst_flags; layout(location = 5) in vec3 inst_col;
layout(location = 6) in uint inst_flags;
const uint FLAG_INST_COLOR = 1;
const uint FLAG_INST_GLOW = 2;
layout(location = 0) out vec3 f_pos; layout(location = 0) out vec3 f_pos;
layout(location = 1) out vec3 f_norm; layout(location = 1) out vec3 f_norm;
@ -47,8 +51,13 @@ void main() {
#endif #endif
f_norm = v_norm; f_norm = v_norm;
f_col = vec4(vec3(inst_col) * (1.0 / 255.0) * v_col * (hash(inst_pos.xyxy) * 0.4 + 0.6), 1.0);
f_flags = inst_flags; if ((v_flags & FLAG_INST_COLOR) > 0u) {
f_col = vec4(inst_col, 1.0);
} else {
f_col = vec4(v_col, 1.0);
}
f_flags = inst_flags | (v_flags & FLAG_INST_GLOW);
gl_Position = gl_Position =
all_mat * all_mat *

View File

@ -8,31 +8,38 @@ pub const ZONE_SIZE: u32 = 32;
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Flags: u8 { pub struct InstFlags: u8 {
const SNOW_COVERED = 0b00000001; const SNOW_COVERED = 0b00000001;
const IS_BUILDING = 0b00000010; const GLOW = 0b00000010;
const IS_GIANT_TREE = 0b00000100;
} }
} }
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize, EnumIter)] #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize, EnumIter)]
#[repr(u16)] #[repr(u16)]
pub enum ObjectKind { pub enum ObjectKind {
Oak, GenericTree,
Pine, Pine,
Dead, Dead,
House, House,
GiantTree, GiantTree,
MapleTree, Mangrove,
Cherry, Acacia,
AutumnTree, Birch,
Redwood,
Baobab,
Frostpine,
Haniwa,
Desert,
Palm,
Arena,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Object { pub struct Object {
pub kind: ObjectKind, pub kind: ObjectKind,
pub pos: Vec3<i16>, pub pos: Vec3<i16>,
pub flags: Flags, pub flags: InstFlags,
pub color: Rgb<u8>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]

View File

@ -128,6 +128,7 @@ num_cpus = "1.0"
inline_tweak = { workspace = true } inline_tweak = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
sha2 = { workspace = true } sha2 = { workspace = true }
bitflags = { workspace = true, features = ["serde"] }
# Discord RPC # Discord RPC
discord-sdk = { version = "0.3.0", optional = true } discord-sdk = { version = "0.3.0", optional = true }

View File

@ -1,5 +1,6 @@
use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait}; use super::super::{AaMode, GlobalsLayouts, Vertex as VertexTrait};
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use common::util::srgb_to_linear;
use std::mem; use std::mem;
use vek::*; use vek::*;
@ -9,20 +10,27 @@ pub struct Vertex {
pos: [f32; 3], pos: [f32; 3],
norm: [f32; 3], norm: [f32; 3],
col: [f32; 3], col: [f32; 3],
flags: u32,
} }
impl Vertex { impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, col: Rgb<f32>) -> Self { pub fn new(
pos: Vec3<f32>,
norm: Vec3<f32>,
col: Rgb<f32>,
flags: crate::scene::lod::VertexFlags,
) -> Self {
Self { Self {
pos: pos.into_array(), pos: pos.into_array(),
norm: norm.into_array(), norm: norm.into_array(),
col: col.into_array(), col: col.into_array(),
flags: flags.bits() as u32,
} }
} }
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
const ATTRIBUTES: [wgpu::VertexAttribute; 3] = const ATTRIBUTES: [wgpu::VertexAttribute; 4] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3]; wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3, 3 => Uint32];
wgpu::VertexBufferLayout { wgpu::VertexBufferLayout {
array_stride: Self::STRIDE, array_stride: Self::STRIDE,
step_mode: wgpu::VertexStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,
@ -40,24 +48,24 @@ impl VertexTrait for Vertex {
#[derive(Copy, Clone, Debug, Zeroable, Pod)] #[derive(Copy, Clone, Debug, Zeroable, Pod)]
pub struct Instance { pub struct Instance {
inst_pos: [f32; 3], inst_pos: [f32; 3],
inst_col: [u8; 4], inst_col: [f32; 3],
flags: u32, flags: u32,
} }
impl Instance { impl Instance {
pub fn new(inst_pos: Vec3<f32>, col: Rgb<u8>, flags: common::lod::Flags) -> Self { pub fn new(inst_pos: Vec3<f32>, col: Rgb<u8>, flags: common::lod::InstFlags) -> Self {
Self { Self {
inst_pos: inst_pos.into_array(), inst_pos: inst_pos.into_array(),
inst_col: Rgba::new(col.r, col.g, col.b, 255).into_array(), inst_col: srgb_to_linear(col.map(|c| c as f32 / 255.0)).into_array(),
flags: flags.bits() as u32, flags: flags.bits() as u32,
} }
} }
fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
const ATTRIBUTES: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![ const ATTRIBUTES: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
3 => Float32x3, 4 => Float32x3,
4 => Uint8x4, 5 => Float32x3,
5 => Uint32, 6 => Uint32,
]; ];
wgpu::VertexBufferLayout { wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Self>() as wgpu::BufferAddress, array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,

View File

@ -12,7 +12,7 @@ use common::{
assets::{AssetExt, ObjAsset}, assets::{AssetExt, ObjAsset},
lod, lod,
spiral::Spiral2d, spiral::Spiral2d,
util::srgba_to_linear, util::{srgb_to_linear, srgba_to_linear},
weather, weather,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
@ -23,6 +23,16 @@ use vek::*;
// For culling // For culling
const MAX_OBJECT_RADIUS: i32 = 64; const MAX_OBJECT_RADIUS: i32 = 64;
bitflags::bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct VertexFlags: u8 {
// Use instance not vertex colour
const INST_COLOR = 0b00000001;
// Glow!
const GLOW = 0b00000010;
}
}
struct ObjectGroup { struct ObjectGroup {
instances: Instances<LodObjectInstance>, instances: Instances<LodObjectInstance>,
// None implies no instances // None implies no instances
@ -63,7 +73,10 @@ impl Lod {
data, data,
zone_objects: HashMap::new(), zone_objects: HashMap::new(),
object_data: [ object_data: [
(lod::ObjectKind::Oak, make_lod_object("oak", renderer)), (
lod::ObjectKind::GenericTree,
make_lod_object("oak", renderer),
),
(lod::ObjectKind::Pine, make_lod_object("pine", renderer)), (lod::ObjectKind::Pine, make_lod_object("pine", renderer)),
(lod::ObjectKind::Dead, make_lod_object("dead", renderer)), (lod::ObjectKind::Dead, make_lod_object("dead", renderer)),
(lod::ObjectKind::House, make_lod_object("house", renderer)), (lod::ObjectKind::House, make_lod_object("house", renderer)),
@ -71,15 +84,30 @@ impl Lod {
lod::ObjectKind::GiantTree, lod::ObjectKind::GiantTree,
make_lod_object("giant_tree", renderer), make_lod_object("giant_tree", renderer),
), ),
(lod::ObjectKind::MapleTree, make_lod_object("oak", renderer)),
(lod::ObjectKind::Cherry, make_lod_object("oak", renderer)),
( (
lod::ObjectKind::AutumnTree, lod::ObjectKind::Mangrove,
make_lod_object("oak", renderer), make_lod_object("mangrove", renderer),
), ),
(lod::ObjectKind::Acacia, make_lod_object("acacia", renderer)),
(lod::ObjectKind::Birch, make_lod_object("birch", renderer)),
(
lod::ObjectKind::Redwood,
make_lod_object("redwood", renderer),
),
(lod::ObjectKind::Baobab, make_lod_object("baobab", renderer)),
(
lod::ObjectKind::Frostpine,
make_lod_object("frostpine", renderer),
),
(lod::ObjectKind::Haniwa, make_lod_object("haniwa", renderer)),
(
lod::ObjectKind::Desert,
make_lod_object("desert_houses", renderer),
),
(lod::ObjectKind::Palm, make_lod_object("palm", renderer)),
(lod::ObjectKind::Arena, make_lod_object("arena", renderer)),
] ]
.into_iter() .into(),
.collect(),
} }
} }
@ -127,21 +155,10 @@ impl Lod {
z_range.start.min(pos.z as i32)..z_range.end.max(pos.z as i32) z_range.start.min(pos.z as i32)..z_range.end.max(pos.z as i32)
}, },
)); ));
// TODO: Put this somewhere more easily configurable, like a manifest
let color = match object.kind {
lod::ObjectKind::Pine => Rgb::new(0, 25, 12),
lod::ObjectKind::Oak => Rgb::new(10, 50, 5),
lod::ObjectKind::Dead => Rgb::new(20, 10, 2),
lod::ObjectKind::House => Rgb::new(20, 15, 0),
lod::ObjectKind::GiantTree => Rgb::new(8, 35, 5),
lod::ObjectKind::MapleTree => Rgb::new(20, 0, 5),
lod::ObjectKind::Cherry => Rgb::new(70, 40, 70),
lod::ObjectKind::AutumnTree => Rgb::new(60, 25, 0),
};
objects objects
.entry(object.kind) .entry(object.kind)
.or_default() .or_default()
.push(LodObjectInstance::new(pos, color, object.flags)); .push(LodObjectInstance::new(pos, object.color, object.flags));
} }
objects objects
.into_iter() .into_iter()
@ -257,17 +274,30 @@ fn make_lod_object(name: &str, renderer: &mut Renderer) -> Model<LodObjectVertex
let mesh = model let mesh = model
.read() .read()
.0 .0
.triangles() .objects()
.map(|vs| { .flat_map(|(objname, obj)| {
let [a, b, c] = vs.map(|v| { let mut color = objname.split('_').filter_map(|x| x.parse::<u8>().ok());
LodObjectVertex::new( let color = color
v.position().into(), .next()
v.normal().unwrap_or([0.0, 0.0, 1.0]).into(), .and_then(|r| Some(Rgb::new(r, color.next()?, color.next()?)))
Rgb::broadcast(1.0), .unwrap_or(Rgb::broadcast(127));
//v.color().unwrap_or([1.0; 3]).into(), let color = srgb_to_linear(color.map(|c| (c as f32 / 255.0)));
) let flags = match objname {
}); "InstCol" => VertexFlags::INST_COLOR,
Tri::new(a, b, c) "Glow" => VertexFlags::GLOW,
_ => VertexFlags::empty(),
};
obj.triangles().map(move |vs| {
let [a, b, c] = vs.map(|v| {
LodObjectVertex::new(
v.position().into(),
v.normal().unwrap_or([0.0, 0.0, 1.0]).into(),
color,
flags,
)
});
Tri::new(a, b, c)
})
}) })
.collect(); .collect();
renderer.create_model(&mesh).expect("Mesh was empty!") renderer.create_model(&mesh).expect("Mesh was empty!")

View File

@ -1,7 +1,11 @@
use crate::util::math::close; use crate::{
util::{math::close, sampler::Sampler},
IndexRef,
};
use common::terrain::structure::StructureBlock;
use std::ops::Range; use std::ops::Range;
use strum::EnumIter; use strum::EnumIter;
use vek::Vec2; use vek::*;
#[derive(Copy, Clone, Debug, EnumIter)] #[derive(Copy, Clone, Debug, EnumIter)]
pub enum ForestKind { pub enum ForestKind {
@ -126,6 +130,28 @@ impl ForestKind {
} }
} }
pub fn leaf_block(&self) -> StructureBlock {
match self {
ForestKind::Palm => StructureBlock::PalmLeavesOuter,
ForestKind::Acacia => StructureBlock::Acacia,
ForestKind::Baobab => StructureBlock::Baobab,
ForestKind::Oak => StructureBlock::TemperateLeaves,
ForestKind::Chestnut => StructureBlock::Chestnut,
ForestKind::Cedar => StructureBlock::PineLeaves,
ForestKind::Pine => StructureBlock::PineLeaves,
ForestKind::Redwood => StructureBlock::PineLeaves,
ForestKind::Birch => StructureBlock::TemperateLeaves,
ForestKind::Mangrove => StructureBlock::Mangrove,
ForestKind::Giant => StructureBlock::TemperateLeaves,
ForestKind::Swamp => StructureBlock::TemperateLeaves,
ForestKind::Frostpine => StructureBlock::FrostpineLeaves,
ForestKind::Dead => StructureBlock::TemperateLeaves,
ForestKind::Mapletree => StructureBlock::MapleLeaves,
ForestKind::Cherry => StructureBlock::CherryLeaves,
ForestKind::AutumnTree => StructureBlock::AutumnLeaves,
}
}
pub fn proclivity(&self, env: &Environment) -> f32 { pub fn proclivity(&self, env: &Environment) -> f32 {
self.ideal_proclivity() self.ideal_proclivity()
* close(env.humid, self.humid_range()) * close(env.humid, self.humid_range())
@ -136,6 +162,30 @@ impl ForestKind {
} }
} }
pub fn leaf_color(
index: IndexRef,
seed: u32,
lerp: f32,
sblock: &StructureBlock,
) -> Option<Rgb<u8>> {
let ranges = sblock
.elim_case_pure(&index.colors.block.structure_blocks)
.as_ref()
.map(Vec::as_slice)
.unwrap_or(&[]);
ranges
.get(crate::util::RandomPerm::new(seed).get(seed) as usize % ranges.len())
.map(|range| {
Rgb::<f32>::lerp(
Rgb::<u8>::from(range.start).map(f32::from),
Rgb::<u8>::from(range.end).map(f32::from),
lerp,
)
.map(|e| e as u8)
})
}
/// Not currently used with trees generated by the tree layer, needs to be /// Not currently used with trees generated by the tree layer, needs to be
/// reworked /// reworked
pub struct TreeAttr { pub struct TreeAttr {

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
column::{ColumnGen, ColumnSample}, column::{ColumnGen, ColumnSample},
util::{FastNoise, RandomField, RandomPerm, Sampler, SmallCache}, util::{FastNoise, RandomField, Sampler, SmallCache},
IndexRef, CONFIG, IndexRef, CONFIG,
}; };
use common::{ use common::{
@ -212,8 +212,7 @@ pub fn block_from_structure(
) -> Option<(Block, Option<SpriteCfg>)> { ) -> Option<(Block, Option<SpriteCfg>)> {
let field = RandomField::new(structure_seed); let field = RandomField::new(structure_seed);
let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.8 let lerp = field.get_f32(Vec3::from(structure_pos)) * 0.8 + field.get_f32(pos) * 0.2;
+ ((field.get(pos + i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.2;
let block = match sblock { let block = match sblock {
StructureBlock::None => None, StructureBlock::None => None,
@ -284,49 +283,21 @@ pub fn block_from_structure(
| StructureBlock::MapleLeaves | StructureBlock::MapleLeaves
| StructureBlock::CherryLeaves | StructureBlock::CherryLeaves
| StructureBlock::AutumnLeaves => { | StructureBlock::AutumnLeaves => {
let ranges = sblock if calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas))
.elim_case_pure(&index.colors.block.structure_blocks) && field.chance(pos + structure_pos, 0.025)
.as_ref() {
.map(Vec::as_slice) Some(Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0)))
.unwrap_or(&[]); } else if calendar.map_or(false, |c| c.is_event(CalendarEvent::Halloween))
let range = if ranges.is_empty() { && (*sblock == StructureBlock::TemperateLeaves
None || *sblock == StructureBlock::Chestnut
|| *sblock == StructureBlock::CherryLeaves)
{
crate::all::leaf_color(index, structure_seed, lerp, &StructureBlock::AutumnLeaves)
.map(|col| Block::new(BlockKind::Leaves, col))
} else { } else {
ranges.get( crate::all::leaf_color(index, structure_seed, lerp, sblock)
RandomPerm::new(structure_seed).get(structure_seed) as usize % ranges.len(), .map(|col| Block::new(BlockKind::Leaves, col))
) }
};
range.map(|range| {
if calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas))
&& field.chance(pos + structure_pos, 0.025)
{
Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0))
} else if calendar.map_or(false, |c| c.is_event(CalendarEvent::Halloween))
&& *sblock != StructureBlock::PineLeaves
{
let (c0, c1) = match structure_seed % 6 {
0 => (Rgb::new(165.0, 150.0, 11.0), Rgb::new(170.0, 165.0, 16.0)),
1 | 2 => (Rgb::new(218.0, 53.0, 3.0), Rgb::new(226.0, 62.0, 5.0)),
_ => (Rgb::new(230.0, 120.0, 20.0), Rgb::new(242.0, 130.0, 25.0)),
};
Block::new(
BlockKind::Leaves,
Rgb::<f32>::lerp(c0, c1, lerp).map(|e| e as u8),
)
} else {
Block::new(
BlockKind::Leaves,
Rgb::<f32>::lerp(
Rgb::<u8>::from(range.start).map(f32::from),
Rgb::<u8>::from(range.end).map(f32::from),
lerp,
)
.map(|e| e as u8),
)
}
})
}, },
StructureBlock::BirchWood => { StructureBlock::BirchWood => {
let wpos = pos + structure_pos; let wpos = pos + structure_pos;

View File

@ -69,12 +69,13 @@ pub fn apply_trees_to(
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
enum TreeModel { enum TreeModel {
Structure(Structure), Structure(Structure),
Procedural(ProceduralTree, StructureBlock), Procedural(ProceduralTree),
} }
struct Tree { struct Tree {
pos: Vec3<i32>, pos: Vec3<i32>,
model: TreeModel, model: TreeModel,
leaf_block: StructureBlock,
seed: u32, seed: u32,
units: (Vec2<i32>, Vec2<i32>), units: (Vec2<i32>, Vec2<i32>),
lights: bool, lights: bool,
@ -105,151 +106,103 @@ pub fn apply_trees_to(
let models: AssetHandle<_> = match forest_kind { let models: AssetHandle<_> = match forest_kind {
ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => *OAK_STUMPS, ForestKind::Oak if QUIRKY_RAND.chance(seed + 1, 1.0 / 16.0) => *OAK_STUMPS,
ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => { ForestKind::Oak if QUIRKY_RAND.chance(seed + 2, 1.0 / 20.0) => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::apple(&mut RandomPerm::new(seed), scale),
TreeConfig::apple(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::TemperateLeaves,
);
}, },
ForestKind::Palm => *PALMS, ForestKind::Palm => *PALMS,
ForestKind::Acacia => { ForestKind::Acacia => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::acacia(&mut RandomPerm::new(seed), scale),
TreeConfig::acacia(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::Acacia,
);
}, },
ForestKind::Baobab => { ForestKind::Baobab => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::baobab(&mut RandomPerm::new(seed), scale),
TreeConfig::baobab(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::Baobab,
);
}, },
ForestKind::Oak => { ForestKind::Oak => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::oak(&mut RandomPerm::new(seed), scale),
TreeConfig::oak(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::TemperateLeaves,
);
}, },
ForestKind::Dead => { ForestKind::Dead => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::dead(&mut RandomPerm::new(seed), scale),
TreeConfig::dead(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::TemperateLeaves,
);
}, },
ForestKind::Chestnut => { ForestKind::Chestnut => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::chestnut(&mut RandomPerm::new(seed), scale),
TreeConfig::chestnut(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::Chestnut,
);
}, },
ForestKind::Pine => { ForestKind::Pine => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::pine(&mut RandomPerm::new(seed), scale, calendar),
TreeConfig::pine(&mut RandomPerm::new(seed), scale, calendar), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::PineLeaves,
);
}, },
ForestKind::Cedar => { ForestKind::Cedar => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::cedar(&mut RandomPerm::new(seed), scale),
TreeConfig::cedar(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::PineLeaves,
);
}, },
ForestKind::Redwood => { ForestKind::Redwood => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::redwood(&mut RandomPerm::new(seed), scale),
TreeConfig::redwood(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::PineLeaves,
);
}, },
ForestKind::Birch => { ForestKind::Birch => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::birch(&mut RandomPerm::new(seed), scale),
TreeConfig::birch(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::TemperateLeaves,
);
}, },
ForestKind::Frostpine => { ForestKind::Frostpine => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::frostpine(&mut RandomPerm::new(seed), scale),
TreeConfig::frostpine(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::FrostpineLeaves,
);
}, },
ForestKind::Mangrove => { ForestKind::Mangrove => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::jungle(&mut RandomPerm::new(seed), scale),
TreeConfig::jungle(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::Mangrove,
);
}, },
ForestKind::Swamp => *SWAMP_TREES, ForestKind::Swamp => *SWAMP_TREES,
ForestKind::Giant => { ForestKind::Giant => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::giant(&mut RandomPerm::new(seed), scale, inhabited),
TreeConfig::giant(&mut RandomPerm::new(seed), scale, inhabited), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::TemperateLeaves,
);
}, },
ForestKind::Mapletree => { ForestKind::Mapletree => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::oak(&mut RandomPerm::new(seed), scale),
TreeConfig::oak(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::MapleLeaves,
);
}, },
ForestKind::Cherry => { ForestKind::Cherry => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::cherry(&mut RandomPerm::new(seed), scale),
TreeConfig::cherry(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::CherryLeaves,
);
}, },
ForestKind::AutumnTree => { ForestKind::AutumnTree => {
break 'model TreeModel::Procedural( break 'model TreeModel::Procedural(ProceduralTree::generate(
ProceduralTree::generate( TreeConfig::autumn_tree(&mut RandomPerm::new(seed), scale),
TreeConfig::autumn_tree(&mut RandomPerm::new(seed), scale), &mut RandomPerm::new(seed),
&mut RandomPerm::new(seed), ));
),
StructureBlock::AutumnLeaves,
);
}, },
}; };
@ -260,6 +213,7 @@ pub fn apply_trees_to(
.clone(), .clone(),
) )
}, },
leaf_block: forest_kind.leaf_block(),
seed, seed,
units: UNIT_CHOOSER.get(seed), units: UNIT_CHOOSER.get(seed),
lights: inhabited, lights: inhabited,
@ -269,7 +223,7 @@ pub fn apply_trees_to(
for tree in trees { for tree in trees {
let bounds = match &tree.model { let bounds = match &tree.model {
TreeModel::Structure(s) => s.get_bounds(), TreeModel::Structure(s) => s.get_bounds(),
TreeModel::Procedural(t, _) => t.get_bounds().map(|e| e as i32), TreeModel::Procedural(t) => t.get_bounds().map(|e| e as i32),
}; };
let rpos2d = (wpos2d - tree.pos.xy()) let rpos2d = (wpos2d - tree.pos.xy())
@ -282,7 +236,7 @@ pub fn apply_trees_to(
let hanging_sprites = match &tree.model { let hanging_sprites = match &tree.model {
TreeModel::Structure(_) => [(0.0004, SpriteKind::Beehive)].as_ref(), TreeModel::Structure(_) => [(0.0004, SpriteKind::Beehive)].as_ref(),
TreeModel::Procedural(t, _) => t.config.hanging_sprites, TreeModel::Procedural(t) => t.config.hanging_sprites,
}; };
let mut is_top = true; let mut is_top = true;
@ -303,7 +257,7 @@ pub fn apply_trees_to(
info.index(), info.index(),
if let Some(block) = match &tree.model { if let Some(block) = match &tree.model {
TreeModel::Structure(s) => s.get(model_pos).ok(), TreeModel::Structure(s) => s.get(model_pos).ok(),
TreeModel::Procedural(t, leaf_block) => Some( TreeModel::Procedural(t) => Some(
match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) { match t.is_branch_or_leaves_at(model_pos.map(|e| e as f32 + 0.5)) {
(_, _, true, _) => { (_, _, true, _) => {
sblock = StructureBlock::Filled( sblock = StructureBlock::Filled(
@ -314,7 +268,7 @@ pub fn apply_trees_to(
}, },
(_, _, _, true) => &StructureBlock::None, (_, _, _, true) => &StructureBlock::None,
(true, _, _, _) => &t.config.trunk_block, (true, _, _, _) => &t.config.trunk_block,
(_, true, _, _) => leaf_block, (_, true, _, _) => &tree.leaf_block,
_ => &StructureBlock::None, _ => &StructureBlock::None,
}, },
), ),
@ -578,20 +532,20 @@ impl TreeConfig {
let log_scale = 1.0 + scale.log2().max(0.0); let log_scale = 1.0 + scale.log2().max(0.0);
Self { Self {
trunk_len: 9.0 * scale, trunk_len: 45.0 * scale,
trunk_radius: 2.0 * scale, trunk_radius: 1.8 * scale,
branch_child_len: 0.9, branch_child_len: 0.4,
branch_child_radius: 0.75, branch_child_radius: 0.6,
branch_child_radius_lerp: true, branch_child_radius_lerp: true,
leaf_radius: 4.0 * log_scale..5.0 * log_scale, leaf_radius: 2.0 * log_scale..2.5 * log_scale,
leaf_radius_scaled: 0.0, leaf_radius_scaled: 0.0,
straightness: 0.4, straightness: 0.3,
max_depth: 4, max_depth: 2,
splits: 1.75..2.0, splits: 16.0..18.0,
split_range: 0.75..1.5, split_range: 0.2..1.2,
branch_len_bias: 0.0, branch_len_bias: 0.7,
leaf_vertical_scale: 0.4, leaf_vertical_scale: 0.3,
proportionality: 0.0, proportionality: 0.7,
inhabited: false, inhabited: false,
hanging_sprites: &[(0.00007, SpriteKind::Beehive)], hanging_sprites: &[(0.00007, SpriteKind::Beehive)],
trunk_block: StructureBlock::Filled(BlockKind::Wood, Rgb::new(110, 68, 65)), trunk_block: StructureBlock::Filled(BlockKind::Wood, Rgb::new(110, 68, 65)),

View File

@ -608,7 +608,7 @@ impl World {
// Add trees // Add trees
prof_span!(guard, "add trees"); prof_span!(guard, "add trees");
objects.append( objects.extend(
&mut self &mut self
.sim() .sim()
.get_area_trees(min_wpos, max_wpos) .get_area_trees(min_wpos, max_wpos)
@ -621,15 +621,16 @@ impl World {
.filter_map(|(col, tree)| { .filter_map(|(col, tree)| {
Some(lod::Object { Some(lod::Object {
kind: match tree.forest_kind { kind: match tree.forest_kind {
all::ForestKind::Oak => lod::ObjectKind::Oak,
all::ForestKind::Dead => lod::ObjectKind::Dead, all::ForestKind::Dead => lod::ObjectKind::Dead,
all::ForestKind::Pine all::ForestKind::Pine => lod::ObjectKind::Pine,
| all::ForestKind::Frostpine all::ForestKind::Mangrove => lod::ObjectKind::Mangrove,
| all::ForestKind::Redwood => lod::ObjectKind::Pine, all::ForestKind::Acacia => lod::ObjectKind::Acacia,
all::ForestKind::Mapletree => lod::ObjectKind::MapleTree, all::ForestKind::Birch => lod::ObjectKind::Birch,
all::ForestKind::Cherry => lod::ObjectKind::Cherry, all::ForestKind::Redwood => lod::ObjectKind::Redwood,
all::ForestKind::AutumnTree => lod::ObjectKind::AutumnTree, all::ForestKind::Baobab => lod::ObjectKind::Baobab,
_ => lod::ObjectKind::Oak, all::ForestKind::Frostpine => lod::ObjectKind::Frostpine,
all::ForestKind::Palm => lod::ObjectKind::Palm,
_ => lod::ObjectKind::GenericTree,
}, },
pos: { pos: {
let rpos = tree.pos - min_wpos; let rpos = tree.pos - min_wpos;
@ -639,19 +640,26 @@ impl World {
rpos.map(|e| e as i16).with_z(col.alt as i16) rpos.map(|e| e as i16).with_z(col.alt as i16)
} }
}, },
flags: lod::Flags::empty() flags: lod::InstFlags::empty()
| if col.snow_cover { | if col.snow_cover {
lod::Flags::SNOW_COVERED lod::InstFlags::SNOW_COVERED
} else { } else {
lod::Flags::empty() lod::InstFlags::empty()
}, },
color: {
let field = crate::util::RandomField::new(tree.seed);
let lerp = field.get_f32(Vec3::from(tree.pos)) * 0.8 + 0.1;
let sblock = tree.forest_kind.leaf_block();
crate::all::leaf_color(index, tree.seed, lerp, &sblock)
.unwrap_or(Rgb::black())
},
}) })
}) }),
.collect(),
); );
drop(guard); drop(guard);
// Add buildings // Add structures
objects.extend( objects.extend(
index index
.sites .sites
@ -661,68 +669,55 @@ impl World {
.map2(min_wpos.zip(max_wpos), |e, (min, max)| e >= min && e < max) .map2(min_wpos.zip(max_wpos), |e, (min, max)| e >= min && e < max)
.reduce_and() .reduce_and()
}) })
.filter_map(|(_, site)| match &site.kind { .filter_map(|(_, site)| {
SiteKind::Refactor(site) => { site.site2().map(|site| {
Some(site.plots().filter_map(|plot| match &plot.kind { site.plots().filter_map(|plot| match &plot.kind {
site2::plot::PlotKind::House(_) => Some(site.tile_wpos(plot.root_tile)), site2::plot::PlotKind::House(h) => Some((
site.tile_wpos(plot.root_tile),
h.roof_color(),
lod::ObjectKind::House,
)),
site2::plot::PlotKind::GiantTree(t) => Some((
site.tile_wpos(plot.root_tile),
t.leaf_color(),
lod::ObjectKind::GiantTree,
)),
site2::plot::PlotKind::Haniwa(_) => Some((
site.tile_wpos(plot.root_tile),
Rgb::black(),
lod::ObjectKind::Haniwa,
)),
site2::plot::PlotKind::DesertCityMultiPlot(_) => Some((
site.tile_wpos(plot.root_tile),
Rgb::black(),
lod::ObjectKind::Desert,
)),
site2::plot::PlotKind::DesertCityArena(_) => Some((
site.tile_wpos(plot.root_tile),
Rgb::black(),
lod::ObjectKind::Arena,
)),
_ => None, _ => None,
})) })
}, })
_ => None,
}) })
.flatten() .flatten()
.filter_map(|wpos2d| { .filter_map(|(wpos2d, color, model)| {
ColumnGen::new(self.sim()) ColumnGen::new(self.sim())
.get((wpos2d, index, self.sim().calendar.as_ref())) .get((wpos2d, index, self.sim().calendar.as_ref()))
.zip(Some(wpos2d)) .zip(Some((wpos2d, color, model)))
}) })
.map(|(col, wpos2d)| lod::Object { .map(|(column, (wpos2d, color, model))| lod::Object {
kind: lod::ObjectKind::House, kind: model,
pos: (wpos2d - min_wpos) pos: (wpos2d - min_wpos)
.map(|e| e as i16) .map(|e| e as i16)
.with_z(self.sim().get_alt_approx(wpos2d).unwrap_or(0.0) as i16), .with_z(self.sim().get_alt_approx(wpos2d).unwrap_or(0.0) as i16),
flags: lod::Flags::IS_BUILDING flags: if column.snow_cover {
| if col.snow_cover { lod::InstFlags::SNOW_COVERED
lod::Flags::SNOW_COVERED } else {
} else { lod::InstFlags::empty()
lod::Flags::empty() },
}, color,
}),
);
// Add giant trees
objects.extend(
index
.sites
.iter()
.filter(|(_, site)| {
site.get_origin()
.map2(min_wpos.zip(max_wpos), |e, (min, max)| e >= min && e < max)
.reduce_and()
})
.filter(|(_, site)| matches!(&site.kind, SiteKind::GiantTree(_)))
.filter_map(|(_, site)| {
let wpos2d = site.get_origin();
let col = ColumnGen::new(self.sim()).get((
wpos2d,
index,
self.sim().calendar.as_ref(),
))?;
Some(lod::Object {
kind: lod::ObjectKind::GiantTree,
pos: {
(wpos2d - min_wpos)
.map(|e| e as i16)
.with_z(self.sim().get_alt_approx(wpos2d).unwrap_or(0.0) as i16)
},
flags: lod::Flags::empty()
| lod::Flags::IS_GIANT_TREE
| if col.snow_cover {
lod::Flags::SNOW_COVERED
} else {
lod::Flags::empty()
},
})
}), }),
); );

View File

@ -80,6 +80,18 @@ impl GiantTree {
None None
} }
} }
pub fn leaf_color(&self) -> Rgb<u8> {
let fast_noise = FastNoise::new(self.seed);
let dark = Rgb::new(10, 70, 50).map(|e| e as f32);
let light = Rgb::new(80, 140, 10).map(|e| e as f32);
Lerp::lerp(
dark,
light,
fast_noise.get((self.wpos.map(|e| e as f64) * 0.05) * 0.5 + 0.5),
)
.map(|e| e as u8)
}
} }
impl Structure for GiantTree { impl Structure for GiantTree {
@ -88,14 +100,7 @@ impl Structure for GiantTree {
#[cfg_attr(feature = "be-dyn-lib", export_name = "render_gianttree")] #[cfg_attr(feature = "be-dyn-lib", export_name = "render_gianttree")]
fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) { fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
let fast_noise = FastNoise::new(self.seed); let leaf_col = self.leaf_color();
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,
fast_noise.get((self.wpos.map(|e| e as f64) * 0.05) * 0.5 + 0.5),
);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
self.tree.walk(|branch, parent| { self.tree.walk(|branch, parent| {
let aabr = Aabr { let aabr = Aabr {
@ -122,10 +127,7 @@ impl Structure for GiantTree {
parent.get_leaf_radius(), parent.get_leaf_radius(),
branch.get_leaf_radius(), branch.get_leaf_radius(),
) )
.fill(Fill::Block(Block::new( .fill(Fill::Block(Block::new(BlockKind::Leaves, leaf_col)));
BlockKind::Leaves,
leaf_col.map(|e| e as u8),
)));
// Calculate direction of the branch // Calculate direction of the branch
let branch_start = branch.get_line().start; let branch_start = branch.get_line().start;
let branch_end = branch.get_line().end; let branch_end = branch.get_line().end;

View File

@ -95,6 +95,8 @@ impl House {
} }
pub fn z_range(&self) -> Range<i32> { self.alt..self.alt + self.levels as i32 * STOREY } pub fn z_range(&self) -> Range<i32> { self.alt..self.alt + self.levels as i32 * STOREY }
pub fn roof_color(&self) -> Rgb<u8> { self.roof_color }
} }
const STOREY: i32 = 5; const STOREY: i32 = 5;