Saltearse al contenido

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:

  • En esta obra me parecieron impactantes la relación entre el sonido y los diferentes visuales que se generaban. El semestre pasado uno de mis compañeros exploró una aplicación similar pero con un teclado MIDI que intercedía también para cambiar la visualización de partículas. Mi parte favorita del video fueron los diferentes escenarios en los que se plantearon los proyectos musicales porque significa que la posibilidad de replicabilidad de ese proyecto es mucho más amplia y que básicamente puedes hacerlo bajo el marco de diseño que tú prefieras. La estética no me encantó pero fue interesante la manera en la interactuan con los distintos fluidos en las escenas. Me gustaría saber sobre cómo se están procesando las notas musicales y cómo se están sincronizando el audio con el video.

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() y pop()? 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:

Simulación 1

Manejo de ángulos

¿Qué está pasando en esta simulación? ¿Cuál es la interacción?

En este código, se está creando una animación en la que una línea y dos círculos rotan alrededor de su punto central. La interacción se da cuando presionas una tecla, ya que al presionar cualquier tecla, la variable angle aumenta en 0.1, lo que hace que los elementos gráficos rotan.

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?

La instrucción translate(width / 2, height / 2); mueve el origen del sistema de coordenadas al centro de la pantalla. Es decir, quepara realizar el desplazamiento no se hace manualmente sino que se realiza una rotación en el centro del canvas. Cuando el origen está en el centro, todo lo que se dibuje (líneas, círculos, etc.) se rotará alrededor de este centro.

Cuál es la relación entre el sistema de coordenadas y la función rotate().

La función rotate() rota los objetos alrededor del origen del sistema de coordenadas. Al mover el origen con translate(), la rotación afectará a los objetos en relación con el nuevo centro (que ahora está en el centro de la pantalla). Sin translate(), la rotación se aplicaría respecto al origen por defecto (en la esquina superior izquierda).

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?

Los elementos se dibujan con coordenadas relativas al origen (0,0), pero como translate(width / 2, height / 2) mueve el origen al centro de la pantalla, todo se posiciona alrededor de ese punto. Aunque el código de dibujo es el mismo en cada frame, rotate(angle) aplica una rotación acumulativa. Como angle aumenta con cada tecla presionada, los objetos giran progresivamente en torno al centro.

Simulación 2

Apunten en la dirección del movimiento

Identifica el marco motion 101. ¿Qué es lo que se está haciendo en este marco?

El código aplica Motion 101 en la función update(), donde en cada frame: se calcula la dirección y la aceleración, se ajusta la velocidad y se actualiza la posición.

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()?

Es un método de p5.Vector que devuelve el ángulo de dirección de un vector respecto al eje x.

let angle = this.velocity.heading();

this.velocity.heading() obtiene el ángulo en radianes que forma el vector de velocidad con el eje x. Este ángulo se usa para rotar el objeto en la dirección en la que se mueve.

¿Qué hace la función push() y pop()? Realiza algunos experimentos para entender su funcionamiento.

push(): Guarda el estado actual del sistema de coordenadas. pop(): Restaura el estado guardado antes del push().

Sin push() y pop(), Con push() y pop(), las transformaciones solo afectan el rectángulo del objeto y no el resto del lienzo.

Sin push() y pop() (Transformaciones Acumuladas): experimento 1

  • Cada frame, el sistema de coordenadas se sigue rotando y trasladando.
  • El rectángulo parece girar de forma descontrolada.

Con push() y pop() (Transformaciones Controladas): experimento 2

  • El rectángulo azul en el centro rota correctamente.
  • El rectángulo rojo no se ve afectado por la rotación, porque está fuera de push() y pop().

Varias transformaciones independientes: experimento 3

¿Qué hace rectMode(CENTER)? Realiza algunos experimentos para entender su funcionamiento.

Esta función cambia la forma en que se posicionan los rectángulos.

  • Por defecto (rectMode(CORNER)), un rectángulo se dibuja desde la esquina superior izquierda.
  • Con rectMode(CENTER), el rectángulo se dibuja desde su centro.

experimento

