<commit_before>var obj = require('../objects/objects')

// We are simply extending objects
module.exports = obj

function defaultBlocks(name) {
  return "module.exports." + name + ".prototype.get = function(name) { \
    if(typeof this.__inVars[name] != 'undefined') { \
      return this.__inVars[name] \
    } \
    else if(typeof this.__outVars[name] != 'undefined') { \
      return this.__outVars[name] \
    } \
    else if(typeof this.__localVars[name] != 'undefined') { \
      return this.__localVars[name] \
    } \
    else { \
      throw 'Unknown variable ' + name \
    } \
  }; \
  module.exports." + name +".prototype.set = function(name, value) { \
    self = this; \
    if(typeof this.__inVars[name] != 'undefined') { \
      this.__inVars[name] = value; \
      this.__inVarTerms[name].forEach(function(term) { \
        self.__inVarTermValues[name][term] = self.__fuzzFunctions[name][term]() \
      }) \
    } \
    else { \
      throw 'Unknown input variable ' + name \
    } \
  }; \
  module.exports." + name + ".prototype.evaluate = function() { \
  }; ";
}

var internalArrays =
  "this.__inVars = {}; \
  this.__outVars = {}; \
  this.__localVars = {}; \
  this.__fuzzFunctions = {}; \
  this.__defuzzFunctions = {}; \
  this.__defuzzTermFunctions = {}; \
  this.__inVarTerms = {}; \
  this.__outVarTerms = {}; \
  this.__inVarTermValues = {}; \
  this.__outVarTermValues = {};";

obj.FunctionBlock.prototype.toString = function() {
  var result = "module.exports." + this.name + " = function() {"
  result += "self = this;"
  result += internalArrays

  this.varBlocks.forEach(function(block) {
    result += block.toString()
  })

  this.fuzzifyBlocks.forEach(function(block) {
    result += block.toString()
  })

  if(this.defuzzifyBlocks != null) {
    this.defuzzifyBlocks.forEach(function(block) {
      result += block.toString()
    })
  }

  result += "}; "

  result += defaultBlocks(this.name)

  return result
}

obj.VarBlock.prototype.toString = function() {
  result = ""
  this.vars.forEach(function(v) {
    result += v.toString() + " = 0;"
    if(v.type == obj.VarTypes.INPUT) {
      result += v.toString().replace('inVars', 'inVarTerms') + " = [];"
      result += v.toString().replace('inVars', 'fuzzFunctions') + " = {};"
      result += v.toString().replace('inVars', 'inVarTermValues') + " = {};"
    }
    else if(v.type == obj.VarTypes.OUTPUT) {
      result += v.toString().replace('outVars', 'defuzzTermFunctions') + " = {};"
      result += v.toString().replace('outVars', 'outVarTerms') + " = [];"
      result += v.toString().replace('outVars', 'outVarTermValues') + " = {};"
    }
  })
  return result
}

obj.Var.prototype.toString = function() {
  switch(this.type) {
    case obj.VarTypes.INPUT:
      return "self.__inVars." + this.name
    case obj.VarTypes.OUTPUT:
      return "self.__outVars." + this.name;
    case obj.VarTypes.LOCAL:
      return "self.__localVars." + this.name;
  }
}

obj.FuzzifyBlock.prototype.toString = function() {
  self = this
  result = ""
  this.terms.forEach(function(t) {
    result += "this.__inVarTerms." + self.var.name + ".push('" + t.name + "');"
    result += "this.__fuzzFunctions." + self.var.name + "." + t.name + " = function() {"
    result += t.func.toString()
    result += "return calc(" + self.var.toString() + ");"
    result += "};"
  })
  return result
}

obj.DefuzzifyBlock.prototype.toString = function() {
  self = this
  result = ""
  this.terms.forEach(function(t) {
    result += "this.__outVarTerms." + self.var.name + ".push('" + t.name + "');"
    result += "this.__defuzzTermFunctions." + self.var.name + "." + t.name + " = function() {"
    result += t.func.toString()
    result += "return calc(" + self.var.toString() + ");"
    result += "};"
  })
  result += "this.__defuzzFunctions." + self.var.name + " = function() {"
  result += "if(Object.keys(self.__outVarTermValues[" + this.var.name + "]).length == 0) {"
  if(self.defaultVal == null || self.defaultVal.isNC) {
    result += "return 0"
  }
  else {
    result += "return " + self.defaultVal.value
  }
  result += "};"

  switch(self.defuzzMethod) {
    case obj.DefuzzMethods.COG:
      result += "sum = 0, weightedSum = 0, step = (max - min) / 1000; \
      for(i = min; i < max; i += step) { \
        vals = []; \
        self.__outVarTerms[" + this.var.name + "].forEach(function(t) { \
          vals.push(this.__defuzzTermFunctions[" + this.var.name + "][t](i) * self.__outVarTermValues[" + this.var.name + "][t]); \
        }); \
        sum += i; \
        weightedSum += Math.max(vals) * result; \
      }; \
      val = weightedSum / sum;"
      break

    case obj.DefuzzMethods.COGS:
      // @TODO
      break

    case obj.DefuzzMethods.COA:
      // @TODO
      break

    case obj.DefuzzMethods.LM:
      // @TODO
      break

    case obj.DefuzzMethods.RM:
      // @TODO
      break
  }

  if(self.range) {
    result += "if(val < " + self.range.min + ") { \
      return " + self.range.min + " \
    } else if(val > " + self.range.max + ") { \
      return " + self.range.max + " \
    } else { \
      return val \
    }"
  }
  else {
    result += "return val"
  }

  result += "};"
  return result
}

obj.Gauss.prototype.toString = function() {
  // @TODO
}

obj.Gbell.prototype.toString = function() {
  // @TODO
}

obj.Func.prototype.toString = function() {
  return "return " + t.func.func
}

obj.Piecewise.prototype.toString = function() {
  result = "function calc(x) {"
  xs = "xs = ["
  ys = "ys = ["
  min = this.points[0].x
  max = this.points[0].x
  this.points.forEach(function(point) {
    min = Math.min(min, point.x)
    max = Math.max(min, point.x)
    xs += point.x + ","
    ys += point.y + ","
  })
  result += xs + "];" + ys + "];"
  result +=
    "lo = 0, hi = xs.length - 1; \
    while (hi - lo > 1) { \
      mid = (lo + hi) >> 1; \
      if (x < xs[mid]) hi = mid; \
      else lo = mid; \
    } \
    return ys[lo] + (ys[hi] - ys[lo]) / (xs[hi] - xs[lo]) * (x- xs[lo]) \
  };"
  result += "min = " + min + ";"
  result += "max = " + max + ";"
  return result
}

obj.Sigm.prototype.toString = function() {
  // @TODO
}

obj.Singleton.prototype.toString = function() {
  result = "function calc(x) {"
  result += "if(x == "
  if(typeof this.func.value == 'number') {
    result += this.func.value
  } else {
    result += this.func.value.toString()
  }
  result +=
    ") { \
    return 1 \
  } else { \
    return 0 \
  } \
  };"
  return result
}

obj.Trian.prototype.toString = function() {
  result = "function calc(x) {"
  result += "xs = [" + this.min + "," + this.mid + "," + this.high + "];"
  result += "ys = [0, 1, 0];"
  result +=
    "lo = 0, hi = xs.length - 1; \
    while (hi - lo > 1) { \
      mid = (lo + hi) >> 1; \
      if (x < xs[mid]) hi = mid; \
      else lo = mid; \
    } \
    return ys[lo] + (ys[hi] - ys[lo]) / (xs[hi] - xs[lo]) * (x - xs[lo]) \
  };"
  result += "min = " + this.min + ";"
  result += "max = " + this.max + ";"
  return result
}

obj.Trape.prototype.toString = function() {
  // @TODO
}<commit_msg>More on rule block generation<commit_after>var obj = require('../objects/objects')

// We are simply extending objects
module.exports = obj

function defaultBlocks(name) {
  return "module.exports." + name + ".prototype.get = function(name) { \
    if(typeof this.__inVars[name] != 'undefined') { \
      return this.__inVars[name] \
    } \
    else if(typeof this.__outVars[name] != 'undefined') { \
      return this.__outVars[name] \
    } \
    else if(typeof this.__localVars[name] != 'undefined') { \
      return this.__localVars[name] \
    } \
    else { \
      throw 'Unknown variable ' + name \
    } \
  }; \
  module.exports." + name +".prototype.set = function(name, value) { \
    self = this; \
    if(typeof this.__inVars[name] != 'undefined') { \
      this.__inVars[name] = value; \
      this.__inVarTerms[name].forEach(function(term) { \
        self.__inVarTermValues[name][term] = self.__fuzzFunctions[name][term]() \
      }) \
    } \
    else { \
      throw 'Unknown input variable ' + name \
    } \
  };"
}

var internalArrays =
  "this.__inVars = {}; \
  this.__outVars = {}; \
  this.__localVars = {}; \
  this.__fuzzFunctions = {}; \
  this.__defuzzFunctions = {}; \
  this.__defuzzTermFunctions = {}; \
  this.__inVarTerms = {}; \
  this.__outVarTerms = {}; \
  this.__inVarTermValues = {}; \
  this.__outVarTermValues = {};";

