Compare commits

...

28 Commits

Author SHA1 Message Date
annieversary 9906e3f611 add 25 2021-10-25 20:32:07 +01:00
annieversary 9e706fa8c9 minor fix 23 2021-10-23 20:21:46 +01:00
annieversary 92214054f9 add 22, 23, 24, 2021-10-20 18:52:16 +01:00
annieversary 5e0d3b3ca0 change some comments 2021-09-30 22:06:58 +02:00
annieversary 57044bd9cd 19, 20, 21 2021-09-30 22:05:19 +02:00
annieversary f20a328784 subtitled #18: gay core 2021-09-18 13:50:29 +02:00
annieversary e89291202c subtitled #17: chaikin 2021-09-18 13:49:54 +02:00
annieversary 658093b77f utils 2021-09-18 13:49:13 +02:00
annieversary 8fe1852e55 m 2021-09-10 13:07:31 +02:00
annieversary 5f8b643263 subtitled #16: ink spillage 2021-09-08 18:36:37 +02:00
annieversary 7cf5891323 subtitled #15 2021-09-08 18:36:24 +02:00
annieversary 9731315e98 subtitled #14: doom fire 2021-09-07 23:07:18 +02:00
annieversary 858ed70ab4 updated 13 to not show ants 2021-09-04 21:45:10 +02:00
annieversary d333c9c9d6 subtitled #13: goop eating ants 2021-09-03 18:59:19 +02:00
annieversary 45fbe5a37f subtitled #12 2021-09-01 20:01:22 +02:00
annieversary 7506d4907f m 11 2021-09-01 20:00:54 +02:00
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
61 changed files with 2834 additions and 53 deletions

229
Cargo.lock generated
View File

@ -236,7 +236,7 @@ dependencies = [
"daggy 0.5.0",
"fnv",
"instant",
"num",
"num 0.3.1",
"pistoncore-input",
"rusttype 0.8.3",
]
@ -1075,7 +1075,7 @@ dependencies = [
"gif",
"jpeg-decoder",
"num-iter",
"num-rational",
"num-rational 0.3.2",
"num-traits",
"png",
"scoped_threadpool",
@ -1188,6 +1188,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "line_drawing"
version = "0.7.0"
@ -1581,11 +1587,25 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-bigint",
"num-complex",
"num-bigint 0.3.2",
"num-complex 0.3.1",
"num-integer",
"num-iter",
"num-rational",
"num-rational 0.3.2",
"num-traits",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint 0.4.2",
"num-complex 0.4.0",
"num-integer",
"num-iter",
"num-rational 0.4.0",
"num-traits",
]
@ -1600,6 +1620,17 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.3.1"
@ -1609,6 +1640,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@ -1637,7 +1677,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-bigint",
"num-bigint 0.3.2",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
dependencies = [
"autocfg",
"num-bigint 0.4.2",
"num-integer",
"num-traits",
]
@ -1649,6 +1701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@ -2027,6 +2080,16 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_distr"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051b398806e42b9cd04ad9ec8f81e355d0a382c543ac6672c62f5a5b452ef142"
dependencies = [
"num-traits",
"rand 0.8.4",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@ -2348,6 +2411,87 @@ dependencies = [
"utils",
]
[[package]]
name = "subtitled10"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled11"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled12"
version = "0.1.0"
dependencies = [
"nannou",
"rand_distr",
"utils",
]
[[package]]
name = "subtitled13"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled14"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled15"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled16"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled17"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled18"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled19"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled2"
version = "0.1.0"
@ -2356,6 +2500,55 @@ dependencies = [
"utils",
]
[[package]]
name = "subtitled20"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled21"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled22"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled23"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled24"
version = "0.1.0"
dependencies = [
"nannou",
"utils",
]
[[package]]
name = "subtitled25"
version = "0.1.0"
dependencies = [
"nannou",
"num 0.4.0",
"utils",
]
[[package]]
name = "subtitled3"
version = "0.1.0"
@ -2388,6 +2581,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

@ -3,7 +3,3 @@
this is a bunch of nannou projects
i'll add a list with pictures here soon
you'll notice that the numbers i've put on fedi and the ones here are different :)
also don't criticize my code pls i'm sensitive and i Will cry

