Floating Memories - Star Wu, Ariel Jiang - Prof Viola He
Video
Concept and Design
This project aims to create an interactive art installation that evokes feelings of nostalgia and childhood through playful interactions with raising and lowering balls that represent bubbles. The concept connects to how our memories, especially those from childhood, are often linked to simple, joyful activities like playing with bubbles. Inspired by an artwork space installed in TeamLabs Planets "Floating Flower Garden", our idea is to have a servo motor pulling a piece of cloth that has multiple plastic balls that represent bubbles attached. When a person approaches they will be sensed by a distance sensor, triggering the servo motor to rotate winding up the string attached to the cloth. Eventually as the cloth is winded the attached balls will create a gradient of different heights. We also wanted to include images to our project and decided to incorporate a projector to project bubble like images onto the floor like a bubble gun. Lastly including music to create a more immersive environment for the users.
Sketches:
Fabrication and production
Our initial idea was to incorporate translucent plastic balls attached to strings on a grid above the ceiling. Having multiple servo motors pulling each ball up by rotating and are triggered by the distance of a person using a distance sensor. The height of the ball would correspond to the height sensed by the ditance sensor. We intended to incorporate LED lights into each plastic ball so users can interact with a laptop by clicking bubble like shaped to light up a random light. According to our first idea we wanted to use servo motors to windup the string that would pull up the plastic balls. But after doing multiple tests and gaining some advice we realized stepper motors would be a better option for our project. However since our project is a rather large scale installation we required at least 10 stepper motors to pull up each plastic ball. After managing to code and circuit one stepper motor for user testing we realized multiplying it by 10 would be too tedious and unrealistic, so we decided to reduce the stepper motor number to 4. We managed to code for the stepper motors to rotate a set amount to the right and turn back when the distance was below a set distance:
Problems Encountered:
Throughout the process of coding we encountered many problems like the stepper motor continuously spinning or spinning really slow. Also, when we tried to copy and paste our circuits to make multiple the same circuit would not work. Later finding out the problem was caused by the wires itself and switching out the wires fixed the problem. But some of the motors still spun slowly and glitching, which was fixed by switching on and off the small button on the stepper motor to make it spin faster or slower. While working on the stepper motors the Arduino board and motors would heat up and sometimes burn out. So we had to change the amount of voltage that was going through the motors to stop it from heating up.
A big turning point in our project was thinking of how to incorporate processing into our project. Since from user testing we realized we need some indication for the user to know how to interact with our project. But our initial idea of using a laptop conflicted with out projects overall aesthetic and mood. Creating a marker on the ground to indicate for people to step at that location was the best option. So gathering advice we decided to attach the plastic balls to a piece of cloth and by pulling the cloth raises the balls instead of pulling the balls themselves. This way we could add more interactions to our project and we decided to include a projector to project the image from our laptop onto the ground. This way when the distance sensor senses someone the image from processing will project onto the ground. At first we wanted the user to be able to see themselves in the projection so we connected a webcam to our laptop:
But we later realized that it is not necessary and having a black background would suffice. After connecting the stepper motor to the distance sensor we used this code to have the motor rotate from the distance sensor:
#define DIR_PIN2 5
#define STEP_PIN2 6
#define EN_PIN2 7
const int trigPin2 = 12;
const int echoPin2 = 13;
long duration2;
int distance2;
void setup() {
Serial.begin(9600);
pinMode(trigPin1, OUTPUT);
pinMode(echoPin1, INPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(STEP_PIN, OUTPUT);
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
pinMode(trigPin2, OUTPUT);
pinMode(echoPin2, INPUT);
pinMode(DIR_PIN2, OUTPUT);
pinMode(STEP_PIN2, OUTPUT);
pinMode(EN_PIN2, OUTPUT);
digitalWrite(EN_PIN2, LOW);
}
void loop() {
digitalWrite(trigPin1, LOW);
delayMicroseconds(2);
digitalWrite(trigPin1, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin1, LOW);
duration1 = pulseIn(echoPin1, HIGH);
distance1 = duration1 * 0.0344 / 2;
Serial.print("Distance 1: ");
Serial.println(distance1);
digitalWrite(trigPin2, LOW);
delayMicroseconds(2);
digitalWrite(trigPin2, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin2, LOW);
duration2 = pulseIn(echoPin2, HIGH);
distance2 = duration2 * 0.0344 / 2;
Serial.print("Distance 2: ");
Serial.println(distance2);
if (distance1 < 10) {
digitalWrite(DIR_PIN, HIGH);
moveStepper(2500);
delay(1000);
digitalWrite(DIR_PIN, LOW);
moveStepper(2500);
delay(1000);
}
if (distance2 < 10) {
digitalWrite(DIR_PIN2, HIGH);
moveStepper2(2500);
delay(1000);
digitalWrite(DIR_PIN2, LOW);
moveStepper2(2500);
delay(1000);
}
delay(100);
}
void moveStepper(int steps) {
for (int i = 0; i < steps; i++) {
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(2000);
digitalWrite(STEP_PIN, LOW);
delayMicroseconds(2000);
}
}
void moveStepper2(int steps) {
for (int i = 0; i < steps; i++) {
digitalWrite(STEP_PIN2, HIGH);
delayMicroseconds(2000);
digitalWrite(STEP_PIN2, LOW);
delayMicroseconds(2000);
}
}
Next we started to fabricate the main part of our project. We ordered many sized clear plastic balls from ToaBao and considering the weight of the balls would affect the stepper motors rotation we chose four of the smallest sizes to use. First cutting a piece of cloth into the size we wanted and laying out the position of the plastic balls to a even orientation:
We then cut similar length pieces of clear string to attach the balls to the cloth, threading the string through the cloth and securing with hot glue. Next we tried to flip the cloth over so the balls would be underneath, but when we tried to do that we encountered a problem of the balls rolling and entangling with each other. This was time consuming to fix because of how many strings there were and the clear strings made it harder to see how to untangle them. After fixing that problem we attached thicker clear wire to the four corners of the cloth, so we can attach the cloth to the ceiling pipes.
At this point we decided to start attaching our circuit boards and stepper motor to the ceiling. However we encountered many problems after. For example, our distance sensor could not sense the correct distance because of how far it was from the ground. Also the string attached to the stepper motor kept slipping off or getting stuck in the crevice's. we managed to fic the problem encountered with the distance sensor by lowering the sensor closer to the ground to the reading were much more accurate than before. Attaching the sensor to a wooden stick hanging from the ceiling. For a better performance we also updated our code:
#include <AccelStepper.h>
int DIR_PIN = 2;
int STEP_PIN = 3;
int EN_PIN = 4;
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
const int trigPin = 10;
const int echoPin = 11;
long duration;
int distance;
int bubble = 0;
void setup() {
Serial.begin(9600);
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
stepper.setMaxSpeed(2000);
stepper.setAcceleration(600);
stepper.moveTo(3000);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.0344 / 2;
if (distance < 400) {
Serial.println(distance);
bubble = 1;
if (stepper.distanceToGo() == 0)
stepper.moveTo(-stepper.currentPosition());
stepper.run();
} else {
bubble = 0;
Serial.println("not moving");
}
}
In order to fix the stepper motor we had to disassemble the motor many times to change the speed and 3D print an attachment piece to hold the string in place.
Then we had to consider how to incorporate the projector into our project and connect the distance sensor from the Arduino to processing. We ran into a problem with connecting the distance sensor to processing. After many attempts we decided to make processing a separate part from our distance sensor and motor. Creating a stepping pad that would indicate for users to stand on top of the plate triggering a a pressure sensor and sending data to processing:
On processing we coded for different color circles to be moving forward from one point and spreading away from each other similar to a cone shape.:
import processing.serial.*
Serial serialPort;
import processing.sound.*;
SoundFile sound;
int bubble;
int NUM_OF_VALUES_FROM_ARDUINO = 1;
int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO];
Serial myPort;
boolean sensorTriggered = false;
Bubble[] bubbles;
int bubbleCount = 70;
float hueStep = 0.5
void setup() {
size(1920, 1080);
colorMode(HSB, 360, 100, 100);
sound = new SoundFile(this, "windbell.mp3");
fullScreen();
printArray(Serial.list());
serialPort = new Serial(this, "COM7", 9600);
bubbles = new Bubble[bubbleCount];
for (int i = 0; i < bubbleCount; i++) {
bubbles[i] = new Bubble(width / 2, 0, random(30, 80));
}
}
void draw() {
background(0);
if (sound.isPlaying() == false) {
sound.play();
}
getSerialData();
bubble = arduino_values[0];
if (arduino_values[0] == 1) {
for (int i = 0; i < bubbleCount; i++) {
bubbles[i].display();
if (sensorTriggered) {
bubbles[i].burst();
sound.play();
}
bubbles[i].update();
}
} else {
for (int i = 0; i < bubbleCount; i++) {
// bubbles[i].stop();
}
}
}
class Bubble {
float x, y;
float size;
float speed;
float spreadX;
float deceleration;
color col;
Bubble(float x, float y, float size) {
this.x = x;
this.y = y;
this.size = size;
this.speed = random(12, 16);
this.spreadX = random(-4, 4);
this.deceleration = random(0.04, 0.08);
this.col = color(random(200, 360), 50, 100, 150);
}
void display() {
noStroke();
fill(col);
ellipse(x, y, size, size);
}
void update() {
y += speed;
x += spreadX;
speed = max(0.2, speed - deceleration);
if (y > height || x < 0 || x > width) {
reset();
}
}
void burst() {
size += random(-2, 2);
col = color(random(200, 360), random(40, 50), 100, 200);
}
void reset() {
y = 0;
x = width / 2;
size = random(30, 80);
col = color(random(200, 360), 50, 100, 150);
speed = random(8, 12);
deceleration = random(0.04, 0.08);
spreadX = random(-4, 4);
}
}
void getSerialData() {
while (serialPort.available() > 0) {
String in = serialPort.readStringUntil( 10 );
if (in != null) {
print("From Arduino: " + in);
String[] serialInArray = split(trim(in), ",");
if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) {
for (int i=0; i<serialInArray.length; i++) {
arduino_values[i] = int(serialInArray[i]);
}
}
}
}
}
Lastly, we attached the projector to the ceiling and hid any additional wires that would be seen. The projector projecting onto the cloth played well into our project. It made the images look as if it were falling onto the cloth and rolling down. Our final project came out really well in the end:
Conclusion
Our project aimed to create an interactive art installation to evokes feelings of nostalgia and childhood through playful interaction. I believe our project achieved that goal really well. Users interacting with our project whether young or old were able to express curiosity and joy. People interacting would look up to see the balls rising and some tried to reach for the balls as they were pulled away, which were all expected responses that we thought of. Most if not all would try to capture this moment by taking pictures and video. Children were particularly interested and would try to play with the projected bubbles on the ground. Our project aligns with my idea of interaction encouraging users to engage with the computer system by standing under the system and getting active responses from the computer to lower or raise the balls and projecting bubble images. From our final show I've realized our project could be improved on its height limit. Children interacting with our project were too short to trigger the sensors distance. In addition, having the scale of our project increase could give the audience a more immersive experience and incorporate themselves into the project. To further improve, adding lights to the bubbles would also provide a better viewing experience. I've learned from this project to create more from a little. I can achieve certain effects that I want from just simple techniques. I've learned to experiment more with different ways to fabricate my project to get the best result using the smartest method.
Disassembly
Appendix
Arduino code:
#include <AccelStepper.h>
int DIR_PIN = 2;
int STEP_PIN = 3;
int EN_PIN = 4;
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
const int trigPin = 10;
const int echoPin = 11;
long duration;
int distance;
int bubble = 0;
void setup() {
Serial.begin(9600);
pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, LOW);
stepper.setMaxSpeed(2000);
stepper.setAcceleration(600);
stepper.moveTo(3000);
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = duration * 0.0344 / 2;
if (distance < 400) {
Serial.println(distance);
bubble = 1;
if (stepper.distanceToGo() == 0)
stepper.moveTo(-stepper.currentPosition());
stepper.run();
} else {
bubble = 0;
Serial.println("not moving");
}
}
Processing code:
import processing.serial.*
Serial serialPort;
import processing.sound.*;
SoundFile sound;
int bubble;
int NUM_OF_VALUES_FROM_ARDUINO = 1;
int arduino_values[] = new int[NUM_OF_VALUES_FROM_ARDUINO];
Serial myPort;
boolean sensorTriggered = false;
Bubble[] bubbles;
int bubbleCount = 70;
float hueStep = 0.5
void setup() {
size(1920, 1080);
colorMode(HSB, 360, 100, 100);
sound = new SoundFile(this, "windbell.mp3");
fullScreen();
printArray(Serial.list());
serialPort = new Serial(this, "COM7", 9600);
bubbles = new Bubble[bubbleCount];
for (int i = 0; i < bubbleCount; i++) {
bubbles[i] = new Bubble(width / 2, 0, random(30, 80));
}
}
void draw() {
background(0);
if (sound.isPlaying() == false) {
sound.play();
}
getSerialData();
bubble = arduino_values[0];
if (arduino_values[0] == 1) {
for (int i = 0; i < bubbleCount; i++) {
bubbles[i].display();
if (sensorTriggered) {
bubbles[i].burst();
sound.play();
}
bubbles[i].update();
}
} else {
for (int i = 0; i < bubbleCount; i++) {
// bubbles[i].stop();
}
}
}
class Bubble {
float x, y;
float size;
float speed;
float spreadX;
float deceleration;
color col;
Bubble(float x, float y, float size) {
this.x = x;
this.y = y;
this.size = size;
this.speed = random(12, 16);
this.spreadX = random(-4, 4);
this.deceleration = random(0.04, 0.08);
this.col = color(random(200, 360), 50, 100, 150);
}
void display() {
noStroke();
fill(col);
ellipse(x, y, size, size);
}
void update() {
y += speed;
x += spreadX;
speed = max(0.2, speed - deceleration);
if (y > height || x < 0 || x > width) {
reset();
}
}
void burst() {
size += random(-2, 2);
col = color(random(200, 360), random(40, 50), 100, 200);
}
void reset() {
y = 0;
x = width / 2;
size = random(30, 80);
col = color(random(200, 360), 50, 100, 150);
speed = random(8, 12);
deceleration = random(0.04, 0.08);
spreadX = random(-4, 4);
}
}
void getSerialData() {
while (serialPort.available() > 0) {
String in = serialPort.readStringUntil( 10 );
if (in != null) {
print("From Arduino: " + in);
String[] serialInArray = split(trim(in), ",");
if (serialInArray.length == NUM_OF_VALUES_FROM_ARDUINO) {
for (int i=0; i<serialInArray.length; i++) {
arduino_values[i] = int(serialInArray[i]);
}
}
}
}
}
Pictures
Videos