Light & Interactivity Week 4 – Light Fixture Ideation

For Week 7’s Light Fixture, Rui and I planned to work together again and make a wall sconce light fixture.

Concept

Our idea was inspired from the game Tic-tac-toe, and we wanted to transform it into a wall sconce that comprised of nine glowing tiles.

The light will come from the back of each tile, projected onto the background fixture that hosts all the tiles, and mediated out to the environment.

Interaction for Brightness Control

There will be three levels of brightness for each tile: None, Weak, and Strong. There will be two ways to control the brightness: Individual Mode and Centralized Mode.

Under the Individual Mode, which is also the default mode, each tile acts on its own. Users can adjust its brightness though consecutive tapping, and the brightness will loop between None -> Weak -> Strong -> None.

Under the Centralized Mode, users will press & hold on the center tile for a while to activate it, and then all the tiles will be turned into Brightness Selection phase. The top row will represent Strong, the middle Weak, and the Bottom None. Then, the user can tap on one of the tiles to apply its brightness to all other tiles.

Material Plan

Rui mentioned that he is interesting concrete texture, so we are planning to experiment with making concrete tiles using modes. For the background, we’re thinking of using wood as the host.

For the lighting source of each tile, we plan to use LED strips, and fix it onto the back of each tile, and power them using 12V DC converted from AC. Peizo sensors might be used to identify user behaviors.

A current estimation of total power required is:

80mA * 20 * 9 * 12V = 172.8W

Diffuser materials are yet to be determined. The challenging part might be making the light that coming out of the strips appear as homogenized as possible, as well as the light between multiple tiles as homogenized as possible.

Light & Interactivity Week 3 – Lighting Shop Observation

This week’s trip to the lighting shops at Bowery street was fun – people have managed to do all sorts of tricks on tweaking the lights to produce hundreds of effects!

Below are some interesting ones I found.

Diffusers

Crystal – a dazzling effect created by tons of crystals surrounded LED strips in the middle.

Stained Glass? .. their yellow & red-ish color adds to the reality effect of the light source.

Bubbles – just adding some air bubbles into the glass diffuser gives it a totally different flavor.

Rasin/Glass..? Irregular diffuser carves light at the curvy edges of different shapes.

I have no clue how this circular rainbow is created.

Triangle – a light beam is bent into 60 degrees in the 3D space by this triangular disffuser.

Double mirror trick – Light is trapped in this tiny, thin space created by two reflective layers, resulting in an seemingly deep pathways. The curvature of the diffuser helps bending the pathways toward the center.

Structures


Some simple but effective structures that create a stage for the light sources.

—-Other—-

Ghost light in a normal shell..?

The light source is actually made of tiny glowing balls lining up in spirals. Also, the reflected world captured in the outer layer of glass is magnificent.

Light & Interactivity Week 2 – Roly-poly Candle

This week’s task is to create the “effect” of a candle. I worked with Rui Wang at ITP and we created a roly-poly candle that can simulate how the candle light would react to its shifting center of gravity when you give it a gentle push.

Ideation

Considering what type of candle to build, we checked several examples online, and found Hayeon Hwang’s brush light. She used a neopixel jewel vertically to create the light of a candle, and used brush hairs to diffuse – this had led to an amazing simulation in terms of the color, texture and shape of the candle light. But since it can be viewed from only one angle, Rui and I, after discussion, agreed that we still want to make a candle that can be seen 360. Eventually, we landed on the idea to make one that no longer stands still on a table — it can wobble, and its light will change with respect to how light moves under a wobbling environment.

Prototype

In considering how to create the effect of a candle, rather than just the candle itself, we broke down the characteristics of the candle light into the following aspects:

  • a constant, steady light base comprised of three colors: a little bit of blue in the bottom core, a red ring surrounding the blue at the bottom, and a gradual transition to bright yellow at the top-outer half
  • occasional flickering
  • occasional hue shifting between the red part and the yellow part
  • occasional deforming of the light shape due to small changes in air turbulence

So we decided to:

  • combine a neopixel ring and a jewel to create this varying color base
  • use timed and random functions to recreate different layers of flickering, hue shifting and deformation

And tested with diffuser:

We also used an accerlerometer to tune the intensity of different pixels so that the overall candle light can respond to the tilting of the candle.

We made a base similar to a rolyl-poly toy as the fixture of the candle:

Final Work

Code

#include <ColorConverter.h>
#include <Adafruit_Circuit_Playground.h>

