Hay Klingons Virando A Estribor...

Ahora que conoces la arquitectura básica del sistema de animación VRML, vamos a empezar cubriendo los nodos que usarás normalmente para la interacción y animación de tus mundos. Hay tres clases principales de nodos sobre los que vamos a aprender: Los sensores, Interpoladores y Scripts. Éstos, a su vez, pueden ser divididos en subcategorías. Ahora vamos a mirar los Sensores. En este primer capítulo vamos a aprender algo sobre Sensores Medioambientales. Éstos no aceptan la entrada directamente del usuario, pero en cambio captan eventos medioambientales, como el paso de tiempo, la posición del usuario, y otras cosas útiles.

TimeSensor

El TimeSensor es básicamente un timer (cronómetro). Es único en VRML, no tiene ninguna posición en el mundo, y ninguna geometría asociada. Simplemente es un cronómetro abstracto, sentado tranquilamente mientras cuenta. También es uno de los nodos más importantes en la animación de VRML. Puede usarse generar eventos regulares, proporcionar eventos cronometrados, o manejar los nodos interpoladores. La sintaxis es como sigue. Explicaré cada campo individualmente en un minuto...

TimeSensor {
   exposedField   SFTime      cycleInterval     1
   exposedField   SFBool      enabled           TRUE
   exposedField   SFBool      loop              FALSE
   exposedField   SFTime      startTime         0
   exposedField   SFTime      stopTime          0
   eventOut       SFTime      cycleTime
   eventOut       SFFloat     fraction_changed
   eventOut       SFBool      isActive
   eventOut       SFTime      time
}

Vale. En primer lugar está el campo cycleInterval. Es bastante autoexplicativo: devuelve el tiempo que el timer correrá antes de restablecestse. enabled (habilitado) es también un campo bastante obvio, y muy útil para activar y detener los timers. El campo loop especifica si el timer se ejecutará continuamente o si simplemente lo hará una vez. Si está a true, se generará un evento cada cycleInterval. De otra manera sólo se generará uno, después del cycleInterval. Los campos startTime y stopTime poseen valores SFTime que especifican cuándo comienza a contar el cronómetro y cuándo se detiene, igual que para el nodo Sound.

Ahora, los eventos. Son partes muy importantes del TimeSensor. El primero es el cycleTime. Este evento se envía cada vez el cronómetro alcanza el cycleInterval, tanto si se repite con el loop como si no. El valor enviado es el tiempo actual. Así, si tuvieras un TimeSensor repitiendose continuamente con un cycleInterval de 1 segundo, el evento del cycleTime se enviaría todos los segundos con un valor del tiempo actual (qué aumentaría en 1 cada vez). Esto es útil para eventos regulares y y señales intermitentes. Para manejar animaciones continuadas, como las producidas con nodos Interpoladores, necesitamos un riego continuo de señales. Esto se logra mediante el eventOut del fraction_changed. Esto genera eventos tan rápido como puede (aunque no hay ninguna garantía sobre la regularidad de esos eventos) y devuelve un valor SFFloat que es el fragmento del cycleInterval que está actualmente completo. Por ejemplo, si tuvieras un cycleInterval de 10 segundos, esto es lo que un fraction_changed devuelve en un bucle TimeSensor.
{short description of image}

Piensa, sin embargo, que no hay garantía de que un evento se generará en este momento particular, sólo de que se generarán eventos en general. Este evento es muy útil para interpoladores, que emplean claves para enviar valores que interpolan en distintos nodos, unido a fracciones de tiempo particulares. Todo esto será cubierto más adelante, de todos modos, o sea que no te preocupes en entenderlo de momento. El evento time se genera al mismo tiempo que fraction_changed, y contiene un valor que corresponde al tiempo absoluto del evento. isActive se genera siempre que el cronómetro empiece y se detenga. El valor del SFBool es TRUE o FALSE, dependiendo si el cronómetro ha comenzado o ha detenido su funcionamiento.

Este ejemplo( y su código) son muy simples, muestra el evento cycleTime de un TimeSensor que se repite dirigido (route) al exposedField del startTime del nodo AudioClip. Este mundo se compone sólo de un texto y el sonido que se activa cada 2 segundos. Es una manera muy obvia y sencilla de realizar el ejemplo, pero transmite el mecanismo, ¿no?. Aprenderás más sobre como usar el fraction_changed cuando lleguemos a los interpoladores.

VisibilitySensor

El próximo tipo de sensor es el VisibilitySensor. Es más un sensor para la interacción; en él se define una caja invisible que envía eventos cuando entra y sale del campo de visión del usuario. Si la caja entra el campo de visión, el evento isActive envía un valor de TRUE, y el evento enterTime el momento de entrada. Si sale, el evento isActive envía un valor de FALSE. Los campos exitTimecenter y size definen el tamaño de la caja. La definición completa se muestra debajo:

VisibilitySensor {
   exposedField   SFVec3f     center            0 0 0
   exposedField   SFBool      enabled           TRUE
   exposedField   SFVec3f     size              0 0 0
   eventOut       SFTime      enterTime
   eventOut       SFTime      exitTime
   eventOut       SFBool      isActive
}

