Objetos Voladores No Identificados

En este capítulo, vamos a cubrir aspectos más avanzados de ECMAScript. Ya conoces los tipos de datos simples, el number, el boolean, y todos los demás. Ahora vamos a introducir objetos, que simplemente son colecciones de variables que puedes usar como si fueran variables sencillas. Imaginate las variables normales como pedazos de papel sobre los que puedes escribir información. Pudes, igualmente, imaginar los objetos como cajas en las que puedes guardar un montón de papeles como estos. Así puedes manejar la caja como si fuese un sólo papel, pero manejando de una sola vez una gran cantidad de información, ya que dentro de esa caja hay muchos papeles. Así puedes guardar varias variables relaccionadas como un solo objeto. Por ejemplo, si quisieras guardar un vector, en vez de tener tres variables numéricas independientes X, Y y Z, crearías el objeto Vector, dentro del cual estarían esas tres variables. El vector es ahora un objeto, y es mucho más fácil de manejar en tu programa. ¿Lo ves?

Hay varias cosas que puedes guardar en tus objetos, y de momento vamos a empezar mirando un cierto tipo de estos pedazos de papel: las propiedades.

Las propiedades

Una propiedad es simplemente una variable que vive dentro del objeto, es decir, es equivalente a uno de los pedazos de papel que puedes guardar dentro de la caja . Si miObjeto es un objeto y x es una de sus propiedades, para usar la variable x dentro de miObjeto tendrías que escribir:

miObjeto.x

Puedes emplear esto para cálculos, asignaciones, o lo que sea. Por ejemplo:

miObjeto.x = 3;
print(miObjeto.x);

e imprime el valor 3. Todo bastante claro hasta ahora, ¿bien?

Los métodos

Ahora, las propiedades no son lo único que puedes tener dentro de tus objetos. También pueden contener métodos. Éstos son exactamente iguales que las funciones de ECMAScript normales, excepto por el motivo de que sólo existen como una parte de un objeto. No puedes usar una función propia de un objeto si no tienes una instancia de ese objeto (es decir, si ese objeto no existe en tu programa). Puedes imaginar los métodos como duendes que se sientan al borde de esa caja, si lo prefieres. Cada duende tiene una función particular. Uno de ellos podría borrar un número de un pedazo de papel de la caja y escribir en su lugar una cadena de caracteres, o cualquier cosa. Lo principal es que puedes decirles que trabajen para ti. Desaparecen por un segundo dentro de la caja (buscando el papel) y regresan con los resultados. Puedes pasar parámetros a estos métodos de la misma manera que con las funciones normales.

Ahora, ¿a que no adivinas como usar estos métodos? Sip, lo has cogido. Exactamente igual que cuando usas una propiedad. Si miObjeto tiene un método llamado multiplicalo(num) que multiplica x por num, podemos hacer lo siguiente:

miObjeto.x = 3;
resultado = miObjeto.multiplicalo(4);
print(resultado);

Esto nos dará el valor 12. No olvides que siempre puedes tomar un ojeto completo como si fuera una variable normal y corriente, así que podríamos hacer algo como lo siguiente, asumiendo que tenemos dos objetos y el código apropiado dentro del método multiplicalo ():

miObjeto.x = 3;
miOtroObjeto.x = 4;
resultado = miObjeto.multiplicalo(miOtroObjeto);

Esto tomaría el objeto entero como un parámetro y recibiría la información apropiada dentro del método. Y de nuevo, imprimiría 12.

Creando objetos

Vale, pero ¿de dónde vienen estos objetos? Bien, se crean objetos usando un tipo especial de función llamada constructor. Esto se usa para crear un objeto de un cierto tipo definido por ti. Continuemos con nuestro objeto Vector. Si queremos crear un nuevo tipo del objeto llamado Vector, nosotros necesitamos emplear un constructor. Esto resultaría así:

function Vector(x, y, z) {
   this.x = x;
   this.y = y;
   this.z = z;
}

Éste constructor toma algunos parámetros, pero puedes hacerlo sin ellos de esta manera:

function Vector() {
   this.x = 0;
   this.y = 0;
   this.z = 0;
}

