Enchanted Forest

Made by Pengyuan Huang and boril1

Found in IoT Ecosystems

The enchanted forest is a IoT ecosystem with interactive LED lightnings designed to activate the forest area in the Tunxis golf club as a new event venue.

0

Intention

Wedding and events venues are a key source of revenue for a variety of destinations-from hotels to golf courses. As a result, a significant emphasis is put on both positioning these destinations as potential wedding/event location and driving repeat business through the delivery of memorable experiences. Due to fickle and ever changing consumer preference, a continuous, customer-centric approach is required to deliver the best experience. Technology, and more specifically, the IoT presents unique opportunities to deliver this experience when implemented in a tasteful and integrated manner.  

0

Context

The objective of this project is to enhance the memorable wedding and other event experience offered by Tunixs Country Club (Connecticut) via introducing the IoT connectivity. There is a forest area locates right beside the wedding pavilion which is currently under utilized. Hence, we hope to activate the forest space, especially at night, as the extension of the current event venue and ultimately as the alternative event center.

0

Process

We started with secondary research on the trend of wedding, especially with the fusion of IoT.  

After that, possible technical precedents were studied in terms of technology and implementation. 

We then conducted the stake holder interview including Loredana M. Soucy, Director of Sales & Banquets At Tunxis. The other is customer-side, Shaina Tsupros, a bride.

The object inventory of various event venues were studied to inspire the choice of item to embed the IoT connectivity. 

At last, we study the event venue and process of Tunxis and created an ecosystem map to visualize the relationship between people, objects, data and more.

  


0

Proposal


Tunxis can charge a premium for events and elevate its brand perception through the expansion of its outdoor area into The Enchanted Forest.

Every bride wants a wedding tailor made for her. At Tunxis, this is what we provide. From a completely customizable experiential dance environment, to a thematically lit enchanted forest, no two weddings will ever be alike. Immerse yourself in the forest and explore into the evening. Whether it be our magic firepit or responsive luminous ceiling!

Our wedding experience is broken down into three zones – the indoor dancing/ceremony area, the enchanted forest, and the after-dinner firepit. This is the order and flow of most weddings but can be customized based on your requirements. 

The indoor dancing area has two distinctive lighting elements that define the space, the primary being an overhead luminous ceiling made of a low-resolution LED panel with integrated motion sensors that allow standardized images to react to the movement of dancers beneath them. The secondary, supporting, element is the traditional AV equipment that is integrated with the ceiling to provide a consistent lighting experience. 

The enchanted forest is like other outdoor lighting experiences but differs in two key ways – it is reactive and utilizes different lighting styles to create distinct zones. The reactive nature is key to the wedding experience and allows the bride and groom to act as guides when ushering the guests into the outdoors after the event. The pathway, lined with LED strips integrated landscaped into the edges of the path, light up when the couple are in proximity and create a chain reaction, setting off other lighting zones within the forest. The distinct zones then add depth the experience, but providing some visual variety to guests as they enjoy a post-ceremony/dance stroll. These zones are created through glass tubes with LED lighting stuck in the ground which create a “grass like” feel, sections of trees lit bottom-up with LED spot lights and other sections of trees that lit top-down with led strips that create a cascading effect.

Finally, to ensure the experience allows for exploration and is not simply a path that guests should follow, we activated the firepit, an informal gathering place at the end of our events. We did this by setting up proximity activated wall washers on the trees around the area that amplify the effects of the visual impact of a fire.
Wow! What an experience!

0

Prototype

A prototype was created to demonstrate the proposed enchanted forest and possibly be used as a test platform to verify the lighting effect before full scale implementation. 

The physical model was constructed of laser cut acrylic board (white, 3mm). The lighting were modeled with various type of LEDs:

Trees: Adafruit Mini Skinny NeoPixel Digital RGB LED Strip - 60 LED/m

Fire pit: NeoPixel Ring - 16 x 5050 RGBW LEDs - Warm White

Path walk: White two pin LED 

Pavilion Ceiling: iPad (with animated display)

The LEDs were all connected to a single Particle Photon. Controlled was done with basic Neopixel library.

0
#include "math.h"
#include "application.h"
#include <neopixel.h>

#define STATE_BLANK 1
#define STATE_START 2
#define STATE_SCENES 3
#define STATE_FIRE 4

#define SCENE_WEDDING 1
#define SCENE_HALLOWEEN 2
#define SCENE_CHRISMAS 3

int state = 1;


// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_PIN1 D2
#define PIXEL_COUNT1 168
#define PIXEL_TYPE1 WS2812B
#define PIXEL_PIN2 D0
#define PIXEL_COUNT2 96
#define PIXEL_TYPE2 SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT1, PIXEL_PIN1, PIXEL_TYPE1);
Adafruit_NeoPixel ring(PIXEL_COUNT2, PIXEL_PIN2, PIXEL_TYPE2);

//fire

