Simulación de Ecosistemas

En este apartado se verá como construir agentes independientes que simulen el comportamientos de organismos. Aquí sólo trataremos el tema del movimiento y nuetra finaidad es lograr tener organismos virtuales que sean capaces de manejarse en forma autónoma y que expongan cierta naturalidad en su movimiento. también veremos como implementar el espacio de la escena de la mejor forma para aprovechar los recursos del sistema.


Organismos y autonomía
Ejemplo Vida 00

Como ya se ha dicho, aquí trataremos la creación de varios organismos independientes unos de otros. La autonomía de estos organismos se logra a través de la creación de una clase Organismo, la cual permite crear las variables internes necesarias para tener registro de la evolución de cada individuo por separado. A la hora de programar necesitamos que tratamientos homogéneos a la hora del diseño, manifiesten autonomía e independencia a la hora de la ejecución. esto se logra creando un arreglo de objetos de tipo Organismo. Así desde el código principal del programa se les pide a cada organismo las mismas acciones, pero cada organismo responde a estas ordenes según su propia historia.

A continuación podemos ver el código de la clase ogranismo:

class Organismo{

  float x, y; //posicion del organismo

  //----------------------------  

  Organismo(  float x_ , float y_ ){ //inicializa el organismo
    iniciar( x_ , y_ ); 
  }
  //----------------------------  

  Organismo( ){ //inicializa el organismo
    iniciar( random(width) , random(height) ); //si no recibe parametros inicia con x e y al azar
  }
  //----------------------------  

  void iniciar( float x_ , float y_ ){  //inicialización del organismo
    x = x_;
    y = y_;
  }
  //----------------------------  

  void dibujar(){  //dibuja el organismo
    rectMode(CENTER);
    rect(x,y,10,10);
  }

}

    				

Hecho en Processing


En esta primera implementación de la clase Organismo, un organismo posee las variables necesarias para ubicarlas en un espacio bidimensional (x e y). Por el momento lo único que pueden hacer nuestro organismos es ocupar un lugar en el espacio. El código que ejecuta las acciones se muestra a continuación:

Organismo[] animales; //Un arreglo de animales
int cantAnimales; //Define la cantidad de animales
void setup(){

    ...

    cantAnimales = 20; //Se establece la cantidad de animales
    iniciar(); //ejecuta la inicialización

    ...

}
void draw(){

    ...

    for(int i=0;i$lt;cantAnimales;i++){ //se recorre cada animal y

        animales[i].dibujar(); //dibuja cada animal

    }
    ...

}
...
void iniciar(){

    animales = new Organismo[cantAnimales]; //Se inicia el arreglo
    for(int i=0;i$lt;cantAnimales;i++){ //se recorre cada animal

        animales[i] = new Organismo(); // se inicia cada animal

    }

}

        	
        

Hecho en Processing


Los puntos suspensivos indican que existe código que no está siendo mostrado.

Como muestra el ejemplo anterior: 1) la cantidad de organismos es de 20, que es el valor de la variable cantAnimale. 2) Dos ciclos for son los encargados de ordenar los comportamientos de los organismos, por ejemplo, cuando se les envía a inicializarse con el constructor de la clase ( animales[ i ] = new Organismo( ) ), o cuando se les pide que se dibujen ( animales[ i ].dibujar( ) ).

Como dijimos anteriormente, las ordenes generales tiene un tratamiento homogéneo, por ejemplo se les pide a todos los organismos, que se dibujen; pero la forma en que cada organismo responde a esta acción depende de su propia historia. En este caso la historia está dado por la inicialización, que determina una posición al azar:

...

Organismo( ){ //inicializa el organismo

	iniciar( random(width) , random(height) );//si no recibe parametros inicia con x e y al azar

}

void iniciar( float x_ , float y_ ){ //inicialización del organismo

	x = x_;
	y = y_;

}

...

        

Hecho en Processing



Movimiento y desplazamiento

Ejemplo Vida 01

Una vez que hemos ubicado nuestro organismos en diferentes posiciones, es momento de que cada organimo se mueva. la forma de implementar este movimiento, es declarando dos variable dx y dy que representan los desplazamentos en x e y, respectivamente. Así se les asigna valor al azar al inicio, para luego ser usadas en la acción mover:

class Organismo{

	...

	float dx,dy; //desplazamiento en x e y

	...

	void iniciar( float x_ , float y_ ){ //inicialización del organismo
	
	    x = x_;
	    y = y_;
	    dx = random(-10,10);
	    dy = random(-10,10);
	
	}

	void mover(){ //actualiza la ubicación del organismo
	
	    x += dx; //aplica los desplazamiento
	    y += dy; //aplica los desplazamiento
	
	}

	...
}
      		

Hecho en Processing


El problema que surge en este ejempo es que la velocidad varía según la conjunción de los valores de dx y dy. Además, los valores de estos desplazamiento, nos dificulta prever la dirección exacta en la que avanzará un organismo. Seria deseable poder controlar la velocidad independientemente de la dirección, y viceversa.



Coordenadas polares y rectangulares

Ejemplo Vida 02

Cuando hablamos de movimiento en informática tenemos que hablar de la representación del movimiento y por ende tenemos que hablar del sistema de coordenadas que se utiliza. El sistema de Coordenadas Cartesianas (ver en Wikipedia), también llamado Coordenadas Rectangulares, permite utilizar las distancia con respecto a dos ejes (X e Y) para definir un punto en el espacio bidimensional.

