Week 1 – Fireflies by Dachi

Sketch

Code:

let fireflies = [];
const numFireflies = 50;
const avoidanceRadius = 150;  // Radius around cursor where fireflies start avoiding
let backgroundImage;
let canteenImage;
let mainTheme;

function preload() {
  // Load assets before setup
  backgroundImage = loadImage('background.png');
  canteenImage = loadImage('canteen2.png');
  soundFormats('mp3', 'ogg');
  mainTheme = loadSound('grave_of_fireflies_theme.mp3');
}

function setup() {
  createCanvas(800, 600);

  // Initialize fireflies
  for (let i = 0; i < numFireflies; i++) {
    fireflies.push(new Firefly());
  }

  noCursor();  // Hide default cursor

  // Start background music
  mainTheme.setVolume(0.5);
  mainTheme.loop();
}

function draw() {
  image(backgroundImage, 0, 0, width, height);

  drawShadow();

  // Update and display fireflies
  for (let firefly of fireflies) {
    firefly.move();
    firefly.display();
  }

  drawCanteen();
}

function drawShadow() {
  let canteenSize = 100;
  let shadowSize = canteenSize * 1.5;

  // Calculate shadow offset based on simulated light source
  let lightX = width / 2;
  let lightY = height / 2;
  let shadowOffsetX = map(mouseX - lightX, -width/2, width/2, -20, 20);
  let shadowOffsetY = map(mouseY - lightY, -height/2, height/2, -20, 20);

  push();
  translate(mouseX + shadowOffsetX, mouseY + shadowOffsetY);

  // Create and draw radial gradient for shadow
  let gradient = drawingContext.createRadialGradient(0, 0, 0, 0, 0, shadowSize/2);
  gradient.addColorStop(0, 'rgba(0, 0, 0, 0.3)');
  gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
  drawingContext.fillStyle = gradient;
  ellipse(0, 0, shadowSize, shadowSize * 0.6);

  pop();
}

function drawCanteen() {
  let canteenSize = 100;
  let tiltAmount = 10;

  // Tilt canteen based on mouse position
  let tiltX = map(mouseX, 0, width, -tiltAmount, tiltAmount);
  let tiltY = map(mouseY, 0, height, -tiltAmount, tiltAmount);

  push();
  translate(mouseX, mouseY);
  rotate(radians(tiltX));
  rotate(radians(tiltY));
  image(canteenImage, -canteenSize/2, -canteenSize/2, canteenSize, canteenSize);
  pop();
}

class Firefly {
  constructor() {
    this.reset();
  }

  reset() {
    // Initialize firefly properties
    this.x = random(width);
    this.y = random(height);
    this.brightness = random(150, 255);
    this.blinkRate = random(0.01, 0.03);
    this.speed = random(0.2, 0.5);
    this.size = random(10, 15);
    this.angle = random(TWO_PI);
    this.turnSpeed = random(0.05, 0.1);
  }

  move() {
    let d = dist(this.x, this.y, mouseX, mouseY);

    if (d < avoidanceRadius) {
      // Avoid cursor
      let avoidanceSpeed = map(d, 0, avoidanceRadius, this.speed * 5, this.speed);
      let angle = atan2(this.y - mouseY, this.x - mouseX);
      this.x += cos(angle) * avoidanceSpeed;
      this.y += sin(angle) * avoidanceSpeed;
    } else {
      // Normal movement using Perlin noise
      let noiseScale = 0.01;
      let noiseVal = noise(this.x * noiseScale, this.y * noiseScale, frameCount * noiseScale);
      this.angle += map(noiseVal, 0, 1, -this.turnSpeed, this.turnSpeed);
      this.x += cos(this.angle) * this.speed;
      this.y += sin(this.angle) * this.speed;
    }

    // Wrap around edges of canvas
    if (this.x < -this.size) this.x = width + this.size;
    if (this.x > width + this.size) this.x = -this.size;
    if (this.y < -this.size) this.y = height + this.size;
    if (this.y > height + this.size) this.y = -this.size;

    // Update brightness for blinking effect
    this.brightness += sin(frameCount * this.blinkRate) * 5;
    this.brightness = constrain(this.brightness, 150, 255);
  }

  display() {
    noStroke();
    let glowSize = this.size * 2;
    let alpha = map(this.brightness, 150, 255, 50, 150);

    // Draw firefly with layered glow effect
    fill(255, 255, 150, alpha * 0.3);
    ellipse(this.x, this.y, glowSize, glowSize);
    fill(255, 255, 150, alpha * 0.7);
    ellipse(this.x, this.y, this.size * 1.5, this.size * 1.5);
    fill(255, 255, 150, alpha);
    ellipse(this.x, this.y, this.size, this.size);
  }
}

Concept

This project is an interactive simulation inspired by the Studio Ghibli film “Grave of the Fireflies”. It aims to capture mesmerizing fireflies which is connected to the main movie theme. The simple movement creates an atmospheric environment where glowing fireflies interact with the user’s cursor, represented by the famous candy tin from the film. This concept seeks to evoke the bittersweet feelings of the movie, allowing users to subtly engage with its symbolism.

How It Works

Each firefly is represented by a glowing orb that moves automatically across the screen. The movement of these digital insects is controlled by Perlin noise. This technique results in firefly movements that appear organic and natural, where the erratic flight patterns of real fireflies are mimicked.

The visual representation of the fireflies is achieved through different layers. Each firefly consists of multiple semi-transparent circles of varying sizes which create a soft bloomy look. The brightness of each firefly pulsates over time, simulating the flashing characteristic of these insects.

As the user moves this cursor across the screen, nearby fireflies react by moving away, creating a dynamic and responsive environment. This small interaction element is quite smooth and I fine-tuned it to not appear too rapid.

Background music is the theme of the movie which enhances the experience and is quite emotional.
I used Adobe Photoshop to modify the background image to create a suitable environment for the fireflies. Additionally, the candy tin image used for the cursor was edited to include a subtle red glow, helping it integrate seamlessly with the illumination of the fireflies.

Potential Improvements

While the current implementation achieves its basic goals, there are many ways to improve. The most significant one would be, adding more interactive elements that could enhance user engagement. This might include implementing sound effects that respond to firefly movements or user interactions or introducing environmental factors like wind or obstacles that influence firefly behavior.
Lastly, providing user controls to adjust parameters such as firefly count, speed, or glow intensity could allow for a more personalized experience, enabling users to experiment with different atmospheres.

Difficulties and Challenges

One of the main challenges in this project was creating a natural-looking movement pattern for the fireflies that didn’t appear too random or too uniform. Initially, the fireflies tended to drift in one direction over time, which required careful tuning of the Perlin noise parameters to correct. Another significant challenge was implementing the avoidance behavior in a way that felt organic; early iterations had the fireflies reacting too abruptly to the cursor, which broke the illusion of natural movement.

References

1. Studio Ghibli. (1988). Grave of the Fireflies [Motion Picture]. Japan.
2. Coding Train from Youtube

(This project satisfies the assignment requirements by experimenting with motion through the implementation of Perlin noise-based movement and avoidance behavior of the fireflies. Additionally, it applies rules of motion to another medium of expression by translating the movement algorithms into visual representations of color and brightness in the fireflies’ glow effect.)

Leave a Reply

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