obj.FunctionBlock.prototype.toString = function() {
  var result = "module.exports." + this.name + " = function() {"
  result += "self = this;"
  result += internalArrays

  this.varBlocks.forEach(function(block) {
    result += block.toString()
  })

  this.fuzzifyBlocks.forEach(function(block) {
    result += block.toString()
  })

  if(this.defuzzifyBlocks != null) {
    this.defuzzifyBlocks.forEach(function(block) {
      result += block.toString()
    })
  }

  result += "}; "

  result += defaultBlocks(this.name)

  if(this.ruleBlocks != null) {
    result += "module.exports." + name +".prototype.evaluate = function() { "
    this.ruleBlocks.forEach(function(block) {
      result += block.toString()
    })
    result += "};"
  }

  return result
}

obj.VarBlock.prototype.toString = function() {
  result = ""
  this.vars.forEach(function(v) {
    result += v.toString() + " = 0;"
    if(v.type == obj.VarTypes.INPUT) {
      result += v.toString().replace('inVars', 'inVarTerms') + " = [];"
      result += v.toString().replace('inVars', 'fuzzFunctions') + " = {};"
      result += v.toString().replace('inVars', 'inVarTermValues') + " = {};"
    }
    else if(v.type == obj.VarTypes.OUTPUT) {
      result += v.toString().replace('outVars', 'defuzzTermFunctions') + " = {};"
      result += v.toString().replace('outVars', 'outVarTerms') + " = [];"
      result += v.toString().replace('outVars', 'outVarTermValues') + " = {};"
    }
  })
  return result
}

obj.Var.prototype.toString = function() {
  switch(this.type) {
    case obj.VarTypes.INPUT:
      return "self.__inVars." + this.name
    case obj.VarTypes.OUTPUT:
      return "self.__outVars." + this.name;
    case obj.VarTypes.LOCAL:
      return "self.__localVars." + this.name;
  }
}

obj.FuzzifyBlock.prototype.toString = function() {
  self = this
  result = ""
  this.terms.forEach(function(t) {
    result += "this.__inVarTerms." + self.var.name + ".push('" + t.name + "');"
    result += "this.__fuzzFunctions." + self.var.name + "." + t.name + " = function() {"
    result += t.func.toString()
    result += "return calc(" + self.var.toString() + ");"
    result += "};"
  })
  return result
}

obj.DefuzzifyBlock.prototype.toString = function() {
  self = this
  result = ""
  this.terms.forEach(function(t) {
    result += "this.__outVarTerms." + self.var.name + ".push('" + t.name + "');"
    result += "this.__defuzzTermFunctions." + self.var.name + "." + t.name + " = function() {"
    result += t.func.toString()
    result += "return calc(" + self.var.toString() + ");"
    result += "};"
  })
  result += "this.__defuzzFunctions." + self.var.name + " = function() {"
  result += "if(Object.keys(self.__outVarTermValues[" + this.var.name + "]).length == 0) {"
  if(self.defaultVal == null || self.defaultVal.isNC) {
    result += "return 0"
  }
  else {
    result += "return " + self.defaultVal.value
  }
  result += "};"

  switch(self.defuzzMethod) {
    case obj.DefuzzMethods.COG:
      result += "sum = 0, weightedSum = 0, step = (max - min) / 1000; \
      for(i = min; i < max; i += step) { \
        vals = []; \
        self.__outVarTerms[" + this.var.name + "].forEach(function(t) { \
          vals.push(this.__defuzzTermFunctions[" + this.var.name + "][t](i) * self.__outVarTermValues[" + this.var.name + "][t]); \
        }); \
        sum += i; \
        weightedSum += Math.max(vals) * result; \
      }; \
      val = weightedSum / sum;"
      break

    case obj.DefuzzMethods.COGS:
      // @TODO
      break

    case obj.DefuzzMethods.COA:
      // @TODO
      break

    case obj.DefuzzMethods.LM:
      // @TODO
      break

    case obj.DefuzzMethods.RM:
      // @TODO
      break
  }

  if(self.range) {
    result += "if(val < " + self.range.min + ") { \
      return " + self.range.min + " \
    } else if(val > " + self.range.max + ") { \
      return " + self.range.max + " \
    } else { \
      return val \
    }"
  }
  else {
    result += "return val"
  }

  result += "};"
  return result
}

obj.Gauss.prototype.toString = function() {
  // @TODO
}

obj.Gbell.prototype.toString = function() {
  // @TODO
}

obj.Func.prototype.toString = function() {
  return "return " + t.func.func
}

obj.Piecewise.prototype.toString = function() {
  result = "function calc(x) {"
  xs = "xs = ["
  ys = "ys = ["
  min = this.points[0].x
  max = this.points[0].x
  this.points.forEach(function(point) {
    min = Math.min(min, point.x)
    max = Math.max(min, point.x)
    xs += point.x + ","
    ys += point.y + ","
  })
  result += xs + "];" + ys + "];"
  result +=
    "lo = 0, hi = xs.length - 1; \
    while (hi - lo > 1) { \
      mid = (lo + hi) >> 1; \
      if (x < xs[mid]) hi = mid; \
      else lo = mid; \
    } \
    return ys[lo] + (ys[hi] - ys[lo]) / (xs[hi] - xs[lo]) * (x- xs[lo]) \
  };"
  result += "min = " + min + ";"
  result += "max = " + max + ";"
  return result
}

obj.Sigm.prototype.toString = function() {
  // @TODO
}

obj.Singleton.prototype.toString = function() {
  result = "function calc(x) {"
  result += "if(x == "
  if(typeof this.func.value == 'number') {
    result += this.func.value
  } else {
    result += this.func.value.toString()
  }
  result +=
    ") { \
    return 1 \
  } else { \
    return 0 \
  } \
  };"
  return result
}

obj.Trian.prototype.toString = function() {
  result = "function calc(x) {"
  result += "xs = [" + this.min + "," + this.mid + "," + this.high + "];"
  result += "ys = [0, 1, 0];"
  result +=
    "lo = 0, hi = xs.length - 1; \
    while (hi - lo > 1) { \
      mid = (lo + hi) >> 1; \
      if (x < xs[mid]) hi = mid; \
      else lo = mid; \
    } \
    return ys[lo] + (ys[hi] - ys[lo]) / (xs[hi] - xs[lo]) * (x - xs[lo]) \
  };"
  result += "min = " + this.min + ";"
  result += "max = " + this.max + ";"
  return result
}

obj.Trape.prototype.toString = function() {
  // @TODO
}

obj.RuleBlock.prototype.toString = function() {
  result = ""

  switch(this.andOperatorDef) {
    case obj.OperatorFuncs.PROD:
      result += "function and(one, two) { \
        return one * two \
      }; \
      function or(one, two) { \
        return one + two - one * two \
      };"
      break

    case obj.OperatorFuncs.BDIF:
      result += "function and(one, two) { \
        return Math.max(0, one + two - 1) \
      }; \
      function or(one, two) { \
        return Math.min(1, one + two) \
      };"
      break

    case obj.OperatorFuncs.MIN:
    default:
      result += "function and(one, two) { \
        return Math.min(one, two) \
      }; \
      function or(one, two) { \
        return Math.max(one, two) \
      };"
      break
  }

  switch(this.activationMethod) {
    case obj.ActivationMethods.PROD:
      result += "function act(one, two) { \
        return Math.min(one, two) \
      };"
      break

    case obj.ActivationMethods.MIN:
    default:
      result += "function act(one, two) { \
        return one * two \
      };"
      break
  }

  switch(this.accumulationMethod) {
    case obj.AccumulationMethods.BSUM:
      result += "function acc(one, two) { \
        return Math.min(1, one + two) \
      };"
      break

    case obj.AccumulationMethods.NSUM:
      result += "function acc(one, two) { \
        return (one + two) / Math.max(1, Math.max((1 - one) + (1 - two))) \
      };"
      break

    case obj.AccumulationMethods.MAX:
    default:
      result += "function acc(one, two) { \
        return Math.max(one, two) \
      };"
      break
  }

  this.rules.forEach(function(rule) {
    result += rule.toString()
  })

  return result
}

obj.RuleBlock.prototype.toString = function() {
  result = ""

}<|endoftext|>


<commit_before>import { Element as PolymerElement } from '/node_modules/@polymer/polymer/polymer-element.js'
import { html } from '/node_modules/lit-html/lit-html.js'
import { render } from '/node_modules/lit-html/lib/lit-extended.js'
import '/src/components/tap-button/index.js'
import '/src/components/connect-button/index.js'
import '/src/components/connect-bluetooth-button/index.js'
import '/src/components/download-config-button/index.js'
import '/src/components/bpm-meter/index.js'
import '/src/components/channel-grid/index.js'
import '/src/components/device-list/index.js'
import '/src/components/midi-manager/index.js'
import '/src/components/timeline-item/index.js'

import BluetoothManager from '/src/core/BluetoothManager.js'
import USBManager from '/src/core/USBManager.js'
import MidiManager from '/src/core/MidiManager.js'
import StorageManager from '/src/core/StorageManager.js'
import DeviceManager from '/src/devices/DeviceManager.js'
import AnimationManager from '/src/core/AnimationManager.js'
import SceneManager from '/src/core/SceneManager.js'
import Render from '/src/core/Render.js'
import Configuration from '/src/core/Configuration.js'

function flatten(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
  }, []);
}

class AppContent extends PolymerElement {

