viernes, 5 de marzo de 2010

IPhone Box2D Tutorial Part II: Starting with Box2D

Con este post continúo el tutorial para crear un primer proyecto Box2D en IPhone. Si no has leído la primera parte la tienes aquí.

En este punto deberíamos tener el proyecto XCode con el código Box2D compilando perfectamente listo para empezar a implementar, pero antes vamos a ver una pequeña introducción al funcionamiento de Box2D y los elementos que maneja:
  •  Cuerpo rígido (body)
    •  Elemento compuesto de una materia tan fuerte que la distancia entre dos partículas del mismo permanece inalterable. A partir de ahora hablaremos de cuerpo para referirnos a cuerpos rígidos.
  • Forma (shape):
    • Cuerpo geométrico bidimensional que está asociado a un cuerpo. Box2D las usa para la detección de colisiones. Tienen propiedades materiales de fricción y elasticidad.
  • Limitación (constraint):
    • Una limitación es una conexión física que elimina grados de libertad de los cuerpos. En 2D los cuerpos tienen 3 grados de libertad. Si clavamos un cuerpo a una pared (como un péndulo), hemos LIMITADO el movimiento de dicho cuerpo de tal forma que solo podemos rotar el cuerpo alrededor del anclaje, hemos eliminado por tanto 2 grados de libertad.
  • Limitación de contacto (contact constraint):
    • Limitación especial diseñada para prevenir la penetración de cuerpos rígidos y simular así la fricción y elasticidad. Este tipo de limitaciones nunca se crean, Box2D las crea automáticamente.
  • Unión, enlace (joint):
    • Es un tipo especial de limitación que se emplea para mantener dos o más cuerpos unidos. Box2D soporta diferentes tipos de uniones. Las uniones pueden tener límites o guías.
  • Límite de unión (joint limit):
    • Restringe el rango de movimiento de una unión, por ejemplo, el codo de un humano sólo permite un determinado rango de movimiento.
  • Guía de unión (joint motor) :
    • Conduce el movimiento de los cuerpos conectados de acuerdo a los grados de libertad de la unión, por ejemplo, podemos usar una guía para conducir la rotación de un codo humano.
  • Mundo (world):
    • Es una colección de cuerpos, formas y limitaciónes que interactúan. 


Ahora que ya tenemos una idea básica de los elementos con los que contamos, podemos empezar a implementar nuestro primer proyecto de simulación. Para ello lo primero que necesitamos es crear un mundo al que iremos añadiendo cuerpos y formas. Este mundo será un atributo de nuestra clase MyFirstBox2DViewController por tanto lo añadimos en la declaración de la interface:

#import
#import "Box2D.h"

@interface MyFirstBox2DViewController : UIViewController {
    b2World *world;
}

@end
Vamos ahora con la inicialización del mundo, en el archivo .m, escribimos las siguientes lineas:
b2AABB worldAABB;
worldAABB.lowerBound.Set (-100.0f, -100.0f);
worldAABB.upperBound.Set (100.0f, 100.0f);
b2Vec2 gravity (0.0f, 10.0f);
bool doSleep = true;
    
world = new b2World (worldAABB, gravity, doSleep);
 Con esto creamos una región que contendrá el mundo (la estructura b2AABB), representa el "bounding box"  del mundo. Box2D utiliza las "Bounding Box" para acelerar la detección de colisiones. La región del mundo debe siempre ser más grande que la región donde los cuerpos se van a alojar, ya que si algún cuerpo alcanza los límites de la región del mundo quedará congelado y su simulación se detiene.
En este punto hay que resaltar que Box2D no utiliza, en principio, los píxeles como unidad fundamental de medida, sino que utiliza el metro, el kilogramo para las medidas de peso y el segundo como unidad de tiempo. Más adelante veremos cómo establecer una relación entre las unidades con las que trabaja Box2D y los píxeles con los que trabajamos en Cocoa.

Aparte del Bounding Box del mundo, también definimos el vector de la gravedad. En este punto cabe decir que el sistema de coordenadas que usa Box2D tiene el origen en la esquina superior izquierda, creciendo hacia la derecha el eje X y hacia abajo el eje Y, por tanto, iniciamos el mundo para un proyecto con orientación Portrait.

