Hablamos de animación, en un sentido amplio, cuando uno de los elementos que compone un cuadro pasa de un estado a otro. Puede tratarse del cambio de aspecto de un objeto (forma, tamaño, material o textura), de su posición, del ambiente, o el entorno en el que está situado.
Un proyecto de animación puede encararse de varias maneras, haciendo uso de las técnicas conocidas. Esto dependerá del efecto buscado y responderá en alguna medida a alguno de los siguientes enfoques:
El primero suele hacer uso de cálculos físicos, datos reales y modelos
verosímiles. Utiliza por lo general módulos, subprocedimientos o macros,
que una vez escritos facilitan muchísimo la tarea.
El segundo, mas artístico, se apoya en algunas funciones que
responden a patrones conocidos, alterando el valor del reloj.
Estas funciones se usan a modo de "plantilla" o "curva de tiempo",
para generar la animación y "modelarla". Se regula con ellas la transición
entre posiciones, ademanes o poses clave, para lograr que
los personajes y sus movimientos ganen mayor expresividad y realismo.
Esta técnica de trabajo suele ser simple, rápida y efectiva, ya que requiere
de unas pocas líneas de código que declaren que patrón se aplicará a la
transición.
La simulación, en cambio, toma el valor del reloj para determinar la fase de
cada evento en la animación, sincronizándolo con los demás, tras calcular
los valores determinados por las ecuaciones que recrean el fenómeno que imita.
El infografista, puede hacer uso de cualquiera de las técnicas que respondan a estos dos enfoques, según lo dicte su criterio, a fin de lograr el efecto o resultado deseado.
Como introducción a las técnicas de animación recomiendo visitar la página Animations with POV-Ray de Friedrich A. Lohmüller. Esta contiene tutoriales que explican los términos y conceptos básicos, además de brindarnos ejemplos de animaciones cíclicas y sus códigos fuente (rotar objetos, rotar cámara, vaivén de péndulo y resorte, ruedas y engranajes)
POV-Ray genera cada una de las imagenes que componen una animación en base a su variable clock. Podemos setear el rango de esta variable con los valores iniciales y finales que necesitemos. Podrían ser 0 y 8, si nuestra animación durara 8 segundos, o sus valores por defecto, 0 y 1. En este ultimo caso estaríamos utilizando la variable como un coeficiente de la transición o la fase del movimiento, que tendríamos que multiplicar por 8, si queremos obtener el valor del tiempo transcurrido en segundos.
En los siguientes ejemplos me enfocaré en el cambio de posición o desplazamiento de un objeto en función del tiempo (movimiento). Las técnicas utilizadas pueden emplearse tambien para ajustar otras transiciones.
El movimiento mas simple es el rectilíneo uniforme (MRU). Una representación del mismo puede lograrse sencillamente con una declaración del tipo ...
object { Mi_Objeto translate DistanciaTotal * clock}
... y comprobaremos que el objeto parece moverse de manera uniforme a lo largo de la distancia a recorrer. Aquí, el espacio recorrido es directamente proporcional al valor de reloj, de modo que se multiplica el valor de reloj (que varía de 0 a 1, es decir, que siempre es fracción de un entero y determina la fase de la animación) por el espacio total a recorrer para determinar la posición del objeto. Esta fracción o fase que determina el reloj es válida en este caso tanto para el tiempo transcurrido como para el espacio. Por ejemplo si la distancia a recorrer es 100 metros y el intervalo de tiempo 10 segundos, un valor de reloj de 0.5 nos indicará que se ha recorrido la mitad del trayecto (100 m * 0.5 = 50 m) y que ha transcurrido la mitad del tiempo (10 s * 0.5 = 5 s).
La fórmula para calcular la posición del objeto puede condensarse en una macro de interpolación que tome como parámetros su posición inicial, su posición final y el valor de reloj.
// TSPosition(Reloj, PosicionInicial, PosicionFinal) #macro TSPosition(MClock,I_Pos,F_Pos) #local deltaE = F_Pos-I_Pos; #local Pos = I_Pos + deltaE * MClock ; Pos #end
En este caso...
#declare MiReloj = clock;
object{MiObjeto translate TSPosition(MiReloj,-x*5, x*5)}
... nos servirá para trasladar nuestro objeto hasta el punto de destino en el tiempo de reloj.
Hemos creado la variable MiReloj para que asuma valores correspondientes a un patrón de movimiento específico en función del tiempo transcurrido. Para modificar el modo en que un objeto se mueve se suelen utilizar distintas funciones del reloj, dependiendo del patrón que se quiera emular:
// ----------------------------------------------------------------------------------
// Algunas modificaciones de reloj
// ----------------------------------------------------------------------------------
// Sin modificación - Movimiento uniforme
#declare MiReloj = clock;
/* Comienza lento y acelera (por ejemplo un coche). Se lo eleva a una potencia
(en este caso a 4), pero se lo puede elevar a otras, dependiendo del grado
de aceleración que se quiera lograr. Si utilizamos como exponente el valor
"1" la aceleración es nula. */
#declare MiReloj = pow(clock, 4);
// Comienza veloz y desacelera (por ejemplo una explosion)
#declare MiReloj = pow(clock, 1/4);
// Emula físicamente a un objeto pendiendo de un resorte.
#declare MiReloj = 1 - pow(2*clock - 1, 2);
// Comienza lento, acelera y luego desacelera.
#declare MiReloj = sin((clock-.5) * pi) / 2;
// Comienza lento, acelera y regresa rapidamente (como retroceso de arma de fuego)
#declare MiReloj = pow(1 - abs(clock*2-1), 3);
// Comienza rápido, desacelera y regresa (como banda elástica)
#declare MiReloj = (clock < 0.5 ? pow(clock*2, 1/3) : pow(clock*2-2, 4));
// De acuerdo al efecto que queremos obtener lo aplicamos al parámetro x,y,z de
// deltaE que corresponda, o a todos ellos:
object { MiObjeto translate deltaE * MiReloj }
La última declaración de MiReloj está enunciada en forma condicional. También se puede declarar como una condición #if tradicional:
#if(clock < 0.5)
#declare MiReloj = pow(clock*2, 1/3);
#else
#declare MiReloj = pow(clock*2-2, 4);
#end
Aplicando estas modificaciones del reloj en la macro TSPosition() podremos variar la velocidad de nuestro objeto.
Ajustando la cantidad de cuadros por segundo, conseguiremos que el movimiento se vea aun mas verosímil. Pero al tratar de mover varios objetos a la vez y sincronizarlos con otros eventos, como por ejemplo una explosión, es probable que nos encontremos con desfasajes de tiempo y velocidad.
Una manera de solucionar estos desfasajes es estimar el segmento de tiempo en segundos correspondiente a cada evento, la modificación de reloj mas apropiada e invocar una macro de interpolación como en el siguiente ejemplo:
// Clock (Segmento)
// Determina el valor del reloj en el intervalo de tiempo
#macro Clock( Segment )
#local I_ClockSgmnt = Segment.x/TotalTime;
#local F_ClockSgmnt = Segment.y/TotalTime;
#switch(clock)
#range(0,I_ClockSgmnt)
#local valor=0;
#break
#range(I_ClockSgmnt,F_ClockSgmnt)
#local valor= (clock-I_ClockSgmnt) / (F_ClockSgmnt-I_ClockSgmnt);
#break
#range(F_ClockSgmnt,1)
#local valor=1;
#break
#end
valor
#end
// --------------------------------------------------
// Parametros Generales
#declare TotalTime = 10; // seconds
#declare P1= -x*5; // Start Position
#declare P2= x*5; // Final Position
// Segmento de tiempo en segundos
#declare TimeSegment1 = < 1, 6>;
// Modificaciones del reloj
#declare Clock1 = pow(Clock(TimeSegment1),2); // Acelerado
object{MiObjeto translate TSPosition(Clock1, P1, P2) }
Podemos reunir los tres procedimientos (segmentar reloj, aplicar función, interpolar posición) en una sola macro, si previamente definimos las funciones para modificar el reloj como un subprocedimiento al que podamos invocar desde ella.
Chris Colefax, en su archivo ClockMod.inc, ha incluído los procedimientos que nos lo permiten hacerlo. Las funciones configuradas son 9: Accelerate, Decelerate, Smooth-Curve, Triangle, Jump, Bounce, Oscillate, Wave y Recoil. El archivo zipeado incluye ademas AutoClock.mcr (Automatic Clock Modifier), útil e intuitivo para animar simultaneamente texturas, traslación, rotación y escalado para cada objeto, al modo de un simple comando POV. Tambien se puede ver la gráfica del reloj que seleccionemos utilizando el comando Preview_Clock() con la misma signatura que el comando Using().
// Ejemplo de los comandos de AutoClock.mcr
// La signatura de la macro Using() es:
// (clock_type, clock_strength, clock_repeat, clock_combine)
object{ MiObjeto
texture {
// reloj lineal
Texture_From (0, myTexture1)
Using ( "", 1, 1, "")
Texture_To (1, myTexture2) }
}
rotate //S-Curve combinado con Accelerate
From (0, <45, 0, 0>) // (clock_start, vector1)
Using ( "S", 0.8, 3, "A")
To (1, <45, 180, 0> ) (clock_finish, vector2)
translate
From (0, <-0.5, 0, 0>)
Using ( "J", 0.9, 2, "D")
To (0.5, <0.5, 0, 0>)
scale
From (0.5, 1)
Using ( "W", 0.8, 1, "")
To (1, 0.25)
}
En la página de ClockMod hay un enlace a un excelente tutorial online de AutoClock.mcr, escrito por Steve Strickland que durante años estuvo en la red. Este enlace se encuentra desactualizado (el tutorial ya no está alojado allí y no lo he podido encontrar en ningun otro sitio). Debido a esto he puesto aquí una copia zipeada, para quienes lo requieran: Tutorial for the Automatic Clock Modifier by Steve Strickland
Siguiendo con lo desarrollado en esta página, podemos utilizar uno de los relojes del archivo ClockMod.inc, adaptando la macro TSPosition() del siguiente modo:
// Adaptacion de la macro TSPosition()
// Utiliza "ClockMod.inc" de Chris Colefax, que devuelve la variable mclock
// ClockMod recalcula el valor de reloj en el segmento y aplica la función seleccionada.
// http://www.geocities.com/ccolefax/clockmod.html
#macro CMPosition(clock_type,Segment,I_Pos,F_Pos)
#local deltaE = F_Pos-I_Pos;
#local clock_start = Segment.x/TotalTime; // Start/Total
#local clock_finish = Segment.y/TotalTime; // Finish/Total;
#include "ClockMod.inc"
#local Pos = I_Pos + deltaE * mclock ;
Pos
#end
object{MiObjeto translate CMPosition("A",TimeSegment1, P1, P2)}
|
Un demo generado con las macros "Position". Todas las esferas se mueven dentro de un segmento de 10 unidades de distancia, utilizando distintos relojes. La blanca lo hace en tiempo lineal durante 5 segundos. Las otras lo hacen en intervalos de 2.5 segundos. Puedes descargarte el archivo fuente de esta animación, para ensayar tus propias modificaciones del reloj. |
Para la esfera roja utilicé la macro CMPosition() con el comando "Accelerate" de ClockMod.inc. ClockMod recalcula el valor del reloj dentro del segmento y luego lo modifica. Permite además combinarlo con otro y regular la frecuencia en cantidad de ciclos por segmento. Con las demás esferas utilicé la macro Clock() que solo recalcula el valor de reloj en cada segmento, y luego los alteré con algunas de las fórmulas expuestas en esta página.
La esfera amarilla recorre la distancia en 2,5 segundos a velocidad constante. Si tomamos esta velocidad como referencia, notaremos que la esfera verde comienza con una velocidad mas elevada que luego decae a cero (movimiento desacelerado), mientras que la roja lo hace a la inversa (movimiento acelerado), aunque no del mismo modo. La esfera roja mantiene una aceleración constante, la verde no. Esto se hace evidente si calculamos velocidades y aceleraciones en cada punto del trayecto, y tambien es apreciable en sus gráficas.
| Gráficos generados con AutoClock.mcr | ||
|---|---|---|
| Linear Clock | Accelerate Clock | Decelerate Clock |
| clock | pow(clock,2) | pow(clock, 0.5) |
![]() |
![]() |
![]() |
| Sin modificaciones. Espacio (y) y tiempo (x) proporcionales. | La aceleración se logra "curvando" el reloj hacia abajo | La desaceleración "curvándolo" hacia arriba |
En la primera, el espacio recorrido (ordenadas) es proporcional al tiempo transcurrido (absisas), de modo que su cociente nos da como resultado una velocidad constante de valor 1 (unidad de espacio/unidad de tiempo). La segunda gráfica muestra una curva suave generada por la función f(x) = x2, en donde el espacio crece geométricamente con respecto al tiempo. Comienza con velocidad inicial cero para alcanzar 2 unidades de velocidad final, con una aceleración constante de 2. Al ser un movimiento uniformemente acelerado, la velocidad media difiere de las instantáneas, aunque no su aceleración. En la tercera curva la velocidad alcanza un valor importante al principio, para luego desacelerar. Esto se ve tambien en el movimiento que realiza la esfera verde en la animación. Para lograr una reducción de la velocidad con desaceleración constante, en lugar de utilizar la raíz del valor de reloj como función, utilizaremos sus cuadrados como en el caso de la aceleración, pero invirtiendo la fase de reloj de 1 a 0.
#declare tension = 1; #declare MiReloj = 1 - pow( 1 - clock, 2 * tension);
| Uniformemente Desacelerado | ||
| 1 - pow( 1 - clock, 2 * strength) | ||
![]() |
![]() |
![]() |
| La función invierte la curva de "Accelerate Clock" | Tensión 0.7 | Tensión 1.5 |
Agregando a la fórmula un factor de "strength", podemos tensionar la curva para aumentar la desaceleración, utilizando valores mayores a 1, o hacer lo contrario, pudiendose llegar a anular la aceleración con una tensión de 0.5. Valores inferiores a éste generan una aceleración. En todos los casos la velocidad final será 0. Esta función puede agregarse (junto con otras) al archivo ClockMod, para poder disponer de ella cuando la necesitemos.
La velocidad y la aceleración son magnitudes vectoriales. Podemos aplicar a cada componente del vector una función distinta, lo que generaría en la mayoría de los casos una trayectoria curva, aun cuando las aceleraciones en cada uno de los ejes fueran constantes.
#declare Cx = pow(clock,0.7);
#declare Cy = pow(clock,2);
#declare Cz = clock;
#declare Pos_i = <20, 0, 10>;
#declare Pos_f = <50,30,150>;
#declare DeltaE = Pos_f - Pos_i;
object { Mi_Objeto translate Pos_i + DeltaE * <Cx,Cy,Cz>}
En el caso de que quisieramos utilizar una velocidad inicial o final mayor que cero en una función del reloj hemos de profundizar ciertos conceptos matemáticos relacionados con la cinemática desarrollados en Principios de mecánica y animación (2).
La siguiente es una macro escrita por John VanSickle, útil para generar movimientos rectilíneos y curvos, que utiliza la función coseno. Devuelve como resultado el vector de la posición del objeto en cada fase del movimiento.
/*
transition.mcr by John VanSickle
This is a "semi-spline" function, useful mainly for animation work. The user
selects the Start and End points for a movement, and supplies a Phase value to
say how far along the movement has gone.
The Swerve parameter allows you to specify the amount by which motion will move
to the side, so that motion can be along an arc instead of along a straight
line. At Phase=0 the macro returns the Start point, at Phase=1 the macro
returns the End point, and for intermediate values of Phase the macro returns
intermediate values.
The macro uses the cosine function to provide smooth motion from Start to End.
*/
#macro Transition(Start,End,Swerve,Phase)
#local t_P=(Phase)*pi;
#local t_TL=(1-cos(t_P))/2;
#local t_TS=(1-cos(t_P*2))/2;
( (Start)*(1-t_TL) + (End)*t_TL + (Swerve)*t_TS )
#end
// Modo de uso:
object{MiObjeto translate Transition(I_Pos, F_Pos, v_Desviacion, MiReloj)}
En el caso de movimientos simultaneos y complejos, una manera de lograr que cada objeto se mueva correctamente en relación al resto, es recurriendo a las fórmulas clásicas de movimiento, comenzando con el MRU:
e=v*t v=e/t t=e/v
#declare p1 = <0, 100, 0>; #declare p2 = <100, 100, 100>; #declare deltaE = p2 - p1 ; // segmento de espacio (vectorial) #declare Dl= vlength(p2-p1); // distancia (escalar)
El vector espacio determina tres magnitudes <x,y,z>, correspondientes
a la distancia en cada uno de los tres ejes.
Los cálculos implicados podrían realizarse de dos modos, de acuerdo a lo que
necesitáramos saber o controlar en el movimiento en cada instante en que
ocurriera el mismo:
La velocidad en m/s dependerá del valor relativo que le asignemos a la
variable clock, y del valor que asignemos a cada unidad de espacio.
En el ejemplo utilizaremos la relación de 1 unidad pov = 1 metro y el valor
final de reloj igual a 10 s. Para que la animación transcurra en el tiempo
real de 10 s habrá que ajustar para ello
la cantidad de cuadros por segundo. Una animación de 25 cuadros por segundo
estará compuesta por 250 cuadros en total.
Al tratarse de MRU, el espacio recorrido (Er)
será proporcional con respecto al tiempo transcurrido (Tt)
// -------- Ejemplo de calculo para MRU ---------------
// Parámetros de la trayectoria
#declare Pto_i = <0, 100, 0>; // Punto inicial
#declare Pto_f = <100, 100, 100>; // Punto final
#declare Ti=0; // Tiempo inicial en segundos
#declare Tf=10; // Tiempo final en segundos
// Fase, momento o fracción de tiempo de acuerdo al reloj
#declare fT=clock;
// Delta correspondiente a cada segmento.
#declare deltaT=Tf-Ti; // Segmento completo de tiempo
#declare deltaE=Pto_f-Pto_i; // Segmento completo de espacio
#declare Tt=fT*deltaT; // Tiempo transcurrido en segundos
// Velocidad en m/s
#declare V=deltaE/deltaT;
// Movemos nuestro objeto al punto del espacio correspondiente
// al tiempo transcurrido.
#declare Er=V*Tt; // Espacio recorrido
object { MiObjeto translate Pto_i + Er }
// Obtenemos los datos del movimiento en la ventana mensajes
// de POV-Ray
#declare Dt= vlength(Pto_f-Pto_i); // Distancia Total
#declare Rap= Dt/deltaT; // Rapidez
#declare Dr =Rap*Tt; // Distancia recorrida
#debug concat("Tiempo Transcurrido = ",str(Tt,0,3)," s","\n")
#debug concat("Distancia Recorrida = ",str(Dr,0,3)," m","\n")
#debug concat("Rapidez = ",str(Rap,0,3)," m/s","\n")
#debug concat("Espacio recorrido = ",
concat("<",str(Er.x,0,3),",",str(Er.y,0,3),",",str(Er.z,0,3),">"),
"\n")
En este ejemplo, he definido los parámetros de movimiento en sistema
MKS (metro, kilogramo, segundo) junto con las ecuaciones necesarias.
La distancia recorrida sería la diagonal o hipotenusa, y su valor la raíz
cuadrada de la suma de los catetos (lados), en este caso 141.42 metros.
La rapidez r = d/t : r = 141.4 m / 10 s : r = 14.14 m/s en
la dirección y sentido del vector (p1 hacia p2). La velocidad <10, 0, 10> m/s.
La velocidad del objeto y su rapidez, nos pemitirían trabajar
directamente con datos reales (la velocidad de un coche o de
un proyectil), utilizando para ello macros modulares de cálculo cinemático.