martes, 24 de febrero de 2015

Transformaciones



Node Transform

Para situar, orientar y escalar los objetos según nuestras necesidades dentro del entorno que estemos construyendo, debemos poder aplicar las transformaciones geométricas básicas, que son: traslación, rotación y escalado. También existen variantes de estas pero que no veremos aquí.
Para aplicar transformaciones en VRML se utiliza el node Transform. Este node puede realizar cualquiera de las tres operaciones o las tres a la vez. Para ver cómo funciona iremos analizando ejemplos:

Traslaciones

Ejemplo1: Definiremos una esfera roja en el origen de coordenadas y una esfera verde trasladada 10 unidades en el eje de las X.
 
Shape { # Esfera roja que nos marca el origen de coordenadas
geometry Sphere { radius 2 }
appearance Appearance { material Material { diffuseColor 1 0 0 } }
}
 
Transform {
translation 10 0 0
children [
Shape { # Esfera verde trasladada 10 unidades en el eje X
geometry Sphere { radius 2 }
appearance Appearance { material Material { diffuseColor 0 1 0 } }
}
]
}

Analicemos este código. La primera parte define la esfera roja que nos servirá de referencia para ver claramente que la esfera verde ha sido efectivamente trasladada. Como se puede observar, la esfera roja no sufre ninguna transformación.
La segunda parte es la que realmente nos interesa. Aquí definimos un node Transform. Este tiene un field translation donde se define el desplazamiento que sufrirá el objeto que deseamos trasladar. Así pues se define un desplazamiento de 10 unidades en el eje X, 0 unidades en el eje Y y 0 unidades en el eje Z. Las traslaciones son relativas y por lo tanto se suman a la posición actual de los objetos.
Después del field translation podemos ver el field children. Este field permite dar una lista de objetos que serán afectados por la trasformación definida. En este caso la lista de objetos se limita a uno solo: la esfera verde que queremos situar a 10 unidades del origen en el eje X. (De hecho este node es uno de los nodes agrupadores que mencionábamos en el módulo Nodos, Campos y Eventos I).
(NOTA: Como referencia de tipos de fields, el field children es de tipo MFNode, es decir, de tipo multivaluado por el hecho que puede contener una lista de valores).
Compliquemos un poco más el entorno:
Ejemplo2: Ahora queremos añadir un cono azul sobre nuestra esfera verde.
Para hacer esto debemos trasladar el cono 10 unidades en el eje de las X al igual que la esfera verde, pero además, debemos trasladarlo 3 unidades en el eje de las Y (en concordancia con las medidas de la esfera y el cono).
 
Shape { # Esfera roja que nos marca el origen de coordenadas
geometry Sphere { radius 2 }
appearance Appearance { material Material { diffuseColor 1 0 0 } }
}
 
Transform {
translation 10 0 0
children [
Shape { # Esfera verde trasladada 10 unidades en el eje X
geometry Sphere { radius 2 }
appearance Appearance { material Material { diffuseColor 0 1 0 } }
}
]
}

Transform {
translation 10 3 0
children [
Shape { # Cono azul trasladado 10 unidades en el eje X y 3 en el eje Y
geometry Cone { bottomRadius 2 height 2 }
appearance Appearance { material Material { diffuseColor 0 0 1 } }
               }
        ]
}


Rotaciones

Ejemplo3: Definiremos una caja amarilla en el origen de coordenadas con las caras paralelas a los planos coordenados, y otra caja azul (más estrecha y más alta) rotada 45 grados respecto al eje Y.
 
Shape { # Caja amarilla que nos señala la rotación cero
geometry Box { size 4 2 4 }
appearance Appearance { material Material { diffuseColor 1 1 0 } }
}

Transform {
rotation 0 1 0  0.7854 # 45 grados en radianes
children [
Shape { # Caja azul que es rotada 45 grados respecto al eje Y
geometry Box { size 2 4 2 }
appearance Appearance { material Material { diffuseColor 0 0 1 } }
}
]
}
Analicemos este fragmento de código. La primera parte define la caja amarilla que servirá de referencia para ver claramente que la caja azul ha sido rotada. Como se puede observar, la caja amarilla no sufre ninguna transformación y por lo tanto mantiene sus caras paralelas a los planos coordenados.
En la segunda parte definimos un node Transform. Este tiene un field rotation donde se define la rotación que sufrirá el objeto a modificar. Así pues, una rotación de 45 grados (0.7854 radianes) respecto al eje Y. Esto en VRML se define de una forma poco común.
El field rotation tiene en realidad dos partes. Los tres primeros valores determinan un vector en el espacio 3D que corresponde a la orientación del eje de rotación de nuestra transformación. El eje de rotación coincide con los ejes coordenados cuando:
  • eje rotación = (1 0 0) -> equivale a X
  • eje rotación = (0 1 0) -> equivale a Y
  • eje rotación = (0 0 1) -> equivale a Z