#include <Interval.h>
#include <Adafruit_NeoPixel.h>

Interval flickerInterval1;
Interval flickerInterval2;
Interval flickerRestoreInterval;
Interval randomDimInterval;
ColorConverter converter;

//switch
float overallIntensityScale = 0;
int ignitionCount = 0;

int dir = 0; //0 = netrual, 1 = tilted north, 5 = tilted south, 9 directions in total, clock-wise
int strongBaseLED = 5;
int weakBaseLED = 0;
int strongUpperLED = 3;
int weakUpperLED = 6;

//base ring
const int neoPixelPin1 = 9;
const int pixelCount1 = 10;
float baseH = 18; // from RGB 255, 83, 12
float baseS = 100;
float baseI = 53;
float baseChange = 0.1;
float upperChange = 0.1;


//upper ring
const int neoPixelPin2 = 10;
const int pixelCount2 = 7;
float upperH = 18;
float upperS = 100;
float upperI = 53;
float coreH = 240;
float coreS = 100;
float coreI = 50;

float baseIntensityScale = 0.7;
float upperIntensityScale = 1;
float coreIntensityScale = 0.3;

float defaultDirIntensity = 10;
float dirIntensity = 100;
float dirChange = 2;

boolean isRandomDim = false;
float randomIntensityOffset = 0;
long randomDimTime;

//float flickerIntensity = 255;
int flickerDim = 0;
boolean flickerRestored = true;
long flickerTime;

// set up upper ring:
Adafruit_CPlay_NeoPixel strip2 = Adafruit_CPlay_NeoPixel(pixelCount2, neoPixelPin2, NEO_GRBW + NEO_KHZ800);

int prevX, prevY, prevZ;

void setup() {

  CircuitPlayground.begin();

  strip2.begin();    // initialize pixel strip
  strip2.clear();    // turn all LEDs off
  Serial.begin(9600);

  //flicer randomly
  flickerInterval1.setInterval(flickerDim1, 7000);
  flickerInterval2.setInterval(flickerDim2, 9890);
  randomDimInterval.setInterval(randomDim, 6000);

}