  constructor() {
    super()
    this.storage = new StorageManager()
    this.configuration = new Configuration({
      storage: this.storage,
      restore: true
    })
    this.config = this.configuration
    window.configuration = this.configuration

    this.bluetooth = new BluetoothManager({ configuration: this.configuration })

    this.usb = new USBManager({ configuration: this.configuration })
    window.usbManager = this.usb

    // Manage connected MIDI devices
    this.midiManager = new MidiManager({ config: this.config.getConfig() })
    // Expose it globally so we can use it in the console
    window.midiManager = this.midiManager

    this.deviceManager = new DeviceManager({
      configuration: this.configuration,
      output: this.usb.output
    })
    this.deviceManager.register()

    // Initialize all animations
    this.animationManager = new AnimationManager({
      config: this.config.getConfig(),
      deviceManager: this.deviceManager
    })
    this.animationManager.register()

    // Initialize all scenes
    this.sceneManager = new SceneManager({
      config: this.config.getConfig(),
      animationManager: this.animationManager
    })
    this.sceneManager.register()

    // Manage playback of all animations, scenes, timelines
    this.render = new Render({
      config: this.config,
      dmxUsbInterface: this.usb,
      sceneManager: this.sceneManager
    })
    // this.render.start(this.config.getConfig().global.fps)

    this.deviceManager.reset()

    const bpm = this.configuration.getConfig().global.bpm || 120
    const measures = 8
    this.state = {
      bpm: bpm,
      measures,
      connected: false,
      bluetoothConnected: false,
      time: new Date(),
      duration: ~~(60 / bpm * 1000 * measures)
    }

    this.dmxList = [...this.deviceManager.list].map((e, i) => {
      const [key, value] = e

      return {
        id: key,
        channel: i,
        bufferOffset: value.instance.bufferOffset,
        deviceId: value.instance.deviceId,
        type: value.type,
        params: Object.keys(value.instance.params).map(x => ({
          param: x,
          channels: value.instance.params[x].channels
        }))
      }
    })
    this.scenesList = [...this.sceneManager.list].map((e, i) => {
      const [key, value] = e
      return {key, value}
    })

    this.dmxList.sort((a, b) => a.bufferOffset - b.bufferOffset)
    // console.log(this.scenesList)
  }

  setState(newState) {
    if (typeof this.state === 'object') {
      Promise.resolve().then(()=> {
        this.state = {...(this.state), ...newState}
      })
    } else {
      throw new Error('no state is available. Please make sure to define an initital state in your constructor')
    }
  }

  connectedCallback() {
    super.connectedCallback()
    this.listen()
    this.setTime()
  }

  setTime(){
    const {time, bpm, measures, duration} = this.state
    const now = new Date()
    const timeCounter = (now - time) / duration
    if (now - time > duration) {
      this.setState({
        time: now,
      })
    }
    this.setState({
      timeCounter
    })
    setTimeout(() => {
      requestAnimationFrame(this.setTime.bind(this))
    }, 0)

    this.runTimeline(timeCounter)
  }

  runTimeline(counter) {
    const values = this.getValues(counter)

    values.forEach(scene => {
      scene.children.forEach(layer => {
        this.render.run()

        layer.children.forEach(animation => {
          animation.animation.values = animation.values
          animation.animation.run()
        })
      })
    })

    // Render all DMX devices into a buffer

  }

  getValues(counter) {
    const items = this.scenesList.map(scene => {
      return {
        id: scene.key,
        children: scene.value.layers.map(layer => {
          return {
            id: layer.layerId,
            devices: layer.devices,
            children: layer.animations.map(animation => {
              const progress = counter * this.state.measures / animation.duration
              const values = animation.timeline.values(progress)
              return {
                id: animation.animationId,
                devices: animation.devices,
                animation: animation,
                values: values
              }
            })
          }
        })
      }
    })

    return items
  }

  ready() {
    super.ready()
  }

  listen() {
    window.addEventListener('USBDriver', event => {
      const {connected} = event.detail
      // Connection status for USB DMX controller
      this.configuration.getConfig().dmxInterface.connected = connected
      this.setState({connected})
    })
  }

  handleTap(e) {
    const {bpm} = e.detail
    const duration = ~~(60 / bpm * 1000 * this.state.measures)
    this.configuration.data.global.bpm = bpm
    this.setState({bpm, duration})
  }

  handleConnect(e) {
    this.usb.enable()
  }

  handleDisconnect(e) {
    this.usb.port.disconnect()
    this.usb.port = null
  }

  handleUpdate(e) {
    const { value, channelId } = e.detail
    this.usb.update(channelId, value)
  }

  handleBluetoothConnect(e) {
    this.bluetooth.enable()
    this.state.bluetoothConnected = true
  }

  handleBluetoothDisconnect(e) {
  }

  handleDownload(e) {
    console.log(window.configuration.data)
  }

  static get template() {
    return `

          <connect-button connected="{{state.connected}}"
                          on-connect="handleConnect"
                          on-disconnect="handleDisconnect"></connect-button>
          <connect-bluetooth-button connected="{{state.bluetoothConnected}}"
                          on-connect="handleBluetoothConnect"
                          on-disconnect="handleBluetoothDisconnect"></connect-bluetooth-button>

          <download-config-button on-download="handleDownload"></download-config-button>

          <bpm-meter bpm="{{state.bpm}}"></bpm-meter>
          <tap-button class="one"
                      on-tap="handleTap"
                      delay="1500"
                      items="2"
                      controllerId="korgnanopad2"
                      partId="button16"
                      ></tap-button>

          <timeline-item scenes="{{scenesList}}"
                         time="{{state.timeCounter}}"
                         duration="{{state.duration}}"
                         bpm="{{state.bpm}}"
                         measure$="{{state.measures}}"></timeline-item>

          <device-list on-update="handleUpdate"
                       list="{{dmxList}}"></device-list>

    `
  }
}

class MyApp extends PolymerElement {
  static get template() {
    return render(html`<app-content></app-content>`, document.body)
  }
}
customElements.define('my-app', MyApp)
customElements.define('app-content', AppContent)
<commit_msg>feat(timeline): add play button<commit_after>import { Element as PolymerElement } from '/node_modules/@polymer/polymer/polymer-element.js'
import { html } from '/node_modules/lit-html/lit-html.js'
import { render } from '/node_modules/lit-html/lib/lit-extended.js'
import '/src/components/tap-button/index.js'
import '/src/components/connect-button/index.js'
import '/src/components/connect-bluetooth-button/index.js'
import '/src/components/download-config-button/index.js'
import '/src/components/bpm-meter/index.js'
import '/src/components/channel-grid/index.js'
import '/src/components/device-list/index.js'
import '/src/components/midi-manager/index.js'
import '/src/components/timeline-item/index.js'

import BluetoothManager from '/src/core/BluetoothManager.js'
import USBManager from '/src/core/USBManager.js'
import MidiManager from '/src/core/MidiManager.js'
import StorageManager from '/src/core/StorageManager.js'
import DeviceManager from '/src/devices/DeviceManager.js'
import AnimationManager from '/src/core/AnimationManager.js'
import SceneManager from '/src/core/SceneManager.js'
import Render from '/src/core/Render.js'
import Configuration from '/src/core/Configuration.js'

function flatten(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
  }, []);
}

class AppContent extends PolymerElement {

  constructor() {
    super()
    this.storage = new StorageManager()
    this.configuration = new Configuration({
      storage: this.storage,
      restore: true
    })
    this.config = this.configuration
    window.configuration = this.configuration

    this.bluetooth = new BluetoothManager({ configuration: this.configuration })

    this.usb = new USBManager({ configuration: this.configuration })
    window.usbManager = this.usb

    // Manage connected MIDI devices
    this.midiManager = new MidiManager({ config: this.config.getConfig() })
    // Expose it globally so we can use it in the console
    window.midiManager = this.midiManager

    this.deviceManager = new DeviceManager({
      configuration: this.configuration,
      output: this.usb.output
    })
    this.deviceManager.register()

    // Initialize all animations
    this.animationManager = new AnimationManager({
      config: this.config.getConfig(),
      deviceManager: this.deviceManager
    })
    this.animationManager.register()

    // Initialize all scenes
    this.sceneManager = new SceneManager({
      config: this.config.getConfig(),
      animationManager: this.animationManager
    })
    this.sceneManager.register()

    // Manage playback of all animations, scenes, timelines
    this.render = new Render({
      config: this.config,
      dmxUsbInterface: this.usb,
      sceneManager: this.sceneManager
    })
    // this.render.start(this.config.getConfig().global.fps)

    this.deviceManager.reset()

    const bpm = this.configuration.getConfig().global.bpm || 120
    const measures = 8
    this.state = {
      bpm: bpm,
      measures,
      connected: false,
      bluetoothConnected: false,
      time: new Date(),
      duration: ~~(60 / bpm * 1000 * measures)
    }

    this.dmxList = [...this.deviceManager.list].map((e, i) => {
      const [key, value] = e

      return {
        id: key,
        channel: i,
        bufferOffset: value.instance.bufferOffset,
        deviceId: value.instance.deviceId,
        type: value.type,
        params: Object.keys(value.instance.params).map(x => ({
          param: x,
          channels: value.instance.params[x].channels
        }))
      }
    })
    this.scenesList = [...this.sceneManager.list].map((e, i) => {
      const [key, value] = e
      return {key, value}
    })

    this.dmxList.sort((a, b) => a.bufferOffset - b.bufferOffset)
    // console.log(this.scenesList)
  }

  setState(newState) {
    if (typeof this.state === 'object') {
      Promise.resolve().then(()=> {
        this.state = {...(this.state), ...newState}
      })
    } else {
      throw new Error('no state is available. Please make sure to define an initital state in your constructor')
    }
  }

