Day 20 - Art 1

Generative Architecture

Planetary Skyline

Planetary Skyline is a generative art piece that simulates the growth of buildings on the surface of a rotating planet. As time progresses, buildings gradually grow in height, and new ones are added, ensuring they don't overlap. The artwork features a dynamic 3D view of a planet with buildings emerging like a futuristic cityscape, continuously evolving as the planet rotates and new structures are added. The piece explores the concept of urban expansion on a planetary scale, blending the organic growth of architecture with the vastness of space.

    let buildings = []; // Array to hold all the buildings on the planet's surface
    let buildingGrowthRate = 0.1; // Rate at which the buildings grow taller
    let rotationAngle = 75; // Initial rotation angle of the planet
    let rotationSpeed = 0.002; // Speed of the planet's rotation (slower rotation)
    let maxBuildings = 50; // Maximum number of buildings allowed on the planet
    let buildingDistanceThreshold = 0.1; // Minimum distance between buildings to avoid overlap
    let canvas; // Reference to the canvas element
    
    function setup() {
      // Create the canvas with WEBGL renderer for 3D rendering
      canvas = createCanvas(400, 400, WEBGL);
      canvas.parent('canvas-container'); // Attach the canvas to an HTML container with id 'canvas-container'
      noStroke(); // Disable stroke for shapes drawn on the canvas
    
      // Create some initial buildings on the planet's surface (5 buildings)
      for (let i = 0; i < 5; i++) {
        let lat = random(-PI / 2, PI / 2); // Random latitude between -PI/2 and PI/2 (from pole to pole)
        let lon = random(-PI, PI); // Random longitude between -PI and PI (full range around the globe)
    
        // Create a building at a random position on the sphere's surface
        let building = {
          lat: lat, // Latitude of the building
          lon: lon, // Longitude of the building
          maxHeight: random(10, 100), // Random max height for the building
          height: random(10, 20) // Initial height of the building
        };
        buildings.push(building); // Add the building to the buildings array
      }
    }
    
    function draw() {
      // Main draw loop
      background(0); // Set background to black
      rotatePlanet(); // Rotate the planet to simulate rotation
      drawPlanet(); // Draw the planet as a sphere
      growBuildings(); // Grow the buildings (increase their height)
      addNewBuildings(); // Gradually add new buildings to the planet
    }
    
    function rotatePlanet() {
      // Rotate the planet to simulate rotation (slower rotation speed)
      rotationAngle += rotationSpeed; // Increment the rotation angle by the rotation speed
      rotateY(rotationAngle); // Apply the rotation around the Y-axis (vertical axis)
    }
    
    function drawPlanet() {
      // Draw the planet as a sphere
      push(); // Save the current transformation state
      fill(46, 52, 43); // Set the color for the planet (dark greenish-brown)
      sphere(150); // Draw a sphere with a radius of 150 (the planet's surface)
      pop(); // Restore the previous transformation state
    }
    
    function growBuildings() {
      // Draw and grow buildings on the surface of the planet
      for (let i = buildings.length - 1; i >= 0; i--) {
        let building = buildings[i];
    
        // Calculate the position of the building on the sphere's surface using spherical coordinates
        let x = 150 * cos(building.lat) * cos(building.lon); // X coordinate on the sphere
        let y = 150 * cos(building.lat) * sin(building.lon); // Y coordinate on the sphere
        let z = 150 * sin(building.lat); // Z coordinate on the sphere
    
        // Increase the height of the building
        if (building.height < building.maxHeight) {
          building.height += buildingGrowthRate; // Gradually increase the building height
        }
    
        // Calculate the outward direction from the surface of the sphere (normal vector)
        let normalX = cos(building.lat) * cos(building.lon);
        let normalY = cos(building.lat) * sin(building.lon);
        let normalZ = sin(building.lat);
    
        // Calculate the position for the building base (on the surface of the sphere)
        let buildingRadius = 150; // The radius of the sphere (surface level)
        let bx = buildingRadius * normalX;
        let by = buildingRadius * normalY;
        let bz = buildingRadius * normalZ;
    
        // Move the building outward from the surface along the normal vector, keeping the base at the surface
        let buildingHeight = building.height; // The height of the building
        let bxOut = (buildingRadius + buildingHeight) * normalX;
        let byOut = (buildingRadius + buildingHeight) * normalY;
        let bzOut = (buildingRadius + buildingHeight) * normalZ;
    
        // Draw the building with its base anchored to the sphere's surface
        push(); // Save the current transformation state
        translate(bx, by, bz); // Place the base of the building at the surface of the planet
    
        // Align the building to grow outward from the center of the sphere
        // Rotate the building to align with the normal vector (straight outward from the surface)
        let normalVec = createVector(normalX, normalY, normalZ);
        let upVec = createVector(0, 0, 1); // Vector pointing straight up
    
        // Calculate the angle between the normal vector and the up vector
        let angle = normalVec.angleBetween(upVec);
    
        // Rotate the building to be perpendicular to the surface (aligned with the normal vector)
        let axis = upVec.cross(normalVec); // Find the axis of rotation
        rotate(angle, axis); // Rotate the building to align with the normal vector
    
        fill(200, 200, 255); // Set the color for the building (light blue)
        rotateX(HALF_PI); // Rotate the building to align the cylinder's base with the surface
        cylinder(5, building.height); // Draw the building as a cylinder with a radius of 5 and the calculated height
        pop(); // Restore the previous transformation state
      }
    }
    
    function addNewBuildings() {
      // Gradually add new buildings until we reach the maximum number of buildings
      if (buildings.length < maxBuildings) {
        let newLat = random(-PI / 2, PI / 2); // Random latitude for the new building
        let newLon = random(-PI, PI); // Random longitude for the new building
    
        // Check if the new position overlaps with existing buildings
        let isOverlapping = false;
        for (let building of buildings) {
          let dist = distOnSphere(building.lat, building.lon, newLat, newLon); // Calculate the distance between the new position and existing buildings
          if (dist < buildingDistanceThreshold) { // If the distance is smaller than the threshold, it's overlapping
            isOverlapping = true;
            break; // Stop checking further if overlap is detected
          }
        }
    
        // If no overlap, add the new building
        if (!isOverlapping) {
          let newBuilding = {
            lat: newLat,
            lon: newLon,
            maxHeight: random(10, 100), // Random max height for the new building
            height: random(10, 20) // Initial height of the new building
          };
          buildings.push(newBuilding); // Add the new building to the array
        }
      }
    }
    
    // Function to calculate the distance between two points on the sphere's surface using spherical coordinates
    function distOnSphere(lat1, lon1, lat2, lon2) {
      let dLat = lat2 - lat1; // Difference in latitudes
      let dLon = lon2 - lon1; // Difference in longitudes
      let a = sin(dLat / 2) * sin(dLat / 2) + cos(lat1) * cos(lat2) * sin(dLon / 2) * sin(dLon / 2); // Haversine formula
      let c = 2 * atan2(sqrt(a), sqrt(1 - a)); // Calculate the central angle between the two points
      return c; // This is the angular distance between the two points
    }