Final Project – Wildfire

Concept:

In order to define fire suppression tactics and design fire risk management policies, it is important to simulate the propagation of wildfires. In my final project, I’ll be using cellular automata to model the spread of wind-driven fire across a grid-based terrain, demonstrating the dynamics of wildfires and exploring different scenarios and factors affecting their spread.

Users will be able to set the initial conditions (i.e. density of vegetation, vegetation type, ignition point), adjust parameters (i.e. wind speed, wind direction, temperature), monitor variables (i.e. frames per second, land proportion, water proportion, burned land), pause, and reset the simulation.

Final Sketch:

https://editor.p5js.org/bdr/full/2S_n0X2gV

Code:

https://editor.p5js.org/bdr/sketches/2S_n0X2gV

Initial sketches:

View post on imgur.com

View post on imgur.com

Papers to be used:

https://www.fs.usda.gov/rm/pubs_int/int_rp115.pdf

Code walkthrough:
Terrain generation:

The generateCells() function creates a terrain by dividing the map into cells to form a 2D grid (representing a cellular automata). Within each grid cell (defined by x and y coordinates), various noise functions are used to generate different aspects of the terrain.

generateCells() mainly defines the elevation of each cell in the grid through a combination of noise functions. Noise, Marks, Distortion, Bump, and Roughness are all variables employed with different scaling factors and combinations of input derived from the x and y coordinates. The noise functions were determined through trial and error.

roughness = noise(x / 600, y / 600) - 0.3;
bumpdistort = noise(x / 20, y / 20);
bumpnoise = noise(x / 50, y / 50, 2 * bumpdistort);
h = noise1 + sq(sq(noise2)) + roughness * bumpnoise - 0.8;

The color of each point is determined based on its height and other noise values, influencing factors such as land type (e.g., vegetation or ocean) and terrain features.

Using HSB as the color mode, the hue component represents the color’s tone (e.g., red, green, blue) while the brightness component corresponds to the perceived elevation. This makes it intuitive to represent different elevations using a gradient of colors (e.g., blue for lower elevations to white for higher ones), making the terrain visually more coherent and natural-looking.

if (h > 0) {
  clr = color(20 + 10 * (marks1 - 4) + 10 * (marks2 - 4) + 20 * distort1 + 50 * distort2 + bumpnoise * 15,
  min(100, max(50, 100 - 500 * roughness)), 75 + 65 * h);
  veg = getColor(clr)
  pointData = { x: x, y: y, color: clr, vegetation: veg, fire: 0, elev: elevationClr, temp:null};
  landCnt++;

} else {
  clr = color(160, 100, 185 + max(-45, h * 500) + 65 * h + 75 * (noise2 - 0.75));
  veg = getColor(clr)
  pointData = { x: x, y: y, color: clr, vegetation: veg, fire: 0, elev: elevationClr, temp:null};
  oceanCnt++;
}

The elevation/height is mapped as to the more the cell is in the middle of the ocean, the deeper it gets, and the more in the middle of the land, the higher it gets.

Initial step:

View post on imgur.com

Simplified sketch: As the sketch needed around 3 seconds to render, and cellular automata required a constant update of the sketch, simplifying the sketch was crucial. So, instead of calculating the noise for every pixel, I skip one by predicting its color.

View post on imgur.com

Vegetation:

Vegetation type propagation probability:
– No vegetation: -1
– Cultivated: -0.4
– Forests: 0.4
– Shrub: 0.4

Vegetation density propagation probability:
– No vegetation: -1
– Sparse: -0.3
– Normal: 0
– Dense: 0.3

Rules:

R1: A cell that can’t be burned stays the same
R2: A cell that is burning down at the present time will be completely burned in the next iteration.
R3: A burned cell can’t be burned again.
R4: If a cell is burned, and its neighbors contain vegetation fue, the fire can then propagate with a given probability (factors).

The fire propagation probabilities are:

View post on imgur.com

P0: ignition point (user interaction)
P_veg: vegetation type
P_den: vegetation density
Ps: topography
Pw: wind speed and direction

View post on imgur.com

with C1 and C2 are adjustable coefficients
V is the wind speed
O is the angle between the wind direction and the fire propagation (if aligned, Pw increases)

View post on imgur.com

with a: adjustable coefficient
o: the slope angle of the terrain

View post on imgur.com

with E: elevation of the cell
D: the size of the square cell if adjacent or sqrt(2*cellSize) if diagonal

Cellular Automata rules:

Implement rules governing the spread of fire: Fuel, topography, wind, and humidity.
R1: A cell that can’t be burned stays the same
R2: A cell that is burning down at the present time will be completely burned in the next iteration.
R3: A burned cell can’t be burned again.
R4: If a cell is burned, and its neighbors contain vegetation fue, the fire can then propagate with a given probability (factors).

function generateNextGeneration() {
    for (let i = 0; i < width; i+=2) {
        for (let j = 0; j < height; j+=2) {
            let neighbors = countNeighbors(i, j);
            let windAffectedProb = calculateWindEffect(i, j, windSpeed, windDirection);

            if (grid[i][j].fire==0 && neighbors==0){ // no fire, neighbors no fire
                nextGrid[i][j].fire=0;
            }
            if (grid[i][j].fire==0 && neighbors!=0 && grid[i][j].vegetation==0){ // water, neighbor fire, probability 0%
                nextGrid[i][j].fire=0;
            }
            probability = Math.floor(random(1,100));
            windInfluence = random(0, 100);
            if (grid[i][j].fire==0 && neighbors!=0 && grid[i][j].vegetation==1 && probability<10 && windInfluence < windAffectedProb && temperature>0){ // sparse, neighbor fire, probability 10%
                nextGrid[i][j].fire=1;
                nextGrid[i][j].color=color(14, 252, 113);
            }
            probability = Math.floor(random(1,100));
            windInfluence = random(0, 100);
            if (grid[i][j].fire==0 && neighbors!=0 && grid[i][j].vegetation==2 && probability<50 && windInfluence < windAffectedProb && temperature>0){ // no fire, neighbor fire, normal veg, probability 70%
                nextGrid[i][j].fire=1;
                nextGrid[i][j].color=color(14, 252, 113);
            }
            probability = Math.floor(random(1,100));
            windInfluence = random(0, 100);
            if (grid[i][j].fire==0 && neighbors!=0 && grid[i][j].vegetation==3 && probability<30 && windInfluence < windAffectedProb && temperature>0){ // no fire, neighbor fire, dense veg, probability 100%
                nextGrid[i][j].fire=1;
                nextGrid[i][j].color=color(14, 252, 113);
            }
            else if (grid[i][j].fire==1){ // burning
                nextGrid[i][j].fire==-1;
                nextGrid[i][j].color=color(0, 0, 57);
                burnedCnt++;
                burnedBlocks[`${i}_${j}`] = true;
            }
            else if (grid[i][j].fire==-1){ // burned
                nextGrid[i][j].fire==-1;
                nextGrid[i][j].color=color(0, 0, 57);
            }
        }
    }
    swapGenerations();
}
Other Maps:

Elevation Map: Users are able to visualize the elevation of the terrain by using the button at the right of the screen.

View post on imgur.com

 

Elevation type: Users are also able to visualize the tyoe of vegetation (forests, shrubs, cultivated…)

View post on imgur.com

Wind: Users are also able to see the wind direction and modify it using the slider.

View post on imgur.com

Challenges:

Improving the computation time was a rather challenging time. After optimizing it, it went from 3s to 0.35s.

IM Show Pictures:

View post on imgur.com

View post on imgur.com

 

Final Project update #2 – Wildfire

Updates:

For this week’s update, I have added temperature as a parameter. If the temperature is not ideal, the fire won’t propagate. SImilarly, if the temperate is too good, the wildfire will propagate rapidly.

As a next step, I will be adding all the secondary maps:
– Heat map
– Vegetation type
– Wind
– Elevation (height and slope of the land – mountain, hill…)

I will be adding more ways to monitor data, proportions of lands to the terrain, water, burned land etc…

I will be refining more colors to give it a natural look.

https://editor.p5js.org/bdr/full/ShjdWF8uN

Final Project Proposal – Forest Fire

Concept:

In order to define fire suppression tactics and design fire risk management policies, it is important to simulate the propagation of wildfires. In my final project, I’ll be using cellular automata to model the spread of wind-driven fire across a grid-based terrain, demonstrating the dynamics of wildfires and exploring different scenarios and factors affecting their spread.

Users will be able to set the initial conditions (i.e. density of vegetation, ignition point), adjust parameters (i.e. wind speed, humidity), pause, and reset the simulation.

Initial sketches:

View post on imgur.com

View post on imgur.com

Papers to be used:

https://www.fs.usda.gov/rm/pubs_int/int_rp115.pdf

Methodology:
  • Research the propagation of fire: The case study will be the 2012 severe wildfire that took place in Algarve, Portugal, where 25 thousand hectares burned. It is interesting because it had an explosive stage between 25 and 30h after the onset.
  • Define initial states of land: vegetation type and density.