  connectedCallback() {
    super.connectedCallback()
    this.listen()
    this.setTime()
  }

  setTime(){
    const {time, bpm, measures, duration} = this.state
    const now = new Date()
    const timeCounter = (now - time) / duration
    if (now - time > duration) {
      this.setState({
        time: now,
      })
    }
    this.setState({
      timeCounter
    })
    setTimeout(() => {
      requestAnimationFrame(this.setTime.bind(this))
    }, 0)

    this.runTimeline(timeCounter)
  }

  runTimeline(counter) {
    const values = this.getValues(counter)

    values.forEach(scene => {
      scene.children.forEach(layer => {
        this.render.run()

        layer.children.forEach(animation => {
          animation.animation.values = animation.values
          animation.animation.run()
        })
      })
    })

    // Render all DMX devices into a buffer

  }

  getValues(counter) {
    const items = this.scenesList.map(scene => {
      return {
        id: scene.key,
        children: scene.value.layers.map(layer => {
          return {
            id: layer.layerId,
            devices: layer.devices,
            children: layer.animations.map(animation => {
              const progress = counter * this.state.measures / animation.duration
              const values = animation.timeline.values(progress)
              return {
                id: animation.animationId,
                devices: animation.devices,
                animation: animation,
                values: values
              }
            })
          }
        })
      }
    })

    return items
  }

  ready() {
    super.ready()
  }

  listen() {
    window.addEventListener('USBDriver', event => {
      const {connected} = event.detail
      // Connection status for USB DMX controller
      this.configuration.getConfig().dmxInterface.connected = connected
      this.setState({connected})
    })
  }

  handleTap(e) {
    const {bpm} = e.detail
    const duration = ~~(60 / bpm * 1000 * this.state.measures)
    this.configuration.data.global.bpm = bpm
    this.setState({bpm, duration})
  }

  handleConnect(e) {
    this.usb.enable()
  }

  handleDisconnect(e) {
    this.usb.port.disconnect()
    this.usb.port = null
  }

  handleUpdate(e) {
    const { value, channelId } = e.detail
    this.usb.update(channelId, value)
  }

  handleBluetoothConnect(e) {
    this.bluetooth.enable()
    this.state.bluetoothConnected = true
  }

  handleBluetoothDisconnect(e) {
  }

  handlePlayTrigger(e) {
    console.log(e)
    this.setState({
      time: new Date(),
      timeCounter: 0
    })
  }

  handleDownload(e) {
    console.log(window.configuration.data)
  }

  static get template() {
    return `

          <button on-click="handlePlayTrigger">Play</button>
          <connect-button connected="{{state.connected}}"
                          on-connect="handleConnect"
                          on-disconnect="handleDisconnect"></connect-button>
          <connect-bluetooth-button connected="{{state.bluetoothConnected}}"
                          on-connect="handleBluetoothConnect"
                          on-disconnect="handleBluetoothDisconnect"></connect-bluetooth-button>

          <download-config-button on-download="handleDownload"></download-config-button>

          <bpm-meter bpm="{{state.bpm}}"></bpm-meter>
          <tap-button class="one"
                      on-tap="handleTap"
                      delay="1500"
                      items="2"
                      controllerId="korgnanopad2"
                      partId="button16"
                      ></tap-button>

          <timeline-item scenes="{{scenesList}}"
                         time="{{state.timeCounter}}"
                         duration="{{state.duration}}"
                         bpm="{{state.bpm}}"
                         measure$="{{state.measures}}"></timeline-item>

          <device-list on-update="handleUpdate"
                       list="{{dmxList}}"></device-list>

    `
  }
}

class MyApp extends PolymerElement {
  static get template() {
    return render(html`<app-content></app-content>`, document.body)
  }
}
customElements.define('my-app', MyApp)
customElements.define('app-content', AppContent)
<|endoftext|>


<commit_before>/**
 * Controller for Components
 *
 * @module
 */

'use strict';

var _ = require('lodash'),
  db = require('./db'),
  uid = require('../uid'),
  files = require('../files'),
  schema = require('../schema'),
  bluebird = require('bluebird'),
  is = require('../assert-is'),
  path = require('path'),
  config = require('config'),
  glob = require('glob'),
  referenceProperty = '_ref';

/**
 * Takes a ref, and returns the component name within it.
 * @param {string} ref
 * @returns {string}
 * @example /components/base  returns base
 * @example /components/text/instances/0  returns text
 * @example /components/image.html  returns image
 */
function getName(ref) {
  var result = /components\/(.+?)[\/\.@]/.exec(ref) || /components\/(.+)/.exec(ref);

  return result && result[1];
}

/**
 * @param ref
 * @param locals
 */
function get(ref, locals) {
  var promise,
    componentModule = files.getComponentModule(getName(ref));

  if (_.isFunction(componentModule)) {
    promise = componentModule(ref, locals);
  } else {
    promise = db.get(ref).then(JSON.parse);
  }

  return is.promise(promise, ref);
}

/**
 * PUT to just :id or @latest writes to both locations and creates a new version.
 * @param {string} ref   Assumes no @version
 * @param {object} data
 * @returns {Promise}
 */
function putLatest(ref, data) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref, value: data },
    { type: 'put', key: ref + '@latest', value: data },
    { type: 'put', key: ref + '@' + uid(), value: data}
  ];
}

/**
 *
 * @param {string} ref   Assumes no @version
 * @param {object} data
 * @returns {Promise}
 */
function putPublished(ref, data) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref + '@published', value: data },
    { type: 'put', key: ref + '@' + uid(), value: data}
  ];
}

/**
 *
 * @param {string} ref  Assumes no @version
 * @param {object} data
 * @param {} tag  unique tag
 * @returns {Promise}
 */
function putTag(ref, data, tag) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref + '@' + tag, value: data }
  ];
}

/**
 *
 * @param ref
 * @param data
 * @returns {Promise}
 */
function putDefaultBehavior(ref, data) {
  let split = ref.split('@'),
    path = split[0],
    version = split[1];

  if (version) {
    switch (version) {
      case 'list':
        throw new Error('Cannot PUT to @list.');
      case 'latest':
        return putLatest(path, data);
      case 'published':
        return putPublished(path, data);
      default:
        return putTag(path, data, version);
    }
  } else {
    return putLatest(path, data);
  }
}

/**
 * @param ref
 * @param data
 */
function put(ref, data) {
  var result,
    componentModule = files.getComponentModule(getName(ref));

  if (componentModule && _.isFunction(componentModule.put)) {
    result = componentModule.put(ref, data);
  } else {
    result = putDefaultBehavior(ref, data);
  }

  return result;
}

/**
 * Clear all of an object properties (in place), not a new object
 *
 * No need to return anything since it's in-place
 *
 * @param {object} obj
 */
function clearOwnProperties(obj) {
  _.forOwn(obj, function (value, key) {
    delete obj[key];
  });
}

/**
 * True if this is a reference object that also has real data in it.
 *
 * Used to determine if that data should be preserved or not.
 *
 * @param {object} obj
 * @returns {boolean}
 */
function isReferencedAndReal(obj) {
  return _.isString(obj[referenceProperty]) && _.size(obj) > 1;
}

/**
 * @param {string} ref
 * @param {object} data
 * @returns {Promise.object}
 */
function cascadingPut(ref, data) {
  //search for _ref with _size greater than 1
  var ops = [],
    deepObjects = _.listDeepObjects(data, isReferencedAndReal);

  //reverse; children should be before parents
  _.each(deepObjects.reverse(), function (obj) {
    var ref = obj[referenceProperty];

    // since children are before parents, no one will see data below them
    ops.push({key: ref, value: _.omit(obj, referenceProperty)});

    // omit cloned 1 level deep and we clear what omit cloned from obj
    //  so the op gets the first level of data, but it's removed from the main obj
    clearOwnProperties(obj);
    obj[referenceProperty] = ref;
  });

  //add the cleaned root object at the end
  ops.push({key: ref, value: data});

  return bluebird.map(ops, function (op) {
    //run each through the normal put, which may or may not hit custom component logic
    return put(op.key, op.value);
  }).then(function (ops) {
    //flatten to list of final batch ops
    return db.batch(_.filter(_.flattenDeep(ops), _.identity));
  }).return(data); //return root object if successful
}

/**
 * Delete component data.
 *
 * Gets old values, so we can return them when the thing is deleted
 * @param {string} ref
 */
function del(ref) {

  //get old values, so we can return them when the thing is deleted.
  return get(ref).then(function (oldData) {
    var promise,
      componentModule = files.getComponentModule(getName(ref));

    if (componentModule && _.isFunction(componentModule.del)) {
      promise = componentModule.del(ref);
    } else {
      promise = db.del(ref);
    }

    return is.promise(promise, ref).return(oldData)
  });
}

/**
 *
 * @param {string} ref
 */
function getSchema(ref) {
  return bluebird.try(function () {
    var componentName = getName(ref);
    return schema.getSchema(files.getComponentPath(componentName));
  });
}

/**
 *
 * @param {string} ref
 * @returns {*}
 */
