jueves, enero 03, 2008

Raytracing en F#

Tengo que confesar que éstos primeros pasos han estado llenos de lectura, corrección de bugs y pensar como voy a hacer las cosas. Si midiera mi productividad por líneas de código (como muchas organizaciones hacen) podría decirse que es bien pobre. Pero bueno... hasta ahora he realizado poco del raytracer Sin embargo ya se pueden ver los primeros resultados:



Ésta imagen generada no tiene implementado el sombreado de Phong sinó que lo único que hace es para cada rayo, halla una lista con todas las intersecciones. de ésta lista saca la menor intersección. Para mostrar la expresividad de F# (aunque en general este poder puede ser atribuido a cualquier lenguaje funcional) podemos mostrar como hallo todas las intersecciones con todas las superficies. Pero primero entendamos algunas cuestiones de diseño.

Cada superficie es una interfaz. Similar (hay diferencias!) a lo que uno conoce como interfaz en Java, es básicamente el tipado de los métodos y atributos (en F#, no sé si en todo .Net diferencian éstos en valores, miembros y métodos). En el caso de las superficies solo existe un método y éste es intersección, dado un rayo este devuelve un tipo colisión. Para manejar el caso que no haya colisión esta se envuelve en el equivalente a la monada Maybe de Haskell, en F# este tipo es option:


let hits objs r = map ((|>) r) (map get_intersect objs)


Entendamos esto... primero la segunda parte get intersect es de tipo ISurface -> (ray-> hit) es decir, extrae el método del objeto... con map lo que se hace es que se aplica la función a todos los elementos de objs. generando una lista de funciones (ray-> hit).

Ahora bien, teniendo una lista de funciones queremos aplicarlas a un objeto.

El caso parecido sería dado [+1,*3,+3] queremos aplicárselas a 2 para obtener [3,6,5]... map en su forma convencional no nos sirve porque éste recibe una función y la lista, y en este caso las funciones están en la lista. Siendo así usamos el operador pipe (|>) el cual sería en Haskell algo como a -> (a -> b) -> b y se define como (|>) a b = b a

Pues bien eso es precisamente lo que está en la primera parte... tomamos lo que sea que está en la lista (la función) y se lo aplicamos a r.

El problema de ésta aproximación es que estamos realizando 2 maps... medí tiempos y en general me estaba dando que para hacer el render de la imagen anterior se demoraba alrededor de

00:00:01.0625000

Lo cual teniendo en cuenta que son solo 2 esferas y falta mucho por hacer decido hacer caso omiso de:

"Premature optimization is the root of all evil."
C.A.R. Hoare


Como arreglar ésto?

Composición de funciones!!


let hits objs r = map (get_intersect >> ((|>) r)) objs


Él operador (>>) es similar al operador (.) de Haskell. Y similar al concepto de composición de funciones de matemáticas discretas o Cálculo. las funciones de cada map las convierto en una sola y listo! Con lo cual pasa a demorarse:

00:00:00.8750000

En este par de muestras sin ninguna relevancia estadística obtengo 18%... pero mas o menos está entre el 8% y el 20%

No hay comentarios.: