Otros sitios...

Búsqueda

Antispam

Functores en C++

May 26th, 2008 by Jorge Machin

Los functores o functors en C++ son una construcción del lenguaje que nos permite usar objetos como si fueran funciones. Esto es muy útil cuando se esta programando callbacks, interpretes, compiladores y metaprogración.

Una forma de usarlos es encapsulandolos en una clase:

class Function1 {

  public:

    // Default constructor

    Function1() { std::cout <<"Function1 constructor" <<std::endl; }

    // Virtual destructor

    ~Function1() { std::cout <<"Function1 destructor" <<std::endl; }

    //  Function call:

    void operator() (int a) const {
        std::cout <<"operator(): " <<a <<std::endl;
    }
};

La cual podemos usar así:

Function1 function1;
function1();

El lector se puede imaginar que una de las propiedades de los functores es que al agregarle miembros a la clase, estos pueden mantener su estado entre cada una de las llamadas. Sin embargo, es muy probable que la utilidad de los functores se pueda apreciar más con un ejemplo práctico:

Manejador de eventos usando functores

Un manejador de eventos es un subprograma que al recibir un evento, lo comunica a todas los subprogramas que estén subscritos a él. El código fuente es el siguiente:

#include <iostream>
#include <list>
#include <string>

// Una clase base para hacer clases con callbacks:

class callbackbase {

   public:

      virtual void operator()() const { std::cout <<"callbackbase()" <<std::endl; };
      virtual ~callbackbase() { };

};

// El functor que implementa la funcionalidad del callback:

template <class T> class callback : public callbackbase {

   public:

      typedef void (T::*Func)();

      callback( T& t, Func func ) : object(&t), f(func) { }

      void operator()() const { (object->*f)(); }

   private:

      T* object;

      Func f;
};

template<typename T> callback<T> *make_callback( T& t, void (T::*f) () ) {

  return new callback<T>( t, f );

}

// Los tipos de eventos que soportamos:

enum notifications {

   NOTIFICATION_ONE,
   NOTIFICATION_TWO,
   NOTIFICATION_MAX

};

// Nuestro Manejador de Eventos:

class EventManager {

   public:

      void registerHandler( callbackbase *function, enum notifications notification) {

         functions[notification].push_back( function );

      }

      void notifyHandlers( enum notifications notification ) {

         for ( std::list<callbackbase*>::iterator iter = functions[notification].begin(); iter != functions[notification].end(); iter++ ) {

            (*(*iter))();

         }

      }

   private:

      std::list<callbackbase *> functions[NOTIFICATION_MAX];

};

// Clases de ejemplo

class Class1 {

   public:

      Class1( const std::string &instance ) : instance( instance ) { };

      void function1() { std::cout <<"class1::function1: " <<instance <<std::endl; };

      void function2() { std::cout <<"class1::function2: " <<instance <<std::endl; };

   private:

      std::string instance;

};

class Class2 {

   public:

      void function1() { std::cout <<"class2::function1" <<std::endl; };

};

class Class3 : public callbackbase {

   public:

     virtual void operator()() const { std::cout <<"class3::function1" <<std::endl; };

};

//

int main() {

   // Clases que van a recibir los eventos:

   Class1 class1a("1a");
   Class1 class1b("1b");
   Class2 class2;
   Class3 class3;

   // Creamos nuestro manejador de eventos:

   EventManager eventManager;

   // Creamos al vuelo callbacks a funciones de clases que no heredan de callbackbase:

   eventManager.registerHandler( make_callback( class1a, &Class1::function1 ), NOTIFICATION_ONE );

   eventManager.registerHandler( make_callback( class1a, &Class1::function2 ), NOTIFICATION_ONE );

   eventManager.registerHandler( make_callback( class1b, &Class1::function1 ), NOTIFICATION_ONE );

   eventManager.registerHandler( make_callback( class2, &Class2::function1 ), NOTIFICATION_ONE );

   // Registramos una clase que hereda callbackbase:

   eventManager.registerHandler( &class3, NOTIFICATION_ONE );

   // Lanzamos un evento a nuestras funciones:

   eventManager.notifyHandlers( NOTIFICATION_ONE  );

   return 1;

}

Al ejecutarlo, obtenemos la siguiente salida:

class1::function1: 1a
class1::function2: 1a
class1::function1: 1b
class2::function1
class3::function1

Obviamente al programa le falta poder retirar callbacks del manejador de eventos y su administración de memoria, pero sólo busca servir como ejemplo del funcionamiento de los functores.

Hay que aclarar que este mismo programa se puede implementar usando apuntadores a funciones aunque se disminuye un poco el enfoque orientado a objetos.

Posteado en C/C++ | No hay comentarios »

Aparatos Biomachin

May 23rd, 2008 by Jorge Machin

Mi papá continúa diseñando y fabricando aparatos para laboratorio desde en su taller en Michoacán. Estos son tres de los productos que hay en su cátalogo:

Mezclador de sangre
Mezclador de tubos

