Introducción a Processing 1


Parte 15 Movimientos mecánico y orgánico



Descargar 1,94 Mb.
Página10/11
Fecha de conversión04.03.2017
Tamaño1,94 Mb.
1   2   3   4   5   6   7   8   9   10   11
Parte 15 Movimientos mecánico y orgánico
  • Movimiento mecánico 1/4
  • Generalmente asociamos al movimiento mecánico con aquel producido por diversos dispositivos: el péndulo, el metrónomo, el reloj, el pistón, etc.
  • Todos se caracterizan por el ritmo regular, la repetición y la eficiencia.
  • La función sin() se utiliza regularmente para producir un movimiento elegante.
  • La misma puede generar un movimiento de velocidad variable (más lento en los extremos y más rápido en el centro) como podemos ver en la figura siguiente:
  • Movimiento mecánico 2/4
  • El siguiente ejemplo utiliza la función sin() para poner en movimiento un círculo:
    • float angulo = 0.0; // Ángulo actual
    • float velocidad = 0.1; // Velocidad del movimiento
    • float rango = 30.0; // Rango del movimiento
    • void setup() {
    • size(100, 100);
    • noStroke();
    • smooth();
    • }
    • void draw() {
    • fill(0, 20);
    • rect(0, 0, width, height);
    • fill(255);
    • angulo += velocidad;
    • float sinval = sin(angulo);
    • float yoffset = sinval * rango;
    • ellipse(50, 50 + yoffset, 40, 40);
    • }
  • Movimiento mecánico 3/4
  • A continuación veremos otro ejemplo que utiliza una combinación de funciones sin() y cos() para generar, mediante ecuaciones más elaboradas, movimientos visualmente más complejos:
    • float angulo = 0.0; // Ángulo actual
    • float velocidad = 0.05; // Velocidad del movimiento
    • float rango = 30.0; // Rango del movimiento
    • float sx = 1.0;
    • float sy = 2.0;
    • void setup() {
    • size(100, 100);
    • noStroke();
    • smooth();
    • }
    • // *** continúa ***
  • Movimiento mecánico 4/4
    • void draw() {
    • fill(0, 4);
    • rect(0, 0, width, height);
    • angulo += velocidad; // Actualiza ángulo
    • float sinVal = sin(angulo);
    • float cosVal = cos(angulo);
    • // Configuro la posición de círculo pequeño basado
    • // en los valores nuevos de sin() y cos()
    • float x = 50 + (cosVal * rango);
    • float y = 50 + (sinVal * rango);
    • fill(255);
    • ellipse(x, y, 2, 2); // Dibujo círculo pequeño
    • // Configuro la posición de círculo grande basado
    • // en la nueva posición de círculo pequeño
    • float x2 = x + cos(angulo * sx) * rango/2;
    • float y2 = y + sin(angulo * sy) * rango/2;
    • ellipse(x2, y2, 6, 6); // Dibujo círculo grande
    • }
  • Fase 1/3
  • La fase de una función corresponde a una iteración completa a través de todos sus valores posibles.
  • El desplazamiento de fase ocurre cuando se comienza a recorrer una función desde un lugar diferente al inicial:
  • El desplazamiento de fase del ángulo usado para generar valores de la función sin() provee la misma secuencia de números, pero compensados en distintos cuadros de la animación.
  • Fase 2/3
    • float angulo = 0.0;
    • float velocidad = 0.1;
    • void setup() {
    • size(100, 100);
    • noStroke();
    • smooth();
    • }
    • void draw() {
    • background(0);
    • angulo += velocidad;
    • ellipse(50 + (sin(angulo + PI) * 5), 25, 30, 30);
    • ellipse(50 + (sin(angulo + HALF_PI) * 5), 55, 30, 30);
    • ellipse(50 + (sin(angulo + QUARTER_PI) * 5), 85, 30, 30);
    • }
  • Fase 3/3
    • float angulo = 0.0; // Ángulo
    • float velocidad = 0.05; // Velocidad de crecimiento
    • void setup() {
    • size(100, 100);
    • noStroke();
    • smooth();
    • fill(255, 180);
    • }
    • void draw() {
    • background(0);
    • circuloFase(0.0);
    • circuloFase(QUARTER_PI);
    • circuloFase(HALF_PI);
    • angulo += velocidad;
    • }
    • void circuloFase(float fase) {
    • float diameter = 65 + (sin(angulo + fase) * 45);
    • ellipse(50, 50, diameter, diameter);
    • }
  • Movimiento orgánico 1/12
  • Algunas exploraciones a través de software hechas en los últimos veinte años han servido para modelar diversos tipos de comportamientos de movimientos orgánicos.
  • Ejemplos de movimiento orgánico incluyen: la caída de una hoja, el camino recorrido por un insecto, el vuelo de un pájaro, la respiración de una persona, el fluir de un río, el desplazamiento del humo en el aire, etc.
  • Este tipo de movimiento es considerado idiosincrático y estocástico.
  • The Boids, software creado por Craig Reynolds en 1986, simula el comportamiento de movimiento colectivo de aves y peces posibilitando así nuevas comprensiones de estos tipos de movimientos emergentes.
  • Evolved Virtual Creatures, realizado por Karl Sims en 1994, presenta un trabajo donde criaturas virtuales construidas a partir de bloques rectangulares se disponen en abierta competencia entre sí, a partir de movimientos de tipo orgánico. Es notable apreciar cómo diversos sujetos de observación describen sensaciones de cualidades emotivas al percibir sus acciones.
  • Movimiento orgánico 2/12
  • A continuación veremos un ejemplo de movimiento browniano, llamado así en honor al reconocido botánico Robert Brown. Es un tipo de movimiento estocástico, muy variable, que originalmente fue relacionado al tipo de movimiento realizado por las diminutas partículas suspendidas en el aire.
  • Este movimiento puede ser simulado mediante software determinando, en cada cuadro, una nueva posición para una partícula, sin preferencia determinada de la dirección del movimiento. Si dejamos el rastro de las posiciones anteriores nos permitirá ver el recorrido realizado en el espacio:
  • Movimiento orgánico 3/12
    • float x = 50.0; // coordenada X
    • float y = 80.0; // coordenada Y
    • void setup() {
    • size(100, 100);
    • randomSeed(0); // Fuerza los mismos valores aleatorios
    • background(0);
    • stroke(255);
    • }
    • void draw() {
    • x += random(-2, 2); // Asigna nueva coordenada X
    • y += random(-2, 2); // Asigna nueva coordenada Y
    • point(x, y);
    • }
  • Movimiento orgánico 4/12
  • Las funciones sin() y cos() pueden ser usadas para crear un movimiento impredecible cuando se emplean junto a la función random(). El siguiente ejemplo presenta una línea –con una posición y dirección determinadas– que en cada cuadro cambia levemente su dirección mediante un rango aleatorio pequeño entre -0.3 y 0.3:
  • Movimiento orgánico 5/12
    • float x = 0.0; // coordenada X
    • float y = 50.0; // coordenada Y
    • float angulo = 0.0; // Dirección de movimiento
    • float velocidad = 0.5; // Velocidad de movimiento
    • void setup() {
    • size(100, 100);
    • background(0);
    • stroke(255, 130);
    • randomSeed(121); // Fuerza los mismos valores aleatorios
    • }
    • void draw() {
    • angulo += random(-0.3, 0.3);
    • x += cos(angulo) * velocidad; // Asigna nueva coordenada X
    • y += sin(angulo) * velocidad; // Asigna nueva coordenada Y
    • translate(x, y);
    • rotate(angulo);
    • line(0, -10, 0, 10);
    • }
  • Movimiento orgánico 6/12
  • En este otro ejemplo la variable angulo cambia para producir un movimiento de balanceo. Dado que los ángulos para cada figura se acumulan con cada unidad, las figuras más grandes –las que tienen más unidades– se balancean de lado a lado generando una curvatura mayor:
  • Movimiento orgánico 7/12
    • float inc = 0.0;
    • void setup() {
    • size(100, 100);
    • stroke(255, 204);
    • smooth();
    • }
    • void draw() {
    • background(0);
    • inc += 0.01;
    • float angulo = sin(inc)/10.0 + sin(inc*1.2)/20.0;
    • alga(18, 9, angulo/1.3);
    • alga(33, 12, angulo);
    • alga(44, 10, angulo/1.3);
    • alga(62, 5, angulo);
    • alga(88, 7, angulo*2);
    • }
    • // ***continúa***
  • Movimiento orgánico 8/12
    • void alga(int x, int unidades, float angulo) {
    • pushMatrix();
    • translate(x, 100);
    • for (int i = unidades; i > 0; i--) {
    • strokeWeight(i);
    • line(0, 0, 0, -8);
    • translate(0, -8);
    • rotate(angulo);
    • }
    • popMatrix();
    • }
  • Movimiento orgánico 9/12
  • La función noise() es otro buen recurso para producir un movimiento orgánico. Ya que los números producidos con noise() son fáciles de controlar, se presentan como una buena forma para agregar sutiles irregularidades al movimiento.
  • Veamos un ejemplo:
  • Movimiento orgánico 10/12
    • float inc1 = 0.1;
    • float n1 = 0.0;
    • float inc2 = 0.09;
    • float n2 = 0.0;
    • void setup() {
    • size(100, 100);
    • stroke(255);
    • strokeWeight(20);
    • smooth();
    • }
    • void draw() {
    • background(0);
    • float y1 = (noise(n1) - 0.5) * 30.0; // Valores -15 a 15
    • float y2 = (noise(n2) - 0.5) * 30.0; // Valores -15 a 15
    • line(0, 50, 40, 50 + y1);
    • line(100, 50, 60, 50 + y2);
    • n1 += inc1;
    • n2 += inc2;
    • }
  • Movimiento orgánico 11/12
  • La función noise() también es útil para producir texturas dinámicas. En el siguiente ejemplo los dos primeros parámetros son usados para producir una textura bidimensional mientras que el tercero incrementa su valor en cada cuadro para variar la textura.
  • Los cambios en la variable densidad modifican la resolución de la imagen, mientras que los cambios en la variable inc modifican la resolución de la textura:
  • Movimiento orgánico 12/12
    • float inc = 0.06;
    • int densidad = 4;
    • float zRuido = 0.0;
    • void setup() {
    • size(100, 100);
    • noStroke();
    • }
    • void draw() {
    • float xRuido = 0.0;
    • float yRuido = 0.0;
    • for (int y = 0; y < height; y += densidad) {
    • for (int x = 0; x < width; x += densidad) {
    • float n = noise(xRuido, yRuido, zRuido) * 256;
    • fill(n);
    • rect(y, x, densidad, densidad);
    • xRuido += inc;
    • }
    • xRuido = 0;
    • yRuido += inc;
    • }
    • zRuido += inc;
    • }
  • Lectura recomendada
  • Capítulo “Motion 2: Machine, Organism” (pag. 291).
  • Parte 16 Arrays
  • Introducción 1/5
  • El término array hace referencia a una agrupación sistemática de objetos.
  • Es posible encontrar en la bibliografía en español diversas traducciones del antedicho término tales como arreglo, vector o matriz, siendo la primera una traducción literal, la segunda haciendo referencia al vector algebraico (generalmente de una dimensión), y la tercera a una matriz algebraica (generalmente de dos). Pocas veces es traducido como el objeto matemático tensor (multidimensional).
  • En informática llamamos array a un conjunto de elementos de datos, todos ellos almacenados bajo un mismo nombre.
  • Los arrays pueden almacenar números, caracteres, cadenas de texto, valores booleanos, datos de posición de vértices correspondientes a una figura compleja, teclas pulsadas, clics de botones de ratón, datos leidos de un archivo de texto, etc.
  • Introducción 2/5
  • Veamos un ejemplo: queremos almacenar cinco datos (elementos), en este caso cinco números enteros, correspondientes a un conjunto ordenado de datos que llamaremos fechas:
  • Los elementos de un array son numerados a partir del número cero. El primer elemento se encuentra en la posición [0], el segundo en la posición [1], etc.
  • La posición de cada elemento es determinada por el desplazamiento desde el inicio del array. El primer elemento se encuentra en la posición [0] ya que existe desplazamiento; el segundo elemento se encuentra en la posición [1] ya que su lugar se encuentra desplazado un espacio desde el inicio del array.
  • La última posición se calcula mediante la sustracción de 1 a la longitud (cantidad total de elementos) del array. En este ejemplo el último elemento se encuentra en la posición [4] ya que tenemos un total de cinco elementos en el array.
  • Introducción 3/5
  • Los arrays pueden facilitar mucho la programación. Si bien su uso no es obligatorio, son estructuras valiosas y eficaces para la administración de datos.
  • A continuación veremos, en un ejemplo concreto de aplicación, algunos de los beneficios de utilizar arrays en lugar de un gran número de variables:
  • Partimos de un conjunto de datos que determinan las coordenadas de posición de los vértices que dibujan una figura “estrella”.
  • La “estrella” está conformada por 10 puntos vértice, cada uno con 2 valores (pos. x e y). Si quisieramos utilizar variables deberíamos declarar 20 variables, cantidad bastante considerable.
  • Introducción 4/5
  • Si utilizásemos arrays, existen dos maneras de resolverlo: implementar 10 arrays que integren los valores de las coordenadas xy para cada punto, o bien implementar 2 arrays que integren todos los valores de los puntos para cada eje (x e y).
  • Introducción 5/5
  • Si bien el código en el medio mejora notablemente la situación, es posible optimizarlo aún más. El código en la derecha muestra cómo los elementos de datos pueden ser agrupados de manera lógica en 2 arrays, uno para la abscisa y el otro para la ordenada.
  • A continuación veremos cómo es posible implementar el ejemplo y cómo acceder a cada valor de los arrays mediante el uso de una estructura for:
    • void setup() {
    • int[] x = {50, 61, 83, 69, 71, 50, 29, 31, 17, 39};
    • int[] y = {18, 37, 43, 60, 82, 73, 82, 60, 43, 37};
    • beginShape();
    • // Lee un elemento de cada array por vez mediante el for()
    • for (int i = 0; i < x.length; i++) {
    • vertex(x[i], y[i]);
    • }
    • endShape(CLOSE);
    • }
  • Uso de arrays 1/3
  • En Processing, cuando trabajamos con un array, en primer lugar debemos declararlo, en segundo crearlo y por último asignarlo:
    • Los arrays deben ser declarados de forma similar a las variables pero se distinguen por el uso de los corchetes [ y ]. Además se debe especificar el tipo de dato que almacena.
    • Luego de la declaración se debe crear el array mediante la palabra clave new. Este paso adicional designa un espacio en la memoria del ordenador para almacenar los datos del array.
    • Una vez creado el array, se puede asignar los valores al mismo.
  • Uso de arrays 2/3
  • Existen diversas formas de declarar, crear y asignar arrays:
  • Estos tres ejemplos asumen que los tres arrays están siendo usados junto a estructuras setup() y draw().
  • int[] datos = new int[5]; // Declaración y creación
  • void setup() {
  • datos[0] = 19; // Asignación
  • datos[1] = 40;
  • datos[2] = 75;
  • datos[3] = 76;
  • datos[4] = 90;
  • println(datos[1]);
  • }
  • int[] datos; // Declaración
  • void setup() {
  • datos = new int[5]; // Creación
  • datos[0] = 19; // Asignación
  • datos[1] = 40;
  • datos[2] = 75;
  • datos[3] = 76;
  • datos[4] = 90;
  • println(datos[1]);
  • }
  • int[] datos = {19, 40, 75, 76, 90}; // Declaración, creación y asignación
  • void setup() {
  • println(datos[1]);
  • }
  • Uso de arrays 3/3
  • Los pasos de declaración, creación y asignación nos habilitan a la lectura de los valores de los distintos elementos del array.
  • Se puede obtener el valor de uno de los elementos del array llamando al nombre del array seguido de los corchetes los cuales encierran el número índice del elemento que se desea acceder:
    • int[] datos = {19, 40, 75, 76, 90};
    • line(datos[0], 0, datos[0], height);
    • line(datos[1], 0, datos[1], height);
    • line(datos[2], 0, datos[2], height);
    • line(datos[3], 0, datos[3], height);
    • line(datos[4], 0, datos[4], height);
  • Processing responderá con el mensaje ArrayIndexOutOfBoundsException con cualquier número índice ingresado que exceda el rango actual del array.
  • Uso del campo length
  • Para obtener la cantidad total de elementos almacenados en un array usamos el campo length. Más adelante veremos qué es un campo en relación a la programación orientada a objetos, pero por el momento diremos que es posible acceder a un campo mediante el operador punto (.).
  • El siguiente ejemplo muestra cómo utilizarlo:
    • int[] datos1 = {19, 40, 75, 76, 90};
    • int[] datos2 = {19, 40};
    • int[] datos3 = new int[127];
    • println(datos1.length); // Imprime "5" en la consola
    • println(datos2.length); // Imprime "2" en la consola
    • println(datos3.length); // Imprime "127" en la consola
  • Uso de la estructura for 1/4
  • Usualmente se utiliza una estructura for para acceder a los elementos de un array, especialmente cuando éste contiene numerosos elementos.
  • El presente ejemplo…
    • int[] datos = {19, 40, 75, 76, 90};
    • line(datos[0], 0, datos[0], height);
    • line(datos[1], 0, datos[1], height);
    • line(datos[2], 0, datos[2], height);
    • line(datos[3], 0, datos[3], height);
    • line(datos[4], 0, datos[4], height);
  • … puede ser reemplazado por este otro:
    • int[] datos = {19, 40, 75, 76, 90};
    • for (int i = 0; i < datos.length; i++) {
    • line(datos[i], 0, datos[i], 100);
    • }
  • Uso de la estructura for 2/4
  • La estructura for también puede ser usada para ingresar datos en un array.
  • El siguiente ejemplo se almacena en un array los valores provenientes de una función sin() dentro del bloque setup(), y luego muestra dichos valores a través del color de contorno de líneas dentro del bloque draw():
    • float[] ondaSeno;
    • void setup() {
    • size(100, 100);
    • ondaSeno = new float[width];
    • for (int i = 0; i < width; i++) {
    • // Llena el array con los valores de sin()
    • float r = map(i, 0, width, 0, TWO_PI);
    • ondaSeno[i] = abs(sin(r));
    • }
    • }
    • void draw() {
    • for (int i = 0; i < ondaSeno.length; i++) {
    • // Aplica los valores del array en la función stroke()
    • stroke(ondaSeno[i] * 255);
    • line(i, 0, i, height);
    • }
    • }
  • Uso de la estructura for 3/4
  • Otra forma de hacer más sencilla la lectura y gestión de un programa puede darse gracias al uso de un array para almacenar las coordenadas de gran cantidad de elementos.
  • En el siguiente ejemplo el array x[] almacena la coordenada-x para cada uno de los 12 elementos, y el array velocidad[] almacena la relación correspondiente a cada uno de ellos.
  • La escritura de este programa sin arrays hubiera requerido la inclusión de 24 variables independientes.
  • En cambio, de esta manera, resulta sencillo cambiar el valor asignado a numLineas para modificar el número de elementos dibujados en la pantalla.
  • Uso de la estructura for 4/4
    • int numLineas = 12;
    • float[] x = new float[numLineas];
    • float[] velocidad = new float[numLineas];
    • float offset = 8; // Espacio entre líneas
    • void setup() {
    • size(100, 100);
    • smooth();
    • strokeWeight(10);
    • for (int i = 0; i < numLineas; i++) {
    • x[i] = i; // Posición inicial
    • velocidad[i] = 0.1 + (i / offset); // Velocidad inicial
    • }
    • }
    • void draw() {
    • background(204);
    • for (int i = 0; i < x.length; i++) {
    • x[i] += velocidad[i]; // Actualiza posición de la línea
    • if (x[i] > (width + offset)) { // Si sale por derecha,
    • x[i] = -offset * 2; // regresa por la izquierda.
    • }
    • float y = i * offset; // Determina la posición-y de la línea
    • line(x[i], y, x[i]+offset, y+offset); // Dibuja la línea
    • }
    • }
  • Almacenamiento de datos del ratón 1/4
  • Los arrays también son muy usados para almacenar los datos obtenidos del ratón.
  • Las variables de sistema pmouseX y pmouseY almacenan las coordenadas del ratón del cuadro previo, pero no existe una manera ya incorporada en el sistema para acceder a los valores del cursor de cuadros anteriores.
  • En cada cuadro, los valores de las variables mouseX, mouseY, pmouseX y pmouseY son reemplazadas con nuevos valores y los previos son descartados.
  • La forma más sencilla de almacenar la historia de dichos valores se da gracias al uso de un array.
  • En el siguiente ejemplo, los 100 últimos valores de mouseY son almacenados en un array y son mostrados en pantalla mediante una línea que la recorre de izquierda a derecha.
  • En cada cuadro, los valores del array son transladados a la derecha y el valor más reciente es agregado al inicio.
  • Almacenamiento de datos del ratón 2/4
  • int[] y;
  • void setup() {
  • size(100, 100);
  • y = new int[width];
  • }
  • void draw() {
  • background(204);
  • // Desplaza los valores a la derecha
  • for (int i = y.length-1; i > 0; i--) {
  • y[i] = y[i-1];
  • }
  • // Agrega nuevos valores al inicio
  • y[0] = constrain(mouseY, 0, height-1);
  • // Muestra cada par de valores como una línea
  • for (int i = 1; i < y.length; i++) {
  • line(i, y[i], i-1, y[i-1]);
  • }
  • }
  • Almacenamiento de datos del ratón 3/4
  • Aplique el mismo código a los valores de mouseX y mouseY para almacenar la posición del cursor.
  • La visualización de estos valores en cada cuadro crea una estela detrás del cursor:
  • void draw() {
  • background(0);
  • // Desplaza los valores a la derecha
  • for (int i = num-1; i > 0; i--) {
  • x[i] = x[i-1];
  • y[i] = y[i-1];
  • }
  • // Agrega los nuevos valores al inicio del array
  • x[0] = mouseX;
  • y[0] = mouseY;
  • // Dibuja los círculos
  • for (int i = 0; i < num; i++) {
  • ellipse(x[i], y[i], i/2.0, i/2.0);
  • }
  • }
  • int num = 50;
  • int[] x = new int[num];
  • int[] y = new int[num];
  • void setup() {
  • size(100, 100);
  • noStroke();
  • smooth();
  • fill(255, 102);
  • }
  • // *** continúa ***
  • Almacenamiento de datos del ratón 4/4
  • El siguiente ejemplo produce el mismo resultado que el anterior pero usa una técnica más eficiente. En lugar de ordenar los elementos del array en cada cuadro, el programa escribe los nuevos datos en la próxima posición del array disponible.
  • Los elementos del array permanecen en la misma posición una vez escritos, pero son leídos en un orden diferente en cada cuadro. La lectura comienza en la posición del elemento más antiguo y continúa hasta el final del array. Al final del array, se usa el operador % para volver nuevamente al principio.
  • Esta técnica es especialmente útil para aplicar a arrays de gran tamaño para evitar la copia de datos innecesarios que puede llevar a una considerable reducción de velocidad del programa.
  • void draw() {
  • background(0);
  • x[indicePosicion] = mouseX;
  • y[indicePosicion] = mouseY;
  • // Ciclo entre 0 y el número de elementos
  • indicePosicion = (indicePosicion + 1) % num;
  • for (int i = 0; i < num; i++) {
  • // Determina la posición del array a leer
  • int pos = (indicePosicion + i) % num;
  • float radio = (num-i) / 2.0;
  • ellipse(x[pos], y[pos], radio, radio);
  • }
  • }
  • int num = 50;
  • int[] x = new int[num];
  • int[] y = new int[num];
  • int indicePosicion = 0;
  • void setup() {
  • size(100, 100);
  • noStroke();
  • smooth();
  • fill(255, 102);
  • }
  • // *** continúa ***
  • Funciones de array
  • Processing provee un grupo de funciones que permiten asistir en la gestión de los arrays:
    • append()
    • shorten()
    • expand()
    • arrayCopy()
    • concat()
    • subset()
    • sort()
    • reverse()
    • splice()
  • Función de array: append()
  • Expande un elemento del array, añade los datos en la nueva posición y regresa el array incrementado.
  • El tipo de dato del segundo parámetro (elemento) debe ser el mismo que el tipo de dato del array.
  • int[] numeros = {1, 3};
  • append(numeros, 5); // INCORRECTO! No cambia el array
  • println(numeros); // Imprime "1" y "3"
  • println();
  • numeros = append(numeros, 5); // Agrega "5" al final
  • println(numeros); // Imprime "1", "3" y "5"
  • println();
  • // Agrega "7" al final del array "numeros", y crea un nuevo
  • // array donde guarda el cambio
  • int[] masNumeros = append(numeros, 7);
  • println(masNumeros); // Imprime "1", "3", "5" y "7"
  • Función de array: shorten()
  • Disminuye un array en un elemento y regresa el array acortado.
  • int[] numeros = {1, 3, 5, 7};
  • numeros = shorten(numeros); // Recorta el último elemento
  • println(numeros); // Imprime "1", "3" y "5"
  • Función de array: expand()
  • Incrementa el tamaño de un array. Puede expandirlo a un tamaño determinado (según el segundo parámetro) o, si no se especifica el tamaño, se lo dobla.
  • int[] numeros = {1, 3, 5, 7};
  • println(numeros.length); // Imprime "4“
  • numeros = expand(numeros);
  • println(numeros.length); // Imprime "8"
  • numeros = expand(numeros, 512);
  • println(numeros.length); // Imprime "512"
  • Función de array: arrayCopy()
  • Copia un array (o parte de él) a otro array.
  • Existen tres versiones de dicha función:
  • int[] numeros1 = {1, 3, 5, 7};
  • int[] numeros2 = {2, 4, 6, 8};
  • arrayCopy(numeros1, numeros2);
  • println(numeros2); // Imprime "1", "3", "5" y "7"
  • int[] numeros1 = {1, 3, 5, 7};
  • int[] numeros2 = {2, 4, 6, 8};
  • arrayCopy(numeros1, 1, numeros2, 0, 2);
  • println(numeros2); // Imprime "3", "5", "6" y "8"
  • arrayCopy(arrayOrigen, arrayDestino)
  • arrayCopy(arrayOrigen, arrayDestino, cantElementosACopiar)
  • arrayCopy(arrayOrigen, arrayOrigenPos, arrayDestino, arrayDestinoPos, cantElementosACopiar)
  • Función de array: concat()
  • Concatena dos arrays.
  • int[] numeros1 = {1, 3, 5, 7};
  • int[] numeros2 = {2, 4, 6, 8};
  • numeros1 = concat(numeros1, numeros2);
  • println(numeros1); // Imprime "1", "3", "5", "7", "2", "4", "6" y "8"
  • Función de array: reverse()
  • Invierte el orden de un array.
  • int[] numeros = {1, 3, 5, 7};
  • numeros = reverse(numeros);
  • println(numeros); // Imprime "7", "5", "3" y "1"
  • Función de array: sort()
  • Ordena un array de números de menor a mayor, o pone en orden alfabético un array de palabras. El array original no resulta modificado, se regresa un array re-ordenado.
  • float[] decimales = {3.4, 2, 0, 7.1};
  • decimales = sort(decimales);
  • println(decimales); // Imprime "0.0", "2.0", "3.4" y "7.1"
  • Función de array: splice()
  • Inserta un valor o un array de valores dentro de un array existente.
  • int[] numeros = {1, 3, 5, 7};
  • numeros = splice(numeros, 2, 1);
  • println(numeros); // Imprime "1", "2", "3", "5" y "7"
  • int[] numeros1 = {1, 3, 5};
  • int[] numeros2 = {2, 4, 6};
  • numeros2 = splice(numeros1, numeros2, 2);
  • println(numeros2); // Imprime "1", "3", "2", "4", "6" y "5"
  • splice(array, valorASerInsertado, posIndiceArray)
  • splice(array, arrayASerInsertado, posIndiceArray)
  • int[] numeros1 = {1, 3, 5};
  • int[] numeros2 = {2, 4, 6};
  • numeros1 = splice(numeros2, numeros1, 2);
  • println(numeros1); // Imprime "2", "4", "1", "3", "5" y "6"
  • Función de array: subset()
  • Extrae una serie de elementos de un array.
  • El parámetro array define el array desde el cual serán tomados los elementos.
  • El parámetro offset define la posición desde la cual se comienza a extraer (primer valor).
  • El parámetro numeroDeValoresAExtraer determina la cantidad de elementos que serán extraídos. Si este parámetro no es utilizado, los elementos serán extraídos desde el offset hasta el final del array.
  • int[] numeros1 = {1, 3, 5, 7};
  • int[] numeros2 = subset(numeros1, 2);
  • println(numeros2); // Imprime "5" y "7"
  • subset(array, offset)
  • subset(array, offset, numeroDeValoresAExtraer)
  • int[] numeros1 = {1, 3, 5, 7, 9, 11};
  • int[] numeros2 = subset(numeros1, 2, 3);
  • println(numeros2); // Imprime "5", "7" y “9"
  • Consideraciones particulares sobre los arrays 1/3
  • Se pueden escribir nuevas funciones que realizan operaciones sobre los arrays, pero los arrays se comportan de manera diferente a otros tipos de dato como int o char.
  • Cuando un array es usado como parámetro de función, la dirección (ubicación en la memoria) del array es transferida dentro de la función en lugar de los datos reales.
  • No resulta creado ningún array nuevo, y los cambios realizados dentro de la función afectan al array usado como parámetro.
  • Consideraciones particulares sobre los arrays 2/3
  • En el siguiente ejemplo, el array datos[] es usado como parámetro de la función mitad(). La dirección de datos[] es pasada al array d[] mediante la función mitad(). Ya que la dirección de d[] y datos[] es la misma, ellos afectan al mismo contenido. Cuando se realizan cambios en d[], estos cambios se producen sobre los valores del array datos[].
  • float[] datos = {19.0, 40.0, 75.0, 76.0, 90.0};
  • void setup() {
  • mitad(datos);
  • println(datos[0]); // Imprime "9.5"
  • println(datos[1]); // Imprime "20.0"
  • println(datos[2]); // Imprime "37.5"
  • println(datos[3]); // Imprime "38.0"
  • println(datos[4]); // Imprime "45.0"
  • }
  • void mitad(float[] d) {
  • for (int i = 0; i < d.length; i++) { // Cada elemento del array,
  • d[i] = d[i] / 2.0; // se divide por dos.
  • }
  • }
  • Consideraciones particulares sobre los arrays 3/3
  • El cambio de los datos de un array dentro de una función, sin modificar el array original, requiere algunas líneas de código adicional.
  • En el siguiente ejemplo, se pasa el array como parámetro dentro de una función, se crea un nuevo array, los valores del array original son copiados en el nuevo array, los cambios son realizados en el nuevo array, y finalmente se regresa el array modificado.
  • float[] datos = {19.0, 40.0, 75.0, 76.0, 90.0};
  • float[] mitadDatos;
  • void setup() {
  • mitadDatos = mitad(datos); // Ejecuta la función mitad()
  • println(datos[0] + ", " + mitadDatos[0]); // Imprime "19.0, 9.5"
  • println(datos[1] + ", " + mitadDatos[1]); // Imprime "40.0, 20.0"
  • println(datos[2] + ", " + mitadDatos[2]); // Imprime "75.0, 37.5"
  • println(datos[3] + ", " + mitadDatos[3]); // Imprime "76.0, 38.0"
  • println(datos[4] + ", " + mitadDatos[4]); // Imprime "90.0, 45.0"
  • }
  • float[] mitad(float[] d) {
  • float[] numeros = new float[d.length]; // Crea un nuevo array
  • arraycopy(d, numeros);
  • for (int i = 0; i < numeros.length; i++) { // Cada elemento del array,
  • numeros[i] = numeros[i] / 2; // se divide por dos.
  • }
  • return numeros; // Regresa el nuevo array
  • }
  • Arrays bidimensionales 1/2
  • Los datos también pueden ser almacenados y recuperados mediante arrays con más de una dimensión.
  • A partir del ejemplo de la introducción de esta sección, mostraremos cómo utilizar un array 2D para almacenar los puntos vértice de la estrella:
  • Un array 2D es esencialmente una lista de arrays 1D. Debe ser primero declarado, luego creado, y por último asignado tal como un array 1D. A continuación el código:
  • int[][] puntos = { {50,18}, {61,37}, {83,43}, {69,60}, {71,82},
  • {50,73}, {29,82}, {31,60}, {17,43}, {39,37} };
  • println(puntos[4][0]); // Imprime "71"
  • println(puntos[4][1]); // Imprime "82"
  • println(puntos[4][2]); // ERROR! Este elemento se encuentra fuera de rango
  • println(puntos[0][0]); // Imprime "50"
  • println(puntos[9][1]); // Imprime "37"
  • println(puntos[1]); // Imprime "61" y "37"
  • Arrays bidimensionales 2/2
  • El próximo ejemplo muestra cómo se aplica el array 2D en el ejemplo de la estrella:
  • Si bien es posible crear arrays multidimensionales (3D, 4D o más) extrapolando estas técnicas, su implementación suele ser muy compleja y frecuentemente se prefiere aplicar múltiples arrays 1D o 2D.
  • int[][] puntos = { {50,18}, {61,37}, {83,43}, {69,60}, {71,82},
  • {50,73}, {29,82}, {31,60}, {17,43}, {39,37} };
  • void setup() {
  • size(100, 100);
  • fill(0);
  • smooth();
  • }
  • void draw() {
  • background(204);
  • translate(mouseX - 50, mouseY - 50);
  • beginShape();
  • for (int i = 0; i < puntos.length; i++) {
  • vertex(puntos[i][0], puntos[i][1]);
  • }
  • endShape();
  • }
  • Lectura recomendada
  • Capítulo “Data 4: Arrays” (pag. 301).
  • Parte 17 Objetos
  • Introducción 1/2
  • El paradigma planteado por la programación estructurada tradicional define las variables (datos) y las funciones (procedimientos) como los bloques básicos de construcción. Distintas funciones serán frecuentemente usadas en conjunto para trabajar sobre una serie determinada de variables.
  • La programación orientada a objetos (POO) fue desarrollada para hacer más explícito este proceso.
  • La POO utiliza clases y objetos como bloques básicos de construcción.
  • Una clase define un grupo de métodos (funciones) y campos (variables).
  • Un objeto es una única instancia de una clase.
  • Los campos dentro de un objeto se acceden, típicamente, sólo a través de sus propios métodos, permitiendo a un objeto ocultar su complejidad de otras partes del programa.
  • Introducción 2/2
  • La POO difiere de la programación estructurada tradicional, en la que los datos y los procedimientos están separados y sin relación, ya que lo único que se busca es el procesamiento de unos datos de entrada para obtener otros de salida.
  • La programación estructurada anima al programador a pensar sobre todo en términos de procedimientos o funciones, y en segundo lugar en las estructuras de datos que esos procedimientos manejan.
  • En la programación estructurada sólo se escriben funciones que procesan datos. Los programadores que emplean POO, en cambio, primero definen objetos para luego enviarles mensajes solicitándoles que realicen sus métodos por sí mismos.
  • Programación estructurada Progr. orientada a objetos
  • Variable Campo
  • Función Método
  • POO 1/3
  • Un programa modular está compuesto de módulos de código los cuales realizan, cada uno, una tarea específica.
  • El uso de variables es un medio fundamental para estudiar la reutilización de elementos dentro de un programa. Permite que un determinado valor aparezca las veces que se necesite dentro de un programa y que sea fácilmente cambiado.
  • Las funciones resumen una tarea específica y permiten que bloques de código sean usados en todo el programa. Típicamente, uno se concentra sólo en qué es lo que la función hace, no en cómo esta trabaja.
  • Todo esto permite que la mente se concentre en los objetivos del programa antes que en las complejidades de la infraestructura.
  • POO 2/3
  • La programación orientada a objetos amplía aún más la modularidad -el uso de variables y escritura de funciones- al permitir la agrupación de funciones relacionadas.
  • Podemos asociar los objetos de la POO con artefactos concretos:
  • Si extendemos el ejemplo de la Manzana podremos apreciar un poco más las consideraciones acerca de las relaciones entre los objetos concretos y los objetos de software: el método crecer() podría tener entradas para temperatura y humedad; el método caer() podría continuamente controlar peso y hacerla caer cuando supere un determinado umbral; el método descomponer() podría entonces hacerse cargo comenzando a disminuir el valor de peso y cambiar color.
  • Clase Manzana Mariposa
  • Campo color, peso especie, genero
  • Método crecer(), caer(), descomponer() vatirAlas(), tomarTierra()
  • Clase Radio Auto
  • Campo frecuencia, volumen marca, modelo, color
  • Método encender(), sintonizar(), ajustarVol() acelerar(), frenar(), girar()
  • POO 3/3
  • Los objetos son creados a partir de una clase, y una clase describe un conjunto de campos y métodos.
  • Una instancia de una clase debe tener un nombre único. Si más de un objeto es creado a partir de una clase, cada uno debe tener un nombre único. Por ejemplo de la clase Manzana podemos crear dos objetos con sus correspondientes valores de campos:
  • Se accede a los campos y métodos de un objeto mediante el operador punto (.).
  • Para obtener el valor del campo color del objeto deliciosa, se utiliza la sintaxis deliciosa.color.
  • Para activar (o llamar) al método crecer() del objeto grannySmith, también se utiliza la siguiente sintaxis con el operador punto: grannySmith.crecer().
  • Objeto deliciosa grannySmith
  • Campos color: rojo color: amarillo
  • peso: 200 peso: 230
  • Uso de clases y objetos 1/12
  • Definir una clase es, en definitiva, crear nuestro propio tipo de dato.
  • A diferencia de otros tipos primitivos como int, float y boolean, se trata de un tipo compuesto -como String, PImage y PFont. Esto significa que puede almacenar muchas variables y métodos bajo un mismo nombre.
  • Al momento de crear una clase, se debe pensar cuidadosamente sobre qué quiere que el código haga.
  • Es común realizar primero una lista de variables (estas serán los campos) y resolver los tipos de dato correspondientes.
  • Ejemplifiquemos: queremos dibujar un círculo blanco en la pantalla, por lo tanto pensamos en tres campos: dos para la posición y uno para el diámetro, prefiriendo el tipo float para aportar mayor flexibilidad para controlar el movimiento:
  • float x coordenada-x del círculo
  • float y coordenada-y del círculo
  • float diametro diámetro del círculo
  • Uso de clases y objetos 2/12
  • El nombre de la clase debe ser cuidadosamente considerado. Por convención se recomienda utilizar una letra mayúscula en la primera letra para diferenciarlo de las variables.
  • Una vez determinados el nombre de la clase y los campos, considere cómo hubiera escrito el programa sin el uso de objetos:
  • float x = 33;
  • float y = 50;
  • float diametro = 30;
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • }
  • void draw() {
  • background(0);
  • ellipse(x, y, diametro, diametro);
  • }
  • Uso de clases y objetos 3/12
  • En el próximo ejemplo veremos cómo aplicar la POO en el código: moveremos los campos que pertenecen al círculo a su propia clase.
  • La primera línea declara el objeto circ de tipo Circulo.
  • La clase Circulo se declara a continuación del bloque setup() y draw().
  • El objeto circ es construido dentro de setup(), permitiendo así el acceso a sus campos. En las tres líneas siguientes se asignan valores a los campos dentro de Circulo.
  • Se accede a estos valores dentro de draw() para determinar la posición y el tamaño del círculo.
  • El operador punto (.) se usa para asignarsetup()- y accederdraw()- a las variables de la clase.
  • Circulo circ; // Declaración del objeto
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • circ = new Circulo(); // Construcción del objeto
  • circ.x = 33; // Asigna 33 al campo x
  • circ.y = 50; // Asigna 50 al campo y
  • circ.diametro = 30; // Asigna 30 al campo diametro
  • }
  • void draw() {
  • background(0);
  • ellipse(circ.x, circ.y, circ.diametro, circ.diametro); //Accede a los campos
  • }
  • class Circulo {
  • float x, y; // Coordenadas xy
  • float diametro; // Diámetro del círculo
  • }
  • Uso de clases y objetos 4/12
  • La clase Circulo que hemos declarado no es muy útil por el momento, sin embargo es un inicio. El próximo ejemplo se construye sobre el anterior y agrega un método a dicha clase.
  • El método mostrar() ha sido agregado a la definición de la clase para dibujar la figura en la pantalla.
  • La última línea en draw() ejecuta el método mostrar() delegado al objeto circ al escribir los nombres del objeto y del método conectados por un operador punto (.).
  • Note también que no se usa el nombre del objeto para acceder a los campos. Esto sucede así porque que la función ellipse() se llama desde dentro de la clase Circulo. Ya que esta línea es parte del método mostrar(), este puede acceder a sus propias variables sin especificar su propio nombre.
  • Circulo circ ; // Declaración del objeto
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • circ = new Circulo(); // Construcción del objeto
  • circ.x = 33;
  • circ.y = 50;
  • circ.diametro = 30;
  • }
  • void draw() {
  • background(0);
  • circ.mostrar();
  • }
  • class Circulo {
  • float x, y, diametro; // Campos
  • void mostrar() { // Método
  • ellipse(x, y, diametro, diametro);
  • }
  • }
  • Uso de clases y objetos 5/12
  • Resulta prudente a esta altura reforzar la diferencia que existe entre la clase Circulo y el objeto circ.
  • Aunque el código pueda parecer indicarnos que los campos x, y y diametro, y el método mostrar() pertenecen a la clase, Circulo es sólo la definición para cualquier objeto creado a partir de dicha clase.
  • Cada uno de estos elementos pertenecen a (están encapsulados por) la variable circ, la cual es una instancia del tipo de dato Circulo.
  • El próximo ejemplo presenta un nuevo elemento de programación llamado constructor.
  • Uso de clases y objetos 6/12
  • Un constructor es un bloque de código activado al momento de la creación de un objeto.
  • El constructor siempre tiene el mismo nombre que la clase y es típicamente usado para asignar valores a los campos de un objeto cuando este se construye.
  • El constructor funciona como cualquier otro método, excepto en que no es precedido con un tipo de dato o la palabra clave void ya que no contempla ningún tipo de retorno.
  • Cuando se crea el objeto circ, los parámetros 33, 50 y 30 son asignados en correspondencia a las variables xpos, ypos y diam dentro del constructor. Dentro del bloque constructor, estos valores resultan asignados a los campos x, y y diam del objeto.
  • Para que los campos sean accesibles entre cada método del objeto, estos son declarados fuera del constructor. Recuerde las reglas del ámbito de las variables: si los campos son declarados dentro del constructor, estos no pueden ser accedidos por fuera del constructor.
  • Uso de clases y objetos 7/12
  • Ahora sí veamos el ejemplo:
  • Circulo circ; // Declaración del objeto
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • circ = new Circulo(33, 50, 30); // Construcción del objeto
  • }
  • void draw() {
  • background(0);
  • circ.mostrar();
  • }
  • class Circulo {
  • float x, y, diametro; // Campos
  • Circulo(float xpos, float ypos, float diam) { // Constructor
  • x = xpos; // Asigna 33 a x
  • y = ypos; // Asigna 50 a y
  • diametro = diam; // Asigna 30 a diametro
  • }
  • void mostrar() { // Método
  • ellipse(x, y, diametro, diametro);
  • }
  • }
  • Uso de clases y objetos 8/12
  • El comportamiento de la clase Circulo puede ser extendido aún más gracias a la incorporación en su definición de una mayor cantidad de campos y métodos.
  • El siguiente ejemplo extiende la clase para que el círculo se mueva hacia arriba y hacia abajo, y que cambie de dirección cuando este alcance el borde superior o inferior de la ventana de visualización.
  • Ya que el círculo estará moviéndose, este necesita un campo que defina la velocidad y otro campo que almacene la dirección. Llamaremos a estos campos velocidad y direccion.
  • El campo velocidad será un float para maximizar el rango de valores obtenibles, mientras que direccion será suficiente un int para aplicar operaciones aritméticas básicas (1 dirección hacia arriba, -1 dirección hacia abajo).
  • Uso de clases y objetos 9/12
  • Para crear el movimiento deseado, necesitamos actualizar la posición del círculo en cada cuadro.
  • La dirección también debe actualizarse cuando alcance los bordes de la ventana de visualización. La evaluación de borde se da cuando la coordenada-y es menor que el radio del círculo, o cuando esta es mayor que la altura de la ventana menos el radio del círculo. Luego la dirección cambiará cuando el borde exterior del círculo (en vez de su centro) llegue al borde de la ventana.
  • Además de decidir qué necesitan hacer los métodos y qué nombre tendrán, debemos considerar también el tipo de retorno. Ya que no se devolverá nada, se usará la palabra clave void.
  • Los códigos dentro de los métodos mover() y mostrar() podrían haber sido combinados en un solo método; fueron separados para hacer más claro el ejemplo. El cambio de la posición del objeto y la visualización en la pantalla del mismo son tareas distintas, y el uso de métodos separados así lo refleja.
  • Uso de clases y objetos 10/12
  • Circulo circ; // Declaración del objeto
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • circ = new Circulo(33, 50, 30, 1.5); // Construcción del objeto
  • }
  • void draw() {
  • fill(0, 15);
  • rect(0, 0, width, height);
  • fill(255);
  • circ.mover();
  • circ.mostrar();
  • }
  • class Circulo {
  • // Campos
  • float x, y, diametro;
  • float velocidad; // Distancia recorrida en cada cuadro
  • int direccion = 1; // Dirección del movimiento (1 hacia abajo, -1 hacia arriba)
  • // Constructor
  • Circulo(float xpos, float ypos, float diam, float vel) {
  • x = xpos;
  • y = ypos;
  • diametro = diam;
  • velocidad = vel;
  • }
  • // Métodos
  • void mover() {
  • y += (velocidad * direccion);
  • if ((y > (height - diametro/2)) || (y < diametro/2)) {
  • direccion *= -1;
  • }
  • }
  • void mostrar() {
  • ellipse(x, y, diametro, diametro);
  • }
  • }
  • Uso de clases y objetos 11/12
  • Al igual que una función, una clase bien escrita permite al programador concentrarse en comportamiento resultante y no en los detalles de ejecución.
  • Los objetos deben ser escritos con la finalidad de reutilización.
  • Como ocurre con otros tipos de variables, objetos adicionales son agregados al declarar más nombres.
  • El siguiente ejemplo tiene tres objetos hechos a partir de la clase Circulo. Cada uno de estos objetos (circ1, circ2 y circ3) tienen su propio conjunto de campos y métodos.
  • Se ejecuta un método declarado en la clase por cada objeto que lo llama.
  • Cuando se llaman a estos métodos, ellos acceden a los valores de los campos pertenecientes a cada objeto. Es decir, cuando circ2 llama por primera vez al método mover(), el valor del campo y es actualizado por el valor 2.0 del campo velocidad ya que dicho valor fue pasado al objeto circ2 a través del contructor.
  • Uso de clases y objetos 12/12
  • Ahora sí veamos el ejemplo:
  • Circulo circ1, circ2, circ3; // Declaración de los objetos
  • void setup() {
  • size(100, 100);
  • smooth();
  • noStroke();
  • circ1 = new Circulo(20, 50, 40, 0.5); // Construcción del objeto circ1
  • circ2 = new Circulo(50, 50, 10, 2.0); // Construcción del objeto circ2
  • circ3 = new Circulo(80, 50, 30, 1.5); // Construcción del objeto circ3
  • }
  • void draw() {
  • fill(0, 15);
  • rect(0, 0, width, height);
  • fill(255);
  • circ1.mover();
  • circ2.mover();
  • circ3.mover();
  • circ1.mostrar();
  • circ2.mostrar();
  • circ3.mostrar();
  • }
  • // Insertar aquí la clase Circulo
  • Otro ejemplo 1/2
  • Huevo humpty; // Declaración del objeto
  • void setup() {
  • size(100, 100);
  • smooth();
  • // Entradas: coord-x, coord-y, factor de balanceo y altura
  • humpty = new Huevo(50, 100, 8, 80); // Construcción del objeto
  • }
  • void draw() {
  • background(0);
  • humpty.balancear();
  • humpty.mostrar();
  • }
  • class Huevo {
  • // Campos
  • float x, y; // coords-xy
  • float inclinacion; // Offset ángulo izquierda y derecha
  • float balanceo; // Factor de balanceo
  • float angulo; // Ángulo de balanceo
  • float altura; // Altura del huevo
  • // Constructor
  • Huevo(int xpos, int ypos, float balFactor, float h) {
  • x = xpos;
  • y = ypos;
  • balanceo = balFactor;
  • altura = h / 100.0;
  • }
  • // *** continúa ***
  • Otro ejemplo 2/2
  • // Métodos
  • void balancear() {
  • inclinacion = cos(angulo) / balanceo;
  • angulo += 0.1;
  • }
  • void mostrar() {
  • noStroke();
  • fill(255);
  • pushMatrix();
  • translate(x, y);
  • rotate(inclinacion);
  • scale(altura);
  • beginShape();
  • vertex(0, -100);
  • bezierVertex(25, -100, 40, -65, 40, -40);
  • bezierVertex(40, -15, 25, 0, 0, 0);
  • bezierVertex(-25, 0, -40, -15, -40, -40);
  • bezierVertex(-40, -65, -25, -100, 0, -100);
  • endShape();
  • popMatrix();
  • }
  • }



Compartir con tus amigos:
1   2   3   4   5   6   7   8   9   10   11


La base de datos está protegida por derechos de autor ©absta.info 2019
enviar mensaje

    Página principal