¿Cuál es la relación entre el ángulo de rotación y el vector de velocidad?

  • El vector de velocidad (this.velocity) define la dirección y rapidez con la que se mueve el objeto.
  • El ángulo de rotación (angle) se obtiene con this.velocity.heading(), que devuelve el ángulo en radianes correspondiente a la dirección del vector de velocidad.
  • La rotación del objeto (rotate(angle)) hace que el dibujo del objeto se alinee con la dirección en la que se mueve.

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:

image

Enlace a la simulación aquí

let vehicle;
function setup() {
createCanvas(600, 400);
vehicle = new Vehicle(width / 2, height - 50);
}
function draw() {
// Dibujar el fondo
background(135, 206, 235); // Cielo azul
// Dibujar el sol
fill(255, 204, 0);
noStroke();
ellipse(80, 80, 60, 60);
// Dibujar montañas
fill(16, 165, 23);
triangle(0, 400, 200, 100, 500, 400);
triangle(300, 400, 500, 150, 700, 400);
// Dibujar la carretera
fill(50);
rect(0, height - 40, width, 40);
// Dibujar líneas de la carretera
stroke(255);
strokeWeight(4);
for (let i = 0; i < width; i += 40) {
line(i, height - 20, i + 20, height - 20);
}
vehicle.update();
vehicle.edges();
vehicle.display();
}
function keyPressed() {
if (keyCode === LEFT_ARROW) {
vehicle.applyForce(createVector(-0.5, 0));
} else if (keyCode === RIGHT_ARROW) {
vehicle.applyForce(createVector(0.5, 0));
}
}
class Vehicle {
constructor(x, y) {
this.position = createVector(x, y);
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
this.topspeed = 6;
}
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
}
edges() {
if (this.position.x > width) this.position.x = 0;
if (this.position.x < 0) this.position.x = width;
}
display() {
let angle = this.velocity.heading();
push();
translate(this.position.x, this.position.y);
rotate(angle);
fill(21, 16, 165 );
stroke(255);
strokeWeight(2);
// Cuerpo del carro (triángulo estilizado)
beginShape();
vertex(20, 0);
vertex(-15, -10);
vertex(-15, 10);
endShape(CLOSE);
// Ruedas
fill(50);
pop();
}
}

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:

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.

  • En el código, esto está implementado en la clase Mover en el método update().

El marco de Motion 101 describe cómo los objetos se mueven en un entorno simulado utilizando posición, velocidad y aceleración.

  • Aplicar una fuerza → Se suma una aceleración basada en la fuerza aplicada.
  • Actualizar la velocidad → La aceleración cambia la velocidad.
  • Actualizar la posición → La velocidad cambia la posición.

⭐Cuando se aplican múltiples fuerzas en un solo frame, es importante sumarlas antes de actualizar la velocidad y posición. En applyForce(), las fuerzas se dividen por la masa y se agregan a la aceleración:

applyForce(force) {
let f = p5.Vector.div(force, this.mass);
this.acceleration.add(f); // Se acumulan todas las fuerzas aplicadas en el frame
}

Luego, en update(), la aceleración se usa para modificar la velocidad y la posición:

update() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.acceleration.mult(0); // Importante: Se resetea la aceleración para el siguiente frame
}

⭐Si no lo hacemos, las fuerzas se seguirían acumulando indefinidamente, lo que haría que el objeto acelerara de forma incontrolable. Al resetearla en cada frame, solo se consideran las fuerzas aplicadas en ese instante.

Identifica dónde está el Attractor en la simulación. Cambia el color de este.

  • Se dibuja en la clase attractor.js y se dibuja en el método display()
display() {
ellipseMode(CENTER);
stroke(0);
if (this.dragging) {
fill(50);
} else if (this.rollover) {
fill(100);
} else {
fill(161, 82, 255); // Aquí le cambié el color por moradooo
}
ellipse(this.position.x, this.position.y, this.mass * 2);
}

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.

Simulación modificada

  • Movimiento del Attractor con el mouse: implementé mousePressed() y mouseReleased() para arrastrar el Attractor.
  • Cambio de color cuando el mouse está sobre el Attractor: agregué una función rollover() que detecta si el mouse está sobre el Attractor y cambia su color por fucsia.

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:

¿Cuál es la relación entre r y theta con las posiciones x y y?

  • r (radio) es la distancia desde el origen.
  • θ (ángulo) es la dirección en la que se encuentra el punto con respecto al eje 𝑥

