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
}