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
}