#define NUMBER_OF_FLAMES 24 // depends on number of neopixel triplets. 5 for 16 NeoPixel ring. 4 for 12 NeoPixel ring
#define FLAME_WIDTH 4      // How wide are the flames (in LEDs)
#define FLICKER_CHANCE 3   // increase this to increase the chances an individual flame will flicker

uint32_t rez_range = 256*3;
#define D_ false

// console buttons:
struct flame_element{
  int brightness;
  int step;
  int max_brightness;
  long rgb[3];
  byte state;
  } flames[NUMBER_OF_FLAMES];
  
  int new_brightness = 0;
  unsigned long rgb[3]; //reusable temporary array
  uint8_t scaleD_rgb[3];
  byte acc;
 
 #define SCALERVAL 256*3
 const int flamecolors[22][3] = {
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, 0,  0},
{ SCALERVAL, SCALERVAL*.4,  },
{ SCALERVAL, SCALERVAL*.4,  0},
{ SCALERVAL, SCALERVAL*.4,  0},
{ SCALERVAL, SCALERVAL*.4,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  0},
{ SCALERVAL, SCALERVAL*.3,  },
{ SCALERVAL, SCALERVAL*.3,  SCALERVAL}, // white
{ 0, SCALERVAL*.2,  SCALERVAL}, // that one blue flame
{ SCALERVAL,  SCALERVAL*.3,  SCALERVAL*.5}
};

int rainbowBrightness=0;

uint8_t  offset = 0; // Position of spinny eyes
uint32_t color  = 0xffffff; // Start white
//double twinkleTime = 0;
uint32_t prevTime;

int sceneNum = 1;


void setup() {
    
    strip.begin();
    strip.setBrightness(50);
    strip.show(); // Initialize all pixels to 'off'
    ring.begin();
    ring.setBrightness(50);
    ring.show(); // Initialize all pixels to 'off'
    Particle.function("changeState",changeState);
    Particle.function("changeScene",changeScene);
    randomSeed(analogRead(4));
    InitFlames();
    
    prevTime = millis();
    
  }

void loop() {

//Switch state and update display ---------------------------------------------------
    switch (state){
    
        case STATE_BLANK:
            ring.clear();
            ring.show();
            tree();
        break;
        
        case STATE_START:
            ring.clear();
            ring.show();
            rainbow(20);
        break;
        
        case STATE_SCENES:
            ring.clear();
            ring.show();
            scenes();
        break;
        
        case STATE_FIRE:
            strip.clear();
            strip.show();
            //tree();
            fire();
        break;

  }

}

//cloud function to switch case
int changeState (String nextState){
  if (nextState.toInt() == 0 or nextState.toInt()>4 ){
    state = (state + 1) % 5;
  }
  else{
    state = nextState.toInt();
  }
  return state;
}

int changeScene (String nextScene){
  if (nextScene.toInt() == 0 or nextScene.toInt()>3 ){
    sceneNum = (sceneNum + 1) % 4;
  }
  else{
    sceneNum = nextScene.toInt();
  }
  return sceneNum;
}

