Midterm Project – Spring Rain

Embedded Sketch and Link


https://editor.p5js.org/ss14740/sketches/HiD0F6V9R

Exported Images 

Concept and Artistic Vision

At first, I was inspired by the concept of identity and was attempting to create fingerprint patterns. However, I couldn’t get the shape to look the way I wanted to. Here is my sketch.

Although I am proud of the work I put into the fingerprints, but they did not have the organic shape I wanted, and I couldn’t figure out how to generative movement while still making the sketch feel natural and organic. In brainstorming other ideas, I revisited my other sketches for this class. Many of my sketches were of natural elements. I think it’s interesting to use computation to create more fluid, nature-related objects. So, I re-imagined my midterm to show rain falling on a lake, and the creation of the ripples to be the main artistic element of my sketch. Here are some of the images I was inspired by.

Luckily, I was able to draw from my previous draft in creating the new sketch, as both shared the same logic in creating the circular shapes and in modifying these shapes with Perlin noise.

Coding Translation and Logic 

I planned the sketch by first modifying my fingerprint sketch to create the ripples. I created a class for the ripples and a class for the raindrops, as these were my main two elements

class Raindrop {
  constructor(x, y) {
    this.pos = createVector(x, y); // Initial position
    this.size = 15; // Size of the raindrop
    this.speed = random(initialRaindropSpeedRange[0], initialRaindropSpeedRange[1]); // Random speed
    this.acceleration = 1.2; // Acceleration (gravity)
    this.targetY = random(height / 6, height); // Random point at which raindrop will stop
  }

  update() {
    this.speed += this.acceleration; // Increase speed due to acceleration (gravity)
    this.pos.y += this.speed; // Move the raindrop down by updating its y-coordinate

    // If mouse is pressed, make the raindrop fall faster
    if (mouseIsPressed) {
      this.speed = random(10, 15);
    } else {
      this.speed = constrain(this.speed, initialRaindropSpeedRange[0], initialRaindropSpeedRange[1]);
    }
  }

  display() {
    fill(201, 243, 255, 100); // Light blue, semi-transparent fill
    stroke(255); // White stroke
    strokeWeight(0.2); // Thin stroke weight

    // Draw a bezier curve to represent the raindrop's shape
    beginShape();
    vertex(this.pos.x, this.pos.y);
    bezierVertex(this.pos.x - this.size / 2, this.pos.y + this.size, this.pos.x + this.size / 2, this.pos.y + this.size, this.pos.x, this.pos.y);
    endShape(CLOSE);
  }

  hitsBottom() {
    return this.pos.y >= this.targetY; // Checks if the raindrop has reached the target Y position
  }
}

In the Raindrop class, I initialized all the elements to the raindrop. I made the speed random and added acceleration as each raindrop falls so it creates a more natural effect. When the mouse is pressed, the raindrops fall faster, creating a rainstorm-like effect. The shape of the raindrops are created with bezierVertex so that they look fluid. If the raindrop returns true that it hit the ‘ground’, then a ripple is created.

class Ripple {
  constructor(x, y) {
    this.pos = createVector(x, y);
    this.size = 0;
    this.alpha = 230; // Initial transparency
    this.growthRate = 2; // How fast the ripple grows
    this.fadeRate = 4; // How fast the ripple fades
    this.layers = 6; // Number of ripple layers
    this.wideningFactor = 1.1; // Factor to widen the ripple's oval shape
  }

  update() {
    // Increase ripple size
    this.size += this.growthRate;
    // Decrease transparency so ripple fades out
    this.alpha -= this.fadeRate;
  }

  display() {
    noFill();
    stroke(201, 243, 255, this.alpha);
    strokeWeight(2);

    // Draw multiple layers of ripples
    for (let i = 0; i < this.layers; i++) {
      let layerSize = this.size - i * 10;
      let widening = pow(this.wideningFactor, i); // Widen each subsequent ripple
      if (layerSize > 0) {
        this.drawOvalRipple(layerSize * 2 * widening, layerSize * widening); // Draw each ripple layer
      }
    }
  }

  // Draw ripples with distortion using Perlin noise
  drawOvalRipple(ovalWidth, ovalHeight) {
    beginShape();
    for (let angle = 0; angle < TWO_PI; angle += 0.1) {
      let xoff = cos(angle) * 5; // X offset for Perlin noise
      let yoff = sin(angle) * 5; // Y offset for Perlin noise

      // Apply Perlin noise to distort the ripple shape
      let distortion = map(noise(xoff + this.size * noiseScale, yoff + this.size * noiseScale), 0, 1, 0.95, 1.05);
      
      // Calculate the X and Y coordinates for the ripple based on the oval's size and the distortion
      let x = this.pos.x + (ovalWidth / 2 * cos(angle)) * distortion;
      let y = this.pos.y + (ovalHeight / 2 * sin(angle)) * distortion;

      vertex(x, y); // Add vertex to the shape
    }
    endShape(CLOSE);
  }

  // Check if the ripple is fully faded
  isDone() {
    return this.alpha <= 0;
  }
}

In the Ripple class, I initialized all the elements to the ripple. I updated the ripple’s size and level of opacity. I drew multiple layers of ripples, with each growing larger. Each ripple has Perlin noise applied to it to distort the shape and make it look more organic. If the ripple is fully faded, it is removed from the simulation. In the draw function, I used ‘splice’ to remove each ripple and raindrop from their respective arrays.

Challenges

I wanted to make the background more interesting and fluid. Instead of just having a one colour background, I want to make a gradient background of two colours. Although this is usually a simple process that I have worked with in the past, I found it hard to implement into my code as my code worked with objects that changed and moved. This meant I needed to find a way to the gradient to be drawn as the background without affecting the raindrop or ripple objects (both objects have transparency to them and thus are visually effected) To do this, I drew the gradient on PGraphics (gradientBG) rather than directly onto the canvas. This way, the gradient is stored and I don’t need to redraw it every frame. In the draw function the gradient is applied using “image(gradientBG, 0, 0)”.

Pen Plotting

I did not have to change my sketch for the pen plotting. The process was enjoyable and informative, as it was my first time using a pen plotter. One improvement that I think could have been made is drawing different layers, and using a light colour for some of the individual ripples, as in the code the ripples become lighter in colour as they fade out. Here is my pen plot and the video of the process.

Future Improvements

To improve this project, I would add rainfall sounds and music in the back to create a calming ambiance. I would create other objects to add to the ambiance, such as lily pads or lake animals. I would also create a boundary for each ripple, and modify the ripples when they collide with each other, such as increasing the Perlin noise. I was attempting to do this, but kept running into errors/having glitches.

Leave a Reply

Your email address will not be published. Required fields are marked *