Rotor de placas
Rotor de placas

Contador de Células
Contador de células

Por cierto, todos estos aparatos están a la venta, por lo que si están interesados en comprarlos, pueden preguntar conmigo en lo que pongo los datos completos en esta página.

Posteado en Biomachin, Personal | 3 Comentarios »

Proyección geométrica plana (Parte I)

May 18th, 2008 by Jorge Machin

Cuando era niño, programaba juegos en mi compú donde todo vivía en un mundo de dos dimensiones. No estaba tan mal porque las gráficas en ese entonces eran muy sencillas. De hecho, una nave espacial solía ser un aburrido cuadro rojo. Sin embargo, conforme fuí aprendiendo física, me fue necesario construir un mundo tridimensional, no sólo para hacer juegos, sino también para hacer mis cálculos y simulaciones.

Antes de consultar libros sobre el tema, intenté resolver el problema por mi cuenta. Yo lo visualizaba como convertir un espacio 3D (el mundo) a un espacio 2D (la pantalla). La manera más sencilla que se me ocurrió fue simplemente proyectar todos los puntos a una pared imaginaria en frente de mí. Las proyecciones podían ser de forma recta ignorando simplemente el eje Z; pero así no es muy atractivo porque no se crea la sensación de perspectiva. Es mejor proyectar con un ángulo imaginando que es el ángulo de visión del ojo. Mi primera representación (aplanada) fue esta:


Diagrama 1

Nuestro observador esta en el origen y mirando hacia Z, P es el punto que queremos proyectar, X es el punto proyectado y d es la distancia a nuestra pantalla imaginaria.

Ahora, ni modo, a hacer álgebra. Por triángulos semejantes tenemos:

