Introducción
Hasta ahora la mayoría de las interfaces de usuario de las
aplicaciones a la medida se basan en el uso de controles
básicos que brindan los entornos de desarrollo. Algunas técnicas de
interacción basadas en efectos de tres dimensiones (3D) han sido de
dominio casi exclusivo de las aplicaciones de juegos o de
aplicaciones muy especializadas que requieren de desarrolladores
expertos en estas técnicas. El acceso a recursos 3D ha estado
disponible solo a través de librerías implementadas
fundamentalmente en C++, como OpenGL y DirectX. Pero no resulta
sencillo integrar tales librerías con los paquetes de controles
existentes para el desarrollo de interfaces de usuario, como es el
caso de Windows Forms. Esta falta de integración ha desanimado la
inclusión de efectos 3D en la interfaz de usuario de muchas de
nuestras aplicaciones.
Precisamente una de las características más atractivas de Windows
Presentation Foundation (WPF), que ya ha sido tema de un cuaderno
técnico de dotNetManía [1] y de algunos artículos previos [2-4], es
que reduce la impedancia entre los mundos 2D y 3D. Las capacidades
de extensibilidad de WPF permiten crear interfaces de usuario con
efectos 3D sin exigir un conocimiento tan especializado como el que
se requiere para usar DirectX u OpenGL. En un artículo anterior [3]
se hizo una primera introducción al mundo 3D que puede lograrse con
WPF, viendo cómo definir un objeto 3D importante como es el caso de
una esfera. En este trabajo veremos cómo crear con WPF un objeto 3D
que nos permite mostrar las páginas de un documento simulando
la sensación visual de hojear las páginas, como ocurre cuando éstas
son de papel1.
Simulando la realidad
La vía más simple para lograr efectos visuales 3D es simularlos con
recursos 2D. Por ejemplo, se puede dibujar un botón que parezca
abultado gracias al uso de gradientes, con colores más claros por
encima y más oscuros debajo, como los botones de la aplicación
Media Player de Windows Vista que se muestra en la figura 1. Otra
solución con más realismo sería construir un objeto tridimensional
parecido a un botón real (como el de algún equipo electrónico) y
aplicarle las texturas y colores de iluminación adecuados.
La mayoría de los controles intentan mostrar una apariencia 3D con
efectos en 2D. Esto es posible gracias a las pequeñas dimensiones
de los controles, a la velocidad de movimiento cuando se aplica una
animación, o a la sutileza del efecto, lo que permite ocultar el
simulacro. Pero para lograr un mayor realismo en los efectos 3D es
preferible, aunque por supuesto más complejo, el uso de la
maquinaria 3D de WPF.
En este trabajo veremos cómo construir un objeto para mostrar una
hoja de un documento en 3D simulando los movimientos y
deformaciones que sufre una hoja real de un libro o revista cuando
se hojea.
Para comprender esta simulación, comience por observar cómo usted
hojea las páginas de esta propia revista dotNetManía. El movimiento
más evidente es el que hace con cada hoja cuando se rota ésta sobre
su borde interior, que hace de eje con la encuadernación. Si
identificamos las distintas posiciones de una rotación sobre este
borde en una propiedad Progress, que tome valores entre 0 y 180,
donde 0 indica que la hoja está completamente de frente y 180 sería
la misma hoja rotada 180 grados, es decir mostrándonos su reverso,
podríamos simular los movimientos de la hoja como se muestra en la
figura 2.
Sin embargo, a este efecto le falta realismo, porque las hojas se
ven rígidas. Hay que buscar un modo de lograr un efecto de
flexibilidad. Para ello introducimos dos propiedades: Twist y Bend.
Twist representa el grado de torcedura de la hoja, con un valor en
el rango desde -1 hasta 1. Si se mueve la hoja tomada por una
esquina, ésta se va torciendo en dirección al borde de rotación de
la misma. En la figura 3 se muestra una hoja (vista desde arriba)
con diferentes grados de torcedura.
Por su parte, la propiedad Bend es la que permite doblar o curvar
la hoja hacia fuera o hacia dentro. Con un valor entre -1 y 1,
muestra diferentes grados de curvatura del papel, como en la figura
4.
Si se varían adecuadamente los valores de estas tres propiedades
mediante una animación, se puede obtener el efecto visual de hojear
la página, como se muestra en los fotogramas de la figura 5.
La base de una animación en WPF consiste en ir generando valores
que, al cambiar con ellos determinadas propiedades, provoquen los
efectos deseados. Para poder animar los valores de una propiedad
ésta debe ser una DependencyProperty, y para poder ubicar un objeto
visible en un entorno 3D se debe utilizar la clase ModelVisual3D o
una heredera de ésta. Combinando estos dos elementos, vamos a
construir la malla 3D para visualizar cada hoja y poder animar sus
propiedades. Estrictamente hablando, la clase ModelVisual3D no es
un control, sino una clase heredera de Visual3D, por lo que puede
considerarse como pariente lejano de los controles 2D a los que
estamos acostumbrados. Para poder usar un objeto de este tipo en la
interfaz de usuario, será necesario incluirlo dentro de un control
Viewport3D, como se verá en la última sección.
Construcción de la malla
En primer lugar, definiremos una clase SimplePageModel3D (listado
1). Note que la propiedad Content (que se hereda de la superclase
ModelVisual3D) es de tipo Model3D; ésta es la que indica la malla
del objeto que se visualiza.
En el constructor de SimplePageModel3D se hace:
GeometryModel3D geom3D = new
GeometryModel3D();
this.Content = geom3D;
Con lo que se crea una instancia de GeometryModel3D (clase derivada
de Model3D) que se define a partir de una malla o geometría
tridimensional (Geometry). Es esta instancia de GeometryModel3D la
que se asigna a la propiedad Content.
Después, al hacer:
Binding binding = new
Binding("Material");
binding.Source = this;
BindingOperations.SetBinding(geom3D,
GeometryModel3D.MaterialProperty,
binding);
binding = new Binding("BackMaterial");
binding.Source = this;
BindingOperations.SetBinding(geom3D,
GeometryModel3D.BackMaterialProperty,
binding);
Se indica que se debe asociar el mismo material para la superficie
del objeto (Material) a través de la propiedad Material, y se
asocia el mismo material para la superficie interior (BackMaterial)
a través de la propiedad BackMaterial del modelo. En nuestro caso
usaremos ambos materiales para revestir la cara de una hoja de la
revista y su reverso.
La propiedad Geometry de GeometryModel3D toma valor en el método
BuildGeometry que se llama en la última línea del constructor, y
también al cambiar cualquiera de las propiedades de la hoja que
afecta la forma de la malla. Este método BuildGeometry (listado 2)
es el que se encarga de construir una malla a partir de los valores
de las propiedades Progress, Twist, Bend, Width, Height,
WidthSegments y HeightSegments para describir las dimensiones de la
hoja y el grado de resolución o de definición de la malla.
Este método crea una instancia de MeshGeometry3D que se asocia a la
propiedad Geometry del Content. Las propiedades Positions,
TriangleIndices, TextureIndices y Normals de esta instancia definen
la malla de un objeto tridimensional. Cada una de estas propiedades
contiene una colección de diferentes tipos. Una colección de
Point3D para Positions, una colección de Vector3D para Normals, una
colección de int para TriangleIndices y una colección de Point para
TextureIndices2.
Las posiciones se refieren a la ubicación en el espacio de los
vértices de la malla, tomando como referencia al punto (0, 0, 0)
como el origen de coordenadas tridimensionales. Las propiedades
Width y Height determinan las coordenadas de las cuatro esquinas de
la hoja, cuando ésta no tiene ninguna deformación aplicada. Las
propiedades WidthSegments y HeightSegments determinan las
posiciones del resto de los vértices de la malla, interpolando
posiciones entre las cuatro esquinas de la hoja. A mayores valores
de estas propiedades se crea una mayor cantidad de vértices, por lo
que al deformar la página se logra una superficie más uniforme. Sin
embargo, un mayor número de vértices puede afectar el
rendimiento. La sintonización adecuada de estos parámetros influye
por tanto en el realismo que tendrá el efecto de hojear, ya que al
aplicar una animación a la página, se pueden notar cambios bruscos
si el número de vértices es muy grande.
Luego de ubicar los vértices se debe "tapizar" la malla con
triángulos. La propiedad TriangleIndices contiene una colección de
índices de vértices. Estos índices deben añadirse según los tríos
de vértices que forman un triángulo.
A su vez, la colección TextureCoordinates contiene una instancia de
Point por cada vértice de la malla, para indicar qué parte de la
textura corresponde con cada vértice. Estas coordenadas de textura
se dan en dos dimensiones porque son relativas al Material que se
usa para "pintar" el GeometryModel3D. Este material se conforma a
partir de brochas 2D.
El método BuildGeometry realiza un recorrido por los vértices de la
malla que se va a construir. Por cada uno de ellos calcula su
posición inicial, antes de aplicar ninguna deformación producida
por los parámetros de la página. Luego va aplicando paso a paso el
efecto de las propiedades Twist, Bend y Progress para después
llevar los vértices a la escala indicada por las propiedades Width
y Height. Por cada vértice calculado, a su vez se calcula también
la posición de la textura correspondiente al vértice, que no es
afectada por las deformaciones de los parámetros. Las coordenadas
de textura son relativas al rectángulo ubicado en la posición (0,
0) y de dimensiones (1.0, 1.0), de manera que la esquina superior
izquierda de la textura corresponde con la coordenada (0, 0) y la
inferior derecha con la coordenada (1.0, 1.0), como muestra la
figura 6.
Luego de calcular los vértices, se declaran lo triángulos que
conforman la malla. Por cada cuatro vértices que formen un pequeño
rectángulo (que en conjunto conforman toda la malla de la página),
se deben generar dos triángulos, como se muestra en la figura 7.
Los índices t0, t1, t2, t3 utilizados en BuildGeometry representan
cuatro de estos vértices.
El orden de los tres índices que determinan el triángulo es
importante. La cara del triángulo que muestra los vértices en el
sentido contrario a las manecillas del reloj se asocia con el
material dado por la propiedad Material de la malla. La cara
opuesta se asocia con BackMaterial. Es por ello que t0, t2,
t3 y t0, t3, t2 producen el mismo triángulo, pero uno muestra a la
cámara un material diferente del otro, según se observe el sentido
de los vértices desde la cámara.
Pasando la página
Una vez que tenemos definida nuestra clase personalizada
SimplePageModel3D, solo hay que usarla adecuadamente en el XAML de
una aplicación para dar el efecto deseado. El listado 3 nos muestra
un código XAML para animar la página moviéndola hacia izquierda (en
contra de las manecillas del reloj) y hacia derecha (a favor de las
manecillas del reloj). Las animaciones asociadas a cada sentido
describen un trayecto para los tres parámetros dados por las
propiedades Progress, Twist y Bend, de forma de lograr un efecto de
hojear la página como el que se ilustra en las imágenes de la
figura 5.
Para que las animaciones tengan acceso desde los recursos al
elemento page3D en el Viewport3D, hay que registrar el control bajo
dicho nombre. Para ello, en el constructor de la clase TestPage3D
se obtiene la instancia de INameScope de la página y se registra el
control page3D con el identificador "page3D", como muestra el
listado 4.
Ejecute el código que se adjunta con este artículo y utilice los
dos botones ubicados junto con la página, para pasar hacia
izquierda y derecha. Intente hojear la página hacia un lado, y a
mitad de camino cambiar de dirección hacia el lado opuesto. Observe
cómo la animación muestra en forma suave el movimiento de la
página.
Conclusiones
Las capacidades de gráficos 3D que nos ofrece WPF se llevan la
palma cuando se trata de dotar de mayor realismo a la interfaz de
usuario de una aplicación. Esto debe estimular y ayudar a
familiarizarse con más facilidad con el concepto que la aplicación
quiere mostrar. La novedad con WPF no está solo en permitir
representar elementos 3D, como nuestro SimplePageModel3D, con más
facilidad que si se usa directamente OpenGL o DirectX. El mayor
atractivo viene dado por la fácil integración de estas capacidades
con el mundo de los controles 2D y con la lógica del negocio de
nuestras aplicaciones; por ejemplo, las páginas a hojear pueden ser
el resultado de una búsqueda (todo lo sofisticada que la lógica de
la aplicación pretenda).
Anímese a poner algo de 3D en su aplicación de negocio: es solo
cuestión de inspirarse en la realidad y abstraerse un poco (o
mucho, eso depende), y claro, a veces atreverse a pasar un rato con
las matemáticas. Sus usuarios lo apreciarán.
Es imposible explicar todos los detalles en el limitado espacio del
que disponemos para este artículo. Descárguese el código desde la
página Web de dotNetManía y experimente con él.