Universitat Oberta de Catalunya

Detección de colisiones con lenguaje de programación Processing

En este ejemplo vamos a detectar si un objeto que llevamos literalmente “pegado” a nuestro cursor del ratón (un círculo rojo), colisiona con un número aleatorio de otros objetos (varios círculos verdes) dispuestos a lo largo de nuestro canvas. Además informaremos al usuario/a mediante texto de si se está produciendo la colisión o no. El ejemplo tiene fines didácticos  y por lo tanto no vamos a centrarnos en la más absoluta eficiencia por lo que respecta al cálculo. Eso lo podríamos hacer si ya tenemos muy claro este ejemplo precisamente. Veamos la apariencia de nuestro detector de colisiones en la figura 1.

colisiones

Figura 1. En nuestro detector de colisiones entre círculos observamos como el sistema detecta si no hay colisión (“Not Collided”, izquierda) o si la hay (“Collided”, derecha).

Se trata de crear un programa que:

  • Sitúe un círculo rojo en el lugar del ratón. De hecho el cursor se esconderá.
  • Sitúe un número determinado de círculos verdes en posiciones aleatorias.
  • Compare, si movemos el ratón, las posiciones relativas entre el círculo rojo y el resto de círculos verdes. Éstos tendrán que estar almacenados en un array (variable que contiene muchas variables) para tener acceso a sus posiciones XY en todo momento.
  • Determine si se está produciendo alguna colisión.
  • En caso afirmativo, lo reporte. Y en caso negativo, también.
  • Nos permita resituar aleatoriamente todos los círculos verdes en nuevas posiciones aleatorias de cara a seguir “jugando” en una “pantalla nueva y diferente”.

Vamos a ello pues y empecemos con la primera porción de código (código 1).

// We will test a collision detector
// by creating several circles (green)
// that might collide, or not, with
// another circle (red) that we are carrying via our mouse

// The arrays to store the 2D coordinates
// for the greenish static circles
int[] objects_x_coord;
int[] objects_y_coord;
// The amount of circles to randomly locate
int amount_objects = 10;
// The radius of our circles
int radius_circles = 10;

// The setup function to initialize everything
void setup(){
// Our window
size(400,400);
// No strokes for our rendered circles
noStroke();
// We create 2 arrays of amount_objects positions
objects_x_coord = new int[amount_objects];
objects_y_coord = new int[amount_objects];
// We initialize the content inside each position
// of the arrays with a randomly generated number
// to locate a number equal to amount_objects of circles
// in random locations
for(int counter = 0; counter < amount_objects; counter++){
objects_x_coord[counter] = (int)random(width);
objects_y_coord[counter] = (int)random(height);
}
}

Código 1. Variables e inicialización de nuestro detector de colisiones.

Observemos como iniciamos creando diversas variables globales. Además de las variables que permiten definir la cantidad de círculos verdes que deseamos generar así como el radio que deberán tener a la hora de dibujarlos (amount_objects, radius_circles), que por defecto indican 10 objetos de radio 10 píxeles, también creamos una pareja de arrays (objects_x_coord, objects_y_coord) para almacenar las coordenadas X e Y de cada uno de nuestros círculos verdes situados por la escena. Las coordenadas del círculo rojo que “viaja” con el ratón no deberán almacenarse ya que ya las tenemos (son las coordenadas del ratón que en Processing pueden obtenerse en todo momento accediendo a las variables de sistema mouseX y mouseY).

En nuestra función de inicialización setup(), creamos una ventana de 400 x 400 píxeles y eliminamos el color de borde para nuestros círculos (se trata de un criterio puramente estético que podéis variar a gusto lógicamente). A continuación, “reservamos memoria” para nuestros arrays. Observemos que al declararlos como variables globales no definimos de cuantas posiciones los queríamos. Eso mismo hacemos ahora al determinar que deberán tener un número de posiciones igual al número de círculos verdes que deseemos generar en pantalla (amount_objects). Por último utilizamos una estructura de control de tipo FOR para recorrer todas las posiciones de ambos arrays, tantas como marque amount_objects, y situar en ellas sendos números aleatorios para las coordenadas X e Y de cada círculo verde, gracias a la función random(). Tengamos en cuenta que como es lógico, las coordenadas aleatorias en X las generamos entre 0 y el ancho de ventana (width) y de igual forma, las coordenadas en Y las generamos entre 0 y el alto de ésta (height).

En la figura 2 observamos las diferentes configuraciones que pueden producirse dependiendo de los valores que demos a nuestras variables globales amount_objects y radius_circles.

