Now that it’s *actually* completed, I can post the actual documentation! Introducing my final project: “StarScape”.
Concept:
For my midterm project, I made a retro game, so this time I wanted to make something more artsy, i.e. focused more on the display and visuals, rather than having a linear progression of events that have an end-goal. Personally, my favorite type of interactive art installations are the ones that make use of lights (some very primitive part of my brain still goes “Wow” like a child when I see lights turn on and off). Especially inspired by N O W I S W H E N W E A R E (the stars), I wanted to make a piece that simulates the stars and has a corresponding display of lights.
The interaction is relatively straightforward: the user presses the Arcade buttons, which will trigger a corresponding movement on the screen and a light sequence on the RGB strip.
Implementation:
Hardware:
Four arcade LED buttons, and a NeoPixel RGB Strip
Software:
For this project, I used p5.js and the Arduino IDE.
In the p5.js code, there is a Galaxy object that contains all the particles (and has the methods that trigger the changes). Everytime the code detects that the user has pressed a button on the Arduino, the repective method for that button is called. Each method (jitterParticles, jitterParticlesCloser, moveParticlesInHeart, moveParticlesInSpiral
) does two things: move the particles around, and change their colors.
The p5.js code is scattered across three files:
particle.js:
// Reference: https://p5js.org/examples/simulate-particles.html
// this class describes the properties of a single particle.
class Particle {
// setting the co-ordinates, radius and the
// speed of a particle in both the co-ordinates axes.
constructor(x, y, particleColor, strokeColor){
// this.x = random(-rangeBuffer, width + rangeBuffer);
// this.y = random(-rangeBuffer, height + rangeBuffer);
this.particleColor = particleColor;
this.strokeColor = strokeColor;
this.x = x;
this.y = y;
this.r = random(1,5);
this.xSpeed = random(-2,2);
this.ySpeed = random(-1,1.5);
}
// creation of a particle.
createParticle() {
noStroke();
fill(this.particleColor);
circle(this.x,this.y,this.r);
}
// setting the particle in motion.
moveParticle() {
if(this.x < 0 || this.x > width)
this.xSpeed*=-1;
if(this.y < 0 || this.y > height)
this.ySpeed*=-1;
this.x+=this.xSpeed;
this.y+=this.ySpeed;
}
// this function creates the connections(lines)
// between particles which are less than a certain distance apart
joinParticles(particles) {
particles.forEach(element =>{
let dis = dist(this.x,this.y,element.x,element.y);
if(dis<85) {
stroke(this.strokeColor);
line(this.x,this.y,element.x,element.y);
}
});
}
//make the particle move in a specific direction
moveInDirection(xMovement, yMovement) {
this.x += xMovement;
this.y += yMovement;
}
}
galaxy.js:
let jitterAmount = 5; //the distance particles jitter
let jitterIterations = 5; // Number of jitter iterations
//variables for the particles colors
let galaxyColor = "rgba(200,169,169,0.5)"; //White
let galaxyStroke = "rgba(255,255,255,0.04)";
let heartColor = "rgba(255, 100, 100, 0.5)"; //Pink
let heartStroke = "rgba(255, 150, 150, 0.04)";
let spiralColor = "rgba(184, 134, 11, 0.5)"; // Dark Golden
let spiralStroke = "rgba(255, 215, 0, 0.04)";
let convergenceColor = "rgba(100, 149, 237, 0.5)"; // Blue
let convergenceStroke = "rgba(173, 216, 230, 0.04)";
let scatterColor = "rgba(60, 179, 113, 0.5)"; // Green color
let scatterStroke = "rgba(173, 255, 47, 0.04)";
// function that calculates the center of the particle cluster
function calculateCenter(particleArray) {
let centerX = 0;
let centerY = 0;
for (let i = 0; i < particleArray.length; i++) {
centerX += particleArray[i].x;
centerY += particleArray[i].y;
}
centerX /= particleArray.length;
centerY /= particleArray.length;
return [centerX, centerY];
}
class Galaxy {
constructor() {
this.num = 350; //number of particles in the galaxy
this.rotationSpeed = 0.002;
this.radius = max(width / 2, height / 2);
this.particles = [];
this.centerX = 0;
this.centerY = 0;
this.beforeMovement = [];
// initialize the particles to be scattered across the canvas in a circular distribution
for (let i = 0; i < this.num; i++) {
let angle = random(TWO_PI); //generate a random angle btwn 0 and 2π radians
let r = sqrt(random()) * this.radius; // random radius (limited to the MAXRADIUS of the distribution)
//calculate the x and y coordinates for the particle based on polar coordinates (angle and radius), converting them to Cartesian coordinates
let particleX = width / 2 + r * cos(angle);
let particleY = height / 2 + r * sin(angle);
//add the particle to the array
this.particles.push(
new Particle(
particleX,
particleY,
galaxyColor,
galaxyStroke,
"rgba(255,255,255,0.04)"
)
);
}
let center = calculateCenter(this.particles);
this.centerX = center[0];
this.centerY = center[1];
}
//move the entire cluster in a specific direction
moveGalaxy(xMovement, yMovement) {
for (let i = 0; i < this.particles.length; i++) {
this.particles[i].moveInDirection(xMovement, yMovement);
}
}
moveGalaxyRandom() {
for (let i = 0; i < this.particles.length; i++) {
// Generate random movement for each particle
let xMovement = random(-2, 2); // Random movement along the x-axis
let yMovement = random(-2, 2); // Random movement along the y-axis
this.particles[i].moveInDirection(xMovement, yMovement);
}
}
//move the entire galaxy downwards
moveGalaxyDownwards() {
let iterations = 0;
let prevPositions = [];
function moveDown(particleArray) {
if (iterations < 30) {
// Adjust the number of iterations as needed
for (let i = 0; i < particleArray.length; i++) {
if (iterations == 0) {
prevPositions.push([particleArray[i].x, particleArray[i].y]);
}
particleArray[i].y += 2; // Move particles downwards
}
iterations++;
setTimeout(() => moveDown(particleArray), 10);
}
}
moveDown(this.particles);
this.returnToOriginalPositions(prevPositions);
}
//return the particles to the position that they were in before a certain position was enacted
returnToOriginalPositions() {
for (let i = 0; i < this.particles.length; i++) {
let direction = createVector(
this.beforeMovement[i][0] - this.particles[i].x,
this.beforeMovement[i][1] - this.particles[i].y
);
this.particles[i].x += direction.x * 0.1;
this.particles[i].y += direction.y * 0.1;
}
}
//rotate the galaxy
// Function to rotate the galaxy by a given rotation speed
rotateGalaxy(rotationSpeed) {
// Loop through all particles in the galaxy
for (let i = 0; i < this.particles.length; i++) {
// Calculate the angle between the particle's position and the center of the canvas
let angle = atan2(
this.particles[i].y - height / 2, // Y-component distance from the center of the canvas
this.particles[i].x - width / 2 // X-component distance from the center of the canvas
);
// Add the rotation speed to the angle to rotate the particle
angle += rotationSpeed;
// Calculate the distance from the particle to the center of the canvas
let radius = dist(
width / 2, // X-coordinate of the center of the canvas
height / 2, // Y-coordinate of the center of the canvas
this.particles[i].x, // X-coordinate of the particle
this.particles[i].y // Y-coordinate of the particle
);
// Update the particle's position based on the new angle and radius
this.particles[i].x = width / 2 + radius * cos(angle);
this.particles[i].y = height / 2 + radius * sin(angle);
}
// Calculate the new center of the galaxy based on the updated particle positions
let center = calculateCenter(this.particles);
// Update the center X and Y coordinates of the galaxy
this.centerX = center[0];
this.centerY = center[1];
}
// Function to jitter (move randomly) particles within a given limit
jitterParticles() {
let iterations = 0; // Variable to track the number of iterations for jittering
let prevPositions = []; // Array to store previous positions of particles
// Inner function to perform the actual jittering of particle positions recursively
function jitter(particleArray) {
if (iterations < 10) {
// Perform jittering for 10 iterations
for (let i = 0; i < particleArray.length; i++) {
// Store the previous positions of particles before jittering
prevPositions.push([particleArray[i].x, particleArray[i].y]);
// Move particles randomly within a specific range (jitterAmount)
particleArray[i].x += random(-jitterAmount, jitterAmount) * 4;
particleArray[i].y += random(-jitterAmount, jitterAmount) * 4;
// On the first iteration, randomly change the color of some particles
if (iterations == 0) {
let changeColor = random(0, 1);
if (changeColor > 0.5) {
particleArray[i].particleColor = scatterColor; // Change particle color
particleArray[i].strokeColor = scatterStroke; // Change stroke color
} else if (changeColor < 0.3) {
particleArray[i].particleColor = galaxyColor; // Restore particle color
particleArray[i].strokeColor = galaxyStroke; // Restore stroke color
}
}
}
iterations++; // Increment the iteration count
// Use setTimeout to call the jitter function recursively after a delay of 10 milliseconds
setTimeout(() => jitter(particleArray), 10);
}
}
// Start the jittering process for the current set of particles
jitter(this.particles);
// Save the positions of particles before the movement for reference
this.beforeMovement = prevPositions;
// Calculate the new center of the particle set after jittering
let center = calculateCenter(this.particles);
// Update the center X and Y coordinates of the particle set
this.centerX = center[0];
this.centerY = center[1];
}
// Function to jitter particles upwards within a given limit
jitterParticlesUpwards() {
let iterations = 0; // Variable to track the number of iterations for jittering upwards
// Inner function to perform the upward jittering of particle positions recursively
function jitterUpwards(particleArray) {
if (iterations < jitterIterations) {
// Perform upward jittering for a specified number of iterations
for (let i = 0; i < particleArray.length; i++) {
// Move particles randomly within a specific range horizontally (x-axis)
// Move particles upwards by adjusting the y-coordinate (subtracting from y-axis)
particleArray[i].x += random(-jitterAmount, jitterAmount) * 4;
particleArray[i].y -= random(0, jitterAmount) * 4; // Adjusting y coordinate to move particles upwards
}
iterations++; // Increment the iteration count
// Use setTimeout to call the jitterUpwards function recursively after a delay of 10 milliseconds (adjustable)
setTimeout(() => jitterUpwards(particleArray), 10); // Adjust timeout as needed for speed
}
}
// Start the upward jittering process for the current set of particles
jitterUpwards(this.particles);
}
jitterParticlesCloser() {
let iterations = 0;
let prevPositions = [];
function jitterCloser(particleArray) {
if (iterations < jitterIterations) {
for (let i = 0; i < particleArray.length; i++) {
prevPositions.push([particleArray[i].x, particleArray[i].y]);
let xOffset = random(-jitterAmount, jitterAmount) * 0.4;
let yOffset = random(-jitterAmount, jitterAmount) * 0.4;
particleArray[i].x += xOffset * 0.5; // Adjust x-coordinate to bring particles closer
particleArray[i].y += yOffset * 0.5; // Adjust y-coordinate to bring particles closer
}
iterations++;
setTimeout(() => jitterCloser(particleArray), 10); // Adjust timeout for speed of jittering
}
}
jitterCloser(this.particles);
this.beforeMovement = prevPositions;
}
jitterParticlesTowardsCenter() {
let iterations = 0;
let totalIterations = 7;
let jitterAmnt = 5;
const convergenceRate = 0.05; // Rate at which particles converge towards the center
// console.log("woooo big function");
function jitter(particleArray, centralY, centralX) {
// console.log("woooo function");
if (iterations < totalIterations) {
// console.log("woooo iterations");
for (let i = 0; i < particleArray.length; i++) {
// Calculate distance to the center
const distanceX = centralX - particleArray[i].x;
const distanceY = centralY - particleArray[i].y;
// Move particles closer together
particleArray[i].x += random(-jitterAmnt, jitterAmnt) * 6;
particleArray[i].y += random(-jitterAmnt, jitterAmnt) * 6;
// Move particles towards the center
particleArray[i].x += distanceX * convergenceRate;
particleArray[i].y += distanceY * convergenceRate;
if (iterations == 0) {
let changeColor = random(0, 1);
if (changeColor > 0.5) {
particleArray[i].particleColor = convergenceColor;
particleArray[i].strokeColor = convergenceStroke;
}
}
}
iterations++;
setTimeout(() => jitter(particleArray, centralX, centralY), 0.5); // Adjust timeout as needed for speed
}
}
jitter(this.particles, this.centerX, this.centerY);
}
explodeParticles() {
let iterations = 0;
const explodeIterations = 30; // Adjust the number of iterations for the explosion
function explode(particleArray) {
if (iterations < explodeIterations) {
// Calculate the center of the galaxy (average position of all particles)
let centerX = 0;
let centerY = 0;
for (let i = 0; i < particleArray.length; i++) {
centerX += particleArray[i].x;
centerY += particleArray[i].y;
}
centerX /= particleArray.length;
centerY /= particleArray.length;
for (let i = 0; i < particleArray.length; i++) {
// Move particles away from the center of the galaxy
let deltaX = (particleArray[i].x - centerX) / 15;
let deltaY = (particleArray[i].y - centerY) / 15;
// Adjust the particles' positions
particleArray[i].x += deltaX * 0.1; // Adjust the factor to control the speed of explosion
particleArray[i].y += deltaY * 0.1; // Adjust the factor to control the speed of explosion
}
iterations++;
setTimeout(() => explode(particleArray), 10); // Adjust timeout as needed
}
}
explode(this.particles);
}
moveParticlesInHeart() {
let iterations = 0;
let prevPositions = [];
let heartIterations = 30;
function moveTowardsHeart(particleArray, heartParticles) {
if (iterations < heartIterations) {
for (let i = 0; i < heartParticles.length; i++) {
prevPositions.push([particleArray[i].x, particleArray[i].y]);
// Calculate the movement towards the heart shape
let targetX = heartParticles[i].x;
let targetY = heartParticles[i].y;
// Update particle positions towards the heart shape
particleArray[i].x += (targetX - particleArray[i].x) * 0.4; // Adjust the animation speed as needed
particleArray[i].y += (targetY - particleArray[i].y) * 0.4; // Adjust the animation speed as needed
particleArray[i].particleColor = heartColor; // Change the color to red during animation
particleArray[i].strokeColor = heartStroke; // Change the stroke color to blue during animation
}
iterations++;
setTimeout(() => moveTowardsHeart(particleArray, heartParticles), 10); // Adjust timeout as needed for speed
}
}
let heartParticles = []; // Define heart shape particles here
// Calculate heart shape particles as before
let spacing = 15; // Adjust this for the heart shape
for (let angle = 0; angle < TWO_PI; angle += 0.1) {
// Calculate x and y coordinates for the heart shape using mathematical functions
let x = 16 * pow(sin(angle), 3);
let y = -(
(
13 * cos(angle) - // First circular pattern
5 * cos(2 * angle) - // Second circular pattern with twice the frequency
2 * cos(3 * angle) - // Third circular pattern with three times the frequency
cos(4 * angle)
) // Fourth circular pattern with four times the frequency
);
// Scale the coordinates by a spacing factor
x *= spacing;
y *= spacing;
// Shift the heart shape to the center of the canvas
x += width / 2;
y += height / 2;
// Store the calculated x and y coordinates as a vector in the heartParticles array
heartParticles.push(createVector(x, y));
}
moveTowardsHeart(this.particles, heartParticles);
this.beforeMovement = prevPositions;
let center = calculateCenter(this.particles);
this.centerX = center[0];
this.centerY = center[1];
}
moveParticlesInSpiral() {
let iterations = 0;
let prevPositions = [];
let spiralIterations = 35;
function moveTowardsSpiral(particleArray, spiralParticles) {
if (iterations < spiralIterations) {
for (let i = 0; i < spiralParticles.length; i++) {
prevPositions.push([particleArray[i].x, particleArray[i].y]);
// Calculate the movement towards the spiral shape
let targetX = spiralParticles[i].x;
let targetY = spiralParticles[i].y;
// Update particle positions towards the spiral shape
particleArray[i].x += (targetX - particleArray[i].x) * 0.4; // Adjust the animation speed as needed
particleArray[i].y += (targetY - particleArray[i].y) * 0.4; // Adjust the animation speed as needed
particleArray[i].particleColor = spiralColor; // Change the color to red during animation
particleArray[i].strokeColor = spiralStroke;
}
iterations++;
setTimeout(() => moveTowardsSpiral(particleArray, spiralParticles), 10); // Adjust timeout as needed for speed
}
}
let spiralParticles = []; // Define spiral shape particles here
// Calculate spiral shape particles
let spacing = 10; // Adjust this for the spiral shape
for (let angle = 0; angle < 6 * PI; angle += 0.1) {
// Calculate x and y coordinates for the spiral shape using trigonometric functions
let x = angle * cos(angle) * spacing + width / 2;
let y = angle * sin(angle) * spacing + height / 2;
spiralParticles.push(createVector(x, y));
}
moveTowardsSpiral(this.particles, spiralParticles);
this.beforeMovement = prevPositions;
let center = calculateCenter(this.particles);
this.centerX = center[0];
this.centerY = center[1];
}
//function to actually draw the galaxy
drawGalaxy() {
for (let i = 0; i < this.particles.length; i++) {
this.particles[i].createParticle(); // Create a single particle
this.particles[i].joinParticles(this.particles.slice(i)); // Join the created particle with others
}
}
}
sketch.js:
//background music source: https://freesound.org/people/Seth_Makes_Sounds/sounds/701610/
let numParticles = 200;
let nightParticles;
let portConnected = false;
let showStartScreen = true;
let font, font2;
let bgMusic;
//variables to store data from Arduino board
let redAction = 0;
let blueAction = 0;
let yellowAction = 0;
let greenAction = 0;
//flags to check if the action was performed
let redActionPerformed = false;
let yellowActionPerformed = false;
let greenActionPerformed = false;
let blueActionPerformed = false;
let lastInteractionTime = 0;
const idleTimeThreshold = 60000;
let amp;
//start screen for user
function startScreen() {
textFont(font);
textSize(80);
fill("white");
let message = "StarScape";
// console.log(width, height);
let textW = textWidth(message);
let textH = textAscent() + textDescent();
let centerX = width / 2;
let centerY = height / 2;
// Set text alignment to center and display the text
textAlign(CENTER, CENTER);
text(message, centerX, centerY);
textFont(font2);
textSize(20);
let captionX = centerX;
let captionY = centerY + 80;
text("Click anywhere to begin. Click again to restart.", captionX, captionY);
}
function preload() {
soundFormats("mp3", "ogg");
bgMusic = loadSound("/sounds/bgMusic2.mp3");
font = loadFont("fonts/NewYork.otf");
font2 = loadFont("fonts/AppleGaramond-LightItalic.ttf");
}
function setup() {
//responsive canvas set to the dimensions of the window
createCanvas(windowWidth, windowHeight);
//initialize particles
nightParticles = new Galaxy();
//loop the music
bgMusic.loop();
amp = new p5.Amplitude();
}
function draw() {
background("#0f0f0f");
if (showStartScreen) {
startScreen();
} else {
//if red button pressed, particles jitter + scatter
if (redAction && !redActionPerformed) {
nightParticles.jitterParticles();
redActionPerformed = true;
//if yellow button is pressed, particles cluster together
} else if (yellowAction && !yellowActionPerformed) {
nightParticles.jitterParticlesTowardsCenter();
yellowActionPerformed = true;
//if the green button is pressed, particles form a heart
} else if (greenAction && !greenActionPerformed) {
nightParticles.moveParticlesInHeart();
greenActionPerformed = true;
// if blue button, the particles form a spiral
} else if (blueAction && !blueActionPerformed) {
nightParticles.moveParticlesInSpiral();
blueActionPerformed = true;
}
//the particles are continuously rotating, with a speed proportional to the amplitude of the music
let vol = amp.getLevel();
let pSpeed = map(vol, 0, 1, 0, 0.005);
nightParticles.rotateGalaxy(pSpeed);
nightParticles.drawGalaxy();
}
}
function readSerial(data) {
////////////////////////////////////
//READ FROM ARDUINO HERE
////////////////////////////////////
if (data != null) {
// make sure there is actually a message
// split the message
let fromArduino = split(trim(data), ",");
// console.log(fromArduino);
// if the right length, then proceed
if (fromArduino.length == 4) {
// only store values here
// do everything with those values in the main draw loop
// We take the string we get from Arduino and explicitly
// convert it to a number by using int()
redAction = int(fromArduino[0]);
yellowAction = int(fromArduino[1]);
greenAction = int(fromArduino[2]);
blueAction = int(fromArduino[3]);
//reset the actionsPerformed to false everytime data is read from the arduino
redActionPerformed = false;
yellowActionPerformed = false;
greenActionPerformed = false;
blueActionPerformed = false;
}
//////////////////////////////////
//SEND TO ARDUINO HERE (handshake)
//////////////////////////////////
let sendToArduino = "\n";
writeSerial(sendToArduino);
}
}
function mousePressed() {
//first, connect the port
if (!portConnected) {
setUpSerial();
portConnected = true;
} else {
//after port is connected, use mouse press to start (and restart)
showStartScreen = !showStartScreen;
//if the game is restarting, create a new instance of Galaxy class
if (showStartScreen) {
nightParticles = new Galaxy();
}
}
}
The Arduino code sets up the serial connection with the p5.js sketch, and then reads the data from the buttons. After detecting a button press, it sends it to the sketch. After sending it to the sketch, it triggers a light sequence on the strip. The code in the Arduino IDE:
#include <Adafruit_NeoPixel.h>
#include <avr/power.h>
unsigned long previousMillis = 0;
const long interval =250;
#define PIN_NEO_PIXEL 10 // Arduino pin that connects to NeoPixel
#define NUM_PIXELS 13 // The number of LEDs (pixels) on NeoPixel
#define DELAY_INTERVAL 250 // 250ms pause between each pixel
int arcadeBtnRed = 5;
// int arcadeLEDRed = 13;
int arcadeBtnYellow = 4;
// int arcadeLEDRed = 13;
int arcadeBtnGreen = 3;
int arcadeBtnBlue = 2;
int neoPixelPin = 10;
int redAction = 0;
int blueAction = 0;
int yellowAction = 0;
int greenAction = 0;
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800);
// Adafruit_NeoPixel strip = Adafruit_NeoPixel(24, neoPixelPin, NEO_RGBW + NEO_KHZ800);
void heart(){
NeoPixel.clear();
// unsigned long currentMillis = millis();
// turn pixels to green one by one with delay between each pixel
for (int pixel = 0; pixel < NUM_PIXELS; pixel++) { // for each pixel
NeoPixel.setPixelColor(pixel, NeoPixel.Color(255, 50, 150)); // Pink color: Red=255, Green=50, Blue=150
NeoPixel.show(); // send the updated pixel colors to the NeoPixel hardware.
delay(60); // pause between each pixel
}
}
void spiral() {
NeoPixel.clear();
int centerPixel = NUM_PIXELS / 2; // Center of the LED strip
int startPixel = 0; // Starting pixel
for (int i = 0; i < NUM_PIXELS / 2; i++) {
NeoPixel.setPixelColor(startPixel + i, NeoPixel.Color(255, 165, 0)); // Set color to golden (RGB: 255, 215, 0)
NeoPixel.setPixelColor(NUM_PIXELS - 1 - i, NeoPixel.Color(255, 165, 0)); // Set color to golden (RGB: 255, 215, 0)
NeoPixel.show();
delay(100); // Adjust the delay to control the speed of the spiral
// Fade out the previously lit LEDs
NeoPixel.setPixelColor(startPixel + i, NeoPixel.Color(0, 0, 0)); // Turn off the LED
NeoPixel.setPixelColor(NUM_PIXELS - 1 - i, NeoPixel.Color(0, 0, 0)); // Turn off the LED
}
}
void jitterIllusion() {
NeoPixel.clear();
for (int i = 0; i < 50; i++) { // Repeat the jitter effect multiple times
for (int pixel = 0; pixel < NUM_PIXELS; pixel++) { // Loop through each pixel
// Set a random shade of green for each pixel
NeoPixel.setPixelColor(pixel, NeoPixel.Color(0, random(256), 0)); // Random green shades (RGB: 0, random value, 0)
}
NeoPixel.show(); // Show the updated colors
delay(50); // Adjust the delay to control the speed of the jitter illusion
}
}
void gentleWaves() {
NeoPixel.clear();
int duration = 5000; // Duration of the wave effect in milliseconds
int waves = 5; // Number of waves to display
for (int i = 0; i < duration; i += 50) { // Time loop
float phaseShift = (float)i / duration * 2 * PI * waves; // Phase shift based on time for wave effect
for (int pixel = 0; pixel < NUM_PIXELS; pixel++) { // Loop through each pixel
// Calculate a blue value based on a sine wave to create a gentle wave effect
int blueValue = (sin(phaseShift + (float)pixel / NUM_PIXELS * 2 * PI) + 1) * 128; // Adjust the amplitude and offset as needed
NeoPixel.setPixelColor(pixel, NeoPixel.Color(0, 0, blueValue)); // Set the pixel color with varying shades of blue
}
NeoPixel.show(); // Show the updated colors
delay(50); // Adjust the delay to control the speed of the gentle waves
}
}
void allWhite() {
NeoPixel.clear();
for (int pixel = 0; pixel < NUM_PIXELS; pixel++) {
NeoPixel.setPixelColor(pixel, NeoPixel.Color(40, 40, 40)); // Set all pixels to white (RGB: 255, 255, 255)
}
NeoPixel.show(); // Show the updated pixel colors
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
// pinMode(LED_BUILTIN, OUTPUT);
pinMode(arcadeBtnRed, INPUT);
pinMode(arcadeBtnYellow, INPUT);
pinMode(arcadeBtnBlue, INPUT);
pinMode(arcadeBtnGreen, INPUT);
// pinMode(arcadeLEDRed, OUTPUT);
NeoPixel.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
// NeoPixel.clear();
// strip.begin();
// strip.show();
// start the handshake
while (Serial.available() <= 0) {
// digitalWrite(LED_BUILTIN, HIGH); // on/blink while waiting for serial data
Serial.println("0,0,0,0"); // send a starting message
delay(300); // wait 1/3 second
// digitalWrite(LED_BUILTIN, LOW);
// delay(50);
}
}
void loop() {
while (Serial.available()) {
if (Serial.read() == '\n') {
redAction = digitalRead(arcadeBtnRed);
delay(5);
blueAction = digitalRead(arcadeBtnBlue);
delay(5);
greenAction = digitalRead(arcadeBtnGreen);
delay(5);
yellowAction = digitalRead(arcadeBtnYellow);
Serial.print(redAction);
Serial.print(',');
Serial.print(yellowAction);
Serial.print(',');
Serial.print(greenAction);
Serial.print(',');
Serial.println(blueAction);
}
}
if (redAction) {
jitterIllusion();
} else if (yellowAction) {
gentleWaves();
} else if (greenAction) {
heart();
} else if (blueAction) {
spiral();
} else {
allWhite();
}
}
What I’m particularly proud of
I’m proud of the methods in the galaxy class… it took me a while to figure out how to code the movement of the particles in a way that looks satisfying. I’m also proud of the overall aesthetics (most people’s first reactions were to comment on how the sketch *looked* before they even interacted with it).
Reflections and Future Improvements
I love the idea of my piece, but, in its current scope I don’t think its full potential has been realized. I think the concept would be much more impactful if there were much more lights, or if it was designed to be within an art installation where the lights are all around the user (similar to Andrew’s piece). If I were given the opportunity to expand the piece to be as such, it would be closer to the original thing I envisioned. Regardless, I’m happy with how it turned out and though its small, I think it still captures the essence of what I wanted to do.