Ambos se combinan para definir la ubicación de un punto en el plano de forma radial y angular. Este sistema es particularmente útil cuando trabajamos con fenómenos que tienen simetría radial o circular, como la trayectoria de planetas, ondas, o incluso en gráficos y simulaciones.

Modificaciones

Modificación 1

  • Problema: en consola sale un ReferenceError: x is not defined. Utiliza un vector unitario (de magnitud 1) sin considerar el valor de r, por lo que la posición del círculo y la longitud de la línea no cambian en función de r. El movimiento será constante, con una distancia fija de 1 unidad desde el origen.

  • Solución: reemplazar x e y por las componentes del vector v, que son v.x y v.y. Como v es un vector creado con fromAngle(), estas propiedades representan las coordenadas x y y del vector unitario.

Modificación 2

  • El vector creado ahora tiene una longitud igual a r. El círculo y la línea se dibujarán correctamente en función de la distancia del origen definida por r y la dirección dada por el ángulo theta. Además, la posición del círculo se desplazará de acuerdo con theta, haciendo que se mueva en un círculo alrededor del centro de la pantalla.

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:

Simulación inspirada en el juego Flappy Bird

image

Simulación aquí

El código

let bird;
let pipes = [];
let gravity = 0.5;
let lift = -10;
let gameOver = false;
function setup() {
createCanvas(400, 600);
bird = new Bird();
pipes.push(new Pipe());
}
function draw() {
background(135, 206, 250);
if (!gameOver) {
bird.update();
bird.show();
if (frameCount % 100 === 0) {
pipes.push(new Pipe());
}
for (let i = pipes.length - 1; i >= 0; i--) {
pipes[i].update();
pipes[i].show();
if (pipes[i].offscreen()) {
pipes.splice(i, 1);
}
if (pipes[i].hits(bird)) {
gameOver = true;
}
}
if (bird.y >= height) {
gameOver = true;
}
} else {
showGameOverScreen();
}
}
function keyPressed() {
if (key === ' ') {
if (!gameOver) {
bird.up();
} else {
resetGame();
}
}
}
function showGameOverScreen() {
fill(0, 0, 0, 150);
rect(0, 0, width, height);
fill(255);
textSize(32);
textAlign(CENTER, CENTER);
text("Game Over", width / 2, height / 3);
textSize(16);
text("Presiona ESPACIO para reiniciar", width / 2, height / 2);
}
function resetGame() {
bird = new Bird();
pipes = [];
pipes.push(new Pipe());
gameOver = false;
}
class Bird {
constructor() {
this.x = 50;
this.y = height / 2;
this.velocity = 0;
}
up() {
this.velocity = lift;
}
update() {
this.velocity += gravity;
this.velocity *= 0.9;
this.y += this.velocity;
if (this.y > height) {
this.y = height;
this.velocity = 0;
}
if (this.y < 0) {
this.y = 0;
this.velocity = 0;
}
}
show() {
fill(255, 204, 0);
ellipse(this.x, this.y, 30, 30);
}
}
class Pipe {
constructor() {
this.x = width;
this.w = 50;
this.gap = 150;
this.amplitude = 50;
this.frequency = 0.02;
this.offset = random(TWO_PI);
}
update() {
this.x -= 3;
}
show() {
let centerY = height / 2 + this.amplitude * sin(frameCount * this.frequency + this.offset);
let top = centerY - this.gap / 2;
let bottom = centerY + this.gap / 2;
fill(34, 139, 34);
rect(this.x, 0, this.w, top);
rect(this.x, bottom, this.w, height - bottom);
}
offscreen() {
return this.x + this.w < 0;
}
hits(bird) {
let centerY = height / 2 + this.amplitude * sin(frameCount * this.frequency + this.offset);
let top = centerY - this.gap / 2;
let bottom = centerY + this.gap / 2;
if (bird.x > this.x && bird.x < this.x + this.w) {
if (bird.y < top || bird.y > bottom) {
return true;
}
}
return false;
}
}

Enlace a la simulación en el editor de p5.js. Código de la simulación. Captura de pantalla de la simulación.

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:

Simulación con un oscilador de objetos

Enlace a la simulación aquí

Activación del modo atracción

Estado: Verde (Modo de Atracción)

