Final Project: H A V O C

Concept:

Rethinking this project, I moved away from one specific requirement of the assignment just to satisfy the artist in me. We were asked to give users the freedom of interactivity. However, as a strong believer that art always belongs to the artist and they have more right to control their art, I created H A V O C.

H A V O C is a project that starts by motivating the user to build large terrains, in this case, big mountains, hoping to create the illusion that the user has control. After 10 seconds, the terrain switches into chaos mode and ruins what the user created. This happens for three rounds. Once it’s done, a very short video plays as an open question or an open-ended discussion.

Coding Concepts:
  • WEBGL
  • Terrains: 
    • Object-Oriented Programming
    • Forces
    • Oscillation
    • Fractals
  • Sky:
    • Particle System
    • Noise-based

 

Highlight of my code

class Terrain {
  constructor(cols, rows, scl, w, h) {
    this.cols = cols;
    this.rows = rows;
    this.scl = scl;
    this.w = w;
    this.h = h;
    this.terrain = [];
    this.controlledTerrain = [];
    this.flying = 100;
    this.noiseFactor = 0.1;
    this.waveSpeed = 0.05;

    // Initialize both terrain heights to 0
    for (let x = 0; x < this.cols; x++) {
      this.terrain[x] = [];
      this.controlledTerrain[x] = [];
      for (let y = 0; y < this.rows; y++) {
        this.terrain[x][y] = 0; // Original terrain for water effect
        this.controlledTerrain[x][y] = 0; // Controlled terrain for user interaction
      }
    }
  }

  updateTerrain(isChaosMode) {
    if (isChaosMode) {
      this.waveSpeed = 0.01; // Faster wave speed in chaos mode
    } else {
      this.waveSpeed = 0.008; // Slower wave speed in normal mode
    }

    this.flying -= this.waveSpeed; // Animate the noise with speed for chaos effect
    let yoff = this.flying;

    // Generate noise-based water effect (waving surface) with more momentum and bigger waves in chaos mode
    for (let y = 0; y < this.rows; y++) {
      let xoff = 0;
      for (let x = 0; x < this.cols; x++) {
        let noiseValue = noise(xoff, yoff);

        if (isChaosMode) {
          this.terrain[x][y] = map(noiseValue, 0, 1, -120, 200); // Increase amplitude for bigger waves
        } else {
          this.terrain[x][y] = map(noiseValue, 0, 1, -30, 60); // Calm small waves
        }

        // Destruction logic: Collision of large waves with high terrain
        let waveHeight = this.terrain[x][y];
        if (waveHeight > 50 && this.controlledTerrain[x][y] > 50) { // When both wave and terrain are high
          // Destroy or lower the terrain if wave height is large
          this.controlledTerrain[x][y] = lerp(this.controlledTerrain[x][y], waveHeight, 0.05); // Smooth destruction
        }

        xoff += this.noiseFactor;
      }
      yoff += 0.05; // Adjust frequency for both modes
    }

    if (isChaosMode) {
      // Drop the land terrain in chaos mode (simulating destruction or sudden collapse)
      for (let x = 0; x < this.cols; x++) {
        for (let y = 0; y < this.rows; y++) {
          if (this.controlledTerrain[x][y] > 0) {
            this.controlledTerrain[x][y] -= 0.5; // Gradually drop terrain height
          }
        }
      }
    }
  }

  displayTerrain(isChaosMode) {
    // Loop through the terrain and apply colors based on height
    rotateX(PI / 3);
    translate(-this.w / 2, -this.h / 2);

    // Draw the original terrain with colors based on height
    for (let y = 0; y < this.rows - 1; y++) {
      beginShape(TRIANGLE_STRIP);
      for (let x = 0; x < this.cols; x++) {
        let height = this.terrain[x][y];
        let colorValue;

        if (isChaosMode) {
          // Chaos mode - Apply bigger waves and faster momentum
          colorValue = height > 0 ? lerpColor(color(54, 54, 117), color(11, 11, 71), map(height, 0, 100, 0, 1)) : color(30, 30, 80); // Dark blue for water
        } else {
          // Normal mode colors
          colorValue = height > 0 ? lerpColor(color(19, 90, 197), color(34, 139, 34), map(height, 0, 100, 0, 1)) : color(19, 90, 197); // Blue for water
        }

        fill(colorValue);
        stroke(0);  // Keep the terrain strokes black
        strokeWeight(1); // Set stroke thickness
        vertex(x * this.scl, y * this.scl, this.terrain[x][y]);
        vertex(x * this.scl, (y + 1) * this.scl, this.terrain[x][y + 1]);
      }
      endShape();
    }
  }

  updateControlledTerrain(xIndex, yIndex) {
    if (xIndex >= 0 && xIndex < this.cols && yIndex >= 0 && yIndex < this.rows) {
      this.controlledTerrain[xIndex][yIndex] += 7; // Increase height when dragging
      this.controlledTerrain[xIndex][yIndex] = constrain(this.controlledTerrain[xIndex][yIndex], -100, 200); // Constrain height
    }
  }

  displayControlledTerrain(isChaosMode) {
    for (let y = 0; y < this.rows - 1; y++) {
      beginShape(TRIANGLE_STRIP);
      for (let x = 0; x < this.cols; x++) {
        let height = this.controlledTerrain[x][y];
        let colorValue;

        if (isChaosMode) {
          colorValue = lerpColor(color(255, 204, 0), color(255, 165, 0), map(height, 0, 100, 0, 1)); // Yellowish-orange for controlled land
        } else {
          colorValue = color(34, 139, 34); // Green for controlled land
        }

        fill(colorValue);
        stroke(0);  // Keep the terrain strokes black
        strokeWeight(1); // Set stroke thickness
        vertex(x * this.scl, y * this.scl, this.controlledTerrain[x][y]);
        vertex(x * this.scl, (y + 1) * this.scl, this.controlledTerrain[x][y + 1]);
      }
      endShape();
    }
  }
}

 

Embedded sketch:

 

Sketch link:

https://editor.p5js.org/mariamalkhoori/sketches/TSxf20kTR

User Testing Feedback:

Both people’s comment were that that they were a little confused with the interactions and its laggy on their device.

 

IM Showcase:

https://youtube.com/shorts/pRWZ8Rjyd3c

Parts I’m proud of:

Switch between normal and chaos mode

if (isChaosMode) {
  this.waveSpeed = 0.01;
  this.terrain[x][y] = map(noiseValue, 0, 1, -120, 200);  // Larger waves
} else {
  this.waveSpeed = 0.008;
  this.terrain[x][y] = map(noiseValue, 0, 1, -30, 60);  // Calm waves
}

 

Challenges and Areas for improvement:

Challenges:
This was my first time working with 3D using WebGL, and it was challenging to incorporate 2D elements, such as text, into the 3D environment.
Additionally, adding the video at the end was difficult, and I faced issues with the timer, which kept running continuously.

Improvements:
I had plans to add sound effects, but the complexity of the particle system made it difficult to implement.
I also wanted to create a more polished display screen for better user interaction.
The timer should start when the start button is pressed, but it only begins when the page refreshes.
Finally, I wanted the “Return” button to restart the entire sketch, but this functionality wasn’t working as expected.

References:
  • https://www.youtube.com/watch?app=desktop&v=ELpZW62HGVs&t=3100s&ab_channel=TheCodingTrain
  • https://www.youtube.com/watch?v=fN0Wa9mvT60&ab_channel=PattVira
  • https://editor.p5js.org/dlatolley/sketches/Bu3JRNqgF

Leave a Reply

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