Vegetation type propagation probability:
– No vegetation: -1
– Cultivated: -0.4
– Forests: 0.4
– Shrub: 0.4

Vegetation density propagation probability:
– No vegetation: -1
– Sparse: -0.3
– Normal: 0
– Dense: 0.3

  • Implement rules governing the spread of fire: Fuel, topography, wind, and humidity.
    R1: A cell that can’t be burned stays the same
    R2: A cell that is burning down at present time will be completely burned in the next iteration.
    R3: A burned cell can’t be burned again.
    R4: If a cell is burned, and its neighbors contain vegetation fue, the fire can then propagate with a given probability (factors).

The probability will be defined as follows:

P_burn = P0(1+P_veg)(1+P_den)PwPs

P0: ignition point (user interaction)
P_veg: vegetation type
P_den: vegetation density
Ps: topography
Pw: wind speed and direction

The probability of the wind is:

Pw = exp[V(C1 + C2 (cos(o)-1))]

with C1 and C2 are adjustable coefficients
V is the wind speed
O is the angle between the wind direction and the fire propagation (if aligned, Pw increases)

The probability of the terrain is:

Ps = exp(a.o)

with a: adjustable coefficient
o: the slope angle of the terrain

Os = atan[(E1 – E2)/D]

with E: elevation of the cell
D: the size of the square cell if adjacent or sqrt(2*cellSize) if diagonal

Challenges:

Make it as realistic as possible.

Coding Assignment #11 – CA

Concept:

In this week’s assignment, I tried to recreate water droplet ripples using Cellular Automata. To do that, I created a simple cellular automaton with rules that propagate a disturbance/wave across the grid. Ripples start at the corners then in random positions in the grid and propagate outward based on the defined rules. As for user interaction, I gave the user the ability to modify the cell size up/down using the arrow keys to seem as if they are zooming in/out. I chose to use a grayscale palette for an aesthetic and more abstract look.

View post on imgur.com

Sketch:

Code Walkthrough:

Code link:https://editor.p5js.org/bdr/sketches/XuyWHkF4S

Cell class:

Creates a cell within a cellular automaton grid. It has the following properties:

x: X-position of the cell.
y: Y-position of the cell.
prevState: Previous state of the cell.
newState: New state of the cell.
currState: Current state of the cell.
neighbors: Array containing neighboring cells.

The methods are:

Change(): Adds a new updated neighbor to the cell.
Display(): Displays the cell in the grid.
ChangeState(): Updates the state of the cell based on its neighbors.

changeState() {
        // Get the total
        let t = 0;
        for (let i = 0; i < this.neighbours.length; i++) {
            t += this.neighbours[i].currState;
        }
        // Get the average
        let avg = int(t / 8);
        if (avg === 255) {
            this.newState = 0;
        } 
        else if (avg === 0) {
            this.newState = 255;
        } 
        else {
            this.newState = this.currState + avg;
            if (this.prevState > 0) {
                this.newState -= this.prevState;
            }
            if (this.newState > 255) {
                this.newState = 255;
            } else if (this.newState < 0) {
                this.newState = 0;
            }
        }
        this.prevState = this.currState;
    }
Start() Function:

Initializes a grid of cells and sets up the neighbors for each cell within a cellular automaton simulation. In each iteration, it sets the neighbors positions depending on the pixels (up, down, left, right)

function start() {
    // Create a grid of cells
    for (let x = 0; x < 64; x++) {
        cells[x] = [];
        for (let y = 0; y < 64; y++) {
            let newCell = new Cell(x, y);
            cells[x].push(newCell);
        }
    }
    // Setup the neighbors
    for (let x = 0; x < 64; x++) {
        for (let y = 0; y < 64; y++) {
            // Cell above
            let up = y - 1;
            if (up < 0) {
                up = 64 - 1;
            }
            // Cell in left
            let left = x - 1;
            if (left < 0) {
                left = 64 - 1;
            }
            // Cell below
            let down = y + 1;
            if (down === 64) {
                down = 0;
            }
            // Cell in right
            let right = x + 1;
            if (right === 64) {
                right = 0;
            }
            // Update the cells
            cells[x][y].change(cells[left][up]);
            cells[x][y].change(cells[left][y]);
            cells[x][y].change(cells[left][down]);
            cells[x][y].change(cells[x][down]);
            cells[x][y].change(cells[right][down]);
            cells[x][y].change(cells[right][y]);
            cells[x][y].change(cells[right][up]);
            cells[x][y].change(cells[x][up]);
        }
    }
}
User Interaction:

Using the Up and Down keys, the user is able to change the cells size and give the impression that they are zooming in and zooming out. Further error control was set to avoid decreasing the cell to a size where it can fit the canvas.

Coding assignment #10 – Pendulum

Concept:

Inspired by Einstein’s cradle and the vintage clock’s pendulum, I’ve tried to create a collision system using Matter.js. It involves randomly generated pendula that collide with each other as they fall down. The user has the ability to move the pendula up and down, left and right, to create more collisions using mouse drag.

View post on imgur.com

View post on imgur.com

 

Sketch:

Code Walkthrough:

Repo: https://editor.p5js.org/bdr/sketches/vq6UC-qLu

Mouse constraints: Create a mouse constraint for the user to interact with each pendulum up and down, left and right.

// add mouse interaction
    let mouse = Mouse.create(canvas.elt)
    mouseConstraint = MouseConstraint.create(engine,{
      mouse: mouse
    })
    // add the mouse constraint to the world
    World.add(engine.world,mouseConstraint)
    // add the engine
    Matter.Runner.run(engine)

 

newPendulum() function: Generate a new pendulum body with a random position, create a constraint to suspend the pendulum from a fixed point at the top, and add the pendulum body and its constraint to the world.

function newpendulum() {
    let { Bodies, World, Constraint } = Matter
    let pendulum;
    // create new pendulum
    pendulum = Bodies.circle(random(width),random(height/5)-height/5, 40)
    pendulum.color = "#f9cca5"
    pendulums.push(pendulum)
    pendulum.s = 40
    // add line constraint
    let constraint = Constraint.create({
        pointA: { x: width/2, y: 52 },
        bodyB: pendulum,
        length: random(height/2,height*3/4),
        stiffness: 0.1,
    })
    // push to the world
    constraints.push(constraint)
    World.add(engine.world,[pendulum,constraint])
}

draw(): Generate a new pendulum every 20 frames, up to a maximum of 20 pendulums.

// new pendulum every 20 frames, up to a max of 20
if(frameCount % 20==0 && pendulums.length < 20){
     newpendulum(frameCount)
}
Possible Improvements:
  • Add sound to the collisions to make it more realistic
  • Implement a 3D sphere instead of a 2D circle

Lecture Response

AI is rapidly changing the world around us, and many people are worried that it will lead to widespread job losses. I believe that AI is more likely to automate routine tasks and create new opportunities than it is to replace humans entirely, i.e. AI could create jobs in areas such as data science, machine learning, and LLM development.

Although Prof. Neil Leach compared these advancements to a second Copernicus moment, AI is still in progress and is far from reaching human intelligence or superintelligence given that its capabilities are limited. It still struggles with tasks that require common sense, and its problem-solving skills are not enough.

On the artistic side, it is unlikely that AI will completely replace architects in the foreseeable future. It could certainly help automate tasks such as 3D modeling, but it still lacks the creativity, and will certainly struggle to understand the needs of clients, interpret complex regulations, and design structures that are both aesthetically pleasing and functionally sound.

Coding Assignment #9 – Nematodes

Concept & Inspiration:

For this week’s assignment, I tried to recreate a microscopic view of nematodes (parasitic species that feed on plants and are found in soil environments) using flocking behaviors (alignment, cohesion, separation). Nematodes have smooth unsegmented bodies similar to worms, as shown in the following picture:

View post on imgur.com

In the sketch, there is a circle in the middle representing the view from the microscope oculars (eyepieces), and starts with a smooth fade in animation as if the light of the microscope was turned on.

View post on imgur.com

Sketch:

Code Walkthrough:

https://editor.p5js.org/bdr/sketches/n0c4DAM2S

Nematode (vehicle) Class:

Flocking – Separation: to cause nematodes to steer away from all the others.

separate() {
    let desiredSeparation = 25;
    let steer = createVector(0, 0);
    let count = 0;

    for (let i = 0; i < Nematodes.length; i++) {
      let d = p5.Vector.dist(this.position, Nematodes[i].position);
      if (d > 0 && d < desiredSeparation) {
        let diff = p5.Vector.sub(this.position, Nematodes[i].position);
        diff.normalize();
        diff.div(d);
        steer.add(diff);
        count++;
      }
    }

    if (count > 0) {
      steer.div(count);
    }

    if (steer.mag() > 0) {
      steer.normalize();
      steer.mult(this.maxspeed);
      steer.sub(this.velocity);
      steer.limit(this.maxforce);
    }
    return steer;
  }

Flocking – Cohesion: to cause nematodes to steer towards the center of mass – the average position of the nematodes within the circle.