Este sistema de definir un eje de rotación de la transformación es muy flexible y genérico, pero resulta compleja cuando el eje de rotación no es paralelo a un eje coordenado.
La segunda parte es el ángulo de rotación que queremos aplicar (expresado en radianes) respecto al eje definido en la primera parte. Al estar en un sistema de ejes a derechas, los ángulos positivos resultan en rotaciones en sentido antihorario (y los ángulos negativos, en rotaciones en sentido horario).
Después del field translation podemos ver el field children que, como antes, engloba la lista de objetos que serán afectados por la rotación. En este caso, la lista de objetos es, de nuevo, un solo objeto en nuestro ejemplo: la caja azul que deseamos rotar 45 grados respecto al eje Y.
Compliquemos un poco más el entorno:
Ejemplo4: Ahora queremos que la caja azul también rote 45 grados respecto al eje X.
 
Shape { # Caja amarilla que nos marca la rotación cero
geometry Box { size 4 2 4 }
appearance Appearance { material Material { diffuseColor 1 1 0 } }
}
 
Transform {
rotation 1 0 0  0.7854 # Rotación eje X
children [
Transform {
rotation 0 1 0  0.7854 # Rotación eje Y
children [
Shape { # Caja azul
geometry Box { size 2 4 2 }
appearance Appearance { material Material { diffuseColor 0 0 1 } }
                               }
                       ]
               }
 
        ]
}
La comprensión de esta variante se deja al lector.
 

Escalados

Por lo que refiere a los escalados se deben tener en cuenta dos tipos distintos: los escalados uniformes y los no uniformes (aunque los uniformes sean un caso concreto de los no uniformes). Los uniformes agrandan o reducen un objeto en todas las direcciones por igual, mientras que los no uniformes tan solo modifican la talla en una dirección. Por lo tanto los escalados uniformes mantienen las proporciones originales de los objetos, mientras que los no uniformes deforman los objetos.
Ejemplo5: Definiremos un escalado no uniforme sobre un cubo.
En concreto, escalaremos el cubo al doble de su tamaño en la dirección de las X.

Transform {
scale 2 1 1 
children [
Shape { # Cubo naranja
geometry Box { size 1 1 1 }
appearance Appearance { material Material { diffuseColor 1 0.5 0 } }
}
]
}
Analicemos este código. Se define un node Transform que tiene un field scale donde se define el escalado no uniforme que sufrirá el objeto que deseamos modificar. Para definir el escalado, damos el tanto por uno que queremos agrandar/reducir el objeto en cada una de las direcciones que marcan los ejes coordenados.
Como el escalado no uniforme que definimos es de [2 1 1], el cubo de 1 unidad por lado ya no aparece como tal si no que aparece como una caja alargada horizontalmente de exáctamente 2 unidades de ancho, 1 de alto y 1 de fondo.
Si queremos aplicar un escalado uniforme, solo tenemos que poner el mismo valor de escalado para las tres direcciones.

Encadenamiento de Transformaciones

Cuando se desea aplicar más de una transformación a un objeto, no es necesario definir un node Transform para cada una, sinó que podemos definir las tres bajo un mismo node.
Ejemplo6: Definiremos un cubo de 1 unidad y le aplicaremos una traslación de [5 4 0], una rotación de 30 grados respecto el eje X y un escalado de [0.5 0.5 2].
 
Transform {
        translation 5 4 0
        rotation 1 0 0  0.5236 # 30 grados
        scale 0.5 0.5 1
        children [
Shape { # Cubo verde obscuro
geometry Box { size 1 1 1 }
appearance Appearance { material Material { diffuseColor 0 0.5 0.2 } }
}
]
}
Analizando el código vemos que lo que hemos definido no es tan claro como parece ya que no obtenemos el mismo resultado si aplicamos las tres operaciones en ordenes distintos.
Por definición, VRML define un orden de preferencia de operaciones de transformación que es el siguiente:
  1. En primer lugar se aplica el escalado.
  2. En segundo lugar se aplica la rotación.
  3. En tercer lugar se aplica la traslación.
Por lo tanto el código de arriba es totalmente equivalente al siguiente, realizando las transformaciones por separado:
 
Transform {
translation 5 4 0
children [
Transform {
rotation 1 0 0  0.5236 # 30 grados
children [
Transform {
scale 0.5 0.5 1
children [
Shape { # Cubo verde obsuro
geometry Box { size 1 1 1 }
appearance Appearance { material Material { diffuseColor 0 0.5 0.2 } }
                                              }
                                      ]
                               }
                       ]
               }
        ]
}
Como podemos comprobar el resultado es el mismo que antes.
No se debe caer en el error de pensar que esto se lee de arriba a abajo. Este código se lee de dentro hacia fuera y por lo tanto el orden en que se aplican las operaciones es empezando desde el objeto e ir saliendo hacia fuera: cubo > escalado > rotación > traslación.
Para aplicar las transformaciones en el orden que hemos dicho inicialmente deberíamos invertir la secuencia de la siguiente forma:
 
