#![cfg_attr(feature = "ci", deny(warnings))]
mod gpu;
mod noise_traits;
mod raster;
mod render;
pub mod attributes;
pub mod canvas;
pub mod forms;
pub mod paint;
pub mod path;
pub mod shaders;
pub mod transforms;
pub mod uniforms;
pub mod prelude {
pub use self::{
attributes::*, canvas::*, forms::*, paint::*, path::*, shaders::*, transforms::*,
uniforms::*,
};
pub use super::*;
pub use euclid;
pub use noise::{self, *};
pub use noise_traits::*;
pub use rayon::{self, prelude::*};
pub use palette::{
self, encoding::Srgb, Alpha, Blend, ComponentWise, Hue, IntoColor, LinSrgb, LinSrgba,
Saturate, *,
};
pub use rand::{self, rngs::StdRng, Rng, SeedableRng};
pub use structopt::StructOpt;
pub use std::f32::consts::PI;
}
pub use self::{
gpu::{Gpu, Shader},
render::Context,
shaders::ShaderProgram,
};
use self::{gpu::*, prelude::*, raster::Method};
use euclid::{Point3D, Size2D, UnknownUnit, Vector2D, Vector3D};
use failure::Error;
use lyon_path::math::Point;
use render::*;
use std::{path::PathBuf, time::Duration};
pub type P2 = Point;
pub type P3 = Point3D<f32, UnknownUnit>;
pub type V2 = Vector2D<f32, UnknownUnit>;
pub type V3 = Vector3D<f32, UnknownUnit>;
pub type S2 = Size2D<f32, UnknownUnit>;
pub type Angle = euclid::Angle<f32>;
pub type Program = glium::program::Program;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(StructOpt, Debug, Clone)]
#[structopt(name = "valora")]
pub struct Options {
#[structopt(flatten)]
pub world: World,
#[structopt(short = "b", long = "brainstorm")]
pub brainstorm: bool,
#[structopt(short = "d", long = "delay", default_value = "0")]
pub delay: usize,
#[structopt(short = "o", long = "output", parse(from_os_str))]
pub output: Option<PathBuf>,
}
#[derive(StructOpt, Debug, Copy, Clone)]
#[structopt(name = "world")]
pub struct World {
#[structopt(short = "e", long = "seed", default_value = "0")]
pub seed: u64,
#[structopt(short = "w", long = "width", default_value = "512")]
pub width: f32,
#[structopt(short = "h", long = "height", default_value = "650")]
pub height: f32,
#[structopt(short = "s", long = "scale", default_value = "1.0")]
pub scale: f32,
#[structopt(short = "f", long = "frames")]
pub frames: Option<usize>,
#[structopt(short = "r", long = "frames_per_second", default_value = "24")]
pub framerate: usize,
}
impl World {
pub fn normalize(&self, p: P2) -> P2 {
P2::new(p.x / self.width, p.y / self.height)
}
pub fn center(&self) -> P2 {
P2::new(self.width / 2.0, self.height / 2.0)
}
pub fn rect(&self) -> Rect {
Rect {
bottom_left: P2::new(0., 0.),
width: self.width,
height: self.height,
}
}
}
impl Paint for World {
fn paint(&self, comp: &mut Canvas) {
comp.line_to(P2::new(0.0, 0.0));
comp.line_to(P2::new(self.width, 0.0));
comp.line_to(P2::new(self.width, self.height));
comp.line_to(P2::new(0.0, self.height));
comp.line_to(P2::new(0.0, 0.0));
}
}
pub trait Artist: Sized {
fn setup(gpu: Gpu, world: World, rng: &mut StdRng) -> Result<Self>;
fn paint(&mut self, ctx: Context, canvas: &mut Canvas);
}
pub fn run_fn<F>(options: Options, f: impl Fn(Gpu, World, &mut StdRng) -> Result<F>) -> Result<()>
where
F: FnMut(Context, &mut Canvas),
{
let (output_width, output_height) = (
(options.world.width as f32 * options.world.scale) as u32,
(options.world.height as f32 * options.world.scale) as u32,
);
let number_width = options
.world
.frames
.unwrap_or(0)
.to_string()
.chars()
.count();
let (gpu, mut strategy) = if let Some(base_path) = options.output.clone() {
let (gpu, _) = Gpu::new()?;
let buffer = gpu.build_texture(output_width, output_height)?;
std::fs::create_dir_all(&base_path)
.expect(&format!("To create save directory {}", base_path.display()));
(
gpu,
RenderStrategy::File {
buffer,
output_path: move |frame_number: usize, seed: u64| {
let mut base_path = base_path.clone();
base_path.push(format!(
"{}_{number:>0width$}.png",
seed,
number = frame_number,
width = number_width
));
base_path
},
},
)
} else {
let (gpu, events_loop, (screen_width, screen_height)) =
Gpu::with_window(output_width, output_height)?;
let buffer = gpu.build_texture(screen_width, screen_height)?;
let wait = Duration::from_secs_f64(1. / options.world.framerate as f64);
let gpu_clone = gpu.clone();
let texture_glsl = include_str!("shaders/texture.frag");
let texture_program = gpu.compile_glsl(texture_glsl)?;
(
gpu,
RenderStrategy::Screen {
events_loop,
wait,
buffer,
texture_program,
get_frame: move || {
gpu_clone
.get_frame()
.expect("To get frame from windowed gpu")
},
},
)
};
let mut current_seed = options.world.seed;
let mut render_count = 0;
loop {
let mut rng = StdRng::seed_from_u64(current_seed);
let mut paint_fn = f(gpu.clone(), options.world, &mut rng)?;
let mut renderer = Renderer {
strategy: &mut strategy,
gpu: &gpu,
options: Options {
world: World {
seed: current_seed,
frames: match (options.brainstorm, options.output.as_ref()) {
(true, Some(_)) => Some(1),
_ => options.world.frames,
},
..options.world
},
..options.clone()
},
rng: &mut rng,
output_width: output_width,
output_height: output_height,
};
let report = renderer.render_frames(|ctx, canvas| paint_fn(ctx, canvas))?;
if let Some(rebuild) = report.rebuild {
match rebuild {
Rebuild::NewSeed(new_seed) => {
current_seed = new_seed;
}
}
} else if options.brainstorm
&& options.output.is_some()
&& render_count < options.world.frames.unwrap_or(usize::max_value())
{
current_seed += 1;
} else if report.explicit_quit || !options.brainstorm || options.output.is_some() {
break;
}
render_count += 1;
}
Ok(())
}
pub fn run<A: Artist>(options: Options) -> Result<()> {
run_fn(options, |gpu, world, rng| {
let mut artist = A::setup(gpu, world, rng)?;
Ok(move |ctx: Context, canvas: &mut Canvas| artist.paint(ctx, canvas))
})
}