// Flocking - Cohesion
  cohere() {
    let neighborDistance = 50;
    let sum = createVector(0, 0);
    let count = 0;

    for (let i = 0; i < Nematodes.length; i++) {
      let d = p5.Vector.dist(this.position, Nematodes[i].position);
      if (d > 0 && d < neighborDistance) {
        sum.add(Nematodes[i].position);
        count++;
      }
    }

    if (count > 0) {
      sum.div(count);
      return this.seek(sum);
    } else {
      return createVector(0, 0);
    }
  }

Flocking – Alignment: to cause the nematodes to line up with others close by.

align() {
    let neighborDistance = 40;
    let sum = createVector(0, 0);
    let count = 0;

    for (let i = 0; i < Nematodes.length; i++) {
      let d = p5.Vector.dist(this.position, Nematodes[i].position);
      if (d > 0 && d < neighborDistance) {
        sum.add(Nematodes[i].velocity);
        count++;
      }
    }

    if (count > 0) {
      sum.div(count);
      sum.normalize();
      sum.mult(this.maxspeed);
      let steer = p5.Vector.sub(sum, this.velocity);
      steer.limit(this.maxforce);
      return steer;
    } else {
      return createVector(0, 0);
    }
  }

Trail: FrameRate animation: To get a smooth animation, I used frame rates.

frameRate(30)
// Add a trail
if (frameCount % 20 == 0) {
  fill(160, 160, 45, 15);
}
ellipse(width/2, height/2, 440, 440);
Improvements:

One of the next steps would be to add user interactions using the mouse, i.e. creating an obstacle when the user clicks within the bounds of the circle, add food to the nematodes to consume that would help increase their speed/behavior…

Coding Assignment #7 – Week 9

Concepts:

For this week’s assignment, I tried to implement a cloud of particles, each inspired by Craig Reynolds’s autonomous agents, that seek invisible and randomly generated vertices. I wanted to see how these agents work together, figure out the best path, and adapt to the changing environment. In terms of technicalities, I’m using a seek function and acceleration to drift toward the vertices one by one, and the HSB color mode for aesthetics.

Sketch:

Process:

View post on imgur.com

View post on imgur.com

View post on imgur.com

Code Walkthrough:

Vehicle Class:

The vehicle class represents the autonomous agents (cloud of particles), and has one movement function alongside the constructor. It uses position, velocity, acceleration, direction, and color properties.

Move():

The move() function combines the seeking force and other helper functions (finding the next vertex index, checking if the particle reaches the vertex, changing direction)

move() {
  // Go to the next vertex
  let target = this.vertices[this.idx];
  let distance = dist(this.position.x, this.position.y, target.x, target.y);
        
  if (distance < 45) {
    if ((this.direction==1 && this.idx==this.vertices.length-1) || (this.direction==-1 && this.idx==0)) {
    this.direction*= -1;
    }
    this.idx+=this.direction;
  }

  // Seek the next vertex
  if (distance >1) {
    let steer = new p5.Vector(target.x, target.y);
    steer.sub(this.position);
    steer.normalize();
    steer.mult(0.045);
    this.acceleration.add(steer);
  }
        
  // Add movement
  this.velocity.mult(0.99);
  this.velocity.add(this.acceleration);
  this.velocity.limit(6);
  this.position.add(this.velocity);
  this.acceleration.mult(0);
}

Upon every mouseclick, 1000 new particles are generated and the vertices change as well.

Vertices:

To generate the vertices, I’m using a random function:

vertices=[];
for (let i = 0; i < 6; i++) {
      let x = map(i, 0, 6, width/4, width - width/4);
      let y = random(120, height - 120);
      vertices.push(new p5.Vector(x, y))
}

HSB Color Mode:

I’m using the Hue specifically and brightness to add an aesthetic look to the particles.

Next Steps:

Future steps would be to add other forces i.e. avoiding obstacles.

Midterm Project – Realms

Concept:

My midterm project consists of a landscape NFT collection as a combination of my interest in cryptography and generative art. It uses different mathematical and artistic concepts to produce unique nature-related artwork.

Think of NFTs as digital collectibles, like trading cards or unique stamps. When an artist creates a digital artwork, they can “mint” an NFT, which essentially means they create a special, one-of-a-kind digital token associated with that artwork.

In my project, through a GUI, the user can specify the style (acrylic, japanese, animated…) and mint their own NFT. The revolving theme is Nature, as I was inspired by the course’s name “decoding nature”.

Example from OpenSea – NFT marketplace:

View post on imgur.com

Sketch:

For a better experience (Full Screen), use the following link.

Code Walkthrough (Special Functions):

