En ocasiones es interesante escribir algún código, para que nuestro modelo se desenvuelva con cierta autonomía, al modo de un personaje de videojuego, y ejecute acciones como "levantar un brazo", "asir un objeto" en base a una simple instrucción, o se mueva sobre terreno accidentado de manera automática. Los programadores de juegos, en estos casos, suelen recurrir a subrutinas auxiliares que se encargan de detectar posiciones, obstáculos, colisiones, etc. Se trata de un tema complejo, en lo que respecta a la arquitectura y jerarquía de nuestro modelo, el universo virtual en que se desenvuelve, y de los eventos que deseamos que ocurran. Sin embargo, este tema se ha resuelto en muchos lenguajes, que utilizan subrutinas similares, en cuanto a su funcionamiento.
Las poderosas capacidades de programación de POV-Ray permiten desarrollar rutinas como
las mencionadas. Un buen ejemplo de ello son las macros generales de
simulación mecánica (mechanics simulation) escritas por Christoph Hormann, que se
incorporaron al MegaPOV 1.0
Sim-POV
MechSim Mechanics simulation experiments
Sim-POV sample scenes
En esta página no pretendo llegar tan lejos, aunque a modo de ejercicio, se puede desarrollar algo mas el tema de movimiento ocular, siguiendo con lo mencionado en la página de macros. También citar instrucciones y procedimientos de POV-Ray que podrían servir para desarrollar las subrutinas necesarias.
Siguiendo con lo anterior, sería interesante darle a nuestro modelo una mayor naturalidad al movimiento de enfoque visual y hacer que lo realice automáticamente por medio de un comando directo o una detección.
Supongamos que queremos que el modelo mire hacia determinados puntos del camino mientras se mueve, o que repentinamente mire a la cámara, rotando la cabeza o el torso de ser necesario, cuando el objeto a observar se encuentre fuera de su ángulo o campo de visión. En este caso nuestra figura adquirirá cierta autonomía en sus movimientos dada por factores que le impongamos, ejerciendo de este modo un control indirecto sobre ella, con instrucciones del tipo DetectarObjeto(Intervalo) y MirarHacia(Objeto, Intervalo). Digo "cierta" autonomía ya que podríamos hacer uso de una técnica mixta, con el fin de simplificar pasos o evitar todos los cálculos que implicaría un modelo totalmente automático. Aplicaríamos ciertas soluciones de Cinemática Inversa (IK ó Inverse Kinematic) que resolvieran esta situación, y para lo demás seguiríamos aplicando Cinemática Directa (FK ó Forward Kinematic), calculando entonces los ángulos necesarios en que deberán rotar los ojos con exactitud, en lugar de aplicar ángulos directamente, al tanteo, sin garantías de éxito inmediato.
De esta manera, habiendo definido el movimiento de locomoción como una interpolación entre poses clave, podemos luego corregir el ángulo de los ojos y eventualmente la cabeza, con el fin de que mire directamente hacia la cámara o hacia algún otro lugar cuando se lo requiera.
También sería interesante definir estas rotaciones como si se tratara de un robot que utiliza servomotores para flexionar sus extremidades, para que se comporte como un verdadero autómata virtual. Necesitaríamos entonces conocer sus ángulos de rotación en lugar de realizar una transformación directa resuelta con una matriz. Si tomáramos en cuenta la masa del objeto a mover hasta podríamos calcular el torque exigido por el movimiento.
La cinemática inversa comprende varios métodos que pueden investigarse en la red, seguramente mas apropiados que el que presentaré a modo de ilustración, basado en cálculos trigonométricos. Sin embargo esta opción no requiere amplios conocimientos matemáticos y resulta interesante para comprender básicamente como llegar al resultado buscado.
Para incluir los ojos ya rotados en la cabeza, mirando hacia el objetivo antes de aplicar la pose, podríamos seguir los siguientes pasos:
En base a estas premisas, modifiqué el archivo Figura_Demo.pov presentado en la página de macros para generar estas escenas, a modo de demostración.
![]() |
![]() |
| Rotación de la cabeza y los ojos mediante la macro "MirarHacia(Target)" | Rotación de la cabeza y los ojos - Vista Lateral |
![]() |
![]() |
| Rotación de los ojos sin movimiento de cabeza | La cámara definida como "Target" |
En el archivo resultante redefiní la cabeza del modelo, agregándole los ojos, la boca simplificada y las macros de cálculo. Utiliza los mismos archivos de inclusión, de la página de Macros.
En el caso de animaciones, se pueden realizar algunas modificaciones para lograr mayor naturalidad y realismo. Por ejemplo, la variable IK_Eyes mas que controlar la condición de si la cabeza y otros elementos del modelo se moverán o no, puede determinar directamente su grado de movimiento, si se aplica como un factor que afecte el grado de libertad de los ojos, es decir, si se multiplica por el límite angular de los mismos. En este caso asumiría valores entre 0 y 1, representando estos dos valores el mínimo y máximo grado de libertad ocular y/o de la cabeza.
trace(OBJECT_IDENTIFIER, A, B, [VECTOR_IDENTIFIER])
Se utiliza para hallar la posición exacta en la que un rayo con origen en un punto A en la dirección especificada por el vector B intersecta la superficie de un objeto declarado previamente (OBJECT_IDENTIFIER). Si lo intersecta devuelve la coordenada en donde esto ocurre. Si no lo hace, devuelve <0,0,0>.
También nos brinda la opción de declarar un vector (VECTOR_IDENTIFIER) en donde almacenar el valor de la normal del objeto (no de su textura) en el punto de intersección. Si no existir tal punto, el valor almacenado será <0,0,0>.
Esto nos abre muchas posibilidades de interacción, fundamentalmente con objetos irregulares. Se utiliza por ejemplo para plantar al azar objetos como árboles y arbustos sobre terrenos accidentados. Nos evita además el averiguar posiciones a ojo mediante acercamientos con la cámara. También nos permite utilizar la normal de la superficie si queremos ubicar cabello o espinas, perpendicularmente a ella.
![]() |
![]() |
![]() |
| Imagen del demo trace1.pov | Imagen del demo trace2.pov | Utilización de trace para hallar puntos de apoyo |
Así, estas líneas nos devolverán los vectores Intersect y Norm.
#declare Norm=<0,0,0>; #declare Intersect=trace(objeto, desde, direccion, Norm);
Si queremos hallar una interseccion se puede utilizar una macro del tipo:
#macro Interseccion(objeto, desde, direccion)
#local Intersect=trace(objeto, desde, direccion);
Intersect
#end
El siguiente ejemplo utiliza una macro que escribí para adherir objetos utilizando la función trace. En este caso defino un cono como púa a adherir al objeto principal; una esfera deformada. Luego utilizo la función while para realizar un bucle que adhiera mas púas. La función rand genera valores pseudo-aleatorios entre 0 y 1 para darles una distribución aleatoria. Para generar un cuerpo irregular utilizo un objeto isosurface (isosuperficie) y una función de esfera afectada por f_noise3d(), tomado del archivo de ejemplo biscuit.pov que trae POV-Ray en su paquete de demos. El comienzo del código es:
// **************************************************************************
// POV-Ray v3.5 Scene File
// Created by Rodolfo Nothstein
// Desc: Ejemplo de macro utilizando la función trace
// **************************************************************************
#version 3.5;
#include "functions.inc"
background { color rgb <0.6,0.7,1.0> }
global_settings { assumed_gamma 1.0}
// Macro de Spiritone (http://www.spiritone.com/~english/cyclopedia/index.html)
// Coloca un objeto en la posicion V_Locacion y lo orienta hacia el vector V_Direccion.
// Uso:
// object{MiObjeto OrientZ(V_Locacion, V_Direccion)
#macro OrientZ(p1,p2)
#local v_Sky=<0,1,0>;
// #local nz = vnormalize(p2-p1); // Objeto frente a +z
#local nz = vnormalize(p1-p2); // Objeto frente a -z
#local nx = vnormalize(vcross(v_Sky,nz));
#local ny = vcross(nz,nx);
matrix < nx.x,nx.y,nx.z, ny.x,ny.y,ny.z, nz.x,nz.y,nz.z, p1.x,p1.y,p1.z >
#end
// Adhiere el objeto2 al objeto1 en la intersección especificada, orientándolo segun la normal.
#macro Adherir(objeto1, desde, direccion, objeto2)
#local Norm=<0,0,0>;
#local Intersect=trace(objeto1, desde, direccion, Norm);
object{objeto2 OrientZ(<0,0,0>,Norm) translate Intersect}
#end
camera {
location <0.0, 1.0, -2.5>
look_at <0.0, 0.0, 0.0>
}
light_source{<1500,2500,-2500> color rgb <1,1,1>}
#declare Fun_Sphere = function {x*x + y*y +z*z}
#declare cuerpo=
isosurface {
function {Fun_Sphere(x,y,z) + f_noise3d(x*2,y*2,z*2)*0.4}
threshold 1 max_gradient 5 accuracy 0.01
contained_by {box {-1,1}}
scale 0.6
pigment{rgb x+y*0.7}
}
#declare pua = cone{z*0, 0.02, -z*0.9, 0 pigment{rgb x}}
object{cuerpo}
Agregando a este código el que acompaña a las siguientes imágenes se obtienen éstas por resultado.
![]() |
Agregando la línea:
Adherir(cuerpo,<-0.25,0.25,-5>, <0,0,1>,pua)logramos que la macro encuentre la intersección desde el punto dado en dirección +Z para así ubicar una púa alineándola con la normal. |
![]() |
Reemplazando la línea de la escena anterior por este otro código, generaremos un bucle
que adosará púas cada 15 grados alrededor del eje vertical, a una altura específica.
#declare Rot = 0; #while (Rot < 360) #declare RotY = <0,Rot,0>; #declare altura = 0.3; Adherir(cuerpo,vrotate(<0,altura,-5>,RotY),vrotate(<0,0,1>,RotY),pua) #declare Rot = Rot + 15; #endLa función vrotate() trabaja de modo similar a rotate, aunque devuelve la rotación de un vector en lugar de la de un objeto. |
![]() |
Este código define una altura aleatoria para las púas, en base a la función rand. Ésta
es muy utilizada para colocar objetos al azar.
#declare R1 = seed(0); // inicializa la secuencia pseudo-aleatoria #declare Rot = 0; #while (Rot < 360) // Comienzo del bucle #declare RotY = <0,Rot,0>; #declare altura = -0.45+0.9*rand(R1); Adherir(cuerpo,vrotate(<0,altura,-5>,RotY),vrotate(<0,0,1>,RotY),pua) #declare Rot = Rot + 15; #end // Final del bucleLa función seed se carga con un valor "semilla" que genera la secuencia pseudo-aleatoria. |
En el paquete de archivos de POV-Ray hay macros que utilizan la función trace, por ejemplo en el archivo shapes.inc. También hay un algoritmo de detección de colisiones en makestacks.inc, utilizado por los archivos que lo acompañan en la carpeta blocks. Se pueden hallar otros ejemplos del uso de trace en abyss.pov, gaussianblob.pov, isocacti.pov, povcatray.pov.
Jaime Vives Piqueres utiliza ampliamente esta función para el emplazamiento de cámara y objetos en su Proyecto Tierra, para generar paisajes fotorealistas de gran calidad. El paquete de 9 archivos (que creó en el año 2004) pesa en conjunto solo 102 KB, algo por cierto admirable, además de sus técnicas, que denotan el gran conocimiento que posee sobre trazadores. Este paquete genera "automáticamente" escenarios muy convincentes, alterando solo algunas variables y switches en el archivo principal.
Esta funcion permite comprobar si un punto está adentro de un objeto sólido o no.
inside(objeto, vector)
Retorna 0.0 cuando el vector esta fuera del objeto y 1.0 cuando el vector esta adentro de él. Sirve para detectar si un objeto fue atravesado por otro (por ejemplo la punta de un proyectil), o si un punto se encuentra en el interior de un ámbito declarado como objeto. Podemos hallar una demostración de su uso en la línea 132 del archivo biscuit.pov.