Introducción a Processing v1.2.1 Raúl Lacabanne - 2011 Versión 41 Resumen de los tópicos...
-
Upload
alfredo-alcazar -
Category
Documents
-
view
19 -
download
1
Transcript of Introducción a Processing v1.2.1 Raúl Lacabanne - 2011 Versión 41 Resumen de los tópicos...
Introducción aProcessing v1.2.1
Raúl Lacabanne - 2011Versión 41
Resumen de los tópicos básicos del libro: “Processing: A Programming Handbook
for Visual Designers and Artists”de Casey Reas y Ben Fry. MIT Press, 2007.
Design By Numbers Design By Numbers (DBN) fue
creado como una plataforma de introducción al diseño asistido por ordenador para diseñadores visuales y artistas. Conceptualizado por John Maeda en el ACG (Aesthetics + Computation Group) del MIT, quien cree que la calidad del diseño y arte de medios sólo puede mejorar a través del establecimiento de infraestructuras educativas en escuelas de arte y tecnología que fomenten la formación de individuos transdiciplinarios competentes.
John Maeda. DBN. 1999-2001.
Processing Processing es un lenguaje y
entorno de programación de código abierto basado en Java, de fácil utilización, y que sirve como instrumento didáctico para la enseñanza y producción de proyectos multimedia e interactivos de diseño digital. Fue iniciado por Ben Fry y Casey Reas. Processing es desarrollado por artistas y diseñadores como una herramienta alternativa al software propietario. Puede ser utilizado tanto para aplicaciones locales así como aplicaciones para la web.Casey Reas & Ben Fry. 2001.
Parte 1Elementos de sintaxis
Descarga y descompresión Dirigirse a la siguiente URL: http://www.processing.org/download
/ y descargar la versión apropiada a su sistema operativo. Se recomienda a los usuarios de Windows descargar la versión Windows (a secas) y no la versión Windows (Without Java).
Una vez descargado el archivo, descomprimirlo en alguna carpeta del disco rígido, ej: el escritorio. Esto quiere decir que el entorno de desarrollo Processing no necesita instalación.
A continuación abrirá la carpeta descomprimida y ejecutará el archivo correspondiente al icono:
Comentarios En Processing tenemos dos formas de realizar comentarios.
El primero es un comentario simple:
// esto es un comentario...// ...y esto también lo es
el segundo es un bloque de comentario:
/*estotambiénesuncomentario
*/
Funciones (I)
Las funciones permiten ejecutar algoritmos, es decir, obtener un resultado a partir de un o una serie de pasos lógicos y estructurados.
En general, el nombre de una función comienza en minúsculas y es seguido por un par de paréntesis.
Los elementos que se encuentran entre los paréntesis se llaman parámetros.
Funciones (II)
Ejemplo de funciones
size(200, 200)
background(102)
noCursor()
Sensibilidad a mayúsculas y minúsculas
b =/= Bsize() =/= Size()
Espaciado
Processing es flexible a los espaciados. Sin embargo se recomienda ser prudente con los mismos para que la lectura del código sea cómoda.
Instrucciones (I)
Si utilizamos una metáfora del lenguaje humano, podemos entender a la instrucción como si fuera una oración.
Las instrucciones siempre se deben terminan con el signo punto y coma (;).
Dentro de una instrucción se pueden realizar las siguientes acciones: definir una variable o array, asignar un valor a una variable (o valores a un array), ejecutar una función, o construir un objeto
Instrucciones (II)
Ejemplo de un boceto que contiene un conjunto de instrucciones:
size(200, 200); // Ejecuta la función size() con dos parámetros
background(102); // Ejecuta la función background() con un parámetro
noCursor(); // Ejecuta la función noCursor() con ningún parámetro
Lectura recomendada Capítulo “Structure 1: Code Elements” (pag. 17).
Reas, C. & Fry, B. "Processing: A Programming Handbook for Visual Designers and Artists”, MIT Press, 2007.
Información complementaria
Utilización de la consola// Para imprimir un valor – Uso de la función print()
print(10); // Imprime 10 en la consola
// Para imprimir un texto, coloque el mismo entre comillas
print(“processing"); // Imprime processing a continuación del dato anterior
// Para imprimir en líneas separadas – Uso de la función println()
println(10); // Imprime 10 en la consola y salta a una nueva línea
println(“processing"); // Imprime processing y salta a una nueva línea
Función cursor() y noCursor() cursor()
Sintaxis cursor() cursor(MODE) cursor(image, x, y)
Parámetros MODE: Puede optar por ARROW, CROSS, HAND, MOVE, TEXT, WAIT image PImage: cualquier variable del tipo PImage x int: el punto activo horizontal del cursor y int: el punto activo vertical del cursor
noCursor()
Parte 2Coordenadas y
figuras primitivas
Coordenadas
En Processing, el origen se encuentra en el margen superior izquierdo.
0, 0+100
+100
Primitivas: punto
point(20, 20);
point(30, 30);
point(40, 40);
point(50, 50);
point(60, 60);
Primitivas: línea
line(25, 90, 80, 60);
line(50, 12, 42, 90);
line(45, 30, 18, 36);
Primitivas: elipse
ellipse(40, 40, 60, 60); // círculo grande
ellipse(75, 75, 32, 32); // círculo pequeño
Primitivas: rectángulo
rect(15, 15, 40, 40); // cuadrado grande
rect(55, 55, 25, 25); // cuadrado pequeño
Orden de dibujo
En Processing, el orden de dibujo siempre es secuencial, es decir, la primer instrucción se representa primero, las siguientes por encima, y la última por sobre todas estas.
rect(15, 15, 60, 60); // cuadrado inferior
rect(20, 20, 40, 40); // cuadrado intermedio
rect(25, 25, 20, 20); // cuadrado superior
Suavizado
smooth()noSmooth()
Atributos: strokeWeight()tamaño de contorno
smooth();strokeWeight(1); // por defectoline(20, 20, 80, 20);strokeWeight(4); // 4 pixelesline(20, 40, 80, 40);strokeWeight(10); // 10 pixelesline(20, 70, 80, 70);
Atributos: strokeCap()extremo de contorno
smooth();strokeWeight(12.0);strokeCap(ROUND); // redondeadoline(20, 30, 80, 30);strokeCap(SQUARE); // planoline(20, 50, 80, 50);strokeCap(PROJECT); // proyecciónline(20, 70, 80, 70);
Atributos: strokeJoin()extremo de contorno
smooth();strokeWeight(10);strokeJoin(MITER); // mitratriangle(50, 20, 80, 80, 20, 80);
smooth();strokeWeight(10);strokeJoin(BEVEL); // biseltriangle(50, 20, 80, 80, 20, 80);
smooth();strokeWeight(10);strokeJoin(ROUND); // redondeadatriangle(50, 20, 80, 80, 20, 80);
Lectura recomendada Capítulo “Shape 1: Coordinates, Primitives” (pag. 23).
Información complementaria
Primitivas: triángulo
triangle(60, 10, 25, 60, 75, 65);
Primitivas: cuadrilátero
quad(38, 31, 86, 20, 69, 63, 30, 76);
Primitivas: curva bezier
bezier(32, 20, 80, 5, 80, 75, 30, 75);
// Dibujo de puntos de controlline(32, 20, 80, 5);ellipse(80, 5, 4, 4);line(80, 75, 30, 75);ellipse(80, 75, 4, 4);
Ejercicio 1• EJ01: Escribir un boceto con la finalidad de componer un cuadro, con una resolución
de 300x300 píxeles, que contenga las siguientes figuras:– dos elipses, cuatro líneas y un rectángulo.
• Comentar todas las instrucciones.
• De aquí en más adaptar el siguiente comentario (a modo de título) e ingresarlo en el lugar de la primera instrucción:// ***********************************************// * Alumno: Nombre y apellido del alumno *// * Legajo: xxxxx *// * Ejercicio Nro: 01 *// * Asignatura: xxxx *// * Carrera: xxxx *// * Institución: UNQ *// * Año: XXXX Cuatrimestre: x *// ***********************************************
Ejercicio 2
• EJ02: Componer un cuadro con una resolución de 300x300 que contenga las siguientes figuras:
– dos elipses, cuatro líneas y un rectángulo.
• Generar las figuras con cambios de atributos.
• Comentar todas las instrucciones.
Parte 3Modos de color.
Fondo, contorno y relleno
colorMode() - Modo de color En general trabajamos con RGB pero es más recomendable
trabajar con HSB ya que se ajusta más a un modelo de color plástico. Para esto utilizamos la función colorMode().
Veremos que para utilizar la función colorMode() podremos escoger cuatros versiones distintas de dicha función:
colorMode(modo) HSB o RGBcolorMode(modo, rango) int o floatcolorMode(modo, rango1, rango2, rango3) int o floatcolorMode(modo, rango1, rango2, rango3 , alpha) int o float
Sintaxis de colorMode() Sintaxis
colorMode(modo); colorMode(mode, rango); colorMode(modo, rango1, rango2, rango3); colorMode(modo, rango1, rango2, rango3, alpha);
Parámetros modo RGB o HSB: correspondientes a Red/Green/Blue y
Hue/Saturation/Brightness. range int o float: rango para todos los elementos de color rango1 int o float: rango para el Rojo o Tono dependiendo del
modo de color actual. rango2 int o float: rango para el Verde o Saturación
dependiendo del modo de color actual. rango3 int o float: rango para el Azul o Brillo dependiendo
del modo de color actual. alpha int o float: rango para Alpha (0 transparencia total,
máximo opacidad total).
Ejemplo de colorMode()
colorMode(HSB, 360, 100, 100);
Fondo, contorno y relleno
Fondo = background() Valor por defecto = 204 (gris claro)
Contorno = stroke() Valor por defecto = 0 (negro) Sin contorno = noStroke()
Relleno = fill() Valor por defecto = 255 (blanco) Sin relleno = noFill()
Sintaxis de background()
background(gray)background(gray, alpha)background(value1, value2, value3)background(value1, value2, value3, alpha)background(color)background(color, alpha)background(hex)background(hex, alpha)
Parámetros de las versionesde background()
gray int o float: valores entre blanco y negro. alpha int o float: valor de opacidad (0 = transparencia –
255 = opacidad). value1 int o float: rojo o valor de matiz (depende del
modo de color). value2 int o float: verde o valor de saturación
(depende del modo de color). value3 int o float: azul o valor de brillo (depende del
modo de color). color color: cualquier valor del tipo de dato color. hex int: valor de color en notación hexadecimal (ej.:
#FFCC00 ó 0xFFFFCC00).
Ejemplos de background()
colorMode(HSB, 360, 100, 100);
background(51);
o bien:
colorMode(HSB, 360, 100, 100);
background(255, 204, 0);
Parámetros de versiones de background(), stroke() y fill()
gray int o float: valores entre blanco y negro. alpha int o float: valor de opacidad (0 = transparencia –
255 = opacidad). value1 int o float: rojo o valor de matiz (depende del
modo de color). value2 int o float: verde o valor de saturación
(depende del modo de color). value3 int o float: azul o valor de brillo (depende del
modo de color). color color: cualquier valor del tipo de dato color. hex int: valor de color en notación hexadecimal (ej.:
#FFCC00 or 0xFFFFCC00).
Sintaxis de stroke()
stroke(gray)stroke(gray, alpha)stroke(value1, value2, value3)stroke(value1, value2, value3, alpha)stroke(color)stroke(color, alpha)stroke(hex)stroke(hex, alpha)
Ejemplos de stroke()
stroke(153);
rect(30, 20, 55, 55);
o bien:
stroke(204, 102, 0);
rect(30, 20, 55, 55);
Sintaxis de fill()
fill(gray)fill(gray, alpha)fill(value1, value2, value3)fill(value1, value2, value3, alpha)fill(color)fill(color, alpha)fill(hex)fill(hex, alpha)
Ejemplos de fill()
fill(153);
rect(30, 20, 55, 55);
o bien:
fill(204, 102, 0);
rect(30, 20, 55, 55);
Lectura recomendada Capítulo “Color 1: Color by Numbers” (pag. 85).
Ejercicio 3
• EJ03: Componer un cuadro con una resolución de 300x300 que contenga las siguientes figuras:
– dos elipses, cuatro líneas y un rectángulo.
• Las figuras podrán tener sólo dos colores y el fondo de la composición (background) otro distinto.
• Comentar todas las instrucciones.
Parte 4Tipos de datos. Variables.
Datos
En general consisten de mediciones de características físicas.
Processing puede administrar distintos tipos de datos:números,letras,colores,imágenes,tipografíasy valores booleanos.
Tipos de datos básicos
Nombre Tamaño Rango de valores
boolean 1 bit true o false
byte 8 bits -128 a 127
char 16 bits 0 a 65535
int 32 bits -2,147,483,648 a 2,147,483,647
float 32 bits -3.40282347E+38 a3.40282347E+38
color 32 bits 16,777,216
Variables (I)
Podemos entender una variable como un contenedor que nos permite almacenar un tipo de dato.
Las variables permiten la reutilización de datos en un programa tantas veces como se necesite.
Las variables constan de tres partes: tipo de dato, => floatnombre de la variable => alturay valor => “1.72”
Variables (II)
En Processing, cuando trabajamos con variables, primero debemos declararla y luego asignar el valor que corresponda:
int x; // declaración de la variable x de tipo intfloat y; // declaración de la variable y de tipo floatboolean b; // declaración de la variable b de tipo booleanx = 50; // asignación del valor 50 a la variable xy = 12.6; // asignación del valor 12.6 a la variable yb = true; // asignación del valor true a la variable b
Durante la asignación utilizamos el signo “=“, el cual es llamado operador de asignación. El sentido de asignación se da siempre de derecha a izquierda de dicho signo.
Variables (III)
Los pasos de declaración y asignación pueden ser resumidos en una sola línea de código:
int x = 50; // declaración y asignación
float y = 12.6; // declaración y asignación
boolean b = true; // declaración y asignación
Instrucciones (III)
Otro ejemplo de boceto que contiene un conjunto de instrucciones:
size(200, 200); // Ejecuta la función size()
int x; // Declara una nueva variable x
x = 102; // Asigna el valor 102 a la variable x
background(x); // Ejecuta la función background()
Lectura recomendada Capítulo “Data 1: Variables” (pag. 37).
Parte 5Aritmética
Operadores aritméticos básicos en Processing
+ Suma- Resta* Multiplicación/ División% Módulo
Orden de ejecución de operaciones aritméticas
??? 3 + 4 * 5
1) * / %2) + -3) =
Expresiones Podemos pensar en una expresión como si fuera una frase. Las expresiones contienen frecuentemente o bien un solo valor, o
combinaciones de valores y operadores matemáticos y/o relacionales.
Una expresión siempre tiene un valor determinado por la evaluación de su contenido: Expresión Valor
5 5122.3 + 3.1 125.4((3 + 2) * -10) + 1 -496 > 3 true54 < 50 false
Muchas veces un conjunto de expresiones conforma una instrucción.
Uso de una variable como parámetro de función
Ejemplo:
int a = 30;
line(a, 0, a, height);
Uso de una variable como componen-te de una expresión de asignación
Ejemplo:
int a = 30;
int b = a + 40;
line(b, 0, b, height);
Uso de variables como componentes de una expresión perteneciente a un parámetro de función
Ejemplo:
int a = 30;
int b = 40;
line(b - a, 0, b - a, height);
Lectura recomendada Capítulo “Math 1: Arithmetic, Functions” (pag. 43).
Información complementaria
Operador Módulo %
El operador % calcula el resto de un cociente.A menudo se lo utiliza para mantener los
números dentro de un rango deseado.Por ej.: si partimos de un contador ascendente
desde el número 0 y queremos obtener cuatro valores que se reiteren (un ciclo de valores), usamos la siguiente expresión:
x 0 1 2 3 4 5 6 7 8 9 10 11 12 …
x % 4 0 1 2 3 0 1 2 3 0 1 2 3 0 …
Atajos aritméticos
Operador incremental ++int x = 1;println(x); // Imprime "1" en la consolax++; // Equivale a x = x + 1println(x); // Imprime "2" en la consola
Operador decremental --int y = 1;println(y); // Imprime "1" en la consolay--; // Equivale a y = y - 1println(y); // Imprime “0" en la consola
x++ ó ++x x++
En este caso el valor es incrementado LUEGO de que se evalúa la expresión.
int x = 1;println(x++); // Imprime "1" en la consolaprintln(x); // Imprime “2" en la consola
++x En este segundo caso se actualiza el valor ANTES de evaluar la
expresión.
int x = 1;println(++x); // Imprime “2" en la consolaprintln(x); // Imprime “2" en la consola
Operadores de asignación de suma y substracción
Se utilizan para realizar saltos de más de un paso (una unidad de valor).
Suma +=int x = 1;println(x); // Imprime "1" en la consolax += 5; // Equivalente a x = x + 5println(x); // Imprime “6" en la consola
Resta -=int y = 1;println(y); // Imprime "1" en la consolay -= 5; // Equivalente a y = y - 5println(y); // Imprime “-4" en la consola
Operadores de asignación de multiplicación y división
Se utilizan para realizar saltos de más de un paso (una unidad de valor).
Multiplicación *=int x = 4;println(x); // Imprime "4" en la consolax *= 2; // Equivalente a x = x * 2println(x); // Imprime "8" en la consola
División /=int y = 4;println(y); // Imprime "4" en la consolay /= 2; // Equivalente a y = y / 2println(y); // Imprime “2" en la consola
Operadores de negación
Cambia el signo del valor.
Negación –int x = 5; // Asigna 5 a x
x = -x; // Equivalente a x = x * -1
println(x); // Imprime "-5"
Ejercicio 4
• EJ04: Tomar el EJ03, declarar tres variables y utilizarlas en tres contextos distintos:
1) como parámetro de función.
2) como componente de una expresión de asignación. Utilizar operadores aritméticos de suma o resta.
3) como componente de una expresión perteneciente a un parámetro de función. Utilizar operadores aritméticos de suma o resta.
• Comentar todas las instrucciones.
Parte 6Control: Decisiones
Expresiones relacionales
Las expresiones relacionales nos informan la condición de verdad de dicha expresión.
Una expresión relacional compara dos valores y evalúa si el resultado es verdadero o falso.
Expresión Evaluación
3 > 5 false
3 < 5 true
5 < 3 false
5 > 3 true
Operadores relacionales
Operador Significado> mayor a
< menor a
>= mayor o equivalente a
<= menor o equivalente a
== equivalente a
!= no equivalente a
Condicional: if
Ejemplo: if
boolean dibujoCirculo = true;
if (dibujoCirculo == true) {
ellipse(50, 50, 50, 50);
}
rect(30, 45, 40, 10);
Condicional: if/else
Ejemplo: if/else
boolean dibujoCirculo = true;
if (dibujoCirculo == true) { ellipse(50, 50, 50, 50);} else { line(25, 25, 75, 75); line(75, 25, 25, 75);}rect(30, 45, 40, 10);
Condicional: if/else if
Ejemplo: if/else if
boolean dibujoCirculo = false;
boolean dibujoElipse = false;
if (dibujoCirculo == true) {
ellipse(50, 50, 50, 50);
} else if (dibujoElipse == true){
ellipse(50, 50, 50, 25);}
rect(30, 45, 40, 10);
Condicional: switch() Funciona como una estructura “if”, sin
embargo es más conveniente utilizar switch() cuando usted necesita seleccionar entre tres o más alternativas.
El control del programa se dirige al caso (case) que contenga el mismo valor de la expresión. Todas las demás instrucciones del switch() serán ejecutadas a menos que sean redirigidas mediante un corte (break).
Sólo los tipos de datos primitivos que pueden ser convertidos a un entero (byte, char e int) pueden ser usados como parámetro de expresión.
El caso por defecto (default) es opcional.
Sintaxis
switch(expresión){ case etiqueta1: instrucciones case etiqueta2: // Opcional instrucciones // Opcional default: // Opcional instrucciones // Opcional}
Parámetros
expresión byte, char o intetiqueta byte, char o intinstrucciones una o más
Ejemplo 1/3: switch()
int num = 1;
switch(num) { case 0: println("Cero"); // No se ejecuta break; case 1: println("Uno"); // Imprime "Uno" break;}println("¡Listo!"); // Imprime "¡Listo!"
Ejemplo 2/3: switch()char letra = 'N';
switch(letra) { case 'A': println("Alfa"); // No se ejecuta break; case 'B': println("Bravo"); // No se ejecuta break; default: // El caso por defecto se ejecuta si println("Ninguna"); // ninguna etiqueta coincide con el break; // parámetro de switch().}println("¡Listo!"); // Imprime "¡Listo!"
Ejemplo 3/3: switch()// La remoción de un "break" permite la// evaluación de más de un valor a la vez
char letra = 'b';
switch(letra) { case 'a': case 'A': println("Alfa"); // No se ejecuta break; case 'b': case 'B': println("Bravo"); // Imprime "Bravo" break;}println("¡Listo!"); // Imprime "¡Listo!"
Operadores lógicos (I) En la lógica proposicional se utilizan conectivas lógicas, también
llamadas operadores lógicos. En programación se los utilizan para combinar valores de verdad
y obtener nuevos valores que determinen el flujo de control de un algoritmo o programa.
Los operadores lógicos presentes en Processing se usan para combinar dos o más expresiones relacionales y/o para invertir los valores lógicos. Permiten considerar más de una condición simultáneamente.
Operador Significado&& AND (Y – conjunción)|| OR (o – disyunción (disyunción inclusiva))! NOT (no - negación)
Operadores lógicos (II) Tabla de funciones de verdad:
Expresión Evaluacióntrue && true truetrue && false falsefalse && true falsefalse && false false
true || true truetrue || false truefalse || true truefalse || false false
!true false!false true
Operador lógico AND El operador AND hace que una expresión relacional sea verdadera si AMBAS
partes son verdaderas.
int a = 10;int b = 20;
// La expresión "a > 5" debe ser verdadera// y "b < 30“ tambiçen debe serlo.// Debido a que ambas son verdaderas, se ejecutará el código del bloque.if ((a > 5) && (b < 30)) {
line(20, 50, 80, 50);}
// La expresión "a > 15" es falsa, pero "b < 30" es verdadera.// Debido a que el operador AND requiere que ambas sean// verdaderas, no se ejecutará el código del bloque.if ((a > 15) && (b < 30)) {
ellipse(50, 50, 36, 36);}
Operador lógico OR El operador OR hace que una expresión relacional sea verdadera si
SÓLO una parte es verdadera.
int a = 10;int b = 20;// Cualquiera de las dos expresiones pueden ser verdaderas.// Debido a que ambas son verdaderas, se ejecutará el código del bloque.if ((a > 5) || (b < 30)) {
line(20, 50, 80, 50);}// La expresión "a > 15" es falsa, pero "b < 30" es verdadera.// Debido a que el operador OR requiere sólo que una parte sea verdadera// en toda la expresión, se ejecutará el código del bloque.if ((a > 15) || (b < 30)) {
ellipse(50, 50, 36, 36);}
Operador lógico NOT El operador NOT se nota mediante un signo de exclamación (!). Invierte
el valor lógico de las variables booleanas asociadas. Es decir cambia los valores TRUE a FALSE y viceversa. El operador lógico NOT se aplica sólo a variables booleanas.
boolean b = true; // Asigna true a bprintln(b); // Imprime "true"println(!b); // Imprime "false"b = !b; // Asigna false a bprintln(b); // Imprime "false"println(!b); // Imprime "true"println(5 > 3); // Imprime "true"println(!(5 > 3)); // Imprime "false"int x = 5; // Declara y asigna 5 a xprintln(!x); // ¡ERROR! Sólo es posible trabajar con
// variables booleanas
Lectura recomendada Capítulo “Control 1: Decisions” (pag. 51).
Ejercicio 5
• EJ05: Realizar un boceto donde se declare una variable que, en función del valor dibuje:
– TRUE: un círculo con relleno negro.
– FALSE: un círculo con relleno blanco.
• Comentar todas las instrucciones.
Ejercicio 6
• EJ06: Realizar un boceto donde se dibuje una línea horizontal a mitad de pantalla, y que además a partir del valor de la declaración de una variable dibuje:
– TRUE: un círculo con relleno negro.
– FALSE: un círculo con relleno blanco.
• Comentar todas las instrucciones.
Ejercicio 7
• EJ7: Realizar un boceto donde se declare una variable que en función del valor dibuje:
– un círculo sin relleno.
– un círculo con relleno blanco.
– un círculo con relleno negro.
• Comentar todas las instrucciones.
Parte 7Control: Iteraciones
Las estructuras iterativas para simplificar instrucciones repetitivas.
Ejemplo código original
size(200, 200);line(20, 20, 20, 180); line(30, 20, 30, 180); line(40, 20, 40, 180);line(50, 20, 50, 180);line(60, 20, 60, 180);line(70, 20, 70, 180);line(80, 20, 80, 180);line(90, 20, 90, 180);line(100, 20, 100, 180);line(110, 20, 110, 180);line(120, 20, 120, 180);line(130, 20, 130, 180);line(140, 20, 140, 180);
Ej. código optimizado
size(200, 200);for (int i = 20; i < 150; i += 10) {
line(i, 20, i, 180);}
Estructura y funcionamiento general de for
for (init; test; update) {statements}
1. Se ejecuta la instrucción init.2. Se evalua la condición true o false de test.3. Si test es true, continúa al paso 4. Si el test es false salta al
paso 6.4. Ejecuta las instrucciones del bloque.5. Ejecuta la instrucción update y salta al paso 2.6. Sale de la estructura de iteración y continúa ejecutando el
programa.
Iteración: for
Ejemplo: for
for (int i = 10; i <= 90; i += 5) {
line(i, 10, i, 90);
}
Iteraciones anidadas La estructura for produce repeticiones en una dimensión. Si anidamos esta estructura
DENTRO de otra, combinando su efecto, crearemos iteraciones en dos dimensiones.
Ej. 1for (int y = 10; y < 100; y += 10) {
point(10, y);}
Ej. 2for (int x = 10; x < 100; x += 10) {
point(x, 10);}
Ej. 1 y 2 anidadosfor (int y = 10; y < 100; y += 10) {
for (int x = 10; x < 100; x += 10) {point(x, y);
}}
Por cada punto dibujado en la estructura externa, se dibujan 9 puntos en la estructura interna.
Iteración: while La estructura while ejecuta una serie de
instrucciones de manera continua mientras que la expresión sea verdadera.
La expresión debe ser actualizada durante la iteración, de lo contrario nunca saldremos del while.
Esta función puede resultar peligrosa ya que el código dentro del bucle while() no se detendrá hasta que la expresión dentro del mismo resulte falsa. Bloqueará cualquier otro código a ser utilizado (los eventos de ratón no serán actualizados, etc.). Por lo tanto debemos ser cautelosos ya que podemos llegar a inmovilizar el código (y hasta a veces el entorno Processing mismo) si se usa de manera incorrecta.
Sintaxis
while (expresión) { instrucciones}
Parámetros
expresión una expresión válidainstrucciones una o más
Ejemplo: while
int i=0;
while(i < 80) {
line(30, i, 80, i);
i = i + 5;
}
Lectura recomendada Capítulo “Control 2: Repetition” (pag. 61).
Información complementaria
Ejemplo 1/16: for
for (int x = -16; x < 100; x += 10) {
line(x, 0, x+15, 50);
}
strokeWeight(4);
for (int x = -8; x < 100; x += 10) {
line(x, 50, x+15, 100);
}
Ejemplo 2/16: for
noFill();
for (int d = 150; d > 0; d -= 10) {
ellipse(50, 50, d, d);
}
Ejemplo 3/16: for
/* Cambio de matiz, mientras la saturación y el brillo se mantienen constantes */
colorMode(HSB);
for (int i = 0; i < 100; i++) {
stroke(i*2.5, 255, 255);
line(i, 0, i, 100);
}
Ejemplo 4/16: for
/* Cambio de saturación, mientras el matiz y el brillo se mantienen constantes */
colorMode(HSB);
for (int i = 0; i < 100; i++) {
stroke(132, i*2.5, 204);
line(i, 0, i, 100);
}
Ejemplo 5/16: for
/* Cambio de brillo, mientras el matiz y la saturación se mantienen constantes */
colorMode(HSB);
for (int i = 0; i < 100; i++) {
stroke(132, 108, i*2.5);
line(i, 0, i, 100);
}
Ejemplo 6/16: for
/* Cambio de saturación y brillo, mientras el matiz se mantiene constante */
colorMode(HSB);
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 100; j++) {
stroke(132, j*2.5, i*2.5);
point(i, j);
}
}
Ejemplo 7/16: for
// Cambio del azul al verde en modo RGB
colorMode(RGB);
for (int i = 0; i < 100; i++) {
float r = 61 + (i*0.92);
float g = 156 + (i*0.48);
float b = 204 - (i*1.43);
stroke(r, g, b);
line(i, 0, i, 100);
}
Ejemplo 8/16: for
// Cambio del azul al verde en modo HSB
colorMode(HSB, 360, 100, 100);
for (int i = 0; i < 100; i++) {
float newHue = 200 - (i*1.2);
stroke(newHue, 70, 80);
line(i, 0, i, 100);
}
Ejemplos 9 a 12/16: for
Ejemplos 13 a 16/16: for
Ejemplos 1/8: iteraciones anidadas
for (int y = 1; y < 100; y += 10) {
for (int x = 1; x < y; x += 10) {
line(x, y, x+6, y+6);
line(x+6, y, x, y+6);
}
}
Ejemplos 2/8: iteraciones anidadas
noStroke();
for (int y = 0; y < 100; y += 10) {
for (int x = 0; x < 100; x += 10) {
fill((x+y) * 1.4);
rect(x, y, 10, 10);
}
}
Ejemplos 3 a 4/8: iteraciones anidadas
Ejemplos 5 a 6/8: iteraciones anidadas
Ejemplos 7 a 8/8: iteraciones anidadas
Ejercicio 8
• EJ08: Utilizar dos estructuras for() para generar:– un fondo con cambio de matiz;
– y por sobre este, una serie de ocho cuadrados negros concéntricos (el menor deberá tener 10 px de lado y el mayor 80 px), separados por una distancia de 5 px en cada uno de sus lados.
• Comentar todas las instrucciones.
Parte 8Aleatoriedad
Valores inesperados La función random() es utilizada para crear valores
impredecibles dentro de un rango especificado por sus parámetros.
Los números devueltos por la función random() son siempre de punto flotante.
random(valorAlto)random(valorBajo, valorAlto)
random(5); // regresa valores entre 0.0 y 5.0random(5.0); // regresa valores entre 0.0 y 5.0random(-5.0, 10.2); // regresa valores entre -5.0 y 10.2
Ejemplos (I)
smooth();
strokeWeight(10);
stroke(0, 130);
line(0, random(100), 100, random(100));
line(0, random(100), 100, random(100));
line(0, random(100), 100, random(100));
line(0, random(100), 100, random(100));
line(0, random(100), 100, random(100));
Ejemplos (II)
smooth();strokeWeight(20);float r = random(5, 45);stroke(r * 5.6, 230);line(0, r, 100, random(55, 95));r = random(5, 45);stroke(r * 5.6, 230);line(0, r, 100, random(55, 95));r = random(5, 45);stroke(r * 5.6, 230);line(0, r, 100, random(55, 95));
Ejemplos (III)
background(0);
stroke(255, 60);
for (int i = 0; i < 100; i++) {
float r = random(10);
strokeWeight(r);
float desplazamiento = r * 5.0;
line(i-20, 100, i+desplazamiento, 0);
}
randomSeed() - Semillas
Ya que el ordenador no puede “inventar numeros al azar”, estos se obtienen con más o menos complejas ecuaciones. Por lo tanto son repetibles.
Para obtener cadenas de valores reiterados, utilizamos una semilla mediante la función randomSeed().
randomSeed(valor)
El valor debe ser siempre un int.
Ejemplo
int s = 6; // Valor de semilla, probar: s=6 o s=12background(0);stroke(255, 60);randomSeed(s); // Produce los mismos nros cada vezfor (int i = 0; i < 100; i++) {
float r = random(10);strokeWeight(r);float desplazamiento = r * 5;line(i-20, 100, i+ desplazamiento, 0);
}
noise() – Ruido (I)
La función noise() es utilizada para crear valores inesperados de una manera más controlada. Utiliza la técnica de ruido Perlin, fue desarrollada por el matemático Ken Perlin en 1985.
Funciona interpolando valores aleatorios para crear transiciones más suaves que las obtenidas mediante la función random(). Siempre devuelve valores de punto flotante entre 0.0 y 1.0.
noise() – Ruido (II)
noise(x)
noise(x, y)
noise(x, y, z)
La versión con un solo parámetro es utilizada para crear una secuencia única de números aleatorios, Los parámetros adicionales producen ruido en más dimensiones (2D: texturas, 3D: formas, texturas 3D o texturas animadas en 2D).
noise() – Ruido (III) Los números devueltos por noise() pueden resultar más
cercanos o más lejanos del anterior mediante cambios en el parámetro de frecuencia de incremento.
Como regla general, mientras más pequeña sea la diferencia, más suave resultará la secuencia de ruido. Entonces resulta que un incremento pequeño genera números más cercanos al valor anterior que un incremento mayor.
Se estila nombrar la variable de incremento como inc.
noiseSeed() – semilla de ruido
La función noise() suele ser utilizada en conjunto con la función noiseSeed(), siendo esta similar a randomSeed().
Generalmente se utilizan valores de incremento que se sitúan en el rango 0.005 a 0.03.
Ejemplo con un parámetro
size(600, 100);float v = 0.0;float inc = 0.1; // Probar 0.1 o 0.01noStroke();fill(0);noiseSeed(0);for (int i = 0; i < width; i = i+4) {float n = noise(v) * 70.0;rect(i, 10 + n, 3, 20);v = v + inc;
}
Ejemplo con dos parámetros
float xnoise = 0.0;float ynoise = 0.0;float inc = 0.04; //Probar 0.04, 0.02 o 0.1for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float gray = noise(xnoise, ynoise) * 255; stroke(gray); point(x, y); xnoise = xnoise + inc; } xnoise = 0; ynoise = ynoise + inc;}
Lectura recomendada Capítulo “Math 4: Random” (pag. 127).
Ejercicio 9
• EJ09: Utilizar una estructura for() para generar:– diez círculos sin relleno, con posición, diámetro y color aleatorios.
• Usar función random().
• Comentar todas las instrucciones.
Ejercicio 10
• EJ10: Utilizar una estructura for() para generar:– diez rectángulos sin relleno, con posición, diámetro y color aleatorios pero cuyos
valores resulten cercanos entre sí.
• Usar función noise().
• Comentar todas las instrucciones.
Parte 9Continuidad: de la imagen
estática a la dinámica
Ejecución continua
Todos los bocetos programados hasta el momento son ejecutados por única vez y luego se detienen.
Programas animados o sensibles a información en vivo deben ejecutarse continuamente.
Función draw()
Los programas que se ejecutan continuamente DEBEN incluir una función llamada draw().
El código incluido en el bloque de la función draw() se ejecuta en forma de BUCLE hasta que el usuario presione el botón de detención o cierre la ventana.
Un programa puede tener un único draw().
Cada vez que el bloque draw() finaliza, dibuja un nuevo cuadro (frame) y comienza nuevamente el código del bloque desde su primera línea.
Bloque de función draw()
Por defecto, el ordenador intenta dibujar 60 cuadros por segundo.
Si utilizamos la variable de sistema frameRate podremos saber cuantos cuadros máximos por segundo puede renderizar nuestro ordenador.
void draw() {
println(frameRate);
}
Función frameRate() La función frameRate() determina el número máximo de cuadros
por segundo que renderizará el programa siempre y cuando no exceda los valores posibles a realizar por el sistema.
Si utilizamos la variable de sistema frameCount podremos llevar un registro de la cantidad de cuadros renderizados hasta el momento:
void draw() { frameRate(1); println(frameCount);}
Animación (I)
Creamos animaciones cuando cambiamos atributos visuales de un cuadro al otro:
float y = 0.0;
void draw() {frameRate(30);line(0, y, 100, y);y = y + 0.5;
}
Animación (II)
Debe ser observado que se define la variable y por fuera del bloque draw(). ¿Qué hubiera pasado si la incluíamos dentro?
void draw() {frameRate(30);float y = 0.0;line(0, y, 100, y);y = y + 0.5;
}
Animación (III)
Para que tengamos una animación la variable y debe ser declarada fuera del bloque, ya que de lo contrario esta se redefiniría y reasignaría al MISMO valor cada vez que se vuelve a iniciar el bucle.
Animación (IV) Como podemos observar, el fondo del lienzo va cambiando de color
de acuerdo a cada nueva línea que aparece.
SI deseamos poder ver el avance de la línea sobre el fondo, debemos “refrescar” el fondo cada vez que reiniciemos el draw() mediante la función background().
float y = 0.0;
void draw() {frameRate(30);background(204);line(0, y, 100, y);y = y + 0.5;
}
Animación (V)
Incorporando una pequeña expresión en los parámetros de background(), la animación va ganando interés!
float y = 0.0;
void draw() {frameRate(30);background(y * 2.5);line(0, y, 100, y);y = y + 0.5;
}
Animación (VI) Y si incorporamos una pequeña estructura de condicional
generamos ciclos visibles!
float y = 0.0;
void draw() { frameRate(30); background(204); line(0, y, 100, y); y = y + 0.5;
if (y > height) { y = 0; }}
Animación (VII) O bien podemos volver a tomar la animación IV y modificarla para obtener otro resultado visual:
int y = 0;int direccion = 1;
void draw() { frameRate(30); background(204);
if (y > 100) { direccion = -1; }
if (y < 0) { direccion = 1; }
y = y + (1 * direccion); line(0, y, 100, y);}
Animación (VIII) Esta es otra manera de resolver la animación anterior. No existen formas fijas de
resolver un problema, por suerte podemos abordar diferentes enfoques para la resolución del mismo:
int y = 0;int direccion = 1;
void draw() { frameRate(30); background(204);
if (y > 100 || y < 0) { direccion *= -1; }
y = y + (1 * direccion); line(0, y, 100, y);}
Lectura recomendada Capítulo “Structure 2: Continuous” (pag. 173).
Ejercicio 11
• EJ11: Utilizar la estructura draw() y dos variables para animar:– una elipse y tres líneas.
• Comentar todas las instrucciones.
Parte 10Anatomía de un programa
estructurado
Función setup() Para optimizar la programación, nos damos cuenta que ciertas funciones sólo
deben ser ejecutadas una vez. Exponemos como ejemplo la función frameRate() de los bocetos anteriores.
float y = 0.0;
void setup() { frameRate(30);}
void draw() { background(y * 2.5); y = y + 0.5; line(0, y, 100, y); if (y > 100) { y = 0; }}
Bloque de Función setup()
Cuando en Processing se ejecuta un programa, el código que se encuentra fuera de los bloques setup() y draw() es contemplado en primera instancia.
Luego, se ejecuta por única vez el código dentro del bloque setup().
Por último, se ejecuta continuamente el contenido del bloque draw().
Diagrama de estructura formal standard de un programa
Sección de variables globales
Sección de inicialización
Sección de dibujo
Ejemplo
float y = 0.0;
void setup() {size(100, 100);smooth();fill(0);
}void draw() {
background(204);ellipse(50, y, 70, 70);y += 0.5;if (y > 150) {
y = -50.0;
}}
Observaciones (I)
Las variables que cambian en cada repetición del bloque draw() DEBEN ser declaradas fuera de los bloques de setup() y draw().
Si su programa dibuja un solo cuadro, puede escribirlo por completo dentro del bloque setup():
void setup() {size(100, 100);smooth();fill(0);ellipse(50, 50, 66, 66);
}
Observaciones (II)
Otra manera de realizar un solo cuadro, es utilizar la función noLoop() dentro del bloque setup().
void setup() {size(100, 100);smooth();fill(0);noLoop();
}void draw() {
ellipse(50, 50, 66, 66);}
Ámbito de las variables int d = 51; // d variable global
void setup() {size(100, 100);int val = d * 2; // val variable local en
setup()fill(val);
}
void draw() {int y = 60; // y variable local en draw()line(0, y, d, y);y -= 25;line(0, y, d, y);
}
Lectura recomendada Capítulo “Structure 2: Continuous” (pag. 173).
Parte 11Interactividad: ratón y teclado
Ratón: variables de sistema
El mouse no es más que un indicador XY de posición de pantalla.
Las variables de sistema más usadas:mouseXmouseYpmouseXpmouseYmousePressedmouseButton
Ejemplo de variables de sistema mouseX y mouseY 1/6
void draw() {
frameRate(12);
println(mouseX + " : " + mouseY);
}
Ejemplo de variables de sistema mouseX y mouseY 2/6
void setup() {
size(200, 200);
smooth();
noStroke();
}
void draw() {
background(126);
ellipseMode(CENTER);
ellipse(mouseX, mouseY, 33, 33);
}
Ejemplo de variables de sistema mouseX y mouseY 3/6
void setup() { size(200, 200); smooth(); noStroke();}void draw() { background(126); ellipse(mouseX, 16, 33, 33); ellipse(mouseX + 20, 50, 33, 33); ellipse(mouseX - 20, 84, 33, 33);}
Ejemplo de variables de sistema mouseX y mouseY 4/6
void setup() { size(200, 200); smooth(); noStroke();}void draw() { background(126); ellipse(mouseX, 16, 33, 33); ellipse(mouseX / 2, 50, 33, 33); ellipse(mouseX * 2, 84, 33, 33);}
Ejemplo de variables de sistema mouseX y mouseY 5/6void setup() { size(200, 200); smooth(); noStroke();}void draw() { float x = mouseX; float y = mouseY; float ix = width - mouseX; // Inverso de X float iy = mouseY - height; // Inverso de Y background(126); fill(255, 150); ellipse(x, height/2, y, y); fill(0, 159); ellipse(ix, height/2, iy, iy);}
Ejemplo de variables de sistema mouseX y mouseY 6/6
void setup() { size(200, 200); smooth(); noStroke();}void draw() { background(126); float normX = mouseX / float(width); ellipse(mouseX, 16, 33, 33); ellipse(pow(normX, 4) * width, 50, 33, 33); ellipse(pow(normX, 8) * width, 84, 33, 33);}
Ejemplo de variables de sistema pmouseX y pmouseY 1/2
void draw() {
frameRate(12);
println(pmouseX - mouseX);
}
Ejemplo de variables de sistema pmouseX y pmouseY 2/2
void setup() {
size(100, 100);
strokeWeight(8);
smooth();
}
void draw() {
background(204);
line(mouseX, mouseY, pmouseX, pmouseY);
}
Ejemplo de if y variable de sistema mousePressed y otros
void setup() {
size(640, 200);
background(102);
}
void draw() {
stroke(255);
if(mousePressed == true) {
line(mouseX, mouseY, pmouseX, pmouseY);
}
}
Ejemplo de estructura if y variable de sistema mousePressed
// Pinta el relleno a negro
// cuando se presiona el botón del ratón.
void draw() {
if (mousePressed == true) {
fill(0);
}
else {
fill(255);
}
rect(25, 25, 50, 50);
}
Ejemplo de estructura if y variable de sistema mouseButton 1/2
// Pinta el relleno a negro si presiono el botón izquierdo,
// a blanco si es el derecho y a gris si es el medio.
// mouseButton acepta las constantes: LEFT, RIGHT y CENTER.
void setup() {
size(100, 100);
}
void draw() {
if (mouseButton == LEFT) {
fill(0); // Negro
}
else if (mouseButton == RIGHT) {
fill(255); // Blanco
}
else {
fill(126); // Gris
}
rect(25, 25, 50, 50);
}
Ejemplo de estructura if y variable de sistema mouseButton 2/2
// Versión alternativa del ejemplo anterior.
void setup() {
size(100, 100);
}
void draw() {
if (mousePressed == true) {
if (mouseButton == LEFT) {
fill(0); // Negro
}
else if (mouseButton == RIGHT) {
fill(255); // Blanco
}
}
else {
fill(126); // Gris
}
rect(25, 25, 50, 50);
}
Teclado: variables de sistema
Las variables de sistema más usadas:keyPressedkeykeyCode
Ejemplo de variable de sistema keyPressed 1/2// Dibuja un rectángulo mientras se // mantiene presionada una tecla.
void setup() { size(100, 100); smooth(); strokeWeight(4);}void draw() { background(204); if (keyPressed == true) { rect(40, 40, 20, 20); } else { line(20, 20, 80, 80); }}
Ejemplo de variable de sistema keyPressed 2/2// Mueve una línea si se // mantiene presionada una tecla.
int x = 20;void setup() { size(100, 100); smooth(); strokeWeight(4);}void draw() { background(204); if (keyPressed == true) { x++; } line(x, 20, x-60, 80);}
Ejemplo de variables de sistema keyPressed y key// Dibuja una línea si se mantiene// presionada la tecla 'a' o 'A'.
void setup() { size(100, 100); smooth(); strokeWeight(4);}void draw() { background(204); if ((keyPressed == true) && ((key == 'a') || (key == 'A'))) { line(50, 25, 50, 75); } else { ellipse(50, 50, 50, 50); }}
Ejemplo de variables de sistema keyPressed, key y keyCode// Uso de variable de sistema keyCode.// keyCode acepta como constantes:// las teclas de dirección UP, DOWN, LEFT, RIGHT,// y ALT, CONTROL y SHIFT.
int y = 35;void setup() { size(100, 100);}void draw() { background(204); line(10, 50, 90, 50); if (key == CODED) { if (keyCode == UP) { y = 20; } else if (keyCode == DOWN) { y = 50; } } else { y = 35; } rect(25, y, 50, 30);}
Ratón: Funciones de evento
Las funciones de evento más usadas: mousePressed()
El código dentro de esta función se ejecuta una vez cuando se presiona un botón de ratón.
mouseReleased() El código dentro de esta función se ejecuta una vez cuando se
libera un botón de ratón.mouseMoved()
El código dentro de esta función se ejecuta una vez cuando se mueve un ratón.
mouseDragged() El código dentro de esta función se ejecuta una vez cuando se
mueve un ratón mientras se encuentra presionado un botón de ratón.
Ejemplo de if...else y función de evento mouseReleasedint valor = 0;
void draw() { fill(valor); rect(25, 25, 50, 50);}
void mouseReleased() { if(valor == 0) { valor = 255; } else { valor = 0; }}
Teclado: Funciones de evento
Las funciones de evento más usadas: keyPressed()
El código dentro de esta función se ejecuta una vez cuando se presiona cualquier tecla.
keyReleased() El código dentro de esta función se ejecuta una vez
cuando se libera cualquier tecla.
Ejemplo de función de evento keyPressed() y keyReleased()boolean drawT = false;
void setup() { size(100, 100); noStroke();}void draw() { background(204); if (drawT == true) { rect(20, 20, 60, 20); rect(39, 40, 22, 45); }}void keyPressed() { if ((key == 'T') || (key == 't')) { drawT = true; }}void keyReleased() { drawT = false;}
Ejemplo de función de evento mousePressed() y keyPressed()
void setup() { size(200, 200); background(255); fill(0, 102);}
void draw() {}
void mousePressed() { rectMode(CENTER); rect(mouseX, mouseY, 32, 32);}
void keyPressed() { background(255);}
Lectura recomendada Capítulo “Input 1: Mouse I” (pag. 205). Capítulo “Drawing 1: Static Forms” (pag. 217). Capítulo “Input 2: Keyboard” (pag. 223). Capítulo “Input 3: Events” (pag. 229).
Información complementaria
Ejemplo de variables de sistema mouseX y mouseY y estructura if 1/4
// La posición del cursor pinta la mitad izquierda// o derecha de la ventana de visualización.
void setup() { size(100, 100); noStroke(); fill(0);}void draw() { background(204); if (mouseX < 50) { rect(0, 0, 50, 100); // Izquierda } else { rect(50, 0, 50, 100); // Derecha }}
Ejemplo de variables de sistema mouseX y mouseY y estructura if 2/4
// La posición del cursor pinta el tercio izquierdo// central o derecho de la ventana de visualización.
void setup() { size(100, 100); noStroke(); fill(0);}void draw() { background(204); if (mouseX < 33) { rect(0, 0, 33, 100); // Izquierda } else if ((mouseX >= 33) && (mouseX <= 66)) { rect(33, 0, 33, 100); // Centro } else { rect(66, 0, 33, 100); // Derecha }}
Ejemplo de variables de sistema mouseX y mouseY y estructura if 3/4
// La posición del cursor pinta un cuadrante// de la ventana de visualización.
void setup() { size(100, 100); noStroke(); fill(0);}void draw() { background(204); if ((mouseX <= 50) && (mouseY <= 50)) { rect(0, 0, 50, 50); // Superior-izquierdo } else if ((mouseX <= 50) && (mouseY > 50)) { rect(0, 50, 50, 50); // Inferior-izquierdo } else if ((mouseX > 50) && (mouseY < 50)) { rect(50, 0, 50, 50); // Superior-derecho } else { rect(50, 50, 50, 50); // Inferior-derecho }}
Ejemplo de if...else… if, variables de sistema mouseX, mouseY y operadores lógicos
void setup() {
size(200, 200);
}
void draw() {
background(255);
stroke(0);
line(100, 0, 100, 200);
line(0, 100, 200, 100);
// Relleno de color negro
noStroke();
fill(0);
if (mouseX < 100 && mouseY < 100) {
rect(0, 0, 100, 100);
}
else if (mouseX > 100 && mouseY < 100) {
rect(100, 0, 100, 100);
}
else if (mouseX < 100 && mouseY > 100) {
rect(0, 100, 100, 100);
}
else if (mouseX > 100 && mouseY > 100) {
rect(100, 100, 100, 100);
}
}
Ejemplo de variables de sistema mouseX y mouseY y estructura if 4/4
// La posición del cursor cambia el color// de relleno de un área rectangular.
void setup() { size(100, 100); noStroke(); fill(0);}void draw() { background(204); if ((mouseX > 40) && (mouseX < 80) && (mouseY > 20) && (mouseY < 80)) { fill(255); } else { fill(0); } rect(40, 20, 40, 60);}
Ejercicio 12
• EJ12: Animar tres círculos de acuerdo a los datos ingresados mediante el ratón.
• Comentar todas las instrucciones.
Parte 12Funciones de usuario
Introducción 1/2 En Processing cualquier usuario puede programar sus propias funciones. Llamamos
a esto función de usuario.
Una función es un módulo de programación autocontenido.
Las funciones de usuario hacen más conciso el código redundante al extraer los elementos comunes e incluirlos en bloques de código para que puedan ejecutarse tantas veces se quiera dentro del programa.
Esto permite una lectura más fácil del código y reduce las probabilidades de error al actualizar el código.
Las funciones generalmente tienen parámetros que definen sus acciones.
Las funciones pueden operar de forma diferente dependiendo del número de parámetros usados.
Una función puede ser imaginada como una caja con mecanismos dentro que actúan sobre los datos ingresados y devuelven un resultado.
Introducción 2/2 Convencionalmente posee una o varias entradas, un bloque de código que procesa dichas
entradas, y finalmente una salida.
Algunos ejemplos de diagramas de función de usuario:
Abstracción 1/2 En terminología de software se llama abstracción al proceso que permite esconder
los detalles de realización y concentrarnos en el resultado.
En realidad todas las funciones de sistema que hemos visto hasta el momento son, técnicamente, abstracciones: los autores han escondido los detalles de implementación para que el programador se concentre en los resultados.
Cuando construimos funciones estas podrán devolver un resultado o no. Depende evidentemente de qué querramos hacer con ella. Pero en el caso de optar por la no devolución de un resultado deberemos comenzar por construir el bloque con la palabra clave void.
Es por esto que todos los ejemplos que veremos a continuación comienzan a construirse con dicha palabra clave.
Introducción a las funciones 1/12// Programación basada en primitivas
// usando funciones personalizadas o funciones definidas por el usuario
// Autor: Ariel Malka | www.chronotext.org
// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461
// Traducción: Raúl Lacabanne - 2009
void setup() { // esta función será llamada automáticamente por Processing cuando el programa se ejecute
size(200, 200); // configura el tamaño de pantalla
}
void draw() { // esta función también será llamada automáticamente luego de setup()
rect(100, 30, 90, 160); // crea un rectángulo
}
// El "flujo de ejecución" será el siguiente:
// 1) setup()
// 2) size(200, 200)
// 3) draw()
// 4) rect(10, 10, 90, 160);
Introducción a las funciones 2/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
void setup() { size(200, 200); background(255); // establece el color de fondo en blanco }
void draw() { // cuatro llamadas a la función definida por el usuario cross()
// el "origen del sistema de coordenadas" por defecto se encuentra en la esquina superior izquierda de la pantalla cruz(); // esquina superior izquierda de la cruz en 0,0
translate(50, 50); // el "origen del sistema de coordenadas" se mueve 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 50,50
translate(50, 50); // el "origen del sistema de coordenadas" se mueve otros 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 100,100
translate(50, 50); // el "origen del sistema de coordenadas" se mueve otros 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 150,150 }
void cruz() { // nuestra función definida por el usuario (podemos nombrarla como querramos) noStroke(); fill(255, 0, 0); // rojo rect(0, 10, 30, 10); rect(10, 0, 10, 30); }
Introducción a las funciones 3/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
// cuando dibujamos usando funciones es importante poder tener la posibilidad de dibujar una forma desde el centro... void setup() { size(200, 200); background(255); } void draw() { cruz(); // esquina superior izquierda de la cruz en 0,0
translate(50, 50); // el "origen del sistema de coordenadas" se mueve 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 50,50
translate(50, 50); // el "origen del sistema de coordenadas" se mueve otros 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 100,100
translate(50, 50); // el "origen del sistema de coordenadas" se mueve otros 50 px a la derecha y 50 px abajo cruz(); // esquina superior izquierda de la cruz en 150,150 } void cruz() { noStroke(); fill(255, 0, 0); rectMode(CENTER); // los rectángulos serán dibujados desde el centro rect(0, 0, 30, 10); rect(0, 0, 10, 30); }
Introducción a las funciones 4/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
// cuando creamos funciones personalizadas es posible agregar un número arbitrario de "parámetros"// que actuarán como variables dentro del bloque de la función
void setup() { size(200, 200); background(255); }
void draw() { // cuando llamamos a nuestra función, estamos "pasando" 2 parámetros que // afectarán la posición de la cruz
cruz(0, 0); // el centro de la cruz se encuentra en 0,0 cruz(50, 50); // el centro de la cruz se encuentra en 50,50 cruz(100, 100); // el centro de la cruz se encuentra en 100,100 cruz(150, 150); // el centro de la cruz se encuentra en 150,150 }
void cruz(float ejeX, float ejeY) { // estamos usando dos parámetros (los nombramos como querramos) noStroke(); fill(255, 0, 0); rectMode(CENTER);
// ejeX y ejeY están actuando como variables y han sido declaradas por fuera del bloque de la función rect(ejeX, ejeY, 30, 10); rect(ejeX, ejeY, 10, 30); }
Introducción a las funciones 5/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
// introducción de parámetros adicionales void setup() { size(200, 200); background(255); } void draw() { // definición de variables que contienen información de color color rojo = color(255, 0, 0); color azul = color(51, 153, 255); color gris = color(128, 128, 128); color verde = color(153, 255, 51); // estamos pasando un tercer parámetro que afectará el tamaño de la cruz // y un cuarto parámetro que permitirá en control de color de la cruz cruz(0, 0, 1, rojo); cruz(50, 50, 3, azul); cruz(100, 100, 0.5, gris); cruz(150, 150, 5.5, verde); } void cruz(float x, float y, float tamanio, color colorCruz) { // 2 nuevos parámetros han sido agregados a la función noStroke(); fill(colorCruz); // esto controla el color de la cruz rectMode(CENTER); rect(x, y, 30 * tamanio, 10 * tamanio); rect(x, y, 10 * tamanio, 30 * tamanio); }
Introducción a las funciones 6/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
// ¡cuando se programa es posible alcanzar los mismos resultados utilizando diferentes acercamientos! void setup() { size(200, 200); background(255); } void draw() { fill(255, 0, 0); // rojo cruz(0, 0, 1); fill(51, 153, 255); // azul cruz(50, 50, 3); fill(128, 128, 128); // gris cruz(100, 100, 0.5); fill(153, 255, 51); // verde cruz(150, 150, 5.5); } void cruz(float x, float y, float tamanio) { noStroke(); rectMode(CENTER); rect(x, y, 30 * tamanio, 10 * tamanio); rect(x, y, 10 * tamanio, 30 * tamanio); }
Introducción a las funciones 7/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?board=TheoryandPractice;action=display;num=1078263461// Traducción: Raúl Lacabanne - 2009
// usar funciones que usan otras funciones... // primero hagamos dos figuras diferentes y examinémoslas por separado void setup() { size(200, 200); background(255); fill(0, 0, 0); // todas las figuras con relleno negro} void draw() { bubbles(50, 100); tube(150, 100); } void tube(float x, float y) { noStroke(); rectMode(CENTER); ellipseMode(RADIUS); rect(x, y, 40, 100); rect(x, y - 50, 60, 10); ellipse(x, y + 50, 20, 20); } void bubbles(float x, float y) { noStroke(); ellipseMode(RADIUS); ellipse(x + 4, y - 24, 10, 10); ellipse(x - 4, y, 9, 9); ellipse(x + 4, y + 24, 8, 8); ellipse(x - 4, y + 48, 7, 7); }
Introducción a las funciones 8/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas
por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?
board=TheoryandPractice;action=display;num=1078263461
// Traducción: Raúl Lacabanne - 2009
// usar funciones que usan otras funciones... // ahora hagamos una función que usa juntas y de manera
compuesta nuestras dos funciones anteriores void setup() { size(200, 200); background(255); } void draw() { peligro(50, 100); peligro(75, 80); peligro(100, 100); peligro(125, 120); peligro(150, 100); } void peligro(float x, float y) { fill(0, 0, 0); // negro tubo(x, y); fill(255, 255, 255); // blanco burbujas(x, y); }
void tubo(float x, float y) { noStroke(); rectMode(DIAMETER); ellipseMode(RADIUS); rect(x, y, 40, 100); rect(x, y - 50, 60, 10); ellipse(x, y + 50, 20, 20); } void burbujas(float x, float y) { noStroke(); ellipseMode(RADIUS); ellipse(x + 4, y - 24, 10, 10); ellipse(x - 4, y, 9, 9); ellipse(x + 4, y + 24, 8, 8); ellipse(x - 4, y + 48, 7, 7); }
Introducción a las funciones 9/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas
por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?
board=TheoryandPractice;action=display;num=1078263461
// Traducción: Raúl Lacabanne - 2009
// usar funciones que usan otras funciones...
// generar una función que utiliza nuestras 2 piezas compuestas juntas
// más la introducción de escalado (usando un parámetro adicional)
void setup() { size(200, 200); background(255); }
void draw() { peligro(50, 100, 13); // más grande peligro(150, 100, 5); // más pequeño}
void peligro(float x, float y, float tamanio) { fill(0, 0, 0); // negro tubo(x, y, tamanio);
fill(255, 255, 255); // blanco burbujas(x, y, tamanio); }
void tubo(float x, float y, float tamanio) { noStroke(); rectMode(DIAMETER); ellipseMode(RADIUS);
rect(x, y, 4 * tamanio, 10 * tamanio); rect(x, y - 5 * tamanio, 6 * tamanio, 1 * tamanio); ellipse(x, y + 5 * tamanio, 2 * tamanio, 2 * tamanio); }
void burbujas(float x, float y, float tamanio) { noStroke(); ellipseMode(RADIUS);
ellipse(x + 0.4 * tamanio, y - 2.4 * tamanio, 1 * tamanio, 1 * tamanio);
ellipse(x - 0.4 * tamanio, y, 0.9 * tamanio, 0.9 * tamanio);
ellipse(x + 0.4 * tamanio, y + 2.4 * tamanio, 0.8 * tamanio, 0.8 * tamanio);
ellipse(x - 0.4 * tamanio, y + 4.8 * tamanio, 0.7 * tamanio, 0.7 * tamanio);
}
Introducción a las funciones 10/12// Programación basada en primitivas// usando funciones personalizadas// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?
board=TheoryandPractice;action=display;num=1078263461
// Traducción: Raúl Lacabanne - 2009
// usar funciones que usan otras funciones... // generar una función que utiliza nuestras 2 piezas
compuestas juntas// más la introducción de escalado basado en una matriz
de transformación void setup() { size(200, 200); background(255); } void draw() { peligro(50, 100, 1.3); peligro(150, 100, 0.5); } void peligro(float x, float y, float sz) { fill(0, 0, 0); // negro pushMatrix(); translate(x, y); scale(sz); tubo(); popMatrix(); fill(255, 255, 255); // blanco pushMatrix(); translate(x, y); scale(sz); burbujas(); popMatrix(); }
void tubo() { noStroke(); rectMode(DIAMETER); ellipseMode(RADIUS); rect(0, 0, 40, 100); rect(0, - 50, 60, 10); ellipse(0, 50, 20, 20); } void burbujas() { noStroke(); ellipseMode(RADIUS); ellipse(4, - 24, 10, 10); ellipse(-4, 0, 9, 9); ellipse(4, 24, 8, 8); ellipse(-4, 48, 7, 7); }
Introducción a las funciones 11/12// Programación basada en primitivas// usando funciones personalizadas o funciones definidas
por el usuario// Autor: Ariel Malka | www.chronotext.org// URL: http://processing.org/discourse/yabb/YaBB.cgi?
board=TheoryandPractice;action=display;num=1078263461
// Traducción: Raúl Lacabanne - 2009
// usar funciones que usan otras funciones...
// generar una función que utiliza nuestras 2 piezas compuestas juntas
// más la introducción de escalado basado en una matriz de transformación
void setup() { size(200, 200); background(255); }
void draw() { peligro(50, 100, 1.3); peligro(150, 100, 0.5); }
void peligro(float x, float y, float tamanio) { fill(255, 0, 0); // rojo pushMatrix(); translate(x, y); scale(tamanio); tubo(); fill(255, 255, 255); // blanco burbujas(); popMatrix(); }
void tubo() { noStroke(); rectMode(DIAMETER); ellipseMode(RADIUS);
rect(0, 0, 40, 100); rect(0, - 50, 60, 10); ellipse(0, 50, 20, 20); }
void burbujas() { noStroke(); ellipseMode(RADIUS);
ellipse(4, - 24, 10, 10); ellipse(-4, 0, 9, 9); ellipse(4, 24, 8, 8); ellipse(-4, 48, 7, 7); }
Introducción a las funciones 12/12void setup(){ size(640, 360); background(102); smooth();}
void draw() {
elipseVariable(mouseX, mouseY, pmouseX, pmouseY);}
// elipseVariable() calcula la velocidad del ratón.// Si el ratón se mueve lentamente: dibuja una elipse pequeña,// si el ratón se mueve rápidamente: dibuja una elipse mayor si
void elipseVariable(int x, int y, int px, int py) { float speed = abs(x-px) + abs(y-py); stroke(speed); ellipse(x, y, speed, speed);}
Valor de retorno 1/2 En todos los ejemplos vistos hasta ahora, hemos visto que la salida, por ejemplo,
de una funcion de primitivas ha sido en forma de dibujo en el área de representación.
Sin embargo a veces preferiremos que la salida sea un número u otro tipo de dato. La salida de una función se llama valor de retorno.
Se espera que todas las funciones regresen un valor, tal como un entero o un decimal. Si la función no regresa un valor, se utiliza la palabra especial void. El tipo de dato regresado por una función se encuentra a la izquierda del nombre de función.
El comando clave return es usado para salir de una función y regresar al lugar desde el cual fue llamado. Cuando una función regresa un valor, return es usado para especificar qué valor debe ser regresado.
La instrucción que incluye return es típicamente la última de una función, ya que la misma finaliza inmediatamente después de un retorno.
Ya hemos usado funciones que devuelven valores: por ej.: random() regresa un decimal, color() regresa un tipo de dato de color, etc.
Si una función regresa un valor, dicha función casi siempre aparece a la derecha de un operador de asignación o como parte de una expresión mayor.
Una función que no regresa un valor es frecuentemente usada como una instrucción completa.
Valor de retorno 2/2 Las funciones no están limitadas a regresar números: pueden regresar boolean,
String, PImage o cualquier otro tipo de dato. Para escribir una función de usuario que regrese un valor, reemplace void con
el tipo de dato que necesite regresar, e incluya dentro de la función la palabra clave return seguido de la variable que contenga el valor que desee regresar para habilitar la salida del mismo.
A continuación veremos un ejemplo:
void setup() {
size(100, 100);
float f = promedio(12.0, 6.0); // Asigna 9.0 a f
println(f);
}
float promedio(float num1, float num2) {
float av = (num1 + num2) / 2.0;
return av;
}
Sobrecarga de funciones (Function overloading) 1/2
Se llama sobrecarga de funciones al procedimiento de crear diferentes versiones de una misma función.
Las distintas versiones pueden compartir el mismo nombre de función siempre y cuando tengan diferentes números de parámetros o tipos de datos de los mismos. Es decir, un programa puede tener dos funciones con el mismo número de parámetros, pero sólo si el tipo de dato de uno de sus parámetros es diferente.
Processing identifica qué versión de función debe ejecutar al comparar el número y el tipo de dato de sus parámetros.
Veamos el próximo ejemplo:
Sobrecarga de funciones (Function overloading) 2/2
void setup() {
size(100, 100);
smooth();
}
void draw() {
dibujoX(255); // Ejecuta primer dibujoX()
dibujoX(5.5); // Ejecuta segundo dibujoX()
dibujoX(0, 2, 44, 48, 36); // Ejecuta tercer dibujoX()
}
// dibujoX con el valor de gris determinado por el parámetro
void dibujoX(int gris) {
stroke(gris);
strokeWeight(20);
line(0, 5, 60, 65);
line(60, 5, 0, 65);
}
// dibujoX negro con el valor ancho de contorno determinado por el parámetro
void dibujoX(float ancho) {
stroke(0);
strokeWeight(ancho);
line(0, 5, 60, 65);
line(60, 5, 0, 65);
}
// dibujoX con la posición , el valor de gris, tamaño y el ancho de
// contorno determinados por sus correspondientes parámetros
void dibujoX(int gris, int ancho, int x, int y, int s) {
stroke(gris);
strokeWeight(ancho);
line(x, y, x+s, y+s);
line(x+s, y, x, y+s);
}
Lectura recomendada Capítulo “Structure 3: Functions” (pag. 181).
Ejercicio 13
• EJ13: Crear una forma animada autónoma que comunique la idea de "orden".
• Utilizar funciones de usuario.
• Comentar todas las instrucciones.
Ejercicio 14
• EJ14: Crear una forma animada autónoma que comunique la idea de "caos".
• Utilizar funciones de usuario.
• Comentar todas las instrucciones.
Ejercicio 15
• EJ15: Crear una forma interactiva que comunique la idea de "orden y caos".
• Utilizar funciones de usuario.
• Comentar todas las instrucciones.
Parte 13Otras funciones
matemáticas
Función sq() – Cuadrado
Para calcular el cuadrado de un número usamos la función sq(). La misma nos regresa el resultado, el cual será siempre un número positivo aunque usemos un valor negativo.
sq(valor)
float x = sq(1); // Asigna 1.0 a x: equivalente a 1 * 1
float y = sq(-5); // Asigna 25.0 a y: equivalente a -5 * -5
float z = sq(9); // Asigna 81.0 a z: equivalente a 9 * 9
Función sqrt() – Raíz cuadrada
La función sqrt() es usada para calcular la raíz cuadrada de un número. La misma regresa el resultado.
sqrt(valor)
Recordemos que también lo podemos expresar de la siguiente manera: (√a) == (a1/2 )
float r = sqrt(6561); // Asigna 81.0 a r
float s = sqrt(625); // Asigna 25.0 a s
float t = sqrt(1); // Asigna 1.0 a t
Función pow() – Potenciación
La función pow() calcula la potencia en función de dos términos: base y exponente. La misma regresa el resultado.
pow(base, exponente)
Recordemos que, cuando el exponente es una fracción irreducible, también lo podemos expresar de la siguiente manera:
(m√an) == (an/m )
float d = pow(1, 3); // Asigna 1.0 a d: equivalente a 1*1*1
float e = pow(3, 4); // Asigna 81.0 a e: equivalente a 3*3*3*3
float f = pow(3, -2); // Asigna 0.11 a f: equivalente a 1 / (3*3)
float g = pow(-3, 3); // Asigna -27.0 a g: equivalente a -3*-3*-3
Función norm() – Normalización 1/2
Muchas veces se vuelve conveniente convertir un rango de números dados al rango 0.0 a 1.0. A este procedimiento se lo llama normalización.
Cuando multiplicamos números entre 0.0 y 1.0, el resultado nunca será menor a 0.0 ni mayor a 1.0. Esto permite no salir de un rango determinado.
Desde luego que todas las operaciones de normalización deben ser realizadas con el tipo de dato float.
Para normalizar un número debemos dividirlo por el valor máximo que este represente. Por ejemplo: para normalizar una serie de valores entre 0.0 y 255.0, divida cada uno por 255.0:
Valor inicial Cálculo Valor normalizado
0.0 0.0 / 255.0 0.0
102.0 102.0 / 255.0 0.4
255.0 255.0 / 255.0 1.0
Función norm() – Normalización 2/2
Para simplificar esta tarea podemos utilizar la función norm():
norm(valor_a_convertir, valor_mínimo, valor_máximo)
Si el valor a convertir se encuentra fuera del rango, el resultado podrá ser menor a 0.0 o mayor a 1.0 de acuerdo al caso.
float x = norm(0.0, 0.0, 255.0); // Asigna 0.0 a x
float y = norm(102.0, 0.0, 255.0); // Asigna 0.4 a y
float z = norm(255.0, 0.0, 255.0); // Asigna 1.0 a z
Función lerp() – Interpolación lineal 1/2
Luego de la normalización, podemos convertir el número a otro rango mediante operaciones aritméticas.
Por ejemplo, para convertir desde un rango entre 0.0 y 1.0 al rango entre 0.0 y 500.0, simplemente los multiplicamos por 500.0. Para convertir números entre 0.0 y 1.0 al rango que se extiende entre 200.0 y 500.0, multiplicamos por 300 y luego sumamos 200.
Veamos a continuación algunos ejemplos de conversión:
Rango inicial de x Rango de destino de x Conversión
0.0 a 1.0 0.0 a 255.0 x * 255.0
0.0 a 1.0 -1.0 a 1.0 (x * 2.0) - 1.0
0.0 a 1.0 -20.0 a 60.0 (x * 80.0) - 20.0
Función lerp() – Interpolación lineal 2/2
Nuevamente, para simplificar esta tarea podemos utilizar la función lerp(). La misma presenta tres parámetros
lerp(valor_mínimo_del_rango_a_interpolar, valor_máximo_del_rango_a_interpolar, valor_a_interpolar)
El tercer parámetro (valor a interpolar) debe ser siempre un valor entre 0.0 y 1.0. Veamos algunos ejemplos:
float r = lerp(-20.0, 60.0, 0.0); // Asigna -20.0 a r
float s = lerp(-20.0, 60.0, 0.5); // Asigna 20.0 a s
float t = lerp(-20.0, 60.0, 1.0); // Asigna 60.0 a t
Función map() – Mapeo 1/2
Existe otra función que nos permite aplicar las operaciones de normalización e interpolación lineal en una: hablamos de la función map().
Con dicha función podemos convertir directamente un valor correspondiente a un rango de números a otro correspondiente a otro rango de números.
Posee cinco parámetros:
map(valor, mínimo1, máximo1, mínimo2, máximo2)
Donde valor corresponde al número de origen a mapear, mínimo1 y máximo1 a los números del rango origen, y mínimo2 y máximo2 a los números del rango de destino.
Función map() – Mapeo 2/2
El próximo ejemplo muestra el uso de map() para convertir valores del rango de origen 0 a 255 al rango de destino -1 a 1. Este proceso equivale a, primero, normalizar el valor, y luego a multiplicar y sumar para desplazar el rango de 0 a 1 al rango -1 a 1:
float x = map(20.0, 0.0, 255.0, -1.0, 1.0); // Asigna -0.84 a x
float y = map(0.0, 0.0, 255.0, -1.0, 1.0); // Asigna -1.0 a y
float z = map(255.0, 0.0, 255.0, -1.0, 1.0); // Asigna 1.0 a z
Función constrain() – Limitación de rangos 1/2
La función constrain() permite limitar un número a un rango determinado. Trabaja con enteros o decimales.
Posee tres parámetros:
constrain(valor, mínimo, máximo)
Donde valor corresponde al número a limitar, mínimo al valor mínimo posible y máximo al valor máximo posible. Esta función regresa el número mínimo si el parámetro valor es menor o equivalente al antedicho, regresa el número máximo si el mismo es mayor o equivalente, y regresa valor si se encuentra en el rango previsto.
int x = constrain(35, 15, 90); // Asigna 35 a x
int y = constrain(10, 15, 90); // Asigna 15 a y
int z = constrain(91, 15, 90); // Asigna 90 a z
Función constrain() – Limitación de rangos 2/2
// Limitar la posición de una elipse a una región determinada
void setup() { size(100, 100); smooth(); noStroke();}void draw() { background(0); // Limita mx entre 35 y 65 float mx = constrain(mouseX, 35, 65); // Limita my entre 40 y 60 float my = constrain(mouseY, 40, 60); fill(102); rect(20, 25, 60, 50); fill(255); ellipse(mx, my, 30, 30);}
Función dist() – Distancia entre coordenadas 1/3
La función dist() calcula la distancia entre dos coordenadas. Trabaja con enteros o decimales, pero regresa decimales.
Posee cuatro parámetros:
dist(x1, y1, x2, y2)
El primer par de parámetros corresponde a la primera coordenada y el segundo par a la segunda.
float r = dist(0, 0, 50, 0); // Asigna 50.0 a r
float s = dist(50, 0, 50, 90); // Asigna 90.0 a s
float t = dist(30, 20, 80, 90); // Asigna 86.023254 a t
Función dist() – Distancia entre coordenadas 2/3
// La distancia entre el centro de la ventana de representación// y el puntero determina el díametro del círculo
void setup() { size(100, 100); smooth();}void draw() { background(0); float d = dist(width/2, height/2, mouseX, mouseY); ellipse(width/2, height/2, d*2, d*2);}
Función dist() – Distancia entre coordenadas 3/3// Dibujo de una grilla de círculos y cálculo de la// distancia de cada uno de ellos para determinar el tamaño
float distanciaMax;void setup() { size(100, 100); noStroke(); smooth(); fill(0); distanciaMax = dist(0, 0, width, height);}void draw() { background(204); for (int i = 0; i <= width; i += 20) { for (int j = 0; j <= height; j += 20) { float distanciaMouse = dist(mouseX, mouseY, i, j); float diametro = (distanciaMouse / distanciaMax) * 66.0; ellipse(i, j, diametro, diametro); } }}
Técnica Easing – Aligeramiento 1/7
La técnica de animación llamada Easing, es en realidad una técnica de interpolación entre dos puntos. Al mover en cada cuadro una fracción de la distancia total de una figura, el movimiento de esta parece desacelerarse (o acelerarse) al acercarse a la ubicación de destino.
El siguiente diagrama muestra qué ocurre cuando un punto siempre se mueve la mitad del recorrido entre su posición actual y la posición de destino:
A medida que la figura se acerca a la posición de destino, la distancia recorrida disminuye en cada fotograma, por lo tanto el movimiento de la misma parece ralentizarse.
Técnica Easing – Aligeramiento 2/7
En el siguiente ejemplo la variable x corresponde a la posición horizontal actual del círculo y la varible destinoX corresponde a la posición de destino.
La variable easing dispone la fracción de la distancia entre la posición actual del círculo y la posición del ratón que el círculo se mueve en cada cuadro. El valor de esta variable cambia la rapidez con que el círculo llega al destino.
El valor de easing debe estar siempre entre 0.0 y 1.0, y los números cercanos a 0.0 causan que el movimiento se ralentice más.
Un valor de easing de 0.5 hará que el círculo se mueva la mitad de la distancia en cada cuadro, mientras que un valor de 0.01 hará que el círculo se mueva una centésima de la distancia en cada cuadro.
El círculo superior es dibujado de acuerdo a la posición destinoX, mientras que el círculo inferior es dibujado de acuerdo a la posición interpolada.
Ahora sí veamos el ejemplo:
Técnica Easing – Aligeramiento 3/7
float x = 0.0; // Distancia actual en xfloat easing = 0.05; // Números 0.0 a 1.0
void setup() { size(100, 100); smooth();}
void draw() { background(0); float destinoX = mouseX; x += (destinoX - x) * easing; ellipse(mouseX, 30, 40, 40); ellipse(x, 70, 40, 40);}
Técnica Easing – Aligeramiento 4/7
En el siguiente ejemplo utilizamos dos variables para controlar la técnica easing en las dos dimensiones. Observe que estructuralmente es igual al anterior. El ejemplo se encuentra en la siguiente página:
Técnica Easing – Aligeramiento 5/7float x = 0; // Distancia actual en xfloat y = 0; // Distancia actual en yfloat easing = 0.05; // Números 0.0 a 1.0
void setup() { size(100, 100); smooth(); noStroke();}
void draw() { background(0); float destinoX = mouseX; float destinoY = mouseY; x += (destinoX - x) * easing; y += (destinoY - y) * easing; fill(255); ellipse(x, y, 40, 40); //Círculo blanco grande interpolado fill(153); ellipse(mouseX, mouseY, 20, 20); //Círculo gris pequeño destino}
Técnica Easing – Aligeramiento 6/7
Los dos ejemplo previos continúan realizando el cálculo para la posición del círculo incluso luego de haber alcanzado su destino. Desde el punto de vista informático resulta ineficiente, y si hubieran cientos de círculos todos aligerando las posiciones, esto ralentizaría el programa en general.
Para detener los cálculos cuando estos no son necesarios, evalúe que la posición de destino y la posición actual sean equivalentes y detenga el cálculo si esta condición resulta verdadera.
El siguiente ejemplo presenta el uso de la función abs() la cual devuelve el valor absoluto de un número.
Esta es necesaria ya que los valores resultantes de la técnica Easing pueden ser tanto negativos como positivos dependiendo de si la posición se encuentra a la izquierda o a la derecha del destino.
Técnica Easing – Aligeramiento 7/7float x = 0.0; // Distancia actual en xfloat easing = 0.05; // Números 0.0 a 1.0
void setup() { size(100, 100); smooth();}
void draw() { background(0); float destinoX = mouseX; // Distancia desde l posición hasta el destino float dx = destinoX - x; // Si la distancia entre la posición actual y el destino // es mayor a 1.0, actualizo la posición if (abs(dx) > 1.0) { x += dx * easing; println(dx); } ellipse(mouseX, 30, 40, 40); ellipse(x, 70, 40, 40);}
Cálculo de velocidad 1/3 A continuación calcularemos la velocidad del ratón mediante la comparación de la posición
actual con la posición anterior.
Esto lo haremos usando la función dist() con los valores de parámetros de las variables mouseX, mouseY, pmouseX y pmouseY.
Entonces, el siguiente ejemplo calcula la velocidad del ratón y convierte este valor en el diámetro de una elipse:
void setup() {
size(100, 100);
noStroke();
smooth();
}
void draw() {
background(0);
float velocidad = dist(mouseX, mouseY, pmouseX, pmouseY);
float diametro = velocidad * 3.0;
ellipse(50, 50, diametro, diametro);
}
Cálculo de velocidad 2/3
El ejemplo previo muestra la velocidad instanténea del ratón. Los números producidos son extremos –saltan constantemente entre cero y otros valores más grandes de un cuadro al otro.
Se puede utilizar la técnica Easing para incrementar o mermar la velocidad de forma suavizada.
El siguiente ejemplo muestra cómo aplicar la técnica Easing en dicho contexto. La barra superior representa la velocidad instantánea mientras que la inferior representa la velocidad aligerada:
Cálculo de velocidad 3/3
float velocidad = 0.0;
float easing = 0.05; // Números 0.0 al 1.0
void setup() {
size(400, 400);
noStroke();
smooth();
}
void draw() {
background(0);
float destino = dist(mouseX, mouseY, pmouseX, pmouseY);
velocidad += (destino - velocidad) * easing;
rect(0, 33, destino, 17);
rect(0, 50, velocidad, 17);
}
Orientación 1/4 La función atan2() es usada para calcular el ángulo desde cualquier punto del área de
representación a la coordenada de origen (0, 0). Tiene dos parámetros:
atan2(y, x)
Donde los parámetro x e y corresponden a la coordenada de interés. Note que la posición de ambos parámetros se presentan en reverso comparado a cómo las usa otras funciones tales como point().
Los valores de ángulo son regresados en radianes dentro del rango π a –π.
Recordemos que un circulo mide 2*π radianes, lo cual equivale en grados a 360º. Por lo tanto, un ángulo de 90º corresponde a π/2 (1.5707964). Para convertir una medida de radianes a grados utilizamos la función degrees().
Veamos un ejemplo donde aplicamos estas dos funciones:
Orientación 2/4
// El ángulo se incrementa a medida de que el ratón
// se mueve desde la esquina superior-derecha de la
// pantalla a la esquina inferior-izquierda
void setup() {
size(100, 100);
frameRate(15);
fill(0);
}
void draw() {
float angulo = atan2(mouseY, mouseX);
float grados = degrees(angulo);
println(grados + "º");
background(204);
ellipse(mouseX, mouseY, 8, 8);
rotate(angulo);
line(0, 0, 150, 0);
}
Orientación 3/4 Por último, para calcular la orientación relativa a otro punto de referencia que no sea la
coordenada de origen (0, 0), utilizamos la función atan2() cuyos parámetros son substraídos por los valores correspondientes al otro punto de referencia que se desea fijar.
Veamos lo antedicho en el siguiente ejemplo:
Orientación 4/4
// Rota el triángulo apuntando siempre
// a la posición del puntero
float x = 50;
float y = 50;
void setup() {
size(100, 100);
noStroke();
smooth();
}
void draw() {
background(0);
float angulo = atan2(mouseY - y, mouseX - x);
pushMatrix();
translate(x, y);
rotate(angulo);
triangle(-20, -8, 20, 0, -20, 8);
popMatrix();
}
Lectura recomendada Capítulo “Shape 2: Vertices” (pag. 69). Capítulo “Math 2: Curves” (pag. 79). Capítulo “Math 3: Trigonometry” (pag. 117). Capítulo “Transform 1: Translate, Matrices ” (pag. 133). Capítulo “Transform 2: Rotate, Scale” (pag. 137). Capítulo “Shape 3: Parameters, Recursion” (pag. 197). Capítulo “Input 4: Mouse II” (pag. 237).
Información complementaria
Reducción de rango
Disponemos de cinco funciones básicas:ceil()floor()round()min()max().
ceil() - techo
Calcula el valor entero más cercano que el valor más grande o igual del de su parámetro.
int w = ceil(2.0); // Asigna 2 a w
int x = ceil(2.1); // Asigna 3 a x
int y = ceil(2.5); // Asigna 3 a y
int z = ceil(2.9); // Asigna 3 a z
floor() - piso
Calcula el valor entero más cercano que el valor más pequeño o igual del de su parámetro.
int w = floor(2.0); // Asigna 2 a w
int x = floor(2.1); // Asigna 2 a x
int y = floor(2.5); // Asigna 2 a y
int z = floor(2.9); // Asigna 2 a z
round() - redondeo
Calcula el valor entero más cercano al valor de la media de su parámetro.
int w = round(2.0); // Asigna 2 a w
int x = round(2.1); // Asigna 2 a x
int y = round(2.5); // Asigna 3 a y
int z = round(2.9); // Asigna 3 a z
min() - mínimo
int u = min(5, 9); // Asigna 5 a u
int v = min(-4, -12, -9); // Asigna -12 a v
float w = min(12.3, 230.24); // Asigna 12.3 a w
max() - máximo
int x = max(5, 9); // Asigna 9 a x
int y = max(-4, -12, -9); // Asigna -4 a y
float z = max(12.3, 230.24); // Asigna 230.24 a z
Parte 14Movimientos simples
Movimiento
En esta sección revisaremos tres clases de movimiento:
Movimiento implícito
Movimiento explícito
Movimiento mediante transformación
Movimiento implícito en una dirección 1/2
Para poner en movimiento una figura, necesitamos al menos usar una variable para cambiar un atributo.
El siguiente ejemplo presenta un movimiento implícito, es decir un movimiento rectilíneo uniforme que no contempla posición de origen ni de destino:
Movimiento implícito en una dirección 2/2
float y = 50.0;
float velocidad = 1.0;
float radio = 15.0;
void setup() {
size(100, 100);
smooth();
noStroke();
ellipseMode(RADIUS);
}
void draw() {
background(0);
ellipse(33, y, radio, radio);
y = y + velocidad;
if (y > height+radio) {
y = -radio;
}
}
Efecto de desenfoque 1/2
Puede crear un efecto de desenfoque utilizando un rectángulo semitransparente dentro del bloque draw().
La cantidad de desenfoque es controlado por el valor del parámetro de transparencia usado para pintar el relleno del rectángulo (la función fill()).
Los números cercanos a 255 refrescarán rápidamente la pantalla, mientras que los cercanos a 0 crearán un fundido lento.
Veamos su aplicación en el siguiente ejemplo:
Efecto de desenfoque 2/2float y = 50.0;
float velocidad = 1.0;
float radio = 15.0;
int direccion = 1;
void setup() {
size(100, 100);
smooth();
noStroke();
ellipseMode(RADIUS);
}
void draw() {
fill(0, 12); // Valores funcionales entre 10 y 100
rect(0, 0, width, height);
fill(255);
ellipse(33, y, radio, radio);
y += velocidad * direccion;
if ((y > height-radio) || (y < radio)) {
direccion = -direccion;
}
}
Movimiento implícito en dos direcciones 1/2
También podemos utilizar un segundo conjunto de variables para aprovechar el movimiento en el eje X:
float x = 50.0; // coordenada en X
float y = 50.0; // coordenada en Y
float radio = 15.0; // Radio del círculo
float velocidadX = 1.0; // Velocidad de mov. en eje X
float velocidadY = 0.4; // Velocidad de mov. en eje Y
int direccionX = 1; // Dirección de mov. en eje X
int direccionY = -1; // Dirección de mov. en eje X
void setup() {
size(100, 100);
smooth();
noStroke();
ellipseMode(RADIUS);
}
// ***continúa***
Movimiento implícito en dos direcciones 2/2
void draw() {
fill(0, 12);
rect(0, 0, width, height);
fill(255);
ellipse(x, y, radio, radio);
x += velocidadX * direccionX;
if ((x > width-radio) || (x < radio)) {
direccionX = -direccionX; // Cambia direccion
}
y += velocidadY * direccionY;
if ((y > height-radio) || (y < radio)) {
direccionY = -direccionY; // Cambia direccion
}
}
Movimiento explícito en dos direcciones 1/2
Si deseamos mover una figura desde y hasta una posición específica, debemos incorporar algunas variables de control más:
float origenX = 20.0; // Coordenada de origen en X
float origenY = 10.0; // Coordenada de origen en Y
float destinoX = 70.0; // Coordenada de destino en X
float destinoY = 80.0; // Coordenada de destino en Y
float distX; // Distancia a mover en eje-X
float distY; // Distancia a mover en eje-Y
float x = 0.0; // Coordenada actual en X
float y = 0.0; // Coordenada actual en Y
float paso = 0.02; // Tamaño de cada paso (0.0 a 0.4)
float pct = 0.0; // Porcentaje recorrido (0.0 a 1.0)
// ***continúa***
Movimiento explícito en dos direcciones 2/2
void setup() {
size(100, 100);
noStroke();
smooth();
distX = destinoX - origenX;
distY = destinoY - origenY;
}
void draw() {
fill(0, 12);
rect(0, 0, width, height);
pct += paso;
if (pct < 1.0) {
x = origenX + (pct * distX);
y = origenY + (pct * distY);
}
fill(255);
ellipse(x, y, 20, 20);
}
Movimiento explícito + easing + desenfoque
float origenX = 20.0; // Coordenada de origen en X
float origenY = 10.0; // Coordenada de origen en Y
float destinoX = 70.0; // Coordenada de destino en X
float destinoY = 80.0; // Coordenada de destino en Y
float easing = 0.05; // Tamaño de cada para a lo largo del recorrido
void setup() {
size(100, 100);
noStroke();
smooth();
}
void draw() {
fill(0, 12);
rect(0, 0, width, height);
float d = dist(origenX, origenY, destinoX, destinoY);
if (d > 1.0) {
origenX += (destinoX - origenX) * easing;
origenY += (destinoY - origenY) * easing;
}
fill(255);
ellipse(origenX, origenY, 20, 20);
}
Curvas simples 1/5
Las funciones exponenciales son muy útiles a la hora de crear curvas simples.
En general se utilizan valores normalizados en conjunción con la función pow() para producir incrementos y decrementos exponenciales de números que nunca exceden el rango de 0.0 a 1.0.
Estas ecuaciones tienen la siguiente forma:
y = xe
donde x (base) corresponde a un valor decimal entre 0.0 y 1.0 y e (exponente) corresponde a un valor entero o decimal.
Curvas simples 2/5
Ejemplo 1:
for (int x = 0; x < 100; x++) {
float n = norm(x, 0.0, 100.0); // Rango 0.0 a 1.0
float y = pow(n, 4); // Cálculo de curva
y *= 100; // Rango 0.0 a 100.0
point(x, y);
}
Ejemplo 2:
for (int x = 0; x < 100; x++) {
float n = norm(x, 0.0, 100.0); // Rango 0.0 a 1.0
float y = pow(n, 0.4); // Cálculo de curva
y *= 100; // Rango 0.0 a 100.0
point(x, y);
}
Curvas simples 3/5
Curvas simples 4/5
Curvas simples 5/5
Movimiento explícito curvo 1/5
Entonces podemos utilizar ecuaciones exponenciales para generar recorridos curvos en vez de rectilíneos:
float origenX = 20.0; // Coordenada de origen en X
float origenY = 10.0; // Coordenada de origen en Y
float destinoX = 70.0; // Coordenada de destino en X
float destinoY = 80.0; // Coordenada de destino en Y
float distX; // Distancia a mover en eje-X
float distY; // Distancia a mover en eje-X
float exponente = 0.5; // Determina el tipo de curva
float x = 0.0; // Coordenada actual en X
float y = 0.0; // Coordenada actual en Y
float paso = 0.01; // Tamaño de cada paso (0.0 a 1.0)
float pct = 0.0; // Porcentaje recorrido (0.0 a 1.0)
// *** continúa ***
Movimiento explícito curvo 2/5
void setup() {
size(100, 100);
noStroke();
smooth();
distX = destinoX - origenX;
distY = destinoY - origenY;
}
void draw() {
fill(0, 2);
rect(0, 0, width, height);
pct += paso;
if (pct < 1.0) {
x = origenX + (pct * distX);
y = origenY + (pow(pct, exponente) * distY);
}
fill(255);
ellipse(x, y, 20, 20);
}
Movimiento explícito curvo 3/5
Todas las curvas simples presentadas anteriormente pueden ser escaladas y combinadas para generar recorridos únicos de movimiento. Una vez que se haya calculado un paso de una curva, el programa puede calcular otras posiciones basadas en una curva diferente:
float origenX = 20.0; // Coordenada de origen en X
float origenY = 10.0; // Coordenada de origen en Y
float destinoX = 70.0; // Coordenada de destino en X
float destinoY = 80.0; // Coordenada de destino en Y
float distX; // Distancia a mover en eje-X
float distY; // Distancia a mover en eje-X
float exponente = 3.0; // Determina el tipo de curva
float x = 0.0; // Coordenada actual en X
float y = 0.0; // Coordenada actual en Y
float paso = 0.01; // Tamaño de cada paso (0.0 a 1.0)
float pct = 0.0; // Porcentaje recorrido (0.0 a 1.0)
int direccion = 1;
// *** continúa ***
Movimiento explícito curvo 4/5
void setup() {
size(100, 100);
noStroke();
smooth();
distX = destinoX - origenX;
distY = destinoY - origenY;
}
void draw() {
fill(0, 2);
rect(0, 0, width, height);
pct += paso * direccion;
if ((pct > 1.0) || (pct < 0.0)) {
direccion = direccion * -1;
}
// *** continúa ***
Movimiento explícito curvo 5/5
if (direccion == 1) {
x = origenX + (pct * distX);
float e = pow(pct, exponente);
y = origenY + (e * distY);
}
else {
x = origenX + (pct * distX);
float e = pow(1.0-pct, exponente*2);
y = origenY + (e * -distY) + distY;
}
fill(255);
ellipse(x, y, 20, 20);
}
Cambio de velocidad mediante ecuación exponencial 1/3
El siguiente ejemplo muestra el cambio de velocidad del movimiento de una figura mediante una ecuación exponencial. El círculo comienza a moverse muy lentamente y luego se detiene en el márgen inferior del área de representación. La variable exponente describe la pendiente de la curva, la cual cambia la velocidad del movimiento. Utilice el botón del ratón para seleccionar un nuevo punto de origen:
float origenX = 20.0; // Coordenada de origen en X
float origenY = 10.0; // Coordenada de origen en Y
float destinoX = 70.0; // Coordenada de destino en X
float destinoY = 80.0; // Coordenada de destino en Y
float distX; // Distancia a mover en eje-X
float distY; // Distancia a mover en eje-X
float exponente = 3.0; // Determina el tipo de curva
float x = 0.0; // Coordenada actual en X
float y = 0.0; // Coordenada actual en Y
float paso = 0.01; // Tamaño de cada paso (0.0 a 1.0)
float pct = 0.0; // Porcentaje recorrido (0.0 a 1.0)
// *** continúa ***
Cambio de velocidad mediante ecuación exponencial 2/3
void setup() {
size(100, 100);
noStroke();
smooth();
distX = destinoX - origenX;
distY = destinoY - origenY;
}
// *** continúa ***
Cambio de velocidad mediante ecuación exponencial 3/3
void draw() {
fill(0, 2);
rect(0, 0, width, height);
if (pct < 1.0) {
pct = pct + paso;
float velocidad = pow(pct, exponente);
x = origenX + (velocidad * distX);
y = origenY + (velocidad * distY);
}
fill(255);
ellipse(x, y, 20, 20);
}
void mousePressed() {
pct = 0.0;
origenX = x;
origenY = y;
distX = mouseX - x;
distY = mouseY - y;
}
Movimiento mediante transformación 1/3
Las funciones de transformación (translate(), rotate(), y scale()) también pueden crear movimiento al cambiar los valores de sus parámetros. Antes de usar las transformaciones para el movimiento, es importante remarcar que las transformaciones se reinicializan al comienzo de cada bloque draw().
Por lo tanto, cuando se ejecuta translate(50, 0)dentro del bloque draw(), el sistema de coordenadas se ajusta 50 pixeles a la derecha por única vez hasta que se detenga el programa.
void setup() {
size(100, 100);
smooth();
}
void draw() {
background(0);
translate(50, 0); // Se reinicia a 50 px cada vez que entra en draw
ellipse(0, 50, 60, 60);
}
Movimiento mediante transformación 2/3
Del mismo modo, las translaciones dentro del bloque setup() no tienen efecto en las figuras producidas en el bloque draw().
void setup() {
size(100, 100);
smooth();
translate(50, 0); // No tiene efecto
}
void draw() {
background(0);
ellipse(0, 50, 60, 60);
}
Movimiento mediante transformación 3/3 Las funciones de transformación pueden ser utilizadas para generar movimiento, sin
embargo su uso puede ser un poco engorroso.
float angulo = 0.0;
void setup() {
size(100, 100);
smooth();
noStroke();
}
void draw() {
fill(0, 12);
rect(0, 0, width, height);
fill(255);
angulo = angulo + 0.02;
translate(70, 40);
rotate(angulo);
rect(-30, -30, 60, 60);
}
Lectura recomendada Capítulo “Motion 1: Lines, Curves” (pag. 279).
Parte 15Movimientos 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/4void 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/3float 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/3float 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/12float 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/12float 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/12float 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/12void 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/12float 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/12float 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 16Arrays
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/4int 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/4int[] 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 arrayprintln(numeros); // Imprime "1" y "3"println();
numeros = append(numeros, 5); // Agrega "5" al finalprintln(numeros); // Imprime "1", "3" y "5"println();
// Agrega "7" al final del array "numeros", y crea un nuevo// array donde guarda el cambioint[] 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 elementoprintln(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 rangoprintln(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 17Objetos
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 asignar –setup()- y acceder –draw()- a las variables de la clase.
Circulo circ; // Declaración del objetovoid 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 objetovoid 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/12Circulo circ; // Declaración del objetovoid 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 objetosvoid 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/2Huevo 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(); }}
Ejercicio 16
• EJ16: Crear dos "entes autónomos" en el escenario. Uno debe exhibir movimientos de cualidades más bien mecánicas y el otro orgánicas.
• Utilizar clases.
• Comentar todas las instrucciones.
Lectura recomendada Capítulo “Structure 4: Objects I” (pag. 395).
Parte 18Arrays de objetos
Introducción El trabajo con arrays de objetos es similar al trabajo con arrays de otros tipos de dato.
Como todo array, un array de objetos se distingue de cualquier objeto gracias a los corchetes.
Ya que cada elemento de array es un objeto, cada elemento del array debe ser creado antes de que pueda ser accedido.
Los pasos para el trabajo con un array de objetos son los siguientes:
1. Declaración del array
2. Creación del array
3. Creación de cada objeto del array
Veamos cómo se implementa en los próximos dos ejemplos:
Ejemplo 1 1/2int numCirc = 6;
// Declaración y creación del arrayCirculo[] circulos = new Circulo[numCirc];
void setup() { size(100, 100); smooth(); noStroke(); for (int i = 0; i < circulos.length; i++) { float x = 10 + i*16; float rate = 0.5 + i*0.05; // Creación de cada objeto del array circulos[i] = new Circulo(x, 50, 16, rate); }}
void draw() { fill(0, 12); rect(0, 0, width, height); fill(255); for (int i = 0; i < circulos.length; i++) { circulos[i].mover(); // Move each object circulos[i].mostrar(); // Display each object }}
// *** continúa ***
Ejemplo 1 2/2class Circulo { // Campos float x, y, diametro; float velocidad; // Distancia movida 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); }} // *** continúa ***
Ejemplo 2 1/2Anillo[] anillos; // Declaración del array
int numAnillos = 50;int actualAnillo = 0;
void setup() { size(100, 100); smooth(); anillos = new Anillo[numAnillos]; // Creación del array for (int i = 0; i < numAnillos; i++) { anillos[i] = new Anillo(); // Creación de cada objeto }}
void draw() { background(0); for (int i = 0; i < numAnillos; i++) { anillos[i].crecer(); anillos[i].mostrar(); }}
// Click para crear un nuevo anillovoid mousePressed() { anillos[actualAnillo].iniciar(mouseX, mouseY); actualAnillo++; if (actualAnillo >= numAnillos) { actualAnillo = 0; }} // *** continúa ***
Ejemplo 2 2/2class Anillo { float x, y; // Coordenadas-xy float diametro; // Diámetro del anillo boolean on = false; // Enciende o apaga la visualización
void iniciar(float xpos, float ypos) { x = xpos; y = ypos; on = true; diametro = 1; }
void crecer() { if (on == true) { diametro += 0.5; if (diametro > 400) { on = false; } } }
void mostrar() { if (on == true) { noFill(); strokeWeight(4); stroke(155, 153); ellipse(x, y, diametro, diametro); } }}
Multiples archivos Cuando un programa crece de sobremanera resulta más conveniente dividirlo y separarlo en
múltiples archivos.
Esta práctica también es aconsejada cuando se desea reusar clases en otros programas.
Para realizar esto se recomienda primero guardar el cuerpo principal del programa en un archivo, por ejemplo, anillos-main, y luego hacer clic en el botón del extremo derecho, en el sector de lengüetas.
Allí se despliega un menú contextual y se seleccionará la primera opción New Tab.
Entonces se nos pide el nombre del nuevo archivo e ingresaremos Anillo-clase.
Finalizamos con clic en OK.
En la carpeta correspondiente a anillos-main encontraremos dos archivos: anillos-main.pde y Anillo-clase.pde.
Lectura recomendada Capítulo “Structure 4: Objects I” (pag. 395).
Parte 19Objetos II
Múltiples constructores 1/3 A medida que el programa comienza a crecer y las ideas se hacen más ambiciosas,
los conceptos y técnicas avanzados sobre POO se vuelven ineludibles para la gestión del código.
Una clase puede tener múltiples constructores que asignan los campos de formas diferentes. A veces resulta beneficioso especificar cada aspecto de los datos de un objeto al asignar parámetros a los campos, pero otras veces puede resultar apropiado definir sólo uno o algunos de ellos.
En el siguiente ejemplo, un constructor aplica los valores de los campos x, y, y radio, mientras que el otro aplica valores por defecto.
Cuando el objeto es creado, Processing elige el constructor apropiado de acuerdo a la identificación del número y tipo de variables especificado:
Múltiples constructores 2/3
Circulo circ1, circ2;
void setup() { size(100, 100); smooth(); noLoop();
// Ejecuta el constructor sin parámetros circ1 = new Circulo();
// Ejecuta el constructor con tres parámetros circ2 = new Circulo(66, 50, 20);}
void draw() { circ1.mostrar(); circ2.mostrar();}
// *** continúa ***
Múltiples constructores 3/3
class Circulo { float x, y, radio;
// Primera versión del constructor Circulo; // se asigna a los campos valores por defecto Circulo() { x = 33; y = 50; radio = 8; }
// Segunda versión del constructor Circulo; // se asigna a los campos los valores de los parámetros Circulo(float xpos, float ypos, float r) { x = xpos; y = ypos; radio = r; }
void mostrar() { ellipse(x, y, radio*2, radio*2); }}
Objetos compuestos 1/5 Un objeto puede incluir otros objetos. La creación de semejantes objetos compuestos resultan
una forma apropiada para aplicar el principio de modularidad y construir niveles más altos de abstracción.
En el mundo concreto, los objetos frecuentemente poseen componentes que operan autónomamente pero en relación a otros componentes.
Si se permite usar una analogía biológica, puede crear una clase celula, grupos de ellas que combinadas conforman un tejido muscular o un tejido nervioso. Estos tejidos pueden ser combinados en órganos, y los órganos en un organismo.
Con múltiples capas de abstracción, cada paso es construido a partir de compuestos de una capa anterior.
El siguiente ejemplo combina la clase Huevo y la clase Anillo para crear una nueva clase llamada HuevoAnillo. Cuando se usa la clase HuevoAnillo en un programa, cada instancia dibuja un huevo en la pantalla con un anillo creciendo desde su centro.
El ejemplo cuenta con un objeto de tipo Huevo llamado ovoide, creado en el constructor, y un objeto de tipo Anillo llamado circulo, creado en la base de la clase.
El método transmitir() llama a los métodos de ambas clases y reinicializa circulo cuando el objeto alcanza su máximo tamaño.
Ahora sí, veamos el ejemplo:
Objetos compuestos 2/5
HuevoAnillo ha1, ha2;
void setup() { size(100, 100); smooth(); ha1 = new HuevoAnillo(33, 66, 16, 33); ha2 = new HuevoAnillo(66, 90, 8, 66);}
void draw() { background(0); ha1.transmitir(); ha2.transmitir();}
// Se debe incluir las sig. Clases: Huevo, Anillo y HuevoAnillo
Objetos compuestos 3/5class Huevo { 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 Huevo(int xpos, int ypos, float balFactor, float h) { x = xpos; y = ypos; balanceo = balFactor; altura = h / 100.0; } 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(); }}
Objetos compuestos 4/5class Anillo { float x, y; // Coordenadas-xy float diametro; // Diámetro del anillo boolean on = false; // Enciende o apaga la visualización void iniciar(float xpos, float ypos) { x = xpos; y = ypos; on = true; diametro = 1; } void crecer() { if (on == true) { diametro += 0.5; if (diametro > 400) { on = false; } } } void mostrar() { if (on == true) { noFill(); strokeWeight(4); stroke(155, 153); ellipse(x, y, diametro, diametro); } }}
Objetos compuestos 5/5
class HuevoAnillo {
Huevo ovoide; Anillo circulo = new Anillo();
HuevoAnillo(int x, int y, float t, float sp) { ovoide = new Huevo(x, y, t, sp); circulo.iniciar(x, y - sp/2); }
void transmitir() { ovoide.balancear(); ovoide.mostrar(); circulo.crecer(); circulo.mostrar(); if (circulo.on == false) { circulo.on = true; } }}
Herencia Una clase puede ser definida usando otra clase como fundamento. En términos de POO, una
clase puede heredar campos o métodos de otra.
Un objeto que hereda de otro (heredero) es llamado subclase, mientras que el objeto del cual se hereda (antecesor) es llamado superclase.
Una subclase extiende las capacidades de una superclase. Cuando una clase extiende a otra, todos los campos y métodos de la superclase resultan automáticamente incluidos en la subclase. Cuando se define la subclase se utiliza la palabra clave extends antecediendo el nombre de la superclase.
Se puede agregar nuevos campos y métodos a la subclase para poder trabajar sobre los datos y los comportamientos de la superclase.
Si un nombre de método es repetido dentro de la subclase, y además tiene el mismo prototipo (mismo número de parámetros y mismo tipo de datos) que el presente en la superclase, el método de la subclase anula el de la superclase, por lo tanto lo reemplaza.
Cuando un campo o método de la superclase es llamado desde la subclase, el nombre es antecedido por la palabra clave super para hacer saber a Processing que dicho campo o método es parte de la superclase.
El siguiente ejemplo refleja estos nuevos términos y conceptos:
HerenciaEjemplo (Giro: superclase)
class Giro {
float x, y, velocidad; float angulo = 0.0;
Giro(float xpos, float ypos, float vel) { x = xpos; y = ypos; velocidad = vel; }
void actualizar() { angulo += velocidad; }}
HerenciaEjemplo (GiroLinea: subclase)
class GiroLinea extends Giro {
GiroLinea(float x, float y, float v) { super(x, y, v); }
void mostrar() { strokeWeight(1); stroke(0); pushMatrix(); translate(x, y); angulo += velocidad; rotate(angulo); line(0, 0, 100, 0); popMatrix(); }}
HerenciaEjemplo (GiroCirculos: subclase)
class GiroCirculos extends Giro {
float dimen;
GiroCirculos(float x, float y, float v, float d) { super(x, y, v); dimen = d; }
void mostrar() { noStroke(); pushMatrix(); translate(x, y); angulo += velocidad; rotate(angulo); ellipse(-dimen/2, 0, dimen, dimen); ellipse(dimen/2, 0, dimen, dimen); popMatrix(); }}
HerenciaEjemplo (animacion: main)
GiroCirculos circulos;GiroLinea linea;
void setup() { size(100, 100); smooth(); linea = new GiroLinea(width/2, height/2, 0.01); circulos = new GiroCirculos(width/2, height/2, -0.02, 33.0); }
void draw() { background(204); linea.actualizar(); linea.mostrar(); circulos.actualizar(); circulos.mostrar(); }
Lectura recomendada Capítulo “Structure 5: Objects II” (pag. 453).
Parte 20ArrayList
ArrayList La clase Java ArrayList permite implementar arrays de tamaño flexible, donde es
posible agregar o remover elementos del principio, mitad o final del array.
El uso de un ArrayList conceptualmente es similar al de un array estandar, pero lo que difiere es la sintáxis.
El siguiente ejemplo muestra el uso de ArrayList para crear una aplicación que genera un emisor de partículas.
Cada vez que presionamos el botón del mouse, la aplicación genera una partícula en cada fotograma mientras dure la presión del mouse.
ArrayListEjemplo (ArrayList: main)
// Learning Processing// Daniel Shiffman// http://www.learningprocessing.com// Ejemplo 23-2: Sistema de partículas simple con ArrayList
ArrayList particulas;
void setup() { size(200,200); particulas = new ArrayList(); smooth();}
void draw() { // Cuando el botón del mouse es presionado, se agrega un nuevo // objeto Particula al ArrayList en cada ciclo del draw().if (mousePressed) { particulas.add(new Particula()); } background(255); // Iteración a través del ArrayList y obtiene cada partícula. // ArrayList mantiene un seguimiento del nñúmero total de partículas. for (int i = 0; i < particulas.size(); i++ ) { Particula p = (Particula) particulas.get(i); p.ejecutar(); p.gravedad(); p.mostrar(); } // Si ArrayList contiene más de 100 elementos, eliminamos // el primer elemento usando el método remove(). if (particulas.size() > 100) { particulas.remove(0); }}
ArrayListEjemplo (Particula: clase)
// Learning Processing// Daniel Shiffman// http://www.learningprocessing.com// Ejemplo 23-2: Sistema de partículas simple con ArrayList// Una clase de Partícula simpleclass Particula { float x; float y; float xVelocidad; float yVelocidad; Particula() { x = mouseX; y = mouseY; xVelocidad = random(-1,1); yVelocidad = random(-2,0); } void ejecutar() { x = x + xVelocidad; y = y + yVelocidad; } void gravedad() { yVelocidad += 0.1; } void mostrar() { stroke(0); fill(0,75); ellipse(x,y,10,10); }}
Lectura recomendada Capítulo 23 “Java” (pag. 423).
Shiffman, D. “Learning Processing - A Beginner’s Guide to Programming Images, Animation, and Interaction”, Morgan Kaufman, 2008.
Ej 17
• EJ17: Crear una superficie sensible a datos de entrada (mouse y/o teclado) cuya programación haga uso de las técnicas, procedimientos y estructuras aprendidas durante el curso.
• Comentar todas las instrucciones.
Trabajo Práctico Final
• TPFinal: Crear un sistema generativo de imagen (processing) y sonido (pure data), en el cual se establezcan relaciones de influencia audiovisual en una o en dos vías a través de datos enviados entre sí mediante el formato de contenido Open Sound Control.
• Comentar todas las instrucciones.
FIN