Week 4 – Assignment

My goal was to use p5.js to build a dynamic visualisation of Simple Harmonic Motion (SHM), inspired by Memo Akten. Vibrant circles oscillate vertically in the sketch, adding to its visual attractiveness with a trace effect. The intention was to play with colour and motion while capturing the essence of oscillation’s beauty.

//Assignment 4 - SP
let numCircles = 15;
let circles = [];
let amplitude = 50; // maximum displacement
let frequency = 0.08; // speed of the motion

function setup() {
  createCanvas(400, 400);
  background(0); //
  for (let i = 0; i < numCircles; i++) {
    circles.push({
      x: width / 2 + (i - numCircles / 2) * 30,
      angle: random(TWO_PI),
      color: color(random(255), random(255), random(255)),
    });
  }
}

function draw() {
  fill(0, 20); // semi-transparent black
  rect(0, 0, width, height);

  for (let circle of circles) {
    // calculate y position using sine function
    let y = height / 2 + amplitude * sin(circle.angle);

    fill(circle.color);
    noStroke();
    ellipse(circle.x, y, 30, 30);

    // increment the angle to create motion
    circle.angle += frequency;
  }
}

Highlighted Code:

One part I’m particularly proud of is the fading background, which enhances the trace effect. The use of fill(0, 50) allows for gradual fading and creates a more immersive experience as the circles move.

Reflection:

This project taught me a lot about using p5.js for visualizations. I encountered challenges with managing the transparency and ensuring the circles maintained their vibrancy against the fading background.

For future iterations, I’d like to:

  • Experiment with varying the amplitude and frequency based on user input.
  • Add interactivity, such as changing colors or shapes when the mouse hovers over the circles.
  • Explore other wave patterns beyond simple harmonic motion.

Week 3 – Assignment

Visualise slow, sine-wave courses being travelled by unseen attractors, creating faint forces comparable to whirlpools that attract and release visible particles (movers) floating around the canvas. These particles leave behind soft glowing trails, and the closer they are to the attractors, the more varied their colours are. The entire landscape has the appearance of a serene, lively garden with petals or lights floating in a mild wind.

Breakdown of the Sketch

Invisible Attractors:  These generate soft swirling forces as they follow sine-wave pathways.

The movers (or particles): Are softly shimmering as they leave traces behind and swirl around the attractors. The invisible attractors affect their movement, and the closer they are to the attractors, the more their colour changes.

Visual Appeal: The doodle has a soothing and attractive appearance thanks to its soft gradients, shimmering particles, vibrant colours, and delicate trails.

let particles = [];
let attractors = [];
let numAttractors = 3;
let numParticles = 200;

function setup() {
  createCanvas(800, 800);
  noStroke();
  
  // Create particles
  for (let i = 0; i < numParticles; i++) {
    particles.push(new Particle(random(width), random(height)));
  }
  
  // Create attractors (moving along sine waves)
  for (let i = 0; i < numAttractors; i++) {
    attractors.push(new Attractor(i * TWO_PI / numAttractors));
  }
}

function draw() {
  background(10, 10, 30, 25); // Fading background for trails
  
  // Update and display attractors
  for (let attractor of attractors) {
    attractor.move();
    attractor.display();
  }
  
  // Update and display particles
  for (let particle of particles) {
    particle.applyAttractors(attractors);
    particle.update();
    particle.display();
  }
}

// Particle class representing each mover
class Particle {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.vel = createVector(0, 0);
    this.acc = createVector(0, 0);
    this.maxSpeed = 2;
  }
  
  applyAttractors(attractors) {
    for (let attractor of attractors) {
      let force = attractor.getAttractionForce(this.pos);
      if (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);
  }
  
  display() {
    let glow = map(this.vel.mag(), 0, this.maxSpeed, 150, 255);
    fill(100, 150, 255, glow);
    ellipse(this.pos.x, this.pos.y, 8, 8);
  }
}

// Attractor class
class Attractor {
  constructor(offset) {
    this.offset = offset;  // Offset for sine wave motion
    this.pos = createVector(0, 0);
    this.speed = 0.005;
  }
  
  move() {
    let time = frameCount * this.speed;
    this.pos.x = width / 2 + sin(time + this.offset) * 300;
    this.pos.y = height / 2 + cos(time + this.offset) * 300;
  }
  