Transform {
scale 0.5 0.5 1
children [
Transform {
rotation 1 0 0  0.5236 # 30 grados
children [
Transform {
translation 5 4 0
children [
Shape { # Cubo verde obscuro
geometry Box { size 1 1 1 }
appearance Appearance { material Material { diffuseColor 0 0.5 0.2 } }
}
]
}
]
}
]
}

Como podemos comprobar el resultado es totalmente distinto a los dos anteriores. De hecho nuestra caja que era un paralelepípedo de ángulos rectos ahora ha quedado deformada pasando a ser un trapezoide.

jueves, 19 de febrero de 2015

Primitivas y Materiales



Primitivas

En VRML existe una serie de objetos predefinidos que forman el conjunto de primitivas de geometría. Estas primitivas son:
  • Box (paralelepípedos genéricos: cubos, cajas)
  • Sphere (esfera)
  • Cone (cono)
  • Cylinder (cilindro)
Cada una de estas primitivas es un node.

El node Shape

Para poder visualizar estas primitivas, es necesario utilizar el node Shape. Este node tiene dos fields: geometry y appearance. El segundo lo veremos en el apartado siguiente (Materiales I).
El field geometry es donde se especifica la geometría de algún objeto 3D. Es a través de este field que se especifican las primitivas. A continuación se muestra como definir cada una de las primitivas existentes:
Box:
 
Shape {
        geometry Box { size 4 3 5 }
}
Aquí definimos una caja de 4 unidades de ancho, 3 de alto y 5 de fondo (size 4 3 5). Si quisiéramos definir un cubo perfecto de 10 unidades por lado, pondríamos las tres cifras iguales a 10 (size 10 10 10).
(NOTA: Al visualizar el ejemplo vemos una masa de color gris uniforme debido a que aún no hemos definido un material para el objeto. Esto lo veremos en el apartado siguiente).
El objeto queda definido de forma que su centro coincide con el origen de coordenadas locales, y que en este caso, como no se han aplicado transformaciones, coincide también con el origen de coordenadas globales o de mundo.
Sphere:
 
Shape {
        geometry Sphere { radius 5 }
}
Aquí definimos una esfera de 5 unidades de radio (radius 5). También queda centrada en el origen de coordenadas.
Cone:
 
Shape {
        geometry Cone { 
               bottomRadius 3
               height 2
        }
}
Aquí definimos un cono con una base de 3 unidades de radio (bottomRadius 3) y una altura de 2 unidades (height 2). También queda centrado en el origen de coordenadas.
Cylinder:
 
Shape {
        geometry Cylinder { 
               radius 4
               height 5
        }
}
Aquí definimos un cilindro con una base de 4 unidades de radio (radius 4) y una altura de 5 unidades (height 5). También queda centrado en el origen de coordenadas.

Materiales

Para definir una apariencia de nuestras primitivas (y en general de todos los objetos) debemos definir un color o una textura a aplicar a su superficie. En este apartado tan solo veremos cómo definir un color y dejaremos la definición completa de texturas para un módulo posterior.
Así pues, tal y como hemos visto arriba, el node Shape dispone de un field llamado appearance donde definiremos las características del material del objeto. Y para hacerlo utilizaremos el node Appearance de VRML.
Este node tiene diversos fields pero de momento sólo nos concentraremos en su field material. Para definir este field debemos utilizar el node Material. Aquí también sólo nos concentraremos en un field de este node: el field diffuseColor, que hace referencia al color que refleja el objeto, es decir, el color del objeto.
Tal y como hemos visto en el módulo "Ejes y Unidades", los colores en VRML se definen en RGB (Red-Green-Blue o Rojo-Verde-Azul). Los valores de R, G y B deben estar dentro del rango [0,1]. Veamos un ejemplo:
Ejemplo1: Queremos definir la caja de antes de color rojo (RGB = [1,0,0]).
 
Shape {
        geometry Box { size 4 3 5 }
        appearance Appearance {
               material Material {
                       diffuseColor 1 0 0
               }
        }
}
Como se puede apreciar, la caja que antes no se distinguía por el hecho de no tener una apariencia asignada que le confiriera características de superfície, ahora se ve claramente y del color rojo que hemos definido.
Otro ejemplo:
Ejemplo2: Queremos definir el cilindro de antes, de color cian (azul claro) (RGB = [0,1,1]).
 
Shape {
        geometry Cylinder { 
               radius 4
               height 5
        }
        appearance Appearance {
               material Material {
                       diffuseColor 0 1 1
               }
        }
}