Vintage effect:
In order to get an ancient style, I’m using noise as a primary way to simulate the imperfections and irregularities alongside the random function. i.e. most shapes have irregular edges, cracks, and distortions.

To generate the dots in the corner, I’m using the following:

function displayNoise(){
    strokeWeight(0.5);
    for(let dx = 0; dx < width; dx += random(1, 3)) {
        for(let dy = 0; dy < height; dy += random(1, 3)) {
            let pointX = dx * noise(dy/10);
            let pointY = dy * noise(dx/10);
            if (get(pointX, pointY)[0]==241) {
                stroke(darkColor);
            } else {
                stroke(lightColor);
            }
            point(pointX, pointY);
        }
    }
}

Hand-drawn effect:
To create hand-drawn contours, I’m using sin-waves relying on vertices, noise, and random functions. To get varying amplitudes I’m using the following:

let a = random(-width/2, width/2);
let b = random(-width/2, width/2);
let c = random(2, 4);
let d = random(40, 50);
let e = random(-width/2, width/2);
for (let x = 0; x < width; x ++){          
      let y = currY[j];
      y += 10*j*sin(2*dx/j + a);
      y += c*j*sin(5*dx/j + b);
      y += d*j*noise(1.2*dx/j +e);
      y += 1.7*j*noise(10*dx);
    dx+=0.02;
    mountains.push({x,y})
}

As for the vertices and to be able to close the shape in order to be able to fill it later on, I’m using:

beginShape();
vertex(0, height);
for (let i = 0; i < mountains.length; i++) {
    stroke(darkColor);
    vertex(mountains[i].x, mountains[i].y);
}
vertex(width, height);
endShape(CLOSE);

Gradient:
To get a smooth gradient, I’m using the mathematical concept: linear interpolation.

It constructs new data points within the range of a given discrete set data points which are in our case colors.

View post on imgur.com

function applyGradient(x, y, w, h, color1, color2) {
   noFill();
   for (let i = y; i <= y + h; i++) {
      let mid = map(i, y, y + h, 0, 1);
      let clr = lerpColor(color1, color2, mid);
      stroke(clr);
      line(x, i, x + w, i);
   }
}

Granulation and Blur:
To do that, I looped over the canvas pixels and shifted some of them randomly. To further fuzzify the artwork, I rounded the pixels after moving them as well and getting their density.

View post on imgur.com

The code for it is:

function granulateFuzzify(amount) {
    loadPixels();
    let d = pixelDensity();
    let fuzzyPixels = 2;
    let modC = 4 * fuzzyPixels;
    let modW = 4 * width * d;
    let pixelsCount = modW * (height * d);
    for (let i = 0; i < pixelsCount; i += 4) {
        let f = modC + modW;
            if (pixels[i+f]) {
            pixels[i] = round((pixels[i] + pixels[i+f])/2);
                  pixels[i+1] = round((pixels[i+1] + pixels[i+f+1])/2);
                  pixels[i+2] = round((pixels[i+2] + pixels[i+f+2])/2);
            }
        pixels[i] = pixels[i] + random(-amount, amount);
        pixels[i+1] = pixels[i+1] + random(-amount, amount);
        pixels[i+2] = pixels[i+2] + random(-amount, amount);
    }
    updatePixels();
}

Branches generation:
To display realistic branches, I’m alternating the size after each iteration. The branch is drawn using a sequence of circles. I’m using a seed variable called “BranchChance” to add randomness.

display() {
    if (this.size < 2) {
      return false;
    }

    stroke(0);
    fill(0, 0, 0, min(255, 5 * this.size));
    ellipse(this.x, this.y, this.size, this.size);

    this.x += (this.size / 7) * cos(this.angle);
    this.y += (this.size / 7) * sin(this.angle);
    this.size *= random(0.99, 1);
    this.angle += random(-0.05, 0.05);

    if (this.size < 10) {
      this.angle += random(-0.1, 0.1);
    }
    if (this.size < 30) {
      this.angle += random(-0.1, 0.1);
    }

    if (
      (this.size < 10 && random(0, 1) < 0.2 * branchChance) ||
      (this.size > 4 && random(0, 100 / (this.t + 1)) < branchChance)
    ) {
      this.branches.push(new Branch(this.x, this.y, this.angle - PI / 7, this.size, 0));

      this.angle += PI / 7;
      this.t = 0;
    }

    for (let i = this.branches.length - 1; i >= 0; i--) {
      if (!this.branches[i].display()) {
        this.branches.splice(i, 1);
      }
    }
    this.t++;
    return true;
}

Noise Gradient background:
To set a sunset gradient for the canvas with some noise, I’m using ellipses once again as per the following:

function animateSunset(){
  clear();
  let cells = 128;
  let sat = 255;
  let bri = 192;
  let maxNoiseScale = 14;
  let p = 8;
  let cell = width / cells;

  randomSeed(0);

  z2 += 0.001;
  xoffset += 0.001;

  let noisePower = Math.pow(10, Math.log10(maxNoiseScale) / cells);

  let passes = 1;
  for (let z = 1; z <= passes; z++) {
    let cols = cells;
    let rows = cells;
    let noiseScale = noisePower;

    for (let j = -2; j < rows + 15; j++) {
      let y = (j / rows);
      let wy = y * height;

      for (let i = -2; i < cols + 8; i++) {
        let x = (i / cols);
        let wx = x * width;

        // Color
        let q = noise((x * noiseScale) + xoffset, y * noiseScale, z2);
        if (q > 0.5) {
          q = (((Math.pow(((q - 0.5) * 2) - 1, p) - 1) * -1) / 2) + 0.5;
        }
        else {
          q = Math.pow(q * 2, p) / 2;
        }
        q = ((q * 150) + 145) % 255;
        let boxColor = color(q, sat, bri, random(11) + 1);
        fill(boxColor);

        // Draw circle
        let factor = noise(x + xoffset, y, z2) * 23 + 1;
        let diameter = cell * factor;
        ellipse(wx, wy, diameter, diameter);
      }
      noiseScale *= noisePower;
    }
  }
}

Matrix Japanese Text:
The Japanese text represents different nature and life-related quotes that are displayed vertically in a Matrix style. I’m thinking of animating it later.

["風流韻事" // Appreciating nature through elegant pursuits such as poetry, calligraphy and painting
,"柳は緑花は紅" // Willows are green, flowers are crimson; natural state, unspoiled by human touch, nature is beautiful, all things have different natures or characteristics.
,"花鳥風月" // The beauties of nature. 
,"旅はまだ途中だぞ" // Our adventure is not done yet
,"前向きにね" // Stay positive
,"雨降って地固まる" // After rain, comes fair weather
,"苦あれば楽あり" // There are hardships and also there are pleasures
,"初心忘るべからず" // Should not forget our original intention
,"浮世憂き世" // Floating world
,"自分の生きる人生を愛せ" // Love the life you are living
,"行雲流水" // Flow as the clouds and water
,"人生は夢だらけ" // Life is full of dreams
,"春になると森は青々としてくるです" // In spring, the forest becomes lush
,"今日の夕焼けはとてもきれいです" // Today's sky is nice and red
,"生き甲斐" // Realisation of what one expects and hopes for in life
];
Challenges:

One of the challenges I have faced is certainly setting the right parameters for the waves to get a somewhat hand-drawn but still realistic set of mountains. It took a lot of experimentation, trial and errors to finally find the right combination. Similarly, for both the gradients to be smooth and match all the components of the artwork, I had to find the right settings for the linear interpolation.

Future Plans:

Incorporate 3D elements using WebGL or other 3D engines.
Allow more customization through the GUI.

Plotter result:

View post on imgur.com

 

View post on imgur.com

Project Images:

View post on imgur.com

View post on imgur.com

 

Midterm Progress #2 – Landscape

Concept:

My midterm project consists of a landscape NFT collection as a combination of my interest in cryptography and generative art. It uses different mathematical and artistic concepts to produce unique nature-related artwork. Through a GUI, the user can specify the style (acrylic, japanese, animated…) and generate their own NFT. As for choosing nature as a theme, I was inspired by the course’s name “decoding nature”.

Librairies/Algorithms:

TweakPane
Perlin Noise
Sin-Wave
Particle System
Recursion
Granulation
Linear Interpolation

Code: https://editor.p5js.org/bdr/sketches/qhH_1I3FP

Progress:

View post on imgur.com

View post on imgur.com

Code Walkthrough (Special Functions):

Vintage effect:
In order to get an ancient style, I’m using noise as a primary way to simulate the imperfections and irregularities alongside the random function. To generate the dots in the corner, I’m using the following:

function displayNoise(){
    strokeWeight(0.5);
    for(let dx = 0; dx < width; dx += random(1, 3)) {
        for(let dy = 0; dy < height; dy += random(1, 3)) {
        	let pointX = dx * noise(dy/10);
            let pointY = dy * noise(dx/10);
            if (get(pointX, pointY)[0]==241) {
                stroke(darkColor);
            } else {
                stroke(lightColor);
            }
        	point(pointX, pointY);
        }
    }
}

