Curso de Fundamentos de VRML


Para crear un mundo de realidad virtual se utiliza un fichero de texto, creado con un procesador cualquiera de textos que se debe guardar con la extensión .wrl

Este fichero constituye un documento VRML, que será ejecutado por el visualizador, de manera completamente análoga a los documentos HTML, que son ficheros de texto con la extensión .html y que son ejecutados por los navegadores para visualizar las páginas del Web.

También se pueden crear estos documentos utilizando ciertos progamas editores de VRML, que los generan automáticamente, sin necesidad de saber programar en este lenguaje. Se hablará sobre estos programas más adelante.

Estructura de los documentos VRML

En líneas generales, un documento VRML contiene los siguientes elementos:

Línea de cabecera

Todo documento VRML debe comenzar necesariamente con la siguiente línea:


   #VRML V2.0 utf8

Con esta línea inicial se hace la declaración de que el estándar empleado es el VRML 2.0. El identificativo utf8 sirve para permitir el uso de caracteres internacionales.

No se debe dejar ningén espacio en blanco antes del comienzo de la línea (antes del símbolo #), pues en caso contrario el visualizador no la reconocería, y daría error al ejecutar el documento VRML.

Comentarios al código

En todos los lenguajes se utilizan comentarios al código, no visibles en la pantalla, que son muy étiles como recordatorio de lo que se ha hecho y que facilitan los cambios futuros.

En este lenguaje los comentarios se escriben en una sola línea que comienza con el símbolo #. Ejemplo:


   # Este es un comentario al código

Nodos

Los nodos son bloques de información que definen las características de un objeto concreto (su forma, su apariencia, etc.), o la relación entre distintos objetos. Sirven también para crear sonidos, el panorama de fondo, enlaces a otros mundos o a páginas Web, etc.

Es decir, son las unidades básicas que forman el mundo virtual, y pueden ser afinadas hasta el detalle deseado.

Los nodos son los bloques de información básicos con los que se construye un mundo virtual. Vamos a ver la sintaxis de los nodos partiendo de los más sencillos, los nodos de geometría primitiva, o nodos primitivos, que son:

Con estas figuras geométricas básicas, agrupándolas y modificándolas convenientemente se pueden construir formas más complicadas.

Para formas realmente complejas, hay otros procedimientos para crear objetos, partiendo de puntos, líneas o caras.

Box

La estructura más general de un nodo es:

 
   Box { ... }

Con respecto a los nombres, y en general cualquier otro comando usado en este lenguaje, se debe tener muy en cuenta que son sensibles a las mayésculas y minésculas. Es decir, se debe escribir Box y no box, o fontStyle y no fontstyle, etc.

Sustituyendo ahora los puntos suspensivos por el campo que define este nodo, que es size (tamaño) con sus valores correspondientes (las dimensiones), queda el nodo completado de esta forma: 

 
   Box {size 2 0.5 3}

Los némeros representan respectivamente la anchura, la altura y el fondo. Son dimensiones abstractas, pero es conveniente, aunque no obligatorio, suponer que son metros. El nodo, por tanto, define un paralelepído (una caja) de 2 m. de ancho, 0.5 m de alto y 3 m de fondo.

Se ha escrito el nodo en una sóla línea, debido a su sencillez y a que sólo tiene un campo, pero se podría haber escrito de esta otra forma:

 
   Box {
        size 2 0.5 3
   }

Cone

Veamos un ejemplo de este nodo, que define la geometría de un cono:

 
   Cone {
         height 3
         bottomRadius 0.75
   }

Como se puede observar, tiene dos campos: height (altura), al que se le ha puesto el valor de 3, y bottomRadius (radio de la base) el de 0.75. Con estos dos valores queda perfectamente definido el cono.

Cylinder

Ejemplo del nodo que define un cilindro:

 
   Cylinder {
          height 2
          radius 1.5
   }

Tiene dos campos: height (altura) y radius (radio) 

Sphere

Ejemplo del nodo que define una esfera:

 
   Sphere {
          radius 1
   }

Tiene un solo campo, radius (radio) pues es suficiente para definir con él una esfera.

Utilización de estos nodos

Estos cuatro nodos de geometría primitiva, que acabamos de ver, no se pueden utilizar directamente ellos solos, sino que son parte integrante de otro nodo más general, el nodo Shape (forma), que veremos en el capítulo siguiente.

Es decir, si queremos crear el cono del ejemplo, sería incorrecto crear este documento VRML:

 
   #VRML V2.0 utf8

   #Este documento es incorrecto

   Cone {
         height 3
         bottomRadius 0.75
   }

Suponiendo que lo guardáramos con el nombre de cono.wrl, por ejemplo, y tratáramos de ejecutarlo en un visualizador, nos daría error o no se vería nada.

El motivo de esto es que estos nodos definen énicamente la geometría de estos cuerpos, pero no dan ninguna indicación de cuál deberá ser su apariencia, es decir, su color, textura, iluminación, etc.

En cambio el nodo Shape se encarga de ambas cosas, ya que tiene dos campos: la apariencia y la geometría, en donde utiliza precisamente estos nodos que acabamos de ver. Es decir, estos nodos irán incrustados en el nodo Shape.

Sintaxis de los nodos

Con los ejemplos anteriores podemos entender más fácilmente el concepto y la sintaxis (reglas para la correcta escritura) de los nodos, desde un punto de vista general.

 
   Nombre {
	campo1 x y z
	campo2 x 
	...   
   }

para crear un objeto visible se debe utilizar el nodo Shape.

Este nodo tiene dos campos: appearance y geometry. Con el primero se controla la apariencia del objeto (color, textura, iluminación, etc.) y con el segundo su geometría.

Por tanto, la estructura general del nodo es:

 
   Shape {
	appearance ...
	geometry ...
   }

Los puntos suspensivos representan los valores de cada campo.

El primer campo (appearance) es opcional, y se puede prescindir de él, lo que vamos a hacer de momento por motivo de sencillez en la explicación. O sea, que el nodo quedaría de esta manera:

 
   Shape {
	geometry ...
   }

Pero el valor del campo geometry (los puntos suspensivos) es precisamente uno de los cuatro nodos primitivos que hemos visto en el capítulo anterior (Box, Cone, Cylinder y Sphere).

Escojamos el primero de ellos (Box), del ejemplo del capítulo anterior, y pongámoslo como valor del campo geometry:

 
   Shape {
	geometry Box {size 2 0.5 3}
   }

Con esto hemos definido un objeto visible (aunque todavía sin ningén atributo de apariencia definido), que tiene la geometría de una caja de medidas 2x0.5x3

Hemos puesto el nodo Box en una sola línea, pero conviene que lo pongamos desarrollado en varias líneas, para irnos acostumbrando, pues es muy importante que los símbolos { y } estén colocados en el orden que les corresponde, y no olvidarse de ninguno de ellos:

 
   Shape {
	geometry Box {
		size 2 0.5 3
	}
   }

Ahora ya estamos en condiciones de crear el primer documento VRML con el que se podrá ver el objeto con el visualizador, aunque todavía en unas condiciones bastante imperfectas, ya que todavía no tiene definido ningén atributo de apariencia.

Para crear el documento VRML sólo falta ponerle la línea de cabecera, y algén comentario si queremos:

 
   #VRML V2.0 utf8
   #Primer documento VRML 
   #Caja sin apariencia definida

   Shape {
	geometry Box {
		size 2 0.5 3
	}
   }

Se guarda con el nombre que se quiera, con la extensión .wrl

En este caso, va a ser cap05_caja.wrl Para verlo en el visualizador, basta con abrir este fichero en el navegador (con el mené Archivo/Abrir).

A la derecha se puede ver el resultado. 

Como se puede observar en la imagen, las caras del objeto son de color blanco y no se distinguen unas de otras, pudiéndose apreciar sólamente el contorno del objeto.

Ahora vamos a dar el siguiente paso: suministrar al objeto unas cualidades de apariencia (aunque de momento sólo van a ser las que existen por defecto) 

Poniendo la apariencia por defecto

Como se ha visto al comienzo del capítulo, el nodo Shape tiene también el campo appearance, del que habíamos prescindido de momento:

 
	appearance ...

Tenemos que poner un valor (los puntos suspensivos) al campo appearance. No va a ser un némero (o un conjunto de némeros), sino un nodo llamado Appearance.

Obsérvese la A mayéscula de Appearance, que indica que se trata de un nodo, mientras que en el caso de appearance la a minéscula indica que es un campo (del nodo Shape). 

Veamos, primero por separado, la estructura de este nuevo nodo Appearance:

 
   Appearance {
	material Material {   }
   }

Vemos que el nodo Appearance tiene, a su vez, un campo llamado material, cuyo valor es otro nodo, llamado Material, en el que se ha dejado en blanco su contenido (no hay nada entre los símbolos { y } del nodo Material), lo que hace que estén definidas las características por defecto de este nodo.

Encajando todas las piezas

Todo lo anterior puede parecer a primera vista como algo muy complicado. Pero no lo es, si consideramos que, en realidad, los nodos son como módulos de información que encajan unos en otros, formando una red (de ahí su nombre).

Es decir, algo que puede ser muy complejo, tal como un mundo virtual, está compuesto de pequeñas y simples unidades de información (los nodos) que actéan como las piezas de un mecano.

Centrándonos en el caso concreto de la caja, vamos a ensamblar las distintas piezas. Primero, coloquemos el nodo Appearance como el valor del campo appearance:

 
	appearance Appearance {
		material Material {   }
	}

Ahora ya podemos colocar este campo dentro del nodo Shape, en el documento VRML, con algunos comentarios que faciliten su comprensión: 

 
   #VRML V2.0 utf8
   #Caja con la apariencia por defecto

   Shape {
	
	#Campo appearance:
	appearance Appearance {
		material Material {   }
	}

	#Campo geometry:
	geometry Box {
		size 2 0.5 3
	}

   }

Se guarda como cap05_caja1.wrl

A la derecha se puede ver el resultado.

Como se puede apreciar en la figura (y aén más manipulándola con el visualizador), la diferencia es notable. Ahora se distinguen perfectamente las caras de la caja, debido a que tienen una iluminación y textura adecuadas.

Siguiendo un proceso similar para el nodo primitivo Cone, visto en el capítulo anterior, el documento VRML para visualizarlo (también con la apariencia por defecto), sería el siguiente:

 

 
   #VRML V2.0 utf8
   #Cono con la apariencia por defecto

   Shape {
	
	#Campo appearance:
	appearance Appearance {
		material Material {   }
	}

	#Campo geometry:
	geometry Cone {
		height 3
		bottomRadius 0.75
 	  }	

   }

Como se puede observar, el campo appearance es idéntico al caso anterior, sólo cambia el valor del campo geometry, que es el nodo Cone.

En los mundos virtuales será a menudo necesario utilizar textos para guiar al visitante (en forma de carteles, comentarios, paneles de control, etc.) Para ello existe un nodo específico, el nodo Text que, al igual que los nodos primitivos, irá incrustado en el nodo Shape, como se verá más adelante.

Los textos son siempre planos y no tienen espesor. Se puede determinar el tipo de fuente, su estilo y tamaño, etc. 

Estructura del nodo Text

Su estructura general es la siguiente:

 
   Text {
	string ...    
	fontStyle ...
   }

Tiene, por tanto, dos campos:

El campo fontStyle es opcional. Si se omite, el texto tendrá el estilo de fuente por defecto.

Veamos cada campo por separado:

Valor del campo string (el texto)

Se pone de esta manera:

 
   string ["Esta es la primera fila de texto",
           "esta es la segunda fila",
           "etc."]

Como se puede ver, cada fila de texto va entre comillas, con una coma al final de cada línea, y todo ello englobado entre corchetes: [ y ]  

Nodo FontStyle

Veamos su estructura con un ejemplo:

 
   FontStyle {
	family "SERIF",
	style "BOLD",
	size 1
	spacing 1
   }

En este ejemplo, el nodo FontStyle tiene los siguientes campos:

Existen más campos que los que aparecen en este ejemplo, con los que se puede afinar aén más la disposición del texto:

Nodo Text completo

Poniendo ahora el nodo texto completo, con sus dos campos, queda de esta forma:

 
   Text {

	string ["Esta es la primera fila de texto",
		"esta es la segunda fila",
		"etc."]

	fontStyle FontStyle {
		family "SERIF",
		style "BOLD",
		size 1
		spacing 1
	}

   }

Integrando el nodo Text en el nodo Shape

El nodo Text se incrusta en el nodo Shape, de la misma manera que se hacía con los nodos primitivos. En el capítulo anterior vimos que el nodo Shape tenía esta estructura:

 
   Shape {
	appearance ...
	geometry ...
   }

Vimos que los nodos primitivos se ponían como valor del campo geometry. Es lo mismo que vamos a hacer ahora:

 
   Shape {
	appearance ...
	geometry Text { ... }
   }

Aquí está de una manera resumida. Para desarrollarlo completamente, hay que poner como campo de appearance la apariencia por defecto, y lógicamente, el nodo Text completo.

 

Como se puede ver en la imagen, el texto se puede manipular como cualquier otro objeto (girándolo, etc.), ya que, en realidad, es un elemento plano de espesor cero.

Las letras tienen el color por defecto, ya que se ha utilizado la apariencia por defecto. Pero se puede hacer que tengan el color que se desee, segén veremos más adelante.

 


Agrupación de nodos

Hasta ahora hemos visto los objetos aisladamente. Veamos ahora cómo podemos agrupar un conjunto de ellos para conseguir formas más complejas. En los próximos tres capítulos se van a ver diferentes nodos que sirven para agrupar otros nodos:

Nodo Group

Existe el nodo Group que permite tratar a un conjunto de nodos como una entidad énica, pero sin efectuar ninguna transformación en ellos.

Su estructura general es la siguiente:

 
   Group {
	children [ ... ]
   }

Tiene un campo llamado children (en inglés, niños o hijos), cuyo valor (los puntos suspensivos) va entre corchetes [ y ], y que como veremos a continuación, es la lista de los objetos que se quieren agrupar, representados por sus nodos Shape respectivos:

 
   Group {
	children [
		Shape { ... },
		Shape { ... },
		...
	]
   }

Veamos un ejemplo: Vamos a agrupar los ejemplos de una caja y de un cono vistos anteriormente:

 
#VRML V2.0 utf8
#Ejemplo de agrupación de una caja y un cono

Group {
    children [
        #Aquí empieza la caja:
        Shape {
            appearance Appearance {
                material Material {   }
            }
            geometry Box {
                size 2 0.5 3
            }
        },
        #Aquí empieza el cono:
        Shape {
            appearance Appearance {
                material Material {   }
            }
            geometry Cone {
                height 3
                bottomRadius 0.75
            }	
        }
    ]
}

Véase el resultado en la imagen de la derecha. Se puede observar que la caja y el cono están superpuestos. Esto es debido a que los objetos son creados todos en el mismo punto: en el centro del escenario de realidad virtual.

Para colocar un objeto, o grupo de objetos, en otros puntos que no sean el centro, se deberá utilizar otro tipo de nodo, como se verá más adelante.

Consejos prácticos para escribir el código

A lo largo de los capítulos hemos ido viendo la sintaxis de cada nodo y la manera de cómo se incrustan unos nodos dentro de otros, etc., para poder confeccionar los correspondientes documentos VRML.

Si observamos el éltimo ejemplo, parece como si el código se complicara más y más (a pesar de tratarse de un ejemplo relativamente sencillo).

No hay tal complicación, pero sí es muy importante seguir unas cuantas reglas prácticas, pues es absolutamente imprescindible que las cosas estén colocadas en el orden debido, y que no sobre ni falte ninguno de los símobolos de apertura y de cierre { y }, [ y ]

Poniendo nombres propios a los nodos

Podemos observar en el ejemplo que se ha repetido dos veces el nodo Appearance, para dotar de la misma apariencia por defecto a los dos objetos (la caja y el cilindro).

No es tan grave en este caso, pero imaginemos que se quiere dotar de esa misma apariencia, por ejemplo a cinco objetos distintos (como va a ser el caso del ejemplo práctico de este capítulo). Entonces, habría que repetir cinco veces todo el nodo Appearance en cada uno de los cinco objetos, lo cual sería bastante engorroso, además de complicar innecesariamente el código.

Hay una solución prevista para simplificar estas repeticiones, y es que se puede definir un nodo que se piensa repetir en el código, poniéndole un nombre arbitrario.

Supongamos, por ejemplo, que se van a utilizar repetidamente en un escenario unos cilindros exactamente iguales (que podrían ser las columnas de una casa), y que dichos cilindros tienen el siguiente código:

 
Shape {
     appearance Appearance {
          material Material { }
     }
     geometry Cylinder {
          height 2
          radius 0.5
     }
}

(Este es el código de un cilindro con la apariencia por defecto, de 2 unidades de alto y una base de radio 0.5)

Se puede definir, para el ámbito de un documento VRML, que este tipo de cilindro tenga un nombre arbitrario, por ejemplo ColumnaRepetida (o como se quiera, con tal de que comience por mayéscula), de la siguiente manera:

 
DEF ColumnaRepetida Shape {
     appearance Appearance {
          material Material { }
     }
     geometry Cylinder {
          height 2
          radius 0.5
     }
}

Entonces, cada vez que se quiera usar este nodo en otra parte del documento, basta con poner lo siguiente:

 
USE ColumnaRepetida

En el ejemplo anterior de la caja y el cono, lo que está repetido es el nodo Appearance. Vamos a definirlo, en la primera ocasión que se utiliza con el nombre, por ejemplo, PorDefecto y en la segunda vez utilizar sólo el comando USE (se omiten las líneas de comentarios):

 
#VRML V2.0 utf8
#Ejemplo de agrupación de una caja y un cono,
#haciendo uso de los comandos DEF y USE.

Group {
   children [
      Shape {
         appearance DEF PorDefecto Appearance {
            material Material {   }
         }
         geometry Box {
            size 2 0.5 3
         }
      },
      Shape {
         appearance USE PorDefecto
         geometry Cone {
            height 3
            bottomRadius 0.75
         }	
      }
   ]
}

En este caso concreto, la simplificación no ha sido muy grande (sólo un par de líneas menos de código), pero ha servido para ilustrar el concepto.


Como se ha mencionado en el capítulo anterior, por defecto, todos los objetos son creados en el centro del escenario de realidad virtual.

Ahora vamos a ver cómo posicionarlos en otros puntos. Pero para ello, es necesario antes comprender la noción de los sistemas de coordenadas.

Un punto en el espacio está definido por su posición con respecto al centro de coordenadas.En el lenguaje VRML se adopta la convención de que sea X la distancia que ese punto esté desplazado a la derecha o a la izquierda del centro, Y la distancia por encima o por debajo, y Z la distancia hacia delante o hacia atrás. Se puede ver en la imagen de la derecha una representación tridimensional de los ejes de coordenadas. Pulsándola se carga el escenario. (Se ha confeccionado utilizando cilindros muy finos, convenientemente girados para representar los ejes)

Un mundo virtual tiene su sistema de coordenadas situado en el centro. Con el nodo Group, visto en el capítulo anterior, se consigue agrupar un conjunto de objetos, que son creados haciendo coincidir su centro con el centro del sistema de coordenadas, como se puede apreciar en la figura de la derecha. (Se ha superpuesto el conjunto de objetos a los ejes coordenados)

Con el nodo Transform, que vamos a ver a continuación, se determina un nuevo sistema de coordenadas para un grupo de objetos.

Este nuevo sistema de coordenadas sufre unas transformaciones: puede ser trasladado a un punto determinado, puede ser girado un determinado ángulo, y puede tener una escala (tamaño relativo) distinta a la original.

El grupo de objetos especificados en el nodo sufrirán las mismas transformaciones, es decir, serán trasladados, girados y variados de escala. 

Estructura del nodo Transform

Su estructura general es la siguiente:

 
   Transform {
       translation . . .
       rotation    . . .
       scale       . . .
       children  [ . . . ]
   }

Como se puede ver, tiene tres campos con los que se determina su posible traslado, rotación o nueva escala, y un cuarto campo children, en donde se especifican cuáles son los objetos agrupados que sufrirán esas transformaciones.

No tienen por qué estar los tres campos a la vez. Por tanto, para simplificar, vamos a ver cada uno de ellos por separado.

Traslación

Poniendo sólo el campo translation, queda el nodo Transform de esta manera:

 
   Transform {
       translation 20 0 0
       children [ . . . ]
   }

Se ha puesto un valor al campo translation. El orden para el traslado de las coordenadas es X, Y, Z. Por tanto, en este caso, hay una traslación de 20 unidades hacia la derecha (si hubiera sido hacia la izquierda se habría puesto -20.0)

Por supuesto que se podrían haber variado las tres coordenadas, no sólo la X como en este caso.

Con respecto al campo children, se aplica lo dicho anteriormente, para el nodo Group.

Rotación

Poniendo ahora sólo el campo rotation, queda el nodo Transform de esta manera:

 
   Transform {
        rotation 1 0 0 1.57
        children [ ... ]
   }

Tal como se ha puesto el valor del campo rotation quiere decir lo siguiente: rotación de 90 í alrededor del eje X. 

Explicación: Las tres primeras cifras sólo pueden tener el valor 0 ó 1, y representan la rotación alrededor de cada eje, en este orden: X, Y, Z.

Es decir:

La cuarta cifra representa el ángulo girado, expresado en radianes.

Para calcular la correspondencia entre grados y radianes, hay que tener en cuenta que 180í equivalen al némero pi en radianes, es decir a 3.14 radianes. Por tanto, 90í sería la mitad de 3.14, es decir 1.57 radianes.

El sentido del giro es el de un sacacorchos avanzando en la dirección positiva de los ejes.

En la figura de la derecha se puede ver al grupo de objetos, trasladados 20 unidades hacia la derecha y girados 90í alrededor del eje X (pulsar la imagen para cargar el escenario), cuyo código es:

 
   Transform {
        translation 20 0 0
        rotation 1 0 0 1.57
        children [ ... ]
   }

Variación de la escala

Poniendo ahora en el nodo Transform sólo el campo scale, para variar la escala de los objetos, queda de la siguiente forma:

 
   Transform {
        scale 0.5 2 0.5
        children [ ... ]
   }

Los valores del campo scale representan las variaciones de las dimensiones con respecto a los ejes X, Y , Z.

Por tanto, en el ejemplo que se ha puesto, se reducen las dimensiones en la dirección de las X a la mitad (0.5), se duplican en la dirección del eje Y (2), y se reducen a la mitad en la dirección del eje Z (0.5).

Si se hubiera variado la misma cantidad en los tres valores (por ejemplo, 0.5) se habría mantenido las proporciones de la pieza.

Se puede ver en la figura de la derecha la pieza después de sufrir los tres cambios.


Nodo Switch

Este nodo Switch (en inglés, bascular o conmutar) sirve para agrupar otros nodos, pero con una diferencia: sólo será utilizado uno de los hijos agrupados.

Su estructura es la siguiente:

 
    Switch { 
        whichChoice 0 
             choice [...] 
             choice [...] 
             choice [...] 
    }

Como se puede ver, este nodo tiene un campo whichChoice (en inglés, cuál elección) cuyo valor es un némero que indica céal hijo choice es el elegido (0, 1, 2, etc.). Si este valor es -1, entonces se indica que no está elegido ninguno.

áQué utilidad tiene este nodo? Un posible uso de este nodo es el de tener preparadas diferentes versiones del nodo Shape, es decir, diferentes formas de un objeto. Bastará con variar el valor de whichCoice para pasar rápidamente de una forma a otra.

Esta variación se hará utilizando los eventos, que es algo que se verá más adelante. 

Nodo Billboard

Este nodo Billboard (en inglés, cartel) sirve para agrupar otros nodos hijos. Todos los nodos agrupados serán visibles (al contrario del anterior, en el que sólo es visible uno de ellos).

La particularidad de este nodo es que crea un sistema de coordenadas para los nodos agrupados que está siempre de cara al espectador.

Su estructura es la siguiente:

 
   Billboard {
       axisOfRotation 0 1 0
       children [ . . . ]
   }

Tiene un campo, axisOfRotation (eje de rotación), cuyo valor es un grupo de tres cifras que indican cuál es eje alrededor del cual se efectéa automáticamente el giro para permanecer siempre de cara. La convención que se utiliza es la misma que la vista anteriormente para la rotación, excepto que en este caso no hace falta poner un cuarto valor para expresar el ángulo:

Formas complejas

Hasta ahora, para crear los distintos objetos que forman un escenario de realidad virtual, se han utilizado exclusivamente las formas primitivas (caja, cono, cilindro y esfera).

Tal como se ha visto en los anteriormente, agrupando y modificando estas formas primitivas se pueden conseguir otras formas más complejas.

Pero esta técnica tiene un límite. Supongamos que se quiere representar un automóvil, por ejemplo. Con la sola utilización de las formas primitivas sería demasiado complicado de crear, y se generarían ficheros VRML demasiado voluminosos.

Hay un método más eficaz para construir las formas complejas, que consiste en definir una serie de puntos en el espacio y luego unirlos entre sí para crear líneas o superficies

Nodo Coordinate

Este nodo sirve simplemente para definir una serie de puntos en el espacio tridimensional. En sí mismo no sirve para representarlos, sino que se utilizará dentro de otros nodos, como se va a ver a continuación.

La estructura de este nodo es la siguiente:

 
   Coordinate {
       point [
            12 11 17.1,
            20.5 13.8 5.3,
            14 6.1 22.9
       ]
   }

Como se puede ver, tiene un campo point, cuyo valor son grupos de tres cifras, separadas por comas, englobadas entre corchetes [ y ].

Cada grupo de tres cifras representa un punto en el sistema de coordenas. En el ejemplo están definidos tres puntos, pero se pueden poner tantos como se quiera.

Este nodo se utiliza dentro de otros nodos, para conseguir cosas distintas:

Nodo PointSet, para crear puntos aislados

Su estructura es:

 
   PointSet {
       coord Coordinate {
           point [  . . .  ]
       }
   }

Como se puede ver, tiene un campo coord cuyo valor es el nodo Coordinate visto anteriormente.

Aplicando al ejemplo del nodo Coordinate, quedaría de esta manera:

 
   PointSet {
       coord Coordinate {
           point [  
               12 11 17.1,
               20.5 13.8 5.3,
               14 6.1 22.9  
           ]
       }
   }

Si incluyéramos este código, tal como está, en un documento VRML, no podríamos ver ninguno de los puntos todavía. La razón es que, como se vió  anteriormente, para crear un objeto visible se debe utilizar el nodo Shape. 

Inclusión del nodo PointSet en el nodo Shape

Como se vió en el capítulo 5, la estructura general del nodo Shape es:

 
   Shape {
	appearance ...
	geometry ...
   }

Con el campo appearance se define la apariencia del objeto (color, textura, etc.) y con el campo geometry su forma.

Por tanto, es evidente que la inclusión del nodo PointSet deberá hacerse en este éltimo campo, ya que la geometría de este objeto es la de un grupo de puntos. De manera resumida, quedará de esta manera:

 
   Shape {
	appearance ...
	geometry PointSet { ... }
   }

Desarrollando totalmente el ejemplo:
   #VRML V2.0 utf8
   #Ejemplo de un grupo de tres puntos
   Shape {
       appearance Appearance {
           material Material { }
       }
       geometry PointSet {
           coord Coordinate {
               point [  
                   12 11 17.1,
                   20.5 13.8 5.3,
                   14 6.1 22.9   
               ]
            }
       }
   }

Color en los puntos

Hasta ahora hemos considerado que el nodo PointSet tenía un solo campo (coord), que sirve para definir la posición de los puntos. Pero puede tener también otro campo (color), que sirve para definir el color de cada uno de los puntos.

Es decir, el nodo PointSet puede tener esta estructura (resumida):

 

   PointSet {
       coord Coordinate {
           point [ . . . ]
       }
       color Color {
           color [ . . . ]
       }
   }

Como se puede observar, el campo color tiene como valor el nodo Color, que a su vez tiene como valor el campo color de nuevo.

Desarrollando totalmente el ejemplo:

 
   #VRML V2.0 utf8
   #Ejemplo de un grupo de tres puntos con colores

   Shape {
        geometry PointSet {
           coord Coordinate {
               point [  
                   12 11 17.1,        #1í punto
                   20.5 13.8 5.3,     #2í punto
                   14 6.1 22.9        #3í punto   
               ]
            }
            color Color {
                color [
                    1 0 0,    # 1í punto rojo
                    0 1 1,    # 2í punto verde
                    1 1 0     # 3í punto amarillo
               ]
            }
        }
   }

Obsérvese que se ha prescindido del campo appearance del nodo Shape, que había en el ejemplo anterior, ya que no es necesario, pues los puntos no van a tener la apariencia por defecto, sino la que se determina en el campo color.

En el campo color se puede observar que hay (entre corchetes) tres grupos de némeros, separados por comas. Cada grupo de tres cifras representa un color, para cada punto expresado en el campo point, y correspondiéndose en el mismo orden.

Nodo IndexedLineSet, para crear líneas

Con este nodo se crean líneas, uniendo una serie de puntos determinados. Su estructura resumida es la siguiente:

 
 IndexedLineSet {
     coord Coordinate {
         point [  . . .  ]
     }
     coordIndex [ . . .  ]
 }

Como se puede ver, tiene dos campos:

Veamos primero un ejemplo sencillo: tres puntos que se unen ordenadamente entre sí, para formar una línea:

 
 IndexedLineSet {
    coord Coordinate {
       point [
          5 5 0,   # este es el punto 0
          15 9 0,  # este es el punto 1
          20 18 0  # este es el punto 2     
       ]
    }
    coordIndex [
       0, 1, 2, -1       
    ]
 }

En el campo coord se establecen las coordenadas de los tres puntos A cada coordenada se ha añadido una línea de comentario para indicar cuál es la numeración que le corresponde al punto. Obsérvese que se comienza por 0 (y no por 1).

En el campo coordIndex se establece el orden en el que se unen los tres puntos. En este caso la línea empieza en el primero (el 0), sigue con el segundo (el 1) y acaba en el tercero (el 2). Con el valor -1 se indica que ahí acaba la línea.

Inclusión del nodo IndexedLineSet en el nodo Shape

Como se vió anteriormente, para que un nodo pueda verse, debe estar incrustado en el nodo Shape. Por tanto, de manera resumida, la inclusión del nodo IndexedLineSet en el nodo Shape se hará de esta manera:

 
   Shape {
	geometry IndexedLineSet { ... }
   }

No se ha especificado ningén campo para su apariencia ni color, lo que se verá más adelante.

Desarrollando el ejemplo, quedará de esta manera:

 
 #VRML V2.0 utf8
 #Ejemplo de linea uniendo tres puntos, sin definicion del color

 Shape {
    geometry IndexedLineSet {
       coord Coordinate {
          point [
             5 5 0,   #este es el punto 0
             15 9 0,  #este es el punto 1
             20 18 0  #este es el punto 2
          ]
       }
       coordIndex [
          0, 1, 2, -1
       ]
    }
  }

En el ejemplo, se han unido los puntos segén el orden en el que están especificados en el campo point (0,1,2), pero se pueden unir en el orden que se desee, como por ejemplo (0,2,1), con lo que resultará una línea distinta. Habría que modificar el campo coordIndex, que quedaría de esta manera:

 
       coordIndex [
          0, 2, 1, -1
       ]

Se pueden crear simultáneamente diversas líneas. Supongamos que en el campo point se han especificado las coordenadas de 12 puntos (por tanto, del 0 al 11), y queremos crear dos líneas, la primera con los 6 primeros puntos y la segunda con los 6 éltimos. El campo coordIndex quedaría de esta manera:

 
       coordIndex [
          0, 1, 2, 3, 4, 5, -1,    #esta es una línea
          6, 7, 8, 9, 10, 11, -1   #esta es otra línea
       ]

 

El color en las líneas

Para determinar el color de cada uno de los segmentos que compone la línea, hay que hacer uso de tres campos más:

Con el campo color se especifican cuántos y qué colores se van a usar (en el caso de que haya más de una línea). 

Con el campo colorIndex se atribuye a cada una de las líneas existentes alguno de los colores expresados en el campo anterior.

Con el campo colorPerVertex se especifica si los colores serán colores continuos, o un gradiente entre dos colores.

Veamos una aplicación en el ejemplo anterior: una énica línea quebrada que une tres puntos, se va a hacer que sea toda ella de color verde.

 
 #VRML V2.0 utf8
 #Ejemplo de linea de color verde, uniendo tres puntos

 Shape {
    geometry IndexedLineSet {
       coord Coordinate {
          point [
             5 5 0,   #este es el punto 0
             15 9 0,  #este es el punto 1
             20 18 0  #este es el punto 2
          ]
       }
       coordIndex [
          0, 1, 2, -1
       ]
       color Color {
          color [
             0 1 0   #este es el color 0 (verde), 
             ]       #el unico posible pues solo hay una linea
       }
       colorIndex [
          0   #a la unica linea se le atribuye el unico color 0 
       ]
       colorPerVertex FALSE   
    }
  }

En el campo color se especifica el color (o los colores, si hay más de una línea)

En el campo colorIndex se atribuye el color a la la línea (o líneas, si hay más de una). Este campo hace una función análoga con los colores y las líneas a la del campo coordIndex con los puntos y las líneas.

En este ejemplo sólo hay una línea con un énico color. En el ejercicio práctico se va ver un caso de dos líneas con dos colores.

Nodo IndexedFaceSet para obtener caras

La idea es unir una serie de puntos para que formen una polilínea cerrada, que es la que definirá la cara.

Su estructra resumida es:

 
 IndexedFaceSet {
     coord Coordinate {
         point [  . . .  ]
     }
     coordIndex [ . . .  ]

Observamos dos campos (puede tener más, como se verá):

Veamos primero un ejemplo sencillo: tres puntos que se unen ordenadamente entre sí para formar una cara:

 
 IndexedFaceSet {
    coord Coordinate {
       point [
          5 5 0,   # este es el punto 0
          15 9 0,  # este es el punto 1
          20 18 0  # este es el punto 2     
       ]
    }
    coordIndex [
       0, 1, 2, -1       
    ]
 }

En el campo coord se establecen las coordenadas de los tres puntos. A cada coordenada se ha añadido una línea de comentario para indicar cuál es la numeración que le corresponde al punto. Obsérvese que se comienza por 0 (y no por 1).

En el campo coordIndex se establece el orden en el que se unen los tres puntos que van a formar la polilínea cerrada que va a formar la cara. Con el valor -1 se indica que ahí acaba la cara.

Inclusión del nodo IndexedFaceSet en el nodo Shape

Como se vió anteriormente y en repetidas ocasiones, para que un nodo pueda verse, debe estar incrustado en el nodo Shape. Por tanto, de manera resumida, la inclusión del nodo IndexedFaceSet en el nodo Shape se hará de esta manera:

 
   Shape {
	geometry IndexedFaceSet { ... }
   }

No se ha especificado ningén campo para su apariencia ni color, lo que se verá más adelante.

Desarrollando el ejemplo, quedará de esta manera:

 
 #VRML V2.0 utf8
 #Ejemplo de cara uniendo tres puntos, sin definicion del color

 Shape {
    geometry IndexedFaceSet {
       coord Coordinate {
          point [
             5 5 0,   #este es el punto 0
             15 9 0,  #este es el punto 1
             20 18 0  #este es el punto 2
          ]
       }
       coordIndex [
          0, 1, 2, -1
       ]
    }
  }

A la derecha puede verse el resultado (pulsarla para cargar el escenario). Se han añadido unos ejes coordenados y modificado el punto de vista inicial.

Se puede observar que se ha creado una cara triangular, sin color ni apariencia definida. Si se manipula el escenario, se puede comprobar que la cara sólo es visible por un lado.

Se pueden definir más de una cara simultáneamente, como veremos más adelante.

El color en las caras

Para determinar el color de la cara hay que hacer uso de tres campos más:

Con el campo color se especifican cuántos y qué colores se van a usar (en el caso de que haya más de una cara). 

Con el campo colorIndex se atribuye a cada una de las caras existentes alguno de los colores expresados en el campo anterior.

Con el campo colorPerVertex se especifica si los colores serán colores continuos, o un gradiente entre dos colores.

Veamos una aplicación en el ejemplo anterior: vamos a hacer que la cara tenga un color verde.

 
 #VRML V2.0 utf8
 #Ejemplo de cara, uniendo tres puntos, de color verde
 Shape {
    geometry IndexedFaceSet {
       coord Coordinate {
          point [
             5 5 0,   #este es el punto 0
             15 9 0,  #este es el punto 1
             20 18 0  #este es el punto 2
          ]
       }
       coordIndex [
          0, 1, 2, -1
       ]
       color Color {
          color [
             0 1 0   #este es el color 0 (verde), 
             ]             #el unico posible pues solo hay una cara
       }
       colorIndex [
          0   #a la unica cara se le atribuye el unico color 0 
       ]
       colorPerVertex FALSE   
    }
  }

En el campo color se especifica el color (o los colores, si hay más de una cara)

En el campo colorIndex se atribuye el color a la la cara (o caras, si hay más de una).

En este ejemplo sólo hay una cara con un énico color. 

 

 

A la derecha puede verse el resultado. Se han añadido unos ejes coordenados y modificado el punto de vista inicial.

Si se manipula el escenario, se puede observar que la cara verde es visible sólo por un lado, el que está de frente.

Determinando una cara visible

Por defecto, la cara visible es la perpendicular a la parte positiva de los ejes coordenados. En el ejemplo anterior, la cara es perpendicular a la parte positiva del eje Z.

Pero puede haber ocasiones que nos interese que sea visible la otra cara. Para determinar una cosa o la otra, el nodo IndexedFaceSet puede tener el campo ccw (abreviatura de counterclockwise: contrario a la agujas del reloj). Hay dos posibilidades:

Si en el ejemplo anterior se añade el campo ccw FALSE. Para poder ver la cara verde hay que girar 180í el escenario.

Determinando ambas caras visibles

Se puede hacer que ambas caras sean visibles simultáneamente. Para ello el nodo IndexedFaceSet puede tener el campo solid, que tiene dos posibilidades:

Si al ejemplo anterior de la cara verde, se le añade el campo solid FALSE. Girando el escenario, se verá que ambas caras son visibles. 

Utilización de este nodo para crear un suelo

Un uso muy étil del nodo IndexedFaceSet es el de crear un suelo para un escenario de realidad virtual.

Supongamos que queremos crear un suelo de color azul para nuestro mundo, de dimensiones 100x100. Entonces, las coordenadas de las cuatro esquinas serán:

 
	 50,0,50
	-50,0,50
	-50,0,-50
	 50,0,-50

Vamos a añadir el campo solid FALSE. Pero si se carga el escenario, en principio no se verá nada. Esto es debido a que el punto de vista por defecto está precisamente a nivel cero, es decir, en el mismo suelo. Sólo se verá el suelo si se hace girar todo el escenario.

Determinando un punto de vista inicial elevado sobre el suelo (se verá más adelante cómo conseguirlo), y superponiendo unos ejes coordenados, obtenemos el resultado que se puede ver en la imagen de la derecha.

Obsérvese que al cargar el escenario, se "aterriza" sobre el suelo, y que se puede desplazar por él, pero no se pueden efectuar movimientos de elevación. Esto es debido, a que automáticamente se determina que existe gravedad en el mundo. Para poder flotar, hay que pulsar el mando "Float", que inhabilita la gravedad.

Con este método obtenemos un suelo del color que queramos, pero se pueden obtener suelos que tengan una textura determinada (hierba, piedra, etc.), lo que se verá más adelante.

Con el nodo IndexedFaceSet se crean suelos planos. En el siguiente capítulo se verá cómo crear suelos accidentados (terrenos con ditintas elevaciones, colinas, etc.).

 

Nodo ElevationGrid para crear suelos accidentados

El nodo ElevationGrid (en inglés: rejilla de elevaciones) determina una rejlla de puntos, es decir, una cuadrícula de puntos de separación uniforme en el plano horizontal XZ, a los que se atribuyen distintas cotas de altura. Se genererá una superficie irregular uniendo dichas cotas.

Veamos su estructura general con un ejemplo:

 
   ElevationGrid {
       xDimension 6
       xSpacing 4
       zDimension 3
       zSpacing 8
       height [
          0.25, 4, 7, 3, 1.5, 0.25,
          1.5, 2.5, 1.5, 2.1, 1, 0,
          0.3, 0.3, 0, -2.7, -1.5, -3.7
       ]
   }

Observamos los siguientes campos (puede tener otros más, como veremos):

 

La rejilla debe comenzar siempre en el origen de coordenadas y crecer en el sentido positivo de los ejes X y Z.

En el ejemplo se puede observar que habrá un pico máximo de valor 7 en el centro de la hilera trasera, y una depresión máxima de -3.7 a la derecha de la hilera delantera.

Inclusión del nodo ElevationGrid en el nodo Shape

Como se ha dicho repetida veces, para que un nodo que define una forma (como es el caso del nodo ElevationGrid) sea visible, debe incluirse dentro del nodo Shape.

La estructura general del nodo Shape es:

 
   Shape {
      appearance ...
      geometry ...
   }

El nodo ElevationGrid será el valor del campo geometry, quedando por tanto así (de manera resumida):

 
   Shape {
      appearance ...
      geometry ElevationGrid { ... }
   }

Y desarrollando totalmente el ejemplo:

 
   #VRML V2.0 utf8
   #Ejemplo del nodo ElevationGrid, 
   #con la apariencia por defecto

   Shape {
      appearance Appearance {
         material Material { }
      }
      geometry ElevationGrid {
         xDimension 6
         xSpacing 4
         zDimension 3
         zSpacing 8
         height [
            0.25, 4, 7, 3, 1.5, 0.25,
            1.5, 2.5, 1.5, 2.1, 1, 0,
            0.3, 0.3, 0, -2.7, -1.5, -3.7
         ]
      }
   }

Como el punto inicial (el origen de coordenadas) no es demasiado conveniente para visualizar la superficie generada, a la derecha se puede ver una imagen de este ejemplo en donde se ha modificado el punto de vista inicial a uno más conveniente. También se han añadido unos ejes coordenados.

Obsérvese que la superficie generada es sólida y existe gravedad, por lo que si se camina por la supeficie se "escalará" por las elevaciones.

Si se gira completamente el escenario, se puede comprobar también que la superficie sólo es visible por su parte delantera.

Determinando una cara visible

Por defecto, la cara visible es la perpendicular a la parte positiva de los ejes coordenados. En el ejemplo anterior, la cara es perpendicular a la parte positiva del eje Z.

Pero puede haber ocasiones que nos interese que sea visible la otra cara. Para determinar una cosa o la otra, el nodo ElevationGrid puede tener el campo ccw (abreviatura de counterclockwise: contrario a la agujas del reloj). Hay dos posibilidades:

Si en el ejemplo anterior se añade el campo ccw FALSE Para poder ver la superficie hay que girar 180í el escenario.

Determinando ambas caras visibles

Se puede hacer que ambas caras sean visibles simultáneamente. Para ello el nodo ElevationGrid puede tener el campo solid, que tiene dos posibilidades:

Si al ejemplo anterior de la cara verde, se le añade el campo solid FALSE Girando el escenario, se verá que ambas caras son visibles.

Poniendo color a la superficie generada

En el ejemplo visto anteriormente se ha utilizado la apariencia por defecto, sin un color definido. 

Pero en este caso concreto, hay una manera de determinar el color de partes determinadas de la superficie, como vamos a ver a continuación.

En este caso, la rejilla está formada por dos hileras de 5 cuadrículas, es decir, en total 10 cuadrículas. Se puede determinar el color de la superficie generada sobre cada cuadrícula.

A continuación está la representación de la rejilla vista desde arriba. Además, se ha escrito en cada cuadrícula el color que se desea para la parte de la superficie que le corresponda y un némero (del 1 al 10), para señalar el orden de atribución de colores.

Se ha escogido el color blanco para las zonas más altas, el rojo para las intermedias, el verde para las bajas y el azul para las más bajas.

Hay que añadir dos campos al nodo ElevationGrid:

El ejemplo anterior quedará de esta manera:

 
   #VRML V2.0 utf8
   #Ejemplo del nodo ElevationGrid, 
   #con colores en las caras

   Shape {
      appearance Appearance {
         material Material { }
      }
      geometry ElevationGrid {
         xDimension 6
         xSpacing 4
         zDimension 3
         zSpacing 8
         height [
            0.25, 4, 7, 3, 1.5, 0.25,
            1.5, 2.5, 1.5, 2.1, 1, 0,
            0.3, 0.3, 0, -2.7, -1.5, -3.7
         ]
         color Color {
            color [
               1 0 0   #rojo para la cuadr. 1
               1 1 1   #blanco para la cuadr. 2
               1 1 1   #blanco para la cuadr. 3
               1 0 0   #rojo para la cuadr. 4
               0 1 0   #verde para la cuadr. 5
               0 1 0   #verde para la cuadr. 6
               0 1 0   #verde para la cuadr. 7
               0 1 0   #verde para la cuadr. 8
               0 1 0   #verde para la cuadr. 9
               0 0 1   #azul para la cuadr. 10
            ]
         }
         colorPerVertex FALSE   #colores continuos (no degradados)
         solid FALSE   #ambas caras visibles
      }
   }

A la derecha puede verse el resultado. Se han añadido unos ejes coordenados y modificado el punto de vista inicial. 

Concepto básico de la extrusión

En líneas generales, una forma extruida se obtiene de la siguiente manera:

Véanse a continuación un ejemplo de extrusiones: una hélice de diámetro variable:

   

Estructura general del nodo Extrusion

La estructura básica de este nodo es la siguiente:

 
   Extrusion {
       crossSection [
           1 0,
	   0 6.5,
	   .......
       ]
       spine [
           10 0 3,
	   15 1 5,
	   ............
       ]
       solid FALSE
   }

Se pueden observar los campos:

Ejemplo sencillo de extrusión: un cubo

Veamos a continuación con más detalle cómo se define la sección transversal. Como se ha dicho antes, se encuentra en el plano XZ, es decir, en el plano horizontal formado por los ejes coordenados X y Z.

A la derecha se puede ver la representacion de los ejes coordenados.

La sección transversal es una superficie plana, definida en el el plano XZ. Por tanto, se van a utilizar sólo dos coordenas (x,z).

Vamos a ver un ejemplo muy sencillo de extrusión, para comprender este concepto: un cubo. (Recuérdese que existe el nodo Box para conseguirlo directamente)

La sección transversal de un cubo es un cuadrado definido en el plano XZ.

Para definir la sección transversal, se debe señalar un polígono cerrado, es decir, comenzando desde un vértice cualquiera (p. ej. 10,10), y siguiendo el sentido de las agujas del reloj, el siguiente será (-10,10), luego (-10,-10), a continuación (10,-10) y finalmente (10,10) de nuevo, con lo que el polígono se ha cerrado.

 
   crossSection [
      10 10,
      -10 10,
      -10 -10,
      10 -10,
      10 10
   ]

Ahora falta por definir la espina dorsal. La espina dorsal define el recorrido que la sección transversal deberá efectuar para crear le forma extruida.

Vamos a hacer que la sección transversal comience en el punto (0,-10,0) y se mueva hasta el punto (0,10,0).

En la siguiente figura se muestra el camino recorrido por la sección transversal a lo de la espina dorsal del cubo (en rojo): 

El campo spine queda de la siguiente manera:

 
   spine [
      0 -10 0,
      0 10 0
   ]

Inclusion del nodo Extrusion en el nodo Shape

Como se ha dicho repetida veces, para que un nodo que define una forma (como es el caso del nodo Extrusion) sea visible, debe incluirse dentro del nodo Shape.

Por tanto, el documento VRML de este ejemplo quedará de esta forma: 

 
   #VRML V2.0 utf8 
   #Ejemplo de extrusión: cubo
   Shape {
      appearance Appearance {
         material Material {}
      }
      geometry Extrusion {
          crossSection [
             10 10,
             -10 10,
             -10 -10,
             10 -10,
             10 10
          ]
          spine [
             0 -10 0,
             0 10 0
          ]
          solid FALSE
      } 
   }

A la derecha puede verse el resultado(Se han añadido unos ejes coordenados y modificado el punto de vista inicial)

Aberturas en la superficie extruida

En el ejemplo del cubo visto anteriormente, todas las caras eran visibles. Pero, si se desea, se puede hacer que no existan algunas de ellas, es decir, que haya aberturas en la superficie extruida.

Existen dos campos que determinan si la forma extruida está abierta o cerrada en los extremos:

Añadiendo estos dos campos al nodo Extrusion, en el ejemplo anterior:

 
   beginCap FALSE
   endCap FALSE

dará el resultado que se ve a la derecha.

Otra posibilidad es la de dejar deliberadamente sin cerrar el polígono que determina la superficie transversal.

 

 
   crossSection [
      -10 10,
      -10 -10,
      10 -10,
      10 10
   ]

queda sin cerrar el polígono, con lo que el cubo extruido carece de la cara frontal, como se puede ver en la imagen de la derecha.

Complicando la espina dorsal

En los ejemplos vistos anteriormente, la espina dorsal siempre es perpendicular a la sección transversal y además justo en su centro. Pero esto no tiene por qué ser así necesariamente.

La espina dorsal puede estar situada en cualquier sitio, formar una recta inclinada, estar formada por más de dos puntos o tener cualquier forma espacial que se desee.

Veamos una espina dorsal que sea una recta inclinada con respecto a la sección transversal.

La espina dorsal ha sido representada en rojo y la sección transversal en verde.

A la derecha puede verse el resultado..

 

Y ahora veamos un ejemplo de espina dorsal formada por tres puntos:

 

Superficies de revolución

Si la espina dorsal es un círculo, entonces el cuerpo engendrado es una superficie de revolución. Veamos un ejemplo: un toro definido por 8 puntos.

A la derecha puede verse la imagen que representa la sección transversal (en verde) y que está situada, como debe ser, en plano horizontal XZ. Esta sección transversal es un círculo de radio 5 y está representada por 8 puntos.

También está representada (en blanco) una espina dorsal formada por otros ocho puntos que forman un círculo de radio 10, y que también está situada en el plano XZ, porque así lo hemos querido.

El campo crossSection es el siguiente:

 
   crossSection [
      5 0,
      3.53 3.53,
      0 5,
      -3.53 3.53,
      -5 0,
      -3.53 -3.53,
      0 -5, 
      3.53 -3.53,
      5 0
   ]

Recuérdese que en el campo crossSection los puntos se expresan con dos corrdenadas (x,z), y que además hay que repetir al final el primer punto, para cerrar la sección transversal.

El campo spine es el siguiente:

 
   spine [
      10 0 0,
      7.07 0 7.07,
      0 0 10,
      -7.07 0 7.07,
      -10 0 0,
      -7.07 0 -7.07,
      0 0 -10, 
      7.07 0 -7.07,
      10 0 0
   ]

En este caso, los puntos vienen expresados por tres coordenadas (x,y,z). Y también se repite el éltimo para cerrar el toro

A la derecha puede verse el resultado.

Es evidente que los ocho puntos que se han utilizado para definir los dos círculos (el de la sección transversal y el de la espina dorsal) no son suficientes, pues más que círculos son octógonos.

Variación de la escala de la sección transversal

Hasta ahora, en todos los casos vistos, la sección transversal parmanecía inalterable en su forma y tamaño a lo largo de la espina dorsal.

Existe el campo scale que permite modificar la escala de la sección transversal en cada uno de los puntos de la espina dorsal.

Así, en el primer ejemplo del cubo, la espina dorsal estaba constituída por dos puntos. Podemos hacer que en el segundo punto, la escala de la sección transversal se reduzca a la mitad, con lo que la figura resultante sería un tronco de prisma. Basta con añadir el campo scale de la siguiente forma:

 
   scale [
      1 1,   
      0.5 0.5    
   ]

El argumento del campo scale es un par de parámetros (que varían de 0 a 1) y que representan la variación de las coordenadas x ó z. Hay que especificarlo por cada punto que hay en la espina dorsal.

En el ejemplo anterior, quiere decir que en el primer punto de la espina dorsal, queremos que permanezcan inalterables las medidas de las x (factor 1), así como de las z (factor 1). En el segundo punto de la espina dorsal, en cambio, queremos que las x se reduzcan a la mitad (factor 0.5), así como las z (factor 0.5). Se podía haber modificado sólo uno, pasando en vez de a un cuadrado menor, a un rectángulo.

A la derecha puede verse el resultado.

Variación de la orientación de la sección transversal

Hasta ahora, en todos los casos vistos se ha considerado que la sección transversal se traslada paralelamente a sí misma (variando o no su tamaño).

Existe el campo orientation que permite girar la sección transversal en cada punto de la espina dorsal un ángulo determinado.

Veamos un ejemplo: en el cubo vamos a hacer que la sección transversal gire 45í en el segundo punto de la espina dorsal, con lo que se obtendrá un cubo "retorcido". Para conseguirlo, vamos a añadir el campo orientation de la siguiente manera:

 
   orientation [
      0 1 0 0,
      0 1 0 0.875
   ]

El argumento del campo orientation está formado por un grupo de 4 parámetros. Los tres primeros indican el eje alrededor del cual se va a girar la sección transversal. Y el cuarto parámetro indica el ángulo girado, expresado en radianes (esto ya se vió para el campo rotation). Esto hay que especificarlo por cada punto de la espina dorsal.

En el ejemplo anterior, en el primer punto de la espina dorsal expresamos que la rotación alrededor del eje Y (parámetros 0 1 0) sea nula (cuarto parámetro 0). En cambio, en el segundo punto de la espina dorsal, queremos que la rotación alrededor del eje Y (parámetros 0 1 0) sea de 45í (cuarto parámetro, que es 3.14/4=0.875)

Puede verse el resultado en la figura siguiente a la izquierda.  


Color y transparencia con el nodo Material

Como el nodo Material está vacío, se obtiene la apariencia por defecto:

 
   Material { }

pero puede tener diversos campos, con los que se determinan el color y grado de transparencia. Los má importantes son:

 
   Material {
       diffuseColor  ...
       emissiveColor ...
       transparency  ...
   }

Veámoslos separadamente:

Color externo (o difuso) de los objetos

 
   Material {
       diffuseColor 1 0 0
   }

El campo diffuseColor determina el color externo (o difuso) del objeto, y tiene un valor representado con tres némeros, que se corresponden con el código de colores

Código de colores

Un color se representa por un grupo de tres cifras. La primera cifra se refiere a la cantidad de color rojo, la segunda a la cantidad de color verde y la tercera a la cantidad de color azul.

Las cifras pueden oscilar desde 0.0 (nada de ese color) hasta 1.0 (todo de ese color), pasando por cualquier valor intermedio. Por tanto, en el ejemplo anterior se trata de un color rojo puro.

He aquí algunos ejemplos de colores:

 
Color Rojo Verde Azul
Rojo 1 0 0
Verde 0 1 0
Azul 0 0 1
Blanco 1 1 1
Negro 0 0 0
Amarillo 1 1 0
Violeta 1 0 1
Marrón 0.5 0.2 0

Veamos una aplicación de ésto a un, donde tenía la apariencia por defecto. Ahora va a tener un color externo rojo:

 
   #VRML V2.0 utf8
   #Cono de color externo rojo

   Shape {	
       appearance Appearance {
           material Material { 
               diffuseColor 1 0 0
           }
       }	
       geometry Cone {
           height 3
           bottomRadius 0.75
       }	
   }

Este es el resultado.

Color interno (o emisivo)

 
   Material {
       emissiveColor 1 0 0
   }

El campo emissiveColor determina el color interno (o emisivo) del objeto, para cuando se quiere dar la impresión de que el objeto está iluminado por dentro con un color determinado (en este caso también de color rojo).

Para que se puedan comparar, a la derecha están representados dos conos, el de la izquierda con un color externo (o difuso) en rojo, y el de la derecha con un color interno (o emisivo), también en color rojo.

Un cuerpo puede tener ambos campos a la vez, y con colores distintos. Es decir, puede tener un color difuso determinado y otro color emisivo distinto.

A la derecha hay una imagen con un tercer cono que tiene su color difuso rojo y además su color emisivo en azul.

Transparencia

 
   Material {
   	transparency 0.5
   }

El campo transparency determina el grado de transparencia del objeto. Su valor está representado por un énico némero, que puede variar desde 0.0 (objeto totalmente opaco) hasta 1.0 (objeto totalmente transparente, lo que de hecho le hace ser invisible), pasando por cualquier valor intermedio.

Veamos un ejemplo: un objeto en forma de pared (una caja alta y delgada de dimensiones 2.5x2.5x0.3 de color amarillo y grado de transparencia de 0.5):

 
   #VRML V2.0 utf8
   #Pared amarilla semi-transparente

   Shape {
       appearance Appearance {
            material Material {
                 diffuseColor 1 1 0
                 transparency 0.5
           }
       }
       geometry Box {
            size 2.5 2.5 0.3
       }
   }

Si ponemos este objeto él solo en un escenario, no se puede juzgar su grado de transparecia, pues necesitamos otros objetos, para poder ver a su través.

Para poder comparar, se han colocado tres paredes iguales delante de los conos del éltimo ejemplo, con valores de transparencia (de izquierda a derecha de 0.0, 0.5 y 0.7 respectivamente), segén se puede ver en la imagen de la derecha.

Otros campos para definir el color

Además de los campos vistos anteriormente (diffuseColor, emissiveColor y transparency) existen otros tres campos más que se pueden utilizar para refinar aén más la apariencia del color de los objetos:

Hasta ahora, para definir un objeto visible se ha utilizado el nodo Shape de la siguiente forma:

 
   Shape {
       appearance Appearance {
            material ...
       }
       geometry ...
   }

en donde el nodo Appearance tiene un solo campo, material, con el que se definen el color y la transparencia, segén se ha visto en el capítulo anterior.

Pero en realidad puede tener también otros dos campos: texture y textureTransform, con los que se define la textura de los objetos:

 
   Shape {
       appearance Appearance {
            material ...
            texture ...
            textureTransform ...
       }
       geometry ...
   }

Vamos a centrarnos en el segundo campo exclusivamente (texture):

 
   Shape {
       appearance Appearance {
            texture ...
       }
       geometry ...
   }

áQué es la textura?

La textura es la posibilidad que tenemos de envolver un objeto con:

Se van a ver los dos primeros (ImageTexture y MovieTexture) en este capítulo, y el tercero (PixelTexture) en el capítulo siguiente.

Textura con ImageTexture

Tomemos como ejemplo la imagen de la derecha: monalisa.jpg 

Vamos a envolver con ella los objetos primitivos (caja, cono, cilindro y esfera).

Para ello, se hace uso del nodo ImageTexture, como valor del campo texture, de la siguiente manera:

 
   Shape {
       appearance Appearance {
            texture ImageTexture {
                url ["monalisa.jpg"]
            }
       }
       geometry ...
   }

Como se puede ver, el nodo ImageTexture tiene el valor url ["monalisa.jpg"] (se supone que la imagen está en el mismo directorio que el documento VRML. Si no fuera así, se pondría entre comillas la ruta del fichero de imagen)

Aplicando esto a una caja de dimensiones 1.5x2.2x0.5:

 
   #VRML V2.0 utf8
   # Ejemplo de caja con textura de una imagen

   Shape {
       appearance Appearance {
            texture ImageTexture {
                url ["monalisa.jpg"]
            }
       }
       geometry Box {
         size 1.5 2.2 0.5
       }
   }

Imágenes distintas en un mismo objeto

Puede interesar que un objeto tenga imágenes diferentes en alguna o algunas de sus caras.

Este objeto, que representa una lata de bebida (que en realidad es un cilindro), tiene una imagen para su superficie lateral, y otra distinta para los fondos superior e inferior.

Si nos limitamos a ponerlo de esta manera:

 
   Shape {
       appearance Appearance {
           texture ImageTexture {
                url ["etiqueta.jpg"]
            }
       }
       geometry ...
   }

se reproduciría la imagen de la etiqueta también en los fondos superior e inferior (como se ha podido comprobar en el caso del cilindro con la imagen monalisa.jpg)

Nos interesa que en los fondos superior e inferior tenga la imagen de la derecha (fondo.jpg)

La operación se hace de la siguiente manera:

 
   Shape {
       appearance Appearance {
            texture ImageTexture {
                 url ["etiqueta.jpg"]
            }              
       }
       geometry Cylinder {
           height 2
           radius 0.6
           top FALSE
           bottom FALSE
       }
   }

Lo que anula las superficies superior e inferior son los comandos top FALSE y bottom FALSE respectivamente.

 

  • Se define otro cilindro idéntico con la imagen fondo.jpg, pero se anula la superficie lateral.
  •  
       Shape {
           appearance Appearance {
                texture ImageTexture {
                     url ["fondo.jpg"]
                }              
           }
           geometry Cylinder {
               height 2
               radius 0.6
               side FALSE
           }
       }
    

    En este caso, lo que anula la superficie lateral es el comando side FALSE

     

  • Por medio del nodo Group se agrupan ambos cilindros, con lo que el resultado es aparentemente un cilindro con imágenes distintas. 

    Formatos de las imágenes

    Los formatos de imagen soportados para ser utilizados como texturas son:

    Textura con MovieTexture

    En lugar de usar imágenes estáticas como textura de los objetos, se pueden utilizar videos (películas), en formato MPEG, haciendo uso del nodo MovieTexture, en vez de ImageTexture, de la siguiente manera:

  •  
       Shape {
           appearance Appearance {
                texture MovieTexture {
                    url "mivideo.mpg"
                    speed 1
                    loop FALSE
                }
           }
           geometry ...
       }
    

    Con el comando speed se controla la velocidad (1, velocidad normal, 2 doble velocidad, etc.). Con valores negativos el video se ejecutaría hacia atrás.

    Con el comando loop se controla si el video funciona ininterrumpidamente (TRUE) o una sola vez (FALSE).

    volver