(es) Torbellinos de pasión
(eo) Pasiokirloj
http://dx.doi.org/10.13140/RG.2.2.34092.90243 
Descripción artística:
Dos torbellinos entrelazados girando en la misma dirección.
Motivación:
Una lectura parcial del libro Indra's Pearls (ISBN: 0 521 35253 3), y la búsqueda de comprender las espirales loxodrómicas y los loxódromos en sí (ver Líneas de rumbo).
Descripción técnica:
La imagen es una serie de proyecciones de líneas de rumbo (loxódromos) que se construyen en la esfera de Riemann girada sobre su centro, sobre el plano, por medio de una proyección estereográfica.
El proceso de construcción es el siguiente:
Las líneas de rumbo se inician como espirales exponenciales complejas, de la forma: , donde  y . Para que las espirales formen una familia como en la imagen, deben tener el mismo valor de . En este caso .
Para que estas espirales se conviertan en dobles espirales, se les aplica la siguiente transformación de Möbius
, con 
a los números complejos  de la forma , con , y .
Nótese que , . Es decir, lo que hace  es tomar un punto del plano complejo, lo proyecta en la esfera de Riemann, luego rota la esfera respecto de su centro (con eje de giro paralelo al eje complejo) y finalmente lo proyecta sobre el plano compleo de nuevo.
Para los colores, se utilizó una técnica de interpolación lineal segmentada pasando por los colores primarios sustractivos (amarillo, cyan y magenta) asociándolos al paso de .
Archivos
Código (Python):
#!/usr/bin/env python
# coding: utf-8
# Copyright 2015 Eduardo Adam Navas López
# Este programa es Software Libre liberado bajo la licencia GNU GPLv3 o su versión más reciente:
# http://www.gnu.org/licenses/gpl.html
"""Este programa genera una doble espiral.
"""
import pygame
import escala
import cmath
prueba = True
#prueba = False #Hay que descomentar esta línea para una versión 'final'
if prueba:
 ANCHO = 1000
 ALTO  = 1000
 #Grueso de la curva (en pixeles)
 GRUESO_LOXO = 3
 #Cantidad de segmentos por curva
 CANT = 1000 
 #Número de curvas
 NUMLINEAS = 2000
else:
 ANCHO = 8000
 ALTO  = 8000
 #Grueso de la curva (en pixeles)
 GRUESO_LOXO = 2
 #Cantidad de segmentos por curva
 CANT = 2000 
 #Número de curvas
 NUMLINEAS = 25000
INF_IZQ = -1.5-1.5j
SUP_DER = +1.5+1.5j
FACTOR_ESCALA = 0.025
MAGNITUD=4.0/5.0
THETA=cmath.pi/2
complejo_angulo = cmath.rect(1,-cmath.pi/4)
ROJO   = (255,0,0)
VERDE  = (0,255,0)
AZUL   = (0,0,255)
BLANCO = (255,255,255)
NEGRO  = (0,0,0)
def transformadaExpo(n,z,A=MAGNITUD,th=THETA):
 """
 n: Entero
 z: Complejo
 A: Módulo de la semilla (número real positivo)
 th: Argumento de la semilla
 """
 a = cmath.rect(A,th)
 k = a**n
 return k*z
def transformadaLoxo(n,z,A=MAGNITUD,th=THETA):
 """
 n: Entero
 z: Complejo
 A: Módulo de la semilla (número real positivo)
 th: Argumento de la semilla
 """
 a = cmath.rect(A,th)
 k = a**n
 return complejo_angulo*(z*(1+k)+k-1)/(z*(k-1)+1+k)
def definirColor(n):
 "n: índice de la línea [0,NUMLINEAS-1]"
 colores = \
  [255,255,0], \
  [102,153,  50], \
  [0,255,255], \
  [51, 102,153], \
  [255,0,255], \
  [153, 50, 102], \
  [255,255,0]
 #Sólo entre dos colores:
 #return map(lambda i: int(n*(colores[5][i]-colores[4][i])/(NUMLINEAS-1)+colores[4][i]), range(3))
 
 #Entre todos los colores:
 ix = n*(len(colores)-1)/NUMLINEAS #división entera
 n_ant = float(ix)  *NUMLINEAS/(len(colores)-1)
 n_sig = float(ix+1)*NUMLINEAS/(len(colores)-1)
 t = (n-n_ant)/(n_sig-n_ant)
 return map(lambda i: int(t*(colores[ix+1][i]-colores[ix][i])+colores[ix][i]), range(3))
 
if __name__ == "__main__":
 pygame.init()
 if prueba:
  pantalla = pygame.display.set_mode((ANCHO, ALTO), pygame.RESIZABLE)
  pygame.display.set_caption("Torbellino")
 else:
  pantalla = pygame.Surface((ANCHO,ALTO))
 e = escala.EscalaCompleja((ANCHO, ALTO), INF_IZQ, SUP_DER)
 #puntos del plano complejo a graficar:
 MIN = -20
 MAX = 30
 rango = [float(i)*(MAX-MIN)/CANT+MIN for i in range(CANT)]
 
 puntosEspirales = []
 pantalla.fill(BLANCO)
 if not prueba:
  print "Construyendo y dibujando puntos..."
 for i in range(NUMLINEAS):
  #Estríctamente entre pi/4-0.3 y pi/4+0.3
  alfa = 0.6*i/NUMLINEAS+cmath.pi/4-0.3
  z = cmath.rect(1,alfa)
  #inten= int(256.0*i/NUMLINEAS)
  #c = (inten,inten,inten) ##Escala de grices
  c = definirColor(i)
  #puntosExpo = [c] +[transformadaExpo(n,z) for n in rango]
  puntosLoxo = [transformadaLoxo(n,z) for n in rango]
  puntosReales = map(e.vr,puntosLoxo)
  pygame.draw.aalines(pantalla,c,False,puntosReales)
  if (not prueba) and (i+1)%100==0:
   print i+1, "/", NUMLINEAS
 if prueba:
  pygame.display.flip()
  pygame.image.save(pantalla, "torbellino1000.png")
 else:
  print "Guardando archivo... por favor espere"
  pygame.image.save(pantalla, "torbellino.png")
 exit(0)
 
No hay comentarios:
Publicar un comentario