Unidad 4
Introducción
En las unidades anteriores has explorado el movimiento con el marco motion 101 manipulando la posición de los elementos gráficos de la simulación. En esta unidad explorarás el movimiento angular u oscilatorio.
¿Qué aprenderás en esta unidad?
En esta fase te mostraré algunos referentes que aplican los conceptos que investigarás y aplicarás en esta unidad.
Actividad 01
Te presente a Memo Akten
Enunciado: en esta actividad te presentaré a Memo Akten, un artista y programador que ha explorado las posibilidades de la inteligencia artificial en la creación de arte. Te voy a presentar una obra de Memo bajo el título sombrilla de Simple Harmonic Motion.
Entrega: escribe un párrafo en el que describas tu impresión sobre la obra de Memo Akten. ¿Qué te pareció? ¿Qué te llamó la atención? ¿Qué te gustó? ¿Qué no te gustó? ¿Qué te gustaría saber más?
Solo para los curiosos: dale una mirada a la obra Superradiance. Te dejo por aquí un video reciente: SUPERRADIANCE. Chapters 1-2. Short (Performance) version. By Memo Akten & Katie Peyton Hofstadter. Youtube.
🚀 Tu solución:
Memo Akten
Me parece algo un poco extraño de ver, es una sensación interesante ya que la visualización de las formas, los colores, y esto en combinación con la melodía hace que se pueda llegar a volver hipnotizante en algunos momentos, la coordinación con que los puntos chocan en ocasiones y cómo al acercarse unos a otros hace que la figura reaccione de una manera distinta es algo que sólo en ocasiones se hace predescible, ya que constantemente la figura está cambiando, es como que las reacciones tienen iteraciones distintas a lo largo del video, me gusta bastante esto, que no es algo repetitivo y que logra captar correctamente la atención, e incluso lo lleva a uno a pensar en cómo será la siguiente interacción de la figura, sin embargo, llegué a notar que el sonido proviene directamente de los movimientos de la figura, de la interacción con ella misma, los “golpes” entre unos puntos y otros, y eso hace que la melodía pueda llegar a sentirse monótona hablando de ritmo, y pues ya que estamos hablando de esto me entra la duda de si habría alguna forma de hacer que esta interacción se lleve a cabo al contrario, es decir, que en lugar de que la melodía reaccione a la animación, que la animación reaccione a la melodía, pero siguiendo la misma “idea” del patrón, con las mismas coordinaciones, con las iteraciones y cambios constantes, pero dependientes del ritmo, es la única duda que me entra, de si esto es algo realizable y qué tan factible sería y qué tan bien se vería, pero poco más. En general me pareció una animación interesante y el hecho de que la melodía reaccionara de manera directa a la misma, sin ser esta muy repetitiva e incluso llegando a ser hipnotizante, hace de la obra algo muy interesante de ver y una experiencia que puede trascender dependiendo de quien la vea y el estado mental o incluso meramente físico de la misma.
Investigación
Ahora vas a investigar algunos conceptos fundamentales que te servirán para diseñar tu aplicación en la fase que sigue.
Actividad 02
Conceptos fundamentales
Enunciado: analiza las siguientes simulaciones y responde las preguntas.
Te voy a proponer un par de simulaciones para que analices.
Primero mira esta simulación para el manejo de ángulos.
- ¿Qué está pasando en esta simulación? ¿Cuál es la interacción?
- Nota que en cada frame se está trasladando el origen del sistema de coordenadas al centro de la pantalla. ¿Por qué crees que se hace esto?
- Cuál es la relación entre el sistema de coordenadas y la función
rotate()
.
Nota esta parte del código:
line(-50, 0, 50, 0); stroke(0); strokeWeight(2); fill(127); circle(50, 0, 16); circle(-50, 0, 16);
Observa que al dibujar los elementos gráficos parece que se está dibujando en la posición (0, 0)
del sistema de coordenadas. ¿Por qué crees que se hace esto? y ¿Por qué aunque en cada frame se hace lo mismo, los elementos gráficos rotan?
Ahora analiza una simulación que muestra cómo puedes hacer para que los elementos gráficos de la simulación apunten en la dirección del movimiento.
- Identifica el marco motion 101. ¿Qué es lo que se está haciendo en este marco?
Observa detenidamente este fragmento de código de la simulación:
display() { let angle = this.velocity.heading();
stroke(0); strokeWeight(2); fill(127); push(); rectMode(CENTER); translate(this.position.x, this.position.y); rotate(angle); rect(0, 0, 30, 10);
pop(); }
- ¿Qué hace la función
heading()
? - ¿Qué hace la función
push()
ypop()
? Realiza algunos experimentos para entender su funcionamiento. - ¿Qué hace rectMode(CENTER)? Realiza algunos experimentos para entender su funcionamiento.
- ¿Cuál es la relación entre el ángulo de rotación y el vector de velocidad? Trata de dibujar en un papel el vector de velocidad y cómo se relaciona con el ángulo de rotación y la operación de traslación y rotación.
Entrega: reporta la respuesta a las preguntas anteriores.
🚀 Tu solución:
Conceptos Fundamentales
1) Simulación para el manejo de ángulos
- ¿Qué está pasando en esta simulación? ¿Cuál es la interacción?
Esta es una simulación de una barra girando sobre su centro, con un círculo en cada lado, en cada frame se actualiza el ángulo de rotación, aumentando este y haciendo que la barra gire constantemente, aparte de esto parece habre una interacció al presionar una tecla, aumentando aún más el ángulo y acelerando un poco más la rotación, si se aumenta arbitrariamente este valor se puede notar aún más esta interacción.
- En cada frame se está trasladando el origen del sistema de coordenadas al centro de la pantalla. ¿Por qué crees que se hace esto?
Como tal el origen del sistema de coordenadas sería: (0,0), es decir, la esquina superior izquierda, si se dejara la simulación ahí realmente sólo podríamos ver un cuarto de ella, la esquina inferior derecha, las otras partes estarían escondidas fuera del canvas, es por esto que con la función translate, este origen se traslada a la mitad del canvas, permitiéndonos así tener una mejor visualización, se usa la división de la pantalla y no las coordenadas de la mitad para que en caso tal de modificar el tamaño del canvas, siempre nos refiramos al centro del mismo y no un lugar específico.
- ¿Cuál es la relación entre el sistema de coordenadas y la función rotate().
Básicamente rotate() rota el sistema de coordenadas entero, lo cual significa que todo lo que se dibuja después de rotate() estará girado respecto al nuevo sistema de coordenadas. Por eso es importante aplicar primero translate() (para mover el origen al punto deseado) y luego rotate() (para girar el sistema en torno a ese nuevo origen).
- Al dibujar los elementos gráficos parece que se está dibujando en la posición (0, 0) del sistema de coordenadas. ¿Por qué crees que se hace esto? y ¿Por qué aunque en cada frame se hace lo mismo, los elementos gráficos rotan?
El tema aquí es que aunque las coordenadas están fijas respecto al origen, el sistema de coordenadas completo ya fue transformado antes del draw, primero con translate, moviendo el punto de origen, y luego con rotate, rotando todo el sistema, haciendo así que siempre se dibuje la misma línea y los mismos círculos relativos al origen, y al rotar el sistema de coordenadas en cada frame lo que el usuario ve es una rotación no de los objetos individuales como tal, sino del espacio.
2) Simulación que apunta en dirección de movimiento
- Identifica el marco motion 101. ¿Qué es lo que se está haciendo en este marco?
En este caso el marco motion 101 tiene tres pasos y un límite, primero se calcula la aceleración (en este caso, hacia el mouse), luego esa aceleración se suma a la velocidad, y finalmente la velocidad se suma a la posición del objeto. De esta manera el objeto se mueve como si estuviera siendo jalado por el cursor y al final también se limita la velocidad para que no se mueva demasiado rápido.
- ¿Qué hace la función heading()?
La función heading() devuelve el ángulo de dirección de un vector en radianes, en este caso, this.velocity.heading() nos da el ángulo hacia donde se está moviendo el objeto, lo cual se usa para que el rectángulo apunte visualmente hacia esa dirección.
- ¿Qué hace la función push() y pop()?
Push() y pop() se usan para guardar y restaurar el sistema de coordenadas actual, esto significa que cualquier transformación como translate() o rotate() que se haga entre push() y pop() solo afecta a ese bloque de código.
- Experimento: En este caso lo que haré será dibujar algo dentro y fuera del sistema de coordenadas, es decir, dentro del push y pop, para mostrar la diferencia
En este caso se puede ver que dibujé un cuadrado fuera del pop, lo que hace que el cuadrado simplemente esté ahí, existiendo, con su propio sistema de coordenadas aparte, de hecho, la “navecita” que sigue el mouse lo atraviesa, ya que es un sistema aparte, de hecho se podría ver como una capa aparte.
Mientras que por otro lado, si se dibuja dentro del sistema de coordenadas, también seguiría el mouse, junto a nuestra navecita, siguiendo la dirección y siguiendo al mouse
- ¿Qué hace rectMode(CENTER)?
Por defecto, rect(x, y, w, h) dibuja el rectángulo desde la esquina superior izquierda. Pero al usar rectMode(CENTER), el punto (x, y) se convierte en el centro del rectángulo.
- Experimento: Aquí el experimento es básico, lo único que muestro es que si al modo del rectángulo le ponemos (CORNER), este se dibuja desde la esquina, mientras que si le ponemos (CENTER), se dibuja desde el centro, a pesar de que ambos estén en el mismo punto de referencia
- ¿Cuál es la relación entre el ángulo de rotación y el vector de velocidad?
El ángulo que se calcula con heading() representa hacia dónde se dirige el objeto según su velocidad, al rotar el rectángulo por ese ángulo, hacemos que visualmente apunte en la misma dirección que se está moviendo.
Digamos que el rectángulo es como una flecha, y su vector de velocidad es como una línea que sale de su centro y apunta hacia donde se mueve. El heading() de ese vector nos da el ángulo para girar la flechita, de modo que siempre mire en la dirección de su movimiento.
Actividad 03
Practica un poco
Enunciado: ahora es momento de practicar los conceptos anterior. Crea una simulación de un vehículo que puedas conducir por la pantalla utilizando las teclas de flecha: la flecha izquierda acelera el vehículo hacia la izquierda, y la flecha derecha acelera hacia la derecha. El vehículo tendrá forma triangular y debe apuntar en la dirección en la que se está moviendo actualmente.
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Practicando los conceptos
Aquí se hizo justo lo que se pidió, un vehículo que acelera de acuerdo a qué flecha presione, al presionar la derecha apunta y va hacia la derecha, y viceversa
Código completo:
class Vehicle { constructor() { this.position = createVector(width / 2, height / 2); this.velocity = createVector(0, 0); this.acceleration = createVector(0, 0); this.topspeed = 5; }
applyForce(force) { this.acceleration.add(force); }
update() { this.velocity.add(this.acceleration); this.velocity.limit(this.topspeed); this.position.add(this.velocity); this.acceleration.mult(0); // Reset acceleration each frame }
display() { let angle = this.velocity.heading(); // Direction of movement
push(); translate(this.position.x, this.position.y); rotate(angle);
// Dibujo del vehículo como triángulo fill(255,127,127); stroke(0); strokeWeight(2); beginShape(); vertex(20, 0); vertex(-10, 10); vertex(-10, -10); endShape(CLOSE);
pop(); }
checkEdges() { if (this.position.x > width) { this.position.x = 0; } else if (this.position.x < 0) { this.position.x = width; }
if (this.position.y > height) { this.position.y = 0; } else if (this.position.y < 0) { this.position.y = height; } }}
let vehicle;
function setup() { createCanvas(640, 240); vehicle = new Vehicle();}
function draw() { background(155,155,155);
// Control con flechas if (keyIsDown(LEFT_ARROW)) { let leftForce = createVector(-0.2, 0); vehicle.applyForce(leftForce); }
if (keyIsDown(RIGHT_ARROW)) { let rightForce = createVector(0.2, 0); vehicle.applyForce(rightForce); }
vehicle.update(); vehicle.checkEdges(); vehicle.display();}
Actividad 04
Relación con el marco motion 101
Enunciado: es momento de retomar lo que has aprendido en las unidades previas e integrarlo con los nuevos conceptos de esta unidad. Observa detenidamente la siguiente simulación: Motion 101 con fuerzas
- Identifica motion 101. ¿Qué modificación hay que hacer al motion 101 cuando se quiere agregar fuerzas acumulativas? Trata de recordar por qué es necesario hacer esta modificación.
- Identifica dónde está el Attractor en la simulación. Cambia el color de este.
- Observa que el Attractor tiene dos atributos this.dragging y this.rollover. Estos atributos no se modifican en el código, pero permitirían mover el attractor con el mouse y cambiar su color cuando el mouse está sobre él. ¿Cómo podrías modificar el código para que esto funcione? considera las funciones que ofrece p5.js para interactuar con el mouse.
Entrega: reporta la respuesta a las preguntas anteriores y los resultados de las modificaciones realizadas.
🚀 Tu solución:
Relación con el marco 101
-
En este caso, el marco 101, se presenta claramente al final de update, es un hecho que ya está implementado en el código, y esto se puede notar en cada actualización de pantalla, todo se mueve como debería y se reinician las fuerzas para que estas no se acumulen indefinidamente, así que no hay que modificar nada en cuanto al marco 101.
-
El attractor se presenta como un elipse en toda la mitad de la pantalla, cambiarle el color no es complicado, pues solo se trata de poner valores diferentes en la línea de “fill”.
-
Por último queda hacer que el attractor se mueva y reaccione visualmente con el mouse utilizando los atributos que ya están en el código.
Con estos puntos en cuenta el código se terminaría viendo así:
class Attractor { constructor() { this.position = createVector(width / 2, height / 2); this.mass = 20; this.G = 1; this.dragging = false; this.rollover = false; this.dragOffset = createVector(0, 0); }
attract(mover) { let force = p5.Vector.sub(this.position, mover.position); let distance = force.mag(); distance = constrain(distance, 5, 25); let strength = (this.G * this.mass * mover.mass) / (distance * distance); force.setMag(strength); return force; }
hover(mx, my) { let d = dist(mx, my, this.position.x, this.position.y); this.rollover = d < this.mass; }
pressed(mx, my) { let d = dist(mx, my, this.position.x, this.position.y); if (d < this.mass) { this.dragging = true; this.dragOffset.x = this.position.x - mx; this.dragOffset.y = this.position.y - my; } }
released() { this.dragging = false; }
drag(mx, my) { if (this.dragging) { this.position.x = mx + this.dragOffset.x; this.position.y = my + this.dragOffset.y; } }
display() { ellipseMode(CENTER); stroke(0); if (this.dragging) { fill(50); // Más oscuro } else if (this.rollover) { fill(100); // Intermedio } else { fill(255, 100, 100); // Cambiado a un tono rojizo para destacarlo } ellipse(this.position.x, this.position.y, this.mass * 2); }}
class Mover { constructor(x, y, mass) { this.mass = mass; this.radius = this.mass * 8; this.position = createVector(x, y); this.angle = 0; this.angleVelocity = 0; this.angleAcceleration = 0; this.velocity = createVector(random(-1, 1), random(-1, 1)); this.acceleration = createVector(0, 0); }
applyForce(force) { let f = p5.Vector.div(force, this.mass); this.acceleration.add(f); }
update() { this.velocity.add(this.acceleration); this.position.add(this.velocity); this.angleAcceleration = this.acceleration.x / 10.0; this.angleVelocity += this.angleAcceleration; this.angleVelocity = constrain(this.angleVelocity, -0.1, 0.1); this.angle += this.angleVelocity; this.acceleration.mult(0); // acumulación reseteada }
show() { strokeWeight(2); stroke(0); fill(127, 127); rectMode(CENTER); push(); translate(this.position.x, this.position.y); rotate(this.angle); circle(0, 0, this.radius * 2); line(0, 0, this.radius, 0); pop(); }}
let movers = [];let attractor;
function setup() { createCanvas(640, 240);
for (let i = 0; i < 20; i++) { movers.push(new Mover(random(width), random(height), random(0.1, 2))); } attractor = new Attractor();}
function draw() { background(255);
attractor.hover(mouseX, mouseY); attractor.drag(mouseX, mouseY); attractor.display();
for (let mover of movers) { let force = attractor.attract(mover); mover.applyForce(force); mover.update(); mover.show(); }}
function mousePressed() { attractor.pressed(mouseX, mouseY);}
function mouseReleased() { attractor.released();}
Actividad 05
Coordenadas polares
Enunciado: explora otro sistema de coordenadas útil cuando se trabaja con ángulos. Se trata de las coordenadas polares.
Considera esta simulación de coordenadas polares:
- Observa de nuevo esta parte del código ¿Cuál es la relación entre r y theta con las posiciones x y y? Puedes repasar entonces la definición de coordenadas polares y cómo se convierten a coordenadas cartesianas.
function draw() { background(255); // Translate the origin point to the center of the screen translate(width / 2, height / 2); // Convert polar to cartesian let x = r * cos(theta); let y = r * sin(theta); fill(127); stroke(0); strokeWeight(2); line(0, 0, x, y); circle(x, y, 48); theta += 0.02;}
Modifica la función draw()
:
function draw() { background(255); // Translate the origin point to the center of the screen translate(width / 2, height / 2); let v = p5.Vector.fromAngle(theta); fill(127); stroke(0); strokeWeight(2); line(0, 0, x, y); circle(v.x, v.y, 48); theta += 0.02;}
¿Qué ocurre? ¿Por qué?
Ahora realiza esta modificación:
function draw() { background(255); // Translate the origin point to the center of the screen translate(width / 2, height / 2); let v = p5.Vector.fromAngle(theta,r); fill(127); stroke(0); strokeWeight(2); line(0, 0, v.x, v.y); circle(v.x, v.y, 48); theta += 0.02;}
- ¿Qué ocurre aquí? ¿Por qué?
Entrega: la respuesta a las preguntas anteriores.
🚀 Tu solución:
Coordenadas polares
-
Primera modificación: En este código se crea un vector que apunta en la dirección del ángulo theta, pero con una longitud predeterminada de 1, ya que no se especifica el radio (r). Esto hace que el círculo que se dibuja con ese vector se mantenga muy cerca del centro, trazando una trayectoria muy pequeña, casi imperceptible, peeero esta ni se llega a ver, ya que el código intenta dibujar una línea usando las variables “x” y “y”, que no están definidas en esta versión, lo que directamente causa un error.
-
Segunda modificación: Este código mejora la implementación anterior generando un vector con una dirección determinada por el ángulo theta y una magnitud igual al radio r, exactamente como en las coordenadas polares. Esto permite que el círculo se mueva en una trayectoria circular con el mismo radio definido originalmente. Además, tanto la línea como el círculo se dibujan correctamente usando las componentes “x” y “y” del vector, asegurando que el movimiento sea suave, preciso y visualmente correcto. Esta es la versión más limpia y adecuada del código original, usando vectores en lugar de trigonometría manual.
Actividad 06
Funciones sinusoides
Enunciado: repasa la función sinusoide aquí.
- Recuerda estos conceptos: velocidad angular, frecuencia, periodo, amplitud y fase.
- Realiza una simulación en la que puedas modificar estos parámetros y observar cómo se comporta la función sinusoide.
Por ejemplo, te doy ideas, si juego solo con la fase, mira este ejemplo.
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Función sinusoidal
Esta simulación nos permite explorar de forma dinámica cómo se comporta una función sinusoide modificando sus principales parámetros: amplitud, período, fase y velocidad angular simplemente tocando las teclas correspondientes a cada concepto.
Código final:
let amplitude = 100;let period = 120; // afecta la frecuencia y la velocidad angularlet phase = 0;let speed = 1;
function setup() { createCanvas(800, 700); textAlign(LEFT, TOP); textSize(16);
}
function draw() { background(255); translate(width / 2, height / 2);
let omega = TWO_PI / period; // velocidad angular let t = frameCount * speed;
let x1 = amplitude * sin(omega * t); let x2 = amplitude * sin(omega * t + phase);
// Onda 1 stroke(0); fill(127); line(0, -30, x1, -30); circle(x1, -30, 32);
// Onda 2 con fase stroke(50, 100, 200); fill(50, 100, 200, 180); line(0, 30, x2, 30); circle(x2, 30, 32);
// Info en pantalla noStroke(); fill(0); text(`Controles del teclado: (Mayus = Shift + "Letra")- A / a → Aumentar / Disminuir amplitud- P / p → Aumentar / Disminuir período- F / f → Aumentar / Disminuir fase- S / s → Aumentar / Disminuir velocidad del tiempoAmplitud (A): ${amplitude}Período (T): ${period}Frecuencia (f): ${(1 / period).toFixed(3)}Velocidad angular (ω): ${omega.toFixed(3)}Fase (ϕ): ${(degrees(phase)).toFixed(1)}°Velocidad de tiempo: ${speed}`, -width / 2 + 10, -height / 2 + 10);}
function keyPressed() { if (key === 'A') amplitude += 10; if (key === 'a') amplitude = max(10, amplitude - 10);
if (key === 'P') period += 10; if (key === 'p') period = max(10, period - 10);
if (key === 'F') phase += PI / 16; if (key === 'f') phase -= PI / 16;
if (key === 'S') speed += 0.1; if (key === 's') speed = max(0.1, speed - 0.1);}
Actividad 07
Repasa conceptos de las unidades anteriores
Enunciado: aplica conceptos de la unidades anteriores tomando como base esta simulación. La idea es que la modifiques incluyendo un concepto de la unidad 1 (aleatoriedad) y la unidad 3 (fuerzas).
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Conceptos anteriores (Aleatoriedad y Fuerzas)
En la simulación se combinó la aleatoriedad para generar variedad en los comportamientos y colores, junto con una simulación de fuerza para hacer que las trayectorias de las bolitas sean curvas, vivas y con un comportamiento físico más natural y atractivo, pero que este se vea afectado por la fuerza gravitacional de un gran atractor el cual se puede arrastrar con el mouse.
class Attractor { constructor() { this.position = createVector(width / 2, height / 2); this.dragging = false; this.rollover = false; this.mass = 30; this.G = 1; }
attract(oscillator) { let force = p5.Vector.sub(this.position, oscillator.position); let distance = force.mag(); distance = constrain(distance, 5, 25);
let strength = (this.G * this.mass * oscillator.mass) / (distance * distance); force.setMag(strength); return force; }
over() { let d = dist(mouseX, mouseY, this.position.x, this.position.y); this.rollover = d < this.mass; }
pressed() { if (this.rollover) { this.dragging = true; } }
released() { this.dragging = false; }
update() { if (this.dragging) { this.position.x = mouseX; this.position.y = mouseY; } }
display() { stroke(0); fill(this.dragging ? color(50) : this.rollover ? color(150) : color(200)); ellipse(this.position.x, this.position.y, this.mass * 2); }}
class Oscillator { constructor() { this.position = createVector(random(width), random(height)); this.velocity = createVector(); this.acceleration = createVector(); this.angle = createVector(random(TWO_PI), random(TWO_PI)); this.angleVelocity = createVector(random(-0.05, 0.05), random(-0.05, 0.05)); this.amplitude = createVector(random(20, width / 2), random(20, height / 2)); this.mass = random(1, 3); this.hue = random(360); // Valor inicial del color }
applyForce(force) { let f = p5.Vector.div(force, this.mass); this.acceleration.add(f); }
update() { this.velocity.add(this.acceleration); this.position.add(this.velocity); this.velocity.mult(0.95); this.acceleration.mult(0);
this.angle.add(this.angleVelocity);
this.hue = (this.hue + 1) % 360; // Actualiza el color constantemente }
show() { let x = sin(this.angle.x) * this.amplitude.x; let y = sin(this.angle.y) * this.amplitude.y;
push(); translate(this.position.x, this.position.y); stroke(0); strokeWeight(2); fill(this.hue, 100, 100); // Color basado en el valor de hue line(0, 0, x, y); circle(x, y, this.mass * 8); pop(); }}
let oscillators = [];let attractor;
function setup() { createCanvas(800, 700); colorMode(HSB, 360, 100, 100); // Cambia el modo de color attractor = new Attractor();
for (let i = 0; i < 15; i++) { oscillators.push(new Oscillator()); }}
function draw() { background(0, 0, 70); // Blanco en modo HSB attractor.over(); attractor.update(); attractor.display();
for (let osc of oscillators) { let force = attractor.attract(osc); osc.applyForce(force); osc.update(); osc.show(); }}
function mousePressed() { attractor.pressed();}
function mouseReleased() { attractor.released();}
Actividad 08
Ondas
Enunciado: vas a observar este código que simula una onda.
El reto es que hagas que se esta onda se mueva como una ola.
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Moviendo la onda
Aquí lo ideal es trasladar ese código del setup() al draw(), y hacer que el ángulo inicial cambie con el tiempo, como si la onda estuviera “desplazándose” hacia la derecha o izquierda, esto dará una sensación de movimiento.
let startAngle = 0; // ángulo base que se va actualizandolet angleVelocity = 0.2;let amplitude = 100;
function setup() { createCanvas(640, 320);}
function draw() { background(255);
let angle = startAngle; // ángulo inicial que se va incrementando para cada círculo
stroke(0); strokeWeight(2); fill(127, 127);
for (let x = 0; x <= width; x += 24) { let y = amplitude * sin(angle); circle(x, y + height / 2, 48); angle += angleVelocity; }
startAngle += 0.07; // hace que la onda se desplace con el tiempo}
Actividad 09
Resortes
Enunciado: modifica esta simulación para crear un sistema de dos resortes conectados en serie.
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Resortes en serie
Al código se añadió una segunda masa conectada a la primera mediante un segundo resorte, formando un sistema en serie; se creó un nuevo método connectBetween en la clase Spring para aplicar la fuerza de resorte entre dos masas, y se actualizó el draw() para aplicar gravedad, actualizar, arrastrar, conectar y dibujar ambos bobs y resortes.
Código final:
class Bob { constructor(x, y) { this.position = createVector(x, y); this.velocity = createVector(); this.acceleration = createVector(); this.mass = 24; this.damping = 0.98; this.dragOffset = createVector(); this.dragging = false; }
update() { this.velocity.add(this.acceleration); this.velocity.mult(this.damping); this.position.add(this.velocity); this.acceleration.mult(0); }
applyForce(force) { let f = force.copy(); f.div(this.mass); this.acceleration.add(f); }
show() { stroke(0); strokeWeight(2); fill(this.dragging ? 200 : 127); circle(this.position.x, this.position.y, this.mass * 2); }
handleClick(mx, my) { let d = dist(mx, my, this.position.x, this.position.y); if (d < this.mass) { this.dragging = true; this.dragOffset.x = this.position.x - mx; this.dragOffset.y = this.position.y - my; } }
stopDragging() { this.dragging = false; }
handleDrag(mx, my) { if (this.dragging) { this.position.x = mx + this.dragOffset.x; this.position.y = my + this.dragOffset.y; } }}
class Spring { constructor(anchorX, anchorY, length) { this.anchor = createVector(anchorX, anchorY); this.restLength = length; this.k = 0.2; }
// If connected to a fixed anchor connect(bob) { let force = p5.Vector.sub(bob.position, this.anchor); let stretch = force.mag() - this.restLength; force.setMag(-1 * this.k * stretch); bob.applyForce(force); }
// If connected between two bobs connectBetween(bobA, bobB) { let force = p5.Vector.sub(bobB.position, bobA.position); let stretch = force.mag() - this.restLength; force.setMag(this.k * stretch); bobA.applyForce(force); force.mult(-1); bobB.applyForce(force); }
constrainLength(bob, minlen, maxlen) { let dir = p5.Vector.sub(bob.position, this.anchor); let len = dir.mag();
if (len < minlen) { dir.setMag(minlen); bob.position = p5.Vector.add(this.anchor, dir); bob.velocity.mult(0); } else if (len > maxlen) { dir.setMag(maxlen); bob.position = p5.Vector.add(this.anchor, dir); bob.velocity.mult(0); } }
show() { fill(127); circle(this.anchor.x, this.anchor.y, 10); }
showLineTo(bob) { stroke(0); line(bob.position.x, bob.position.y, this.anchor.x, this.anchor.y); }
showLineBetween(bobA, bobB) { stroke(0); line(bobA.position.x, bobA.position.y, bobB.position.x, bobB.position.y); }}
let bob1, bob2;let spring1, spring2;
function setup() { createCanvas(640, 400);
spring1 = new Spring(width / 2, 20, 100); // ancla a bob1 bob1 = new Bob(width / 2, 120);
spring2 = new Spring(0, 0, 100); // conectará bob1 a bob2 (no usa ancla real) bob2 = new Bob(width / 2, 220);}
function draw() { background(255);
let gravity = createVector(0, 2); bob1.applyForce(gravity); bob2.applyForce(gravity);
bob1.update(); bob2.update();
bob1.handleDrag(mouseX, mouseY); bob2.handleDrag(mouseX, mouseY);
spring1.connect(bob1); spring2.connectBetween(bob1, bob2);
spring1.constrainLength(bob1, 30, 200);
spring1.showLineTo(bob1); spring2.showLineBetween(bob1, bob2);
spring1.show(); bob1.show(); bob2.show();}
function mousePressed() { bob1.handleClick(mouseX, mouseY); bob2.handleClick(mouseX, mouseY);}
function mouseReleased() { bob1.stopDragging(); bob2.stopDragging();}
Actividad 10
Péndulos
Enunciado: modifica esta simulación para crear un sistema de dos péndulos conectados en serie.
Entrega:
- Enlace a la simulación en el editor de p5.js.
- Código de la simulación.
- Captura de pantalla de la simulación.
🚀 Tu solución:
Péndulos en serie
Se modificó el código original para crear dos péndulos conectados en serie, el segundo péndulo toma como punto de pivote el extremo del primero. Se añadió un nuevo objeto Pendulum con su propia longitud, color y dinámica, y se actualiza su posición en cada cuadro usando la posición actualizada del primero.
Código final:
class Pendulum { constructor(pivot, r, color) { this.pivot = pivot.copy(); this.bob = createVector(); this.r = r; this.angle = PI / 4;
this.angleVelocity = 0.0; this.angleAcceleration = 0.0; this.damping = 0.995; this.ballr = 24.0; this.color = color; this.dragging = false; }
update() { if (!this.dragging) { let gravity = 0.4; this.angleAcceleration = ((-1 * gravity) / this.r) * sin(this.angle);
this.angleVelocity += this.angleAcceleration; this.angle += this.angleVelocity;
this.angleVelocity *= this.damping; } }
show() { this.bob.set(this.r * sin(this.angle), this.r * cos(this.angle)); this.bob.add(this.pivot);
stroke(0); strokeWeight(2); line(this.pivot.x, this.pivot.y, this.bob.x, this.bob.y);
fill(this.color); circle(this.bob.x, this.bob.y, this.ballr * 2); }
clicked(mx, my) { let d = dist(mx, my, this.bob.x, this.bob.y); if (d < this.ballr) { this.dragging = true; } }
stopDragging() { this.angleVelocity = 0; this.dragging = false; }
drag() { if (this.dragging) { let diff = p5.Vector.sub(this.pivot, createVector(mouseX, mouseY)); this.angle = atan2(-1 * diff.y, diff.x) - radians(90); } }
getBobPosition() { return this.bob.copy(); }}
let pendulum1, pendulum2;
function setup() { createCanvas(800, 500); pendulum1 = new Pendulum(createVector(width / 2, 100), 175, color(150, 100, 255)); pendulum2 = new Pendulum(pendulum1.getBobPosition(), 125, color(255, 100, 100));}
function draw() { background(255);
pendulum1.update(); pendulum1.drag(); pendulum1.show();
// Actualiza el segundo péndulo con la posición del pivote del primero pendulum2.pivot = pendulum1.getBobPosition(); pendulum2.update(); pendulum2.drag(); pendulum2.show();}
function mousePressed() { pendulum1.clicked(mouseX, mouseY); pendulum2.clicked(mouseX, mouseY);}
function mouseReleased() { pendulum1.stopDragging(); pendulum2.stopDragging();}
Aplicación
Es momento de aplicar los conceptos aprendidos en la unidad.
Actividad 11
Obra de arte generativa algorítmica interactiva en tiempo real
Enunciado: diseña e implementa una obra de arte generativa algorítmica interactiva en tiempo real en p5.js que cumpla con los siguientes requisitos:
- Selecciona uno de los conceptos con los que experimentaste en la fase de investigación y propón la obra alrededor de este.
- La obra debe ser interactiva en tiempo real. Puedes usar teclado, mouse, música, el micrófono, video, sensor o cualquier otro dispositivo de entrada.
- Documenta el proceso de creación, incluyendo la idea inicial, bocetos, experimentación con el código y el resultado final.
Entrega:
- Enlace a tu obra en el editor de p5.js.
- El código.
- Una captura de pantalla con una imagen de tu obra.
🚀 Tu solución:
Obra de arte generativa algorítmica interactiva
-
Idea inicial:
Mi idea sería básicamente una pieza de arte en la que se interactúe con el micrófono del dispositivo y sólo se reconozcan los bajos, para que así aunque el micrófono esté abierto, no capture todos los sonidos, sólo los graves.
Tengo pensado que al detectar algún bajo, lo que ocurra sea que desde los bordes de la pantalla aparezcan unas ondas de sonido en una ubicación y con un color aleatorio, la idea es que estas vayan hacia un atractor el cual se pueda arrastrar en la pantalla para focalizar estas ondas en determinado lugar o dejar que simplemente vayan hacia el centro, si estas tocan el atractor se desvanecerán, pero si chocan entre ellas o con los bordes, rebotarán.
-
Boceto ideal:
-
Proceso creativo:
Por ahora sólo he logrado que sean bolitas de colores que aparecen en los bordes y que al tocar el atractor el cual ya se mueve, desaparecen, pero aparte de que lo que deseo son ondas y no bolitas, la interacción entre ellas aún es un poco extraña y poco fluida, por lo cual toca corregir algunas cosas o cambiar la forma en que estas interactúan para que tengan un movimiento más visualmente atractivo
Ahora el código si está corregido, se cambiaron las partículas de bolitas por ondas reales que se desvanecen al llegar al attractor, si mueve este, estas cambian su destino tal y como se puede notar en la foto, él inicialmente estaba en la mitad, pero claramente se nota como al moverse abajo a la derecha las ondas van hacia allá, no rebotan entre ellas, sin embargo me parece que se ve bastante bien, en este caso utilicé el concepto de ondas, aleatoriedad y fuerza gravitacional parar lograr resultados como este:
Me parece que este resultado satisfeca lo que quería, es cierto que bajé un poco las expectativas, sin embargo, me parecen mejor de lo que tenía pensado, realmente se ve bastante bien cuando se logran interpolar varias ondas
- Aplicacion algorítmica de Ondas (Recuerda dar acceso al micrófono, ya que detecta sonidos, es posible que tengas que recargar la página más de una vez si el micrófono está siendo utilizado por otro programa o pestaña del navegador)
El código final:
let mic, fft;let ondas = [];let attractor;
function setup() { createCanvas(800, 800); mic = new p5.AudioIn(); mic.start(); fft = new p5.FFT(); fft.setInput(mic); attractor = createVector(width / 2, height / 2); angleMode(DEGREES); noStroke();}
function draw() { background(0, 10);
// Visual attractor fill(255, 100, 100); ellipse(attractor.x, attractor.y, 30);
// Obtener energía del mic let spectrum = fft.analyze(); let bass = fft.getEnergy("bass");
// Umbral para disparar una onda if (bass > 100) { let pos = generarPosDesdeBorde(); ondas.push(new OndaRadial(pos.x, pos.y)); }
// Dibujar y actualizar ondas for (let i = ondas.length - 1; i >= 0; i--) { let o = ondas[i]; o.update(); o.atraer(attractor); o.display();
if (o.alpha <= 0 || o.pos.dist(attractor) < 20) { ondas.splice(i, 1); } }}
function mousePressed() { attractor.set(mouseX, mouseY);}
function generarPosDesdeBorde() { let lado = floor(random(4)); if (lado === 0) return createVector(random(width), 0); // arriba if (lado === 1) return createVector(width, random(height)); // derecha if (lado === 2) return createVector(random(width), height); // abajo return createVector(0, random(height)); // izquierda}
class OndaRadial { constructor(x, y) { this.pos = createVector(x, y); this.vel = p5.Vector.random2D().mult(2); this.radius = 0; this.alpha = 255; this.color = color(random(150,255), random(150,255), random(150,255), this.alpha); }
update() { this.pos.add(this.vel); this.radius += 1.5; this.alpha -= 2; this.color.setAlpha(this.alpha); }
atraer(target) { let fuerza = p5.Vector.sub(target, this.pos); fuerza.setMag(0.5); this.vel.add(fuerza); this.vel.limit(4); }
display() { push(); translate(this.pos.x, this.pos.y); stroke(this.color); noFill(); strokeWeight(1.5); beginShape(); for (let a = 0; a < 360; a += 10) { let r = this.radius + sin(a * 3 + frameCount * 2) * 5; let x = cos(a) * r; let y = sin(a) * r; vertex(x, y); } endShape(CLOSE); pop(); }}
Consolidación y metacognición
He venido escuchando que esta es una fase de relleno 😭. Nada
más lejos de la verdad, de verdad. En esta fase del proceso de aprendizaje
es donde miras hacia atrás
y consolidas lo que aprendiste. Adicionalmente,
reflexionas sobre tu proceso de aprendizaje y las posibilidades de aplicar lo
aprendido en otros contextos.
Actividad 12
Consolidación
Enunciado: analiza tu obra de arte generativa respondiendo las siguientes preguntas:
- ¿Qué concepto de oscilación utilizaste como base para tu obra? Describe cómo lo implementaste.
- ¿Cómo funciona la interacción en tu obra? Explica cómo el usuario puede modificar la obra en tiempo real.
- ¿Qué desafíos encontraste durante el proceso de creación? ¿Cómo los superaste?
- ¿Qué aprendiste sobre las oscilaciones y su aplicación en el arte generativo?
Entrega: responde a las preguntas.
🚀 Tu solución:
Consolidación
Respondiendo a las preguntas propuestas
-
¿Qué concepto de oscilación utilizaste como base para tu obra? Describe cómo lo implementaste.
En este caso lo que hice fue aplicar el concepto oscilatorio de ondas, principalmente (Acompañado de aleatoriedad, de interacción con mouse y micrófono, y de fuerzas gravitacionales), este concepto es la base de la obra de arte algorítmica, en este caso nacen o se generan con la interacción con el micrófono y se dispersan hacia un attractor el cual deja un efecto de ghosting y se puede cambiar de lugar con el mouse, lo cual hace que las ondas puedan dirigirse a c ualquier parte de la pantalla a merced del usuario.
-
¿Cómo funciona la interacción en tu obra? Explica cómo el usuario puede modificar la obra en tiempo real.
El usuario tiene total libertad de decidir qué tantas ondas genera simplemente hablando, o haciendo un sonido constante, dando palmadas, etc, estas ondas se generan aleatoriamente al rededor de la pantalla y sus colores también son aleatorios, sin embargo, se pueden dirigir hacia un lugar específico o simplemente dejar que se pierdan en el centro si es que se decide dejar el attractor ahí, el cual se puede mover de una manera tan sencilla como dando click en cualquier parte del canvas, de esta manera el usuario puede generar muchas ondas que se dirijan al medio, o pocas ondas que se dirijan hacia una esquuina u otra, todo es cuestión de interacción directa.
-
¿Qué desafíos encontraste durante el proceso de creación? ¿Cómo los superaste?
En este caso lo que documenté en el proceso, en un principio no eran ni siquiera ondas, sino partículas circulares que no venían desde cualquier parte de los bordes sino desde las esquinas, la interacción entre ellas mismas era un poco curiosa y el attractor no colaboraba mucho que digamos, luego tuve problemas ya que tenía errores de código y tuve que empatar todo en el mismo archivo js en lugar de partir el sketch y la clase de las ondas.
Otro problema fue el tema del micrófono, por algún motivo se complicó la detección, parece que era porque otros programas lo estaban utilizando y eso generaba una especie de incompatibilidad, sobre todo en la vista el fullscreen ya estando fuera del editor de código.
Y pues, los superé realmente con prueba y error, dañando el código varias veces y volviendo a versiones que aunque no eran finales, eran parcialmente funcionales.
-
¿Qué aprendiste sobre las oscilaciones y su aplicación en el arte generativo?
Abordar esta pregunta me parece muy importante, y es que considero que las oscilaciones en el arte generativo son demasiado frecuentes, es un hecho que la música son vibraciones, y es que una oscilación, tal y como está definida es un movimiento repetitivo alrededor de un punto de equilibrio, esto da mucho juego para que se interactúe de mucha maneras con las mismas, modificando no sólo como son visualmente mostradas, sino también emparejando o desemparejando la frecuencia dependiendo de sonidos, colores, etc, tiene mucho sentido ahora el hecho de que muchos de los visualizadores predeterminados que vienen en algunos softwares de reproducción son literalmente ondas, sólo que con diseños más o menos atractivos, es muy importante entender que esto es clave para el arte generativo, las oscilaciones tienen un papel muy importante en todo esto.
Actividad 13
Metacognición
Enunciado: reflexiona sobre tu propio proceso de aprendizaje en esta unidad.
- ¿Qué estrategias de aprendizaje utilizaste?
- ¿Qué te funcionó mejor para comprender los conceptos?
- ¿Qué podrías mejorar?
- ¿Cómo te sentiste durante el proceso de aprendizaje?
- ¿Qué te motivó?
- ¿Qué te frustró?
Entrega: respuesta a las preguntas.
🚀 Tu solución:
Metacognición
-
¿Qué estrategias de aprendizaje utilizaste?
Realmente, como ya he mencionado antes, me sigo sintiendo muy cómodo con mi estrategia principal, ver nuevos conceptos y leer sobre ellos en páginas de internet, compararlos con conceptos ya familiares y en caso tal de ser algo un poco más complicado de entender pedirle a chatgpt que haga esto por mí, poniendo como base conceptos que ya conozco y que soy consciente de que están relacionados y pedirle que haga una comparación y una explicación del nuevo usando como referencia el familiar ya.
-
¿Qué te funcionó mejor para comprender los conceptos?
Es lo que ya mencioné, lo principal es eso, comparar conceptos nuevos con los que ya conozco, esto realmente es sumamente importante ya que me permite asociar cosas nuevas con otras que ya conozco, haciendo el proceso de aprendizaje algo muchísimo más digerible e interesante.
-
¿Qué podrías mejorar?
Mis capacidades de programación, en algunos momentos me pongo muy vago al respecto y lo único que hago es tirar un par de líneas y cuando empiezo a ver errores raros, en lugar de esforzarme en entenderlos le pido a chatgpt que los corrija y me explique qué pasa y poco más, es realmente una mala costumbre y algo difícil de dejar pero si es un punto importante a mejorar.
-
¿Cómo te sentiste durante el proceso de aprendizaje?
Nuevamente muy cómodo, ya llevo tiempo sintiéndome muy tranquilo con la forma en la que estoy llevando el curso y las nuevas unidades, siento que digiero los conceptos fácilmente y me estoy tomando el tiempo de repasarlos para tenerlos frescos constantemente hasta que ya logro retenerlos por más tiempo.
-
¿Qué te motivó?
Era interesante jugar con las interacciones y cómo se veían estas, el hecho de cambiar arbitrariamente unos pocos valores y ver como todo toma un rumbo visual muy distinto es algo realmente disfrutable.
-
¿Qué te frustró?
Que por mis limitaciones al momento de codificar, en ocasiones tengo el error justo ahí, y por mucho que leo y leo hay algunos que realmente no entiendo, o si entiendo qué pasa, no estoy seguro de cómo arreglarlo