Compare commits

...

12 Commits

Author SHA1 Message Date
annieversary b21d7d56b3 subtitled #11 2021-08-28 00:21:10 +02:00
annieversary c1fef9993b [#10] add more attractor types 2021-08-28 00:20:46 +02:00
annieversary e315d00bb3 move draw_exterior into utils 2021-08-28 00:20:17 +02:00
annieversary 906e2a66c5 changes to #11 2021-08-25 18:01:34 +02:00
annieversary c39dec8f61 subtitled #10 2021-08-24 23:54:06 +02:00
annieversary fd5c7fa77d subtitled #9 2021-08-24 23:03:09 +02:00
annieversary c35df147be utils 2021-08-24 20:46:57 +02:00
annieversary 99dddfde57 subtitled #8 2021-08-24 20:41:36 +02:00
annieversary 5d0692e8e3 fix colors on #7 2021-08-23 09:35:41 +02:00
annieversary 4e695573d6 add subtitled #7 2021-08-22 16:40:06 +02:00
annieversary ca5e405a7e update recording.sh 2021-08-21 17:59:46 +02:00
annieversary 6e73e65dfc finish subtitled5 2021-08-21 17:59:21 +02:00
20 changed files with 1122 additions and 43 deletions

40
Cargo.lock generated
View File

@ -2348,6 +2348,22 @@ dependencies = [
"utils",
]
[[package]]
name = "subtitled10"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled11"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled2"
version = "0.1.0"
@ -2388,6 +2404,30 @@ dependencies = [
"utils",
]
[[package]]
name = "subtitled7"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled8"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled9"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "syn"
version = "1.0.74"

View File

@ -0,0 +1,8 @@
[package]
name = "subtitled10"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,171 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
// http://paulbourke.net/fractals/clifford/
// http://paulbourke.net/fractals/peterdejong/
#[derive(Clone, Copy, Debug)]
enum Attractor {
Clifford,
DeJong,
}
impl Attractor {
fn random() -> Self {
match random_range(0, 2) {
0 => Self::Clifford,
_ => Self::DeJong,
}
}
}
#[derive(Clone, Copy, Debug)]
struct Params {
a: f32,
b: f32,
c: f32,
d: f32,
attractor: Attractor,
}
struct Model {
points: Vec<Vec2>,
base_hue: f32,
/// params for the attractor
params: Params,
// modulation values for each param
a_mul: f32,
a_add: f32,
b_mul: f32,
b_add: f32,
c_mul: f32,
c_add: f32,
d_mul: f32,
d_add: f32,
// max range for each param
// this would be a const, but acos isn't a const fn
min_a: f32,
max_a: f32,
min_b: f32,
max_b: f32,
min_c: f32,
max_c: f32,
min_d: f32,
max_d: f32,
}
fn model(_app: &App) -> Model {
Model {
params: Params {
a: 0.0,
b: 0.0,
c: 0.0,
d: 0.0,
attractor: Attractor::random(),
},
a_mul: random_range(0.3, 1.0) * random_range(-1.0, 1.0).signum(),
b_mul: random_range(0.3, 1.0) * random_range(-1.0, 1.0).signum(),
c_mul: random_range(0.3, 1.0) * random_range(-1.0, 1.0).signum(),
d_mul: random_range(0.3, 1.0) * random_range(-1.0, 1.0).signum(),
a_add: random_range(0.3, 2.0),
b_add: random_range(0.3, 2.0),
c_add: random_range(0.3, 2.0),
d_add: random_range(0.3, 2.0),
// magic numbers from here:
// http://paulbourke.net/fractals/clifford/paul_richards/main.cpp
min_a: f32::acos(1.6 / 2.0),
max_a: f32::acos(1.3 / 2.0),
min_b: f32::acos(-0.6 / 2.0),
max_b: f32::acos(1.7 / 2.0),
min_c: f32::acos(-1.2 / 2.0),
max_c: f32::acos(0.5 / 2.0),
min_d: f32::acos(1.6 / 2.0),
max_d: f32::acos(1.4 / 2.0),
base_hue: random_range(0., 360.0),
points: vec![Vec2::splat(0.1); 10000],
}
}
fn advance(
Params {
a,
b,
c,
d,
attractor,
}: Params,
point: Vec2,
) -> Vec2 {
let x = point.x;
let y = point.y;
// clifford attractor
// xn+1 = sin(a yn) + c cos(a xn)
// yn+1 = sin(b xn) + d cos(b yn)
// de Jong
// xn+1 = sin(a yn) - cos(b xn)
// yn+1 = sin(c xn) - cos(d yn)
match attractor {
Attractor::Clifford => vec2(
(a * y).sin() + (a * x).cos() * c,
(b * x).sin() + (b * y).cos() * d,
),
Attractor::DeJong => vec2((a * y).sin() - (b * x).cos(), (c * x).sin() - (d * y).cos()),
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 60.0;
// modulate params
model.params.a = map_sin(t * model.a_mul + model.a_add, model.min_a, model.max_a);
model.params.b = map_sin(t * model.b_mul + model.b_add, model.min_b, model.max_b);
model.params.c = map_sin(t * model.c_mul + model.c_add, model.min_c, model.max_c);
model.params.d = map_sin(t * model.d_mul + model.d_add, model.min_d, model.max_d);
// advance all points in list
let mut last = model.points.last().unwrap().clone();
for v in &mut model.points {
last = advance(model.params, last);
*v = last;
}
// move base_hue
model.base_hue += 0.5;
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(BLACK);
} else {
let win = app.window_rect();
draw.rect().wh(win.wh()).color(srgba(0., 0.0, 0.0, 0.02));
}
for &p in &model.points {
let h = random_range(model.base_hue, model.base_hue + 60.0) / 360.0;
draw.ellipse()
.radius(0.2)
.xy(150.0 * p)
.color(hsla(h.fract(), 1.0, 0.5, 0.1));
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

@ -0,0 +1,8 @@
[package]
name = "subtitled11"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,159 @@
use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
automata: Automata,
row: usize,
cube: Vec<Vec4>,
}
fn model(_app: &App) -> Model {
// generate all the points in the cube
let mut cube = vec![];
let p = [-1.0, 1.0];
for &x in &p {
for &y in &p {
for &z in &p {
for &w in &p {
cube.push(vec4(x, y, z, w));
}
}
}
}
Model {
automata: Automata::new(),
row: 0,
cube,
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
if app.elapsed_frames() % 10 == 0 {
model.automata.advance();
model.row += 1;
}
}
fn rot(angle: f32) -> Mat4 {
// 4d rotations https://math.stackexchange.com/questions/1402362/rotation-in-4d
let m = mat4(
vec4(angle.cos(), angle.sin(), 0.0, 0.0),
vec4(-angle.sin(), angle.cos(), 0.0, 0.0),
vec4(0.0, 0.0, 1.0, 0.0),
vec4(0.0, 0.0, 0.0, 1.0),
) * mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, angle.cos(), angle.sin(), 0.0),
vec4(0.0, -angle.sin(), angle.cos(), 0.0),
vec4(0.0, 0.0, 0.0, 1.0),
);
let angle = -angle * 2.0;
m * mat4(
vec4(1.0, 0.0, 0.0, 0.0),
vec4(0.0, 1.0, 0.0, 0.0),
vec4(0.0, 0.0, angle.cos(), angle.sin()),
vec4(0.0, 0.0, -angle.sin(), angle.cos()),
)
}
fn view(app: &App, model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 60.0;
// background
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(SNOW);
} else {
let win = app.window_rect();
draw.rect()
.wh(win.wh())
.color(srgba(1.0, 250.0 / 255.0, 250.0 / 255.0, 0.01));
}
// draw automata
let (l, top, w, h) = app.window_rect().l_t_w_h();
const BALL_SIZE: f32 = 6.0;
let cols = w / BALL_SIZE;
let rows = h / BALL_SIZE;
for (i, &b) in model.automata.points.iter().enumerate() {
if b && i as f32 <= cols {
let x = l + (i as f32 % cols) * BALL_SIZE;
let y = top - (model.row as f32 % rows) * BALL_SIZE;
let p = vec2(x, y);
let h = model.row as f32 / 360.0;
draw.ellipse()
.radius(BALL_SIZE / 2.0)
.xy(p)
.color(hsl(h.fract(), 0.5, 0.5));
}
}
// draw center square
draw.rect().w_h(400.0, 400.0).color(SNOW);
// draw 4d cube
let p: Vec<_> = model
.cube
.iter()
.map(|&v| {
// rotate
let v = rot(t) * v;
// project onto xz plane cause it's the coolest one
100.0 * vec2(v.x, v.z)
})
.collect::<Vec<_>>();
// make all pairs of points
let p = p
.iter()
.enumerate()
.flat_map(|(i, a)| p[i + 1..].iter().map(move |b| (a, b)));
for (&a, &b) in p {
draw.line().points(a, b).color(BLACK);
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}
struct Automata {
points: Vec<bool>,
rule: Vec<bool>,
}
impl Automata {
fn new() -> Self {
let points = (0..200).map(|_| random::<bool>()).collect();
let rule = (0..8).map(|_| random::<bool>()).collect();
Self { points, rule }
}
fn advance(&mut self) {
for i in 0..self.points.len() {
let i1 = (i + 1) % self.points.len();
let im1 = if i == 0 { self.points.len() - 1 } else { i - 1 };
let v = (self.points[im1], self.points[i], self.points[i1]);
self.points[i] = match v {
(false, false, false) => self.rule[0],
(false, false, true) => self.rule[1],
(false, true, false) => self.rule[2],
(false, true, true) => self.rule[3],
(true, false, false) => self.rule[4],
(true, false, true) => self.rule[5],
(true, true, false) => self.rule[6],
(true, true, true) => self.rule[7],
}
}
}
}

View File

@ -34,7 +34,7 @@ fn view(app: &App, _model: &Model, frame: Frame) {
let draw = app.draw();
if frame.nth() == 1 {
if frame.nth() == 0 {
draw.background().color(BG);
} else {
let win = app.window_rect();

View File

@ -9,17 +9,25 @@ fn main() {
struct Model {
shape: Shapes,
count: usize,
}
fn model(_app: &App) -> Model {
Model {
shape: Shapes::new(),
shape: Shapes::new(0),
count: 0,
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
if app.elapsed_frames() % 60 == 0 {
model.shape = Shapes::new();
if app.elapsed_frames() % 60 == 0 && app.elapsed_frames() > 1 {
if model.count >= 20 {
std::process::exit(0);
}
model.shape = Shapes::new(random_range(1, 7));
model.count += 1;
}
}
@ -29,7 +37,7 @@ fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(BLACK);
} else if frame.nth() % 60 > 40 {
} else if frame.nth() % 60 > 30 {
let win = app.window_rect();
draw.rect().wh(win.wh()).color(srgba(0.0, 0.0, 0.0, 0.2));
}

View File

@ -3,41 +3,115 @@ use nannou::prelude::*;
pub struct Shapes(Vec<Shape>);
impl Shapes {
pub fn draw(&self, draw: Draw) {
let w = 1.0;
for shape in &self.0 {
match shape {
Shape::Triangle { center, radius } => {
&Shape::Triangle {
center,
radius,
rotation,
} => {
let points = vec![vec2(0., 1.), vec2(0.866, -0.5), vec2(-0.866, -0.5)]
.into_iter()
.map(|v| v * *radius + *center)
.map(|v| {
vec2(
v.x * rotation.cos() - v.y * rotation.sin(),
v.x * rotation.sin() + v.y * rotation.cos(),
)
})
.map(|v| v * radius + center)
.collect::<Vec<_>>();
draw.polyline().weight(3.).points_closed(points).color(PINK);
draw.polyline().weight(w).points_closed(points).color(PINK);
}
Shape::Circle { center, radius } => {
Shape::Circle { center, radius, .. } => {
draw.ellipse()
.radius(*radius)
.no_fill()
.stroke_weight(3.)
.stroke_weight(w)
.xy(*center)
.stroke_color(PINK);
}
Shape::Line { center, radius } => {}
&Shape::Line {
center,
radius,
rotation,
} => {
let points = vec![vec2(-1.0, 0.), vec2(1.0, 0.0)]
.into_iter()
.map(|v| {
vec2(
v.x * rotation.cos() - v.y * rotation.sin(),
v.x * rotation.sin() + v.y * rotation.cos(),
)
})
.map(|v| v * radius + center)
.collect::<Vec<_>>();
draw.line()
.stroke_weight(w)
.start(points[0])
.end(points[1])
.color(PINK);
}
Shape::LongLine { center, radius, .. } => {
draw.line()
.stroke_weight(w)
.start(Vec2::ZERO)
.end(*center + center.normalize() * *radius)
.color(PINK);
}
&Shape::Square {
center,
radius,
rotation,
} => {
let points = vec![
vec2(1.0, 1.0),
vec2(-1.0, 1.0),
vec2(-1.0, -1.0),
vec2(1.0, -1.0),
]
.into_iter()
.map(|v| {
vec2(
v.x * rotation.cos() - v.y * rotation.sin(),
v.x * rotation.sin() + v.y * rotation.cos(),
)
})
.map(|v| v * radius + center)
.collect::<Vec<_>>();
draw.polyline()
.stroke_weight(w)
.points_closed(points)
.color(PINK);
}
}
}
}
pub fn new() -> Self {
pub fn new(children: usize) -> Self {
let root = Shape::random(Vec2::ZERO, 100.0);
let mut vec = vec![];
for _ in 0..3 {
for _ in 0..children {
let (c, r) = root.get_random_point_distance();
let s = Shape::random(c, r);
let children = random_range(0, 3);
let children = random_range(0, 5.min(children + 2));
for _ in 0..children {
let (c, r) = s.get_random_point_distance();
let s = Shape::random(c, r);
let children = random_range(0, 3.min(children + 1));
for _ in 0..children {
let (c, r) = s.get_random_point_distance();
let s = Shape::random(c, r);
vec.push(s);
}
vec.push(s);
}
@ -49,58 +123,144 @@ impl Shapes {
}
}
pub enum Shape {
Triangle { center: Vec2, radius: f32 },
Circle { center: Vec2, radius: f32 },
Line { center: Vec2, radius: f32 },
macro_rules! sh {
( $($id:ident),* ) => {
pub enum Shape {
$(
$id { center: Vec2, radius: f32, rotation: f32 },
)*
}
};
}
sh!(Triangle, Circle, Line, LongLine, Square);
fn random_sq_rot() -> f32 {
[0.0, PI / 4.0][random_range(0, 2)]
}
fn random_tri_rot() -> f32 {
[0.0, PI / 3.0][random_range(0, 2)]
}
fn random_line_rot() -> f32 {
[0.0, PI / 3.0, PI / 2.0, PI / 4.0, PI][random_range(0, 4)]
}
impl Shape {
fn random(center: Vec2, radius: f32) -> Self {
match random_range(0, 3) {
0 => Self::tri(center, radius),
1 => Self::line(center, radius),
_ => Self::circ(center, radius),
match random_range(0, 5) {
0 => Self::tri(center, radius, random_tri_rot()),
1 => Self::line(center, radius, random_line_rot()),
2 if center.distance(Vec2::ZERO) > 0.01 => Self::long_line(center, radius, 0.0),
3 => Self::square(center, radius, random_sq_rot()),
_ => Self::circ(center, radius, 0.0),
}
}
fn tri(center: Vec2, radius: f32) -> Self {
Self::Triangle { center, radius }
fn tri(center: Vec2, radius: f32, rotation: f32) -> Self {
Self::Triangle {
center,
radius,
rotation,
}
}
fn circ(center: Vec2, radius: f32) -> Self {
Self::Circle { center, radius }
fn circ(center: Vec2, radius: f32, rotation: f32) -> Self {
Self::Circle {
center,
radius,
rotation,
}
}
fn line(center: Vec2, radius: f32) -> Self {
Self::Line { center, radius }
fn line(center: Vec2, radius: f32, rotation: f32) -> Self {
Self::Line {
center,
radius,
rotation,
}
}
fn long_line(center: Vec2, radius: f32, rotation: f32) -> Self {
Self::LongLine {
center,
radius,
rotation,
}
}
fn square(center: Vec2, radius: f32, rotation: f32) -> Self {
Self::Square {
center,
radius,
rotation,
}
}
fn get_random_point_distance(&self) -> (Vec2, f32) {
match self {
Shape::Triangle { center, radius } => {
&Shape::Triangle {
center,
radius,
rotation,
} => {
let points = vec![vec2(0., 1.), vec2(0.866, -0.5), vec2(-0.866, -0.5)]
.into_iter()
.map(|v| v * *radius + *center)
.map(|v| {
vec2(
v.x * rotation.cos() - v.y * rotation.sin(),
v.x * rotation.sin() + v.y * rotation.cos(),
)
})
.map(|v| v * radius + center)
.collect::<Vec<_>>();
let denom = random_range(1.0, 5.0).floor();
let denom = random_range(1.0, 4.0).floor();
(points[random_range(0, points.len())], radius / denom)
}
Shape::Circle { center, radius } => {
&Shape::Circle { center, radius, .. } => {
let point = match random_range(0, 4) {
0 => *center + Vec2::X * *radius,
1 => *center - Vec2::X * *radius,
2 => *center + Vec2::Y * *radius,
_ => *center - Vec2::Y * *radius,
0 => center + Vec2::X * radius,
1 => center - Vec2::X * radius,
2 => center + Vec2::Y * radius,
_ => center - Vec2::Y * radius,
};
let denom = random_range(1.0, 5.0).floor();
let denom = random_range(1.0, 4.0).floor();
(point, radius / denom)
}
Shape::Line { center, radius } => {
let point = todo!("one of the two vertices");
&Shape::Line { center, radius, .. } => {
let point = match random_range(0, 2) {
0 => center - center.normalize() * radius,
_ => center + center.normalize() * radius,
};
let denom = random_range(1.0, 5.0).floor();
let denom = random_range(1.0, 4.0).floor();
(point, radius / denom)
}
&Shape::LongLine { center, radius, .. } => {
let denom = random_range(1.0, 4.0).floor();
(center + center.normalize() * radius, radius / denom)
}
&Shape::Square {
center,
radius,
rotation,
} => {
let points = vec![
vec2(1.0, 1.0),
vec2(-1.0, 1.0),
vec2(-1.0, -1.0),
vec2(1.0, -1.0),
]
.into_iter()
.map(|v| {
vec2(
v.x * rotation.cos() - v.y * rotation.sin(),
v.x * rotation.sin() + v.y * rotation.cos(),
)
})
.map(|v| v * radius + center)
.collect::<Vec<_>>();
let denom = random_range(1.0, 4.0).floor();
(points[random_range(0, 4)], radius / denom)
}
}
}
}

View File

@ -0,0 +1,8 @@
[package]
name = "subtitled7"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,151 @@
use nannou::prelude::*;
use std::collections::VecDeque;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
points: VecDeque<(Vec2, (f32, f32))>,
}
const GOLDEN: f32 = 0.618033988;
fn point(i: usize) -> Vec2 {
let theta = i as f32 * TAU * GOLDEN;
let r = theta.sqrt();
let x = r * theta.cos();
let y = r * theta.sin();
vec2(x, y)
}
fn color() -> (f32, f32) {
(
random_range(345.0, 355.0) / 360.0,
random_range(75.0, 93.0) / 100.0,
)
}
fn model(_app: &App) -> Model {
Model {
points: (0..2000).map(point).map(|a| (a * 10.0, color())).collect(),
}
}
fn circ(p: Vec2, center: Vec2, r: f32) -> f32 {
(p - center).length() - r
}
fn spinny_circles(p: Vec2, t: f32) -> f32 {
let mut c = vec![(Vec2::ZERO, 1.0)];
let mut ext = (0..7)
.map(|i| {
let v: Vec2 = (t + TAU * i as f32 / 7.0).sin_cos().into();
(v * 100.0, 10.0)
})
.collect::<Vec<_>>();
c.append(&mut ext);
let mut ext = (0..7)
.map(|i| {
let v: Vec2 = (-t + TAU * i as f32 / 7.0).sin_cos().into();
(v * 250.0, 10.0)
})
.collect::<Vec<_>>();
c.append(&mut ext);
c.iter()
.fold(f32::MAX, |acc, &(c, r)| acc.min(circ(p, c, r)))
}
fn segment(p: Vec2, a: Vec2, b: Vec2) -> f32 {
let pa = p - a;
let ba = b - a;
let h = (pa.dot(ba) / ba.length_squared()).clamp(0.0, 1.0);
return (pa - ba * h).length();
}
fn pentagram(p: Vec2, center: Vec2, t: f32) -> f32 {
let points = (0..5)
.map(|i| {
let v: Vec2 = (-t + TAU * i as f32 / 5.0).sin_cos().into();
v * 100.0 - center
})
.collect::<Vec<_>>();
(0..5)
.map(|i| (points[i], points[(i + 2) % 5]))
.fold(f32::MAX, |acc, (a, b)| acc.min(segment(p, a, b)))
}
fn pentagrams(p: Vec2, t: f32) -> f32 {
(0..5)
.map(|i| {
let v: Vec2 = (t / 10.0 + TAU * i as f32 / 5.0).sin_cos().into();
let v = v * 330.0;
pentagram(p, v, t)
})
.fold(f32::MAX, |acc, a| acc.min(a))
}
fn f(p: Vec2, t: f32) -> f32 {
spinny_circles(p, t).min(pentagrams(p, t))
}
fn norm_f(p: Vec2, t: f32) -> Vec2 {
const H: f32 = 0.0001;
let k = vec2(1.0, -1.0);
return (k * f(p + k * H, t)
+ k.yy() * f(p + k.yy() * H, t)
+ k.yx() * f(p + k.yx() * H, t)
+ k.xx() * f(p + k.xx() * H, t))
.normalize();
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 120.0;
for (p, _) in &mut model.points {
*p -= norm_f(*p, t) * f(*p, t).signum();
}
if app.elapsed_frames() > 20 {
for _ in 0..10 {
model
.points
.push_back((6.0 * point(random_range(0, 1000)), color()));
}
}
// remove a bunch of them every once in a while
// since we're already not running real time, who cares if we pause for a bit rigth?
if app.elapsed_frames() % 200 == 0 {
// drain_filter but i'm too lazy for nightly
let mut i = 0;
while i < model.points.len() {
if f(model.points[i].0, t) < 0.0 {
model.points.remove(i);
} else {
i += 1;
}
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 120.0;
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(BLACK);
} else {
let win = app.window_rect();
draw.rect().wh(win.wh()).color(srgba(0., 0.0, 0.0, 0.005));
}
for &(p, (h, l)) in &model.points {
draw.ellipse().color(hsl(h, 1.0, l)).xy(p).radius(1.0);
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

@ -0,0 +1,8 @@
[package]
name = "subtitled8"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,133 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
const POINTS: usize = 10;
const NUM: usize = 200;
struct Model {
points: Vec<Vec2>,
offsets: Vec<Vec2>,
bg: Vec<(Vec2, f32)>,
}
impl Model {
fn points(&self) -> Vec<Vec2> {
self.points
.windows(2)
.flat_map(|p| {
let a = p[0];
let b = p[1];
(0..NUM).map(move |i| {
let i = i as f32 / NUM as f32;
(1.0 - i) * a + i * b
})
})
.zip(self.offsets.iter())
.map(|(a, &b)| a + b)
.collect()
}
}
fn model(_app: &App) -> Model {
let points = (0..POINTS)
.map(|_| vec2(random_range(-1., 1.), random_range(-1., 1.)) * 200.0)
.collect::<Vec<_>>();
let offsets = vec![Vec2::ZERO; NUM * POINTS];
let bg = sequences::Halton::points(2.0, 3.0)
.take(10000)
.map(|v| (v - Vec2::splat(0.5)) * 1100.0)
.filter(|v| v.x.abs() > 220.0 || v.y.abs() > 220.0)
.map(|v| (v, 0.5))
.collect();
Model {
points,
offsets,
bg,
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 60.0;
for p in &mut model.offsets {
*p *= 0.95;
*p += 0.3 * vec2(random_range(-1.0, 1.0), random_range(-1.0, 1.0));
}
for (i, p) in model.points.iter_mut().enumerate() {
let a = i as f32 * 0.1;
p.x = (t * a + i as f32).sin();
p.y = (t * 1.3 * a + i as f32).cos();
*p *= 200.0;
}
for (i, p) in model.bg.iter_mut().enumerate() {
p.1 = (t * 2.0 + i as f32).sin() + 1.0;
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(SNOW);
} else {
// we only want fading on the inner box,
// outside of it we want a hard reset
let win = app.window_rect();
draw.rect()
.wh(win.wh())
.color(srgba(1.0, 250.0 / 255.0, 250.0 / 255.0, 0.001));
draw.quad()
.points(
vec2(220.0, -1000.0),
vec2(220.0, 1000.0),
vec2(1000.0, 1000.0),
vec2(1000.0, -1000.0),
)
.color(SNOW);
draw.quad()
.points(
vec2(-220.0, -1000.0),
vec2(-220.0, 1000.0),
vec2(-1000.0, 1000.0),
vec2(-1000.0, -1000.0),
)
.color(SNOW);
draw.quad()
.points(
vec2(-1000.0, 220.0),
vec2(-1000.0, 1000.0),
vec2(1000.0, 1000.0),
vec2(1000.0, 220.0),
)
.color(SNOW);
draw.quad()
.points(
vec2(-1000.0, -220.0),
vec2(-1000.0, -1000.0),
vec2(1000.0, -1000.0),
vec2(1000.0, -220.0),
)
.color(SNOW);
}
draw.polyline().points(model.points()).color(BLACK);
for &(p, r) in &model.bg {
draw.ellipse().xy(p).radius(r).color(BLACK);
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

@ -0,0 +1,8 @@
[package]
name = "subtitled9"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,82 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {}
fn model(_app: &App) -> Model {
Model {}
}
fn update(_app: &App, _model: &mut Model, _update: Update) {}
fn view(app: &App, _model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 60.0;
let draw = app.draw();
if frame.nth() == 0 {
draw.background().color(SNOW);
} else {
// we only want fading on the inner box,
// outside of it we want a hard reset
let win = app.window_rect();
draw.rect()
.wh(win.wh())
.color(srgba(1.0, 250.0 / 255.0, 250.0 / 255.0, 0.01));
}
// okay so this is a bit of a hack
// we draw the ones inside and outside the box in two separate passes
// on the first one, we draw the ones inside the box, with a box of size 220
// then we draw the blank stuff outside of a 200 box
// then we draw the points outside the 200 box
// this is cause otherwise either the colored lines go outside the box,
// or the colored lines finish too early
// this works and it's still real time so
// don't judge me uwu
// inner draw
for i in 0..50 {
let r = 1.5;
let r2 = r * (50 + i) as f32 / 50.0;
let v = vec2((t * r + i as f32).sin(), (t * r2 + (2 * i) as f32).sin()) * 300.0;
// while inside the box, draw a line to the last point
if v.x.abs() < 220.0 && v.y.abs() < 220.0 {
let t = t - 1.0 / 60.0;
let v_prev = vec2((t * r + i as f32).sin(), (t * r2 + (2 * i) as f32).sin()) * 300.0;
let h_prev = map_sin((v_prev.x + v_prev.y) / 400.0 + i as f32, 0.0, 1.0);
draw.line()
.stroke_weight(4.0)
.points(v, v_prev)
.color(hsl(h_prev, 0.5, 0.5));
let h = map_sin((v.x + v.y) / 400.0 + i as f32, 0.0, 1.0);
draw.ellipse().radius(2.0).xy(v).color(hsl(h, 0.5, 0.5));
}
}
// draw exterior
drawing::draw_exterior(&draw, 200.0, SNOW);
// outer draw
for i in 0..50 {
let r = 1.5;
let r2 = r * (50 + i) as f32 / 50.0;
let v = vec2((t * r + i as f32).sin(), (t * r2 + (2 * i) as f32).sin()) * 300.0;
if v.x.abs() > 201.0 || v.y.abs() > 201.0 {
draw.ellipse().radius(2.0).xy(v).color(BLACK);
}
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

@ -1,4 +1,5 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
@ -10,13 +11,18 @@ fn model(_app: &App) -> Model {
Model {}
}
fn update(_app: &App, _model: &mut Model, _update: Update) {}
fn update(app: &App, _model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
}
fn view(app: &App, _model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
draw.background().color(PLUM);
draw.ellipse().color(STEELBLUE);
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

@ -0,0 +1,37 @@
use nannou::{color::IntoLinSrgba, draw::properties::ColorScalar, prelude::*};
/// Draws the opposite of a box
pub fn draw_exterior(draw: &Draw, size: f32, color: impl IntoLinSrgba<ColorScalar> + Clone) {
draw.quad()
.points(
vec2(size, -1000.0),
vec2(size, 1000.0),
vec2(1000.0, 1000.0),
vec2(1000.0, -1000.0),
)
.color(color.clone());
draw.quad()
.points(
vec2(-size, -1000.0),
vec2(-size, 1000.0),
vec2(-1000.0, 1000.0),
vec2(-1000.0, -1000.0),
)
.color(color.clone());
draw.quad()
.points(
vec2(-1000.0, size),
vec2(-1000.0, 1000.0),
vec2(1000.0, 1000.0),
vec2(1000.0, size),
)
.color(color.clone());
draw.quad()
.points(
vec2(-1000.0, -size),
vec2(-1000.0, -1000.0),
vec2(1000.0, -1000.0),
vec2(1000.0, -size),
)
.color(color);
}

View File

@ -1,5 +1,7 @@
pub mod color;
pub mod drawing;
pub mod record;
pub mod sequences;
use nannou::prelude::*;
@ -14,9 +16,29 @@ pub fn map_cos(v: f32, out_min: f32, out_max: f32) -> f32 {
pub trait Vec2Extension {
fn atan2(self) -> f32;
fn yy(self) -> Self;
fn yx(self) -> Self;
fn xx(self) -> Self;
}
impl Vec2Extension for Vec2 {
fn atan2(self) -> f32 {
self.x.atan2(self.y)
}
fn yy(self) -> Self {
vec2(self.y, self.y)
}
fn yx(self) -> Self {
vec2(self.y, self.x)
}
fn xx(self) -> Self {
vec2(self.x, self.x)
}
}
pub trait Tup2Extension {
fn to_vec2(self) -> Vec2;
}
impl Tup2Extension for (f32, f32) {
fn to_vec2(self) -> Vec2 {
self.into()
}
}

View File

@ -18,6 +18,7 @@ pub fn record(app: &App, frame: &Frame) {
// Capture all frames to a directory called `/<path_to_nannou>/nannou/simple_capture`.
.join("recordings")
.join(app.exe_name().unwrap())
.join("frames")
// Name each file after the number of the frame.
.join(format!("{:03}", frame.nth()))
// The extension will be PNG. We also support tiff, bmp, gif, jpeg, webp and some others.

View File

@ -0,0 +1,68 @@
use nannou::prelude::*;
pub struct Halton {
i: usize,
base: f32,
}
impl Halton {
pub fn new(base: f32) -> Self {
Self { i: 0, base }
}
pub fn points(base1: f32, base2: f32) -> impl Iterator<Item = Vec2> {
Self::new(base1)
.zip(Self::new(base2))
.map(crate::Tup2Extension::to_vec2)
}
}
impl Iterator for Halton {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let mut f = 1.0;
let mut r = 0.0;
let mut index = self.i as f32;
while index > 0.0 {
f /= self.base;
r += f * (index % self.base);
index = (index / self.base).floor();
}
self.i += 1;
Some(r)
}
}
pub struct VanDerCorput {
i: usize,
base: f32,
}
impl VanDerCorput {
pub fn new(base: f32) -> Self {
Self { i: 0, base }
}
pub fn points(base1: f32, base2: f32) -> impl Iterator<Item = Vec2> {
Self::new(base1)
.zip(Self::new(base2))
.map(crate::Tup2Extension::to_vec2)
}
}
impl Iterator for VanDerCorput {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
let mut q = 0.0;
let mut bk = 1.0 / self.base;
let mut i = self.i as f32;
while i > 0.0 {
q += (i % self.base) * bk;
i /= self.base;
bk /= self.base;
}
Some(q)
}
}

View File

@ -7,8 +7,9 @@ if [[ -z $1 ]]; then
echo "example:"
echo -e "\t$0 packagename"
else
rm -rf "recordings/$1"
rm -rf "recordings/$1/frames"
cargo run --release --package $1 -- -record
ffmpeg -framerate 60 -i "recordings/$1/%03d.png" -pix_fmt yuv420p "recordings/$1.mp4"
filename="video$(( $(find recordings/$1/videos -type f -exec basename -s .mp4 {} \; | sed 's/^video//' | sort -n | tail -n1) + 1)).mp4"
ffmpeg -framerate 60 -i "recordings/$1/frames/%03d.png" -pix_fmt yuv420p "recordings/$1/videos/$filename"
echo "done"
fi