function getTemplate(ref) {

  // if there are slashes in this name, they've given us a reference like /components/name/instances/id
  if (ref.indexOf('/') !== -1) {
    ref = getName(ref);
  }

  var filePath = files.getComponentPath(ref),
    possibleTemplates;

  if (_.contains(filePath, 'node_modules')) {
    possibleTemplates = [path.join(filePath, require(filePath + '/package.json').template)];
  } else {
    filePath = path.join(filePath, config.get('names.template'));
    possibleTemplates = glob.sync(filePath + '.*');
  }

  if (!possibleTemplates.length) {
    throw new Error('No template files found for ' + filePath);
  }

  // return the first template found
  return possibleTemplates[0];
}

//outsiders can act on components too
module.exports.get = get;
module.exports.put = cascadingPut; //special: could lead to multiple put operations
module.exports.del = del;

//maybe server.js people want to reach in and reuse these?
module.exports.putLatest = putLatest;
module.exports.putPublished = putPublished;
module.exports.putTag = putTag;
module.exports.putDefaultBehavior = putDefaultBehavior;

module.exports.getTemplate = getTemplate;
module.exports.getName = getName;
module.exports.getSchema = getSchema;<commit_msg>fix error when template is missing<commit_after>/**
 * Controller for Components
 *
 * @module
 */

'use strict';

var _ = require('lodash'),
  db = require('./db'),
  uid = require('../uid'),
  files = require('../files'),
  schema = require('../schema'),
  bluebird = require('bluebird'),
  is = require('../assert-is'),
  path = require('path'),
  config = require('config'),
  glob = require('glob'),
  referenceProperty = '_ref';

/**
 * Takes a ref, and returns the component name within it.
 * @param {string} ref
 * @returns {string}
 * @example /components/base  returns base
 * @example /components/text/instances/0  returns text
 * @example /components/image.html  returns image
 */
function getName(ref) {
  var result = /components\/(.+?)[\/\.@]/.exec(ref) || /components\/(.+)/.exec(ref);

  return result && result[1];
}

/**
 * @param ref
 * @param locals
 */
function get(ref, locals) {
  var promise,
    componentModule = files.getComponentModule(getName(ref));

  if (_.isFunction(componentModule)) {
    promise = componentModule(ref, locals);
  } else {
    promise = db.get(ref).then(JSON.parse);
  }

  return is.promise(promise, ref);
}

/**
 * PUT to just :id or @latest writes to both locations and creates a new version.
 * @param {string} ref   Assumes no @version
 * @param {object} data
 * @returns {Promise}
 */
function putLatest(ref, data) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref, value: data },
    { type: 'put', key: ref + '@latest', value: data },
    { type: 'put', key: ref + '@' + uid(), value: data}
  ];
}

/**
 *
 * @param {string} ref   Assumes no @version
 * @param {object} data
 * @returns {Promise}
 */
function putPublished(ref, data) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref + '@published', value: data },
    { type: 'put', key: ref + '@' + uid(), value: data}
  ];
}

/**
 *
 * @param {string} ref  Assumes no @version
 * @param {object} data
 * @param {} tag  unique tag
 * @returns {Promise}
 */
function putTag(ref, data, tag) {
  data = JSON.stringify(data);
  return [
    { type: 'put', key: ref + '@' + tag, value: data }
  ];
}

/**
 *
 * @param ref
 * @param data
 * @returns {Promise}
 */
function putDefaultBehavior(ref, data) {
  let split = ref.split('@'),
    path = split[0],
    version = split[1];

  if (version) {
    switch (version) {
      case 'list':
        throw new Error('Cannot PUT to @list.');
      case 'latest':
        return putLatest(path, data);
      case 'published':
        return putPublished(path, data);
      default:
        return putTag(path, data, version);
    }
  } else {
    return putLatest(path, data);
  }
}

/**
 * @param ref
 * @param data
 */
function put(ref, data) {
  var result,
    componentModule = files.getComponentModule(getName(ref));

  if (componentModule && _.isFunction(componentModule.put)) {
    result = componentModule.put(ref, data);
  } else {
    result = putDefaultBehavior(ref, data);
  }

  return result;
}

/**
 * Clear all of an object properties (in place), not a new object
 *
 * No need to return anything since it's in-place
 *
 * @param {object} obj
 */
function clearOwnProperties(obj) {
  _.forOwn(obj, function (value, key) {
    delete obj[key];
  });
}

/**
 * True if this is a reference object that also has real data in it.
 *
 * Used to determine if that data should be preserved or not.
 *
 * @param {object} obj
 * @returns {boolean}
 */
function isReferencedAndReal(obj) {
  return _.isString(obj[referenceProperty]) && _.size(obj) > 1;
}

/**
 * @param {string} ref
 * @param {object} data
 * @returns {Promise.object}
 */
function cascadingPut(ref, data) {
  //search for _ref with _size greater than 1
  var ops = [],
    deepObjects = _.listDeepObjects(data, isReferencedAndReal);

  //reverse; children should be before parents
  _.each(deepObjects.reverse(), function (obj) {
    var ref = obj[referenceProperty];

    // since children are before parents, no one will see data below them
    ops.push({key: ref, value: _.omit(obj, referenceProperty)});

    // omit cloned 1 level deep and we clear what omit cloned from obj
    //  so the op gets the first level of data, but it's removed from the main obj
    clearOwnProperties(obj);
    obj[referenceProperty] = ref;
  });

  //add the cleaned root object at the end
  ops.push({key: ref, value: data});

  return bluebird.map(ops, function (op) {
    //run each through the normal put, which may or may not hit custom component logic
    return put(op.key, op.value);
  }).then(function (ops) {
    //flatten to list of final batch ops
    return db.batch(_.filter(_.flattenDeep(ops), _.identity));
  }).return(data); //return root object if successful
}

/**
 * Delete component data.
 *
 * Gets old values, so we can return them when the thing is deleted
 * @param {string} ref
 */
function del(ref) {

  //get old values, so we can return them when the thing is deleted.
  return get(ref).then(function (oldData) {
    var promise,
      componentModule = files.getComponentModule(getName(ref));

    if (componentModule && _.isFunction(componentModule.del)) {
      promise = componentModule.del(ref);
    } else {
      promise = db.del(ref);
    }

    return is.promise(promise, ref).return(oldData)
  });
}

/**
 *
 * @param {string} ref
 */
function getSchema(ref) {
  return bluebird.try(function () {
    var componentName = getName(ref);
    return schema.getSchema(files.getComponentPath(componentName));
  });
}

/**
 * Check root of their project, or the 1st level of their node_modules
 *
 * @param {string} ref
 * @returns [string]
 */
function getPossibleTemplates(ref) {
  var filePath = files.getComponentPath(ref);
  if (_.isString(filePath)) {
    if (_.contains(filePath, 'node_modules')) {
      return [path.join(filePath, require(filePath + '/package.json').template)];
    } else {
      filePath = path.join(filePath, config.get('names.template'));
      return glob.sync(filePath + '.*');
    }
  }
  return [];
}

/**
 *
 * @param {string} ref
 * @returns {*}
 */
function getTemplate(ref) {

  // if there are slashes in this name, they've given us a reference like /components/name/instances/id
  if (ref.indexOf('/') !== -1) {
    ref = getName(ref);
  }

  var possibleTemplates = getPossibleTemplates(ref);

  if (!possibleTemplates.length) {
    throw new Error('No template files found for ' + ref);
  }

  // return the first template found
  return possibleTemplates[0];
}

//outsiders can act on components too
module.exports.get = get;
module.exports.put = cascadingPut; //special: could lead to multiple put operations
module.exports.del = del;

//maybe server.js people want to reach in and reuse these?
module.exports.putLatest = putLatest;
module.exports.putPublished = putPublished;
module.exports.putTag = putTag;
module.exports.putDefaultBehavior = putDefaultBehavior;

module.exports.getTemplate = getTemplate;
module.exports.getName = getName;
module.exports.getSchema = getSchema;<|endoftext|>


<commit_before>(function () {"use strict";})();
/**
 * Created by apizzimenti on 7/15/16.
 */

/**
 * @author Anthony Pizzimenti
 *
 * @desc The Message structure handles all the popup messages in the game.
 *
 * @param game {object} Current Phaser game instance.
 * @param size {number} Font size.
 * @param [loc="bottom_left"] {string} Preferred location for messages: can be "bottom_left", "bottom_right",
 * "top_left", or "top_right".
 *
 * @property game {object} Phaser game instance.
 * @property y {number} Height of the game.
 * @property messages {string[]} Message queue.
 * @property fontSize {number} Font size.
 * @property style {object} Message styling.
 * @property style.font {string} Font style.
 * @property style.fill {string} Hexadecimal color value.
 * @property alert {Signal} Signal.
 *
 * @class {object} Message
 * @this Message
 * @constructor
 */

function Message (game, size, loc) {

    this.game = game;
    this.y = this.game.height;
    this.messages = [];
    this.loc = loc;
    this.fontSize = size;
    this.style = {
        font: size + "px Courier",
        fill: "#FFFFFF"
    };

    this.alert = new Phaser.Signal();

    this.alert.add(() => {this._display()});
}

/**
 * @author Anthony Pizzimenti
 *
 * @desc Adds the provided message to the queue.
 *
 * @param message {string} Message to be added to the queue.
 *
 * @this Message
 */

Message.prototype.add = function (message) {
    this.messages.push(message);

    if (this.messages.length <= 1) {
        this.alert.dispatch();
    }
};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Displays the message at the front of the queue on the screen for five seconds, with in and out tweens.
 *
 * @private
 *
 * @this Message
 */