BIN
assets/images/blahaj.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
assets/images/dowom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
assets/images/goop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/images/uwu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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,163 @@
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
50.0 * vec2(v.x + v.y, v.z + v.w)
})
.collect::<Vec<_>>();
for (i, (&a, &b)) in p
.iter()
.enumerate()
// make all pairs of points
.flat_map(|(i, a)| p[i + 1..].iter().map(move |b| (a, b)))
.enumerate()
{
if i % 2 != 0 {
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

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

View File

@ -0,0 +1,67 @@
use nannou::prelude::*;
use nannou::rand::prelude::*;
use rand_distr::Normal;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
lines: Vec<Vec<Vec2>>,
}
fn lines(max_width: f32) -> Vec<Vec<Vec2>> {
let normal = Normal::new(0.0, 0.2).unwrap();
(0..20)
.map(|_| {
let mut line = vec![Vec2::Y];
// generate the y coordinates for all points
let n = random_range(4, 12);
let mut ys = (0..n).map(|_| random_range(-1.0, 1.0)).collect::<Vec<_>>();
ys.sort_by(|a, b| b.partial_cmp(a).unwrap());
// make the actual points and add them to the vec
for y in ys {
let w = map_range(1.0 - y.abs(), 0.0, 1.0, 0.01, max_width);
let x = nannou::rand::thread_rng().sample::<f32, _>(normal) * w;
line.push(vec2(x, y));
}
line.push(-Vec2::Y);
line
})
.collect()
}
fn model(_app: &App) -> Model {
Model { lines: lines(0.0) }
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 60.0;
if app.elapsed_frames() % 2 == 0 {
model.lines = lines(map_sin(t, 0.1, 1.0));
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
draw.background().color(BLACK);
for line in &model.lines {
draw.polyline()
.stroke_weight(1.0)
.points(line.iter().map(|v| *v * 300.0))
.color(PINK);
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,94 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
walkers: Vec<Vec2>,
clustered: Vec<Vec2>,
}
fn model(_app: &App) -> Model {
Model {
walkers: (0..5000).map(|_| vec2_range(-1.0, 1.0) * 300.0).collect(),
clustered: vec![Vec2::ZERO],
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
// sort them by x to be a bit more efficient with the collision detection
model
.clustered
.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let clusters = model.clustered.clone();
let mut to_remove = vec![];
for (i, p) in &mut model.walkers.iter_mut().enumerate() {
*p += vec2_range(-1.0, 1.0) * 3.5 + 0.5 * vec2(p.y, -p.x).normalize();
const RAD: f32 = 6.0;
for &c in &clusters {
// skip the ones outside the x range
if c.x < p.x - RAD {
continue;
}
if c.x > p.x + RAD {
break;
}
// if it's colliding, make it be a clustered point
if p.distance_squared(c) < RAD * RAD {
model.clustered.push(*p);
to_remove.push(i);
break;
}
}
}
// remove the ones that have become clustered
// backwards cause otherwise you shift the indexes
for i in to_remove.into_iter().rev() {
model.walkers.remove(i);
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 60.0;
let draw = app.draw();
drawing::draw_soft_bg(&draw, app, SNOW, 1.0);
for &p in &model.clustered {
let v = map_sin(t + (p.x.powi(2) + p.y.powi(2) + t) * 0.00003, -20.0, 20.0);
let h = (100.0 + v) / 360.0;
draw.ellipse().xy(p).radius(4.0).color(hsl(h, 0.5, 0.5));
}
for &p in &model.walkers {
draw.ellipse().xy(p).radius(0.0).color(BLACK);
}
// use nannou::rand::seq::SliceRandom;
// let mut rng = nannou::rand::thread_rng();
// let n = (frame.nth() as f32 * 0.7) as usize;
// for _ in 0..n {
// let a = model.clustered.choose(&mut rng);
// let b = model.clustered.choose(&mut rng);
// if let (Some(&a), Some(&b)) = (a, b) {
// let v = map_sin(t * 2.0 + a.x * a.y, -20.0, 20.0);
// let h = (100.0 + v) / 360.0;
// draw.line()
// .stroke_weight(3.0)
// .points(a, b)
// .color(hsla(h, 0.5, 0.5, 0.04));
// }
// }
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,152 @@
use nannou::prelude::*;
// use utils::*;
// from https://fabiensanglard.net/doom_fire_psx/
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
fire: Vec<Vec<f32>>,
colors: Vec<Rgb8>,
uwu: wgpu::Texture,
}
const WIDTH: usize = 400;
const HEIGHT: usize = 300;
const BALL_SIZE: f32 = 3.0;
fn model(app: &App) -> Model {
let fire = vec![vec![0.0; WIDTH]; HEIGHT];
let colors = vec![
(0x07, 0x07, 0x07),
(0x1F, 0x07, 0x07),
(0x2F, 0x0F, 0x07),
(0x47, 0x0F, 0x07),
(0x57, 0x17, 0x07),
(0x67, 0x1F, 0x07),
(0x77, 0x1F, 0x07),
(0x8F, 0x27, 0x07),
(0x9F, 0x2F, 0x07),
(0xAF, 0x3F, 0x07),
(0xBF, 0x47, 0x07),
(0xC7, 0x47, 0x07),
(0xDF, 0x4F, 0x07),
(0xDF, 0x57, 0x07),
(0xDF, 0x57, 0x07),
(0xD7, 0x5F, 0x07),
(0xD7, 0x5F, 0x07),
(0xD7, 0x67, 0x0F),
(0xCF, 0x6F, 0x0F),
(0xCF, 0x77, 0x0F),
(0xCF, 0x7F, 0x0F),
(0xCF, 0x87, 0x17),
(0xC7, 0x87, 0x17),
(0xC7, 0x8F, 0x17),
(0xC7, 0x97, 0x1F),
(0xBF, 0x9F, 0x1F),
(0xBF, 0x9F, 0x1F),
(0xBF, 0xA7, 0x27),
(0xBF, 0xA7, 0x27),
(0xBF, 0xAF, 0x2F),
(0xB7, 0xAF, 0x2F),
(0xB7, 0xB7, 0x2F),
(0xB7, 0xB7, 0x37),
(0xCF, 0xCF, 0x6F),
(0xDF, 0xDF, 0x9F),
(0xEF, 0xEF, 0xC7),
(0xFF, 0xFF, 0xFF),
]
.into_iter()
.map(|c| c.into())
.collect();
let assets = app.assets_path().unwrap();
let img_path = assets.join("images").join("blahaj.png");
let uwu = wgpu::Texture::from_path(app, img_path).unwrap();
let mut model = Model { fire, colors, uwu };
// presimulate fire
for _ in 0..400 {
advance(app, &mut model);
}
model
}
fn update(app: &App, model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
advance(app, model);
}
fn advance(app: &App, model: &mut Model) {
let (w, h) = app.window_rect().w_h();
let cols = (w / BALL_SIZE) as usize;
let rows = (h / BALL_SIZE) as usize - 50;
if app.elapsed_frames() > 200 {
for i in 0..cols {
model.fire[rows][i] = 0.0;
}
} else {
for i in 0..cols {
model.fire[rows][i] = 36.0;
}
}
for x in 0..cols {
for y in 0..rows {
let disp = random_range(0, 2);
model.fire[y][(x + disp) % cols] = model.fire[y + 1][x] - random_range(0., 1.0);
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
draw.background().color(BLACK);
draw.texture(&model.uwu)
.w_h(400.0, 282.6)
.y((-200.0 + frame.nth() as f32).min(0.0));
let (l, top, w, h) = app.window_rect().l_t_w_h();
let cols = w / BALL_SIZE;
let rows = h / BALL_SIZE;
for (i, row) in model.fire.iter().enumerate() {
for (j, c) in row.iter().enumerate() {
let col_index = c.max(0.0).trunc() as usize % model.colors.len();
if i as f32 <= rows && j as f32 <= cols && col_index != 0 {
let x = l + j as f32 * BALL_SIZE;
let y = top - i as f32 * BALL_SIZE;
let p = vec2(x, y);
let r = BALL_SIZE;
// * map_range(i as f32, 0 as f32, rows, 0., 1.0)
// * random_range(0.9, 1.1);
draw.quad()
.points(
p + r * vec2(1., 1.),
p + r * vec2(1., -1.),
p + r * vec2(-1., -1.),
p + r * vec2(-1., 1.),
)
.xy(p)
.color(model.colors[col_index]);
}
}
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

Binary file not shown.

View File

@ -0,0 +1,13 @@
// NOTE: This shader requires being manually compiled to SPIR-V in order to
// avoid having downstream users require building shaderc and compiling the
// shader themselves. If you update this shader, be sure to also re-compile it
// and update `frag.spv`. You can do so using `glslangValidator` with the
// following command: `glslangValidator -V shader.frag`
#version 450
layout(location = 0) out vec4 f_color;
void main() {
f_color = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -0,0 +1,15 @@
// NOTE: This shader requires being manually compiled to SPIR-V in order to
// avoid having downstream users require building shaderc and compiling the
// shader themselves. If you update this shader, be sure to also re-compile it
// and update `vert.spv`. You can do so using `glslangValidator` with the
// following command: `glslangValidator -V shader.vert`
#version 450
// maybe change for this https://github.com/castor-software/rethread/blob/69a5746b02c260982a812c52da15ee364bc047e8/code/software-evolution/drift_vis/src/shaders/blur.vert
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}

Binary file not shown.

View File

@ -0,0 +1,127 @@
use nannou::prelude::*;
struct Model {
bind_group: wgpu::BindGroup,
render_pipeline: wgpu::RenderPipeline,
vertex_buffer1: wgpu::Buffer,
vertex_buffer2: wgpu::Buffer,
}
// The vertex type that we will use to represent a point on our triangle.
#[repr(C)]
#[derive(Clone, Copy)]
struct Vertex {
position: [f32; 2],
}
// The vertices that make up our triangle.
const VERTICES1: [Vertex; 3] = [
Vertex {
position: [-1.0, -1.0],
},
Vertex {
position: [1.0, 1.0],
},
Vertex {
position: [-1.0, 1.0],
},
];
const VERTICES2: [Vertex; 3] = [
Vertex {
position: [1.0, -1.0],
},
Vertex {
position: [1.0, 1.0],
},
Vertex {
position: [-1.0, -1.0],
},
];
fn main() {
nannou::app(model).run();
}
fn model(app: &App) -> Model {
let w_id = app.new_window().size(512, 512).view(view).build().unwrap();
// The gpu device associated with the window's swapchain
let window = app.window(w_id).unwrap();
let device = window.swap_chain_device();
let format = Frame::TEXTURE_FORMAT;
let sample_count = window.msaa_samples();
// Load shader modules.
let vs_mod = wgpu::shader_from_spirv_bytes(device, include_bytes!("../shaders/vert.spv"));
let fs_mod = wgpu::shader_from_spirv_bytes(device, include_bytes!("../shaders/frag.spv"));
// Create the vertex buffer.
let usage = wgpu::BufferUsage::VERTEX;
let vertices_bytes = vertices_as_bytes(&VERTICES1[..]);
let vertex_buffer1 = device.create_buffer_init(&BufferInitDescriptor {
label: None,
contents: vertices_bytes,
usage,
});
let vertices_bytes = vertices_as_bytes(&VERTICES2[..]);
let vertex_buffer2 = device.create_buffer_init(&BufferInitDescriptor {
label: None,
contents: vertices_bytes,
usage,
});
// Create the render pipeline.
let bind_group_layout = wgpu::BindGroupLayoutBuilder::new().build(device);
let bind_group = wgpu::BindGroupBuilder::new().build(device, &bind_group_layout);
let pipeline_layout = wgpu::create_pipeline_layout(device, None, &[&bind_group_layout], &[]);
let render_pipeline = wgpu::RenderPipelineBuilder::from_layout(&pipeline_layout, &vs_mod)
.fragment_shader(&fs_mod)
.color_format(format)
.add_vertex_buffer::<Vertex>(&wgpu::vertex_attr_array![0 => Float32x2])
.sample_count(sample_count)
.build(device);
Model {
bind_group,
vertex_buffer1,
vertex_buffer2,
render_pipeline,
}
}
// Draw the state of your `Model` into the given `Frame` here.
fn view(_app: &App, model: &Model, frame: Frame) {
// Using this we will encode commands that will be submitted to the GPU.
let mut encoder = frame.command_encoder();
// The render pass can be thought of a single large command consisting of sub commands. Here we
// begin a render pass that outputs to the frame's texture. Then we add sub-commands for
// setting the bind group, render pipeline, vertex buffers and then finally drawing.
let mut render_pass = wgpu::RenderPassBuilder::new()
.color_attachment(frame.texture_view(), |color| color)
.begin(&mut encoder);
render_pass.set_bind_group(0, &model.bind_group, &[]);
render_pass.set_pipeline(&model.render_pipeline);
render_pass.set_vertex_buffer(0, model.vertex_buffer1.slice(..));
// We want to draw the whole range of vertices, and we're only drawing one instance of them.
let vertex_range = 0..VERTICES1.len() as u32;
let instance_range = 0..1;
render_pass.draw(vertex_range, instance_range);
render_pass.set_bind_group(0, &model.bind_group, &[]);
render_pass.set_pipeline(&model.render_pipeline);
render_pass.set_vertex_buffer(0, model.vertex_buffer2.slice(..));
// We want to draw the whole range of vertices, and we're only drawing one instance of them.
let vertex_range = 0..VERTICES2.len() as u32;
let instance_range = 0..1;
render_pass.draw(vertex_range, instance_range);
// Now we're done! The commands we added will be submitted after `view` completes.
}
// See the `nannou::wgpu::bytes` documentation for why this is necessary.
fn vertices_as_bytes(data: &[Vertex]) -> &[u8] {
unsafe { wgpu::bytes::from_slice(data) }
}

View File

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

View File

@ -0,0 +1,112 @@
use std::time::Duration;
use nannou::prelude::*;
// https://sighack.com/post/flood-fill-art-using-random-walks
const R: f32 = 2.0;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
#[derive(Clone)]
struct Point {
pos: IVec2,
color: f32,
}
impl Point {
fn new(pos: IVec2, color: f32) -> Self {
Self { pos, color }
}
}
fn neighbors(point: IVec2) -> Vec<IVec2> {
let mut cube = vec![];
let p = [-1, 0, 1];
for &x in &p {
for &y in &p {
if (x, y) != (0, 0) {
cube.push(ivec2(x, y) + point);
}
}
}
cube
}
struct Model {
points: Vec<Point>,
visits: Vec<Point>,
}
fn model(_app: &App) -> Model {
Model {
visits: vec![Point::new(IVec2::ZERO, random_range(0.0, 360.0))],
points: vec![],
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
for _ in 0..50 {
run(app, model)
}
}
fn run(app: &App, model: &mut Model) {
let _t = app.elapsed_frames() as f32 / 60.0;
// take a random out of the visits list
let point = model.visits.remove(random_range(0, model.visits.len()));
// visit it and all it's neighbors
let neighbors = neighbors(point.pos)
.into_iter()
.filter(|p| {
model.visits.iter().find(|point| point.pos == *p).is_none()
&& model.points.iter().find(|point| point.pos == *p).is_none()
})
.collect::<Vec<_>>();
for n in neighbors {
model
.visits
// NOTE: this is where the randomization of the color happens
.push(Point::new(n, point.color + random_range(-10.0, 10.0)))
}
// move it to visited points
model.points.push(point);
}
fn view(app: &App, model: &Model, frame: Frame) {
let _t = frame.nth() as f32 / 60.0;
let draw = app.draw();
draw.background().color(SNOW);
for walker in &model.visits {
let h = walker.color / 360.0;
draw.ellipse()
.xy(walker.pos.as_f32() * R * 2.0)
.radius(R)
.color(hsl(h.fract(), 0.5, 0.7));
}
for walker in &model.points {
let h = walker.color / 360.0;
let p = walker.pos.as_f32() * R;
draw.quad()
.points(
p + R * vec2(1., 1.),
p + R * vec2(1., -1.),
p + R * vec2(-1., -1.),
p + R * vec2(-1., 1.),
)
.xy(p)
.color(hsl(h.fract(), 0.5, 0.5));
}
if frame.nth() < 50 {
// cause otherwise it goes too fast when recording and it can't save the frames in time lmao
std::thread::sleep(Duration::from_millis(5));
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,54 @@
use nannou::prelude::*;
use utils::{curves::*, drawing::draw_soft_bg, *};
const POINTS: usize = 10;
const SIZE: f32 = 300.0;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
curves: Vec<Chaikin>,
}
fn model(_app: &App) -> Model {
let curves = (0..10)
.map(|_| {
Chaikin::new(
(0..POINTS)
.map(|_| vec2_range(-1., 1.) * SIZE)
.collect::<Vec<_>>(),
)
})
.collect();
Model { curves }
}
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_soft_bg(&draw, app, SNOW, 0.01);
for (i, curve) in model.curves.iter().enumerate() {
let div = map_sin(t * 0.7 + (2 * i) as f32, 0.15, 0.35);
let h = map_sin(t * 0.3 + i as f32, 0., 1.);
draw.polyline().stroke_weight(1.5).points_colored(
curve
.points(div, 10)
.into_iter()
.enumerate()
.map(|(i, p)| (p, hsla((h + i as f32 / 10000.0).fract(), 0.5, 0.5, 0.5))),
);
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,52 @@
use nannou::prelude::*;
use utils::{drawing::draw_soft_bg, *};
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) {
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_soft_bg(&draw, app, BLACK, 1.0);
let count = 10;
for a in 0..360 {
for i in 1..=count {
let a = a as f32;
let fi = i as f32;
let r = 1500.0 * (t + 0.01 * a + fi).cos();
let r = r.powf(0.9 * fi / count as f32);
// alternate directions of spin for each layer
let s = if i % 2 == 0 { 1.0 } else { -1.0 } * map_sin(a * fi * fi, 0.1, 1.0);
let p = r * (s * t + fi + a).sin_cos().to_vec2();
// jitter
let p = p + map_sin(t * 0.3 + TAU * 0.75, 0., 7.0) * vec2_circ();
let sat = map_sin(t * 0.3 + TAU * 0.75, 0.4, 0.9);
let r = map_range(r, 0., 500.0, 0.5, 13.0).sqrt();
draw.ellipse()
.radius(r)
.xy(p)
.color(hsl(a / 360.0, sat, 0.5));
}
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,66 @@
use nannou::prelude::*;
use utils::{drawing::draw_soft_bg, *};
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Walker {
pos: Vec2,
vel: Vec2,
color: f32,
}
struct Model {
walkers: Vec<Walker>,
}
fn model(_app: &App) -> Model {
Model {
walkers: (0..100)
.map(|_| Walker {
pos: Vec2::ZERO,
vel: Vec2::ZERO,
color: random_range(0.0, 1.0),
})
.collect(),
}
}
fn update(app: &App, model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
for walker in &mut model.walkers {
walker.vel += vec2_range(-2.0, 1.0);
walker.vel = walker.vel.normalize();
walker.pos += walker.vel;
// wrap around the box
const SIZE: f32 = 100.0;
if walker.pos.x.abs() > SIZE {
walker.pos.x = -walker.pos.x;
}
if walker.pos.y.abs() > SIZE {
walker.pos.y = -walker.pos.y;
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 60.0;
let draw = app.draw();
draw_soft_bg(&draw, app, SNOW, 0.01);
for walker in &model.walkers {
let h = (t * 10.0 + walker.color * 50.0) / 360.0;
draw.ellipse()
.xy(walker.pos)
.radius(1.0)
.color(hsl(h.fract(), 0.7, 0.5));
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,60 @@
use nannou::prelude::*;
use utils::*;
// based on https://nbickford.wordpress.com/2011/04/03/the-minsky-circle-algorithm/
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
points: Vec<Vec2>,
base_hue: f32,
}
fn model(_app: &App) -> Model {
Model {
points: (0..1000).map(|_| Vec2::ONE * 6.0).collect(),
base_hue: 0.0,
}
}
fn advance(mut point: Vec2, t: f32) -> Vec2 {
let d = map_sin(t * 1.1, 0.01, 0.12);
let e = (t.sqrt() * 0.01 + 0.3).min(1.1);
point.x -= (point.y * d).floor();
point.y += (point.x * e).floor();
point
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 60.0;
// advance all points in list
let mut last = model.points.last().unwrap().clone();
for v in &mut model.points {
last = advance(last, t);
*v = last;
}
// move base_hue
model.base_hue += random::<f32>();
}
fn view(app: &App, model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 60.0;
let draw = app.draw();
drawing::draw_soft_bg(&draw, app, BLACK, 0.01);
for &p in &model.points {
let h = random_range(model.base_hue, model.base_hue + 60.0) / 360.0;
draw.ellipse()
.radius(map_sin(p.x.powi(3) + p.y.powi(7) + t * 0.1, 1.0, 3.0))
.xy(p * 7.0)
.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 = "subtitled21"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,90 @@
use nannou::prelude::*;
fn main() {
nannou::app(model)
.update(update)
.simple_window(view)
.size(800, 800)
.run();
}
struct Model {}
const BALL_SIZE: f32 = 3.0;
fn model(_app: &App) -> Model {
Model {}
}
fn update(app: &App, _model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
}
fn shader(i: usize, j: usize, w: usize, h: usize, t: f32) -> Rgb {
let x = i as f32 / w as f32;
let y = j as f32 / h as f32;
let pos = vec2(x, y);
creation(pos, t)
}
// https://www.shadertoy.com/view/XsXXDn
fn creation(pos: Vec2, t: f32) -> Rgb {
let mut l = t;
let mut z = t;
let mut c = [0.0; 3];
for i in 0..3 {
let mut uv = pos;
let mut p = pos;
p -= Vec2::splat(0.5);
z += 0.07;
l = p.length();
uv += p / l * (z.sin() + 1.) * abs((l * 9. - z * 2.).sin());
c[i] = 0.01 / ((vec2(uv.x.fract(), uv.y.fract()) - Vec2::splat(0.5)).abs()).length();
}
srgb(c[0] / l, c[1] / l, c[2] / l)
}
fn view(app: &App, _model: &Model, frame: Frame) {
let t = frame.nth() as f32 / 120.0;
let draw = app.draw();
draw.background().color(BLACK);
let (l, top, w, h) = app.window_rect().l_t_w_h();
let cols = (w / BALL_SIZE) as usize;
let rows = (h / BALL_SIZE) as usize;
for i in 0..cols {
for j in 0..rows {
let x = l + j as f32 * BALL_SIZE;
let y = top - i as f32 * BALL_SIZE;
let p = vec2(x, y)
+ (t * 0.4).min(5.0)
* 10.0
* vec2(
(t * 0.7 + (i * j) as f32).sin(),
(t * 0.9 + (2 * i * j) as f32).cos(),
);
let color = shader(i, j, cols, rows, t);
draw.quad()
.points(
p + BALL_SIZE * vec2(1., 1.),
p + BALL_SIZE * vec2(1., -1.),
p + BALL_SIZE * vec2(-1., -1.),
p + BALL_SIZE * vec2(-1., 1.),
)
.xy(p)
.color(color);
}
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,99 @@
use nannou::prelude::*;
use utils::{drawing::draw_soft_bg, *};
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
enum Type {
Square,
Circle,
}
struct Shape {
size: f32,
r: f32,
color: f32,
t: Type,
}
struct Model {
shapes: Vec<Shape>,
}
fn model(_app: &App) -> Model {
Model {
shapes: (0..1000)
.map(|_| {
let size = random_range(7.0, 18.0);
let r = random_range(100.0, 300.0);
let color = (random_range(0.0, 80.0) / 360.0).fract();
let t = if random::<f32>() > 0.3 {
Type::Circle
} else {
Type::Square
};
Shape { size, r, color, t }
})
.collect(),
}
}
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_soft_bg(&draw, &app, SNOW, 1.03);
let count = model.shapes.len() as f32;
for (i, shape) in model.shapes.iter().enumerate() {
let angle = 360.0 * i as f32 / count;
let angle = angle + t * map_sin(angle.powi(4), 0.2, 0.7);
let r = shape.r * map_sin(t + i as f32, 0.5, 1.3);
let size = shape.size * map_sin(0.004 * t + 2.0 * i as f32, 0.5, 1.5);
match shape.t {
Type::Circle => {
let color = (shape.color + 0.01 * t + angle * 0.0001).fract();
let color = hsl(color, map_sin(i.pow(3) as f32, 0.4, 0.7), 0.5);
let p = r * angle.sin_cos().to_vec2();
draw.ellipse().radius(size).xy(p).color(color);
}
Type::Square => {
let p = r * (-angle).sin_cos().to_vec2();
let rotation = t + i as f32;
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 * size + p)
.collect::<Vec<_>>();
draw.polyline()
.stroke_weight(1.5)
.points_closed(points)
.color(SNOW);
}
};
}
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,69 @@
use nannou::prelude::*;
use utils::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
points: Vec<Vec2>,
base_hue: f32,
}
fn model(_app: &App) -> Model {
Model {
base_hue: random_range(0., 360.0),
points: vec![Vec2::splat(0.1); 10000],
}
}
fn advance(point: Vec2, t: f32) -> Vec2 {
// pick a random point
let count = ((t * 0.125).trunc() as usize % 5) + 3;
let mut points = (0..count)
.map(|i| (TAU * i as f32 / count as f32).sin_cos().to_vec2())
.collect::<Vec<_>>();
points.push(Vec2::ZERO);
let random = points[random_range(0, count)];
(random + point) * map_cos(t * PI * 0.25, 0.1, 1.0).powf(1.5)
}
fn update(app: &App, model: &mut Model, _update: Update) {
let t = app.elapsed_frames() as f32 / 60.0;
// advance all points in list
let mut last = model.points.last().unwrap().clone();
for v in &mut model.points {
last = advance(last, t);
*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 = "subtitled24"
version = "0.1.0"
edition = "2018"
[dependencies]
nannou = "0.17"
utils = { path = "../utils" }

View File

@ -0,0 +1,67 @@
use std::time::Duration;
use nannou::prelude::*;
use utils::{
lsystems::LSystem,
turtle::{Turtle, TurtleAlphabet},
};
fn main() {
nannou::app(model)
.update(update)
.simple_window(view)
.size(600, 520)
.run();
}
struct Model {}
fn model(_app: &App) -> Model {
Model {}
}
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();
if frame.nth() == 0 {
draw.background().color(SNOW);
}
if frame.nth() % 60 == 0 {
let iters = t.trunc() as usize % 10;
use TurtleAlphabet::*;
let mut sys = LSystem::new(
vec![Line, Right, Right, Line, Right, Right, Line],
Box::new(|i| match i {
Line => vec![
Line, Right, Right, Line, Right, Right, Line, Right, Right, Move,
],
Move => vec![Move, Move],
a @ _ => vec![a],
}),
);
let h = app.window_rect().h() / 2.0;
let w = app.window_rect().w() / 2.0;
let mut turtle = Turtle::new(vec2(-w, -h), 2.85 * w * 0.5.powi(iters as i32), TAU / 3.0);
turtle.rotation = -PI / 4.0;
let a = sys.nth(iters);
turtle.advance_many(&draw, &a);
// turtle.advance(&draw, TurtleAlphabet::Line);
// turtle.advance(&draw, TurtleAlphabet::Right);
// turtle.advance(&draw, TurtleAlphabet::Line);
}
// cause otherwise it goes too fast when recording and it can't save the frames in time lmao
std::thread::sleep(Duration::from_millis(5));
draw.to_frame(app, &frame).unwrap();
utils::record::record(app, &frame);
}

View File

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

View File

@ -0,0 +1,50 @@
use nannou::prelude::*;
use num::complex::*;
use utils::*;
fn main() {
nannou::app(model)
.update(update)
.simple_window(view)
.size(800, 800)
.run();
}
struct Model {}
const BALL_SIZE: f32 = 3.0;
fn model(_app: &App) -> Model {
Model {}
}
fn update(app: &App, _model: &mut Model, _update: Update) {
let _t = app.elapsed_frames() as f32 / 60.0;
}
fn shader(i: usize, j: usize, w: usize, h: usize, t: f32) -> Hsl {
let x = (i as f32 / w as f32) - 0.5;
let y = (j as f32 / h as f32) - 0.5;
let x = x * 7.0;
let y = y * 7.0;
let mut v = Complex::new(y, x);
let a = map_sin(t * 0.5, 0.0, TAU);
let c = Complex::from_polar(0.7885, a);
let iters = map_sin(t * 0.1, 10.0, 100.0);
for i in 0..(iters as usize) {
v = v.powi(2) + c;
if v.norm() > 10.0 {
return hsl(i as f32 / iters, 0.5, 0.5);
}
}
hsl(0.0, 0.0, 0.0)
}
fn view(app: &App, _model: &Model, frame: Frame) {
utils::shaders::run(app, frame, BALL_SIZE, shader, vec2)
}

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,31 @@
use nannou::prelude::*;
pub struct Chaikin {
pub points: Vec<Vec2>,
}
impl Chaikin {
pub fn new(points: Vec<Vec2>) -> Self {
Self { points }
}
pub fn points(&self, div: f32, n: usize) -> Vec<Vec2> {
let mut points = self.points.clone();
for _ in 0..n {
let first = *points.first().unwrap();
let last = *points.last().unwrap();
points = points
.windows(2)
.flat_map(|p| {
let a = p[0];
let b = p[1];
[a + div * (b - a), a + (1.0 - div) * (b - a)]
})
.collect();
points.insert(0, first);
points.push(last);
}
points
}
}

View File

@ -0,0 +1,49 @@
use nannou::{color::IntoLinSrgba, draw::properties::ColorScalar, prelude::*};
pub fn draw_soft_bg(draw: &Draw, app: &App, color: impl IntoLinSrgba<ColorScalar>, alpha: f32) {
if app.elapsed_frames() <= 1 {
draw.background().color(color);
} else {
let mut color = color.into_lin_srgba();
color.alpha = alpha;
let win = app.window_rect();
draw.rect().wh(win.wh()).color(color);
}
}
/// 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,11 @@
pub mod color;
pub mod curves;
pub mod drawing;
pub mod lsystems;
pub mod record;
pub mod sequences;
pub mod shaders;
pub mod turtle;
use nannou::prelude::*;
@ -14,9 +20,40 @@ 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()
}
}
pub fn vec2_range(min: f32, max: f32) -> Vec2 {
vec2(random_range(min, max), random_range(min, max))
}
pub fn ivec2_range(min: i32, max: i32) -> IVec2 {
ivec2(random_range(min, max), random_range(min, max))
}
/// returns a random vector in the unit circle
pub fn vec2_circ() -> Vec2 {
random_range(0., TAU).sin_cos().into()
}

View File

@ -0,0 +1,66 @@
use std::collections::HashMap;
pub struct LSystem<Alphabet: Clone> {
axiom: Vec<Alphabet>,
rule: Box<dyn FnMut(Alphabet) -> Vec<Alphabet>>,
memo: HashMap<usize, Vec<Alphabet>>,
}
impl<Alphabet: Clone> LSystem<Alphabet> {
pub fn new(axiom: Vec<Alphabet>, rule: Box<dyn FnMut(Alphabet) -> Vec<Alphabet>>) -> Self {
Self {
axiom,
rule,
memo: Default::default(),
}
}
pub fn nth(&mut self, i: usize) -> Vec<Alphabet> {
if i == 0 {
return self.axiom.clone();
}
if let Some(a) = self.memo.get(&i) {
return a.clone();
}
let last = self.nth(i - 1);
let mut res = Vec::new();
for letter in last {
res.extend((self.rule)(letter).into_iter());
}
self.memo.insert(i, res.clone());
res
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lsystems() {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Test {
A,
B,
}
use Test::*;
let mut sys = LSystem::new(
vec![A],
Box::new(|i| match i {
A => vec![A, B],
B => vec![A],
}),
);
assert_eq!(vec![A], sys.nth(0));
assert_eq!(vec![A, B], sys.nth(1));
assert_eq!(vec![A, B, A], sys.nth(2));
assert_eq!(vec![A, B, A, A, B], sys.nth(3));
assert_eq!(vec![A, B, A, A, B, A, B, A], sys.nth(4));
}
}

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

@ -0,0 +1,40 @@
use nannou::prelude::*;
pub fn run<S, T>(app: &App, frame: Frame, ball_size: f32, shader: S, tile_movement: T)
where
S: Fn(usize, usize, usize, usize, f32) -> Hsl,
T: Fn(f32, f32) -> Vec2,
{
let t = frame.nth() as f32 / 120.0;
let draw = app.draw();
draw.background().color(BLACK);
let (l, top, w, h) = app.window_rect().l_t_w_h();
let cols = (w / ball_size) as usize;
let rows = (h / ball_size) as usize;
for i in 0..cols {
for j in 0..rows {
let x = l + j as f32 * ball_size;
let y = top - i as f32 * ball_size;
let p = tile_movement(x, y);
let color = shader(i, j, cols, rows, t);
draw.quad()
.points(
p + ball_size * vec2(1., 1.),
p + ball_size * vec2(1., -1.),
p + ball_size * vec2(-1., -1.),
p + ball_size * vec2(-1., 1.),
)
.xy(p)
.color(color);
}
}
draw.to_frame(app, &frame).unwrap();
super::record::record(app, &frame);
}

View File

@ -0,0 +1,59 @@
use nannou::prelude::*;
#[derive(Clone, Copy, Debug)]
pub enum TurtleAlphabet {
Line,
Move,
Right,
Left,
Push,
Pop,
}
pub struct Turtle {
position: Vec2,
pub rotation: f32,
len: f32,
theta: f32,
stack: Vec<(Vec2, f32)>,
}
impl Turtle {
pub fn new(position: Vec2, len: f32, theta: f32) -> Self {
Self {
position,
rotation: 0.0,
len,
theta,
stack: Default::default(),
}
}
pub fn advance(&mut self, draw: &Draw, l: TurtleAlphabet) {
match l {
TurtleAlphabet::Line | TurtleAlphabet::Move => {
let next = self.position + Vec2::ONE.rotate(self.rotation) * self.len;
draw.line().points(self.position, next);
self.position = next;
}
// TurtleAlphabet::Move => {
// self.position += Vec2::ONE.rotate(self.rotation) * self.len;
// }
TurtleAlphabet::Right => self.rotation = (self.rotation - self.theta).rem_euclid(TAU),
TurtleAlphabet::Left => self.rotation = (self.rotation + self.theta).rem_euclid(TAU),
TurtleAlphabet::Push => self.stack.push((self.position, self.rotation)),
TurtleAlphabet::Pop => {
if let Some((a, b)) = self.stack.pop() {
self.position = a;
self.rotation = b;
}
}
}
}
pub fn advance_many(&mut self, draw: &Draw, l: &[TurtleAlphabet]) {
for &l in l {
self.advance(draw, l)
}
}
}

View File

@ -7,8 +7,10 @@ if [[ -z $1 ]]; then
echo "example:"
echo -e "\t$0 packagename"
else
rm -rf "recordings/$1"
rm -rf "recordings/$1/frames"
mkdir -p "recordings/$1/videos"
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