diff --git a/ffnord-con-2015-2/logo/logo.png b/ffnord-con-2015-2/logo/logo.png
new file mode 100644
index 0000000..ff977e8
Binary files /dev/null and b/ffnord-con-2015-2/logo/logo.png differ
diff --git a/ffnord-con-2015-2/logo/logo.svg b/ffnord-con-2015-2/logo/logo.svg
new file mode 100644
index 0000000..96aafee
--- /dev/null
+++ b/ffnord-con-2015-2/logo/logo.svg
@@ -0,0 +1,130 @@
+
+
+
+
diff --git a/ffnord-con-2015-2/logo/node.lua b/ffnord-con-2015-2/logo/node.lua
new file mode 100644
index 0000000..af63f77
--- /dev/null
+++ b/ffnord-con-2015-2/logo/node.lua
@@ -0,0 +1,8 @@
+gl.setup(1280, 720)
+
+background = resource.load_image("logo.png")
+
+function node.render()
+ gl.clear(0.1, 0.1, 0.1, 1)
+ background:draw(0, 0, WIDTH, HEIGHT)
+end
diff --git a/ffnord-con-2015-2/logo/path3482.png b/ffnord-con-2015-2/logo/path3482.png
new file mode 100644
index 0000000..82fb501
Binary files /dev/null and b/ffnord-con-2015-2/logo/path3482.png differ
diff --git a/ffnord-con-2015-2/node.lua b/ffnord-con-2015-2/node.lua
index c3eec37..b430822 100644
--- a/ffnord-con-2015-2/node.lua
+++ b/ffnord-con-2015-2/node.lua
@@ -1,6 +1,6 @@
gl.setup(1280, 720)
function node.render()
- resource.render_child("31c3"):draw(0, 0, 1280, 720)
- resource.render_child("29c3"):draw(0, 0, 1280, 720)
+ resource.render_child("room"):draw(0, 0, 1280, 720)
+-- resource.render_child("logo"):draw(0, 0, 1280, 720)
end
diff --git a/ffnord-con-2015-2/room/.node.lua.swp b/ffnord-con-2015-2/room/.node.lua.swp
new file mode 100644
index 0000000..638fa26
Binary files /dev/null and b/ffnord-con-2015-2/room/.node.lua.swp differ
diff --git a/ffnord-con-2015-2/room/.service.swp b/ffnord-con-2015-2/room/.service.swp
new file mode 100644
index 0000000..0e89840
Binary files /dev/null and b/ffnord-con-2015-2/room/.service.swp differ
diff --git a/ffnord-con-2015-2/room/Oswald-Medium.ttf b/ffnord-con-2015-2/room/Oswald-Medium.ttf
new file mode 100644
index 0000000..8210fc3
Binary files /dev/null and b/ffnord-con-2015-2/room/Oswald-Medium.ttf differ
diff --git a/ffnord-con-2015-2/room/README.txt b/ffnord-con-2015-2/room/README.txt
new file mode 100644
index 0000000..df38ef1
--- /dev/null
+++ b/ffnord-con-2015-2/room/README.txt
@@ -0,0 +1,14 @@
+Room visualization used at 30c3.
+
+This is almost the code that ran at 30c3. I had to remove the font used
+as the font used at 30c3 cannot be redistributed. The replacement font
+is "Roboto light". See http://www.fontsquirrel.com/license/roboto
+
+Installation:
+
+ Nothing to do
+
+Usage:
+
+ start info-beamer for this directory.
+ Then run ./service
diff --git a/ffnord-con-2015-2/room/clock.png b/ffnord-con-2015-2/room/clock.png
new file mode 100644
index 0000000..8a97e16
Binary files /dev/null and b/ffnord-con-2015-2/room/clock.png differ
diff --git a/ffnord-con-2015-2/room/config.json b/ffnord-con-2015-2/room/config.json
new file mode 100644
index 0000000..14da937
--- /dev/null
+++ b/ffnord-con-2015-2/room/config.json
@@ -0,0 +1,15 @@
+{
+ "devices": {
+ "2429607372": "attraktor"
+ },
+ "rooms": {
+ "attraktor": {
+ "dect": "",
+ "translation": "",
+ "twitter": "#ffnord",
+ "irc": "#ffnord",
+ "texture": "marble",
+ "speed": 70
+ }
+ }
+}
diff --git a/ffnord-con-2015-2/room/g3527.png b/ffnord-con-2015-2/room/g3527.png
new file mode 100644
index 0000000..c8a9603
Binary files /dev/null and b/ffnord-con-2015-2/room/g3527.png differ
diff --git a/ffnord-con-2015-2/room/json.lua b/ffnord-con-2015-2/room/json.lua
new file mode 100644
index 0000000..d95241a
--- /dev/null
+++ b/ffnord-con-2015-2/room/json.lua
@@ -0,0 +1,377 @@
+-----------------------------------------------------------------------------
+-- JSON4Lua: JSON encoding / decoding support for the Lua language.
+-- json Module.
+-- Author: Craig Mason-Jones
+-- Homepage: http://json.luaforge.net/
+-- Version: 0.9.40
+-- This module is released under the MIT License (MIT).
+-- Please see LICENCE.txt for details.
+--
+-- USAGE:
+-- This module exposes two functions:
+-- encode(o)
+-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
+-- decode(json_string)
+-- Returns a Lua object populated with the data encoded in the JSON string json_string.
+--
+-- REQUIREMENTS:
+-- compat-5.1 if using Lua 5.0
+--
+-- CHANGELOG
+-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
+-- Fixed Lua 5.1 compatibility issues.
+-- Introduced json.null to have null values in associative arrays.
+-- encode() performance improvement (more than 50%) through table.concat rather than ..
+-- Introduced decode ability to ignore /**/ comments in the JSON string.
+-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Imports and dependencies
+-----------------------------------------------------------------------------
+local math = require('math')
+local string = require("string")
+local table = require("table")
+
+local base = _G
+
+-----------------------------------------------------------------------------
+-- Module declaration
+-----------------------------------------------------------------------------
+module("json")
+
+-- Public functions
+
+-- Private functions
+local decode_scanArray
+local decode_scanComment
+local decode_scanConstant
+local decode_scanNumber
+local decode_scanObject
+local decode_scanString
+local decode_scanWhitespace
+local encodeString
+local isArray
+local isEncodable
+
+-----------------------------------------------------------------------------
+-- PUBLIC FUNCTIONS
+-----------------------------------------------------------------------------
+--- Encodes an arbitrary Lua object / variable.
+-- @param v The Lua object / variable to be JSON encoded.
+-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
+function encode (v)
+ -- Handle nil values
+ if v==nil then
+ return "null"
+ end
+
+ local vtype = base.type(v)
+
+ -- Handle strings
+ if vtype=='string' then
+ return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string
+ end
+
+ -- Handle booleans
+ if vtype=='number' or vtype=='boolean' then
+ return base.tostring(v)
+ end
+
+ -- Handle tables
+ if vtype=='table' then
+ local rval = {}
+ -- Consider arrays separately
+ local bArray, maxCount = isArray(v)
+ if bArray then
+ for i = 1,maxCount do
+ table.insert(rval, encode(v[i]))
+ end
+ else -- An object, not an array
+ for i,j in base.pairs(v) do
+ if isEncodable(i) and isEncodable(j) then
+ table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
+ end
+ end
+ end
+ if bArray then
+ return '[' .. table.concat(rval,',') ..']'
+ else
+ return '{' .. table.concat(rval,',') .. '}'
+ end
+ end
+
+ -- Handle null values
+ if vtype=='function' and v==null then
+ return 'null'
+ end
+
+ base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
+end
+
+
+--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
+-- @param s The string to scan.
+-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
+-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
+-- and the position of the first character after
+-- the scanned JSON object.
+function decode(s, startPos)
+ startPos = startPos and startPos or 1
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
+ local curChar = string.sub(s,startPos,startPos)
+ -- Object
+ if curChar=='{' then
+ return decode_scanObject(s,startPos)
+ end
+ -- Array
+ if curChar=='[' then
+ return decode_scanArray(s,startPos)
+ end
+ -- Number
+ if string.find("+-0123456789.e", curChar, 1, true) then
+ return decode_scanNumber(s,startPos)
+ end
+ -- String
+ if curChar==[["]] or curChar==[[']] then
+ return decode_scanString(s,startPos)
+ end
+ if string.sub(s,startPos,startPos+1)=='/*' then
+ return decode(s, decode_scanComment(s,startPos))
+ end
+ -- Otherwise, it must be a constant
+ return decode_scanConstant(s,startPos)
+end
+
+--- The null function allows one to specify a null value in an associative array (which is otherwise
+-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
+function null()
+ return null -- so json.null() will also return null ;-)
+end
+-----------------------------------------------------------------------------
+-- Internal, PRIVATE functions.
+-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
+-- functions with an underscore.
+-----------------------------------------------------------------------------
+
+--- Scans an array from JSON into a Lua object
+-- startPos begins at the start of the array.
+-- Returns the array and the next starting position
+-- @param s The string being scanned.
+-- @param startPos The starting position for the scan.
+-- @return table, int The scanned array as a table, and the position of the next character to scan.
+function decode_scanArray(s,startPos)
+ local array = {} -- The return value
+ local stringLen = string.len(s)
+ base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
+ startPos = startPos + 1
+ -- Infinite loop for array elements
+ repeat
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
+ local curChar = string.sub(s,startPos,startPos)
+ if (curChar==']') then
+ return array, startPos+1
+ end
+ if (curChar==',') then
+ startPos = decode_scanWhitespace(s,startPos+1)
+ end
+ base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
+ object, startPos = decode(s,startPos)
+ table.insert(array,object)
+ until false
+end
+
+--- Scans a comment and discards the comment.
+-- Returns the position of the next character following the comment.
+-- @param string s The JSON string to scan.
+-- @param int startPos The starting position of the comment
+function decode_scanComment(s, startPos)
+ base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
+ local endPos = string.find(s,'*/',startPos+2)
+ base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
+ return endPos+2
+end
+
+--- Scans for given constants: true, false or null
+-- Returns the appropriate Lua type, and the position of the next character to read.
+-- @param s The string being scanned.
+-- @param startPos The position in the string at which to start scanning.
+-- @return object, int The object (true, false or nil) and the position at which the next character should be
+-- scanned.
+function decode_scanConstant(s, startPos)
+ local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
+ local constNames = {"true","false","null"}
+
+ for i,k in base.pairs(constNames) do
+ --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
+ if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
+ return consts[k], startPos + string.len(k)
+ end
+ end
+ base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
+end
+
+--- Scans a number from the JSON encoded string.
+-- (in fact, also is able to scan numeric +- eqns, which is not
+-- in the JSON spec.)
+-- Returns the number, and the position of the next character
+-- after the number.
+-- @param s The string being scanned.
+-- @param startPos The position at which to start scanning.
+-- @return number, int The extracted number and the position of the next character to scan.
+function decode_scanNumber(s,startPos)
+ local endPos = startPos+1
+ local stringLen = string.len(s)
+ local acceptableChars = "+-0123456789.e"
+ while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
+ and endPos<=stringLen
+ ) do
+ endPos = endPos + 1
+ end
+ local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
+ local stringEval = base.loadstring(stringValue)
+ base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
+ return stringEval(), endPos
+end
+
+--- Scans a JSON object into a Lua object.
+-- startPos begins at the start of the object.
+-- Returns the object and the next starting position.
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return table, int The scanned object as a table and the position of the next character to scan.
+function decode_scanObject(s,startPos)
+ local object = {}
+ local stringLen = string.len(s)
+ local key, value
+ base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
+ startPos = startPos + 1
+ repeat
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
+ local curChar = string.sub(s,startPos,startPos)
+ if (curChar=='}') then
+ return object,startPos+1
+ end
+ if (curChar==',') then
+ startPos = decode_scanWhitespace(s,startPos+1)
+ end
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
+ -- Scan the key
+ key, startPos = decode(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ startPos = decode_scanWhitespace(s,startPos)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
+ startPos = decode_scanWhitespace(s,startPos+1)
+ base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+ value, startPos = decode(s,startPos)
+ object[key]=value
+ until false -- infinite loop while key-value pairs are found
+end
+
+--- Scans a JSON string from the opening inverted comma or single quote to the
+-- end of the string.
+-- Returns the string extracted as a Lua string,
+-- and the position of the next non-string character
+-- (after the closing inverted comma or single quote).
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return string, int The extracted string as a Lua string, and the next character to parse.
+function decode_scanString(s,startPos)
+ base.assert(startPos, 'decode_scanString(..) called without start position')
+ local startChar = string.sub(s,startPos,startPos)
+ base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
+ local escaped = false
+ local endPos = startPos + 1
+ local bEnded = false
+ local stringLen = string.len(s)
+ repeat
+ local curChar = string.sub(s,endPos,endPos)
+ -- Character escaping is only used to escape the string delimiters
+ if not escaped then
+ if curChar==[[\]] then
+ escaped = true
+ else
+ bEnded = curChar==startChar
+ end
+ else
+ -- If we're escaped, we accept the current character come what may
+ escaped = false
+ end
+ endPos = endPos + 1
+ base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
+ until bEnded
+ local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
+ local stringEval = base.loadstring(stringValue)
+ base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
+ return stringEval(), endPos
+end
+
+--- Scans a JSON string skipping all whitespace from the current start position.
+-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
+-- @param s The string being scanned
+-- @param startPos The starting position where we should begin removing whitespace.
+-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
+-- was reached.
+function decode_scanWhitespace(s,startPos)
+ local whitespace=" \n\r\t"
+ local stringLen = string.len(s)
+ while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do
+ startPos = startPos + 1
+ end
+ return startPos
+end
+
+--- Encodes a string to be JSON-compatible.
+-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
+-- @param s The string to return as a JSON encoded (i.e. backquoted string)
+-- @return The string appropriately escaped.
+function encodeString(s)
+ s = string.gsub(s,'\\','\\\\')
+ s = string.gsub(s,'"','\\"')
+ s = string.gsub(s,"'","\\'")
+ s = string.gsub(s,'\n','\\n')
+ s = string.gsub(s,'\t','\\t')
+ return s
+end
+
+-- Determines whether the given Lua type is an array or a table / dictionary.
+-- We consider any table an array if it has indexes 1..n for its n items, and no
+-- other data in the table.
+-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
+-- @param t The table to evaluate as an array
+-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
+-- the second returned value is the maximum
+-- number of indexed elements in the array.
+function isArray(t)
+ -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
+ -- (with the possible exception of 'n')
+ local maxIndex = 0
+ for k,v in base.pairs(t) do
+ if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair
+ if (not isEncodable(v)) then return false end -- All array elements must be encodable
+ maxIndex = math.max(maxIndex,k)
+ else
+ if (k=='n') then
+ if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements
+ else -- Else of (k=='n')
+ if isEncodable(v) then return false end
+ end -- End of (k~='n')
+ end -- End of k,v not an indexed pair
+ end -- End of loop across all pairs
+ return true, maxIndex
+end
+
+--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
+-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
+-- In this implementation, all other types are ignored.
+-- @param o The object to examine.
+-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
+function isEncodable(o)
+ local t = base.type(o)
+ return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
+end
+
diff --git a/ffnord-con-2015-2/room/logo.png b/ffnord-con-2015-2/room/logo.png
new file mode 100644
index 0000000..181b8cf
Binary files /dev/null and b/ffnord-con-2015-2/room/logo.png differ
diff --git a/ffnord-con-2015-2/room/logo.svg b/ffnord-con-2015-2/room/logo.svg
new file mode 100644
index 0000000..1d26fa8
--- /dev/null
+++ b/ffnord-con-2015-2/room/logo.svg
@@ -0,0 +1,114 @@
+
+
+
+
diff --git a/ffnord-con-2015-2/room/marble.jpg b/ffnord-con-2015-2/room/marble.jpg
new file mode 100644
index 0000000..53a4290
Binary files /dev/null and b/ffnord-con-2015-2/room/marble.jpg differ
diff --git a/ffnord-con-2015-2/room/marble_lol.jpg b/ffnord-con-2015-2/room/marble_lol.jpg
new file mode 100644
index 0000000..8d870b7
Binary files /dev/null and b/ffnord-con-2015-2/room/marble_lol.jpg differ
diff --git a/ffnord-con-2015-2/room/node.lua b/ffnord-con-2015-2/room/node.lua
new file mode 100644
index 0000000..91e035f
--- /dev/null
+++ b/ffnord-con-2015-2/room/node.lua
@@ -0,0 +1,318 @@
+gl.setup(1280, 720)
+
+font = resource.load_font("Oswald-Medium.ttf")
+
+node.alias("room")
+
+local json = require "json"
+
+-- Configure room here
+local SAAL = "attraktor"
+
+util.auto_loader(_G)
+
+util.file_watch("schedule.json", function(content)
+ print("reloading schedule")
+ talks = json.decode(content)
+end)
+
+util.file_watch("config.json", function(content)
+ local config = json.decode(content)
+ if sys.get_env then
+ saal = config.devices[sys.get_env("SERIAL")]
+ end
+ if not saal then
+ print("using statically configured saal identifier")
+ saal = SAAL
+ end
+ print(saal)
+ rooms = config.rooms
+ room = config.rooms[saal]
+end)
+
+local base_time = N.base_time or 0
+local current_talk
+local all_talks = {}
+local day = 0
+
+function get_now()
+ return base_time + sys.now()
+end
+
+function check_next_talk()
+ local now = get_now()
+ local room_next = {}
+ for idx, talk in ipairs(talks) do
+ if rooms[talk.place] and not room_next[talk.place] and talk.unix + 25 * 60 > now then
+ room_next[talk.place] = talk
+ end
+ end
+
+ for room, talk in pairs(room_next) do
+ talk.lines = wrap(talk.title, 30)
+ end
+
+ if room_next[saal] then
+ current_talk = room_next[saal]
+ else
+ current_talk = nil
+ end
+
+ all_talks = {}
+ for room, talk in pairs(room_next) do
+ if current_talk and room ~= current_talk.place then
+ all_talks[#all_talks + 1] = talk
+ end
+ end
+ table.sort(all_talks, function(a, b)
+ if a.unix < b.unix then
+ return true
+ elseif a.unix > b.unix then
+ return false
+ else
+ return a.place < b.place
+ end
+ end)
+end
+
+function wrap(str, limit, indent, indent1)
+ limit = limit or 72
+ local here = 1
+ local wrapped = str:gsub("(%s+)()(%S+)()", function(sp, st, word, fi)
+ if fi-here > limit then
+ here = st
+ return "\n"..word
+ end
+ end)
+ local splitted = {}
+ for token in string.gmatch(wrapped, "[^\n]+") do
+ splitted[#splitted + 1] = token
+ end
+ return splitted
+end
+
+local clock = (function()
+ local base_time = N.base_time or 0
+
+ local function set(time)
+ base_time = tonumber(time) - sys.now()
+ end
+
+ util.data_mapper{
+ ["clock/midnight"] = function(since_midnight)
+ set(since_midnight)
+ end;
+ }
+
+ local left = 0
+
+ local function get()
+ local time = (base_time + sys.now()) % 86400
+ return string.format("%d:%02d", math.floor(time / 3600), math.floor(time % 3600 / 60))
+ end
+
+ return {
+ get = get;
+ set = set;
+ }
+end)()
+
+check_next_talk()
+
+util.data_mapper{
+ ["clock/set"] = function(time)
+ base_time = tonumber(time) - sys.now()
+ N.base_time = base_time
+ check_next_talk()
+ print("UPDATED TIME", base_time)
+ end;
+ ["clock/day"] = function(new_day)
+ print("DAY", new_day)
+ day = new_day
+ end;
+}
+
+function switcher(screens)
+ local current_idx = 1
+ local current = screens[current_idx]
+ local switch = sys.now() + current.time
+ local switched = sys.now()
+
+ local blend = 0.5
+
+ local function draw()
+ local now = sys.now()
+
+ local percent = ((now - switched) / (switch - switched)) * 3.14129 * 2 - 3.14129
+ progress:use{percent = percent}
+ white:draw(WIDTH-50, HEIGHT-50, WIDTH-10, HEIGHT-10)
+ progress:deactivate()
+
+ if now - switched < blend then
+ local delta = (switched - now) / blend
+ gl.pushMatrix()
+ gl.translate(WIDTH/2, 0)
+ gl.rotate(270-90 * delta, 0, 1, 0)
+ gl.translate(-WIDTH/2, 0)
+ current.draw()
+ gl.popMatrix()
+ elseif now < switch - blend then
+ current.draw(now - switched)
+ elseif now < switch then
+ local delta = 1 - (switch - now) / blend
+ gl.pushMatrix()
+ gl.translate(WIDTH/2, 0)
+ gl.rotate(90 * delta, 0, 1, 0)
+ gl.translate(-WIDTH/2, 0)
+ current.draw()
+ gl.popMatrix()
+ else
+ current_idx = current_idx + 1
+ if current_idx > #screens then
+ current_idx = 1
+ end
+ current = screens[current_idx]
+ switch = now + current.time
+ switched = now
+ end
+ end
+ return {
+ draw = draw;
+ }
+end
+
+content = switcher{
+--[[ {
+ time = 10;
+ draw = function()
+ font:write(400, 200, "Other rooms", 80, 1,1,1,1)
+ white:draw(0, 300, WIDTH, 302, 0.6)
+ y = 320
+ local time_sep = false
+ if #all_talks > 0 then
+ for idx, talk in ipairs(all_talks) do
+ if not time_sep and talk.unix > get_now() then
+ if idx > 1 then
+ y = y + 5
+ white:draw(0, y, WIDTH, y+2, 0.6)
+ y = y + 20
+ end
+ time_sep = true
+ end
+
+ local alpha = 1
+ if not time_sep then
+ alpha = 0.3
+ end
+ font:write(30, y, talk.start, 50, 1,1,1,alpha)
+ font:write(190, y, talk.place, 50, 1,1,1,alpha)
+ font:write(400, y, talk.lines[math.floor((sys.now()/2) % #talk.lines)+1], 50, 1,1,1,alpha)
+ y = y + 60
+ end
+ else
+ font:write(400, 330, "No other talks.", 50, 1,1,1,1)
+ end
+ end
+ }, --]] {
+ time = 30;
+ draw = function()
+ if not current_talk then
+ redU(400, 200, "Next session...", 80, 1,1,1,1)
+ white:draw(0, 300, WIDTH, 302, 0.6)
+ redU(400, 330, "Nope. That's it.", 80)
+
+ else
+ local delta = current_talk.unix - get_now()
+ if delta > 0 then
+ redU(400, 200, "Next session", 80)
+ else
+ redU(400, 200, "This session", 80)
+ end
+ white:draw(0, 300, WIDTH, 302, 0.6)
+
+ blue(130, 330, current_talk.start, 50, 1,1,1,1)
+ if delta > 0 then
+ blue(130, 330 + 60, string.format("in %d min", math.floor(delta/60)+1), 50)
+ end
+ for idx, line in ipairs(current_talk.lines) do
+ if idx >= 5 then
+ break
+ end
+ yellow(400, 330 - 60 + 60 * idx, line, 50)
+ end
+ for i, speaker in ipairs(current_talk.speakers) do
+ blue(400, 510 + 50 * i, speaker, 50)
+ end
+ end
+ end
+ }, --[[ {
+ time = 10;
+ draw = function(t)
+ font:write(400, 200, "Room info", 80, 1,1,1,1)
+ white:draw(0, 300, WIDTH, 302, 0.6)
+ font:write(30, 320, "Audio", 50, 1,1,1,1)
+ font:write(400, 320, "Dial " .. room.dect, 50, 1,1,1,1)
+
+ font:write(30, 380, "Translation", 50, 1,1,1,1)
+ font:write(400, 380, "Dial " .. room.translation, 50, 1,1,1,1)
+
+ font:write(30, 480, "IRC", 50, 1,1,1,1)
+ font:write(400, 480, room.irc, 50, 1,1,1,1)
+
+ font:write(30, 540, "Twitter", 50, 1,1,1,1)
+ font:write(400, 540, room.twitter, 50, 1,1,1,1)
+
+ if t then
+ local banner = -4 + t
+ if banner > 0 and banner < 3.1412 then
+ local alpha = math.pow(math.sin(banner), 2)
+ local x = 400 - math.pow(banner / math.pi - 0.5, 5) * WIDTH * 2
+ font:write(x, 640, "http://info-beamer.org/", 30, 1,1,1,alpha)
+ end
+ end
+ end
+ }, --]]
+}
+
+function redU(x, y, text, size)
+ red(x, y, string.upper(text), size)
+end
+
+function red(x, y, text, size)
+ font:write(x, y, text, size, 0.894, 0.251, 0.506, 1)
+end
+
+function yellowU(x, y, text, size)
+ yellow(x, y, string.upper(text), size)
+end
+
+function yellow(x, y, text, size)
+ font:write(x, y, text, size, 1.0, 0.776, 0.251, 1)
+end
+
+function blueU(x, y, text, size)
+ blue(x, y, string.upper(text), size)
+end
+
+function blue(x, y, text, size)
+ font:write(x, y, text, size, 0.251, 0.714, 0.906, 1)
+end
+
+function node.render()
+ gl.clear(0.1, 0.1, 0.1, 1)
+
+ if base_time == 0 then
+ return
+ end
+
+ util.draw_correct(logo, 20, 20, 300, 120)
+
+ yellowU(310, 20, saal, 80)
+ blueU(730, 20, clock.get(), 80)
+ redU(WIDTH-300, 20, string.format("Day %d", day), 80)
+
+ local fov = math.atan2(HEIGHT, WIDTH*2) * 360 / math.pi
+ gl.perspective(fov, WIDTH/2, HEIGHT/2, -WIDTH,
+ WIDTH/2, HEIGHT/2, 0)
+ content.draw()
+end
diff --git a/ffnord-con-2015-2/room/place.png b/ffnord-con-2015-2/room/place.png
new file mode 100644
index 0000000..8458b4f
Binary files /dev/null and b/ffnord-con-2015-2/room/place.png differ
diff --git a/ffnord-con-2015-2/room/progress.frag b/ffnord-con-2015-2/room/progress.frag
new file mode 100644
index 0000000..1c04e89
--- /dev/null
+++ b/ffnord-con-2015-2/room/progress.frag
@@ -0,0 +1,16 @@
+uniform sampler2D Texture;
+varying vec2 TexCoord;
+uniform float percent;
+
+void main() {
+ vec2 pos = TexCoord;
+ float angle = atan(pos.x - 0.5, pos.y - 0.5);
+ float dist = distance(pos, vec2(0.5, 0.5));
+ if (dist >= 0.5) {
+ gl_FragColor = vec4(0.0);
+ } else if (angle < percent) {
+ gl_FragColor = vec4(0.894, 0.251, 0.506, 1);
+ } else {
+ gl_FragColor = vec4(1.0, 0.776, 0.251, 1);
+ }
+}
diff --git a/ffnord-con-2015-2/room/schedule.json b/ffnord-con-2015-2/room/schedule.json
new file mode 100644
index 0000000..d864a4a
--- /dev/null
+++ b/ffnord-con-2015-2/room/schedule.json
@@ -0,0 +1,121 @@
+[
+ {
+ "duration": 60,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "9:30",
+ "title": "„Einlass“ und Frühstück",
+ "type": "talk",
+ "unix": 1441956600,
+ "unix_end": 1441960200
+ },
+
+ {
+ "duration": 30,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "11:00",
+ "title": "Eröffnungsplenum",
+ "type": "talk",
+ "unix": 1441962000,
+ "unix_end": 1441963800
+ },
+
+ {
+ "duration": 60,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ "t.b.a."
+ ],
+ "start": "12:00",
+ "title": "Vortrags-Slot #1 mit folgender Diskussion und Kaffeepause",
+ "type": "talk",
+ "unix": 1441965600,
+ "unix_end": 1441969200
+ },
+
+ {
+ "duration": 60,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ "t.b.a."
+ ],
+ "start": "14:00",
+ "title": "Vortrags-Slot #2 mit folgender Diskussion und Kaffeepause",
+ "type": "talk",
+ "unix": 1441972800,
+ "unix_end": 1441976400
+ },
+
+ {
+ "duration": 240,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "16:00",
+ "title": "Workshop und Hacking Time",
+ "type": "talk",
+ "unix": 1441980000,
+ "unix_end": 1441994400
+ },
+
+ {
+ "duration": 60,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "9:30",
+ "title": "„Einlass“ und Frühstück",
+ "type": "talk",
+ "unix": 1442043000,
+ "unix_end": 1442046600
+ },
+
+ {
+ "duration": 60,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ "t.b.a."
+ ],
+ "start": "11:00",
+ "title": "Vortrags-Slot #3 mit folgender Diskussion und Kaffeepause",
+ "type": "talk",
+ "unix": 1442048400,
+ "unix_end": 1442052000
+ },
+
+ {
+ "duration": 150,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "13:00",
+ "title": "Workshop und Hacking Time",
+ "type": "talk",
+ "unix": 1442055600,
+ "unix_end": 1442064600
+ },
+
+ {
+ "duration": 30,
+ "lang": "de",
+ "place": "attraktor",
+ "speakers": [
+ ],
+ "start": "16:00",
+ "title": "Abschlussplenum und Diskussion",
+ "type": "talk",
+ "unix": 1442066400,
+ "unix_end": 1442068200
+ }
+]
diff --git a/ffnord-con-2015-2/room/service b/ffnord-con-2015-2/room/service
new file mode 100755
index 0000000..4b3cc4a
--- /dev/null
+++ b/ffnord-con-2015-2/room/service
@@ -0,0 +1,55 @@
+#!/usr/bin/python
+import sys
+import time
+import pytz
+import socket
+from calendar import timegm
+from datetime import datetime, date
+
+diff = None
+
+# Fake time in UTC
+diff = datetime(2015,9,11,8,30,00) - datetime.utcnow()
+
+met = pytz.timezone("Europe/Berlin")
+def current_time():
+ now = datetime.utcnow()
+ if diff:
+ now += diff
+ timestamp = timegm(now.timetuple()) + now.microsecond / 1000000.
+ now = now.replace(tzinfo=pytz.utc)
+ now = now.astimezone(met)
+ now = now.replace(tzinfo=None)
+ return now, timestamp
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+def send(data):
+ sock.sendto(data, ('127.0.0.1', 4444))
+ print >>sys.stderr, "SENT >>> ", data
+
+def send_clock(now, ts):
+ day = (now.date() - date(2015, 9, 11)).days
+ since_midnight = (
+ now -
+ now.replace(hour=0, minute=0, second=0, microsecond=0)
+ )
+ since_midnight = since_midnight.seconds + since_midnight.microseconds / 1000000.
+
+ send('room/clock/day:%d' % day)
+ send('room/clock/set:%f' % ts)
+ send('room/clock/midnight:%f' % since_midnight)
+
+
+def main():
+ while 1:
+ now, ts = current_time()
+ if now.year < 2000:
+ print >>sys.stderr, "too soon"
+ time.sleep(1)
+ continue
+ send_clock(now, ts)
+ time.sleep(2)
+
+if __name__ == "__main__":
+ main()
diff --git a/ffnord-con-2015-2/room/trichter.frag b/ffnord-con-2015-2/room/trichter.frag
new file mode 100644
index 0000000..3489d91
--- /dev/null
+++ b/ffnord-con-2015-2/room/trichter.frag
@@ -0,0 +1,32 @@
+uniform sampler2D Texture;
+uniform sampler2D Grid;
+uniform sampler2D Overlay;
+varying vec2 TexCoord;
+uniform float time;
+
+const float segments = 9.0;
+
+float rand(vec2 co){
+ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
+}
+
+void main() {
+ vec2 pos = TexCoord;
+
+ vec2 coord = texture2D(Texture, pos).st;
+ coord.x = mod(coord.x + time, 1.0);
+ coord.y = coord.y + 0.0175;
+ coord += rand(pos) * 0.008;
+ vec3 col = texture2D(Overlay, coord).rgb;
+
+ float foo = floor(coord.y * segments);
+ col *= (foo / segments) * 0.6 + 0.4;
+
+ if (pos.y > 0.82) {
+ vec3 bar = texture2D(Overlay, pos).rgb;
+ col = col * 0.3 + bar * 0.4; //mix(col, bar, 0.3) * 0.8;
+ }
+
+ vec4 grid = texture2D(Grid, pos);
+ gl_FragColor = vec4(col, 1.0) + grid * 0.09;
+}
diff --git a/ffnord-con-2015-2/room/trichter_grid.png b/ffnord-con-2015-2/room/trichter_grid.png
new file mode 100644
index 0000000..ac8c986
Binary files /dev/null and b/ffnord-con-2015-2/room/trichter_grid.png differ
diff --git a/ffnord-con-2015-2/room/trichter_map.png b/ffnord-con-2015-2/room/trichter_map.png
new file mode 100644
index 0000000..f596552
Binary files /dev/null and b/ffnord-con-2015-2/room/trichter_map.png differ
diff --git a/ffnord-con-2015-2/room/white.png b/ffnord-con-2015-2/room/white.png
new file mode 100644
index 0000000..5e03498
Binary files /dev/null and b/ffnord-con-2015-2/room/white.png differ