Midterm Progress – Khalifa Alshamsi

Concept and Design of Generative Art

The concept for “Evolving Horizons” revolves around the interplay of natural elements in a living landscape. The goal is to create a serene visual experience that captures the passage of time, from dawn to dusk, through smooth transitions of light and shadow. I was inspired by how light can transform landscapes, especially during sunrise and sunset when shadows grow longer, and the colors in the sky shift dramatically.

Key elements in the landscape:

  • Sun: Moves across the sky and influences light and shadows.
  • Hills: Roll across the landscape, with their shadows changing based on the sun’s position.
  • Trees: Stand tall on the hills, casting shadows and swaying gently.
  • Sky: Changes color throughout the day, creating a peaceful and immersive environment.

Sketch

Code

let sun;
let hills = [];
let trees = [];
let numHills = 5;
let numTrees = 30;

function setup() {
  createCanvas(800, 600);
  
  // Create the sun starting at the morning position
  sun = new Sun(100, 100);
  
  // Generate random hills
  for (let i = 0; i < numHills; i++) {
    hills.push(new Hill(random(0, width), random(200, 400), random(100, 200)));
  }
  
  // Generate random trees on the hills
  for (let i = 0; i < numTrees; i++) {
    let hillIndex = floor(random(hills.length));
    trees.push(new Tree(hills[hillIndex].x + random(-50, 50), hills[hillIndex].y - hills[hillIndex].height));
  }
}

function draw() {
  background(135, 206, 235); // Sky blue
  
  // Change the sky color based on sun position
  changeSkyColor(sun.y);
  
  // Move and draw the sun
  sun.moveSun();
  sun.display();
  
  // Draw hills with shadows
  for (let hill of hills) {
    hill.display();
    hill.castShadow(sun);
  }
  
  // Draw trees with shadows
  for (let tree of trees) {
    tree.display();
    tree.castShadow(sun);
  }
}

// Sun class
class Sun {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.diameter = 80;
  }
  
  moveSun() {
    // Sun moves in a circular path from morning to night
    this.x = width / 2 + cos(frameCount * 0.01) * 400;
    this.y = height / 2 + sin(frameCount * 0.01) * 200;
  }
  
  display() {
    fill(255, 204, 0); // Yellow sun
    noStroke();
    ellipse(this.x, this.y, this.diameter);
  }
}

// Hill class
class Hill {
  constructor(x, y, height) {
    this.x = x;
    this.y = y;
    this.height = height;
  }
  
  display() {
    fill(34, 139, 34); // Green hills
    noStroke();
    beginShape();
    vertex(this.x - 100, this.y);
    bezierVertex(this.x, this.y - this.height, this.x + 100, this.y - this.height / 2, this.x + 200, this.y);
    vertex(this.x + 200, this.y);
    endShape(CLOSE);
  }
  
  castShadow(sun) {
    let shadowLength = map(sun.y, 0, height, 100, 400);
    fill(0, 0, 0, 50); // Transparent shadow
    noStroke();
    beginShape();
    vertex(this.x - 100, this.y);
    vertex(this.x - 100 + shadowLength, this.y + shadowLength);
    vertex(this.x + 200 + shadowLength, this.y + shadowLength);
    vertex(this.x + 200, this.y);
    endShape(CLOSE);
  }
}

// Tree class
class Tree {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.trunkHeight = random(40, 80);
    this.trunkWidth = 10;
    this.leafSize = random(30, 50);
  }
  
  display() {
    // Draw tree trunk
    fill(139, 69, 19); // Brown trunk
    noStroke();
    rect(this.x, this.y, this.trunkWidth, -this.trunkHeight);
    
    // Draw leaves
    fill(34, 139, 34); // Green leaves
    ellipse(this.x + this.trunkWidth / 2, this.y - this.trunkHeight, this.leafSize);
  }
  
  castShadow(sun) {
    let shadowLength = map(sun.y, 0, height, 50, 200);
    fill(0, 0, 0, 50); // Transparent shadow
    noStroke();
    beginShape();
    vertex(this.x, this.y);
    vertex(this.x + shadowLength, this.y + shadowLength);
    vertex(this.x + shadowLength + this.trunkWidth, this.y + shadowLength);
    vertex(this.x + this.trunkWidth, this.y);
    endShape(CLOSE);
  }
}

// Change sky color based on sun's height
function changeSkyColor(sunY) {
  let morning = color(135, 206, 235); // Morning sky
  let dusk = color(255, 99, 71);      // Dusk sky
  let t = map(sunY, 0, height, 0, 1);
  let skyColor = lerpColor(morning, dusk, t);
  background(skyColor);
}

Designing the Code: Functions, Classes, and Interactivity

To break the project into manageable parts, I designed the following core components:

Classes:

  • Sun: The sun manages its position and movement across the sky. The sun also affects the length of shadows.
  • Hill: Represents rolling hills with a bezier curve that gives each hill a natural shape. The hill class also handles the casting of shadows.
  • Tree: Trees are placed on the hills, swaying slightly to simulate a breeze, and casting their own shadows based on the sun’s position.

Functions:

  • movesun(): Moves the sun in a circular motion across the sky. The position of the sun changes dynamically with each frame, mimicking the progression of time.
  • castShadow(): Calculates the length of shadows based on the sun’s position in the sky. This adds realism by changing the shape and size of shadows as the sun moves.
  • changeSkyColor(): Shifts the sky color gradually throughout the day, using the lerpColor() function to transition smoothly from morning to dusk.

Interactivity:

  • Time of Day: I’m considering adding controls where users can manually change the time of day by dragging the sun or pressing a key to speed up the passage of time. This would allow users to explore the landscape at different times of the day and see how light and shadows behave.

The combination of these functions creates a seamless, flowing environment where every aspect of the landscape interacts with the light in real-time.

Identifying Complexities and Minimizing Risks

The most complex part of this project was implementing the shadow-casting algorithm. Calculating shadows based on the sun’s position required careful mapping of angles and distances. I needed to ensure that the shadows grew longer as the sun lowered in the sky and became shorter when the sun was overhead.

Steps to Reduce Risk:

To tackle this complexity, I isolated the problem by writing a simple test sketch that calculated shadow lengths based on different sun positions. I used the map() function in p5.js to dynamically adjust shadow length relative to the sun’s y position. By experimenting with this small feature, I was able to fine-tune the shadow casting before incorporating it into the full project.

Additionally, the sky color transitions were another challenge. I experimented with lerpColor() to create smooth transitions from morning to dusk, ensuring the visual experience was gradual and not abrupt.

Next Steps:

Now that the core features are working, I plan to:

  • Fix the positioning of the trees that are randomly generated.
  • Implement wind simulation for the trees, making them sway in response to virtual wind.
  • Enhance user interactivity by allowing users to manipulate the sun’s position, controlling the time of day directly.
  • To make the scene feel more immersive, add finer details to the landscape, such as moving clouds and birds.

Leave a Reply

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