figura2_diferentesConfiguracionesDeObjetosYRadios

Figura 2. Controlamos el radio de nuestros círculos y cuantos deseamos pintar a voluntad.

En la clásica función draw() de Processing, y para nuestro programa, no reside la mayor “inteligencia” de nuestro reto. La tenemos en el código 2.

// The infinite loop
void draw(){
// A black background for our canvas
background(0);
// We are to render several (amount_objects) static greenish circles
fill(0,255,0);
// We loop to render all the static circles
// because we define the radius as the variable to use (radius_circles)
// their magnitude in terms of axes is equal to radius_circles*2
for(int counter = 0; counter < amount_objects; counter++){
ellipse(objects_x_coord[counter],objects_y_coord[counter],radius_circles*2,radius_circles*2);
}
}

Código 2. Función draw() y sus funciones más bien básicas en este ejemplo.

Observemos que básicamente se trata de limpiar el fondo a cada iteración (color negro), definir el color verde como el que deseamos emplear para los círculos generados aleatoriamente y almacenados en nuestros dos arrays y, por último, utilizar una estructura de control en bucle de tipo FOR para recorrerlos (tenemos tantos como “diga” la variable amount_objects) y pintar, para cada uno, una elipse con longitud igual entre ejes (por tanto es un círculo) de valor radius_circles*2 en las posiciones XY de cada uno de nuestros objetos estáticos.

En el código 3 observamos cual es la función que realmente detecta y calcula las colisiones. Se trata del evento de ratón o callback mouseMoved, que será llamado cada vez que movamos el periférico en cualquier dirección. Es más eficiente hacerlo en este evento que dentro de la función draw() porque así sólo realizaremos los cálculos cuando realmente haya posibilidad de colisionar, es decir cada vez que movamos el ratón.

// The callback to be called by the system if the mouse moves
void mouseMoved(){
// We need several variables to evaluate
// the distance between our dynamic reddish circle
// to the rest of static greenish circles
// plus to print some text to the window
boolean collided = false;
float[] distance_between_circles;
float magnitude_of_distance;
String s;

// We initialize a float array the will
// be used when calculating the distance
// between circle's centers
distance_between_circles = new float[2];
// We evaluate the distance from our dynamic circle's center
// to the centers of all the static circles. If there's a single
// case where we detect a collision, we set the collided variable
// to true
// Colliding implies that the distance between 2 circles is less
// than the sum of their radius
for(int counter = 0; counter < amount_objects; counter++){
distance_between_circles[0] = objects_x_coord[counter] - mouseX;
distance_between_circles[1] = objects_y_coord[counter] - mouseY;
magnitude_of_distance = distance_between_circles[0]*distance_between_circles[0]+
distance_between_circles[1]*distance_between_circles[1];
magnitude_of_distance = sqrt(magnitude_of_distance);
if (magnitude_of_distance<radius_circles*2){
collided = true;
break;
}
}
// We render our dynamic reddish circle
// as it moves with the mouse
fill(255,0,0);
ellipse(mouseX,mouseY,radius_circles*2,radius_circles*2);
// We render some text in white
fill(255);
// Different text depending on what happened
if (collided){
s = "Collided :(";
text(s, 10, 370, 200, 40);
}
else{
s = "Not collided :)";
text(s, 10, 370, 200, 40);
}
}

Código 3. Callback de movimiento de ratón mouseMoved. Detecta y calcula las colisiones.

El fundamento de la detección de colisiones entre dos círculos puede observarse en la figura 3. Es sencillo y se basa en el más puro sentido común. Dos círculos empiezan a colisionar siempre y cuando las distancias entre sus centros sean inferiores a la suma de sus dos radios.

figura3_cuandoSeTocanDosCirculos

Figura 3. Cómo saber si dos círculos colisionan.

En el callback de movimiento de ratón mouseMoved es donde se realizan estos cálculos. Para calcular una distancia entre dos puntos podemos remitirnos a la Álgebra Euclidiana de forma que: la distancia entre dos puntos de coordenadas (x1,y1) y (x2,y2) se obtiene como la raíz cuadrada de (x2-x1)2+(y2-y1)2. Y ese es justamente el cálculo que realiza nuestro código 3 entre los pares de puntos que se obtienen de relacionar el centro de nuestro círculo rojo (coordenadas mouseX y mouseY) con los sucesivos centros de todos los círculos verdes (coordenadas objects_x_coord[counter] y objects_y_coord[counter]). Todo dentro de la estructura de control de tipo FOR que los recorre todos (variable counter que va de 0 a amount_objects).