  getAttractionForce(particlePos) {
    let force = p5.Vector.sub(this.pos, particlePos);
    let distance = force.mag();
    
    if (distance < 150) { // Attraction radius
      let strength = map(distance, 0, 150, 0.1, 0);
      force.setMag(strength);
      return force;
    }
    return null; // No attraction if too far away
  }
  
  display() {
    noStroke();
    fill(255, 50, 50, 50);
    ellipse(this.pos.x, this.pos.y, 20, 20); // Faint circle for visualization (optional)
  }
}

Highlights
Smooth Attractor Motion + Dynamic Glow: The sketch has a softer, more natural flow since the moving attractors follow a smooth sine-wave pattern. A lovely, gentle visual impression is produced by the glowing effect on the particles, where their brightness varies according to their velocity.

The particles’ colour changes create a mystical atmosphere as they float around, while the invisible attractors generate the whirling forces that draw the particles in and out of their orbits.

fill(100, 150, 255, glow);
ellipse(this.pos.x, this.pos.y, 8, 8);

Thoughts and Suggestions for Future Improvements:

This sketch produces a lovely, lively image that is evocative of drifting petals in a peaceful garden or fireflies. A serene image with flowing patterns is produced by the interaction of the gently lighting particles and the smooth attractor motion.

Future Improvements:

To produce a wider variety of swirling patterns, introduce more intricate movement for the attractor, such as spiral or circular routes.
To provide even more visual depth, adjust the particle’s size and glow intensity according to its distance from the attractor and speed.
For an ethereal, more immersive effect, add minor backdrop gradient transitions (from dark blue to purple, for example).

Difficulties: The task at hand involved harmonising the visual components by achieving a smooth and glowing particle motion without causing visual disarray.

End Result:

 

Week 2 – Assignment

Concept:

For this week’s assigment, I have decided to get inspired by my favorite season: WINTER (kind of ironic giving the fact that we live in Abu Dhabi). I found myself reminiscing about the snowflakes that I get to see outside my window every time I go back home.

I found this YouTube video for inspiration:

After working for a bit on it, this was the final result:

This is the code:

//SNOWFLAKES ASSIGNMENT 2
//by SP

let snowflakes = [];
let panePadding = 20;

function setup() {
  createCanvas(400, 400);
  noStroke();
}

function draw() {
  setGradient(0, 0, width, height, color(20, 29, 68), color(100, 150, 255));

  // new snowflakes
  let t = frameCount / 60;
  if (random() < 0.05) {
    snowflakes.push(new Snowflake());
  }

  for (let flake of snowflakes) {
    flake.update(t);
    flake.display();
  }

  // remove snowflakes that are off the screen
  snowflakes = snowflakes.filter((flake) => !flake.offScreen());

  drawWindowFrame();
}

class Snowflake {
  constructor() {
    this.posX = random(width);
    this.posY = random(-50, 0); // start slightly off-screen
    this.size = random(5, 12); // size of the snowflake
    this.speed = random(1, 3); // falling speed
    this.drift = random(-0.5, 0.5); // slight drift left or right
    this.angle = random(TWO_PI); // random rotation angle
    this.rotationSpeed = random(-0.01, 0.01); // slight rotation
  }

  update(time) {
    this.posY += this.speed;
    this.posX += this.drift;
    this.angle += this.rotationSpeed;
  }

  display() {
    push();
    translate(this.posX, this.posY);
    rotate(this.angle);
    drawSnowflake(this.size);
    pop();
  }

  offScreen() {
    return this.posY > height;
  }
}

function drawSnowflake(size) {
  stroke(255);
  strokeWeight(2);
  noFill();

  // star-like shapes with lines to represent a snowflakes
  beginShape();
  for (let i = 0; i < 6; i++) {
    let x = cos((TWO_PI * i) / 6) * size;
    let y = sin((TWO_PI * i) / 6) * size;
    line(0, 0, x, y);

    // small branches for each main line
    for (let j = 0.4; j < 1; j += 0.4) {
      let branchX = cos((TWO_PI * i) / 6) * size * j;
      let branchY = sin((TWO_PI * i) / 6) * size * j;
      line(
        branchX,
        branchY,
        branchX + cos((TWO_PI * (i + 0.5)) / 6) * size * 0.2,
        branchY + sin((TWO_PI * (i + 0.5)) / 6) * size * 0.2
      );
      line(
        branchX,
        branchY,
        branchX + cos((TWO_PI * (i - 0.5)) / 6) * size * 0.2,
        branchY + sin((TWO_PI * (i - 0.5)) / 6) * size * 0.2
      );
    }
  }
  endShape(CLOSE);
}