Comportamiento: El botón está en verde, lo que indica que el modo de atracción está activado.

Osciladores: Los osciladores son atraídos hacia el centro de la pantalla (en la posición (width / 2, height / 2)).

Movimiento: Los osciladores se mueven de forma más suave y lenta, siguiendo una fuerza de atracción suave hacia el centro del lienzo, calculada a través de p5.Vector.sub(center, this.position).setMag(0.05). La velocidad de oscilación es más baja en este modo lo que simula un congelamiento.

Fuerza de atracción: La atracción hacia el centro está activada, pero los osciladores se mantienen estáticos con pequeños movimientos en lugar de moverse libremente. Esto da la apariencia de que las bolas están “congeladas” con pequeños movimientos aleatorios.

Fondo: El fondo se pone negro.

Color de las esferas: El color de las esferas cambia a azul claro, como un efecto visual que indica que están en el modo de atracción.

image

Modo desactivado - locura total

image

Estado del botón: Rojo (Modo Normal)

Comportamiento: El botón está en rojo, lo que indica que el modo de atracción está desactivado.

Osciladores: Los osciladores están libres de la atracción al centro y se mueven aleatoriamente con una velocidad de oscilación más alta, determinada por las fuerzas aleatorias aplicadas a angleVelocity en la función update().

Movimiento: Las esferas se mueven de forma más caótica, impulsadas por la aleatoriedad en sus velocidades (angleVelocity). Este comportamiento genera trayectorias erráticas y dinámicas.

Fuerza de atracción: No hay fuerza de atracción hacia el centro del lienzo.

Fondo: El fondo es blanco.

Color de las esferas: El color de las esferas está determinado por el ruido gaussiano, lo que da lugar a una mezcla de colores morados (usando el valor del ruido para calcular el color de las bolas).

Código - sketch.js

// An array of objects
let oscillators = [];
let attractMode = false; // Controla si la fuerza de atracción está activada
function setup() {
createCanvas(640, 240);
// Inicializar todos los osciladores
for (let i = 0; i < 10; i++) {
oscillators.push(new Oscillator());
}
}
function draw() {
// Cambiar fondo y color de las bolitas dependiendo del estado
if (attractMode) {
background(0); // Fondo negro cuando las partículas están atraídas
} else {
background(255); // Fondo blanco cuando no están atraídas
}
// Dibujar el botón circular
fill(attractMode ? 'green' : 'red');
noStroke();
ellipse(40, 40, 30, 30); // Posición y tamaño del botón
// Verificar colisiones entre osciladores
for (let i = 0; i < oscillators.length; i++) {
for (let j = i + 1; j < oscillators.length; j++) {
oscillators[i].checkCollision(oscillators[j]);
}
}
// Actualizar y mostrar los osciladores
for (let osc of oscillators) {
osc.update();
osc.show();
}
}
// Detectar clic en el botón
function mousePressed() {
let d = dist(mouseX, mouseY, 40, 40);
if (d < 15) { // Verifica si se hizo clic en el círculo
attractMode = !attractMode; // Alterna el estado del modo de atracción
for (let osc of oscillators) {
osc.isAttracted = attractMode; // Actualiza la fuerza de atracción de los osciladores
}
}
}

Código - oscillator.js