Message.prototype._display = function () {

    var str = this._format(this.messages[0]),
        tween;

    this.text = this.game.add.text(str.x, str.y, str.msg, this.style);

    this.text.alpha = 0;
    this.text.fixedToCamera = true;

    this.game.add.tween(this.text).to({alpha: 1}, 500, Phaser.Easing.Linear.None, true, 0, 0, false);

    this.game.time.events.add(Phaser.Timer.SECOND * 3, () => {
        tween = this.game.add.tween(this.text).to({alpha: 0}, 500, Phaser.Easing.Linear.None, true, 0, 0, false);

        tween.onComplete.add(() => {
            this.messages = this.messages.slice(1, this.messages.length);
            if (this.messages.length !== 0) {
                this.alert.dispatch();
            }
        })
    });

};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Formats messages so they are 60 characters wide.
 *
 * @private
 *
 * @param message {string} Message to be formatted.
 *
 * @returns {{x: number, y: number, msg: string}} Width and height of Phaser message area; formatted string.
 *
 * @todo Format according to where words are, not just the 60th character.
 */

Message.prototype._format = function (message) {

    var last = 0,
        strs = [],
        dist = message.length % 60 ? message.length - (message.length % 60) : 0,
        vals = {};

    for (var i = 0; i < message.length; i++) {

        if (!(i % 60) && i !== 0) {
            strs.push(message.slice(last, i) + "\n");
            last = i;
        } else if (dist === 0 && i === message.length - 1) {
            strs.push(message.slice(last, message.length));
        } else if (i > dist && dist !== 0) {
            strs.push(message.slice(last, message.length));
            break;
        }
    }

    switch (this.loc) {

        case "bottom_left": default:
            vals.x = 10;
            vals.y = this.y - (strs.length * this.fontSize * 1.8);
            break;

        case "top_left":
            vals.x = 10;
            vals.y = strs.length * (this.fontSize * 1.8);
            break;

        case "top_right":
            vals.x = this.game.width - 540;
            vals.y = 10;
            break;

        case "bottom_right":
            vals.x = this.game.width - 540;
            vals.y = this.y - (strs.length * this.fontSize * 1.8);
            break;
    }

    vals.msg = strs.join(" ");
    return vals;
};
<commit_msg>Message<commit_after>(function () {"use strict";})();
/**
 * Created by apizzimenti on 7/15/16.
 */

/**
 * @author Anthony Pizzimenti
 *
 * @desc The Message structure handles all the popup messages in the game.
 *
 * @param game {object} Current Phaser game instance.
 * @param size {number} Font size.
 * @param [loc="bottom_left"] {string} Preferred location for messages: can be "bottom_left", "bottom_right",
 * "top_left", or "top_right".
 *
 * @property game {object} Phaser game instance.
 * @property y {number} Height of the game.
 * @property messages {string[]} Message queue.
 * @property fontSize {number} Font size.
 * @property style {object} Message styling.
 * @property style.font {string} Font style.
 * @property style.fill {string} Hexadecimal color value.
 * @property alert {Signal} Signal.
 *
 * @class {object} Message
 * @this Message
 * @constructor
 */

function Message (game, size, loc) {

    this.game = game;
    this.y = this.game.height;
    this.messages = [];
    this.loc = loc;
    this.fontSize = size;

    this.style = {
        font: size + "px Courier",
        fill: "#FFFFFF"
    };

    // create Phaser signal dispatcher
    this.alert = new Phaser.Signal();

    // instantiate new events
    this.alert.add(() => {this._display()});
}

/**
 * @author Anthony Pizzimenti
 *
 * @desc Adds the provided message to the queue.
 *
 * @param message {string} Message to be added to the queue.
 *
 * @this Message
 */

Message.prototype.add = function (message) {
    this.messages.push(message);

    if (this.messages.length <= 1) {
        this.alert.dispatch();
    }
};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Displays the message at the front of the queue on the screen for five seconds, with in and out tweens.
 *
 * @private
 *
 * @this Message
 */

Message.prototype._display = function () {

        // correctly format message
    var str = this._format(this.messages[0]),
        tween;

    // add message text
    this.text = this.game.add.text(str.x, str.y, str.msg, this.style);

    this.text.alpha = 0;
    this.text.fixedToCamera = true;

    // tween the text in
    this.game.add.tween(this.text).to({alpha: 1}, 500, Phaser.Easing.Linear.None, true, 0, 0, false);

    this.game.time.events.add(Phaser.Timer.SECOND * 3, () => {

        // tween the text out
        tween = this.game.add.tween(this.text).to({alpha: 0}, 500, Phaser.Easing.Linear.None, true, 0, 0, false);

        // when the above tween is completed, reset the queue and dispactch the next message
        tween.onComplete.add(() => {
            this.messages = this.messages.slice(1, this.messages.length);
            if (this.messages.length !== 0) {
                this.alert.dispatch();
            }
        })
    });

};

/**
 * @author Anthony Pizzimenti
 *
 * @desc Formats messages so they are 60 characters wide.
 *
 * @private
 *
 * @param message {string} Message to be formatted.
 *
 * @returns {{x: number, y: number, msg: string}} Width and height of Phaser message area; formatted string.
 *
 * @todo Format according to where words are, not just the 60th character.
 */

Message.prototype._format = function (message) {

    var last = 0,
        strs = [],
        dist = message.length % 60 ? message.length - (message.length % 60) : 0,
        vals = {};

    for (var i = 0; i < message.length; i++) {

        if (!(i % 60) && i !== 0) {
            strs.push(message.slice(last, i) + "\n");
            last = i;
        } else if (dist === 0 && i === message.length - 1) {
            strs.push(message.slice(last, message.length));
        } else if (i > dist && dist !== 0) {
            strs.push(message.slice(last, message.length));
            break;
        }
    }

    switch (this.loc) {

        case "bottom_left": default:
            vals.x = 10;
            vals.y = this.y - (strs.length * this.fontSize * 1.8);
            break;

        case "top_left":
            vals.x = 10;
            vals.y = strs.length * (this.fontSize * 1.8);
            break;

        case "top_right":
            vals.x = this.game.width - 540;
            vals.y = 10;
            break;

        case "bottom_right":
            vals.x = this.game.width - 540;
            vals.y = this.y - (strs.length * this.fontSize * 1.8);
            break;
    }

    vals.msg = strs.join(" ");
    return vals;
};
<|endoftext|>


<commit_before>(function(d3, fc) {
    'use strict';

    fc.series.multi = function() {

        var multi = function(selection) {

            selection.each(function(data) {

                var container = d3.select(this);

                var g = container.selectAll('g.multi-outer')
                    .data(multi.series.value);

                g.enter()
                    .append('g')
                    .attr('class', 'multi-outer')
                    .append('g')
                    .attr('class', 'multi-inner');

                g.exit()
                    .remove();

                g.select('g.multi-inner')
                    .each(function() {

                        var series = d3.select(this.parentNode)
                            .datum()
                            .xScale(multi.xScale.value)
                            .yScale(multi.yScale.value);

                        d3.select(this)
                            .datum(multi.mapping.value(data, series))
                            .call(series);
                    });
            });
        };

        multi.xScale = fc.utilities.property(d3.time.scale());
        multi.yScale = fc.utilities.property(d3.scale.linear());
        multi.series = fc.utilities.property([]);
        multi.mapping = fc.utilities.property(fc.utilities.fn.identity);

        return multi;
    };
}(d3, fc));<commit_msg>Allow multi to work if scales are referred to as x/y<commit_after>(function(d3, fc) {
    'use strict';

    fc.series.multi = function() {

        var multi = function(selection) {

            selection.each(function(data) {

                var container = d3.select(this);

                var g = container.selectAll('g.multi-outer')
                    .data(multi.series.value);

                g.enter()
                    .append('g')
                    .attr('class', 'multi-outer')
                    .append('g')
                    .attr('class', 'multi-inner');

                g.exit()
                    .remove();

                g.select('g.multi-inner')
                    .each(function() {

                        var series = d3.select(this.parentNode)
                            .datum();

                        (series.xScale || series.x).call(series, multi.xScale.value);
                        (series.yScale || series.y).call(series, multi.yScale.value);

                        d3.select(this)
                            .datum(multi.mapping.value(data, series))
                            .call(series);
                    });
            });
        };

        multi.xScale = fc.utilities.property(d3.time.scale());
        multi.yScale = fc.utilities.property(d3.scale.linear());
        multi.series = fc.utilities.property([]);
        multi.mapping = fc.utilities.property(fc.utilities.fn.identity);

        return multi;
    };
}(d3, fc));<|endoftext|>


<commit_before>'use strict';

// spec.js
describe('my app', function () {
    it('should have a title', function () {
        browser.get('index.html');

        expect(browser.getTitle()).toEqual('Temperature Sensors');
    });

    it('should automatically redirect to /devices when location hash/fragment is empty', function () {
        browser.get('index.html');

        expect(browser.getLocationAbsUrl()).toMatch('/devices');
    });

    it('should have a version', function () {
        browser.get('index.html');

        expect(element.all(by.css('span.app-version')).count()).toBe(1);
    });

    describe('devices', function () {
        browser.get('index.html#/devices');

        it('should render devices when user navigates to /devices', function () {
            expect(element.all(by.css('p.navbar-text')).first().getText()).
                toMatch(/Temperature Monitors/);
        });
    });

    describe('device', function () {
        browser.get('index.html#/devices');

        var device = element.all(by.css('.row.device')).first();
        var a = device.element(by.css('a'));
        var description;
        var loc;
        // FIXME we need q to manage these promises
        device.element(by.css('.description')).getText().then(function(name) {
            description = name;
        });
        a.getAttribute('href').then(function (val) {
            loc = val;
        });
        //device.element(by.css('.description')).getText().then(function (name) {
        //    description = name;
        //});

        it('should have a title that matches the link', function () {
            a.click().then(function() {
                expect(element(by.css('p.navbar-text')).getText()).toEqual(description);
            });
        });
    });
});<commit_msg>test temperature existance<commit_after>'use strict';