En el caso que la distancia calculada (magnitude_of_distance) sea inferior a la suma de los dos radios (como en este caso todos son iguales hacemos radius_circles*2), dispondremos que se ha producido una colisión y por lo tanto activaremos nuestra variable de tipo Boolean (variable collided pasa a ser true).

Después del proceso de detección de colisión, procedemos a pintar nuestro círculo rojo como una elipse centrada en las coordenadas del ratón. También imprimimos un texto en pantalla en color blanco que, dependiendo del valor de la variable collided, será uno u otro.

Terminamos con el código 4, que fundamentalmente controla el evento de clic de ratón para que en caso de accionarlo, recalcule las posiciones de todos nuestros círculos verdes tal y como ya hicimos al inicializar en la función setup().

// The callback to be called by the system if the user
// clicks the mouse
void mousePressed(){
// We will re-generate all static circle's positions
for(int counter = 0; counter < amount_objects; counter++){
objects_x_coord[counter] = (int)random(width);
objects_y_coord[counter] = (int)random(height);
}
}

Código 4. Callback de clic de ratón mousePressed. Si hacemos clic se recalculan las posiciones.

En el código 5 podemos observar el programa al completo.

// We will test a collision detector
// by creating several circles (green)
// that might collide, or not, with
// another circle (red) that we are carrying via our mouse

// The arrays to store the 2D coordinates
// for the greenish static circles
int[] objects_x_coord;
int[] objects_y_coord;
// The amount of circles to randomly locate
int amount_objects = 10;
// The radius of our circles
int radius_circles = 10;

// The setup function to initialize everything
void setup(){
// Our window
size(400,400);
// No strokes for our rendered circles
noStroke();
// We create 2 arrays of amount_objects positions
objects_x_coord = new int[amount_objects];
objects_y_coord = new int[amount_objects];
// We initialize the content inside each position
// of the arrays with a randomly generated number
// to locate a number equal to amount_objects of circles
// in random locations
for(int counter = 0; counter < amount_objects; counter++){
objects_x_coord[counter] = (int)random(width);
objects_y_coord[counter] = (int)random(height);
}
}

// The infinite loop
void draw(){
// A black background for our canvas
background(0);
// We are to render several (amount_objects) static greenish circles
fill(0,255,0);
// We loop to render all the static circles
// because we define the radius as the variable to use (radius_circles)
// their magnitude in terms of axes is equal to radius_circles*2
for(int counter = 0; counter < amount_objects; counter++){
ellipse(objects_x_coord[counter],objects_y_coord[counter],radius_circles*2,radius_circles*2);
}
}

// The callback to be called by the system if the mouse moves
void mouseMoved(){
// We need several variables to evaluate
// the distance between our dynamic reddish circle
// to the rest of static greenish circles
// plus to print some text to the window
boolean collided = false;
float[] distance_between_circles;
float magnitude_of_distance;
String s;

// We initialize a float array the will
// be used when calculating the distance
// between circle's centers
distance_between_circles = new float[2];
// We evaluate the distance from our dynamic circle's center
// to the centers of all the static circles. If there's a single
// case where we detect a collision, we set the collided variable
// to true
// Colliding implies that the distance between 2 circles is less
// than the sum of their radius
for(int counter = 0; counter < amount_objects; counter++){
distance_between_circles[0] = objects_x_coord[counter] - mouseX;
distance_between_circles[1] = objects_y_coord[counter] - mouseY;
magnitude_of_distance = distance_between_circles[0]*distance_between_circles[0]+
distance_between_circles[1]*distance_between_circles[1];
magnitude_of_distance = sqrt(magnitude_of_distance);
if (magnitude_of_distance<radius_circles*2){
collided = true;
break;
}
}
// We render our dynamic reddish circle
// as it moves with the mouse
fill(255,0,0);
ellipse(mouseX,mouseY,radius_circles*2,radius_circles*2);
// We render some text in white
fill(255);
// Different text depending on what happened
if (collided){
s = "Collided :(";
text(s, 10, 370, 200, 40);
}
else{
s = "Not collided :)";
text(s, 10, 370, 200, 40);
}
}

// The callback to be called by the system if the user
// clicks the mouse
void mousePressed(){
// We will re-generate all static circle's positions
for(int counter = 0; counter < amount_objects; counter++){
objects_x_coord[counter] = (int)random(width);
objects_y_coord[counter] = (int)random(height);
}
}

Código 5. Programa de detección de colisiones con Processing al completo.

Deja un comentario