class Oscillator {
constructor() {
this.angle = createVector();
this.angleVelocity = createVector(random(-0.05, 0.05), random(-0.05, 0.05));
this.angleAcceleration = createVector(0, 0);
this.amplitude = createVector(
random(20, width / 2),
random(20, height / 2)
);
this.position = createVector(width / 2, height / 2);
this.radius = 16; // Radio de la esfera
this.isAttracted = false; // Variable para saber si está siendo atraído
this.baseColor = color(random(100, 255), random(50, 150), random(200, 255)); // Color base de la bolita
}
update() {
// Aplicar la fuerza de atracción si está activada
if (this.isAttracted) {
let center = createVector(width / 2, height / 2);
let force = p5.Vector.sub(center, this.position);
force.setMag(0.05); // Fuerza de atracción suave
this.angleVelocity.add(force);
} else {
// Simular una fuerza aleatoria
this.angleAcceleration = createVector(random(-0.001, 0.001), random(-0.001, 0.001));
this.angleVelocity.add(this.angleAcceleration);
this.angle.add(this.angleVelocity);
}
// Caminata aleatoria en la amplitud
this.amplitude.x += random(-1, 1);
this.amplitude.y += random(-1, 1);
this.amplitude.x = constrain(this.amplitude.x, 20, width / 2);
this.amplitude.y = constrain(this.amplitude.y, 20, height / 2);
// Actualizar la posición
this.position.x = width / 2 + sin(this.angle.x) * this.amplitude.x;
this.position.y = height / 2 + sin(this.angle.y) * this.amplitude.y;
// Rebote en los bordes
if (this.position.x - this.radius < 0 || this.position.x + this.radius > width) {
this.angleVelocity.x *= -1;
}
if (this.position.y - this.radius < 0 || this.position.y + this.radius > height) {
this.angleVelocity.y *= -1;
}
}
show() {
let speed = this.angleVelocity.mag();
let col;
if (this.isAttracted) {
// Si está siendo atraído, el color es azul
col = color(60, 204, 254);
} else {
// Si no está siendo atraído, el color varía con el ruido gaussiano
let noiseValue = randomGaussian();
col = color(map(noiseValue, -3, 3, 100, 255), map(noiseValue, -3, 3, 0, 100), map(noiseValue, -3, 3, 150, 255));
}
fill(col);
push();
stroke(0);
strokeWeight(2);
line(width / 2, height / 2, this.position.x, this.position.y);
circle(this.position.x, this.position.y, this.radius * 2);
pop();
}
checkCollision(other) {
let d = dist(this.position.x, this.position.y, other.position.x, other.position.y);
if (d < this.radius * 2) {
// Vectores de diferencia de posición y velocidad
let normal = p5.Vector.sub(other.position, this.position).normalize();
let relativeVelocity = p5.Vector.sub(this.angleVelocity, other.angleVelocity);
// Producto punto entre la velocidad relativa y la normal
let velocityAlongNormal = relativeVelocity.dot(normal);
// Si las esferas se están acercando
if (velocityAlongNormal > 0) {
return; // No hay colisión
}
// Coeficiente de restitución (elasticidad perfecta, e = 1)
let e = 1;
// Cálculo de los cambios de velocidad
let impulse = (2 * velocityAlongNormal) / (this.radius + other.radius);
let impulseVector = normal.mult(impulse);
// Actualizar las velocidades de las esferas
this.angleVelocity.sub(impulseVector);
other.angleVelocity.add(impulseVector);
}
}
}

Código - oscillator.js

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:

Simulación de una ola

Enlace a la simulación aquí

image

Código

// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
let angle = 10;
let angleVelocity = 0.08;
let amplitude = 60;
function setup() {
createCanvas(640, 240);
background(255);
stroke(0);
strokeWeight(2);
fill(127, 127);
}
function draw() {
background(255); // Vuelve a dibujar el fondo para eliminar las posiciones anteriores
// Calcular la posición de los círculos y dibujarlos
for (let x = 0; x <= width; x += 24) {
// 1) Calcular la posición y en función de la amplitud y el seno del ángulo
let y = amplitude * sin(angle + x * 0.02); // Desplazamiento adicional para que se vea la onda moverse
// 2) Dibujar el círculo en la posición (x, y)
circle(x, y + height / 2, 48);
}
// 3) Incrementar el ángulo para que la onda se mueva
angle += angleVelocity;
}

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:

Simulación de resortes

Enlace a la simulación aquí

image

Código

sketch.js

let bob;
let spring1;
let spring2;
function setup() {
createCanvas(640, 240);
// Modificamos las posiciones para que los resortes estén más separados
spring1 = new Spring(width / 4, 90, 150); // Resorte más largo
spring2 = new Spring(3 * width / 4, 10, 150); // Resorte más largo
bob = new Bob(width / 2, 200);
}
function draw() {
background(255);
// Gravedad aplicada al bob
let gravity = createVector(0, 0.2); // Menos gravedad para un movimiento más controlado
bob.applyForce(gravity);
// Actualizar bob y manejar el arrastre con el mouse
bob.update();
bob.handleDrag(mouseX, mouseY);
// Cambiar la constante del resorte (elasticidad) dinámicamente
let distance = dist(mouseX, mouseY, bob.position.x, bob.position.y);
let elasticity = map(distance, 0, width / 2, 0.05, 0.5); // Valor de k más pequeño
// Aplicar la fuerza de los resortes
spring1.k = elasticity;
spring2.k = elasticity;
// Conectar los resortes al bob y restringir la longitud
spring1.connect(bob);
spring2.connect(bob);
spring1.constrainLength(bob, 50, 250); // Rango de longitud permitida mayor
spring2.constrainLength(bob, 50, 250); // Rango de longitud permitida mayor
// Dibujar los resortes y el bob
spring1.showLine(bob);
spring2.showLine(bob);
bob.show();
spring1.show();
spring2.show();
}
function mousePressed() {
bob.handleClick(mouseX, mouseY);
}
function mouseReleased() {
bob.stopDragging();
}