// spec.js
describe('my app', function () {
    it('should have a title', function () {
        browser.get('index.html');

        expect(browser.getTitle()).toEqual('Temperature Sensors');
    });

    it('should automatically redirect to /devices when location hash/fragment is empty', function () {
        browser.get('index.html');

        expect(browser.getLocationAbsUrl()).toMatch('/devices');
    });

    it('should have a version', function () {
        browser.get('index.html');

        expect(element.all(by.css('span.app-version')).count()).toBe(1);
    });

    describe('devices', function () {
        browser.get('index.html#/devices');

        it('should render devices when user navigates to /devices', function () {
            expect(element.all(by.css('p.navbar-text')).first().getText()).
                toMatch(/Temperature Monitors/);
        });
    });

    describe('device', function () {
        browser.get('index.html#/devices');

        var device = element.all(by.css('.row.device')).first();
        var a = device.element(by.css('a'));
        var description;
        var loc;
        // FIXME we need q to manage these promises
        device.element(by.css('.description')).getText().then(function(name) {
            description = name;
        });
        a.getAttribute('href').then(function (val) {
            loc = val;
        });
        //device.element(by.css('.description')).getText().then(function (name) {
        //    description = name;
        //});

        it('should have a title that matches the link', function () {
            a.click().then(function() {
                expect(element(by.css('p.navbar-text')).getText()).toEqual(description);
            });
        });

        it('should show the temperature', function () {
            browser.get(loc);

            expect(element(by.css('.temperature-value')).getText()).toBeGreaterThan(-100);
        });
    });
});<|endoftext|>


<commit_before>
//   Copyright 2015 Lawrence Kesteloot
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

(function () {

var TAU = 2*Math.PI;

// For keeping track of simulation time.
var g_time = 0.0;
var g_sim_speed = 1.0;
var g_previous_date = null;

// Simulate the motion of the escapement.
var escapedTime = function (time) {
    var integer = Math.floor(time);
    var fraction = time - integer;

    // Move for the last 1/16 of a second because of the escapement.
    fraction = Math.max(0, fraction*16 - 15);

    return integer + fraction;
};

var keydown = function (event) {
    // Minute hand goes around in one minute instead of one hour.
    if (event.keyCode === 49) { // "1"
        g_sim_speed = 60;
    } else if (event.keyCode === 50) { // "2"
        g_sim_speed = 60*12;
    }
    window.removeEventListener("keydown", keydown);
};

var keyup = function (event) {
    g_sim_speed = 1;
    window.addEventListener("keydown", keydown, false);
};

var closeEnough = function (a, b) {
    return Math.abs(a - b) < 0.0001;
};

var addCylinder = function (cx, cy, z1, z2, r, facets, material, parent) {
    var geometry1 = new THREE.Geometry();
    var geometry2 = new THREE.Geometry();
    var geometry3 = new THREE.Geometry();

    for (var i = 0; i <= facets; i++) {
        var t = i/facets*TAU;
        var x = cx + Math.sin(t)*r;
        var y = cy + Math.cos(t)*r;
        geometry1.vertices.push(new THREE.Vector3(x, y, z1));
        geometry2.vertices.push(new THREE.Vector3(x, y, z2));
        geometry3.vertices.push(new THREE.Vector3(x, y, z1));
        geometry3.vertices.push(new THREE.Vector3(x, y, z2));
    }

    parent.add(new THREE.Line(geometry1, material, THREE.LineStrip));
    parent.add(new THREE.Line(geometry2, material, THREE.LineStrip));
    parent.add(new THREE.Line(geometry3, material, THREE.LinePieces));
};

var findHole = function (holes, x, y) {
    for (var i = 0; i < holes.length; i++) {
        var hole = holes[i];
        if (closeEnough(hole.x, x) && closeEnough(hole.y, y)) {
            return hole;
        }
    }

    return null;
}

var startRendering = function (data) {
    var pieces = data.pieces;

    var scene = new THREE.Scene();

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    window.addEventListener("keydown", keydown, false);
    window.addEventListener("keyup", keyup, false);

    // Each object has two keys:
    //    object3d: the THREE.Object3D object.
    //    piece: the piece (gear, axle, etc.).
    var objects = [];

    // List of holes we've seen. Each item is an object with keys:
    //    x, y: center of holes.
    //    r: smallest radius we've seen.
    //    type: "axle".
    //    attachedTo: piece we're stuck to.
    //    speed: speed of gear with a matching radius.
    // Duplicate (x,y) are removed.
    var holes = [];

    // Lowest and highest Z value.
    var maxZ = -100000;
    var minZ = 100000;

    // Draw each gear.
    for (var gear_index in pieces) {
        var gear = pieces[gear_index];
        var points = gear.points;
        var cx = gear.cx;
        var cy = -gear.cy;
        var cz = gear.cz*data.material_thickness;

        // Create geometry.
        var material = new THREE.LineBasicMaterial({ color: gear.color });
        var gearObject = new THREE.Object3D();

        var geometry1 = new THREE.Geometry();
        var geometry2 = new THREE.Geometry();
        var geometry3 = new THREE.Geometry();
        for (var i = 0; i < points.length; i++) {
            var x = points[i][0];
            var y = points[i][1];
            geometry1.vertices.push(new THREE.Vector3(x, -y, 0));
            geometry2.vertices.push(new THREE.Vector3(x, -y, data.material_thickness));
            geometry3.vertices.push(new THREE.Vector3(x, -y, 0));
            geometry3.vertices.push(new THREE.Vector3(x, -y, data.material_thickness));
        }
        gearObject.add(new THREE.Line(geometry1, material, THREE.LineStrip));
        gearObject.add(new THREE.Line(geometry2, material, THREE.LineStrip));
        gearObject.add(new THREE.Line(geometry3, material, THREE.LinePieces));

        if (gear.hole_radius) {
            addCylinder(0, 0, 0, data.material_thickness, gear.hole_radius, 20, material, gearObject);

            // Find a hole with this center.
            var hole = findHole(holes, cx, cy);
            if (hole === null) {
                // Not found.
                holes.push({
                    x: cx,
                    y: cy,
                    r: gear.hole_radius,
                    type: "axle",
                    attachedTo: gear,
                    speed: gear.speed
                });
            } else if (gear.hole_radius < hole.r) {
                // Keep track of smallest hole -- that's the axle radius.
                hole.r = gear.hole_radius;
                hole.speed = gear.speed;
                hole.attachedTo = gear;
            }

            minZ = Math.min(minZ, cz);
            maxZ = Math.max(maxZ, cz);
        }

        if (gear.bind) {
            for (var i = 0; i < gear.bind.centers.length; i++) {
                var center = gear.bind.centers[i];
                addCylinder(center[0], center[1], 0, data.material_thickness,
                            gear.bind.hole_radius, 20, material, gearObject);
            }
        }

        // Make a parent gear object for all three.
        gearObject.position.x = cx;
        gearObject.position.y = cy;
        gearObject.position.z = cz;

        scene.add(gearObject);
        objects.push({
            object3d: gearObject,
            piece: gear
        });
    }

    // Create axles.
    var material = new THREE.LineBasicMaterial({ color: "#222222" });
    for (var i = 0; i < holes.length; i++) {
        var hole = holes[i];

        var axle = new THREE.Object3D();
        addCylinder(0, 0, minZ - 60, maxZ + 80, data.large_bolt_radius, 20, material, axle);
        axle.position.x = hole.x;
        axle.position.y = hole.y;
        axle.position.z = 0;
        scene.add(axle);
        objects.push({
            object3d: axle,
            piece: hole
        });
    }

    // Create our camera.
    var centerOfScene = new THREE.Vector3(1100, -400, 0);
    var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 10, 10000);
    camera.position.x = centerOfScene.x + 1300;
    camera.position.y = centerOfScene.y + 1300;
    camera.position.z = centerOfScene.z + 1300;
    camera.lookAt(centerOfScene);

    // Create the trackball controller.
    var controls = new THREE.Trackball(camera);
    controls.setTarget(centerOfScene);

    var render = function () {
        requestAnimationFrame(render);

        // Advance time.
        var now = new Date();
        if (g_previous_date !== null) {
            var millis = now - g_previous_date;
            g_time += millis * g_sim_speed / 1000;
        }
        g_previous_date = now;

        // Rotate the gears.
        for (var object_index in objects) {
            var object = objects[object_index];
            var piece = object.piece;
            var object3d = object.object3d;
            var theta;

            if (piece.type === "verge") {
                // Map two seconds to TAU (one cycle), then Sine that, map to 0 to 1.
                var span = Math.sin(g_time/2*TAU)/2 + 0.5;
                span /= 2;
                var left_full_in_angle = piece.left_full_in_angle*TAU/360;
                var right_full_in_angle = piece.right_full_in_angle*TAU/360;
                theta = span*(left_full_in_angle - right_full_in_angle) + right_full_in_angle;
            } else if (piece.type === "axle") {
                // Axles don't turn.
                theta = 0;
            } else {
                // 43200 = 12 hours' worth of seconds.
                theta = escapedTime(g_time) * TAU / 43200 * piece.speed;
            }

            // Negate theta because our Y is upside down.
            object3d.rotation.z = -theta;
        }

        // Update camera position.
        controls.update();

        // Keep camera pointing up so we don't get disoriented.
        camera.up = new THREE.Vector3(0, 1, 0);

        // Render the scene.
        renderer.render(scene, camera);
    };

    var onWindowResize = function () {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
        controls.handleResize();
    };
    window.addEventListener("resize", onWindowResize, false);

    render();
};

$(function () {
    $.getJSON("clock.json", function (data) {
        startRendering(data);
    });
});

})();
<commit_msg>Can stop time; can reload data.<commit_after>
//   Copyright 2015 Lawrence Kesteloot
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