Este sistema de coordenadas es muy útil para representar posiciones. Nos resulta intuitivo trabajar con este sistema sin embargo no nos es tan útil a la hora de pensar movimientos, dado que en general, a la hora de pensar el movimiento, pensamos en dirección y velocidad. Afortunadamente existe un tipo de cordenadas que pueden representar este tipo de variables, son las Coordenadas Polares (ver en Wikipedia). En este sistema, un punto en el espacio se representa como la distancia respceto a un punto de origen, medida como un ángulo y una distancia .

Debido a que la computadora domina un sistema de Coordenadas Rectangulares, pero a nuestro fin, nos es más útil el sistema de Coordenadas Polares, es necesario contar con un sistema de transformación de un sistema a otro. Las ecuaciones que permiten dicha transformación son las siguientes:

 

x = distancia * cos( angulo );
y = distancia * sin( angulo );
        

Hecho en Processing


En el ejemplo Vida 02, el ángulo está representado por la dirección y la distancia por la velocidad. En vez de x e y se emplean dx y dy, dado que en este caso, las primeras representan la posición mientras que dx y dy los respectivos desplazamientos en cada uno de estos ejes. Así en el ejemplo la operación sería:

dx = velocidad * cos(direccion);  
dy = velocidad * sin(direccion);
x += dx;
y += dy; 
          

Hecho en Processing


De esta forma, se agregaron en la clase organismo las variables y operaciones necesarios para trabajar con velocidad y dirección:

class Organismo{

    ...
    
    float x, y; //posicion del organismo
    float direccion; //dirección en la que avanza
    float velocidad; //velocidad a la que avanza
    float dx,dy; //desplazamiento en x e y deducido de la dirección y velocidad.
    
    ...

    void iniciar( float x_ , float y_ ){ //inicialización del organismo

        x = x_;
        y = y_;
        direccion = random(TWO_PI); //inicia con una dirección al azar
        velocidad = 5; //inicia la velocidad en 5 pixels por fotograma

    }

    void mover(){ //actualiza la ubicación del organismo

        dx = velocidad * cos(direccion); //deduce el desplazamiento en X
        dy = velocidad * sin(direccion); //deduce el desplazamiento en Y
        x += dx; //aplica los desplazamiento
        y += dy; //aplica los desplazamiento

    }

    ...
    
}
      		

Hecho en Processing



Variación azarosa de dirección

Ejemplo Vida 03

En este ejemplo se ha agregado un función que permite hacer una variación angular de la dirección. Es decir, en cada paso se varía levemente la dirección para que la trayectoria de nuetros organismo sea más natural. Esto se hace tirando un número al azar (en realidad un número pseudo-aleatorio) y sumando ese valor a la dirección:

      		
class Organismo{

    ...
    
    void variarAngulo( float amplitud ){ //varia la dirección con una amplitud determinada

        float radi = radians( amplitud ); //transforma los grados en radianes
        direccion += random( -radi , radi ); //aplica un valor al azar dentro del rango

    }
    
    ...
    
    void mover(){ //actualiza la ubicación del organismo

        variarAngulo( 30 ); //caria la direccion en un rango de 30 grados para cada lado
        dx = velocidad * cos(direccion); //deduce el desplazamiento en X
        dy = velocidad * sin(direccion); //deduce el desplazamiento en Y
        x += dx; //aplica los desplazamiento
        y += dy; //aplica los desplazamiento

    }
 
    ...

}      		
      	

Hecho en Processing


Es importante destacar que los ángulos en informática se miden en radianes (ver en Wikipedia), sin embargo a nosostros nos resulta más sencillo manejarnos con grados. Debido a eso la función void variarAngulo( float amplitud) recibe el parámetro amplitud expresado en grados, pero interamente convierte este ángulo en radianes con la función de Processing radians( ).

Para aplicar la función variarAngulo se la invoca desde dentro de la acción mover, pasándole como parámetro el valor 30 (en este caso), lo que significa que la dirección puede variar hasta 30 grados en ambos sentidos (horario y antihorario).



Espacio toroidal

Ejemplo Vida 04

En este ejemplo se implementó un espacio toroidal, esto es un espacio continuo en el sentido vertical y horizontal. este término viene de la curva llamada toroide (ver en Wikipedia). Sencillamente, esto significa que cuando algo sale de la escena por el borde derecho, entonces reaparece por el izquierdo, lo mismo sucede en sentido contrario, es decir que cuando sale por la izquierda vuele a ingresar por la derecha, y así con los bordes superior e inferior. De esta forma la escena no tiene límite (o mejor dicho sus límites se tocan), como cuando un circunda una esfera (como nuestro planeta).

La forma en que se realiza esto revisando si los organismos se salen de los bordes, y entonces haciéndolos reingresar por el borde opuesto:

class Organismo{

    ...
    void mover(){ //actualiza la ubicación del organismo

        variarAngulo( 30 ); //caria la dirección en un rango
        // de 30 grados para cada lado
        dx = velocidad * cos(direccion); //deduce el desplazamiento en X
        dy = velocidad * sin(direccion); //deduce el desplazamiento en Y
        x += dx; //aplica los desplazamiento
        y += dy; //aplica los desplazamiento

        if( toroidal ){ //si el espacio es toroidal
        // entonces revisa si se pasó de límite

            x = ( x>width ? x-width : x );
            x = ( x<0 ? x+width : x );
            y = ( y>height ? y-height : y );
            y = ( y<0 ? y+height : y );

        }

    }
    ...

}        		
        	

Hecho en Processing


En el código anterior se utiliza una variable booleana para poder configurar el espacio como toroidal o no.