int ledNum (int treeNum, int height){
    return (treeNum)*7+height;
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    if (rainbowBrightness<150){
        strip.setBrightness(rainbowBrightness);
        if (j%2 ==0){
            rainbowBrightness++;
        }
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

void tree (){
    
    for (int i = 0; i<24; i++){
        for(int j = 0; j<7; j++){
            if (j<3){
                strip.setPixelColor(ledNum(i,j), 255,100,22);
            }
            else{
                strip.setPixelColor(ledNum(i,j), 50, 255,0);
            }
        }
    }
    strip.show();
}

void fire(){
    for(byte flame_count=0; flame_count<NUMBER_OF_FLAMES; flame_count++) {
    switch(flames[flame_count].state){
      case 0: // reset
        CreateNewFlame(flame_count);
      break;
      
      case 1: //increasing
        new_brightness = flames[flame_count].brightness + flames[flame_count].step;
        if (new_brightness > flames[flame_count].max_brightness){
          UpdateFlameColor(flame_count, flames[flame_count].max_brightness);
          flames[flame_count].brightness = flames[flame_count].max_brightness;
          flames[flame_count].step = GetStepSize(); // pick a different speed for flame going out
          flames[flame_count].state = 2;
        } else {
          UpdateFlameColor(flame_count, new_brightness);
          flames[flame_count].brightness = new_brightness;
        }

      break;
      
      
      case 2: //decreasing
        new_brightness = flames[flame_count].brightness - flames[flame_count].step;
        // chance to flicker/rekindle:
        if (random(new_brightness) < FLICKER_CHANCE){
          // rekindle:
          flames[flame_count].state = 1; //increase again
          flames[flame_count].brightness = max(GetMaxBrightness(), flames[flame_count].brightness);
          flames[flame_count].step = GetStepSize(); 
        
        } else {
          if (new_brightness <1){
            flames[flame_count].state = 0; // bottomed out - reset to next flame
            flames[flame_count].brightness = 0;
             UpdateFlameColor(flame_count, 0);
          } else {
            UpdateFlameColor(flame_count, new_brightness);
             flames[flame_count].brightness = new_brightness;
          }
        }
      break;
    }
  
  }
   ring.show();
   delay(50);
}

void InitFlames(){
// Sets initial states in flames array
  for(byte i=0; i<NUMBER_OF_FLAMES; i++) {
    flames[i].state=0;
    
  }
}


void UpdateFlameColor(byte flame_num, int new_brightness){
// 
  uint32_t c = 0;
  uint32_t color_channel_value;
  byte rgb_channel;
  
  new_brightness = min(new_brightness, flames[flame_num].max_brightness);
  

  for(byte rgb_channel=0; rgb_channel<3; rgb_channel++) {
    color_channel_value = flames[flame_num].rgb[rgb_channel];
    color_channel_value = color_channel_value * (uint32_t)new_brightness; // keep it long
    color_channel_value = color_channel_value/(uint32_t)rez_range;
    rgb[rgb_channel] = max(0L,color_channel_value);
  } // step through R G B



  // spread possible values of 0 -768 across 3 pixels
  for(byte sub_pixel=0; sub_pixel<FLAME_WIDTH; sub_pixel++) {
    for(byte i=0; i<3; i++) { // rgb
      acc = rgb[i]/3;
      byte d = rgb[i]%3;
      if (sub_pixel < d){
        acc++;
      }
      scaleD_rgb[i] = acc;
      
    }
    c = ring.Color(scaleD_rgb[1],scaleD_rgb[0], 0 ,0);
    if(scaleD_rgb[1]-scaleD_rgb[0]<5){
    ring.setPixelColor(flame_num * FLAME_WIDTH + sub_pixel, c);
    }
    else{
    ring.setPixelColor(flame_num * FLAME_WIDTH + sub_pixel, 0,0,0,100);
    }
  }
  
}


void CreateNewFlame(byte flame_num){
  flames[flame_num].step = GetStepSize();
 flames[flame_num].max_brightness = GetMaxBrightness();

  flames[flame_num].brightness = 0;
  flames[flame_num].state = 1;
  byte color_index = random(22);
  for(byte i=0; i<3; i++) {
    flames[flame_num].rgb[i] = flamecolors[color_index][i];
  }
 
}

int GetStepSize(){
   return random(70)+1;
}
int GetMaxBrightness(){
  int retVal;
//  retVal = random(rez_range/4) +  random(rez_range/4) + random(rez_range/4) + rez_range/4 +1; // bell curve
//  retVal = random(rez_range*3/4) +  rez_range/4; // flat distribution
    retVal = random(rez_range/2) +  rez_range/2; // brighter flat distribution
    return retVal;
}

/*void twinkle(){
   
    for(int i=0; i<PIXEL_COUNT1; i++) {
      uint32_t c = 0;
      if(((offset + i) & 16) < 2) c = color; // 4 pixels on...
      strip.setPixelColor( i, c); // First eye
      strip.setPixelColor(167-i, c); // Second eye (flipped)
    }
    strip.show();
    offset++;
    delay(50);
 
}*/

void scenes(){
//Switch state and update display ---------------------------------------------------
    switch (sceneNum){
    
        case SCENE_WEDDING:{
            for (int i = 0; i<24; i++){
                for(int j = 0; j<7; j++){
                    if ((i % 2) == 0){
                        strip.setPixelColor(ledNum(i,j), 150,140,140);
                    }
                    else{
                        strip.setPixelColor(ledNum(i,j), 155,100,30);
                    }
            strip.show();
                }
             }
        }
        break;
        
        case SCENE_HALLOWEEN:
        breath();
        break;
        
        case SCENE_CHRISMAS:{
            for (int i = 0; i<24; i++){
                for(int j = 0; j<7; j++){
                    if ((i % 2) == 0){
                        strip.setPixelColor(ledNum(i,j), 255,0,0);
                    }
                    else{
                        strip.setPixelColor(ledNum(i,j), 0,255,50);
                    }
            strip.show();
                }
             }
        }
        break;
  }
}

void breath( ){
  float n = 0.2;
  float val = (exp(sin(millis()/1300.0*M_PI)) - 0.36787944)*108.0;
  //Serial.println( val );
  uint16_t i;
  uint32_t c = strip.Color(n*val, val, 0);
  for(i=0; i< strip.numPixels(); i++) {
    strip.setPixelColor(i, c );
  }
  strip.show();
  delay (10);
}
Click to Expand
0

Reflection

We have study the space and objects involved in the event ecosystem, proposed and prototyped the solution in the short six weeks. The proposal was successfully validated via Guerrilla testing with 20 young adults and via comments during the demo. On the other hand, if the time permits, the potential of this blank lighting canvas should be explored in depth, including the customization process, the collaboration with audio to to bring a real enchanted experience.

x
Share this Project


Focused on
About

The enchanted forest is a IoT ecosystem with interactive LED lightnings designed to activate the forest area in the Tunxis golf club as a new event venue.

Created

December 18th, 2017