VisibilitySensor será muy útil para optimizar tus escenas. Tener un número grande de animaciones que corren al mismo tiempo puede ser un trabajo muy duro para la CPU, y reducirá horriblemente la velocidad de tus mundos. Usando VisibilitySensor para detener animaciones que están fuera del campo de visión de los usuarios, podrás acelerar las animaciones de tus mundos tranquilamente hasta el límite que consideres conveniente. Esto puede hacerse sencillamente enviando el evento isActive al campo enabled del TimeSensor apropiado.

Ahora, echa un vistazo a este ejemplo y a su código. Consiste en una caja, con un VisibilitySensor en la misma posición. Cuando miras hacia la caja, puedes oir el sonido. Si miras a otra parte, mientras la caja esté fuera del borde de la pantalla, el no se oirá el sonido. Y volverá a sonar cuando tengas de nuevo la caja a la vista. Esto se hace de una manera muy simple. El sonido es un AudioClip en un loop, y el enterTime y el exitTime del VisibilitySensor se envia al startTime y stopTime del AudioClip.

ProximitySensor

Un ProximitySensor es muy similar a un VisibilitySensor, sólo que genera eventos isActive cuando el usuario entra o sale de la caja definida por el nodo. Los eventos enterTime y exitTime funcionan como antes, como el evento isActive. Mientras el usuario esté dentro del ProximitySensor se lanzaran eventos, siempre que su posición u orientación relativas al ProximitySensor cambien, a través de los eventOut position_changed y orientation_changed. Éstos contendrán el valor de la nueva posición u orientación del avatar, cualquiera que sea. Recuerda, éstos están en el sistema de la coordenada local del ProximitySensor, que será relativo al centro del sensor.

ProximitySensor {
   exposedField   SFVec3f     center            0 0 0
   exposedField   SFVec3f     size              0 0 0
   exposedField   SFBool      enabled           TRUE
   eventOut       SFBool      isActive
   eventOut       SFVec3f     position_changed
   eventOut       SFRotation  orientation_changed
   eventOut       SFTime      enterTime
   eventOut       SFTime      exitTime
}

Este mundo de ejemplo (con el código) muestra un ProximitySensor en acción. Si el usuario se acerca a una cierta distancia de la caja, entra en el ProximitySensor y el sonido comienza. Cuando sale, se detiene. Este ejemplo usa las mismas asignaciones de ruta que en el ejemplo anterior, dirigiendo el enterTime y exitTime al startTime y stopTime del AudioClip.

Collision

Este nodo quizás es ligeramente distinto de los que ya hemos visto. Se usa para detectar colisiones en las escenas, aunque también se puede usar como un sensor de colisiones para las animaciones. Básicamente, es un nodo de agrupación que puedes usar para habilitar o deshabilitar la detección de colisiones entre el usuario y los children. Si su campo collide tiene el valor TRUE, la colisión será detectada, y el objeto parecerá sólido. Si tiene el valor FALSE, no se comprobarán las colisiones, y el usuario podrá atravesar directamente el objeto en cuestión. También puedes especificar un campo proxy, que es un campo que puede usarse para la detección de clisiones EN VEZ DE geometry en el campo children. Tiene los campos normales de cualquier nodo de agrupación, addChildren, removeChildren, bboxCenter, y bboxSize. De momento no te preocupes por ellos. Luego los veremos con calma. La razón de incluir esto aquí se encuentra en el eventOut collideTime. Puede usarse para activar eventos cuando el usuario colisiona con la geometria. Es un eventOut de SFTime, y se genera siempre que el usuario colisione con la geometría apropiada (niños o proxy), con el valor del tiempo en que la colisión ocurrió.

Collision {
   eventIn        MFNode      addChildren
   eventIn        MFNode      removeChildren
   exposedField   MFNode      children          []
   exposedField   SFBool      collide           TRUE
   field          SFVec3f     bboxCenter        0 0 0
   field          SFVec3f     bboxSize          -1 -1 -1
   field          SFNode      proxy             NULL
   eventOut       SFTime      collideTime
}

El nodo Collision puede ser muy útil para acelerar la velocidad de representación. Si por ejemplo tienes un objeto con una geometría muy compleja, puedes sustituir la colisión con ese objeto por la colisión con una simple caja asignándosela, simplemente, al campo proxy. Esto reducirá el realismo ligeramente, pero aumentará la velocidad hasta extremos impensables. Puedes también utilizar el campo proxy para poner límites a ciertas partes del mundo, por ejemplo, colocando una pared invisible que el usuario no sea capaz de traspasar. Esto hará tus escenas mucho más sencillas de explorar y usar.

Este ejemplo y su código demuestran el uso del nodo Collision como sensor, pero también cómo la geometría del proxy y el campo collision afectarán a tus escenas. La esfera cian de la izquierda tiene el campo collision puesto a FALSE. La esfera magenta lo tiene a TRUE y su collideTime está dirigido al startTime del AudioClip de manera que suene una vez al detectar la colision. La esfera amarilla de la derecha tiene en su proxy una caja de 4 metros. El collideTime de este nodo está dirigido a un AudioClip diferente. Fijate en que no puedes aproximarte a la esfera amarilla a tanta distancia como a la magenta antes e que se oiga el sonido, y que ya no puedes aproximarte más.

Arañémoslos, Jim

Bueno, esto fue todo para los sensores medioambientales por el momento. En el próximo capítulo cubriremos los sensores de tipo "point device", con los que podremos tomar y controlar datos por parte del usuario.