From 56426806871298cfdf270757e9d7ca5134ee788e Mon Sep 17 00:00:00 2001 From: Isidor Nielsen Date: Sat, 15 Jan 2022 13:06:18 +0000 Subject: [PATCH] Site2 util --- world/src/site2/gen.rs | 30 ++++--- world/src/site2/mod.rs | 1 + world/src/site2/plot/house.rs | 46 +++++----- world/src/site2/util/gradient.rs | 92 ++++++++++++++++++++ world/src/site2/util/mod.rs | 140 +++++++++++++++++++++++++++++++ 5 files changed, 275 insertions(+), 34 deletions(-) create mode 100644 world/src/site2/util/gradient.rs create mode 100644 world/src/site2/util/mod.rs diff --git a/world/src/site2/gen.rs b/world/src/site2/gen.rs index a59d895873..8d04340585 100644 --- a/world/src/site2/gen.rs +++ b/world/src/site2/gen.rs @@ -1,6 +1,7 @@ use super::*; use crate::{ block::block_from_structure, + site2::util::Dir, util::{RandomField, Sampler}, }; use common::{ @@ -27,13 +28,13 @@ pub enum Primitive { Ramp { aabb: Aabb, inset: i32, - dir: u8, + dir: Dir, }, Gable { aabb: Aabb, inset: i32, // X axis parallel or Y axis parallel - dir: bool, + dir: Dir, }, Cylinder(Aabb), Cone(Aabb), @@ -92,6 +93,7 @@ pub enum Fill { Sprite(SpriteKind), Block(Block), Brick(BlockKind, Rgb, u8), + Gradient(util::gradient::Gradient, BlockKind), // TODO: the offset field for Prefab is a hack that breaks the compositionality of Translate, // we probably need an evaluator for the primitive tree that gets which point is queried at // leaf nodes given an input point to make Translate/Rotate work generally @@ -115,19 +117,19 @@ impl Fill { Primitive::Ramp { aabb, inset, dir } => { let inset = (*inset).max(aabb.size().reduce_min()); let inner = match dir { - 0 => Aabr { + Dir::X => Aabr { min: Vec2::new(aabb.min.x - 1 + inset, aabb.min.y), max: Vec2::new(aabb.max.x, aabb.max.y), }, - 1 => Aabr { + Dir::NegX => Aabr { min: Vec2::new(aabb.min.x, aabb.min.y), max: Vec2::new(aabb.max.x - inset, aabb.max.y), }, - 2 => Aabr { + Dir::Y => Aabr { min: Vec2::new(aabb.min.x, aabb.min.y - 1 + inset), max: Vec2::new(aabb.max.x, aabb.max.y), }, - _ => Aabr { + Dir::NegY => Aabr { min: Vec2::new(aabb.min.x, aabb.min.y), max: Vec2::new(aabb.max.x, aabb.max.y - inset), }, @@ -156,7 +158,7 @@ impl Fill { }, Primitive::Gable { aabb, inset, dir } => { let inset = (*inset).max(aabb.size().reduce_min()); - let inner = if *dir { + let inner = if dir.is_y() { Aabr { min: Vec2::new(aabb.min.x - 1 + inset, aabb.min.y), max: Vec2::new(aabb.max.x - inset, aabb.max.y), @@ -270,6 +272,7 @@ impl Fill { .get((pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1)) % *range as u32) as u8, )), + Fill::Gradient(gradient, bk) => Some(Block::new(*bk, gradient.sample(pos.as_()))), Fill::Prefab(p, tr, seed) => p.get(pos - tr).ok().and_then(|sb| { let col_sample = canvas_info.col(canvas_info.wpos)?; block_from_structure( @@ -403,22 +406,22 @@ impl Painter { self.prim(Primitive::Ramp { aabb, inset, - dir: 0, + dir: Dir::X, }) .intersect(self.prim(Primitive::Ramp { aabb, inset, - dir: 1, + dir: Dir::NegX, })) .intersect(self.prim(Primitive::Ramp { aabb, inset, - dir: 2, + dir: Dir::Y, })) .intersect(self.prim(Primitive::Ramp { aabb, inset, - dir: 3, + dir: Dir::NegY, })) } @@ -460,6 +463,11 @@ impl<'a> PrimitiveRef<'a> { pub fn fill(self, fill: Fill) { self.painter.fill(self, fill); } pub fn clear(self) { self.painter.fill(self, Fill::Block(Block::empty())); } + + pub fn sample(self, sampling: impl Fn(Vec3) -> bool + 'static) -> PrimitiveRef<'a> { + self.painter + .prim(Primitive::sampling(self, Box::new(sampling))) + } } pub trait Structure { diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 51f51d4b1d..de2c8beba3 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -1,6 +1,7 @@ mod gen; pub mod plot; mod tile; +pub mod util; use self::tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE}; pub use self::{ diff --git a/world/src/site2/plot/house.rs b/world/src/site2/plot/house.rs index 0d7c8457f8..fe04481bda 100644 --- a/world/src/site2/plot/house.rs +++ b/world/src/site2/plot/house.rs @@ -1,5 +1,5 @@ use super::*; -use crate::Land; +use crate::{site2::util::Dir, Land}; use common::terrain::{Block, BlockKind, SpriteKind}; use rand::prelude::*; use vek::*; @@ -118,7 +118,7 @@ impl Structure for House { .with_z(alt + roof + roof_height), }, inset: roof_height, - dir: true, + dir: Dir::Y, }), painter.prim(Primitive::Gable { aabb: Aabb { @@ -135,7 +135,7 @@ impl Structure for House { .with_z(alt + roof + roof_height - 1), }, inset: roof_height - 1, - dir: true, + dir: Dir::Y, }), ) }, @@ -154,7 +154,7 @@ impl Structure for House { .with_z(alt + roof + roof_height), }, inset: roof_height, - dir: false, + dir: Dir::X, }), painter.prim(Primitive::Gable { aabb: Aabb { @@ -171,7 +171,7 @@ impl Structure for House { .with_z(alt + roof + roof_height - 1), }, inset: roof_height - 1, - dir: false, + dir: Dir::X, }), ) }, @@ -190,7 +190,7 @@ impl Structure for House { .with_z(alt + roof + roof_height), }, inset: roof_height, - dir: true, + dir: Dir::Y, }), painter.prim(Primitive::Gable { aabb: Aabb { @@ -203,7 +203,7 @@ impl Structure for House { .with_z(alt + roof + roof_height - 1), }, inset: roof_height - 1, - dir: true, + dir: Dir::Y, }), ), _ => ( @@ -221,7 +221,7 @@ impl Structure for House { .with_z(alt + roof + roof_height), }, inset: roof_height, - dir: false, + dir: Dir::X, }), painter.prim(Primitive::Gable { aabb: Aabb { @@ -237,7 +237,7 @@ impl Structure for House { .with_z(alt + roof + roof_height - 1), }, inset: roof_height - 1, - dir: false, + dir: Dir::X, }), ), }; @@ -905,7 +905,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey, - dir: 3, + dir: Dir::NegY, }), 1 => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -921,7 +921,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey, - dir: 1, + dir: Dir::NegX, }), 2 => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -937,7 +937,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey, - dir: 2, + dir: Dir::Y, }), _ => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -953,7 +953,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey, - dir: 0, + dir: Dir::X, }), }; let shed_empty = match self.front { @@ -971,7 +971,7 @@ impl Structure for House { .with_z(alt + height - 1), }, inset: storey - 1, - dir: 3, + dir: Dir::NegY, }), 1 => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -987,7 +987,7 @@ impl Structure for House { .with_z(alt + height - 1), }, inset: storey - 1, - dir: 1, + dir: Dir::NegX, }), 2 => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -1003,7 +1003,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey - 1, - dir: 2, + dir: Dir::Y, }), _ => painter.prim(Primitive::Ramp { aabb: Aabb { @@ -1019,7 +1019,7 @@ impl Structure for House { .with_z(alt + height), }, inset: storey - 1, - dir: 0, + dir: Dir::X, }), }; painter.fill( @@ -1232,7 +1232,7 @@ impl Structure for House { .with_z(alt + height + 1), }, inset: 3, - dir: true, + dir: Dir::Y, }), 1 => painter.prim(Primitive::Gable { aabb: Aabb { @@ -1245,7 +1245,7 @@ impl Structure for House { .with_z(alt + height + 1), }, inset: 3, - dir: false, + dir: Dir::X, }), 2 => painter.prim(Primitive::Gable { aabb: Aabb { @@ -1258,7 +1258,7 @@ impl Structure for House { .with_z(alt + height + 1), }, inset: 3, - dir: true, + dir: Dir::Y, }), _ => painter.prim(Primitive::Gable { aabb: Aabb { @@ -1271,7 +1271,7 @@ impl Structure for House { .with_z(alt + height + 1), }, inset: 3, - dir: false, + dir: Dir::X, }), }; let window_min = match self.front { @@ -1494,7 +1494,7 @@ impl Structure for House { max: Vec2::new(stair_origin.x + 10, stair_origin.y + stair_width).with_z(alt + previous_height + 1), }, inset: storey, - dir: 0, + dir: Dir::X, }) /*}, 1 => { @@ -1573,7 +1573,7 @@ impl Structure for House { max: Vec2::new(stair_origin.x + 8, stair_origin.y + 2 * stair_width).with_z(alt + previous_height + 1), }, inset: storey, - dir: 1, + dir: Dir::NegX, }) /*}, 1 => { diff --git a/world/src/site2/util/gradient.rs b/world/src/site2/util/gradient.rs new file mode 100644 index 0000000000..ffdf9f3a0f --- /dev/null +++ b/world/src/site2/util/gradient.rs @@ -0,0 +1,92 @@ +use vek::*; + +/// A wrapping mode, used to determine what to do when sampling outside of 0..=1 +#[derive(Clone, Copy)] +pub enum WrapMode { + /// ..............______ + /// No repeat ___/ + Clamp, + /// Saw wave repeat / / / / + Repeat, + /// Triangle wave repeat /\/\/\/\/ + PingPong, +} + +impl WrapMode { + fn sample(&self, t: f32) -> f32 { + match self { + WrapMode::Clamp => t.clamp(0.0, 1.0), + WrapMode::Repeat => (1.0 + t.fract()).fract(), + WrapMode::PingPong => 1.0 - 2.0 * ((t / 2.0).fract().abs() - 0.5).abs(), + } + } +} + +#[derive(Clone, Copy)] +pub enum Shape { + Point, + /// Vector should be normalized for Gradient size to work properly + Plane(Vec3), + /// Vector should be normalized for Gradient size to work properly + Line(Vec3), +} + +impl Shape { + /// Create a new plane shape with the given normal. + pub fn plane(normal: Vec3) -> Self { Shape::Plane(normal.normalized()) } + + /// Create an infinite line shape with the given direction. + pub fn radial_line(direction: Vec3) -> Self { Shape::Line(direction.normalized()) } +} + +#[derive(Clone)] +pub struct Gradient { + /// The center of the gradient shape + pub(super) center: Vec3, + /// The distance the gradient is sampled along + pub(super) size: f32, + /// The shape that the distance is computed to to get the gradient color. + pub(super) shape: Shape, + /// How the graduint should repeat when the distance from the shape is + /// greater than size + pub(super) repeat: WrapMode, + /// The colors the gradient is lerped between + pub(super) colors: (Rgb, Rgb), +} + +impl Gradient { + pub fn new(center: Vec3, size: f32, shape: Shape, colors: (Rgb, Rgb)) -> Self { + Gradient { + center, + size, + shape, + repeat: WrapMode::Clamp, + colors, + } + } + + /// Add a repeat mode to the gradient + #[must_use] + pub fn with_repeat(mut self, repeat: WrapMode) -> Self { + self.repeat = repeat; + self + } + + /// Sample the gradient at a certain point, will always return a color + /// that's in the range color.0..=color.1 + pub fn sample(&self, pos: Vec3) -> Rgb { + // Calculate t by dividing the distance from the shape divided by size + let t = self.repeat.sample(match self.shape { + Shape::Point => pos.distance(self.center) / self.size, + Shape::Plane(normal) => (pos - self.center).dot(normal) / self.size, + Shape::Line(line) => { + let u = pos - self.center; + (u.dot(line) * line - u).magnitude() / self.size + }, + }); + // Lerp colors + self.colors.0.map2(self.colors.1, |a, b| { + (a as f32 * (1.0 - t) + b as f32 * t) as u8 + }) + } +} diff --git a/world/src/site2/util/mod.rs b/world/src/site2/util/mod.rs new file mode 100644 index 0000000000..09624a08e6 --- /dev/null +++ b/world/src/site2/util/mod.rs @@ -0,0 +1,140 @@ +pub mod gradient; + +use rand::Rng; +use vek::*; + +/// A 2d direction. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Dir { + X, + Y, + NegX, + NegY, +} + +impl Dir { + pub fn choose(rng: &mut impl Rng) -> Dir { + match rng.gen_range(0..4) { + 0 => Dir::X, + 1 => Dir::Y, + 3 => Dir::NegX, + _ => Dir::NegY, + } + } + + pub fn from_vector(vec: Vec2) -> Dir { + if vec.x.abs() > vec.y.abs() { + if vec.x > 0 { Dir::X } else { Dir::NegX } + } else if vec.y > 0 { + Dir::Y + } else { + Dir::NegY + } + } + + #[must_use] + pub fn opposite(self) -> Dir { + match self { + Dir::X => Dir::NegX, + Dir::NegX => Dir::X, + Dir::Y => Dir::NegY, + Dir::NegY => Dir::Y, + } + } + + /// Rotate the direction anti clock wise + #[must_use] + pub fn rotate_left(self) -> Dir { + match self { + Dir::X => Dir::Y, + Dir::NegX => Dir::NegY, + Dir::Y => Dir::NegX, + Dir::NegY => Dir::X, + } + } + + /// Rotate the direction clock wise + #[must_use] + pub fn rotate_right(self) -> Dir { + match self { + Dir::X => Dir::NegY, + Dir::NegX => Dir::Y, + Dir::Y => Dir::X, + Dir::NegY => Dir::NegX, + } + } + + pub fn to_vec2(self) -> Vec2 { + match self { + Dir::X => Vec2::new(1, 0), + Dir::NegX => Vec2::new(-1, 0), + Dir::Y => Vec2::new(0, 1), + Dir::NegY => Vec2::new(0, -1), + } + } + + pub fn to_vec3(self) -> Vec3 { + match self { + Dir::X => Vec3::new(1, 0, 0), + Dir::NegX => Vec3::new(-1, 0, 0), + Dir::Y => Vec3::new(0, 1, 0), + Dir::NegY => Vec3::new(0, -1, 0), + } + } + + /// Returns a 3x3 matrix that rotates Vec3(1, 0, 0) to the direction you get + /// in to_vec3. Inteded to be used with Primitive::Rotate. + /// + /// Example: + /// ``` + /// use vek::Vec3; + /// use veloren_world::site2::util::Dir; + /// let dir = Dir::X; + /// + /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// + /// let dir = Dir::NegX; + /// + /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// + /// let dir = Dir::Y; + /// + /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// + /// let dir = Dir::NegY; + /// + /// assert_eq!(dir.to_mat3x3() * Vec3::new(1, 0, 0), dir.to_vec3()); + /// ``` + pub fn to_mat3x3(self) -> Mat3 { + match self { + Dir::X => Mat3::new(1, 0, 0, 0, 1, 0, 0, 0, 1), + Dir::NegX => Mat3::new(-1, 0, 0, 0, -1, 0, 0, 0, 1), + Dir::Y => Mat3::new(0, -1, 0, 1, 0, 0, 0, 0, 1), + Dir::NegY => Mat3::new(0, 1, 0, -1, 0, 0, 0, 0, 1), + } + } + + /// Translates this direction to worldspace as if it was relative to the + /// other direction + #[must_use] + pub fn relative_to(self, other: Dir) -> Dir { + match other { + Dir::X => self, + Dir::NegX => self.opposite(), + Dir::Y => self.rotate_right(), + Dir::NegY => self.rotate_left(), + } + } + + /// Is this direction parallel to x + pub fn is_x(self) -> bool { matches!(self, Dir::X | Dir::NegX) } + + /// Is this direction parallel to y + pub fn is_y(self) -> bool { matches!(self, Dir::Y | Dir::NegY) } +} + +impl std::ops::Neg for Dir { + type Output = Dir; + + fn neg(self) -> Self::Output { self.opposite() } +}