This is largely adapted from Danielle Navarro’s “Art from Code” lessons, that go into a lot more detail: https://art-from-code.netlify.app/
Setup a grid of points, or any set of x and y coordinates
Generate gradient noise for every point on the grid. It represents the vectors that point along the highest gradients in a noise field.
The gradient noise isn’t actually needed in the final plot but it helps in illustrating what is happening.
smol_slope <- gradient_noise(
generator = gen_simplex,
seed = 1,
frequency = .1,
x = smol_grid$x,
y = smol_grid$y
)
Generate curl noise vectors for every point on the grid, these are perpendicular to the gradient noise vectors and follow the lines of zero gradient in the field.
smol_curl <- curl_noise(
generator = gen_simplex,
seed = 1,
frequency = .1,
x = smol_grid$x,
y = smol_grid$y
)
Combine grid, and noise vectors into a single data frame
smol_field <- smol_grid %>%
mutate(
z = gen_simplex(x, y, seed = 1, frequency = .1),
slope_x = smol_slope$x,
slope_y = smol_slope$y,
curl_x = smol_curl$x,
curl_y = smol_curl$y,
)
Plot it with a color map for the “terrain” (the noise field) and arrows for the gradient and curl vectors.
ggplot(smol_field) +
geom_contour_filled( aes(x, y, z = z), show.legend = FALSE, bins = 20) +
geom_segment(
mapping = aes(
x = x,
y = y,
xend = x + slope_x * 2,
yend = y + slope_y * 2
),
colour = "white",
arrow = arrow(length = unit(0.1, "cm"))
) +
geom_segment(
mapping = aes(
x = x,
y = y,
xend = x + curl_x * 2,
yend = y + curl_y * 2
),
colour = "black",
arrow = arrow(length = unit(0.1, "cm"))
) +
theme_minimal() +
coord_equal()
Placing points in such a field and letting it move along the curl
vectors results in interesting paths. Move the points in a loop and for
each iteration, save a copy of their current positions to a new data
frame (traces
) that can then be plotted to show the path
each point has taken.
Add an identifier (path_id
) to each point so that you
can later group the traces into distinct lines.
tribble(
~x, ~y, ~path_id,
1, 1, 1,
2, 1, 2,
3, 1, 3
) -> points
traces <- tibble()
for(i in 1:1000) {
curl <- curl_noise(
generator = gen_simplex,
seed = 1,
frequency = .1,
x = points$x,
y = points$y
)
points %>% mutate(
x = x + curl$x * 0.5,
y = y + curl$y * 0.5,
) -> points
points %>% bind_rows(traces) -> traces
}
traces %>% ggplot(aes(x = x, y = y, group = path_id)) +
geom_path() +
coord_equal() +
theme_minimal()
Add come color and an alpha channel that fades with the number of iterations, remove the plot axes, and play around with all the values.
library(scico)
tibble(
x = 1:50,
y = 0,
path_id = 1:50
) -> points
traces <- tibble()
for(i in 1:2000) {
curl <- curl_noise(
generator = gen_simplex,
seed = 1,
frequency = .2,
x = points$x,
y = points$y
)
points %>% mutate(
x = x + curl$x * 0.01,
y = y + curl$y * 0.01,
iteration = i
) -> points
points %>% bind_rows(traces) -> traces
}
theme_void() %+replace% theme(
legend.position="none",
panel.background = element_rect(fill="#000000")
) -> th
traces %>%
ggplot(aes(
x = x,
y = y,
group = path_id,
color = path_id,
alpha = iteration
)) +
geom_path() +
scale_color_scico(palette = "roma") +
xlim(c(-10,60)) +
ylim(c(-20, 20)) +
th