void loop() {


  //accel--------------------------------------------

  int x = CircuitPlayground.motionX();
  int y = CircuitPlayground.motionY();
  int z = CircuitPlayground.motionZ();

  Serial.print("x: ");
  Serial.print(x);
  Serial.print(" y: ");
  Serial.print(y);
  Serial.print(" z: ");
  Serial.println(z);

  int accel = abs(x - prevX) + abs(y - prevY) + abs(z - prevZ);
  Serial.print("total: ");
  Serial.println(accel);

  prevX = x;
  prevY = y;
  prevZ = z;

  //ignition--------------------------
  if (accel > 0) {
    ignitionCount++;
    Serial.print("ignition count: ");
    Serial.println(ignitionCount);
  }
  if (ignitionCount == 10) {
    overallIntensityScale = 0.5;
  } else if (ignitionCount == 12) {
    overallIntensityScale = 0;
  } else if (ignitionCount == 20) {
    overallIntensityScale = 0.8;
  } else if (ignitionCount == 22) {
    overallIntensityScale = 0;
  } else if (ignitionCount >= 30) {
    overallIntensityScale = 1;
  }

  //direction control--------------------

  if (x <= -2 && y == 0) {
    dir = 1;
  } else if (x <= -2 && y >= 2) {
    dir = 2;
  } else if (x == 2 && y >= 2) {
    dir = 3;
  } else if (x >= 2 && y >= 2) {
    dir = 4;
  } else if (x >= 2 && y == 0) {
    dir = 5;
  } else if (x >= 2 && y <= -2) {
    dir = 6;
  } else if (x == 0 && y <= -2) {
    dir = 7;
  } else if (x <= -2 && y <= -2) {
    dir = 8;
  } else {
    dir = 0;
  }
  //--------------------------------------------
  //flickerDim
  if (!flickerRestored) {
    Serial.println("needs restore!");
    //    flickerRestoreInterval.setTimeout(flickerRestore, 49);
    if (millis() > flickerTime + 49) {
      flickerRestore();
    }
  }

  //randomDim
  if (isRandomDim) {
    Serial.println("needs restore random!");
    randomIntensityOffset = random(0, 3);
    if (millis() > randomDimTime + 2500) {
      randomDimRestore();
    }
  }


  switch (dir) {
    case 0:
      //do noting to change the strong, weak LED
      break;
    case 1:
      strongBaseLED = 5;
      weakBaseLED = 0;
      strongUpperLED = 3;
      weakUpperLED = 6;
      break;
    case 2:
      strongBaseLED = 3;
      weakBaseLED = 8;
      strongUpperLED = 4;
      weakUpperLED = 1;
      break;
    case 3:
      strongBaseLED = 2;
      weakBaseLED = 7;
      strongUpperLED = 4;
      weakUpperLED = 1;
      break;
    case 4:
      strongBaseLED = 0;
      weakBaseLED = 5;
      strongUpperLED = 5;
      weakUpperLED = 2;
      break;
    case 5:
      strongBaseLED = 0;
      weakBaseLED = 5;
      strongUpperLED = 6;
      weakUpperLED = 3;
      break;
    case 6:
      strongBaseLED = 9;
      weakBaseLED = 4;
      strongUpperLED = 6;
      weakUpperLED = 3;
      break;
    case 7:
      strongBaseLED = 7;
      weakBaseLED = 2;
      strongUpperLED = 1;
      weakUpperLED = 4;
      break;
    case 8:
      strongBaseLED = 6;
      weakBaseLED = 1;
      strongUpperLED = 2;
      weakUpperLED = 5;
      break;

  }

  //change intensity based on direction
  if (dir == 0) {
    if (dirIntensity >= 25) {
      dirIntensity = dirIntensity - dirChange;
    }
  } else {
    if (dirIntensity < 50) {
      dirIntensity = dirIntensity + dirChange;
    }
  }


  delay(2);

  //determine the final RGB value

  RGBColor baseColorStrong = converter.HSItoRGB(baseH, baseS, (dirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
  RGBColor baseColorWeak = converter.HSItoRGB(baseH, baseS, (50 - dirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
  RGBColor baseColorNeutral = converter.HSItoRGB(baseH, baseS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * baseIntensityScale * overallIntensityScale);
  RGBColor upperColorStrong = converter.HSItoRGB(upperH, upperS, (dirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
  RGBColor upperColorWeak = converter.HSItoRGB(upperH, upperS, 50 - (dirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
  RGBColor upperColorNeutral = converter.HSItoRGB(upperH, upperS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * upperIntensityScale * overallIntensityScale);
  RGBColor coreColor = converter.HSItoRGB(coreH, coreS, (defaultDirIntensity - flickerDim - randomIntensityOffset) * coreIntensityScale * overallIntensityScale);


  //add a constant shifting of HUE for two rings
  baseH = baseH + baseChange;
  if (baseH < 14 || baseH > 22) {
    baseChange = -baseChange;
  }

  upperH = upperH + upperChange;
  if (upperH < 10 || upperH > 22) {
    upperChange = -upperChange;
  }



  for (int pixel = 0; pixel < pixelCount1; pixel++) {
    CircuitPlayground.setBrightness(255);

    //base ring
    if (pixel == strongBaseLED) {
      //      CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * dirIntensity / 255, baseColor.green * orangeScale * flickerIntensity / 255 * dirIntensity / 255, baseColor.blue * orangeScale * flickerIntensity / 255 * dirIntensity / 255 ); // set the color for this pixel
      CircuitPlayground.strip.setPixelColor(pixel, baseColorStrong.red, baseColorStrong.green, baseColorStrong.blue); // set the color for this pixel
    } else if (pixel == weakBaseLED) {
      //      CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * (1 - dirIntensity / 255), baseColor.green * flickerIntensity / 255 * orangeScale * (1 - dirIntensity / 255), baseColor.blue * flickerIntensity / 255 * orangeScale * (1 - dirIntensity / 255)); // set the color for this pixel
      CircuitPlayground.strip.setPixelColor(pixel, baseColorWeak.red, baseColorWeak.green, baseColorWeak.blue); // set the color for this pixel
    } else {
      //      CircuitPlayground.strip.setPixelColor(pixel, baseColor.red * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255, baseColor.green * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255, baseColor.blue * orangeScale * flickerIntensity / 255 * defaultDirIntensity / 255); // set the color for this pixel
      CircuitPlayground.strip.setPixelColor(pixel, baseColorNeutral.red, baseColorNeutral.green, baseColorNeutral.blue); // set the color for this pixel
    }

    //upper ring
    if (pixel == 0) {
      //      strip2.setPixelColor(pixel, 0 * blueScale  * flickerIntensity / 255, 0 * blueScale * flickerIntensity / 255, 255 * blueScale * flickerIntensity / 255); // set inner most blue
      strip2.setPixelColor(pixel, coreColor.red, coreColor.green, coreColor.blue); // set the color for this pixel
    } else if (pixel == strongUpperLED) {
      //      strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * dirIntensity / 255, green * redScale * flickerIntensity / 255 * dirIntensity / 255, blue * redScale * flickerIntensity / 255 * dirIntensity / 255 ); // set the color for this pixel
      strip2.setPixelColor(pixel, upperColorStrong.red, upperColorStrong.green, upperColorStrong.blue); // set the color for this pixel

    } else if (pixel == weakUpperLED) {
      //      strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * (1 - dirIntensity / 255), green * redScale * flickerIntensity / 255  * (1 - dirIntensity / 255), blue * redScale * flickerIntensity / 255 * (1 - dirIntensity / 255)); // set the color for this pixel
      strip2.setPixelColor(pixel, upperColorWeak.red, upperColorWeak.green, upperColorWeak.blue); // set the color for this pixel
    }
    else {
      //      strip2.setPixelColor(pixel, red * redScale * flickerIntensity / 255 * defaultDirIntensity / 255, green * redScale * flickerIntensity / 255 * defaultDirIntensity / 255, blue * redScale * flickerIntensity / 255 * defaultDirIntensity / 255 ); // set the color for this pixel
      strip2.setPixelColor(pixel, upperColorNeutral.red, upperColorNeutral.green, upperColorNeutral.blue); // set the color for this pixel
    }
    //delay(500);

  }
  CircuitPlayground.strip.show();    // refresh the strip
  strip2.show();    // refresh the strip

  flickerInterval1.check();
  flickerInterval2.check();
  flickerRestoreInterval.check();
  randomDimInterval.check();

}

void flickerDim1() {
  flickerDim = 50;
  flickerRestored = false;
  flickerTime = millis();
}

void flickerDim2() {
  flickerDim = 50;
  flickerRestored = false;
  flickerTime = millis();
}

void flickerRestore() {
  Serial.print("restored!");
  flickerDim = 0;
  flickerRestored = true;
}

void randomDim() {
  isRandomDim = true;
  randomDimTime = millis();
}

void randomDimRestore() {
  randomIntensityOffset = 0;
  isRandomDim = false;
}

Light & Interactivity Week 1 – Emotional Fade

This week’s task is to create an interruptable fading LED, and I created a small device that uses fading LEDs to express different emotions in response to different kinetic stimuli – it’s called Pokemon Box Plus (beta).



Ideation

The concept of this piece is inspired by the Pokemon Ball Plus, a Nintendo Switch Gaming Peripheral that works together with the Let’s Go Pikachu/Eevee game. This ball can store data of a pokemon, and it will glow in several ways along with the pokemon’s growl in response to a player’s movement and actions taken on the joycon. After playing it for a while, I found that the number of glowing effects are rather limited, and does not contribute very well to the feeling of “a living pokemon dwelling in the ball”. So, I decided to make my own. Similarly, the pokemon living in my box will react using light, and depending on how a user move the device, it will respond in different ways.

Photo by Game Rant

Prototype

Given the limited time frame, I only made light responses in this beta version and left the sound part aside for the moment. The goal of this version is to use ONLY fading effects to represent, or at least, relate a user to, different kind of emotions. To testify the idea, I set three constraints:

  • Only use 1 color;
  • Multiple LEDs are OK. But they should act in accordance so that they will fade in / fade out at the same time with the same pattern;
  • The spatial layout of the LED(s) should be as simple as possible.

Based on these constraints, I chose two attributes of fade, Speed and Intensity, and picked three combinations to represent three types of emotions – Peaceful/Calm, Joy/Delight, and Excitement/Surprise, as seen below:

For the user input, since I’m creating a dwelling place for a minimized pokemon, I don’t want to use a hard button/a joycon to forcefully make it react. Instead, I decided to stick to the overall movement of the box in the 3D space, to create a effect that seems like “the pokemon senses how its house is moving and responds in different ways”.

To do this, I used an accelerometer to capture the movement of the box, and relate different moving patterns to the emotions indicated above:

  • Gentle, Small Range — Peaceful / Calm
  • Rhythmic, Medium Range — Joy / Delight
  • Free Fall — Excitement / Surprise

Based on the design above, several iterations are made to testify the optimal size of the box, type of diffuser to use, and how well it responses when it sits on a user’s hand.

Final Work

Here’s how it works in action: