Árboles con funciones recursivas
Arte generativo con R
Hace un par de post hablamos de la generación de números aleatorios ver aquí. Hoy vuelvo a insistir en este tema fascinante que me embruja sin remedio. En cierta forma, lo que me atrae es la belleza del caos, la sorpresa de ver cómo una sucesión espontánea y aleatoria de números puede llegar a transformarse en imágenes de objetos cotidianos y naturales.
Esto es especialmente visible en los seres vivos, que muestran una geometría compleja, caótica aunque en ella subyace un ritmo matemático, caótico, fractal. Como veras en este experimento la belleza natural puede ser fruto del puro azar, simple sucesión aleatoria de ramificaciones.
Hoy vamos a crear una función que hace formas arbóreas fractales con con un método muy simple, las funciones recursivas.
Algoritmo
La idea es la siguiente: partiremos de un punto en el plano -la base del tronco- y dibujamos ahí una línea (vertical). Luego a partir del punto final de esa línea vamos dibujando ramas aleatorias, bifurcando en cada nodo.
Creamos una función llamada ramas()
que tiene de argumentos el punto de inicio de la rama, la longitud, anchura y ángulo de la rama. Se trata de una simple función de pintar líneas, a la que vamos a llamar de forma recursiva una y otra vez en cada nodo final de rama.
En este experimento solo hacemos bifurcaciones, pero si quieres puedes probar a poner la posibilidad de dividir no solo en 1 o 2, sino en 3 ramas en cada bifurcación, aunque esto aumenta sobremanera las ramas y satura la memoria del PC a poco que te descuides.
####################################
######## Función que pinta el arbol
parbol <-
function(h = 100, # altura media del arbol
ramificacion = 50,# posición de x0 del tronco en principio h/2
base = h / 2,# a mayor valor más ramas y más complejo
colort = "white", # color
sobreescribe = NULL) { # si no es null pinta encima del lienzo
# si es NULL pinta en un lienzo nuevo
if (is.null(sobreescribe)) {
#pinta el lienzo en blanco
plot(
1:h,
type = "n",
xlim = c(0, h),
ylim = c(0, h),
xlab = " ",
ylab = " "
)
}
# calculo de variables:
largo <- runif(1, h / 10, h / 5) # altura tronco
ancho <- sample(6:25, 1) # ancho tronco
angulo_rama = pi / 2 # angulo de rama inicial 90
# puntos xy de la linea del tronco principal
x1 = base
y1 = 0
x2 = base
y2 = largo
# pintamos el tronco en las coordenadas x1,x2 y1,y2
# ajuste de color del arbol
col01 <- adjustcolor(colort, alpha.f = 1)
abline(h = 0, lwd = ancho / 2, col = col01) #linea de suelo
# tronco base; lend indica la forma de extremo de linea (par)
lines(c(x1, x2),
c(y1, y2),
lwd = ancho,
col = col01,
lend = 1)
angle1 <- angulo_rama + rnorm(1) * pi / 16 # angulo de la siguiente parte del tronco
# Llama a la función de creación de las ramas a partir del punto
# final del tronco
ramas(c(x2, y2), ancho, largo, angle1, h / ramificacion, color = colort)
}
####################################
######## Funcion que pinta las ramas
ramas <- function(pini,# pini= punto inicio linea rama c(x,y)
w, # w= ancho de la rama
l, # l= largo de la rama
ang, # ang= angulo de la rama
lmin_rama = 10,# lmin_rama= limite min de long de rama recomendado h/50
color = "white") {
x1 = pini[1] - l * cos(ang)
y1 = pini[2] + l * sin(ang)
pfin = c(x1, y1)
# color de las ramas que va aumentando el alfa en las hojas
col01 <- adjustcolor(color, alpha.f = 1.5 - lmin_rama / l)
# pinta la linea con los datos dados
lines(c(pini[1], pfin[1]),
c(pini[2], pfin[2]),
lwd = w,
col = col01)
# Calcula la siguiente bifurcación:
dif <- pi / 2 #- (pi / 16)
angle1 <- ang + runif(1, 0, dif) / 2
angle2 <- ang - runif(1, 0, dif) / 2
# calcula aleatoriamente un nuevo grueso y largo de las ramas bifurcadas
n_ancho = runif(1, w * 0.6, w * 0.8)
n_largo = runif(1, l * 0.7, l * 0.9)
# Controla el crecimiento recursivo hasta el limite min de rama
if (n_largo < lmin_rama) {
# sale del loop, fin de función
} else {
# pinta las nuevas ramas de la bifurcación
ramas(pfin, n_ancho, n_largo, angle1, lmin_rama, col = col01)
if (runif(1) > 0.3) {
# un 30% de las veces no se bifurcará
ramas(pfin, n_ancho, n_largo, angle2, lmin_rama, col = col01)
}
}
}
Una vez hemos definido estas dos funciones veamos un ejemplo de uso, el primero con los valores por defecto y en el segundo cambiamos el fondo del lienzo a negro y las ramas a blanco:
# Creo un arbol de altura 100
parbol(120,150,color="black")
parbol(120,150,base=20,color="black",sobreescribe=1)
# Cambio el fondo a negro
par(bg = 'black')
parbol(100,150)
parbol(100,200,sobreescribe=1)
Las funciones recursivas suelen dar problemas con la memoria de trabajo del sistema. Son lentas cuando llegan a un punto de explosión exponencial de ramas, por lo que hay que tener cierto cuidado en no pasarse y limitar el bucle.
Si intentas reproducir este código, empieza con un valor pequeño del parámetro ramificacion=15
, por ejemplo 15 y ve aumentando este número poco a poco. Si notas que se ralentiza mucho el resultado ya sabes donde tienes el límite.
Conclusiones
Si te ha gustado este experimento, prueba hacer una aplicación shiny en la que los argumentos sean variables introducidos con selectores inputs, y así podrás ver cómo afecta cada uno a la imagen generada.
Hace un tiempo escribí un post en el que dibujábamos un bosque en R, con otras funciones y propósito, pero aquí está si quieres echar un vistazo.