DNM+ Online
dotnetmania 2.0
Paso de páginas en 3D con WPF
En un artículo anterior, los autores nos adentraron en el mundo de los efectos 3D en WPF, mostrándonos cómo lograr un objeto 3D muy utilizado, una esfera. En esta entrega nos definen un “visualizador de páginas” que permite “hojear” un documento con un efecto similar al de hojear un libro o una revista de papel.

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.

blog comments powered by Disqus
autores
referencias