El último parámetro es un valor booleano que indica que permitimos que los cuerpos "duerman" cuando se paran. Un cuerpo dormido no necesita simulación, por tanto, liberamos a la CPU de todos los cálculos asociados al desplazamiento de dicho cuerpo. Los cuerpos dormidos se despiertan cuando algún cuerpo móvil choca contra ellos o cuando explícitamente lo despertemos. Finalmente, creamos el mundo a partir de los parámetros anteriormente definidos.

Ahora que ya tenemos nuestro mundo, vamos a añadir un cuerpo.  
//Create ball body and shape
b2BodyDef ballBodyDef;
ballBodyDef.position.Set (80.0f / RATIO, 0.0f / RATIO);
b2Body *body = world->CreateBody (&ballBodyDef);
Con esto definimos un cuerpo rígido (estructura b2BodyDef) y asignamos su posición en el punto (80, 0). Dividimos estos valores entre una constante RATIO que determina la relación entre las unidades de medida de Box2D y nuestros píxeles, para este ejemplo vamos a considerar que vale 30. De aquí en adelante, esta constante va a estar presente en la mayoría de las interacciones con el motor Box2D. Finalmente indicamos a nuestro mundo que añada un nuevo cuerpo a partir de la definición creada.
A este cuerpo le añadiremos una forma (shape) que, como hemos visto anteriormente sirva para determinar las colisiones que este cuerpo tiene con el entorno.
b2CircleDef ballShapeDef;
ballShapeDef.radius = 20.0f / RATIO;
ballShapeDef.density = 1.0f;
ballShapeDef.friction = 0.3f;
ballShapeDef.restitution = 0.5f;
body->CreateShape (&ballShapeDef);
body->SetMassFromShapes();
Con estas líneas definimos una forma circular (estructura b2CircleDef) a la que asignamos sus propiedades geométricas y físicas:
  • Radio (radius): El radio de la circunferencia, nuevamente dividido por la constante de conversión de medida
  • Densidad (density): Indica la densidad del cuerpo (en kg/m^2). Por defecto la densidad es 0. Box2D considera que un cuerpo con densidad es 0 es estático, por tanto no será simulado.
  • Coeficiente de fricción (friction). Entre 0 y 1.
  • Coeficiente de elasticidad (restitution). Entre 0 y 1.
Añadimos esta forma a nuestro cuerpo previamente creado y le notificamos que calcule la masa que tendrá a partir de las formas añadidas, en este ejemplo sólo hemos añadido una forma al cuerpo, pero podríamos añadirle más.

En este punto tenemos nuestro mundo creado con una bola situada en el punto (80, 0), pero si ejecutáramos el código no veríamos nada, ya que no hemos asociado ninguna figura a nuestra bola. El motor de físicas Box2D es simplemente eso, un motor de físicas, que nada sabe, a priori, del renderizado del mundo que computa. Para renderizar la bola en pantalla vamos a emplear una bola que puedes descargar aquí. Y que debes añadir a los recursos de tu proyecto. Usaremos capas (CALayer) para renderizar los elementos de nuestro mundo. Así definimos una capa a la que colocamos en el mismo punto en el que hemos colocado la bola en el mundo y le asignamos como contenido la imagen recientemente añadida.
UIImage *bImg = [UIImage imageNamed: @"ball.png"];
CALayer *ballLayer = [CALayer layer];
ballLayer.anchorPoint = CGPointMake(0.5, 0.5);
ballLayer.position = CGPointMake(80, 0);
ballLayer.contents = (id) bImg.CGImage;
ballLayer.bounds = CGRectMake(0, 0, bImg.size.width, bImg.size.height);
[self.view.layer addSublayer: ballLayer];
Sólo recordar que para poder usar layers, necesitamos añadir el framework CoreGraphics a nuestro proyecto.
En este punto, Box2D nos facilita asociar cualquier objeto a un cuerpo al que supuestamente representa, por tanto, volvemos a la definición del cuerpo para añadir el objeto, con lo que el código quedaría así:
b2BodyDef ballBodyDef;
ballBodyDef.position.Set (80.0f / RATIO, 0.0f / RATIO);
ballBodyDef.userData = ballLayer;    
b2Body *body = world->CreateBody (&ballBodyDef);
En este punto tenemos que determinar la frecuencia de refresco de la imagen y del mundo para poder representar la animación, una frecuencia recomendada es 60 frames por segundo, por tanto lanzamos un timer con esta frecuencia que invoque una función en la que actualizaremos la posición de la capa según los datos que nos da Box2D.
La función de refresco tiene esta pinta:
- (void) step {
    world->Step (1.0f / 60.0f, 10, 8);
    [CATransaction begin];
    [CATransaction setAnimationDuration: 0.00];
    [CATransaction setDisableActions: YES];
    for (b2Body *b = world->GetBodyList(); b; b=b->GetNext()) {
        if (b->GetUserData() != NULL) {
            CALayer *ball = (CALayer *)b->GetUserData();
            ball.position = CGPointMake(b->GetPosition().x * RATIO, b->GetPosition().y * RATIO);
            float angle = b->GetAngle();
            if (angle != 0.0f) {
                ball.transform = CATransform3DMakeAffineTransform ( CGAffineTransformMakeRotation(angle));   
            }
           
        }
    }
    [CATransaction commit];
}
En esta función indicamos al mapa que se actualice y para cada uno de los cuerpos actualizamos su posición a partir de los datos que nos da Box2D. Sólo recordar que CATransaction sólo está disponible desde la versión 3.0 del firmware en adelante, por tanto es necesario, como mínimo usar esta versión. Si ejecutamos este código en este momento, veremos como la bola aparece en el punto indicado y va cayendo hasta el infinito. Por tanto, vamos a poner suelo a nuestro mundo.