(function () {

var TAU = 2*Math.PI;

// For keeping track of simulation time.
var g_time = 0.0;
var g_sim_speed = 1.0;
var g_previous_date = null;

// Globals for the scene.
var g_scene = null;
var g_objects = [];

// Simulate the motion of the escapement.
var escapedTime = function (time) {
    var integer = Math.floor(time);
    var fraction = time - integer;

    // Move for the last 1/16 of a second because of the escapement.
    fraction = Math.max(0, fraction*16 - 15);

    return integer + fraction;
};

var onKeyDown = function (event) {
    // Minute hand goes around in one minute instead of one hour.
    if (event.keyCode === 48) { // "0"
        g_sim_speed = 0;
    } else if (event.keyCode === 49) { // "1"
        g_sim_speed = 1;
    } else if (event.keyCode === 50) { // "2"
        g_sim_speed = 60;
    } else if (event.keyCode === 51) { // "3"
        g_sim_speed = 60*12;
    } else if (event.keyCode === 82) { // "r"
        // Reload.
        fetchData();
    } else {
        console.log(event.keyCode);
    }
    window.removeEventListener("keydown", onKeyDown);
};

var onKeyUp = function (event) {
    window.addEventListener("keydown", onKeyDown, false);
};

var closeEnough = function (a, b) {
    return Math.abs(a - b) < 0.0001;
};

var addCylinder = function (cx, cy, z1, z2, r, facets, material, parent) {
    var geometry1 = new THREE.Geometry();
    var geometry2 = new THREE.Geometry();
    var geometry3 = new THREE.Geometry();

    for (var i = 0; i <= facets; i++) {
        var t = i/facets*TAU;
        var x = cx + Math.sin(t)*r;
        var y = cy + Math.cos(t)*r;
        geometry1.vertices.push(new THREE.Vector3(x, y, z1));
        geometry2.vertices.push(new THREE.Vector3(x, y, z2));
        geometry3.vertices.push(new THREE.Vector3(x, y, z1));
        geometry3.vertices.push(new THREE.Vector3(x, y, z2));
    }

    parent.add(new THREE.Line(geometry1, material, THREE.LineStrip));
    parent.add(new THREE.Line(geometry2, material, THREE.LineStrip));
    parent.add(new THREE.Line(geometry3, material, THREE.LinePieces));
};

var findHole = function (holes, x, y) {
    for (var i = 0; i < holes.length; i++) {
        var hole = holes[i];
        if (closeEnough(hole.x, x) && closeEnough(hole.y, y)) {
            return hole;
        }
    }

    return null;
}

var initializeThree = function () {
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    g_scene = new THREE.Scene();

    window.addEventListener("keydown", onKeyDown, false);
    window.addEventListener("keyup", onKeyUp, false);

    // Create our camera.
    var centerOfScene = new THREE.Vector3(1100, -400, 0);
    var camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 10, 10000);
    camera.position.x = centerOfScene.x + 1300;
    camera.position.y = centerOfScene.y + 1300;
    camera.position.z = centerOfScene.z + 1300;
    camera.lookAt(centerOfScene);

    // Create the trackball controller.
    var controls = new THREE.Trackball(camera);
    controls.setTarget(centerOfScene);

    var onWindowResize = function () {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
        controls.handleResize();
    };
    window.addEventListener("resize", onWindowResize, false);

    var render = function () {
        requestAnimationFrame(render);

        // Advance time.
        var now = new Date();
        if (g_previous_date !== null) {
            var millis = now - g_previous_date;
            g_time += millis * g_sim_speed / 1000;
        }
        g_previous_date = now;

        // Rotate the gears.
        for (var object_index in g_objects) {
            var object = g_objects[object_index];
            var piece = object.piece;
            var object3d = object.object3d;
            var theta;

            if (piece.type === "verge") {
                // Map two seconds to TAU (one cycle), then Sine that, map to 0 to 1.
                var span = Math.sin(g_time/2*TAU)/2 + 0.5;
                span /= 2;
                var left_full_in_angle = piece.left_full_in_angle*TAU/360;
                var right_full_in_angle = piece.right_full_in_angle*TAU/360;
                theta = span*(left_full_in_angle - right_full_in_angle) + right_full_in_angle;
            } else if (piece.type === "axle") {
                // Axles don't turn.
                theta = 0;
            } else {
                // 43200 = 12 hours' worth of seconds.
                theta = escapedTime(g_time) * TAU / 43200 * piece.speed;
            }

            // Negate theta because our Y is upside down.
            object3d.rotation.z = -theta;
        }

        // Update camera position.
        controls.update();

        // Render the scene.
        renderer.render(g_scene, camera);
    };

    render();
};

var startRendering = function (data) {
    var pieces = data.pieces;

    // New scene.
    g_scene = new THREE.Scene();

    // Each object has two keys:
    //    object3d: the THREE.Object3D object.
    //    piece: the piece (gear, axle, etc.).
    g_objects = [];

    // List of holes we've seen. Each item is an object with keys:
    //    x, y: center of holes.
    //    r: smallest radius we've seen.
    //    type: "axle".
    //    attachedTo: piece we're stuck to.
    //    speed: speed of gear with a matching radius.
    // Duplicate (x,y) are removed.
    var holes = [];

    // Lowest and highest Z value.
    var maxZ = -100000;
    var minZ = 100000;

    // Draw each gear.
    for (var gear_index in pieces) {
        var gear = pieces[gear_index];
        var points = gear.points;
        var cx = gear.cx;
        var cy = -gear.cy;
        var cz = gear.cz*data.material_thickness;

        // Create geometry.
        var material = new THREE.LineBasicMaterial({ color: gear.color });
        var gearObject = new THREE.Object3D();

        var geometry1 = new THREE.Geometry();
        var geometry2 = new THREE.Geometry();
        var geometry3 = new THREE.Geometry();
        for (var i = 0; i < points.length; i++) {
            var x = points[i][0];
            var y = points[i][1];
            geometry1.vertices.push(new THREE.Vector3(x, -y, 0));
            geometry2.vertices.push(new THREE.Vector3(x, -y, data.material_thickness));
            geometry3.vertices.push(new THREE.Vector3(x, -y, 0));
            geometry3.vertices.push(new THREE.Vector3(x, -y, data.material_thickness));
        }
        gearObject.add(new THREE.Line(geometry1, material, THREE.LineStrip));
        gearObject.add(new THREE.Line(geometry2, material, THREE.LineStrip));
        gearObject.add(new THREE.Line(geometry3, material, THREE.LinePieces));

        if (gear.hole_radius) {
            addCylinder(0, 0, 0, data.material_thickness, gear.hole_radius, 20, material, gearObject);

            // Find a hole with this center.
            var hole = findHole(holes, cx, cy);
            if (hole === null) {
                // Not found.
                holes.push({
                    x: cx,
                    y: cy,
                    r: gear.hole_radius,
                    type: "axle",
                    attachedTo: gear,
                    speed: gear.speed
                });
            } else if (gear.hole_radius < hole.r) {
                // Keep track of smallest hole -- that's the axle radius.
                hole.r = gear.hole_radius;
                hole.speed = gear.speed;
                hole.attachedTo = gear;
            }

            minZ = Math.min(minZ, cz);
            maxZ = Math.max(maxZ, cz);
        }

        if (gear.bind) {
            for (var i = 0; i < gear.bind.centers.length; i++) {
                var center = gear.bind.centers[i];
                addCylinder(center[0], center[1], 0, data.material_thickness,
                            gear.bind.hole_radius, 20, material, gearObject);
            }
        }

        // Make a parent gear object for all three.
        gearObject.position.x = cx;
        gearObject.position.y = cy;
        gearObject.position.z = cz;

        g_scene.add(gearObject);
        g_objects.push({
            object3d: gearObject,
            piece: gear
        });
    }

    // Create axles.
    var material = new THREE.LineBasicMaterial({ color: "#222222" });
    for (var i = 0; i < holes.length; i++) {
        var hole = holes[i];

        var axle = new THREE.Object3D();
        addCylinder(0, 0, minZ - 60, maxZ + 80, data.large_bolt_radius, 20, material, axle);
        axle.position.x = hole.x;
        axle.position.y = hole.y;
        axle.position.z = 0;
        g_scene.add(axle);
        g_objects.push({
            object3d: axle,
            piece: hole
        });
    }
};

// Fetch the JSON data from the net and display it.
var fetchData = function () {
    $.getJSON("clock.json", function (data) {
        startRendering(data);
    });
};

$(function () {
    initializeThree();
    fetchData();
});

})();
<|endoftext|>