miércoles, 22 de julio de 2015

Torbellinos de pasión

(es) Torbellinos de pasión

(eo) Pasiokirloj

Torbellinos de pasión

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: T n (z)= a n z , donde n Z y a,z C . Para que las espirales formen una familia como en la imagen, deben tener el mismo valor de a . En este caso a= 4 5 ( cos π 2 +i sin π 2 ) .
Para que estas espirales se conviertan en dobles espirales, se les aplica la siguiente transformación de Möbius
T ˆ n (z)=R T n R -1 (z) , con R(z)= z-1 z+1
a los números complejos z de la forma cos α +i sin α , con α [ π 4 -0.3, π 4 +0.3 ] , y n{-20,-19,-18, ,29,30} .
Nótese que R(0)=-1 , R()=1 . Es decir, lo que hace R 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