El suelo va a ser un elemento estático que definiremos a partir de esta imagen. Para representarla necesitaremos otra CALayer para representarla y definiremos otro cuerpo al que añadiremos otra forma de forma parecida a como hicimos antes con la bola.

UIImage *groundImg = [UIImage imageNamed: @"floor.png"];
CALayer *groundLayer = [CALayer layer];
groundLayer.anchorPoint = CGPointMake(0.5, 0.5);
groundLayer.position = CGPointMake(160, 465);
groundLayer.contents = (id) groundImg.CGImage;
groundLayer.bounds = CGRectMake(0, 0, groundImg.size.width, groundImg.size.height
[self.view.layer addSublayer: groundLayer];
    
CGSize screenSize = [[UIScreen mainScreen] bounds].size;
b2BodyDef groundBodyDef;
groundBodyDef.position.Set (screenSize.width / RATIO / 2, (screenSize.height - groundImg.size.height / 2) / RATIO);
b2Body *groundBody = world->CreateBody (&groundBodyDef);
b2PolygonDef groundShapeDef;
groundShapeDef.SetAsBox (groundImg.size.width / RATIO / 2, groundImg.size.height / RATIO / 2);
groundBody->CreateShape(&groundShapeDef);

En este caso cabe destacar que a la forma no le asignamos propiedades físicas ya que va a ser un cuerpo completamente estático y por tanto no necesita simulación.

Si ejecutamos el proyecto veremos que la bola cae y rebota contra el suelo hasta quedar parada.

Con esto termina la segunda parte del tutorial. Espero que ayude a iniciarse en el uso del motor de físicas Box2D sin necesidad de usar Cocos2D.

Si quieren descargar el proyecto, pueden aquí.

Un Saludo

IPhone Box2D Tutorial Part I: Setting Up Box2D in an IPhone XCode project

Hace un tiempo que por cuestiones de trabajo tuve que aprender a manejar este motor de simulación en un IPhone y no encontré ningún tutorial. Así que aquí aporto mi granito de arena.

Empecemos por describir el motor de físicas Box2d. Es un motor de físicas de cuerpos rígidos en 2D, escrito en C++. Por lo que es fácil incluirlo en un proyecto IPhone. Esto es lo primero que vamos a hacer. Necesitaremos el código fuente del motor, podemos descargarlo desde Google Code. Una vez descomprimido el zip hay muchas carpetas, de las que sólo nos interesan Include y Source.

Creamos un nuevo proyecto en XCode que llamaremos MyFirstBox2D, y utilizamos el template "View based application". Una vez creado, usando el Finder o Terminal copiamos la carpeta de Box2D al directorio raíz de nuestro proyecto y para enlazarlo al proyecto haremos click con el botón derecho (Ctrl + click para ratones de un sólo botón) sobre MyFirstBox2D que se encuentra en Groups & Files y elegimos Add -> Existing Files... buscamos ahora la carpeta Box2D que recientemente hemos copiado al directorio de nuestro proyecto y la añadimos. Si no lo hicimos antes, borramos todo lo que hay en la carpeta Box2D excepto las carpetas Include y Source. Dentro de la carpeta Source, también borramos el archivo Makefile.

Vamos a compilar el proyecto con el código de Box2D incluído por primera vez, para ello hay que configurar los Code Signing Identity en las propiedades del proyecto y el App Id en las propiedades del target, seleccionamos Device - 2.2.1 (o superior) y Debug en el desplegable superior izquierdo y damos a Build.

Aparecerá un montón de errores de compilación, abrimos Build Results y podemos ver el error  
error: 'finite' was not delcared in this scope 
varias veces repetido. Abrir Project -> Edit Project Settings -> Build, buscar Other C Flags y añadir -DTARGET_OS_IPHONE

Reintentamos la compilación y aparentemente parece que todo va bien, pero aún no hemos terminado. Vamos a incluir el archivo de cabecera "Box2D.h" en nuestro "MyFirstBox2DViewController.h" y compilamos. Inmediatamente aparecen errores con esta pinta:
error: initializer element is not constant
Seleccionamos TODOS nuestros archivos de implementación (.m) y  hacemos click en el icono info de la barra de herramientas, vamos a la pestaña general y en el desplegable File Type seleccionamos sourcecode.cpp.objcpp Hecho esto compilamos el proyecto y ahora ya sí tenemos configurado nuestro proyecto para usar Box2D. Sólo queda tener en cuenta repetir este último paso para todos los archivos de implementación que vayamos a añadir al proyecto.

jueves, 25 de diciembre de 2008

Partial matches against an encrypted database in 8 steps

Este es un tópico muy recurrido en muchos foros/blogs y no parece haber una respuesta unánime a cómo realizar querys a una tabla cuyos datos están cifrados. Aquí os escribo mi propuesta personal.
Éste post además va a servir para hacer una pequeña introducción a uno de mis proyectos personales.

El entorno es una aplicación de gestión para una clínica dental, en la que, debido a las restricciones establecidas por la Ley Orgánica de Protección de Datos (LOPD) donde se habla de la seguridad y privacidad de los datos personales de carácter médico, la mayoría de los datos de los pacientes se encuentra cifrada.

A la hora de consultar datos a alguna de estas tablas, surge el problema de realizar una consulta parcial, por ejemplo, obtener un listado de todos los registros cuyo apellido comience por 'A'. La sentencia sql sería de la forma 'select id, nombre, apellido from persona where apellido like 'a%';'. El resultado de esta consulta, suponiendo la tabla persona cifrada, va a ser nulo o inesperado, aunque tengamos registros que cumplen la condición de que su apellido comience por 'A'.

Inmediatamente se ocurre la posibilidad de cifrar, empleando el mismo par algoritmo-clave la condición de búsqueda: 'A' y realizar la consulta nuevamente. De la misma forma que para el caso anterior, el resultado va a ser nulo o inesperado.

Esto es así ya que el resultado de cifrar una cadena 'perro' y una cadena 'perr' no tiene nada que ver. Menos mal, porque sino la fortaleza de los algoritmos de cifrado sería prácticamente nula a los ojos de alguien con cierto conocimiento y paciencia.

Nos encontramos ante la situación de no permitir/soportar consultas parciales, reduciendo a cero el número de usuarios de nuestra aplicación o buscar otra solución.

Aquí propongo la solución de crear una tabla temporal, con el mínimo número de campos (mas el campo clave de la tabla) para satisfacer las condiciones de búsqueda para cada caso particular. Rellenarla con datos en plano extraídos de la base de datos y realizar la consulta sobre esta tabla. Una vez tengamos los identificadores de los registros que sabemos que cumplen las condiciones de búsqueda, extraemos dichos registros de la tabla original (cifrada) y podremos devolver la información requerida.

Un ejemplo práctico:

Sea una tabla persona con los siguientes campos, id (identificador del registro, clave primaria de la tabla), nombre, apellido y teléfono. La información que guarda la tabla está cifrada toda a excepción del campo identificador. Reutilizando la consulta anterior, pongamos que se pide una lista de nombre, apellidos de todas las personas cuyo apellido comience por la letra 'A'. El proceso es el siguiente.

1.- Obtener un nombre único para la tabla, para este propósito podemos obtener un timestamp del milisegundo en que se crea la tabla, o algo por el estilo.

2.- Crear la tabla temporal:
CREATE TEMPORARY TABLE (
id INTEGER PRIMARY KEY,
apellido VARCHAR(32) NOT NULL);

Nota: Si la consulta tuviera más condiciones de búsqueda, como pudiera ser que el teléfono empiece por un prefijo determinado, a esta tabla temporal habría que añadirle el campo teléfono para cumplir con todas las condiciones de búsqueda.

3.- Obtener información para la tabla temporal.
SELECT id, apellido FROM persona;

4.- Para todos los registros obtenidos, descifrar el valor del apellido.

5.- Para todos los registros que ahora tenemos, con el apellido en plano, insertarlos en la tabla temporal.

6.- En este punto podemos consultar la tabla temporal con la sentencia SELECT id from WHERE apellido LIKE 'A%';

7.- Ahora ya tenemos todos los identificadores de registro que cumplen con las condiciones de búsqueda, en este momento podemos consultar la tabla original (persona) con una sentencia de este estilo: SELECT * FROM persona WHERE id IN ();

8.- Finalmente, descifrar los datos recogidos y componer la respuesta.

Con estos sencillos pasos se puede solucionar la problemática de realizar una consulta parcial a una tabla cuyos datos se guarden cifrados.
Sólo me queda aconsejar especial atención a la hora de tomar la decisión de usar tablas con datos cifrados, ya que, como se ha visto, es un lastre importante para el rendimiento de la aplicación, que se agranda cuantos más registros tengan las tablas.
Sobre el tópico de crear tablas con datos cifrados, daré algunas directrices en futuros post.

Espero que les ayude a encontrar una solución cuando se vean obligados a emplear este tipo de tablas.
Carlos.

miércoles, 10 de diciembre de 2008

Web clinicAlonso

Después de muchas muchas horas de curro, me siento muy orgulloso de poder enseñaros la página web que he creado para la clínica dental clinicAlonso.

Esta es la primera vez que hago una página entera yo solo y en serio y la verdad, estoy muy contento con el resultado. La página está hecha en php con plantillas smarty para la presentación y javascript para lograr los efectos.

Tengo que reconocer que javascript era un desconocido para mí hasta que empecé a buscar en google formas de lograr efectos que dieran la pizca de sal a la página y que, después de lo visto, me ha gustado mucho.

Seguramente, la parte más interesante sea la cantidad de ejemplos que hay en internet que puedes usar copiando y pegando prácticamente y, parece que no, pero aprendes.

A raíz de esto, yo mismo desarrollé un script que expande y colapsa una sección con cierta latencia dando la sensación de animación. En siguientes post lo explicaré detalladamente.

Una vez hecho este script, con la euforia del momento hice otro similar para otro proyecto que conocereis en breve. En este caso, el script muestra en un pop-up una sección oculta de la página.

Muchos de vosotros seguramente habréis visto este tipo de scripts o los habréis desarrollado vosotros mismos, pero para aquellos que se encuentren en la misma situación en que yo estaba hasta hace unos meses, en futuros post los explicaré paso a paso.

Como último ya solo me queda recomendaros un paseo por la web clinicAlonso y que disfruteis de la página !!

Bienvenida

Después de mucho tiempo, por fin he sacado tiempo para ir escribiendo aquí cosillas del día a día.

Espero que este blog me sirva para ir guardando constancia de los descubrimientos y de las cosas que vaya aprendiendo en mi vida diaria, tanto en mi trabajo en Unkasoft (Salamanca)
como en mi casa con mis proyectos personales que iréis descubriendo a lo largo de los siguientes post.

Tenía muchas ganas de escribir un blog porque me siento muy orgulloso de mi progreso y quiero compartir con los lectores esas sensaciones, así como sus opiniones acerca de lo que cuento.

Espero que os guste y que, con el tiempo, este blog se convierta en un diario para muchos de vosotros que es hacia quien va dedicado.

1 Saludo.
Carlos Alonso.