// a vertical gradient
function setGradient(x, y, w, h, c1, c2) {
  for (let i = y; i <= y + h; i++) {
    let inter = map(i, y, y + h, 0, 1);
    let c = lerpColor(c1, c2, inter);
    stroke(c);
    line(x, i, x + w, i);
  }
}

// draw the window frame
function drawWindowFrame() {
  fill(80, 60, 40);
  noStroke();

  let paneWidth = (width - panePadding * 3) / 2;
  let paneHeight = (height - panePadding * 3) / 2;

  rect(0, height / 2 - panePadding / 2, width, panePadding);
  rect(width / 2 - panePadding / 2, 0, panePadding, height);

  rect(0, 0, panePadding, height); // left
  rect(width - panePadding, 0, panePadding, height); // right
  rect(0, 0, width, panePadding); // top
  rect(0, height - panePadding, width, panePadding); // bottom
}

The code that I am most proud of is the one for the snowflakes. In the beginning, I just created small circles but then I decided to make them look more realistic. It took me a long time but it was worth it because it all worked out in the end.

Reflection and ideas for future work or improvements:

Overall, I think I did a good job. Of course it is not super realistic but we are slowly getting there. For the future, I would like to recreate the entire scene and add a table next to the window.

Week 1 – Reading Response

I think that Flake’s take on how simple things can generate complex outcomes is an interesting point of view which applies to evolution, learning, and adaptation. These small adjustments can eventually adapt and can result in substantial evolutionary changes in the natural world.

I think this shows that adaptation isn’t just something living creatures do. It’s a basic part of how any system works, whether that’s in nature, a society, or even in computer programs. For example, it is the same way people adjust to new technologies and learn how to use them even if they haven’t interacted with them before.

Here’s how I see it: there is a reciprocal interaction between an adaptive system and its surroundings. The system’s modifications are influenced by the environment, and those changes may have an impact on the environment as well. This reciprocal relationship is what enables everything to change and evolve throughout time, including technology, society, and the natural world.

Week 1 Assignment – Self-Avoiding Walk

For this week’s assignment, I chose to make a random self-avoiding walker. First, I started by looking at the P5JS example of it and took inspiration from it.

But it wasn’t enough. So, I have started looking for ways to make the walk not get stuck. For that, I have modified  the approach to continuously attempt to find a path by using a backtracking method. It took me a few tries, but I have eventually got there.

After everything, I have added the function to change colors everytime it changes directions. This was not as hard because we have tried it in class.

 

 

let spacing = 20;
let cols, rows;
let x, y;
let grid;
let allOptions = [
  { dx: 0, dy: 1 },
  { dx: 1, dy: 0 },
  { dx: 0, dy: -1 },
  { dx: -1, dy: 0 },
];
let stack = [];
let currentColor;

function setup() {
  createCanvas(400, 400);
  cols = width / spacing;
  rows = height / spacing;
  x = floor(cols / 2);
  y = floor(rows / 2);
  background(51);
  grid = Array.from({ length: cols }, () => Array(rows).fill(false));
  grid[x][y] = true;
  stack.push([x, y]);
  currentColor = pastelColor();
}

function draw() {
  stroke(currentColor);
  strokeWeight(spacing * 0.5);
  point(x * spacing, y * spacing);

  let options = allOptions.filter((option) =>
    isValid(x + option.dx, y + option.dy)
  );

  if (options.length > 0) {
    let step = random(options);
    strokeWeight(1);
    stroke(currentColor);
    line(
      x * spacing,
      y * spacing,
      (x + step.dx) * spacing,
      (y + step.dy) * spacing
    );

    x += step.dx;
    y += step.dy;
    grid[x][y] = true;
    stack.push([x, y]);
    currentColor = pastelColor();
  } else {
    if (stack.length > 1) {
      stack.pop();
      [x, y] = stack[stack.length - 1];
    } else {
      noLoop();
    }
  }
}

function isValid(i, j) {
  return grid[i]?.[j] === false && i >= 0 && i < cols && j >= 0 && j < rows;
}

function pastelColor() {
  return color(random(200, 255), random(200, 255), random(200, 255));
}

For the future, I think that I would like to make it more complex. Maybe I can make two of them and make it collide and when they do something happens. Until then, I am pretty pleased with the outcome