Mountains:
To create hand-drawn mountain contours, I’m using sin-waves relying on vertices, noise, and random functions. To get varying amplitudes I’m using the following:

let a = random(-width/2, width/2);
let b = random(-width/2, width/2);
let c = random(2, 4);
let d = random(40, 50);
let e = random(-width/2, width/2);

for (let x = 0; x < width; x ++){          
  	let y = currY[j];
  	y += 10*j*sin(2*dx/j + a);
  	y += c*j*sin(5*dx/j + b);
  	y += d*j*noise(1.2*dx/j +e);
  	y += 1.7*j*noise(10*dx);
    dx+=0.02;
    mountains.push({x,y})
}

As for the vertices and to be able to close the shape in order to be able to fill it later on, I’m using:

beginShape();
vertex(0, height);
for (let i = 0; i < mountains.length; i++) {
    stroke(darkColor);
    vertex(mountains[i].x, mountains[i].y);
}
vertex(width, height);
endShape(CLOSE);

Matrix Japanese Text:
The Japanese text represents different nature and life related quotes that are displayed vertically in a Matrix style. I’m thinking of animating it later.

["風流韻事" // Appreciating nature through elegant pursuits such as poetry, calligraphy and painting
,"柳は緑花は紅" // Willows are green, flowers are crimson; natural state, unspoiled by human touch, nature is beautiful, all things have different natures or characteristics.
,"花鳥風月" // The beauties of nature. 
,"旅はまだ途中だぞ" // Our adventure is not done yet
,"前向きにね" // Stay positive
,"雨降って地固まる" // After rain, comes fair weather
,"苦あれば楽あり" // There are hardships and also there are pleasures
,"初心忘るべからず" // Should not forget our original intention
,"浮世憂き世" // Floating world
,"自分の生きる人生を愛せ" // Love the life you are living
,"行雲流水" // Flow as the clouds and water
,"人生は夢だらけ" // Life is full of dreams
,"春になると森は青々としてくるです" // In spring, the forest becomes lush
,"今日の夕焼けはとてもきれいです" // Today's sky is nice and red
,"生き甲斐" // Realisation of what one expects and hopes for in life
];

Gradient:
To get a smooth gradient, I’m using the mathematical concept: linear interpolation. It constructs new data points within the range of a given discrete set data points which are in our case colors.

function applyGradient(x, y, w, h, color1, color2) {
   noFill();
   for (let i = y; i <= y + h; i++) {
      let mid = map(i, y, y + h, 0, 1);
      let clr = lerpColor(color1, color2, mid);
      stroke(clr);
      line(x, i, x + w, i);
   }
}

Granulation and Blur:
To do that, I looped over the canvas pixels and shifted some of them randomly. To further fuzzify the artwork, I rounded the pixels after moving them as well and getting their density.

function granulateFuzzify(amount) {
    loadPixels();
    let d = pixelDensity();
    let fuzzyPixels = 2;
    let modC = 4 * fuzzyPixels;
    let modW = 4 * width * d;
    let pixelsCount = modW * (height * d);
    for (let i = 0; i < pixelsCount; i += 4) {
        let f = modC + modW;
    		if (pixels[i+f]) {
            pixels[i] = round((pixels[i] + pixels[i+f])/2);
    			  pixels[i+1] = round((pixels[i+1] + pixels[i+f+1])/2);
    			  pixels[i+2] = round((pixels[i+2] + pixels[i+f+2])/2);
    		}
        pixels[i] = pixels[i] + random(-amount, amount);
        pixels[i+1] = pixels[i+1] + random(-amount, amount);
        pixels[i+2] = pixels[i+2] + random(-amount, amount);
    }
    updatePixels();
}

Stars Particle System:
To get a stars system, I had a separate class named Star and created new instances using a for loop.

class Star {
  constructor(x, y) {
    this.xPos = x;
    this.yPos = y;
  }

  draw() {
    let randSize = random(3);
    fill(255, 255, 255, random(0, 200));
    ellipse(this.xPos, this.yPos, randSize, randSize);
  }
}

function makeStars(numStars) {
  if (starCount < numStars) {
    let s = new Star(int(random(width)), int(random(height / 1.8)));
    s.draw();
  }
  starCount++;
}

Challenges:

One of the challenges I have faced is certainly setting the right parameters for the waves to get a somewhat hand-drawn but still realistic set of mountains. It took a lot of experimentation, trial and errors to finally find the right combination. Similarly, for the gradient to be smooth and match all the components of the artwork, I had to find the right settings for the linear interpolation.

Next Steps:

Animation: animate the canvas/artwork, most probably a 3D effect.