spring.js

class Spring {
constructor(x, y, length) {
this.anchor = createVector(x, y);
this.restLength = length;
this.k = 0.2; // Elasticidad inicial
}
// Calcular y aplicar la fuerza del resorte
connect(bob) {
let force = p5.Vector.sub(bob.position, this.anchor);
let currentLength = force.mag();
let stretch = currentLength - this.restLength;
force.setMag(-1 * this.k * stretch);
bob.applyForce(force);
}
constrainLength(bob, minlen, maxlen) {
let direction = p5.Vector.sub(bob.position, this.anchor);
let length = direction.mag();
if (length < minlen) {
direction.setMag(minlen);
bob.position = p5.Vector.add(this.anchor, direction);
bob.velocity.mult(0);
} else if (length > maxlen) {
direction.setMag(maxlen);
bob.position = p5.Vector.add(this.anchor, direction);
bob.velocity.mult(0);
}
}
show() {
fill(127);
circle(this.anchor.x, this.anchor.y, 10);
}
showLine(bob) {
stroke(0);
line(bob.position.x, bob.position.y, this.anchor.x, this.anchor.y);
}
}

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:

Simulación de istema de dos péndulos conectados en serie

Enlace a la simulación aquí

image

Código

pendulum.js

class Pendulum {
constructor(x, y, r, parent = null) {
this.pivot = createVector(x, y);
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.dragging = false;
this.parent = parent; // Si tiene un padre, significa que es el segundo péndulo
}
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;
}
// Si el péndulo es el segundo, su pivote es el bob del primero
if (this.parent) {
this.pivot.set(this.parent.bob.x, this.parent.bob.y);
}
}
show() {
this.bob.set(this.r * sin(this.angle), this.r * cos(this.angle), 0); // Convertir coordenadas polares a cartesianas
this.bob.add(this.pivot); // Ajustar la posición relativa
stroke(0);
strokeWeight(2);
// Dibuja la línea primero para que quede detrás de la bola
line(this.pivot.x, this.pivot.y, this.bob.x, this.bob.y);
fill(127);
noStroke();
// Dibuja la bola después para que quede sobre la línea
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);
}
}
}

sketch,js

let pendulum1, pendulum2;
function setup() {
createCanvas(640, 400);
// Crear dos péndulos conectados en serie
pendulum1 = new Pendulum(width / 2, 10, 150);
pendulum2 = new Pendulum(pendulum1.bob.x, pendulum1.bob.y, 150, pendulum1);
}
function draw() {
background(255);
// Actualizar y mostrar ambos péndulos
pendulum1.update();
pendulum1.show();
pendulum1.drag();
pendulum2.update();
pendulum2.show();
pendulum2.drag();
// Verificar colisión y ajustar velocidades
checkCollision();
}
function mousePressed() {
pendulum1.clicked(mouseX, mouseY);
pendulum2.clicked(mouseX, mouseY);
}
function mouseReleased() {
pendulum1.stopDragging();
pendulum2.stopDragging();
}
// Detecta si las esferas colisionan y ajusta velocidades para imitar un rebote
function checkCollision() {
let d = dist(pendulum1.bob.x, pendulum1.bob.y, pendulum2.bob.x, pendulum2.bob.y);
let minDist = pendulum1.ballr + pendulum2.ballr;
if (d < minDist) {
// Intercambiar velocidades para simular transferencia de energía
let tempVel = pendulum1.angleVelocity;
pendulum1.angleVelocity = pendulum2.angleVelocity;
pendulum2.angleVelocity = tempVel;
}
}

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:

PROCESO CREATIVO

  • Idea inicial

    • Quisiera explorar con el concepto de ondas, con la suavidad de su movimiento y con la fuerza de atracción hacia un atractor. Me inspiré en imáganes de referencia en Pinterest y en el video Generative Art de Hao Hua.

Imágenes de referencia

image

image

image

  • Experimentación con el código

Realicé varias versiones del proyecto hasta crear uno que me hiciera sentir cómoda con el resultado pues quería que se sintiera fluido y pausado para que diera espacio a la contemplación de la obra.

image

image

image

  • Resultado final

Versión FINAL

image

image

Código

let particles = [];
let flowField;
let cols, rows;
let scl = 20;
let inc = 0.1;
function setup() {
createCanvas(windowWidth, windowHeight);
cols = floor(width / scl);
rows = floor(height / scl);
flowField = new Array(cols * rows);
for (let i = 0; i < 5000; i++) {
particles.push(new Particle());
}
}
function draw() {
background(0, 10);
let yoff = 0;
for (let y = 0; y < rows; y++) {
let xoff = 0;
for (let x = 0; x < cols; x++) {
let index = x + y * cols;
let angle = noise(xoff, yoff) * TWO_PI * 4;
let v = p5.Vector.fromAngle(angle);
flowField[index] = v;
v.setMag(1);
xoff += inc;
}
yoff += inc;
}
for (let particle of particles) {
particle.follow(flowField);
particle.update();
particle.edges();
particle.show();
}
}
class Particle {
constructor() {
this.pos = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.acc = createVector(0, 0);
this.maxSpeed = 2;
this.prevPos = this.pos.copy();
// Definir si la partícula sigue el mouse de forma sinusoidal o directa
this.attractionFactor = random(0.2, 1);
this.sinusoidal = this.attractionFactor > 0.6; // 60% en zigzag
// Parámetros personalizados para el movimiento sinusoidal
this.sinAmp = random(3, 15); // Amplitud entre 3 y 15 px
this.freq = random(0.05, 0.3); // Frecuencia entre 0.05 y 0.3
this.phaseOffset = random(TWO_PI); // Fase inicial diferente para cada partícula
}
follow(vectors) {
let x = floor(this.pos.x / scl);
let y = floor(this.pos.y / scl);
let index = x + y * cols;
let force = vectors[index];
this.applyForce(force);
}
applyForce(force) {
this.acc.add(force);
}
update() {
this.vel.add(this.acc);
this.vel.limit(this.maxSpeed);
this.pos.add(this.vel);
this.acc.mult(0);
}
edges() {
if (this.pos.x > width) this.pos.x = 0;
if (this.pos.x < 0) this.pos.x = width;
if (this.pos.y > height) this.pos.y = 0;
if (this.pos.y < 0) this.pos.y = height;
}
show() {
let distToMouse = dist(this.pos.x, this.pos.y, mouseX, mouseY);
let attractionStrength = map(distToMouse, 0, width, 0.05, 0);
let angle = atan2(mouseY - this.pos.y, mouseX - this.pos.x);
let forceX, forceY;
if (this.sinusoidal) {
// Movimiento en zigzag con parámetros personalizados
let sinOffsetX = sin(frameCount * this.freq + this.phaseOffset) * this.sinAmp;
let sinOffsetY = cos(frameCount * this.freq + this.phaseOffset) * this.sinAmp;
forceX = cos(angle) * attractionStrength * 5 + sinOffsetX;
forceY = sin(angle) * attractionStrength * 5 + sinOffsetY;
} else {
// Movimiento directo
forceX = cos(angle) * attractionStrength * 5;
forceY = sin(angle) * attractionStrength * 5;
}
this.vel.x += forceX;
this.vel.y += forceY;
// Color cambia con la distancia al mouse
let colorChange = map(distToMouse, 0, width, 255, 0);
stroke(colorChange, 100, 255 - colorChange, 100);
// Dibujar líneas que muestran el recorrido
strokeWeight(1);
line(this.pos.x, this.pos.y, this.pos.x - this.vel.x, this.pos.y - this.vel.y);
}
}

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:

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.