Echemos un vistazo más profundo al constructor. Lo primero de lo que debemos darnos cuenta es que el nombre de la función es el mismo que el del objeto que crea. Así es como el navegador-intérprete sabe qué función emplear. La segunda es la palabra this empleada dentro de la función. this (o, literalmente, esto) es una palabra muy importante, en tanto que se refiere siempre al objeto actual. También aviso que el constructor no devuelve a nada. Como estamos modificando las propiedades del objeto, no necesitamos devolver ningun valor.

¿Cómo instanciamos finalmente el objeto dentro del programa? Bien, usaremos otra palabra clave: new, de esta manera:

miVector1 = new Vector();
miVector2 = new Vector(1,2,3);

la palabra reservada new dice al programa que estamos creando un nuevo objeto, para lo que debe llamar a la función constructora que ya hemos definido antes. Si te fijas en el código anterior, te darás cuenta de que miVector1 tendrá sus variables inicializadas a cero, por que no se le pasa ningún parámetro, mientras que miVector2 tendrá los valores 1 para X, 2 para Y y 3 para Z, dado que se le pasan en este orden mediante un parámetro.

Una vez que tienes un objeto, puedes agregarle nuevas propiedades simplemente asignandole valores adecuados. Si ves la necesidad de que miVector2 tenga un nombre, por ejemplo, puedes darle uno escribiendo:

miVector2.name = "bob";

Ahora intentemos agregar un método a nuestro objeto. Esto es bastnte sencillo. Pongamos que queremos escribir un método que multiplique el vector, esto es, un escalar. Escribiríamos el código de esta manera:

function multiplicaVector(scalar) {
   this.x *= scalar;
   this.y *= scalar;
   this.z *= scalar;
}

Ésta es la función que hará el trabajo. Multiplicará cada propiedad del objeto por el valor del scalar pasado como parámetro. ¿Ahora, cómo lo añadimos al objeto como un método? Bien, veamos al constructor de nuevo...

function Vector() {
   this.x = 0;
   this.y = 0;
   this.z = 0;
   this.multiplicalo = multiplicaVector;
}

Hemos agregado el método multiplicalo al objeto, que lleva directamente a la función multiplicaVector. Así, si escribimos miVector2.multiplicalo(2), se llamará inmediatamente a la función multiplicaVector() y hará todo el trabajo, alterando los valores en el objeto miVector2.

Echa una ojeada a este ejemplo y al código. Éste es un ejemplo de un script que usa un constructor para crear un nuevo objeto. El resultado es una textura cubriendo una caja que puedes extender tanto como quieras arrastrando la pelota roja sobre la imagen. La esfera roja siempre corresponderá a la esquina superior derecha de la imagen.

Como puedes ver, cuando la posición de la esfera cambia, se llama a la función set_translation (). En primer lugar, ésta verifica dos de las propiedades del objeto que se le ha pasado (SFVec3f) para asegurarse de que nunca van a dar 0 (lo cual rompería el TextureTransform). Se crea un nuevo objeto SFVec2f, usando los valores calculados por las propiedades del objeto pasadas en la función. Se pasa el resultado al TextureTransform, donde se actualiza el mapeado de la textura. En este ejemplo usamos un script que realiza un caso elemental de conversión de tipos, en este caso, una entrada de datos de una posición tridimensional desde un PlaneSensor para controlar la escala o proporción 2D de una textura. ¿No comienzas a ver las enormes posibilidades que ofrece usar scripts?

Los objetos SFVec3f y SFVec2f usados en este script son objetos de VRML/ECMAScript que proporciona cualquier navegador de VRML. Los veremos en un minuto, después de mencionar brevemente los Arrays (o tablas).

Los Arrays

