Day 21 - Art 1

Create a collision detection system (no libraries allowed)

Orbiting Circles

This generative art features a dynamic system of stationary circles arranged in a ring, with one orbiting circle that moves around the stationary ones. The orbiting circle attaches itself to different stationary circles upon collision, creating a continuous cycle of movement. The size of the orbiting circle oscillates between a minimum and maximum value, adding a fluid, organic feel to the motion. A gap between two stationary circles adds an additional visual element, while a cooldown timer prevents immediate reattachment, creating smooth transitions between parent circles.

    // Array to hold stationary circles
    let stationaryCircles = [];
    // Object for the orbiting circle
    let orbitingCircle;
    // Number of stationary circles in the ring
    let numCircles = 9; 
    // Angle for the orbiting circle's motion
    let angle = 0;
    // Speed of the orbiting circle's motion
    let orbitSpeed = 0.05;
    // The current parent circle that the orbiting circle is attached to
    let currentParent = null;
    // Cooldown timer to avoid immediate reattachment to a new parent
    let cooldown = 0; 
    // Index for the gap between circles in the ring formation
    let gapIndex = 1; 
    // Factor to increase the gap size between stationary circles
    let gapSizeFactor = 1.2; 
    // Canvas variable
    let canvas;
    
    // New variables for oscillating the orbiting circle's size
    let minOrbitSize = 8;  // Minimum size of the orbiting circle
    let maxOrbitSize = 15;  // Maximum size of the orbiting circle
    let sizeChangeSpeed = 0.02;  // Speed at which the size changes
    let sizeDirection = 1;  // Direction of size change (1 for increasing, -1 for decreasing)
    
    function setup() {
      // Create the canvas and set its parent container in HTML
      canvas = createCanvas(400, 400); 
      canvas.parent('canvas-container');
      noStroke(); // Disable stroke for shapes
    
      // Calculate the center of the canvas
      let centerX = width / 2;
      let centerY = height / 2;
      // Radius of the ring formation
      let ringRadius = 120; 
    
      // Calculate the angle between adjacent stationary circles
      let angleStep = TWO_PI / numCircles;
    
      // Create stationary circles in a ring formation
      for (let i = 0; i < numCircles; i++) {
        let angleOffset = angleStep * i;
        let randomRadius = random(20, 40); // Random radius for each circle
        let x = centerX + cos(angleOffset) * ringRadius;
        let y = centerY + sin(angleOffset) * ringRadius;
    
        // Add each circle to the stationaryCircles array
        stationaryCircles.push({
          x: x,
          y: y,
          r: randomRadius,
          color: color(random(255), random(255), random(255)), // Random color for each circle
        });
      }
    
      // Adjust positions to create a gap between two circles
      let prevCircle = stationaryCircles[gapIndex];
      let nextCircle = stationaryCircles[(gapIndex + 1) % numCircles];
    
      // Calculate the distance between the two circles
      let dx = nextCircle.x - prevCircle.x;
      let dy = nextCircle.y - prevCircle.y;
      let distance = dist(prevCircle.x, prevCircle.y, nextCircle.x, nextCircle.y);
      // Adjust the next circle's position to create the gap
      let adjustment = (gapSizeFactor * prevCircle.r + gapSizeFactor * nextCircle.r) - distance;
    
      // Move the next circle outward to create the gap
      nextCircle.x += (dx / distance) * adjustment;
      nextCircle.y += (dy / distance) * adjustment;
    
      // Initialize the orbiting circle's starting position and size
      orbitingCircle = {
        x: stationaryCircles[0].x,
        y: stationaryCircles[0].y - stationaryCircles[0].r,
        r: minOrbitSize,  // Start with the minimum size
        color: color(255, 255, 255), // White color for the orbiting circle
      };
    
      // Set the first parent circle to be the first stationary circle
      currentParent = stationaryCircles[0];
    }
    
    function draw() {
      background(30); // Set background color
    
      // Draw all stationary circles
      for (let c of stationaryCircles) {
        fill(c.color);
        ellipse(c.x, c.y, c.r * 2); // Draw each circle with its radius
      }
    
      // Update the cooldown timer to prevent immediate reattachment
      if (cooldown > 0) {
        cooldown -= deltaTime / 150; // Convert milliseconds to seconds
      }
    
      // Update the orbiting circle's position based on its parent
      if (currentParent) {
        orbitingCircle.x = currentParent.x + cos(angle) * (currentParent.r + orbitingCircle.r);
        orbitingCircle.y = currentParent.y + sin(angle) * (currentParent.r + orbitingCircle.r);
      }
    
      // Draw the line linking the orbiting circle to its parent
      stroke(255, 150); // Semi-transparent white line
      line(orbitingCircle.x, orbitingCircle.y, currentParent.x, currentParent.y);
      noStroke(); // Disable stroke for shapes
    
      // Draw the orbiting circle
      fill(orbitingCircle.color);
      ellipse(orbitingCircle.x, orbitingCircle.y, orbitingCircle.r * 2); // Draw the orbiting circle
    
      // Update the angle for the orbiting motion
      angle += orbitSpeed;
    
      // Check if the orbiting circle collides with any stationary circles
      for (let i = 0; i < stationaryCircles.length; i++) {
        let circle = stationaryCircles[i];
        if (
          circle !== currentParent && // Ignore the current parent
          isTouching(orbitingCircle, circle) && // Check for collision
          cooldown <= 0 // Ensure cooldown has elapsed
        ) {
          // Attach the orbiting circle to the new parent
          currentParent = circle;
          // Adjust the angle for smooth transition to the new parent
          angle = atan2(
            orbitingCircle.y - circle.y,
            orbitingCircle.x - circle.x
          );
          cooldown = 0.5; // Set a cooldown period of 0.5 seconds
          break;
        }
      }
    
      // Gradually change the size of the orbiting circle
      orbitingCircle.r += sizeDirection * sizeChangeSpeed;
    
      // Reverse direction when the size reaches the limits
      if (orbitingCircle.r >= maxOrbitSize || orbitingCircle.r <= minOrbitSize) {
        sizeDirection *= -1;  // Reverse the size change direction
      }
    }
    
    // Check if the orbiting circle is touching another stationary circle
    function isTouching(orbit, circle) {
      let distance = dist(orbit.x, orbit.y, circle.x, circle.y);
      // Return true if the orbiting circle's edge is touching the stationary circle's edge
      return abs(distance - (circle.r + orbit.r)) < 1; // Precision for edge-to-edge contact
    }