Me basé en la oscilación sinusoidal para definir el movimiento de las partículas. La idea era que no solo se movieran hacia el mouse, sino que lo hicieran con trayectorias ondulantes, agregando más fluidez y variación en los patrones.

Implementación:

  • Cada partícula tiene su propia amplitud, frecuencia y fase aleatoria, lo que hace que las oscilaciones sean distintas entre ellas.
  • En lugar de un movimiento recto, las partículas siguen caminos que combinan atracción y oscilación, con un factor que decide si se moverán más en zigzag o de manera más directa.
  • Usé sin(frameCount * frecuencia + fase) para modificar sus trayectorias, haciendo que el movimiento no sea repetitivo ni predecible.

¿Cómo funciona la interacción en tu obra? Explica cómo el usuario puede modificar la obra en tiempo real.

La interacción se da con el movimiento del cursor. El usuario no controla las partículas directamente, pero sí influye en su trayectoria.

Lo que pasa en tiempo real:

  • El cursor funciona como atractor y algunas partículas lo siguen de forma más directa, otras oscilan alrededor de su posición.
  • No todas las partículas reaccionan igual, algunas se ven más afectadas por la oscilación que otras.
  • Si el usuario mueve el mouse rápido, se pueden generar estelas más caóticas, mientras que movimientos lentos producen ondulaciones más suaves.

¿Qué desafíos encontraste durante el proceso de creación? ¿Cómo los superaste?

  • Hice muchas versiones, las primeras eran bastante aburridas y no tenían ningún tipo de interacción, sin embargo, seguí implementando funciones y conceptos. La versión tres fue mi favorita pero no aplicaba ninguno de los conceptos vistos en la unidad, igualmente me fue de mucha utilidad para replantear la propuesta y que siguiera teniendo el mismo concepto.
  • Hubo versiones en las que las oscilaciones eran iguales y se sentía artificial. Para romper la monotonía, agregué un desfase aleatorio (phaseOffset) en cada partícula, así no se mueven al mismo ritmo.
  • La atracción al mouse a veces cancelaba el movimiento sinusoidal por lo que las partículas terminaban siguiendo trayectorias rectas así que ajusté la mezcla entre la fuerza de atracción y la oscilación para que ambas coexistieran sin que una dominara completamente.
  • Como son muchas partículas el sketch se volvía lento en ciertos momentos y tuve que optimizar el código, reduciendo la cantidad de cálculos por partícula y evitando dibujar demasiadas líneas en cada frame.

¿Qué aprendiste sobre las oscilaciones y su aplicación en el arte generativo?

  • Las oscilaciones bien implementadas pueden hacer que un sistema parezca más orgánico y dinámico, en lugar de moverse de forma mecánica.
  • Pequeñas variaciones en la frecuencia y fase cambian completamente la percepción del movimiento, haciendo que la obra tenga más vida.
  • El equilibrio entre control y caos es clave: si todo se mueve igual, es aburrido; si todo es demasiado aleatorio, pierde coherencia.
  • Experimentar con muchas ideas abre posibilidades para generar patrones y texturas visuales interesantes sin necesidad de gráficos ni cálculos complejos.

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:

¿Qué estrategias de aprendizaje utilizaste?

  • Para esta unidad no tuvimos que referenciarnos con el libro así que me sirvió investigar en líne y luego preguntarle cositas a ChatGPT

¿Qué te funcionó mejor para comprender los conceptos?

  • Reconfirmar la información con mis propias palabras, lo que leía y entendía, lo reescribí con mis palabras.

¿Qué podrías mejorar?

  • Podría jugar más con la integración de interacción a los conceptos vistos.

¿Cómo te sentiste durante el proceso de aprendizaje?

  • Esta unidad me gustó mucho y sentí que el proceso de aprendizaje fue mucho más fluido que en otras unidades.

¿Qué te motivó?

  • La etapa de experimentación fue mi favorita porque realmente probé con muchas versiones hasta crear la simulación que tanto estaba pensando. Pude tener una libertad creativa increíble y como ya había explorado ejemplos de Nature of code que había modificado me sentí más confiada a la hora de aplicar conceptos.

¿Qué te frustró?

  • No tiene nada que ver con la unidad, simplemente estaba un poco atrasada en cuanto al cronograma pero disfruté tanto hacer esta unidad que no le di mucha importancia.