No quiero emplear demasiado tiempo en esto. Cuando acabes con este tutorial, será mejor que encuentres una referencia apropiada de ECMAscript/JavaScript en otro lugar, de manera que trataré de cepillármelas en un segundito. Los Arrays son una colección de variables del mismo tipo. Se referencia a ellos por su nombre, seguido del índice de la variable pasado entre corchetes (el cual comienza desde cero). Así, si tuvieras un Array de números, miArray[0] contendría el primer número, miArray[1] el segundo, y así sucesivamente. El motivo de plantear esto ahora es por que algunos de los tipos de VRML son básicamente Arrays, por lo que pensé que debía decirte qué son y cómo funcionan, sin embargo creo que debemos dejarlo aquí. Si más tarde los necesitamos, los explicaré debidamente, pero creo que es mejor que lo leas en un buen libro o en un tutorial dedicado en exclusiva a ello. De momento, seguiremos viendo material prácrico sobre cómo VRML97 usa los objetos ECMAScript.

Objetos VRML

Si quieres crear scripts en VRML que sólo hagan un par de cosas, vas a necesitar usar los objetos VRML de ECMAScript. Éstos son objetos de ECMAScript que provee el propio navegador (aunque no en todos los navegadores tienen las mismas funcionalidades), qué tienen un standard compuesto por una selección de propiedades, constructores y funciones que puedes usar. Por ejemplo, el objeto SFVec3f provee de una serie de operaciones geométricas que puedes emplear sin tener que definirlas tú mismo con anterioridad.

La mayoría de los tipos de datos en VRML tienen un equivalente en ECMAScript, por lo que un valor SFVec3f en VRML es equivalente a un objeto SFVec3f en ECMAScript. Sin embargo, los tipos simples se trazan directamente a los tipos ECMAScript:

Todos los otros tipos de datos VRML llevan a objetos ECMAScript, como SFVec3f, SFColor, MFNode y así sucesivamente. Además de los tipos standard, se proporcionan otros dos objetos más a través del navegador. Éstos son el objeto Browser, que puede usarse para recibir información del navegador y el objeto VrmlMatrix que es una matriz 4x4 útil para la geometría 3D seria. Si necesitas usar éste ultimo, probablemente tendrás ya conocimientos avanzados de geometría 3D, por lo que supongo que sabrás usarlo.

Los objetos de tipo SFxxx son todos diferentes, y tienen sus propias funciones y comportamientos apropiados para sus tipos. Dado que son todos diferentes, no voy a cubrirlos aquí. Para más información sobre lo que puedes hacer con cada tipo, Dirígete a la sección adecuada del Anexo C, esto es, la referencia de ECMAScript. Ahí se describe detalladamente cómo construir objetos y cómo darles empleo.

Mencionaré sin embargo los de tipo MFxxx, dado que todos ellos son muy similares. Básicamente son Arrays de sus correspondientes tipos SF, con alguna función añadida. Todos tienen la propiedad length que determina el número de elementos que están en ese momento en la lista. Tienen también la función, toString () que convierte un valor de un determinado tipo (por ejemplo, numérico) a un valor de tipo cadena de texto. Y Tienen un constructor que toma una lista de objetos SF para introducirlos en un nuevo objeto MF. Este puede estar vacía si lo deseas para comenzar. Puedes acceder los objetos SF dentro de un objeto MF de la misma manera en que accedes a cualquier Array, usando el índice entre los corchetes. Así, vectors[0] resulta el primer elemento del objeto MFVec3f vectors, y vectors[7] el octavo. Si quieres introducir un objeto SF en un objeto MF, deberas usar la misma notación. Por ejemplo, esto:

var vectores = new MFVec3f();
vectores[5] = new SFVec3f(0,1,0);

asigna el nuevo SFVec3f(0,1,0) al sexto elemento de los vectores. Si colocas un objeto en un índice superior al último elemento del objeto MFxxx, este se expandirá hasta el índice que le indiques. Así que puedes crear una serie vacía al comienzo del programa e ir añadiendo elementos donde quieras.

Este ejemplo muestra cómo puedes usar muchas funciones propias del VRML97 para realizar grandes efectos. Muestra como se comporta el producto cruzado de dos vectores cuando los mueves. Imagínate lo útil que resultaría en un curso on-line de matemáticas, o algo por el estilo. ¿Sin embargo, qué tenemos realmente aquí? Para empezar, hay dos cilindros (rojo y verde) qué representan los dos vectores de la unidad. Están unidos a SphereSensors, para que puedan moverse con la acción del ratón. El tercero (azul) es el cilindro que representa el producto cruzado de los dos vectores, es decir el verde multiplixado por el rojo. El producto es un vector ortogonal a ambos vectores, y su longitud depende del ángulo entre ellos. No es posible mover el cilindro azul, porque su posicion se genera mediante un script basado en las posiciones de los otros dos cilindros. Juguetea durante algún tiempo con el ejemplo, hasta que entiendas lo que hace, y entonces échale un ojo al código ver cómo hemos conseguido el efecto.