frac { X_p } { sqrt { d^2 + {X_p}^2} } = frac { P_x } { sqrt { {P_x}^2 + {P_z}^2}

Haciendo un lado los pasos engorosos, despejamos Xp:

X_p = frac { P_x} { P_z } d

Por analogía, encontramos Yp:

Y_p = frac { P_y} { P_z } d

De las dos ecuaciones anteriores, es fácil deducir que d es matemáticamente un factor de escala y que al usar estas ecuaciones, los objetos se ven más grandes conforme se acercan al observador y reducen su tamaño al alejarse; sin importar si nuestros puntos se encuentran antes o después del punto d.

Pero no hemos terminado; nuestra pantalla hasta ahora esta en "coordenadas mundiales" las cuales no tienen porque concordar con las coordenadas de nuestra pantalla. De hecho, las coordenadas de nuestra pantalla son siempre positivas y el origen esta en la esquina superior izquierda. Es necesario hacer la transformación de coordenadas mundiales a coordenadas de la pantalla.


Diagrama 2

Con un poco de observación, se puede encontrar el factor de conversión para el eje x:

S_x = S_{xmin} + ( S_{xmax} - S{xmin} ) frac { X_p - W_L} { W_R - W_L }

En la mayoría de los casos se puede suponer para Sxmin un valor de cero:

S_x = S_{xmax}  frac { X_p - W_L} { W_R - W_L }

Si queremos manejar la pantalla mostrando 4 cuadrantes, entonces W_R = -W_L:

S_x = S_{xmax}  frac { X_p + W_L} { 2 W_R }

De forma similar para el caso del eje y:

S_y = S_{ymin} + ( S_{ymax} - S{ymin} ) frac { W_T - Y_P} { W_T - W_B }

De igual forma, se puede suponer para Symin un valor de cero:

S_y = S_{ymax} frac { W_T - Y_P} { W_T - W_B }

Para acomodar en cuatro cuadrantes, W_B = -W_T:

S_y = S_{ymax} frac { W_T - Y_P} { 2 W_T }

Conclusiones:

Ya con esto podemos armar una ciudad de casas de cerrillos con fórmulas con operaciones muy sencillas. Incluso corren relativamente rápido en las antigüas máquinas de 8 bits:


Aplicando las formulas en una computadora de 0.895 MHz

Las fórmulas presentadas aquí son muy limitadas, pero demuestran lo fácil que puede ser trabajar la tercera dimensión. En posts posteriores se trabajarán más para quitar las restricciones autoimpuestas como que el plano de proyección este únicamente perpendicular el eje Z y que el centro de proyección se encuentre únicamente en el origen.

Posteado en 3D, Color Computer, Matemáticas, Maxin Lab | No hay comentarios »

Emulando a la Color Computer 3 (parte IV)

May 4th, 2008 by Jorge Machin

Aunque con un emulador es suficiente usar imágenes de diskettes para trabajar o jugar un rato, no hay nada mejor que tener un sistema con un disco duro donde podamos ordenar en un solo lugar todos nuestros programas y sentir que nuestra CoCo podría rivalizar en funcionalidad a nuestra PC... Bueno, debo de reconocer que a veces algunas cosas me apasionan más de la cuenta, pero la Color Computer es considerada por muchos como la computadora de 8 bits más poderosa que ha existido y si la han utilizado sabrán a que me refiero.

Agregando un disco duro a nuestro emulador

La Color Computer real manejaba diskettes por medio de una "disketera" conectada por medio de un cartucho, el cual tenía un ROM con un "sistema operativo de disco - DOS" que extendía el lenguaje BASIC con comandos y rutinas en lenguaje de máquina que daban el soporte básico para el manejo de archivos en diskettes de 5.25 pulgadas (de un sólo lado).

Que yo sepa, la Radio Shack nunca sacó a la venta discos duros para la Color Computer; sin embargo, otras compañías diseñaron interfaces para soportarlos con versiones adaptadas de su sistema operativo de disco.

Por lo tanto, de igual forma debemos sustituir el Disk Extended Color Basic en el archivo zip con los ROMs en el emulador; es decir, debemos cambiar el archivo DISK11.ROM en el archivo coco3.zip por uno que nos de soporte para disco duro. Afortunadamente, Robert Gault, tiene preparado en su página de Internet el ROM RGBDOS modificado para usarse en emuladores y todas las herramientas necesarias para echar andar nuestro disco duro virtual. La página de Internet de Robert Gault donde se encuentra el RGBDOS es http://home.att.net/~robert.gault/Coco/Downloads/Downloads.htm

Una vez bajado el archivo RGBDOS.ZIP es necesario quitarle los dos primeros bytes al archivo DISK.ROM porque está preparado para el emulador de Jeff Vavasour. El nuevo archivo DISK.ROM debe sustituir al archivo DISK11.ROM (DECB) dentro el archivo coco3h.zip.

Al iniciar MESS, se comparan la checksums de los ROMs, por lo que es muy probable que nos marque un error de CRC al correrlo la primera vez. En la configuración del archivo se puede modificar para evitar el warning. La pantalla de inicio, ahora es esta:

RGB-DOS
El RGB-DOS despliega un nuevo mensaje de bienvenida

El siguiente paso es crear el archivo donde se va a contener nuestro disco virtual. Esto se hace con la opción Devices/Virtual Hard Disk/Create y dándole un nombre al archivo. Al reiniciar, ya no aparecerá el mensaje de error que nos informa sobre la falta del disco duro.

Si queremos que el disco duro se monte automáticamente cada que iniciemos MESS, debemos agregar su ruta al archivo de configuración cfg/coco3h.cfg.

Ahora para inicializar el disco duro seguimos los siguientes pasos:

Paso 1: Montamos el disco TOOLS.dsk en la unidad de diskettes 0 y el NitrOS9boot.os9 en la unidad de diskettes 1. Estas dos imagenes de disco las podemos encontrar en el archivo RGBDOS.ZIP.

Paso 2: Accedemos a los diskettes ejecutando el comando DRIVE OFF.

Paso 3: Cargamos y ejecutamos el programa CREATE.BAS (LOAD "CREATE.BAS" y RUN ). Después de unos minutos nos habrá creado 255 diskettes virtuales y aumentado nuestro archivo vhd a 128 MB aproximadamente. Cada disco virtual es accesible con el comando DRIVE.

Paso 4: Sustituimos el disco TOOLS.dsk por nuestro disco de Sistema de NitrOS-9. Activamos nuevamente las unidades de diskette con DRIVE ON y booteamos desde él con el comando DOS.

Paso 5: Formateamos el disco duro con los siguientes comandos:

load /d1/hdrive
iniz /h0
format /h0

Paso 6: Se copian los archivos del disco del sistema con la siguiente orden:

chd /d0; dsave -s48 /h0 ! shell -p

Paso7: Reseteamos y ahora nos aparecerá un menú del cual abandonamos con la tecla escape. Colocamos nuevamente el disco Tools.dsk en la unidad de discos 0 y ejecutamos el programa link.bas dando como parámetro el número 254 para el número de disco de arranque.

Paso 8: Probamos bootear NitrOS-9 con el comando DOS 254 y ¡ya tenemos nuestro disco duro!.

Posteado en Color Computer, Emuladores | 1 Comment »

Tiro parabólico en tres dimensiones

May 3rd, 2008 by Jorge Machin

El tiro parabólico es indispensable para todo juego que maneje balística. Su ecuación en su caso más simple en dos dimensiones la conocemos desde nuestros primeros estudios de física en la secundaria o la preparatoria. Ahora que esta muy de moda los juegos en tres dimensiones es bueno tener a la mano la ecuación parametrizada:

x(t) = x_0 + v cos(alpha)  cos(beta) t

y(t) = y_0 + v cos(alpha)  sen(beta) t

z(t) = z_0 + v sen(alpha)  t - frac { g t^2 } { 2 }

Tiro Parabólico en 3D

En lo único que cambia es la proyección extra (con un nuevo ángulo) y obviamente la tercera variable espacial. Me fue muy útil para un videojuego de baloncesto.

Posteado en 3D, Física, Matemáticas | No hay comentarios »