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