Para generar el producto cruzado necesitamos dos vectores. Como lo que obtenemos a partir de un SphereSensor son rotaciones, lo primero que tenemos que hacer es convertir este resultado en vectores. Esto se hace en los dos manejadores de eventos creando un vector de la unidad y rotándolo por el nuevo valor de la rotación del eventIn que usa la función multVec () del objeto de SFRotation. El resultado se guarda en un objeto de SFVec3f para calcular el producto cruzado. Así que, cada vez que se recibe un evento(o cuando el mundo está cargado), nosotros recalculamos el producto cruzado y lo aplicamos. Esto se hace en la función del calc_cross_product. La primera línea hace el trabajo duro de calcular el producto. El resto lo convierte de forma que podemos usarlo. Así, primero calculamos el producto de vector1 y vector2, y al obtener el resultado, el crossVec. Sin embargo, para introducir este vector en nuestro mundo VRML, necesitamos convertirlo en una rotación sobre el origen (para conseguir la orientación actual) y una longitud (para usar como un factor de la balanza). Éstos pueden aplicarse entonces al cilindro azul en nuestro mundo con un resultado perfecto.

OK, tratemos primero con el factor de la escala. Podemos conseguir la longitud usando la simplemente la propiedad length() de la función SFVec3f. Sin embargo, no podemos tener un factor de escala de 0, por lo que necesitamos asegurarnos de que nunca llega allí con una sentencia if. Ahora queremos escalar por esta cantidad en la dirección del eje Y nuestro objeto, para lo que creamos un nuevo SFVec3f para usar como un eventOut de scale con 1s en el X y Z, y nuestro nuevo factor de la escala en el Y. Ahora, necesitamos poner la rotación correctamente. Lo que haremos es hacer uso de uno de los constructores del tipo de SFRotation. Esto te permite crear una nueva rotación que usa dos vectores, uno desde, y uno hacia. La rotación creada será la que rota del vector al vector. Creamos un vector en la dirección de Y (la posición predefinida para nuestro cilindro) y crea una rotación que usa para el output. Inteligente, ¿no?

Puedes ver en este ejemplo que hay todas clase de funciones útiles disponibles en los objetos de VRML97 normales, y yo no puedo cubrirlo todo aquí. ¡Ciertamente merece la pena que te sientes y leas las especificaciones del lenguaje antes de que hagas cualquier script, ya que podrías saltarte algo que te haría la vida más fácil diez veces!

El Ángel caído

Bien, ahora estás preparado para el rock & roll en lo que respecta al ECMAscript. Si necesitas una referencia sobre los objetosVRML97 y ECMAScript, hay unas cuantas páginas útiles disponibles en la página de las especificaciones. Tiene un resumen rápido de todas las propiedades, constructores y funciones para todos los Objetos de VRML97. Si estás programando scripts en ECMAScript, es algo bueno que tener al lado. También te sugeriría que le eches un ojo a algunas referencias de javascript, y aprendas un poco más sobre otros objetos, como Date, Math, Array, y todo eso.

Bien, sé que ha sido un capítulo muy árido, quizá con excesivo volumen de contenido, pero no es sencillo enseñar a programar en cuatro capítulos. ¡Sin embargo, has llegado lejos, y creo que estás suficientemente familiarizado con la materia como para apañartelas! Hemos cubierto todos los conceptos que necesitas para hacer scripts, y de ahora en adelante todo será mas sencillo. Hasta que lleguemos a Java, claro...

En el próximo capítulo, vamos a cubrir el objeto Browser. Éste es un objeto especial que ofrece toda clase de información, y también le permite programar sobre tu mundo a un nivel muy bajo. ¡Será divertido!

El Logotipo del Pingüino Linux © Larry Ewing