use hex; use immense::*; use itertools::iproduct; use lazy_static::lazy_static; use noise::{Fbm, NoiseFn}; use palette::encoding::pixel::Pixel; use palette::encoding::srgb::Srgb; use palette::rgb::Rgb; use rand::seq::SliceRandom; use rand::thread_rng; use rand::Rng; use std::fs::File; use std::io::BufWriter; use std::rc::Rc; const SPHERE_RESOLUTION: usize = 0; lazy_static! { static ref PALETTE: [Hsv; 5] = [ hexcolor("4F4052"), hexcolor("6D7577"), hexcolor("95A8A9"), hexcolor("A8C4BE"), hexcolor("AFD8DB"), ]; } fn hexcolor(hex: &str) -> Hsv { let bytes: Vec = hex::decode(hex).expect("raw bytes from hex"); let fmt = |i| bytes[i] as f32 / 255.0; let color: Rgb = *Rgb::from_raw(&[fmt(0), fmt(1), fmt(2)]); Hsv::from(color) } trait Tilable: Clone + 'static { fn to_tile(&self, row: usize, col: usize) -> Rule; } struct GridTile { row: usize, col: usize, tilable: T, } impl ToRule for GridTile { fn to_rule(&self) -> Rule { self.tilable.to_tile(self.row, self.col) } } fn grid(rows: usize, cols: usize, rule: impl Tilable) -> Rule { rule![ tf![Tf::tx((cols - 1) as f32 / -2.0), Tf::tz((rows - 1) as f32 / -2.0)] => iproduct!(0..rows, 0..cols).fold(Rule::new(), |root_rule, (r, c)| { root_rule.push(tf![ Tf::tx(c as f32), Tf::tz(r as f32) ], GridTile { row: r, col: c, tilable: rule.clone() }) }) ] } #[derive(Clone)] struct Pyramid { levels: usize, sphere: Rc, } impl ToRule for Pyramid { fn to_rule(&self) -> Rule { (0..self.levels).fold(Rule::new(), |rule, i| { let target_downscale = 1.0 - ((i + 1) as f32 / self.levels as f32); rule.push( tf![ Tf::sby( target_downscale, ((1.0 / self.levels as f32) * 1.6).powi(2), target_downscale ), Tf::ty(i as f32), Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()) ], (*&[cube(), self.sphere.to_rule()] .choose(&mut thread_rng()) .unwrap()) .clone(), ) }) } } #[derive(Clone)] struct Tower; impl ToRule for Tower { fn to_rule(&self) -> Rule { let thin = 0.002; let bars = thread_rng().gen_range(4, 20); let height = 0.03 * (1.0 / bars as f32); let bar = rule![ tf![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()), Tf::tx(-0.5), Tf::sby(thin, height, 1.0)] => cube(), tf![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()), Tf::tx(0.5), Tf::sby(thin, height, 1.0)] => cube(), tf![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()), Tf::tz(-0.5), Tf::sby(1.0, height, thin)] => cube(), tf![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()), Tf::tz(0.5), Tf::sby(1.0, height, thin)] => cube(), tf![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()), Tf::ty(0.0), Tf::s(0.2)] => icosphere(), ]; rule![ Replicate::n(bars, Tf::ty(height * 13.0)) => bar, ] } } #[derive(Clone)] struct CityBlock { sphere: Rc, noise: Fbm, depth: usize, } impl Tilable for CityBlock { fn to_tile(&self, row: usize, col: usize) -> Rule { let division = (self.noise.get([row as f64, col as f64]).abs() * 10.0) as usize + 4; let mut candidates = vec![ rule![None => Pyramid { levels: division, sphere: self.sphere.clone(), }], rule![None => Tower {}], ]; if self.depth < 3 { candidates.push(rule![tf![Tf::sby(0.5, 0.5, 0.5)] => grid( 2, 2, CityBlock { sphere: self.sphere.clone(), noise: self.noise.clone(), depth: self.depth + 1, }, )]); } let mut rng = thread_rng(); (&candidates).choose(&mut rng).unwrap().clone() } } #[derive(Copy, Clone)] struct Wire; impl Tilable for Wire { fn to_tile(&self, _: usize, _: usize) -> Rule { let height = 0.03; let thin = 0.05; rule![Tf::color(*PALETTE.choose(&mut thread_rng()).unwrap()) => rule![ tf![Tf::tx(-0.5), Tf::sby(thin, height, 1.0)] => cube(), tf![Tf::tx(0.5), Tf::sby(thin, height, 1.0)] => cube(), tf![Tf::tz(-0.5), Tf::sby(1.0, height, thin)] => cube(), tf![Tf::tz(0.5), Tf::sby(1.0, height, thin)] => cube() ]] } } fn main() { let grid_size = 10; let meshes = rule![ tf![ Tf::sby(grid_size as f32, 1.0, grid_size as f32), Tf::ty(-0.5), Tf::color(Hsv::new(0.0, 0.0, 1.0)) ] => cube(), None => grid(grid_size, grid_size, CityBlock { sphere: sphere(SPHERE_RESOLUTION), noise: Fbm::new(), depth: 0 }), None => grid(grid_size, grid_size, Wire {}) ] .generate(); let output = File::create("pyramid.obj").expect("obj file"); write_meshes( ExportConfig { grouping: MeshGrouping::ByColor, export_colors: Some(String::from("pyramid_colors.mtl")), }, meshes, &mut BufWriter::new(output), ) .expect("rendered scene"); }