NEBULA

2019

~2’ wide by 4’ tall

Materials

  • Metal sculpture (acquired from thrift shop)

  • WS2812 LED tape

  • Pixelblaze RGB LED Wifi Controller

  • Battery Pack/USB Wall Power

A dynamic, lighted wall piece that plays swirling light shows of all kinds. This piece has been imbued with a pixelblaze light sensor and custom code by CODYRYANDESIGN that allows it to gradually increase in brightness as the environment dims.

 

A demo of NEBULA reacting to light level changes in the environment.


Below is a rough-draft of the code driving nebula through the pixelblaze microcontroller.

As a day-lit room gradually dims with the setting of the sun, specks of bright starlight twinkle and swell throughout the form of this piece.

// This is a fork of Sparks, 
//  - modified to slow down the sparks
//  - gave them a longer lifetime 
//  - allowed them to loop from one ned to the other

sparkHue = 0; //Variable for gradually shifting through hue value
sparkSaturation = .9; //Variable for setting saturation
numSparks = 1 + (pixelCount); //How many sparks we will generate
decay = 0.99; //The decay rate to be applied to each spark
maxSpeed = 0.08 //The maximum travel speed of a spark
newThreshhold = 0.01 //

export var light;
export var lightAverage;
export var lightPercent;
export var lightMin;
export var lightMax;
export var lightAverageDelta;
export var adjustmentTrigger
export var sparkHue;
export var maxBtn
export var calibratedMax

lightPercent = .1;
lightSum = 0.0;
lightAverage = 0.0;

numLoops = 0;
timer = 0;
calibratedMax = false;
calibratedMin = false;
lightMax = 0.0;
lightMin = 0.0;
maxBtn = touchRead(0)
minBtn = 0


sparks = array(numSparks);
sparkX = array(numSparks);
pixels = array(pixelCount);

export function beforeRender(delta) {


  lightSum = lightSum + light //Store a sum of the last 800ms of light readings.
  timer += delta //Keep track of how many times we loop
  
  if(timer > 400) //so that every 400ms we:
  { 
    if(calibratedMax == 1.0) { //only record the previous light reading after the initial reading
      lightAveragePrev = lightAverage //Record the previous average
      lightAverageDelta = lightAverage - lightAveragePrev //Determine the difference between curr and prev readings
    }
    lightAverage = lightSum/numLoops //Average the lightSum across the total readings taken
      if(!calibratedMax || maxBtn){ //On boot up or manual maxBtn push:
        lightMax = lightAverage; //set maximum threshold to current light level
        calibratedMax = true; //and set our Max calibration flag to True
      }
      if(!calibratedMin){ //On boot up:
        lightMin = -lightMax*PI; //set minimum light reading to 10% of maximum 
        calibratedMin = true; //and set our Min calibration flag to True
      }
      if(minBtn) { //If button is pressed by user:
        lightMin = lightAverage; //set the minimum threshold to current light level (lowest light levels)
      }
    
    lightSum = 0 //At the end of our condition, reset variables
    timer = 0
    numLoops = 0
  }
  //Reverse output values for light to create an inverse map
  lightPercent = InputMap(lightAverage, lightMin, lightMax, 1.0, 0.0); //Comment out this line to set a fixed delta
  delta = clamp(lightPercent, 0.0, 1.0); //CHANGE?
  sparkHue += (random(.002) *triangle(wave(.32)))*delta //Comment out this line to set a fixed color hue
  
  for (i = 0; i < pixelCount; i++)
    pixels[i] = pixels[i]*.99;
  
  for (i = 0; i < numSparks; i++) { //Increment through the sparks
    if (sparks[i] >= -newThreshhold && sparks[i] <= newThreshhold) { 
      sparks[i] = (maxSpeed/2) - random(maxSpeed); //Update maxSpeed randomly
      sparkX[i] = random(pixelCount); //Assign SparkX randomly to a pixel
    }
    
    sparks[i] *= decay; //Update the current sparks value to be 99% of itself
    sparkX[i] += sparks[i] *delta; //Boost the SparkX sparks' value by the product of the current spark and delta 
    
    if (sparkX[i] >= pixelCount) { //If the SparkX sparks' value ever exceeds 277
      sparkX[i] = 0; //Reset it to 0
    }
    
    if (sparkX[i] < 0) { //If the SparkX sparks' value ever drops below 0
      sparkX[i] = pixelCount - 1; //Update the spark's value to 276
    }
    
    pixels[floor(sparkX[i])] += sparks[i]*delta;
  }
  
numLoops++
}

export function render(index) {
  v = pixels[index];
  hsv(sparkHue, sparkSaturation, v*v*v*v*v*v*v)
  
}

// //Map light readings between 0.0 and 1.0
// lightPercent = clamp(InputMap(lightAverage, lightMin, lightMax, 0.0, 1.0), 0.0, 1.0) 

function InputMap(input, in_min, in_max, out_min, out_max) 
{
return (input - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}