<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Acko.net]]></title>
  <link href="http://acko.net/atom.xml" rel="self"/>
  <link href="http://acko.net/"/>
  <updated>2012-05-17T01:12:29-07:00</updated>
  <id>http://acko.net/</id>
  <author>
    <name><![CDATA[Steven Wittens]]></name>
    
  </author>

  
  <entry>
    <title type="html"><![CDATA[Introducing Facing.me]]></title>
    <link href="http://acko.net/blog/introducing-facing-me/"/>
    <updated>2012-04-25T00:00:00-07:00</updated>
    <id>http://acko.net/blog/introducing-facing-me</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

  <h1>Introducing Facing.me</h1>
  <h2>A unique way to meet people</h2>

</div></div>

<div class="c"></div>

<aside class="g5">
  <p class="tc">
    <img style="top: 0" src="/files/fme/facing.me.face.jpg" alt="Facing.me">
  </p>
</aside>

<div class="g7"><div class="pad">

<p>
We've been sending out whispers for a while now, but it's finally out: a new web site called <a href="http://facing.me">Facing.me</a>. Coded and designed by <a href="http://mikejholly.com">Michael Holly</a>, <a href="http://rosshj.com/">Ross Howard-Jones</a> and myself, it promises a <em>unique way to meet people online</em>. This would be the point where the obvious question is dropped: wait, what… you built a <em>dating site</em>?</p>

<p>Sort of. Let me explain.</p>

<p>Having spent many years in the web world, we'd all gotten a bit complacent. The web has settled into its comfortable rhythms. Sites and applications can be modelled quickly and coded on your framework of choice. And nowadays, Web 2.0 cred comes baked in: clean URLs, semantic HTML, AJAX, data feeds, APIs, etc. Isn't this what we all wanted?</p>

<p>But the web continues to evolve, and giants are roaming the playground. Sites like Facebook and Twitter hold people's attention with surgical precision, while engines like Google answer your queries with lightning speed. Given that we've all slotted such services into our workflows and indeed lives, it seems only natural that 'indie' developers should keep up. We can't pretend that a 2000-era style web-page-with-ajax-sprinkles is the pinnacle of modern interactive design.</p>

<p>So we set out to try something different.</p>

</div></div>

<div class="img12">
  <a href="http://facing.me"><img src="/files/fme/facing.me.site.jpg" alt="Facing.me website"></a>
</div>

<!--
<div class="g8 i2 first"><div class="pad">  

</div></div>
-->

<div class="g6"><div class="pad">

<h2>A Guy Walks into a Bar...</h2>

<p>If you've managed to score an invite, the first thing you'll see is the wall of faces that loads and fills the screen. The second thing you'll notice—we hope at least—is the lack of everything else.</p>

<p>The metaphor we kept in mind was the idea of walking into a bar, and looking around. If you see someone you like, you can go up to them and strike up a conversation. So that's exactly what the app lets you do, through video chat. You can pan around to see more people, and just keep going. If you're looking for something specific, you can filter your view with a simple "I'm looking for…" dialog.</p>

<p>As you mouse around, you can see who's online, and flip open their profile. If you want to strike up a video chat, it happens right there too. If the person is online, they'll see your request immediately in a popup and can choose to accept or decline after reviewing your profile. If they're offline, they'll see your request next time they visit.</p>

<p>To avoid missed connections, you can 'like' people you're interested in. You'll see (and hear) a notification pop up the moment they're online. You can keep the app open in a background tab and never miss a thing.</p>

<p>Aside from some minor social glue and a few fun little extras for you to discover, that's it. It's our twist on a <em>minimally viable product</em> if you will. Studies have shown that online matching algorithms are a poor predictor for how well people mesh in person. Until you meet face-to-face, you just don't know. We think direct, spontaneous video chat is a better first step rather than endless profile matching and messaging.</p>

</div></div>

<aside class="g6 m1">
  <p class="p0"><img src="/files/fme/facing.me.start.jpg" alt="Facing.me welcome screen"></p>

  <p class="p0"><img src="/files/fme/facing.me.profile.jpg" alt="Facing.me welcome screen"></p>

  <p class="p0"><img src="/files/fme/facing.me.growl.jpg" alt="Facing.me notification"></p>

  <p class="p0"><img src="/files/fme/facing.me.like.jpg" alt="Facing.me liking"></p>
</aside>

<div class="g8 i2"><div class="pad">

<h2>Polishing Bacon</h2>

<p>But despite its minimalism, a big aspect of Facing.me is the effort and care we put into it. Our goal was to achieve a level of polish typically reserved for premium iPhone apps and bring it into the browser. We wrapped the whole thing in a crisp design, enhanced with tasteful web fonts. But most importantly, we sought to expose the app's functionality with as little interruption as possible. To do that, we layered on plenty of transitions driven by CSS3 and JavaScript, and stream in data and content as needed.</p>

<p>Based on previous work in custom animations—and <a href="/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon">bacon</a>—we refined the approach of using jQuery as an animation helper for completely custom transitions. We tell jQuery to animate placeholder properties on orphaned proxy divs, and key off those animations with per-frame code to drive the fancy stuff.</p>

</div></div>

<div class="img12">
  <img src="/files/fme/transition.jpg" alt="facing.me animation example">
</div>

<div class="g8 i2"><div class="pad">
<p>As a result, we can have a photo grow a picture frame as you pick it up, and then flip it around to show a person's full profile. This careful choreography involves animating about a dozen CSS properties, including borders, shadows, margins and 3D transforms, all with custom expressions and hand-tuned animation curves. Similar transitions are used for lightbox dialogs.</p>

<p>Throughout all of this, the animations remain eminently manageable. We can interrupt and reverse them at any point, and run multiple copies at the same time, thanks to pervasive use of view controllers. Far from being a useless tech demo, it actually enables us to craft the user experience exactly the way we like it: being able to acknowledge user intentions with intuitive feedback no matter what's going on, and firing off new events and requests without worrying about the internal state. Gone are the fragile jQuery behavior soups of old.</p>

<p>The one downside is that only the newer browsers—i.e. Chrome, Safari and Firefox—get to see everything the way it was intended. And actually the performance in Firefox is still a bit disappointing. IE9 users will have to be satisfied with a crude 2D approximation until IE10 comes out.</p>

</div></div>

<div class="g8 first"><div class="pad">

<h2>Rapid Rails and Real-Time Node</h2>

<p>To make all this work effectively on the server-side, we used a dual-mode stack of Rails and Node.js.</p>

<p>The Rails side houses the app's models and controllers, and provides an API for all the client-side JavaScript to do its job. Video chats are handled through Flash and routed through its built-in peer-to-peer functionality.</p>

<p>The node.js component acts as a real-time presence daemon which users connect to over socket.io. It's used to drive the status notifications and to coordinate the video chats. We can exchange any sort of notifications between users with a publish-subscribe model, opening up many interesting avenues for future development.</p>

<p>Overall, this approach has worked out great. Rails' ActiveRecord and the stack around it allowed us to build out functionality quickly and with just the right amount of necessary baggage. We made generous use of Ruby Gems to save time while still maintaining full control.</p>

<p>Node.js's event-driven model adds real-time signalling with no hassle. For the few cases where node.js needs to interface with the Rails database directly, we slot in some manual SQL to take care of that. For everything else, Rails and node.js exchange signed data through the browser.</p>

</div></div>

<aside class="g4 m1"><div class="pad">
  <p><img src="/files/fme/nodejs.png" alt="Node.js"></p>

  <p><img src="/files/fme/rails.jpg" alt="Rails"></p>
</div></aside>

<div class="g8 i2 first"><div class="pad">

<h2>Come Take it for a Spin</h2>

<p>Finally, we also put our heads together and made a promo video, voiced by the lovely <a href="https://twitter.com/t1nah">Tina Hoang</a>:</p>

</div></div>

<div style="max-width: 854px; width: 100%; margin: 0 auto">

<!--
<iframe width="854" height="480" src="http://www.youtube.com/embed/Ua67Hf1T7yI?rel=0" frameborder="0" allowfullscreen></iframe>
-->
<iframe src="http://player.vimeo.com/video/41056588?title=0&amp;byline=0&amp;portrait=0" width="854" height="480" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>

</div>

<div class="g8 i2"><div class="pad">

<p>Built in our spare time by just 3 guys in a virtual garage, we're pretty proud of the end result. We'd love for you to take it for a spin, so <a href="http://facing.me">head over to facing.me</a> and grab yourself an invite. There's a feedback form built-in, and any suggestions are welcome.</p>

<p>Discuss on <a href="https://plus.google.com/112457107445031703644/posts/efHMJE1Wxx2">Google Plus</a>.</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[This is Your Brain on CSS]]></title>
    <link href="http://acko.net/blog/this-is-your-brain-on-css/"/>
    <updated>2012-02-19T00:00:00-08:00</updated>
    <id>http://acko.net/blog/this-is-your-brain-on-css</id>
    <content type="html"><![CDATA[<div style="display: none"><img src="/files/mri/cover.jpg" alt=""></div>

<div class="g8 i2 first"><div class="pad">

<h1>This is Your Brain on CSS</h1>

<p>First things first: the CSS 3D renderer used to power this site is now <a href="https://github.com/unconed/CSS3D.js">available on GitHub.com</a>. However, it's still limited to only solid lines and planes. It's also limited to WebKit browsers, as Firefox's CSS 3D support just isn't quite there yet.</p>

<p>
  But CSS 3D is not a one trick pony, and as with many things, what you get out of it depends entirely on what you put in. So here's a disembodied head made out of CSS 3D. It consists of nothing more than a bunch of images stacked up against each other, and integrates perfectly with the existing 3D parallax on this site. Click and drag to rotate, or use the slider to look inside.
</p>

<link rel="stylesheet" href="/files/mri/head.css" type="text/css" media="screen" />

<div id="head-3d">
  <div class="head-viewport">
    <div class="CSS3DCamera" data-var="transform">
      <div class="pedestal">
        
      </div>
      <div class="VolumetricView" data-var="phi slice"></div>
    </div>
  </div>
  <div class="Slider" data-var="slice"></div>
</div>

<p>
  Making the basic effect was actually quite easy. I took an MRI from the <a href="http://graphics.stanford.edu/data/voldata/">Stanford Volume Data Archive</a> and wrote a small script to turn it into a sheet of CSS sprites. There's <a href="/files/mri/MRbrain-color.jpg">one file for color</a>, <a href="/files/mri/MRbrain-alpha8.png">one for opacity</a>, totalling about 2.1 MB. Both files are composited into Canvases and placed in slices into the DOM, offset forward or backwards in 3D. Then there's just some minor logic to rotate the slices in 90 degree increments to follow the camera.
</p>

<p>
  But the slices are rendered as is, and the MRI consists of <a href="/files/mri/MRbrain-alpha8.png">boring grayscale data</a>. Luckily, I can precompute any amount of shaders and effects I want and just bake them into the slices. I geeked out by applying fake specular lighting, for that 'fresh meat' look, and volumetric obscurance to enhance the sense of depth on the inside. I changed the palette to gory colors based on local density, giving the impression of flesh and bone knitting itself together. Creepy, but cool.
</p>

<p>
  I wrapped it in a custom widget, using straight up CSS rather than Three.js this time. I've wanted to play with <a href="http://worrydream.com/Tangle/">Tangle.js</a>, so I used that to hook up the camera controls and slider. That's pretty much it. In an ideal world, the jarring transition when rotating would be covered up by a nice transition, but the browsers don't like it.
</p>

</div></div>

<script>
setTimeout(function () {
  if ($('div.css3d-support:visible').length == 0) return;

    var model = {
      initialize: function () {
        // State
        this.theta = 0.0;
        this.phi = 0.5;
        this.slice = 0;
      },

      update: function () {
        this.transform = 'rotateX('+ -this.theta +'rad) rotateY('+ this.phi +'rad)';
      },
    };

    Tangle.classes.CSS3DCamera = {
      initialize: function (element, options, tangle, variables) {
        this.element = element;
        this.$element = $(element);

        var that = this;
        $(element).mousedown(function (event) {
          that.drag = true;
          that.dragLast = that.dragOrigin = { x: event.pageX, y: event.pageY };
          event.preventDefault();
        });
        $(document).mouseup(function (event) {
          that.drag = false;
        });
        $(document).mousemove(function (event) {
          if (!that.drag) return;
          var total = { x: event.pageX - that.dragOrigin.x, y: event.pageY - that.dragOrigin.y },
              delta = { x: event.pageX - that.dragLast.x, y: event.pageY - that.dragLast.y };
          that.dragLast = { x: event.pageX, y: event.pageY };
          mousemove(that.dragOrigin, total, delta);
        });

        function mousemove(origin, total, delta) {
          var phi = tangle.getValue('phi') + delta.x * .01,
              theta = Math.min(1, Math.max(-.2, tangle.getValue('theta') + delta.y * .01));

          tangle.setValue('phi', phi);
          tangle.setValue('theta', theta);
        }
      },

      update: function (element, value) {
        this.$element.css({
          WebkitTransform: value,
          MozTransform: value,
          transform: value,
        })
      },
    },

    Tangle.classes.Slider = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;

        this.$element = $(this.element);
        this.$bar = $('<div class="bar">').appendTo(this.element);
        this.$handle = $('<div class="handle">').appendTo(this.element);

        var that = this;
        $(this.element).mousedown(function (event) {
          that.origin = that.$element.offset().left;
          that.width = that.$bar.width();
          that.drag = true;
          return false;
        });
        $(document).mousemove(function (event) {
          if (!that.drag) return;
          tangle.setValue('slice', Math.max(0, Math.min(1, (event.pageX - that.origin) / that.width)));
        });
        $(document).mouseup(function () {
          that.drag = false;
        });
      },

      update: function (element, value) {
        this.$handle.css('left', (100*value) + '%');
      },
    },

    Tangle.classes.VolumetricView = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;
        this.$element = $(element);

        this.width = 364;
        this.height = 384;
        this.depth = 256;

        this.resX = 182;
        this.resY = 192;
        this.slices = 108;
        this.stride = 8;
        this.rows = Math.ceil(this.slices / this.stride);

        this.createSlices();

        var load = 0;
        this.image = new Image();
        this.image.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

        this.mask = new Image();
        this.mask.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

//        this.image.src = 'data/MRbrain.png';
        this.image.src = '/files/mri/MRbrain-color.jpg';
        this.mask.src = '/files/mri/MRbrain-alpha8.png';
      },

      update: function (element, value) {
        var l = Math.abs(Math.cos(value)) > Math.abs(Math.sin(value));

        if (this.l != l || this.slice != slice) {
          var slice = this.tangle.getValue('slice'), index, 
              n = (l ? Math.cos(value) : Math.sin(value)) > 0,
              sn = n ? slice : 1 - slice;

          index = +(this.$slicesX.length * sn);

          this.$slicesX.css('display', l ? 'block' : 'none');
          this.$slicesX
            .slice().css('opacity', n ? .95 : .001).end()
            .slice(0, index).css('opacity', !n ? .95 : .001).end();

          index = +(this.$slicesZ.length * sn);
  //        this.$slicesZ[!l ? 'show' : 'hide']();
          this.$slicesZ.css('display', !l ? 'block' : 'none');
          this.$slicesZ
            .slice(index).css('opacity', n ? .95 : .001).end()
            .slice(0, index).css('opacity', !n ? .95 : .001).end();

          this.slice = slice;
          this.l = l;
        }
      },

      createSlices: function () {
        this.$element.empty();
        this.ctxX = [];
        this.ctxZ = [];

        // X slices
        for (var i = 0; i < this.slices; ++i) {
          var z = -((i / this.slices) - .5) * this.depth,
              t = 'translateZ(' + z + 'px) translateX(70px)';

          var $canvas = $('<canvas>')
              .addClass('x')
              .attr('width', this.resX)
              .attr('height', this.resY)
              .css({
                width: this.width,
                height: this.height,
                WebkitTransform: t,
                MozTransform: t,
                transform: t,
                opacity: 1,
              });

          this.$element.append($canvas);
          this.ctxX.push($canvas[0].getContext('2d'));
        }

        // Z slices
        for (var i = 0; i < this.resX; ++i) {
          var z = -(this.depth - this.width) / 2,
              x = ((i / this.resX) - .5) * this.width,
              t = 'translateX(' + x + 'px) translateX(70px) rotateY(90deg) translateZ(' + z + 'px)';

          var $canvas = $('<canvas>')
              .addClass('z')
              .attr('width', this.slices)
              .attr('height', this.resY)
              .css({
                width: this.depth,
                height: this.height,
                WebkitTransform: t,
                MozTransform: t,
                transform: t,
                opacity: 0,
              });

          this.$element.append($canvas);
          this.ctxZ.push($canvas[0].getContext('2d'));
        }

        this.$slicesX = this.$element.find('canvas.x');
        this.$slicesZ = this.$element.find('canvas.z');
      },

      drawSlices: function () {

        var s = this.stride,
            sl = this.slices,
            r = this.rows,
            w = this.resX,
            h = this.resY,
            img = this.image,
            mask = this.mask,
            ctxX = this.ctxX,
            ctxZ = this.ctxZ;

        var alpha, color;

        // X slices
        this.$slicesX.each(function (i) {
          var c = ctxX[i],
              ox = (i % s) * w, oy = Math.floor(i / s) * h;

          // Draw alpha channel and get pixels
          c.drawImage(mask, ox, oy, w, h, 0, 0, w, h);
          alpha = c.getImageData(0, 0, w, h);

          // Draw color channel and get pixels
          c.drawImage(img, ox, oy, w, h, 0, 0, w, h);
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y < h; ++y) {
            for (var x = 0; x < w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });

        // Z slices
        this.$slicesZ.each(function (i) {
          var c = ctxZ[i];

          // Render transposed slices as vertical strips.
          for (var j = 0; j < sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw alpha channel
            c.drawImage(mask, ox + i, oy, 1, h, j, 0, 1, h);
          }

          // Get pixels
          alpha = c.getImageData(0, 0, w, h);

          // Render transposed slices as vertical strips.
          for (var j = 0; j < sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw color channel
            c.drawImage(img, ox + i, oy, 1, h, j, 0, 1, h);
          }
          // Get pixels
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y < h; ++y) {
            for (var x = 0; x < w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });
      },

    };

    var tangle = new Tangle($('#head-3d')[0], model);

}, 200);
</script>
<script>
//
//  Tangle.js
//  Tangle 0.1.0
//
//  Created by Bret Victor on 5/2/10.
//  (c) 2011 Bret Victor.  MIT open-source license.
//
//  ------ model ------
//
//  var tangle = new Tangle(rootElement, model);
//  tangle.setModel(model);
//
//  ------ variables ------
//
//  var value = tangle.getValue(variableName);
//  tangle.setValue(variableName, value);
//  tangle.setValues({ variableName:value, variableName:value });
//
//  ------ UI components ------
//
//  Tangle.classes.myClass = {
//     initialize: function (element, options, tangle, variable) { ... },
//     update: function (element, value) { ... }
//  };
//  Tangle.formats.myFormat = function (value) { return "..."; };
//

var Tangle = this.Tangle = function (rootElement, modelClass) {

    var tangle = this;
    tangle.element = rootElement;
    tangle.setModel = setModel;
    tangle.getValue = getValue;
    tangle.setValue = setValue;
    tangle.setValues = setValues;

    var _model;
    var _nextSetterID = 0;
    var _setterInfosByVariableName = {};   //  { varName: { setterID:7, setter:function (v) { } }, ... }
    var _varargConstructorsByArgCount = [];


    //----------------------------------------------------------
    //
    // construct

    initializeElements();
    setModel(modelClass);
    return tangle;


    //----------------------------------------------------------
    //
    // elements

    function initializeElements() {
        var elements = rootElement.getElementsByTagName("*");
        var interestingElements = [];
        
        // build a list of elements with class or data-var attributes
        
        for (var i = 0, length = elements.length; i < length; i++) {
            var element = elements[i];
            if (element.getAttribute("class") || element.getAttribute("data-var")) {
                interestingElements.push(element);
            }
        }

        // initialize interesting elements in this list.  (Can't traverse "elements"
        // directly, because elements is "live", and views that change the node tree
        // will change elements mid-traversal.)
        
        for (var i = 0, length = interestingElements.length; i < length; i++) {
            var element = interestingElements[i];
            
            var varNames = null;
            var varAttribute = element.getAttribute("data-var");
            if (varAttribute) { varNames = varAttribute.split(" "); }

            var views = null;
            var classAttribute = element.getAttribute("class");
            if (classAttribute) {
                var classNames = classAttribute.split(" ");
                views = getViewsForElement(element, classNames, varNames);
            }
            
            if (!varNames) { continue; }
            
            var didAddSetter = false;
            if (views) {
                for (var j = 0; j < views.length; j++) {
                    if (!views[j].update) { continue; }
                    addViewSettersForElement(element, varNames, views[j]);
                    didAddSetter = true;
                }
            }
            
            if (!didAddSetter) {
                var formatAttribute = element.getAttribute("data-format");
                var formatter = getFormatterForFormat(formatAttribute, varNames);
                addFormatSettersForElement(element, varNames, formatter);
            }
        }
    }
            
    function getViewsForElement(element, classNames, varNames) {   // initialize classes
        var views = null;
        
        for (var i = 0, length = classNames.length; i < length; i++) {
            var clas = Tangle.classes[classNames[i]];
            if (!clas) { continue; }
            
            var options = getOptionsForElement(element);
            var args = [ element, options, tangle ];
            if (varNames) { args = args.concat(varNames); }
            
            var view = constructClass(clas, args);
            
            if (!views) { views = []; }
            views.push(view);
        }
        
        return views;
    }
    
    function getOptionsForElement(element) {   // might use dataset someday
        var options = {};

        var attributes = element.attributes;
        var regexp = /^data-[\w\-]+$/;

        for (var i = 0, length = attributes.length; i < length; i++) {
            var attr = attributes[i];
            var attrName = attr.name;
            if (!attrName || !regexp.test(attrName)) { continue; }
            
            options[attrName.substr(5)] = attr.value;
        }
         
        return options;   
    }
    
    function constructClass(clas, args) {
        if (typeof clas !== "function") {  // class is prototype object
            var View = function () { };
            View.prototype = clas;
            var view = new View();
            if (view.initialize) { view.initialize.apply(view,args); }
            return view;
        }
        else {  // class is constructor function, which we need to "new" with varargs (but no built-in way to do so)
            var ctor = _varargConstructorsByArgCount[args.length];
            if (!ctor) {
                var ctorArgs = [];
                for (var i = 0; i < args.length; i++) { ctorArgs.push("args[" + i + "]"); }
                var ctorString = "(function (clas,args) { return new clas(" + ctorArgs.join(",") + "); })";
                ctor = eval(ctorString);   // nasty
                _varargConstructorsByArgCount[args.length] = ctor;   // but cached
            }
            return ctor(clas,args);
        }
    }
    

    //----------------------------------------------------------
    //
    // formatters

    function getFormatterForFormat(formatAttribute, varNames) {
        if (!formatAttribute) { formatAttribute = "default"; }

        var formatter = getFormatterForCustomFormat(formatAttribute, varNames);
        if (!formatter) { formatter = getFormatterForSprintfFormat(formatAttribute, varNames); }
        if (!formatter) { log("Tangle: unknown format: " + formatAttribute); formatter = getFormatterForFormat(null,varNames); }

        return formatter;
    }
        
    function getFormatterForCustomFormat(formatAttribute, varNames) {
        var components = formatAttribute.split(" ");
        var formatName = components[0];
        if (!formatName) { return null; }
        
        var format = Tangle.formats[formatName];
        if (!format) { return null; }
        
        var formatter;
        var params = components.slice(1);
        
        if (varNames.length <= 1 && params.length === 0) {  // one variable, no params
            formatter = format;
        }
        else if (varNames.length <= 1) {  // one variable with params
            formatter = function (value) {
                var args = [ value ].concat(params);
                return format.apply(null, args);
            };
        }
        else {  // multiple variables
            formatter = function () {
                var values = getValuesForVariables(varNames);
                var args = values.concat(params);
                return format.apply(null, args);
            };
        }
        return formatter;
    }
    
    function getFormatterForSprintfFormat(formatAttribute, varNames) {
        if (!sprintf || !formatAttribute.test(/\%/)) { return null; }

        var formatter;
        if (varNames.length <= 1) {  // one variable
            formatter = function (value) {
                return sprintf(formatAttribute, value);
            };
        }
        else {
            formatter = function (value) {  // multiple variables
                var values = getValuesForVariables(varNames);
                var args = [ formatAttribute ].concat(values);
                return sprintf.apply(null, args);
            };
        }
        return formatter;
    }

    
    //----------------------------------------------------------
    //
    // setters
    
    function addViewSettersForElement(element, varNames, view) {   // element has a class with an update method
        var setter;
        if (varNames.length <= 1) {
            setter = function (value) { view.update(element, value); };
        }
        else {
            setter = function () {
                var values = getValuesForVariables(varNames);
                var args = [ element ].concat(values);
                view.update.apply(view,args);
            };
        }

        addSetterForVariables(setter, varNames);
    }

    function addFormatSettersForElement(element, varNames, formatter) {  // tangle is injecting a formatted value itself
        var span = null;
        var setter = function (value) {
            if (!span) { 
                span = document.createElement("span");
                element.insertBefore(span, element.firstChild);
            }
            span.innerHTML = formatter(value);
        };

        addSetterForVariables(setter, varNames);
    }
    
    function addSetterForVariables(setter, varNames) {
        var setterInfo = { setterID:_nextSetterID, setter:setter };
        _nextSetterID++;

        for (var i = 0; i < varNames.length; i++) {
            var varName = varNames[i];
            if (!_setterInfosByVariableName[varName]) { _setterInfosByVariableName[varName] = []; }
            _setterInfosByVariableName[varName].push(setterInfo);
        }
    }

    function applySettersForVariables(varNames) {
        var appliedSetterIDs = {};  // remember setterIDs that we've applied, so we don't call setters twice
    
        for (var i = 0, ilength = varNames.length; i < ilength; i++) {
            var varName = varNames[i];
            var setterInfos = _setterInfosByVariableName[varName];
            if (!setterInfos) { continue; }
            
            var value = _model[varName];
            
            for (var j = 0, jlength = setterInfos.length; j < jlength; j++) {
                var setterInfo = setterInfos[j];
                if (setterInfo.setterID in appliedSetterIDs) { continue; }  // if we've already applied this setter, move on
                appliedSetterIDs[setterInfo.setterID] = true;
                
                setterInfo.setter(value);
            }
        }
    }
    

    //----------------------------------------------------------
    //
    // variables

    function getValue(varName) {
        var value = _model[varName];
        if (value === undefined) { log("Tangle: unknown variable: " + varName);  return 0; }
        return value;
    }

    function setValue(varName, value) {
        var obj = {};
        obj[varName] = value;
        setValues(obj);
    }

    function setValues(obj) {
        var changedVarNames = [];

        for (var varName in obj) {
            var value = obj[varName];
            var oldValue = _model[varName];
            if (oldValue === undefined) { log("Tangle: setting unknown variable: " + varName);  continue; }
            if (oldValue === value) { continue; }  // don't update if new value is the same

            _model[varName] = value;
            changedVarNames.push(varName);
        }
        
        if (changedVarNames.length) {
            applySettersForVariables(changedVarNames);
            updateModel();
        }
    }
    
    function getValuesForVariables(varNames) {
        var values = [];
        for (var i = 0, length = varNames.length; i < length; i++) {
            values.push(getValue(varNames[i]));
        }
        return values;
    }

                    
    //----------------------------------------------------------
    //
    // model

    function setModel(modelClass) {
        var ModelClass = function () { };
        ModelClass.prototype = modelClass;
        _model = new ModelClass;

        updateModel(true);  // initialize and update
    }
    
    function updateModel(shouldInitialize) {
        var ShadowModel = function () {};  // make a shadow object, so we can see exactly which properties changed
        ShadowModel.prototype = _model;
        var shadowModel = new ShadowModel;
        
        if (shouldInitialize) { shadowModel.initialize(); }
        shadowModel.update();
        
        var changedVarNames = [];
        for (var varName in shadowModel) {
            if (!shadowModel.hasOwnProperty(varName)) { continue; }
            if (_model[varName] === shadowModel[varName]) { continue; }
            
            _model[varName] = shadowModel[varName];
            changedVarNames.push(varName);
        }
        
        applySettersForVariables(changedVarNames);
    }


    //----------------------------------------------------------
    //
    // debug

    function log (msg) {
        if (window.console) { window.console.log(msg); }
    }

};  // end of Tangle


//----------------------------------------------------------
//
// components

Tangle.classes = {};
Tangle.formats = {};

Tangle.formats["default"] = function (value) { return "" + value; };

</script>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[A Useful BitTorrent Analogy]]></title>
    <link href="http://acko.net/blog/a-useful-bittorrent-analogy/"/>
    <updated>2012-02-05T00:00:00-08:00</updated>
    <id>http://acko.net/blog/a-useful-bittorrent-analogy</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">

<h1>A Useful BitTorrent Analogy</h1>

</div></div>

<div class="c"></div>

<aside class="g5">
  <p class="tc">
    <img style="top: 0" src="/files/bittorrent/xerox.jpg" alt="Xerox 914 copier">
    The first successful commercial photo copier, the&nbsp;<a href="https://secure.wikimedia.org/wikipedia/en/wiki/Xerox_914">Xerox 914</a>.
  </p>

</aside>

<div class="g7 first"><div class="pad">

<p>
BitTorrent has been around for over a decade now. And yet, when mentioned in the media, it's pretty much universally associated with piracy and illegal file sharing.</p>

<p>Just the other day, I saw a journalist write proudly: <em>"No, I don't have a Torrent program and I'm not downloading one."</em> A journalist! Someone who is supposed to be an expert at retrieving information and sharing it!</p>

<p>BitTorrent is not scary, and more so it actually generates the majority of traffic on the internet. In the 21st century it should be a tool that sits on your digital utility belt, not something you wouldn't touch with a 10 foot pole. So here's a simple analogy to help understand it.</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p class="tc m0 orn">· • ·</p>

<p>Imagine a budget-starved teacher needs to hand out notes for class, but can only afford one copy. The document is 10 pages long, and there are 10 students who each need a complete copy.</p>

<p>The teacher could just give the notes to one student, and ask him to make all the copies, but that would only shift the burden, leaving him to pay for all 100 pages.</p>

<p>Instead, the teacher has an idea. She hands page 1 to student #1, page 2 to student #2, and so on, and tells each student to make 10 copies of their single page. The next week, the students can distribute them amongst themselves before class, and everyone gets a complete set. Nobody has to pay for more than their own 10 pages.</p>

<p>Everyone's happy: the teacher gets to share her knowledge cheaply, and the students don't mind paying for their own copies.</p>

<p>In the middle of the term, a new student joins. She could borrow someone else's big pile of notes, and copy the entire stack of paper, but that would mean she would have to pay for it all, and she's on a budget too.</p>

<p>So instead, she just goes around and asks each student to make a single copy of the pages they were assigned previously. The next week, she collects all the pages, and assembles a complete copy without even bothering the teacher.</p>

<p>She gets a free pass to catch up with the class, but the other students don't mind chipping in. That's because she immediately joins the game and can make copies too. The teacher can now hand out one page extra each week, or decide to give one student a free pass. If more students join, it works better and better.</p>

<p>Now instead, imagine that students join and leave the class every single day, and the teacher isn't quite so organized. She just puts her big stack of notes on the desk, and tells everyone they can take any page they want, as long as they promise to immediately make copies for anyone who asks. The students are all friendly, and make sure to keep each other in the loop about which pages everyone has. Both the originals and the copies are copied as many times as needed.</p>

<p class="tc orn">· • ·</p>

<p>That's BitTorrent in a nutshell. For any given class—i.e. a <em>file</em> that people are interested in—a cloud of students forms—i.e. the <em>peers</em> in the so called peer-to-peer network. The peers compare notes, see which pieces they are missing, and swap copies with each other. Eventually, the teacher (a.k.a. the <em>seeder</em>) can leave, taking her original copy with her, and the system will keep working. As long as there is at least one copy of every page in the room, the students can make more, and the document as a whole will live on.</p>

<p>This is pretty much the only way you can effectively distribute a massive archive of sensitive data to thousands or millions of people, without incurring massive bills. You can't use free or ad-supported services, as the material would get taken down instantly due to its sensitive nature. And you can't host it directly, as that would leave a trail pointing back to you.</p>

<p>With BitTorrent, your initial group of 'students' can be sworn to secrecy. After the initial round of copying, the teacher sneaks out, and the students just pin a notice on the bulletin board: "We have copies of <em>The Forbidden Secrets</em> by Dr. X. Come see us." Nobody claims to know who Dr. X is. Ideas and information flow freely, without censorship.</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Love to WebKit]]></title>
    <link href="http://acko.net/blog/making-love-to-webkit/"/>
    <updated>2012-01-09T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-love-to-webkit</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  <h1>Making Love To WebKit</h1>
  <h2>Parallax, GPUs and Technofetishism</h2>

  <p>
    If the world is going to end in 2012, Acko.net will at least go out in style: I've redesigned. Those of you reading through RSS readers will want to <a href="http://acko.net/">enter through the front door</a> in a WebKit-browser like Chrome, Safari or even an iPad.
  </p>

  <p class="bubble">
    The last design was meant to feel spacious, the new design <em>is</em> spacious, thanks to generous use of CSS 3D transforms.
  </p>

  <h3>CSS 3D vs. WebGL</h3>
  
  <p>
    This idea started with an accidental discovery: if you put a CSS perspective on a scrollable &lt;DIV&gt;, then 3D elements inside that &lt;DIV&gt; will retain their perspective while you scroll. This results in smooth, native parallax effects, and makes objects jump out of the page, particularly when using an analog input device with inertial scrolling.
  </p>

  <p>
    This raises the obvious question: how far can you take it? Of course, this only works on WebKit browsers, who currently have the only CSS 3D implementation out of beta, so it's not a viable strategy by itself yet. IE10 and Firefox will be the next browsers to offer it. There's WebGL in Chrome and Firefox that can be used to do similar things, but WebGL is its own sandbox: you can't put DOM elements in there, or use native interaction. And any amount of WebGL rendering in response to e.g. scrolling is going to involve some amount of lag. Still, I wasn't going put a lot of effort into making a CSS 3D-only design without some backup.
  </p>

  <p>
    That's why I actually built the whole thing on top of <a href="https://github.com/mrdoob/three.js/">Three.js</a>, mrdoob's excellent JavaScript 3D engine. Aside from providing a comprehensive standard library for 3D manipulation, it also lets you swap out the rendering component. Out of the box, it can render to a 2D canvas, a WebGL canvas, or SVG.
  </p>

</div></div>
<div class="g7"><div class="pad">

  <h3>The DOM Scenegraph</h3>
  
  <p>
    So I augmented it with a CSS 3D renderer (<a href="https://github.com/unconed/CSS3D.js">GitHub</a>). It reads out the scene and renders each object using DOM elements, shaped and transformed into the right 3D position, orientation and appearance. They sit ‘in’ the page, and the browser projects and composits them for you. Of course, this only works for simple geometric shapes like lines or rectangles, but luckily that's all I need.
  </p>
  
  <p>
    It would be too slow to have to render out new elements for every frame, so the CSS 3D renderer's elements persist. Moving or rotating an object involves just changing a CSS property. Same for the camera: the entire scene is wrapped in a &lt;DIV&gt; that has its own 3D transform.
  </p>

  <p>
    So it's VRML all over again, but this time, it actually sort of performs. With our browsers being actual 3D engines, it's not a huge leap from here to having a &lt;MESH&gt; tag in HTML6, can-of-worms-factor not withstanding.
  </p>

  <p>
    Having built a quick prototype, I was satisfied with how well it worked, particularly in Safari on OS X, where the cross-pollination from the iPhone's mature tile-based GPU renderer has clearly paid off and there is no lag at all.
  </p>

</div></div>

<aside class="g5">
  <p class="m3"><img src="/files/making-love-to-webkit/dom.png" alt="CSS 3D DOM">The DOM tree of this page. Yup, nasty.</p>
</aside>

<div class="c"></div>

<aside class="g5">
  <p class="m3"><img src="/files/making-love-to-webkit/old-acko.png" alt="Acko.net old design">Previous design (<a href="/tag/acko.net">Archive</a>)</p>
  <p><img src="/files/making-love-to-webkit/sketch.jpg" alt="Initial sketch">Initial sketch</p>
  <p><img src="/files/making-love-to-webkit/editor.png" alt="Initial sketch">Scene editor</p>
</aside>

<div class="g7"><div class="pad">

  <h3>
    Design Process
  </h3>

  <p>
    Now all that was needed was a design. Last time I drew out a manual perspective drawing in Illustrator, which was tedious, but still basically came down to designing a flat image. This time, it would have to work in 3D. I started with a quick sketch to get a feel for the perspective, now that it no longer needed to double as a flat frame for the site's content.
  </p>
  
  <p>
    Simple geometric shapes, parallel lines, consistent angles. Simple enough. But if real perspective was involved, I would have to place items so they would look good from multiple angles, and each would need convincing depth and shading. To do this all by hand, typing out coordinates and perpetually refreshing the page, would take forever.
  </p>
  
  <p>
    So instead I built a simple editor to speed up the process. It's super ghetto, and basically just exists to manipulate the colors, positions and orientations of objects in a Three.js scene. It spits out a JSON object describing them, which can then be unserialized again into a scene.
  </p>

  <p>
    This also helped maintain a consistent palette. The colors are built from a few base tints, brightened or darked in linear RGB—i.e. before gamma correction. This ensures even tones and allowed for easy color adjustments.
  </p>

  <p>
    The editor is almost entirely keyboard operated, but with its minimum amount of features I was at least able to place items in 3D, copy/paste objects and see it from any angle or position I wanted. To 'save', I just copied the output into a .JS file, where I could make manual tweaks too if necessary.
  </p>

  <p>
    As for the actual site and content, I wanted to keep it much more sober. Like many others these days, I want to treat blogging more like publishing. That way I can focus on crafting each post more like an article with illustrations and asides rather than just a text blog.
  </p>
    
  <p>
    Hence, while there's a big party upstairs, it's all <a href="http://www.amazon.com/Elements-Typographic-Style-Robert-Bringhurst/dp/0881791326">typography</a> down below. The font of choice is <a href="http://processtypefoundry.com/fonts/klavika/">Klavika</a>, a humanist/geometric sans-serif with just the right kind of “Dutch Art Museum Signage” meets “Cyberpunk” I was looking for. The layout is a responsive multi-column grid that collapses down for smaller screens and devices. Finally, a strict vertical rhythm is enforced in the lines to keep everything nice and tidy.
  </p>
  
</div></div>

<div class="g9"><div class="pad">
  <h4>Editor</h4>
  <iframe frameborder="0" src="/load.html" width="680" height="580"></iframe>
  <p class="m0 l0">
    <a href="/editor.html" target="_blank" class="editor-open">Open editor in new window</a>
  </p>
</div></div>

<div class="g3"><div class="pad">
  <h4>Controls</h4>
  <ul class="flat">
    <li><kbd>Click</kbd>+<kbd>Drag</kbd> — Orbit camera</li>

    <li><kbd>Enter</kbd> — New object</li>
    <li><kbd>Space</kbd> — Clone object</li>
    <li><kbd>Backspace</kbd> — Delete object</li>
    <li><kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd><br>Cycle through objects</li>

    <li><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd>&nbsp; <kbd>Q</kbd><kbd>E</kbd><br>Move object</li>
    <li><kbd>Shift</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br>Resize object</li>
    <li><kbd>Ctrl</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br>Move camera</li>

    <li><kbd>[</kbd><kbd>]</kbd> — Lower/raise units</li>
    <li><kbd>Z</kbd><kbd>X</kbd><br>Orbit distance</li>
    <li><kbd>T</kbd>/<kbd>T</kbd>/<kbd>U</kbd><br>Tag/untag/untag all</li>
  </ul>
</div></div>


<div class="g8 i2"><div class="pad">
  <h3>She cannae take the power cap'n!</h3>
  <p>
    307 objects later it was finished, and not a single image was used. Unfortunately, as you can see there are tons of glitches in the editor—though some objects only have one side by design, and it works a lot better in a separate window. CSS 3D was never meant to do this, and you often see incorrect depth layering and flickering. Luckily most of these are caused by the floating grid markers and aren't a problem in the final view. The rest was resolved by splitting up objects or dual layering problematic surfaces, but some minor problems remain. Also for some reason, the background &lt;DIV&gt;'s click areas extend beyond their visible area, causing some click layering issues that I had to work around. Text resizing in the browser also leads to breakage, though multi-touch zoom works in Safari.
  </p>
  
  <p>
    Performance in Safari is wonderfully smooth too, but Chrome OS X starts to lag a bit. Luckily the effects are turned off as soon as they go off screen, so any lag should be confined to the top of the page. Finally, there's also a random bug where sometimes the page will refuse to scroll if the mouse is over a 3D object, which is unfortunate, but also near-impossible to reproduce reliably.
  </p>
  
  <p>
    In theory the iPad would perform second, but it has its own issues. The use of page-in-page scrolling disables inertia, but this is entirely beyond my control. The other issue is that sometimes, the iPad will decide to render the page content at lower resolution, making it hard to read. I guess the CSS wizardry confuses its GPU texture management. A refresh usually fixes this.
  </p>
  
  <p>
    I also discovered some funny ways of abusing CSS 3D for weird effects. If you have a WebKit browser, scroll to the top and enter the Konami code for an impressionistic version of the same thing.
  </p>
    
  <p>
    I guess I'm now the proud owner of the first unofficial CSS 3D ‘ACID’ test. I'm eager to see how the next browser handles it. If it ends up being a silly idea in the long run, I can always just switch the output to WebGL, but for now I'm willing to run with it. I put in a universal CSS 3D detector and prefixes for all the major browsers.
  </p>
  
  <p>
    For non-CSS 3D browsers, I simply rendered the header into a static image. It's not as fun without the shifting perspective, but it adds its own kind of optical illusion as you scroll down.
  </p>
  
  <h3>
    Putting it all together
  </h3>
  
  <p>
    To power the site, I got rid of Drupal and replaced it with the nimble <a href="http://jekyllrb.com/">Jekyll</a>. Hat tip to <a href="http://walkah.net/">James Walker</a>, who did the same thing just a few days earlier and put all the code on GitHub to learn from.
  </p>
    
  <p>
    I've been really impressed with Jekyll's simple workflow, and though it's all static HTML, it's a refreshing change of pace. And thanks to client-side JS, it doesn't preclude adding interactive elements at all. I can treat my site as just a database of documents retrievable over HTTP, and wrap the logic around that.
  </p>
  
  <p>
    So I created a nice client-side navigator that transitions between pages, using 2D transforms, which also work on Firefox. It uses the HTML5 pushState API and replaces regular links with AJAX requests. Aside from being a faster way to navigate around, it also lets me link up multiple articles in a series elegantly. When you go back to a previous screen, it literally presses the browser's back button, thus avoiding creating a long, useless history trail. You go back exactly the way you came, scrolling back to where you were, just like the real back/forward buttons do. For example, click over to my <a href="/blog/making-worlds-introduction/">Making Worlds</a> series of posts. You can come back right away.
  </p>
  
  <p>
    I didn't use any libraries or router frameworks for this, simply because I wanted to have done it all myself at least once. As it now says on my <a href="/about">About page</a>, quoting Feynman: <em>"What I cannot create, I do not understand"</em>. The only way to grok the intricacies of something like browser history state, which we all use every day, is to dive in and replicate it. Otherwise, you'll just take carefully choreographed behavior for granted and your mental model will be incomplete.
  </p>

  <p>
    To keep code size down, I compiled a custom build of Three.js with only the parts I need. I also used YUI compressor to minify the CSS and JS. However, I don't mean to obfuscate the code: the important bits will make their way onto Github soon enough.
  </p>
  
  <p><em>Update: The CSS 3D renderer and editor are now <a href="https://github.com/unconed/CSS3D.js">available on GitHub</a>.</em></p>
  
  <h3>
    And Done?
  </h3>

  <p>
    I migrated over most of the content and did some house cleaning while I was at it. Most things should be back, but further fixes will be made. I also haven't implemented any commenting solution so far, but I'll be adding it back somehow as soon as I figure something out. In the mean time, there's <a href="https://plus.google.com/112457107445031703644/posts/HDJMgpDRAey">a Google Plus thread</a>.
  </p>

  <p>
    The final result looks like something that would perhaps once unironically be labeled <strong>The Information Superhighway</strong> in a magazine from the 90s, though with less neon green. I like it.
  </p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[On TermKit]]></title>
    <link href="http://acko.net/blog/on-termkit/"/>
    <updated>2011-05-17T00:00:00-07:00</updated>
    <id>http://acko.net/blog/on-termkit</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<h1>On TermKit</h1>

<p>I've been administering Unix machines for many years now, and frankly, it kinda sucks. It makes me wonder, when sitting in front of a crisp, 2.3 million pixel display (i.e. a laptop) why I'm telling those pixels to draw me a computer terminal from the 80s.
</p>

<p>
<img src="/files/termkit/termkit-1.png" alt="Regular bash terminal">
</p>

<p>
And yet, that's what us tech nerds do every day. The default Unix toolchain, marked in time by the 1970 epoch, operates in a world where data is either binary or text, and text is displayed in monospace chunks. The interaction is strictly limited to a linear flow of keystrokes, always directed at only one process. And that process is capable of communicating only in short little grunts of text, perhaps coalescing into a cutesy little ASCII art imitation of things that grown-ups call "dialogs", "progress bars", "tables" and "graphs".
</p>

<p>
The <a href="http://www.faqs.org/docs/artu/ch01s06.html">Unix philosophy</a> talks about software as a toolset, about tiny programs that can be composed seamlessly. The principles are sound, and have indeed stood the test of time. But they were implemented in a time when computing resources were orders of magnitude smaller, and computer interaction was undiscovered country.
</p>

<p>
In the meantime, we've gotten a lot better at displaying information. We've also learned a lot of lessons through the web about data interchange, network transparency, API design, and more. We know better how small tweaks in an implementation can make a world of difference in usability.
</p>

<p>
And yet the world of Unix is rife with jargon, invisible processes, traps and legacy bits. Every new adept has to pass a constant trial by fire, of not destroying their system at every opportunity it gives them.
</p>

<p>
So while I agree that having a flexible toolbox is great, in my opinion, those pieces could be built a lot better. I don't want the computer equivalent of a screwdriver and a hammer, I want a tricorder and a laser saw. TermKit is my attempt at making these better tools and addresses a couple of major pain points.
</p>

<p>
I see TermKit as an extension of what Apple did with OS X, in particular the system tools like Disk Utility and Activity Monitor. Tech stuff doesn't have to look like it comes from the Matrix.
</p>

<h2>Rich Display</h2>

<p>
It's 2011, and monospace text just doesn't cut it anymore. In the default ANSI color palette, barely any of the possible color combinations are even readable. We can't display graphs, mathematical formulas, tables, etc. We can't use the principles of modern typography to lay out information in a readable, balanced way.
</p>

<p>
<img src="/files/termkit/termkit-2.png" alt="TermKit example">
</p>

<p>
So instead, I opted for a front-end built in WebKit. Programs can display anything that a browser can, including HTML5 media. The output is built out of generic widgets (lists, tables, images, files, progress bars, etc.). The goal is to offer a rich enough set for the common data types of Unix, extensible with plug-ins. The back-end streams display output to the front-end, as a series of objects and commands.
</p>

<p>
I should stress that despite WebKit it is not my intent to make HTML the lingua franca of Unix. The front-end is merely implemented in it, as it makes it instantly accessible to anyone with HTML/CSS knowledge.
</p>

<h2>Pipes</h2>

<p>
  Unix pipes are anonymous binary streams, and each process comes with at least three: Standard In, Standard Out and Standard Error. This corresponds to the typical <em>Input</em> > <em>Processing</em> > <em>Output</em> model, with an additional error channel. However, in actual usage, there are two very different scenarios.
</p>

<p>
<img src="/files/termkit/termkit-3.png" alt="">
</p>

<p>
One is the case of interactive usage: a human watches the program output (from Std Out) on a display, and types keystrokes to interact with it (into Std In). Another case is the data processing job: a program accepts a data stream in a particular format on Std In, and immediately outputs a related data stream on Std Out. These two can be mixed, in that a chain of piped commands can have a human at either end, though usually this implies non-interactive operation.
</p>

<p>
These two cases are shoehorned into the same pipes, but happen quite differently. Human input is spontaneous, sporadic and error prone. Data input is strictly formatted and continuous. Human output is ambiguous, spaced out and wordy. Data output is conservative and monolithic.
</p>

<p>
As a result, many Unix programs have to be careful about data. For example, many tools dynamically detect whether they are running in interactive mode, and adjust their output to be more human-friendly or computer-friendly. Other tools come with flags to request the input/output in specific formats.
</p>

<p>
This has lead to "somewhat parseable text" being the default interchange format of choice. This seems like an okay choice, until you start to factor in the biggest lesson learned on the web: there is no such thing as plain text. Text is messy. Text-based formats lie at the basis of every SQL injection, XSS exploit and encoding error. And it's in text-parsing code where you'll likely find buffer overflows.
</p>

<p>
What this means in practice is that in every context, there are some forbidden characters, either by convention or by spec. For example, no Unicode or spaces in filenames. In theory, it's perfectly fine, but in practice, there's at least one shell script on your system that would blow up if you tried. Despite the promise of text as the universal interchange format, we've been forced to impose tons of vague limits.
</p>

<p>
So how do we fix this? By separating the "data" part from the "human" part. Then we can use messy text for humans, and pure data for the machines. Enter "Data In/Out", "View In/Out".
</p>

<p>
<img src="/files/termkit/termkit-4.png" alt="TermKit data flow diagram">
</p>

<p>
The data pipes correspond to the classical Std pipes, with one difference: the stream is prefixed with MIME-like headers (Content-Type, Content-Length, etc). Of these, only the 'Content-Type' is required. It allows programs to know what kind of input they're receiving, and handle it graciously without sniffing. Aside from that, the data on the pipe is a raw binary stream.
</p>

<p>
The view pipes carry the display output and interaction to the front-end. Widgets and UI commands are streamed back and forth as JSON messages over the view pipes.
</p>

<p>
The real magic happens when these two are combined. The last dangling Std Out pipe of any command chain needs to go into the Terminal, to be displayed as output. But the data coming out of Data Out is not necessarily human-friendly.
</p>

<p>
Thanks to the MIME-types, we can solve this universally. TermKit contains a library of output formatters which each handle a certain type of content (text, code, images, ...). It selects the right formatter based on the Content-Type, which then generates a stream of view updates. These go over the View Out pipe and are added to the command output.
</p>

<p>
<img src="/files/termkit/termkit-7.png" alt="Cat'ing an image">
</p>

<p>
As a result, you can <code>cat</code> a PNG and have it just work. TermKit <code>cat</code> doesn't know how to process PNGs or HTML—it only guesses the MIME type based on the filename and pipes the raw data to the next process. Then the formatter sends the image to the front-end. If you <code>cat</code> a source code file, it gets printed with line numbers and syntax highlighting.
</p>

<p>
So where does "somewhat parseable text" fit in? It turns out to be mostly unnecessary. Commands like <code>ls</code> output structured data by nature, i.e. a listing of files from one or more locations. It makes sense to pipe around this data in machine-form. Output flags like <code>ls&nbsp;-l</code> become mere hints for the final display, which can toggle on-the-fly between compact and full listing.
</p>

<p>
In TermKit's case, JSON is the interchange format of choice. The <code>Content-Type</code> for file listings is <code>application/json;&nbsp;schema=termkit.files</code>. The <code>schema</code> acts as a marker to select the right output plug-in. In this case, we want the file formatter rather than the generic raw JSON formatter.
</p>

<p>
<img src="/files/termkit/termkit-8.png" alt="Formatting data in TermKit">
</p>

<p>
Isn't JSON data harder to work with than lines of text? Only in some ways, but parsing JSON is trivial these days in any language. Because of this, I built TermKit <code>grep</code> so it supports grepping JSON data recursively. This happens transparently when the input is <code>application/json</code> instead of <code>text/plain</code>. As a result <code>ls&nbsp;|&nbsp;grep</code> works as you'd expect it to.
</p>

<p>
To slot in traditional Unix utilities in this model, we can pipe their data as <code>application/octet-stream</code> to start with, and enhance specific applications with type hints and wrapper scripts.
</p>

<p>
<img src="/files/termkit/termkit-6.png" alt="">
</p>

<p>
Finally, having type annotations on pipes opens up another opportunity: it allows us to pipe in HTTP GET / POST requests almost transparently. Getting a URL becomes no different from catting a file, and both can have fancy progress bars, even when inside a pipe chain like <code>get&nbsp;|&nbsp;grep</code>.
</p>

<h2>Synchronous interaction</h2>

<p>
All interaction in a traditional terminal is synchronous. Only one process is interactive at a time, and each keystroke must be processed by the remote shell before it is displayed. This leads to an obvious daily frustration: SSH keystroke lag.
</p>

<p>
To fix this, TermKit is built out of a separate front-end and back-end. The front-end can run locally, controlling a back-end on a remote machine. The connection can be tunneled over SSH for security.
</p>

<p class="tc">
<a href="https://github.com/unconed/TermKit/raw/master/Mockups/Architecture.pdf"><img src="/files/termkit/termkit-9.png" alt="TermKit architecture">Architecture diagram</a> (TK stands for TermKit)
</p>

<p>
Additionally, all display updates and queries are asynchronous. The WebKit-based HTML display is split up into component views, and the view pipes of each subprocess are routed to their own view. Vice-versa, any interactive widgets inside a view can send callback messages back to their origin process, as long as it's still running.
</p>

<p>
This also allows background processes to work without overflowing the command prompt.
</p>

<h2>String-based command line</h2>

<p>
A lot of my frustration comes from bash's arcane syntax. It has a particularly nasty variant of C-style escaping. Just go ahead and <em>try</em> to match a regular expression involving both types of quotes.
</p>

<p>
But at its core, a bash command is a series of tokens. Some tokens are single words, some are flags, some are quoted strings, some are modifiers (like | and >). It makes sense for the input to reflect this.
</p>

<p>
<img src="/files/termkit/termkit-5.png" alt="TermKit command-line">
</p>

<p>
TermKit's input revolves around tokenfield.js, a new snappy widget with plenty of tricks. It can do auto-quoting, inline autocomplete, icon badges, and more. It avoids the escaping issue altogether, by always processing the command as tokens rather than text. Keys that trigger special behaviors (like a quote) can be pressed again to undo the behavior and just type one character.
</p>

<p>
The behaviors are encoded in a series of objects and regexp-based triggers, which transform and split tokens as they are typed. That means it's extensible too.
</p>

<h2>Usability</h2>

<p>
At the end of the day, Unix just has bad usability. It tricks us with unnecessary abbreviations, inconsistent arguments (-r vs -R) and nitpicky syntax. Additionally, Unix has a habit of giving you raw data, but not telling you useful facts, e.g. 'r-xr-xr-x' instead of "You can't touch this" (<em>ba-dum tsshh</em>).
</p>

<p>
One of the Unix principles is nobly called "Least Surprise", but in practice, from having observed new Unix users, I think it often becomes "Maximum Confusion". We should be more pro-active in nudging our users in the right direction, and our tools should be designed for maximum discoverability.
</p>

<p>
For example, I want to see the relevant part of a man page in a tooltip when I'm typing argument switches. I'd love for dangerous flags to be highlighted in red. I'd love to see regexp hints of possible patterns inline.
</p>

<p>
There's tons to be done here, but we can't do anything without modern UI abilities.
</p>

<h2>Focus and Status</h2>

<p>
With a project like TermKit, it's easy to look at the shiny exterior and think "meh", or that I'm just doing things differently for difference's sake. But to me, the real action is under the hood. With a couple of tweaks and some uncompromising spring cleaning, we can get Unix to do a lot more for us.
</p>

<p>
The current version of TermKit is just a rough alpha, and what it does is in many ways just parlour tricks compared to what it could be doing in a few months. The architecture definitely supports it.
</p>

<p>
I've worked on TermKit off and on for about a year now, so I'd love to hear feedback and ideas. Please <a href="http://github.com/unconed/TermKit">go check out the code</a>.
</p>

<p>
TermKit owes its existence to Node.js, Socket.IO, jQuery and WebKit. Thanks to everyone who has contributed to those projects.
</p>

<p>
<em>Edit, a couple of quick points:</em>
</p>

<ul>
<li>A Linux port will definitely happen, since it's built out of WebKit and Node.js. Whoever does it first gets a cookie.</li>
<li>TermKit is not tied to JSON except in its own internal communication channels. TermKit Pipes can be in any format, and old-school plain-text still works. JSON just happens to be very handy and very lightweight.</li>
<li>The current output is just a proof of concept and lacks many planned usability enhancements. There are mockups on github.</li>
<li>If you're going to tell me I'm stupid, please read all the other 100 comments doing so first, so we can keep this short for everyone else.</li>
</ul>

<p>
<em>Edit, random fun:</em>
</p>

<p>Someone asked for AVS instead of TermKit in the comments... best I could do was JS1K with a PDF surprise:</p>

<iframe style="margin: 0 auto;" width="425" height="349" src="http://www.youtube.com/embed/dAeZTgRuWsU" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

<iframe width="560" height="349" src="http://www.youtube.com/embed/_6Z5dnlfcls" frameborder="0" allowfullscreen="allowfullscreen"></iframe>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Wiki TL;DR – WikiLeaks Reader]]></title>
    <link href="http://acko.net/blog/wiki-tldr-cable-optimizer/"/>
    <updated>2010-12-23T00:00:00-08:00</updated>
    <id>http://acko.net/blog/wiki-tldr-cable-optimizer</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Wiki TL;DR – WikiLeaks Reader</h1>
  
  <aside class="r"><a href="https://github.com/unconed/WikiTLDR/raw/master/wikitldr.png"><img src="/files/wikitldr/wikitldr-small.png" alt="Wiki TL;DR" /></a>
</aside>

<p>
<strong>Wiki TL;DR</strong> is an extension for Safari and Chrome. It replaces the drab data dumps of WikiLeaks' Cablegate with richly formatted pages optimized for reading.
</p>

<ul>
<li><a href="https://github.com/downloads/unconed/WikiTLDR/WikiTLDR.crx">Wiki TL;DR for Chrome</a> (0.3)</li>
<li><a href="https://github.com/downloads/unconed/WikiTLDR/WikiTLDR.safariextz">Wiki TL;DR for Safari</a> (0.3)</li>
</ul>

<p>
Abbreviations are expanded, text is reflowed, a map is added and the entire page is laid out with a clean design. Security clearances and message priorities are indicated. A summary is included on top.
</p>

<p>
It's also got a variety of formatting rules. So far the majority of cables work perfectly, but <a href="http://acko.net/blog/wiki-tl-dr-available-for-chrome-and-safari">feedback is welcome</a>. Source code is <a href="https://github.com/unconed/WikiTLDR/">available on github</a>.</p>

<p>
  <em>Note: this extension only works if you access wikileaks using the <code>wikileaks.ch</code> domain, instead of <code>wikileaks.org</code>. I'll update it soon.
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My JS1K Demo - The Making Of]]></title>
    <link href="http://acko.net/blog/js1k-demo-the-making-of/"/>
    <updated>2010-08-06T00:00:00-07:00</updated>
    <id>http://acko.net/blog/js1k-demo-the-making-of</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<h1>My JS1K Demo - The Making Of</h1>

<p>If you haven't seen it yet, check out the <a href="http://js1k.com">JS1K demo contest</a>. The goal is to do something neat in 1 kilobyte of JavaScript code.</p>
<p>I couldn't resist making one myself, so I pulled out my bag of tricks from my <a href="/design/avs">Winamp music visualization</a> days and started coding. I'm really happy with how it turned out. And no, it won't work in Internet Explorer 8 or less.</p>

<p><em>Edit: OH SNAP! I just rewrote the demo to include volumetric light beams and still fit in 1K:</em></p>

</div></div>

<div class="g6"><div class="pad">

<h3>Original Version</h3>
<p><iframe id="1kjs" frameborder="0" style="background:#000;border:0" width="510" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="1kjs-stop" onclick="$('#1kjs').attr('src','about:blank');$(this).hide();$('#1kjs-start').show();">Stop Demo</button><button id="1kjs-start" onclick="$('#1kjs').attr('src','/files/making-of-js1k/1022b.html');$(this).hide();$('#1kjs-stop').show();">Start Demo</button><button onclick="$('#1kjs').attr('src','/files/making-of-js1k/1022s.html');$('#1kjs-start').show();$('#1kjs-stop').hide();">View Source</button></p>

</div></div>
<div class="g6"><div class="pad">

<h3>Improved Version</h3>

<p><iframe id="1kjs2" frameborder="0" style="background:#000;border:0" width="510" height="345" src="about:blank"></iframe></p>
<p><button style="display: none" id="1kjs2-stop" onclick="$('#1kjs2').attr('src','about:blank');$(this).hide();$('#1kjs2-start').show();">Stop Demo</button><button id="1kjs2-start" onclick="$('#1kjs2').attr('src','/files/making-of-js1k/1024b.html');$(this).hide();$('#1kjs2-stop').show();">Start Demo</button><button onclick="$('#1kjs2').attr('src','/files/making-of-js1k/1024s.html');$('#1kjs2-start').show();$('#1kjs2-stop').hide();">View Source</button></p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>Now, whenever size is an issue, the best way to make a small program is to generate all data on the fly, i.e. procedurally. This saves valuable storage space. While this might seem like a black art, often it just comes down to clever use of (high school) math. And as is often the case, the best tricks are also the simplest, as they use the least amount of code.</p>
<p>To illustrate this, I'm going to break down my demo and show you all the major pieces and shortcuts used. Unlike the actual 1K demo, the code snippets here will feature legible spacing and descriptive variable names.</p>

<h3>Initialization</h3>

<p>JS1K's rules give you a Canvas tag to work with, so the first piece of code initializes it and makes it fill the window.</p>
<p>From then on, it just renders frames of the demo. There are four major parts to this:</p>
<ul>
<li>Animating the wires</li>
<li>Rotating and projecting the wires into the camera view</li>
<li>Coloring the wires</li>
<li>Animating the camera</li>
</ul>
<p>All of this is done 30 times per second, using a normal <code>setInterval</code> timer:</p>
<p class="codeblock"><code>setInterval(function () { ... }, 33);</code></p>

<h3>Drawing Wires</h3>

<p>The most obvious trick is that everything in the demo is drawn using only a single primitive: a line segment of varying color and stroke width. This allows the whole drawing process to be streamlined into two tight, nested loops. Each inner iteration draws a new line segment from where the previous one ended, while the outer iteration loops over the different wires.</p>

<p><img src="/files/making-of-js1k/drawing-process.png" alt="drawing process for demo" title="Drawing each wire in sequence."></p>

<p>The lines are blended additively, using the built-in 'lighten' mode, which means they can be drawn in any order. This avoids having to manually sort them back-to-front.</p>
<p>To simplify the perspective transformations, I use a coordinate system that places the point (0, 0) in the center of the canvas and ranges from -1 to 1 in both coordinates. This is a compact and convenient way of dealing with varying window sizes, without using up a lot of code:</p>
<p class="codeblock"><code>with (graphics) {<br />&nbsp; ratio = width / height;<br />&nbsp; globalCompositeOperation = &#039;lighter&#039;;<br />&nbsp; scale(width / 2 / ratio, height / 2);<br />&nbsp; translate(ratio, 1);<br />&nbsp; lineWidthFactor = 45 / height;<br />&nbsp; ...</code></p>
<p>I also add a correction <code>ratio</code> for non-square windows and calculate a reference line width <code>lineWidthFactor</code> for later.</p>
<p>Then there's the two nested <code>for</code> loops: one iterating over the wires, and one iterating over the individual points along each wire. In pseudo-code they look like:</p>
<p class="codeblock"><code>For (12 wires =&gt; wireIndex) {<br />&nbsp; Begin new wire<br />&nbsp; For (45 points along each wire =&gt; pointIndex) {<br />&nbsp;&nbsp;&nbsp; Calculate path of point on a sphere: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Extrude outwards in swooshes: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Translate and Rotate into camera view: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Project to 2D: (x,y)<br />&nbsp;&nbsp;&nbsp; Calculate color, width and luminance of this line: (r,g,b) (w,l)<br />&nbsp;&nbsp;&nbsp; If (this point is in front of the camera) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (the last point was visible) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Draw line segment from last point to (x,y)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mark this point as invisible<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; Mark beginning of new line segment at (x,y)<br />&nbsp; }<br />}</code></p>

<h3>Mathbending</h3>

<p>To generate the wires, I start with a formula which generates a sinuous path on a sphere, using latitude/longitude. This controls the tip of each wire and looks like:</p>
<p class="codeblock"><code>offset = time - pointIndex * 0.03 - wireIndex * 3;<br />longitude = cos(offset + sin(offset * 0.31)) * 2<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + sin(offset * 0.83) * 3 + offset * 0.02;<br />latitude = sin(offset * 0.7) - cos(3 + offset * 0.23) * 3;</code></p>
<p>This is classic procedural coding at its best: take a time-based <code>offset</code> and plug it into a random mish-mash of easily available functions like cosine and sine. Tweak it until it 'does the right thing'. It's a very cheap way of creating interesting, organic looking patterns.</p>
<p>This is more art than science, and mostly just takes practice. Any time spent with a graphical calculator will definitely pay off, as you will know better which mathematical ingredients result in which shapes or patterns. Also, there are a couple of things you can do to maximize the appeal of these formulas.</p>
<p>First, always include some non-linear combinations of operators, e.g. nesting the sin() inside the cos() call. Combined, they are more interesting than when one is merely overlaid on the other. In this case, it turns regular oscillations into time-varying frequencies.</p>
<p>Second, always scale different wave periods using prime numbers. Because primes have no factors in common, this ensures that it takes a very long time before there is a perfect repetition of all the individual periods. Mathematically, the <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+31+83+70+23%29%29+%2F+100">least common multiple of the chosen periods</a> is huge (414253 units ~ 4.8 hours). Plotting the longitude/latitude for <code>offset = 0..600</code> you get:</p>
<p><img src="/files/making-of-js1k/js1k-2.jpg" alt="a pseudo-random set of oscillations" title="Looks pretty random"></p>
<p>The graph looks like a random tangled curve, with no apparent structure, which makes for motions that never seem to repeat. If however, you reduce each constant to only a single significant digit (e.g. 0.31 -> 0.3, 0.83 -> 0.8), then suddenly repetition becomes apparent:</p>
<p><img src="/files/making-of-js1k/js1k-3.jpg" alt="a not so pseudo-random set of oscillations" title="Not so random"></p>
<p>This is because the least common multiple has <a href="http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+30+80+70+20%29%29+%2F+100">dropped to 84 units ~ 3.5 seconds</a>. Note that both formulas have the same code complexity, but radically different results. This is why all procedural coding involves some degree of creative fine tuning.</p>
<h3>Extrusion</h3>

<p>Given the formula for the tip of each wire, I can generate the rest of the wire by sweeping its tail behind it, delayed in time. This is why <code>pointIndex</code> appears as a negative in the formula for <code>offset</code> above. At the same time, I move the points outwards to create long tails.</p>
<p>I also need to convert from lat/long to regular 3D XYZ, which is done using the <a href="http://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinate transform</a>:</p>
<p><img src="/files/making-of-js1k/js1k-4.jpg" alt="spherical coordinates"><center>Source: <a href="http://en.wikipedia.org/wiki/File:Coord_system_SE_0.svg">Wikipedia</a></center></p>
<p class="codeblock"><code>distance = f.sqrt(pointIndex+.2);<br />x = cos(longitude) * cos(latitude) * distance;<br />y = sin(longitude) * cos(latitude) * distance;<br />z = sin(latitude) * distance;</code></p>
<p>You might notice that rather than making <code>distance</code> a straight up function of the length <code>pointIndex</code> along the wire, I applied a square root. This is another one of those procedural tricks that seems arbitrary, but actually serves an important visual purpose. This is what the square root looks like (solid curve):</p>
<p><img src="/files/making-of-js1k/js1k-5.jpg" alt="square root"></p>
<p>The dotted curve is the square root's derivative, i.e. it indicates the slope of the solid curve. Because the slope goes down with increasing distance, this trick has the effect of slowing down the outward motion of the wires the further they get. In practice, this means the wires are more tense in the middle, and more slack on the outside. It adds just enough faux-physics to make the effect visually appealing.</p>
<h3>Rotation and Projection</h3>

<p>Once I have absolute 3D coordinates for a point on a wire, I have to render it from the camera's point of view. This is done by moving the origin to the camera's position (X,Y,Z), and applying two rotations: one around the vertical (yaw) and one around the horizontal (pitch). It's like spinning on a wheely chair, while tilting your head up/down.</p>
<p class="codeblock"><code>x -= X; y -= Y; z -= Z;<br /><br />x2 = x * cos(yaw) + z * sin(yaw);<br />y2 = y;<br />z2 = z * cos(yaw) - x * sin(yaw);<br /><br />x3 = x2;<br />y3 = y2 * cos(pitch) + z2 * sin(pitch);<br />z3 = z2 * cos(pitch) - y2 * sin(pitch);</code></p>
<p>The camera-relative coordinates are projected in perspective by dividing by Z — the further away an object, the smaller it is. Lines with negative Z are behind the camera and shouldn't be drawn. The width of the line is also scaled proportional to distance, and the first line segment of each wire is drawn thicker, so it looks like a plug of some kind:</p>
<p class="codeblock"><code>plug = !pointIndex;<br />lineWidth = lineWidthFactor * (2 + plug) / z3;<br />x = x3 / z3;<br />y = y3 / z3;<br /><br />lineTo(x, y);<br />if (z3 &gt; 0.1) {<br />&nbsp; if (lastPointVisible) {<br />&nbsp;&nbsp;&nbsp; stroke();<br />&nbsp; }<br />&nbsp; else {<br />&nbsp;&nbsp;&nbsp; lastPointVisible = true;<br />&nbsp; }<br />}<br />else {<br />&nbsp; lastPointVisible = false;<br />}<br />beginPath();<br />moveTo(x, y);</code></p>
<h3>Coloring</h3>
<p>Each line segment also needs an appropriate coloring. Again, I used some trial and error to find a simple formula that works well. It uses a sine wave to rotate overall luminance in and out of the (Red, Green, Blue) channels in a deliberately skewed fashion, and shifts the R component slowly over time. This results in a nice varied palette that isn't overly saturated.</p>
<p class="codeblock"><code>pulse = max(0, sin(time * 6 - pointIndex / 8) - 0.95) * 70;<br />luminance = round(45 - pointIndex) * (1 + plug + pulse);<br />strokeStyle=&#039;rgb(&#039; + <br />&nbsp; round(luminance * (sin(plug + wireIndex + time * 0.15) + 1)) + &#039;,&#039; + <br />&nbsp; round(luminance * (plug + sin(wireIndex - 1) + 1)) + &#039;,&#039; +<br />&nbsp; round(luminance * (plug + sin(wireIndex - 1.3) + 1)) +<br />&nbsp; &#039;)&#039;;</code></p>
<p>Here, <code>pulse</code> causes bright pulses to run across the wires. I start with a regular sine wave over the length of the wire, but truncate off everything but the last 5% of each crest to turn it into a sparse pulse train:</p>
<p><img src="/files/making-of-js1k/js1k-6.jpg" alt="sine pulse train"></p>
<h3>Camera Motion</h3>

<p>With the main visual in place, almost all my code budget is gone, leaving very little room for the camera. I need a simple way to create consistent motion of the camera's X, Y and Z coordinates. So, I use a neat low-tech trick: repeated interpolation. It looks like this:</p>
<p class="codeblock"><code>sample += (target - sample) * fraction</code></p>
<p><code>target</code> is set to a random value. Then, every frame, <code>sample</code> is moved a certain fraction towards it (e.g. <code>0.1</code>). This turns <code>sample</code> into a smoothed version of <code>target</code>. Technically, this is a <em>one-pole low-pass filter</em>.</p>
<p>This works even better when you apply it twice in a row, with an intermediate value being interpolated as well:</p>
<p class="codeblock"><code>intermediate += (target - intermediate) * fraction<br />sample += (intermediate - sample) * fraction</code></p>
<p>A sample run with <code>target</code> being changed at random might look like this:</p>
<p><img src="/files/making-of-js1k/js1k-7.jpg" alt="sine pulse train"></p>
<p>You can see that with each interpolation pass, more discontinuities get filtered out. First, jumps are turned into kinks. Then, those are smoothed out into nice bumps.</p>
<p>In my demo, this principle is applied separately to the camera's X, Y and Z positions. Every ~2.5 seconds a new target position is chosen:</p>
<p class="codeblock"><code>if (frames++ &gt; 70) {<br />&nbsp; Xt = random() * 18 - 9;<br />&nbsp; Yt = random() * 18 - 9;<br />&nbsp; Zt = random() * 18 - 9;<br />&nbsp; frames = 0;<br />}<br /><br />function interpolate(a,b) {<br />&nbsp; return a + (b-a) * 0.04;<br />}<br /><br />Xi = interpolate(Xi, Xt);<br />Yi = interpolate(Yi, Yt);<br />Zi = interpolate(Zi, Zt);<br /><br />X&nbsp; = interpolate(X,&nbsp; Xi);<br />Y&nbsp; = interpolate(Y,&nbsp; Yi);<br />Z&nbsp; = interpolate(Z,&nbsp; Zi);</code></p>
<p>The resulting path is completely smooth and feels quite dynamic.</p>
<h3>Camera Rotation</h3>

<p>The final piece is orienting the camera properly. The simplest solution would be to point the camera straight at the center of the object, by calculating the appropriate <code>pitch</code> and <code>yaw</code> directly off the camera's position (X,Y,Z):</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X);<br />pitch = atan2(Y, sqrt(X * X + Z * Z));</code></p>
<p>However, this gives the demo a very static, artificial appearance. What's better is making the camera point in the right direction, but with just enough freedom to pan around a bit.</p>
<p>Unfortunately, the 1K limit is unforgiving, and I don't have any space to waste on more 'magic' formulas or interpolations. So instead, I cheat by replacing the formulas above with:</p>
<p class="codeblock"><code>yaw&nbsp;&nbsp; = atan2(Z, -X * 2);<br />pitch = atan2(Y * 2, sqrt(X * X + Z * Z));</code></p>
<p>By multiplying X and Y by 2 strategically, the formula is 'wrong', but the error is limited to about 45 degrees and varies smoothly. Essentially, I gave the camera a lazy eye, and got the perfect dynamic motion with only 4 bytes extra!</p>
<h3>Addendum</h3>

<p>After seeing the other demos in the contest, I wasn't so sure about my entry, so I started working on a version 2. The main difference is the addition of glowy light beams around the object.</p>
<p>As you might suspect, I'm cheating massively here: rather than do physically correct light scattering calculations, I'm just using a 2D effect. Thankfully it comes out looking great.</p>
<p>Essentially, I take the rendered image, and process it in a second Canvas that is hidden. This new image is then layered on the original.</p>
<p>I take the image and repeatedly blend it with a zoomed out copy of itself. With every pass the number of copies doubles, and the zoom factor is squared every time. After 3 passes, the image has been smeared out into an 8 = 2<sup>3</sup> 'tap' radial blur. I lock the zooming to the center of the 3D object. This makes the beams look like they're part of the 3D world rather than drawn on later.</p>
<p>For additional speed, the beam image is processed at half the resolution. As a side effect, the scaling down acts like a slight blur filter for the beams.</p>
<p>Unfortunately, this effect was not very compact, as it required a lot of drawing mode changes and context switches. I had no room for it  in the source code.</p>
<p>So, I had to squeeze out some more room in the original. First, I simplified the various formulas to the bare minimum required for interesting visuals. I replaced the camera code with a much simpler one, and started aggressively shaving off every single byte I could find. Then I got creative, and ended up recreating the secondary canvas every frame just to avoid switching back its state to the default.</p>
<p>Eventually, after a lot of bit twiddling, a version came out that was 1024 bytes long. I had to do a lot of unholy things to get it to fit, but I think the end result is worth it ;). </p>
<h3>Closing Thoughts</h3>

<p>I've long been a fan of the demo scene, and fondly remember <a href="http://www.youtube.com/watch?v=dQveVMQDJlg">Second Reality</a> in 1993 as my introduction to the genre. Since then, I've always looked at math as a tool to be mastered and wielded rather than subject matter to be absorbed.</p>
<p>With this blog post, I hope to inspire you to take the plunge and see where some simple formulas can take you.</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 4 - The Devil's in the Details]]></title>
    <link href="http://acko.net/blog/making-worlds-4-the-devils-in-the-details/"/>
    <updated>2009-12-25T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-worlds-4-the-devils-in-the-details</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Making Worlds 4 - The Devil's in the Details</h1>
  
<aside class="r"><img src="/files/making-worlds/planet-3-th.jpg" alt=""/></aside>

<p>Last time I'd reached a pretty neat milestone: being able to render a somewhat realistic rocky surface from space. The next step is to add more detail, so it still looks good up close.
</p>

<p>
Adding detail is, at its core, quite straightforward. I need to increase the resolution of the surface textures, and further subdivide the geometry. Unfortunately I can't just crank both up, because the resulting data is too big to fit in graphics memory. Getting around this will require several changes.
</p>

<h3>Strategy</h3>

<p>
Until now, the level-of-detail selection code has only been there to decide which portions of the planet should be <em>drawn</em> on screen. But the geometry and textures to choose from are all prepared up front, at various scales, before the first frame is started. The surface is generated as one high-res planet-wide map, using typical cube map rendering:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering.png" alt="" title="Rendering an entire cube face at a time" />
</p>

<p>
This map is then divided into a quad-tree structure of surface tiles. It allows me to adaptively draw the surface at several pre-defined levels of detail, in chunks of various sizes.
<!--break-->
</p>

<p class="tc">
<img src="/files/making-worlds/planets-1-quadtree.png" alt="Quadtree terrain" /><small><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Source</a></small>
</p>

<p>
This strategy won't suffice, because each new level of detail doubles the work up-front, resulting in exponentially increasing time and memory cost. Instead, I need to write an adaptive system to generate and represent the surface on the fly. This process is driven by the Level-of-Detail algorithm deciding if it needs more detail in a certain area. Unlike before, it will no longer be able to make snap decisions and instant transitions between pre-loaded data: it will need to wait several frames before higher detail data is available.
</p>

<p>
<img src="/files/making-worlds/planet-lod-tree.png" alt="Configuration of chunks to render" />
</p>

<p>
Uncontrolled growth of increasingly detailed tiles is not acceptable either: I only wish to maintain tiles useful for rendering views from the current camera position. So if a specific detailed portion of the planet is no longer being used—because the camera has moved away from it—it will be discarded to make room for other data.
</p>

<h2>Generating Individual Tiles</h2>

<p>
The first step is to be able to generate small portions of the surface on demand. Thankfully, I don't need to change all that much. Until now, I've been generating the cube map one cube face at a time, using a virtual camera at the middle of the cube. To generate only a portion of the surface, I have to narrow the virtual camera's viewing cone and skew it towards a specific point, like so:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering-subdivision.png" alt="" title="Rendering one face tile at a time" />
</p>

<p>
This is easy using a mathematical trick called <a href="http://en.wikipedia.org/wiki/Homogeneous_coordinates">homogeneous coordinates</a>, which are commonly used in 3D engines. This turns 2D and 3D vectors into respectively 3D and 4D. Through this dimensional redundancy, we can then represent most geometrical transforms as a 4x4 matrix multiplication. This covers all transforms that translate, scale, rotate, shear and project, in any combination. The right sequence (i.e. multiplication) of transforms will map regular 3D space onto the skewed camera viewing cone.
</p>

<p>
Given the usual centered-axis projection matrix, the off-axis projection matrix is found by multiplying with a scale and translate matrix in so-called "screen space", i.e. at the very end. The thing with homogeneous coordinates is that it seems like absolute crazy talk until you get it. I can only recommend you read a <a href="http://www.cim.mcgill.ca/~langer/558/lecture3.pdf">good introduction to the concept</a>.
</p>

<p>
With this in place, I can generate a zoomed height map tile anywhere on the surface. As long as the underlying brushes are detailed enough, I get arbitrarily detailed height textures for the surface. The normal map requires a bit more work however. 
</p>

<h3>Normals and Edges</h3>

<p>
As I described in <a href="http://acko.net/blog/making-worlds-3-thats-no-moon">my last entry</a>, normals are generated by comparing neighbouring samples in the height map. At the edges of the height map texture, there are no neighbouring samples to use. This wasn't an issue before, because the height map was a seamless planet-wide cube map, and samples were fetched automatically from adjacent cube faces. In an adaptive system however, the map resolution varies across the surface, and there's no guarantee that those neighbouring tiles will be available at the desired resolution.
</p>

<p>
The easy way out is to make sure the process of generating any single tile is entirely self-sufficient. To do this, I expand each tile with a 1 pixel border when generating it. Each such tile is a perfectly dilated version of its footprint and overlaps with its neighbours in the border area:
</p>

<p>
<img src="/files/making-worlds/cubemap-rendering-subdivision-dilated.png" alt="" title="Rendering one face tile at a time" />
</p>

<p>
This way all the pixels in the undilated area have easily accessible neighbour pixels to sample from. This border is only used during tile generation, and cropped out at the end. Luckily I did something similar when I <a href="http://acko.net/blog/making-worlds-2-scaling-heights">played with dilated cube maps</a> before, so I already had the technique down. When done correctly, the tiles match up seamlessly without any additional correction.
</p>

<h3>Adaptive Tree</h3>

<p>
Now I need to change the data structure holding the mesh. To make it adaptive, I've rewritten it in terms of real-time 'split' and 'merge' operations.
</p>

<p>
Just like before, the Level-of-Detail algorithm traverses the tree to determine which tiles to render. But if the detail available is not sufficient, the algorithm can decide that a certain tile in the tree needs a more detailed surface texture, or that its geometry should be split up further. Starting with only a single root tile for each cube face, the algorithm divides up the planet surface recursively, quickly converging to a stable configuration around the camera.
</p>

<p>
As the camera moves around, new tiles are generated, increasing memory usage. To counter this steady stream of new data, the code identifies tiles that fall into disuse and merges them back into their parent. The overall effect is that the tree grows and shrinks depending on the camera position and angle.
</p>

<h3>Queuing and scheduling</h3>

<p>
To do all this real-time, I need to queue up the various operations that modify the tree, such as 'split', 'merge' and 'generate new tile'. They need to be executed in between rendering regular frames on screen. Whenever the renderer decides a certain tile is not detailed enough, a request is placed in a job queue to address this.
</p>

<p>
While continuing to render regular frames, these requests need to be processed. This is harder than it sounds, because both planet rendering and planet generation have to share the GPU, preferably without causing major stutters in rendering speed.
</p>

<p>
The solution is to spread this process over enough visible frames so that the overal rendering speed is not significantly affected. For example, if a new surface texture is requested, several passes are made. First the height map is rendered, the next frame the normal map is derived from it, then the height/normal maps are analyzed and put into the tree, after which they will finally appear on screen:
</p>

<p>
<img src="/files/making-worlds/queued-frame-pipeline.png" alt="" title="Rendering one frame" />
</p>

<p>
I took some inspiration from <a href="http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf">id Tech 5</a>, the next engine coming from technology powerhouse id Software. They describe a queued job system that covers any frame-to-frame computation in a game engine (from texture management to collision detection), and which schedules tasks intelligently.
</p>

<h2>Do the Google Earth</h2>

<p>
With all the above in place, the engine can now progressively increase the detail of the planet across several orders of magnitude. Here's a video that highlights it:
</p>

<p>
<object width="560" height="380"><param name="movie" value="http://www.youtube.com/v/ck27Xu5XAJE&amp;hl=en_US&amp;fs=1&amp;"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/ck27Xu5XAJE&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="380"></embed></object>
</p>

<p>
And some shots that show off the detail:
</p>

</div></div>

<div class="g12"><div class="pad">

<p class="flat">
<img src="/files/making-worlds/planet-1.jpg" alt=""/>
</p>

<p class="flat">
<img src="/files/making-worlds/planet-2.jpg" alt=""/>
</p>

<p class="flat">
<img src="/files/making-worlds/planet-3.jpg" alt=""/>
</p>

</div></div>

<div class="g8 i2"><div class="pad">

<p>
W00t, certainly one of the niftiest things I've built.
</p>

<h3>Engine tweaks</h3>

<p>
Along with the architecture changes, I implemented some engine tweaks, noted here for completeness.
</p>

<p>
In previous comments, <a href="/blog/making-worlds-part-1-of-spheres-and-cubes"> Erlend suggested</a> using displacement mapping, so I gave it a shot. Before, the mesh for every tile was calculated on the CPU once, then copied into GPU memory. However, this mesh data was redundant, because it was derived literally from the height map data. Instead I changed it so that now, the transformation of mesh points onto the sphere surface happens real-time on the GPU in a per-vertex program.
</p>

<p>
This saves memory and pre-calculation time, but increases the rendering load. I'll have to see whether this technique is sustainable, but overall, it seems to be performing just fine. As a side effect, the terrain height map can be changed real-time with very low cost.
</p>

<h3>Technical hurdles</h3>

<p>
I spent some time tweaking the engine to run faster, but there's still plenty of work and some technical hurdles to cover.
</p>

<p>
One involves the Ogre Scene Manager, which is the code object that manages the location of objects in space. In my case, I have to deal with both the 'real world' in space as well as the 'virtual world' of brushes that generate the planet's surface. I chose to use two independent scene managers to represent this, as it seemed like a natural choice. However, it turns out this is <a href="http://www.ogre3d.org/mantis/view.php?id=130">unsupported</a> by Ogre and causes <a href="http://www.ogre3d.org/forums/viewtopic.php?p=189032">random crashes and edge cases</a>. Argh. It looks like I'll have to refactor my code to fix this.
</p>

<p>
Another major hurdle involves the planet surface itself. Currently I'm still just using a single distored-crater-brush to create it, and the lack of variation is showing.
</p>

<p>
Finally, surfaces are being generated using 16-bit floating point height values, and their accuracy is not sufficient beyond a couple levels of zooming. This results in ugly bands of flat terrain. To fix this I'll need to increase the surface accuracy.
</p>

<h2>Future steps</h2>

<p>
With the basic planet surface covered, I can now start looking at color, atmosphere and clouds. I have plenty of reading and experimentation to do. Thankfully the web is keeping me supplied with a steady stream of awesome papers... nVidia's GPU Gems series has proven to be a gold mine, for example.
</p>

<p>
Random factoid: what game developers call a "cube map", cartographers call a "cubic gnomonic grid". It turns out that knowing the right terminology is important when you're looking for reference material...
</p>

<h3>Code</h3>

<p>
The code is <a href="https://github.com/unconed/NFSpace">available on GitHub</a>.
</p>

<h3>References</h3>

<p>
Great ideas are best discovered when standing on the shoulders of giants:
</p>

<ul>
<li><a href="http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf">id Tech 5 Challenges, From Texture Virtualization to Massive Parallelization</a>, J.M.P. van Waveren, id Software</li>
<li><a href="http://developer.nvidia.com/object/gpu_gems_home.html">GPU Gems</a>, nVidia</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Kindle Faux PDF Zoom]]></title>
    <link href="http://acko.net/blog/kindle-faux-pdf-zoom/"/>
    <updated>2009-12-18T00:00:00-08:00</updated>
    <id>http://acko.net/blog/kindle-faux-pdf-zoom</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Kindle Faux PDF Zoom</h1><p>Through the miracle of xmas, I acquired a <a href="http://en.wikipedia.org/wiki/Amazon_Kindle">Kindle</a>. A sleek e-reader, but also a shameless vehicle for Amazon's digital book store. With the latest firmware installed, they do make for great PDF readers... in theory.
</p>

<p>
<img src="/files/kindle-zoom/kindle-fail.jpg" alt="Kindle PDF fail" title="Too small." />
</p>

<p>
  The good news is that the e-ink display on the Kindle is indeed pretty sweet. It works so well that the screen looks positively fake when it's not changing, as if it was just a display item in a shop somewhere. But the bad news is that the software needs a lot of love.
</p>

<p>
The included PDF reader for example has no zoom option. All you can do is toggle between portrait and landscape. Either way, normal sized text ends up tiny and barely readable.
</p>

<p>
Thankfully, we can still do it ourselves. Armed with PyPDF I wrote a simple script that takes a regular A4/Letter PDF and chops each page into four parts. You can pan through the document just by hitting next. Most of the stuff I read these days is academic, in the classic two column paper format, so this orders the sub-pages to match that.
</p>

<p>
The script is <a href="/files/kindle-zoom/rekindle.py.txt">available for download</a>. It requires Python and <a href="http://pybrary.net/pyPdf/">pyPdf</a>. Usage:
</p>
<p class="codeblock">
<code>python&nbsp;rekindle.py&nbsp;file.pdf
</code>
</p>

<p>
It will produce <code>file.kindle.pdf</code>. The code doesn't actually look at the contents, it just cuts blindy, so it might need adjustment for certain docs.
</p>

<p>
<img src="/files/kindle-zoom/kindle.jpg" alt="Kindle Zoom" title="Just right" />
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds - Intermission]]></title>
    <link href="http://acko.net/blog/making-worlds-intermission/"/>
    <updated>2009-11-07T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-worlds-intermission</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Making Worlds - Intermission</h1><p>Today at <a href="http://bazcampyvr.pbworks.com">BazCamp YVR</a> I gave a short presentation and demo of my "Making Worlds" project, as well as an overview of procedural content generation in general.
</p>

<p>
The <a href="/files/making-worlds/Making-Worlds-BazCampYVR.pdf">slides are available for download</a>.
</p>

<p>
<img src="/files/making-worlds/lightcycles.jpg" alt="Tron" />
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 3 - That's no Moon...]]></title>
    <link href="http://acko.net/blog/making-worlds-3-thats-no-moon/"/>
    <updated>2009-11-05T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-worlds-3-thats-no-moon</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Making Worlds 3 - That's no Moon...</h1><p>It's been over two months since the last installment in this series. Oops. Unfortunately, while trying to get to the next stage of this project, I ran into some walls. My main problem is that I'm not just creating worlds, but also learning to work with the <a href="http://www.ogre3d.org">Ogre engine</a> and modern graphics hardware in particular.
</p>

<p>
This presents some interesting challenges: between my own code and the pixels on the screen, there are no less than three levels of indirection. First, there's Ogre, a complex piece of C++ code that provides me with high-level graphics tools (i.e. objects in space). Ogre talks to OpenGL, which abstracts away low-level graphics operations (i.e. commands necessary to draw a single frame). The OpenGL calls are handed off to the graphics driver, which translates them into operations on the actual hardware (processing vertices and pixels in GPU memory). Given this long dependency chain, it's no surprise that when something goes wrong, it can be hard to pinpoint exactly where the problem lies. In my case, an oversight and misunderstanding of an Ogre feature lead to several days of wasted time and a lot of frustration that made me put aside the project for a while.
</p>

<p>
With that said, back to the planets...
</p>

<h2>Normal mapping</h2>

<p>
Last time, I ended with a bumpy surface, carved by applying brushes to the surface. The geometry was there, but the surface was still just solid white. To make it more visually interesting, I'm going to apply light shading.
</p>

<p>
The most basic information you need for shading a surface is the surface normal. This is the vector that points straight away from the surface at a particular point. For flat surfaces, the normal is the same everywhere. For curved surfaces, the normal varies continuously across the surface. Typical materials reflect the most light when the surface normal points straight at the light source. By comparing the surface normal with the direction of incoming light (using the vector <a href="http://en.wikipedia.org/wiki/Dot_product">dot product</a>), you can get a good measure of how bright the surface should be under illumination:
</p>

<p class="tc">
<img src="/files/making-worlds/planet-3-normal-lighting.png" alt="Schematic representation of surface shading with normals" />
Lighting a surface using its normals.
</p>

<p>
To use normals for lighting, I have two options. The first is to do this on a geometry basis, assigning a normal to every triangle in the planet mesh. This is straightforward, but ties the quality of the shading to the level of detail in the geometry. A second, better way is to use a normal map. You stretch an image over the surface, as you would for applying textures, but instead of color, each pixel in the image represents a normal vector in 3D. Each pixel's channels (red, green, blue) are used to describe the vector's X, Y and Z values. When lighting the surface, the normal for a particular point is found by looking it up in the normal map.
</p>

<p>
The benefit of this approach is that you can stretch a high resolution normal map over low resolution geometry, often with almost no visual difference.
</p>

<p class="tc">
<img src="/files/making-worlds/planet-3-normal-lighting-simple.png" alt="Schematic representation of surface shading with normals" />
Lighting a low-resolution surface using high-resolution normals.
</p>

<p>
Here's the technique applied to a real model:
</p>

<p class="tc">
<img src="/files/making-worlds/planet-3-normal-mapping-3d.png" alt="Normal mapping in practice" />
(<a href="http://en.wikipedia.org/wiki/File:Normal_map_example.png">Source</a> - Creative Commons Share-alike Attribution)
</p>

<p>
Normal mapping helps keep performance up and memory usage down.
</p>

<h3>Finding Normals</h3>

<p>
So how do you generate such a normal map, or even a single normal at a single point? There are many ways, but the basic principle is usually the same. First you calculate two different vectors which are tangent to the surface at the point in question. Then you use the <a href="http://en.wikipedia.org/wiki/Cross_product">cross product</a> to find a vector perpendicular to the two. This third vector is unique and will be the surface normal.
</p>

<p>
For triangles, you can pick any two triangle edges as vectors. In my case, the surface is described by a heightmap on a sphere, which makes things a bit trickier and requires some math.
</p>

<p>
I asked my friend <a href="http://twitter.com/mathseeker">Djun Kim</a>, Ph.D. and teacher of mathematics at UBC for help and he recommended <a href="http://en.wikipedia.org/wiki/Calculus_on_Manifolds_(book)">Calculus on Manifolds</a> by Michael Spivak. This deceptively small and thin book covers all the basics of calculus in a dense and compact way, and quickly became my new favorite reading material.
</p>

<h2>Differential Geometry</h2>

<p>
In this section, I'll describe the formulas needed to calculate the normals of a spherical heightmap. Unlike what I've written before, this will dive shamelessly into specifics and not eschew math. The reason I'm writing it down is because I couldn't find a complete reference online. If math scares you, this section might not be for you. Scroll down until you reach the crater, or take a detour by reading <a href="http://www.maa.org/devlin/LockhartsLament.pdf">A Mathematician's Lament</a> by Paul Lockhart, which will enlighten you.
</p>

<p>
First, we're going to derive normals for a regular flat terrain heightmap. To start, we need to define the terrain surface. Starting with a 2D heightmap, i.e. a function <em>f(u,v)</em> of two coordinates that returns a height value, we can create a 3 dimensional surface <em>g</em>:
</p>

<p>
<img src="/files/making-worlds/planet-3-terrain-mapping.png" alt="Mathematical formulation of heightmapping" />
</p>

<p>
We can use this formal description to find tangent and normal vectors. A vector is tangent when its direction matches the slope of the surface in a particular direction. Differential math tells us that slope is found by taking the derivative. For our function of 2 variables, that means we can find tangent vectors along curves of constant <em>v</em> or constant <em>u</em>. These curves are the thin grid lines in the diagram. Actually, we can find tangents along any line, in any direction. But along <em>u</em> and <em>v</em> lines, the other variable acts like a constant, which simplifies things.
</p>

<p>
To do this, we take partial derivatives with respect to <em>u</em> (with <em>v</em> constant) and with respect to <em>v</em> (with <em>u</em> constant). The set of all partial derivatives is called the Jacobian matrix J, whose rows form the tangent vectors <b>t<sub>u</sub></b> and <b>t<sub>v</sub></b>, indicated in red and purple:
</p>

<p>
<img src="/files/making-worlds/planet-3-terrain-mapping-jacobian.png" alt="Heightmapping, jacobian, finding normals" />
</p>

<p>
The cross product of <b>t<sub>u</sub></b> and <b>t<sub>v</sub></b> gives us <b>n</b>, the surface normal.
</p>

<p>
When applied to a discrete heightmap, the function <em>f(u,v)</em> is a 2D array <em>map[u][v]</em>, and the partial derivatives at the end have to be replaced with something else. We can use <a href="http://en.wikipedia.org/wiki/Finite_difference">finite differences</a> to approximate the slope of the surface by differencing neighbouring samples:
</p>

<p>
  <img src="/files/making-worlds/finite-diff.png" alt="Finite differences" /><br>
<img src="/files/making-worlds/planet-3-terrain-mapping-finite-diff.png" alt="Heightmapping, finite differences" />
</p>

<p>
This result and the formula for <b>n</b> are usually provided as-is in terrain mapping guides, without going through the full process of finding tangents first. However, it's important to use the Jacobian matrix formulation once you switch to spherical terrain.
</p>

<p>
<img src="/files/making-worlds/planets-1-cubemap.png" alt="Mapping a cube to a sphere" />
</p>

<p>
To make a sphere, we add an additional function <em>k</em> which warps the flat terrain into a spherical shell. Each shell is the result of warping a single face of the cubemap and covers exactly 1/6th of the sphere. In what follows, We'll only consider a single face and its shell.
</p>

<p>
We designate the intermediate pre-warp coordinates <em>(s,t,h)</em>, and the final post-warp coordinates as <em>(x,y,z)</em>:
</p>

<p>
<img src="/files/making-worlds/planet-3-sphere-mapping.png" alt="Mathematical formulation of spherical heightmapping" />
</p>

<p>
The principle behind the spherical mapping is this: first we take the vector <em>(s, t, 1)</em>, which lies in the base plane of the flat terrain. We normalize this vector by dividing it by its length <em>w</em>, which has the effect of projecting it onto the sphere: <em>(s/w, t/w, 1/w)</em> will be at unit distance from <em>(0, 0, 0)</em>. Then we multiply the resulting vector by the terrain height <em>h</em> to create the terrain on the sphere's surface, relative to its center: <em>(h&middot;s/w, h&middot;t/w, h/w)</em>
</p>

<p>
Just like with the function <em>g(u,v)</em> and <em>J(u,v)</em>, we can find the Jacobian matrix <em>J(s,t,h)</em> of <em>k(s,t,h)</em>. Because there are 3 input values for the function <em>k</em>, there are 3 tangents, along curves of varying <em>s</em> (with constant <em>t and h</em>), varying <em>t</em> (constant <em>s and h</em>) and varying <em>h</em> (constant <em>s and t</em>). The three tangents are named <b>t<sub>s</sub></b>, <b>t<sub>t</sub></b>, <b>t<sub>h</sub></b>.
</p>

<p class="tc">
<img src="/files/making-worlds/planet-3-sphere-mapping-jacobian.png" alt="Spherical heightmapping, jacobian." />
PS: If your skills at derivation are a bit rusty, remember that <a href="http://www.wolframalpha.com/input/?i=d%2Fdx+h%2Fsqrt%28x%5E2%2By%5E2%2B1%29">Wolfram Alpha can do it for you</a>.
</p>

<p>
How does this help? The three vectors describe a local frame of reference at each point in space. Near the edges of the grid, they get more skewed and angular. We use these vectors to transform the flat frame of reference into the right shape, so we can construct a new 90 degree angle here.
</p>

<p>
In mathematical terms, we multiply the 'flat' partial derivatives by the Jacobian matrix. This is similar to the chain rule for regular derivatives, only for multiple variables.
</p>

<p>
That is, to find the partial derivatives (i.e. tangent vectors) of the final spherical terrain with respect to the original terrain coordinates <em>u</em> and <em>v</em>, we can take the flat terrain's tangents <b>t<sub>u</sub></b> and <b>t<sub>v</sub></b> and multiply them by <em>J(s,t,h)</em>. Once we have the two post-warp tangents, we take their cross product, and find the normal of the spherical terrain:
</p>

<p>
<img src="/files/making-worlds/planet-3-sphere-mapping-normal.png" alt="Spherical heightmapping, jacobian." />
</p>

<p>
It's imporant to note that this is not the same as simply multiplying the flat terrain normal with <em>J(s,t,h)</em>. <em>J(s,t,h)</em>'s rows do not form a set of perpendicular vectors (it is not an orthogonal matrix), which means it does not preserve angles between vectors when you multiply by it. In other words, <em>J(s,t,h) * n</em>, with n the flat terrain normal, would not be perpendicular to the spherical terrain. This is why it's important to return to the basic calculus underneath, so we can get the correct, complete formula.
</p>

<p>
Thus ends the magical math adventure. If you read it all the way through, cheers!
</p>

<h2>No Wait, It is a Moon.</h2>

<p>
With the normal map in place, I can now render the planet's surface and get a realistic idea of what it looks like. To show this off, I tweaked the brush system a bit: instead of using the literal brush image (e.g. a smooth, round crater), the brush is distorted with fractal noise. It makes every application of the brush subtly different from the next, and saves me from manually drawing e.g. a hundred different craters.
</p>

<p class="tc">
<img src="/files/making-worlds/planet-3-brush-distortion.png" alt="Brush distortion." /><br />
Here's a side by side comparison of the original brush and a distorted version.
</p>

<p>
Currently I've only implemented one type of distortion, which lends a rocky appearance to the surface. With that in place, my engine can now generate somewhat realistic looking moon surfaces. Here's the demo:
</p>

<p>
<object width="560" height="380"><param name="movie" value="http://www.youtube.com/v/pHjyMs8tm4E&amp;hl=en&amp;fs=1&amp;"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/pHjyMs8tm4E&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="380"></embed></object>
</p>

<h3>References</h3>

<p>
The techniques I used were pioneered by people smarter and older than me, I'm just building my own little digital machine with them.
</p>

<ul>
<li><a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">Creating Spherical Worlds</a>, Maxis/Electronic Arts. (<a href="http://www.andrewwillmott.com/s2007">source</a>).</li>
<li><a href="http://en.wikipedia.org/wiki/Calculus_on_Manifolds_(book)">Calculus on Manifolds</a>, Michael Spivak.</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 2 - Scaling Heights]]></title>
    <link href="http://acko.net/blog/making-worlds-2-scaling-heights/"/>
    <updated>2009-08-31T00:00:00-07:00</updated>
    <id>http://acko.net/blog/making-worlds-2-scaling-heights</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
<h1>Making Worlds 2 - Scaling Heights</h1>

<p>Last time, I had a working, smooth sphere mesh. The next step is to create terrain.
</p>

<h2>Scale</h2>

<p>
Though my goal is to render at a huge range of scales, I'm going to focus on views from space first. That strongly limits how much detail I need to store and render. Aside from being a good initial sandbox in terms of content generation, it also means I can comfortably keep using my current code, which doesn't do any sophisticated memory or resource management yet. I'd much rather work on getting something interesting up first rather than work on invisible infrastructure.
</p>

<p>
That said, this is not necessarily a limitation. The interesting thing about procedural content is that every generator you build can be combined with many others, including a copy of itself. In the case of terrain, there are definite fractal properties, like self-similarity at different levels of scale. This means that once I've generated the lowest resolution terrain, I can generate smaller scale variations and combine them with the larger levels for more detail. This can be repeated indefinitely and is only limited by the amount of memory available.
</p>

<p class="tc"><img src="/files/making-worlds/planet-2-perlin-noise.png" alt="Example of Perlin Noise">
<a href="http://en.wikipedia.org/wiki/Perlin_noise">Perlin Noise</a> is a celebrated classic procedural algorithm,<br>
often used as a fractal generator.
</p>

<h2>Height</h2>
<p>
To build terrain, I need to create heightmaps for all 6 cube faces. Shamelessly stealing more ideas from Spore, I'm doing this on the GPU instead of the CPU, for speed. The GPU normally processes colored pixels, but there's no reason why you can't bind a heightmap's contents as a grayscale (one channel) image and 'draw' into it. As long I build my terrain using simple, repeated drawing operations, this will run incredibly fast.
</p>

<p>
In this case, I'm stamping various brushes onto the sphere's surface to create bumps and pits. Each brush is a regular PNG image which is projected onto the surface around a particular point. The luminance of the brush's pixels determines whether to raise or lower terrain and by how much.
</p>

<p class="tc">
<img src="/files/making-worlds/planet-2-spore-brushes.png" alt="Brushes from Spore"><br>
Three example brushes from Spore. (<a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">source</a>)
</p>

<p>
However, while the brushes need to appear seamless on the final sphere, the drawing area consists only of the straight, square set of cube map faces. It might seem tricky to make this work so that the terrain appears undistorted on the curved sphere grid, but in fact, this distortion is neatly compensated for by good old perspective. All I need to do is set up a virtual scene in 3D, where the brushes are actual shapes hovering around the origin and facing the center. Then, I place a camera in the middle and take a snapshot both ways along each of the main X, Y and Z directions with a perfect 90 degree field of view. The resulting 6 images can then be tiled to form a distortion-free cube map.
</p>

<p>
<img src="/files/making-worlds/planet-2-cubemap-rendering.png" alt="Rendering a cubemap">
Rendering two different cube map faces. The red area is the camera's viewing cone/pyramid, which extends out to infinity.
</p>

<p>
To get started I built a very simple prototype, using Ogre's scene manager facilities. I'm starting with just a simple, smooth crater/dent brush. I generate all 6 faces in sequence on the GPU, pull the images back to the CPU to create the actual mesh, and push the resulting chunks of geometry into GPU memory. This is only done once at the beginning, although the possibility is there to implement live updates as well.
</p>

<p>
Here's a demo showing a planet and the brushes that created it, hovering over the surface. I haven't implemented any shading yet, so I have to toggle back and forth to wireframe mode so you can see the dents made by the brushes:
</p>

<p>
<object width="560" height="380"><param name="movie" value="http://www.youtube.com/v/AX_LiBnZJTc&amp;hl=en&amp;fs=1&amp;"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/AX_LiBnZJTc&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="380"></embed></object>
</p>

<p>
The cubemap for this 'planet' looks like this when flattened. You can see that I haven't actually implemented real terrain carving, because brushes cause sharp edges when they overlap:
</p>

<p>
<img src="/files/making-worlds/planet-2-cubemap.png" alt="Generated planet cubemap">
</p>

<p>
The narrow dent on the left gets distorted and angular where it crosses the cube edge. This is a normal consequence of the cubemapping, as it looks perfectly normal when mapped onto the sphere in the video.
</p>

<h2>Engine Tweaks</h2>

<p>
The demo above also incorporates a couple of engine improvements. With a real heightmap in place, I can implement real level-of-detail selection. That means the resolution of any terrain tile is decided based on how much detail would be lost if a simpler tile was used. The flatter a tile, the less detail is necessary. This ensures complex geometry is used only on those sections that really need it. This is great for visual fidelity, but causes a lot of geometry to pop up if sharp ridges are present in the terrain. In this case, my rendering engine was happily trying to push 700k triangles through the GPU per frame. While even my laptop GPU can actually do that at pretty smooth frame rates nowadays, some optimizations are in order to give me some breathing room.
</p>

<p>
The culprit was that I wasn't really doing any early removal of geometry that was hidden or otherwise out of frame. To fix that, I now do visibility checks together with the level-of-detail selection. First it checks if a chunk is over the horizon or not before considering it for selection. This is easy to calculate and eliminates a lot of unnecessary drawing, especially when looking straight down. If that first visibility check passes, I perform a tighter check using the camera's viewing cone. With these two measures in place, I'm only averaging about 50,000-100,000 triangles visible per frame, with room for more optimization. These optimizations only remove geometry that's already off screen, so there is no visual difference.
</p>

<h3>Cubemap Seams and Dilation</h3>

<p>
When rendering into cube maps, each side is rendered independently. In theory each face should match perfectly with adjacent ones due to the way they've been created. In practice however, slight mismatches can occur due to rounding errors at the edges, creating seams. This can be fixed by explicitly copying one pixel-wide edges from one face into the adjacent ones, until they all match up.
</p>

<p>
The next big step is to start shading the surface, but in order to do that I need to be able to run filters on the cube map. Specifically, I need to be able to compare neighbouring height samples anywhere on the surface. In the straight forward cubemap scenario this is non-trivial, because neighbouring samples at the edges need to be fetched from different cube faces at different orientations in space.
</p>

<p>
I decided to implement something I call 'dilated cubemaps'. I've never really heard this described formally, though I doubt it's never been thought of before:
</p>

<p>
<img src="/files/making-worlds/planet-2-dilated-cubemap.png" alt="A dilated cubemap">
</p>

<p>
Instead of every face neatly matching with the next, I dilate the cube faces so they stick through eachother. At the same time, I use a larger texture size to compensate, and I adjust the field-of-view of the rendering camera to match. If done right, the resulting cubemap is a pixel-perfect expanded version of the undilated map.
</p>

<p>
The dilated cubemap provides reliable neighbouring samples for all samples in the original cube map up to a distance as wide as the new border. Unlike regular cubemap wrapping, the dilated regions are distorted to conform to the current face's grid. This matches the real change in grid direction that occurs on the final sphere mesh and lets you sample exactly across cube map edges.
</p>

<p>
I played with the cubemap dilation because I was thinking of some complicated filters to run that require regular grids (like CFD). But in retrospect, I probably don't need the exact spacing of sample points at the edges for this, so regular undilated cubemapping will probably do. Still, it's good to have around, and certainly was an interesting exercise in pixel-exact rendering.
</p>

<h2>What Next?</h2>

<p>
With basic heightmap generation in place, I can now start putting in some 'tech artist' time to play with various brushes and drawing behaviour. Lighting and shading is another big one and should provide a massive improvement to the visuals.
</p>

<p>
Right now I've taken a week between postings, though it remains to be seen whether I can maintain that. Creating these blog entries is turning into a pretty time consuming endeavour, especially as I get into territory where I have to make my own diagrams and illustrations.
</p>

<h3>References</h3>

<p>
The techniques I used were pioneered by people smarter and older than me, I'm just building my own little digital machine with them.
</p>

<ul>
<li><a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">Creating Spherical Worlds</a>, Maxis/Electronic Arts. (<a href="http://www.andrewwillmott.com/s2007">source</a>).</li>
<li><a href="http://en.wikipedia.org/wiki/Ken_Perlin">Ken Perlin</a>, who invented a lot of this stuff.</li>
</ul>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds 1 - Of Spheres and Cubes]]></title>
    <link href="http://acko.net/blog/making-worlds-1-of-spheres-and-cubes/"/>
    <updated>2009-08-23T00:00:00-07:00</updated>
    <id>http://acko.net/blog/making-worlds-1-of-spheres-and-cubes</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad">
  
<h1>Making Worlds 1 - Of Spheres and Cubes</h1><p>Let's start making some planets! Now, while this started as a random idea kind of project, it was clear from the start that I'd actually need to do a lot of homework for this. Before I could get anywhere, I needed to define exactly what I was aiming for.
</p>

<p>
The first step in this was to shop around for some inspirational art and reference pictures. While there is plenty space art to be found online, in this case, nothing can substitute for the real thing. So I focused my search on real pictures, both of landscapes (terran or otherwise) as well as from space. I found classy shots like these:
</p>

</div></div>

<div class="g3 m1"><div class="pad">
  
  <p>
    <a href="http://en.wikipedia.org/wiki/Enceladus_(moon)"><img class="inline" src="/files/making-worlds/planets-1-enceladus.jpg" alt="The moon Enceladus" /></a>
  </p>

</div></div>

<div class="g6"><div class="pad">

<p class="flat">
  <img src="/files/making-worlds/planets-1-landscape.jpg" alt="Landscape" />
</p>

</div></div>

<div class="g3 m1"><div class="pad">
  
  <p>
    <a href="http://en.wikipedia.org/wiki/Mars"><img class="inline" src="/files/making-worlds/planets-1-mars.png" alt="Mars" /></a>
  </p>

</div></div>

<div class="g8 i2"><div class="pad">
<p>
Hopefully I'll be able to render something similar in a while. At the same time, I eagerly devoured any paper I could find on rendering techniques from the past decade, some intended for real-time rendering, some old enough to be real-time today.
<!--break-->
Out of all this, I quickly settled on my goals:
<ul>
  <li>Represent spherical or lumpy heavenly bodies from asteroids to suns.</li>
  <li>With realistic looking topography and features.</li>
  <li>Viewable across all scales from surface to space.</li>
  <li>At flight-simulator levels of detail.</li>
  <li>Rendered with convincing atmosphere, water, clouds, haze.</li>
</ul>
</p>

<p>
For most of these points, I found one or more papers describing a useful technique I could use or adapt. At the same time, there are still plenty of unknowns I'll need to figure out along the way, not to mention significant amounts of fudging and experimentation.
</p>

<h3>The Spherical Grid</h3>

<p>
To get started I needed to build some geometry, and to do that I needed to figure out what geometry I should use. After reviewing some options, I quickly settled on a regular spherical displacement map (AKA a heightmap). That is, starting with a smooth sphere, move every surface point up or down, perpendicular to the surface, to create terrain on the surface.
</p>

<p>
If these vertical displacements are very small compared to the sphere radius, this can represent the surface of a typical planet (like Earth) at the levels of detail I'm looking for. If the displacements are of the same order as the sphere radius, you can deform it into very irregular potato-like shapes. The only thing heightmaps can't do is caves, tunnels, overhang and other kinds of holes, which is fine for now.
</p>

<p>
The big question is, how should the spherical surface be divided up and represented? With a sphere, this is not an easy question, because there is no single obvious way to divide a spherical surface into regular sections or grids. Various techniques exist, each with their own benefits and specific use cases, and I spent quite some time looking into them. Here's a comparison between four different tesselations:
</p>

<p class="tr">
<img src="/files/making-worlds/planets-1-sphere-tesselations.png" alt="Different tesselations of a sphere" /><small><a href="http://adsabs.harvard.edu/cgi-bin/nph-bib_query?bibcode=2005ApJ...622..759G">Source</a></small>
</p>

<p>
Note that the tesselation labeled ECP is just the regular geographic latitude-longitude grid.
</p>

<p>
The main features I was looking for were speed and simplicity, so I settled on the 'quadcube'. This is where you start with a cube whose faces have been divided into regular grids, and project every surface point out from the middle to an enclosing sphere. This results in a perfectly smooth sphere, built out of 6 identical round shells with curved edges. This arrangement is better known as the 'cube map' and often used for storing arbitrary 360 degree panorama views.
</p>

<p>
Here's a cube and its spherical projection:
</p>

<p>
<img src="/files/making-worlds/planets-1-cubemap.png" alt="Mapping a cube to a sphere" />
<small>The projected cube edges are indicated in red. Note that the resulting sphere is perfectly smooth and round, even though the grid has a bulgy appearance.</small>
</p>

<p>
Cube maps are great, because they are very easy to calculate and do not require complicated trigonometry. In reverse, mapping arbitrary spherical points back onto the cube is even simpler and in fact natively supported by GPUs as a texture mapping feature.
</p>

<p>
This is important, because I'll be generating the surface terrain and texture dynamically and will need to index and access each surface 'pixel' efficiently. Using a cube map, I simply identify the corresponding face, and then index it using x/y coordinates on the face's grid.
</p>

<p>
The downside of cube maps is that the distance and area between points varies along the grid, which makes it harder to perform certain operations on a surface equally. However, these area distortions are much smaller than e.g. a lat-long grid, where the grid spacing actually approaches zero near the poles. Even more, the distortions made by a cube map are the exact opposite of those you get with a regular perspective projection. This makes it easy to render into cube maps, which will be useful for texture generation.
</p>

<h3>Level of Detail</h3>

<p>
There's another reason I picked the cube map approach, and that has to do with the level of detail requirements. My goal is to make a planet that can be viewed from the ground, the air as well as from space. It would be incredibly slow to always render everything at maximum detail, so I need to adaptively add and remove detail as the viewer gets closer to the surface.
</p>

<p>
However, increasing the level of detail uniformly across the entire sphere is not enough, because I only want to render detail where the viewer will see it. To a viewer on the ground, most of the planet is hidden by the horizon, and the engine should be able to effectively cut away the unseen pieces, so no wasteful processing takes place.
</p>

<p>
It is here that I get a huge benefit from the cube map layout of the sphere, because it lets me apply the well-researched realm of grid-based flat terrain rendering with only minor adjustments. Specifically, I am using a 'chunked LOD' approach. Every face of the cube map becomes a quadtree, with each level splitting four ways to form the next level with more detail:
</p>

<p class="tr">
<img src="/files/making-worlds/planets-1-quadtree.png" alt="Quadtree terrain" /><small><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Source</a></small>
</p>

<p>
The chunks for the various levels of detail are all loaded into GPU memory, ready to be accessed at any time. When the terrain has to be rendered, the engine walks down the quad-tree, determines the appropriate level-of-detail for each section, and outputs the list of chunks to be rendered for a particular frame. Then, the GPU does its work, blasting through each chunk at a blistering pace, leaving the CPU to do other things.
</p>

<p>
<img src="/files/making-worlds/planet-lod-tree.png" alt="Configuration of chunks to render" />
</p>

<p>
Because all the data is already in memory, changing the level of detail just means rendering a different set of chunks. Each chunk has the same geometrical complexity, and performance is directly proportional to how many are rendered on screen. More detail means more chunks, but that usually also means you can cut away pieces of the terrain that are far away.
</p>

<p>
The chunked approach is also very easy to work with, because there is no data dependency between the different chunks. Each chunk has a copy of its own vertex data, which means individual chunks can be paged in and out of GPU memory at will. This is important for keeping memory usage down while still being able to scale to massive sizes.
</p>

<h3>Putting It All Together</h3>

<p>
At this point, I have all the pieces in place to render an adaptive sphere mesh. This is what it looks like (sorry, the video capture is a bit jerky):
</p>

<p>
<object width="560" height="380"><param name="movie" value="http://www.youtube.com/v/LxZhWrSmrOY&amp;hl=en&amp;fs=1&amp;"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/LxZhWrSmrOY&amp;hl=en&amp;fs=1&amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="380"></embed></object>
</p>

<p>
The detail increases as the camera gets closer to the sphere and shifts around the surface as it moves.
</p>

<p>
Far from being a little coding experiment, it actually took me quite some time to get to this point, because I was learning OGRE, sharpening my C++ skills, as well as researching the techniques to use.
</p>

<p>
The next step is to look at generating heightmaps and textures for the surface.
</p>

<h3>References</h3>

<p>
The techniques I used were pioneered by people smarter and older than me, I'm just building my own little digital machine with them.
</p>

<ul>
<li><a href="http://www.cs.cmu.edu/~ajw/s2007/0251-SphericalWorlds.pdf">Creating Spherical Worlds</a>, Maxis/Electronic Arts. (<a href="http://www.andrewwillmott.com/s2007">source</a>).</li>
<li><a href="http://tulrich.com/geekstuff/sig-notes.pdf">Rendering Massive Terrains using Chunked Level of Detail Control</a>, Thatcher Ulrich. (<a href="http://tulrich.com/geekstuff/chunklod.html">source</a>)</li>
</ul>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Worlds: Introduction]]></title>
    <link href="http://acko.net/blog/making-worlds-introduction/"/>
    <updated>2009-08-22T00:00:00-07:00</updated>
    <id>http://acko.net/blog/making-worlds-introduction</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Making Worlds: Introduction</h1><p>For the past year or so I've been reacquainting myself with an old friend: C++.
</p>

<p>
More specifically, I've been exploring graphics programming again, this time with the luxurious flexibility of the modern GPU at my fingertips. To get me started, I shopped around for an open source engine to play with. After trying Irrlicht and finding its promises to be a bit lacking, <a href="http://www.ogre3d.org">Ogre</a> turned out to be a really good choice. Though its architecture is a bit intimidating at first, it is all the more sound. More importantly, it seems to have a relatively healthy open-source community around it.
</p>

<p>
So with Ogre as my weapon of choice, I've started a new project: Making Worlds. More specifically, I want to procedurally generate a 3D planet, viewable from outer space as well as the ground (at flight-sim levels of detail), which can be rendered real-time on recent graphics hardware.
</p>

<p>
Why? Because I really like procedural content generation. It's an odd discipline where anything goes, and techniques from across mathematics, engineering and physics are applied. Then, you add a good dose of creativity and artistic sense, and perhaps mix in some real-world data too, until you find something that looks right.
</p>

<p>
Plus, far from being an exercise in pointlessness, procedural content is <a href="http://www.escapistmagazine.com/articles/view/columns/experienced-points/6418-The-Future-is-Procedural">gaining in popularity</a>, especially for video games.
</p>

<p>
So, in the style of Shamus Young's excellent <a href="http://www.shamusyoung.com/twentysidedtale/?p=2940">Procedural city</a> series, I'm going to start blogging about Making Planets. Unlike him however, I'm not going to adhere to a strict schedule.
</p>

<p>
<img src="/files/making-worlds/geosphere.png" alt="Geosphere"/>
</p>

<p>
Here's a teaser for the first installment.
</p>

</div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript audio synthesis with HTML 5]]></title>
    <link href="http://acko.net/blog/javascript-audio-synthesis-with-html-5/"/>
    <updated>2009-08-12T00:00:00-07:00</updated>
    <id>http://acko.net/blog/javascript-audio-synthesis-with-html-5</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>JavaScript audio synthesis with HTML 5</h1>
  
<aside class="r"><a href="http://acko.net/files/audiosynth/index.html"><img src="/files/audiosynth/audio.png" alt="Audio wave"></a></aside>

<p>
  HTML5 gives us a couple new toys to play with, such as &lt;AUDIO&gt; and &lt;VIDEO&gt; tags. On the visual side, we've already seen <a href="https://developer.mozilla.org/samples/video/chroma-key/index.xhtml">live green-screening</a> with Canvas and JS, and in terms of audio there's been several JS <a href="http://www.randomthink.net/labs/html5drums/">drum machines</a> already. But the question I was interested in was: can you use JavaScript to stream live data into these media tags?
</p>

<p>
Enter the <a href="http://acko.net/files/audiosynth/index.html">JavaScript audio synth</a>. It generates a handful of samples using very basic time-domain synthesis, wraps them up in a WAVE file header and embeds them in &lt;AUDIO&gt; tags using base64-encoded data URIs. Each sample is then triggered using timers to play the drum pattern. It's quite simple to do and runs fast enough in HTML5 capable browsers to be unnoticeable. Yes, it sounds tinny, but that's just because I'm too lazy to design proper filters for toys like this.
<!--break-->
Unfortunately, while the synthesis is fast enough to run real-time, you can't actually use it for a full live audio stream, as there is no way to queue up chunks of synthesized audio for seamless playback. I tried triggering multiple &lt;AUDIO&gt; tags in parallel to address this, but that didn't work either.
</p>

<p>
My final attempt was to generate tons of periodic audio loops only a couple of ms long, and to play them back with looping turned on while altering each tag's volume in real time, hence doing a sort of additive wavetable synthesis. Unfortunately, looping is not a fully supported feature, and the only browser I found that does it (Safari) doesn't loop seamlessly at all.
</p>

<p>
All in all, my first brush with the &lt;AUDIO&gt; tag was a major disappointment. The &lt;VIDEO&gt; tag's high-level approach leads to similar limitations, but they are offset by the flexibility and power of the &lt;CANVAS&gt; tag. Unfortunately, there is no 'audio canvas' to solve similar problems with audio.
</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Rick Falkvinge on the Swedish Pirate Party]]></title>
    <link href="http://acko.net/blog/rick-falkvinge-on-the-swedish-pirate-party/"/>
    <updated>2009-07-16T00:00:00-07:00</updated>
    <id>http://acko.net/blog/rick-falkvinge-on-the-swedish-pirate-party</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Rick Falkvinge on the Swedish Pirate Party</h1>
  
<aside class="r"><img src="/files/pirateparty/piratpartiet.png" alt="Pirate party" /></aside>

<p>
Last month, I attended the <a href="http://openwebvancouver.ca">Open Web Vancouver</a> conference. Without a doubt, I thought the most interesting talk at the whole conference was Rick Falkvinge's keynote session about the Swedish Pirate Party.
</p>

<p>
You may have heard of the Pirate Party. Founded in 2006, the popular image is that of an anarchist movement that grew out of the sense of entitlement of media pirates on the internet. It is said that these people want to abolish modern copyright for purely selfish reasons. Unfortunately this is not just a tired old stereotype, but completely wrong.
</p>

<p>
Thankfully, Rick's entire session is available online. In one hour, he calmly and intelligently explains his vision. He shows that the Pirate Party's agenda is about civil liberties, and part of a discussion that has been going on for centuries. By tracing back the history of copyright, he shows a clear pattern: when new technology threatens established powers and businesses, those powers try to use legislation to protect themselves and maintain control. It started with the printing press, and continues today with the internet and file sharing technologies. He shows how we've already allowed the establishment to create legislation that tries to control its true potential, and how we need a political countermovement that represents <em>all</em> the interests of a free, digitally liberated society.
</p>

<p>
You can watch the entire presentation below. The <a href="http://openwebvancouver.ca/sites/default/files/falkvinge-pirates_go_to_parliament.pdf">slides with illustrations and stats</a> are available as well.
</p>

<p>
Part 1:
<embed src="http://blip.tv/play/AYGQ%2B0iZnQk" type="application/x-shockwave-flash" width="512" height="410" allowscriptaccess="always" allowfullscreen="true"></embed>
</p>

<p>
Part 2:
<embed src="http://blip.tv/play/AYGJpFKNsjU" type="application/x-shockwave-flash" width="512" height="410" allowscriptaccess="always" allowfullscreen="true"></embed>
</p>

<p>
After the talk, I got the chance to ask Rick something I'd been wondering for quite a while: whether he thought that the "pirate" label had helped or hurt their cause?
</p>

<p>
His answer was very clever: while it is obviously a controversial name, it gave their movement a lot of much needed exposure very early on, which they wouldn't have gotten otherwise. But more than that, by adopting the "pirate" moniker, they clearly state that they intend to change the meaning and perception of that word. Hence it prevents the copyright lobby (and anyone else) from using it as a slur to delegitimize their movement.
</p>

<p>
So please don't let the "pirate" label scare you off or assume that the issues they are championing are not serious, because they really are.
</p>

<p>
In fact, the entire world got a demonstration of this last month when in Iran, <a href="http://en.wikipedia.org/wiki/Iranian_presidential_election,_2009">post-election protests erupted</a> after accusations of election fraud. The Iranian government denied the accusations and tried to snuff out the dissent, by any means necessary. The suppression of free communication was an essential part of this, and was mostly successful. However, thanks to modern, secure, free networking technology, vital information and media still managed to leak out and show the world what was really going on. If the Iranians hadn't been able to freely record media and communicate, the picture would've been quite different. 
</p>

<p>
This alone should be reason enough to consider access to digital communication an essential human right, and yet, countries such as Sweden, France and New Zealand have introduced laws (or have tried to) to allow the government to wiretap all international digital traffic; to ban certain people permanently from the internet; to force all internet service providers to deny access to sites based purely on government-controlled blacklists. Plus, let's not forget the continued reign of the Grand Firewall of China which keeps China's populace from seeing anything 'disruptive', as well as similar measures in other countries.
</p>

<p>
Such ideas are Wrong and Unjust and should be countered at every step. They are merely the result of an old guard trying to frantically hold on to what they know, in a world that has already irreversibly changed. I urge you to <a href="http://www.piratpartiet.se/international/">support the Pirate Party</a>, and to support similar organisations in your own country.</p></div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Our New Art Wall]]></title>
    <link href="http://acko.net/blog/our-new-art-wall/"/>
    <updated>2009-05-29T00:00:00-07:00</updated>
    <id>http://acko.net/blog/our-new-art-wall</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Our New Art Wall</h1><p><a href="http://www.strutta.com/our-team">We</a> finished our new office decorations today, with chalk on blackboard paint. It covers the <a href="http://www.flickr.com/photos/unconed/3198180586/">hideous wallpaper</a> very nicely.
</p>

<p>
Check the YouTube video below, or <a href="http://www.vimeo.com/4906880">go to Vimeo for a high-def version</a>.
</p>

<p>
<object width="560" height="340"><param name="movie" value="http://www.youtube.com/v/hAmNGvUHIo8&amp;hl=en&amp;fs=1&amp;ap=%2526fmt%3D18"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/hAmNGvUHIo8&amp;hl=en&amp;fs=1&amp;ap=%2526fmt%3D18" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object>
</p>

<p>
Yes, we cheated, shamelessly, by using a projector, but it looks rad!
</p>

<p>
Based on:
<ul>
<li>Girl with Hair Ribbon by <a href="http://en.wikipedia.org/wiki/Roy_Lichtenstein">Roy Lichtenstein</a></li>
<li>A Darth Vader photo/capture found on <a href="http://images.google.com/images?client=safari&amp;rls=nl-be&amp;q=darth%20vader&amp;oe=UTF-8&amp;um=1&amp;ie=UTF-8&amp;sa=N&amp;hl=nl&amp;tab=wi">Google Images</a></li>
<li>An <a href="http://hollywoodinsider.ew.com/2009/04/sin-city-2-and.html">iconic frame</a> from the Sin City movie based on the Frank Miller comic</li>
</ul></p></div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Think Culture, not Race]]></title>
    <link href="http://acko.net/blog/think-culture-not-race/"/>
    <updated>2009-04-12T00:00:00-07:00</updated>
    <id>http://acko.net/blog/think-culture-not-race</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Think Culture, not Race</h1><p>The Edge Foundation has this event called the <a href="http://www.edge.org/q2008/q08_index.html">World Question Center</a> where every year, they ask some of the world's brightest thinkers one Big Question. Last year it was <em>What have you changed your mind about and why?</em>
</p>

<p>
I loved pouring over the answers, mostly because many of them described a shift in the writer's thinking, rather than just changing their opinion on a particular fact.
</p>

<p>
However thinking about what my answer would be, I couldn't really come up with something I felt strongly about. That was until recently, when I saw a very poignant <a href="http://www.youtube.com/watch?v=08VCkyG_C2s">speech on racism</a>. The crystallization of 'race' as an artificial descriptor of culture finally put concrete words onto something that had been in the back of my head for years now. Here goes.
</p>

<p>
<strong>What did I change my mind about and why?</strong>
<!--break-->
</p>

<p>
Growing up liberal and secular, I was imbued with all the virtues of humanistic thinking: freedom of thought, freedom of speech, equality and justice. Part of that was the often repeated mantra that <em>racism was bad</em>. All of us kids accepted it without question, and as we grew up, we applied it in our own life. Meet someone who's darker skinned than you? Whose eyes are different? <em>They are equal to you and you should treat them with respect.</em> So would it echo in my head. But in the homogeneity of ex-catholic Belgium, this was still the exception rather than commonplace. Which meant that every time I had that thought running through my head, it felt like an uneasy reminder rather than something that was just part of my own values and ethics.
</p>

<p>
It wasn't until I moved to Vancouver that I really figured out why, because I was suddenly transplanted into the melting pot of this American-Asian city. It didn't take much for me to lose that guilty reminder about racism and to learn to take people truly at face value. People here come in all sorts of colors and sizes, living in one community, and everyone acts like they are part of the same culture. It's a <em>very</em> diverse and populous culture, but one culture nevertheless.
</p>

<p>
Whereas back in pastoral Belgium, it was a pretty good rule of thumb that someone would only think and talk like you if they also looked like you. Physical traits, grouped by 'race', became a predictor of culture. Which meant that the tension that comes with differences between cultures became associated with people's physical appearance.
</p>

<p>
But what most people forget is that our modern, western lines of 'race' (Caucasian, Latino, African-American, Asian, ...) are just the latest version of an age-old concept. These qualifiers used to be much more numerous and specific. Today's caucasians used to be Slavs, Celts, Normans, Gauls, Moors, etc. But as populations grew, and political divisions shifted, cultures got mixed, and some notions of 'race' became obsolete. Racial divisions evolved, and as such are not arbitrary lines that someone has drawn somewhere just based on looks. They are almost always drawn on top of existing cultural borders.
</p>

<p>
And this is the lesson that I was never really taught. Nobody ever really said that the feeling of unease that I would have around what I perceived to be 'other races' was just cultural tension, and that lumping that in with <em>this person looks different from me</em> was the mistake. Not the fact that I was feeling uneasy. The giant guilt trip that I had been saddled with about inequality and racism left me so uptight, that I was handicapped in relating to the diversity in my own culture as well as other cultures. For a very long time, I was the awkward guy who could say he 'hangs out with plenty of ______ people' and still felt bad about it. But not anymore.
</p>

<p>
It's only when you get over that that you can truly work on the real problem: how to get all our different cultures to live together on the same planet. Which is still a real problem, and one that needs to be tackled, not ignored, and certainly not to be shushed with overzealous accusations of racism.
</p></div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[The Reality of Illegal TV Downloads]]></title>
    <link href="http://acko.net/blog/the-reality-of-illegal-tv-downloads/"/>
    <updated>2009-03-14T00:00:00-07:00</updated>
    <id>http://acko.net/blog/the-reality-of-illegal-tv-downloads</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>The Reality of Illegal TV Downloads</h1><p>As you may know, I'm a sci-fi nerd, hence I've been pretty excited about the reimagined <a href="http://www.scifi.com/battlestar/">Battlestar Galactica</a> series coming to a close. So, me and my fellow connoisseur of the awesome, <a href="http://gregeh.com/blog/">Greg</a>, put together <a href="http://spreadsheets.google.com/viewform?hl=en&amp;formkey=cHNOZC1qa0Z1UEw2OE1zT3VVbWdRYUE6MA..">a quick survey</a> on Google Docs to get predictions about the end of the show. The internets filled it in.
</p>

<p>
The Battlestar nerdery was all in good fun, but more interestingly, I also asked a question about how people watch the show: via live broadcast, recorded or downloaded? Legally or illegally? Depending on your point of view, these results are either entirely obvious, or quite surprising. So far, 313 people filled in the survey, which was advertised only through blogs and Twitter for two days:
</p>

<p>
<img src="/files/illegal-tv/techie-bsg-survey.png" alt="How techie people watch TV" />
</p>

<p>
Given the circumstances, the people who answered this fit two descriptions. One, they are fan enough to actually fill out a survey about a show on TV. Two, they read blogs, talk on Twitter, hang out in forums, i.e. they know and use the web intimately. So, <strike>SciFi channel</strike> SyFy, NBC Universal, all big name media: do you see that big green chunk of people who download your shows illegally? These are merely potential customers that you haven't reached yet.
</p>

<p>
When you look at your ratings and bemoan the dwindling numbers, think of that 30%. Sure, these people are probably not getting you any ad money, but you can profit off them indirectly. Tech savvy people are the backbone of your nerdy fandom, and they add value to your precious intellectual property. Who do you think helped all those girlfriends, husbands, parents or siblings get over the silly name 'Battlestar Galactica' and actually watch the show? Who wrote all that stuff on the <a href="http://en.battlestarwiki.org/wiki">Battlestar wiki</a> in their spare time, providing an anchor for online discussions and activity, keeping your brand active? Yup that's right, the nerds with their computers.
</p>

<p>
And seriously, those 30% aren't all anarchistic hacker types who despise copyright. A lot of them are just people who want to enjoy the show they love in the way that is convenient for them. An illegal high-definition torrent released a couple hours after the TV broadcast is indeed pretty darn convenient. Lucky for you, you are in the unique position of offering something even better.
</p>

<p>
Just stop treating the live broadcast as being sacred: it is merely one showing after the content has been made available. Instead, provide your own episode downloads at the same time as the TV broadcast. Make it attractive with additional extras for your hungry audience, like director commentary or deleted scenes. By all means offer a free ad-supported plan like Hulu for those who don't mind having their shows and brains invaded by rabid commercialism. 
</p>

<p>
But please, open up the modestly priced option of high quality, ad-free, DRM-free downloads. The technology is there. If you do it right, you will go from making no money off of these people, to making <em>some</em> money off of them. Trust me, this group is only getting bigger by the day. Of course, it won't be easy: your inaction has caused an entire ecosystem of illegal distribution to spring up across IRC, Usenet, private trackers and the web. These people are organised and very good at what they do. Your competition is tough.
</p>

<p>
In this light, it's a bit silly to try and push region-restricted, delayed and limited online releases onto people. You're only providing a product worse than what's already available. You're clinging to your old ways, and only succeeding because a lot of this stuff is darn new and <em>only the kids are doing it anyway</em>.
</p>

<p>
Except, even the grown up folks around me are starting to figure it out. Some run Boxee on their cracked Apple TV (a plug and play process). Some have a dedicated torrent box at home that they log into (screen sharing built into their Mac). They've <a href="http://code.google.com/android/">got</a> <a href="http://www.apple.com/iphone/">phones</a> in their pockets that can network literally anywhere, and look, someone can <em>sell them</em> an app to torrent their shows for a buck or two. See how this whole digital economy stuff works?
</p>

<p>
Do you honestly think you can control all that with increasingly restrictive DRM backed by increasingly restrictive law? Time's arrow points to our point of view becoming the dominant one. Either reinvent yourself, or continue losing. It's your move, TV.
</p></div></div>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Using Web APIs for Research]]></title>
    <link href="http://acko.net/blog/using-web-apis-for-research/"/>
    <updated>2009-01-08T00:00:00-08:00</updated>
    <id>http://acko.net/blog/using-web-apis-for-research</id>
    <content type="html"><![CDATA[<div class="g8 i2 first"><div class="pad"><h1>Using Web APIs for Research</h1><p>Recently we launched our new product at <a href="http://www.strutta.com/">Strutta</a>, a 'create your own contest site' web service. In each contest, users submit and vote on each other's videos, pictures, songs or writings.
</p>

<p>
As part of the research we did for the development, we wanted to examine our competition. So, I dove into YouTube to try and figure out some of their ideas and algorithms. For me, this wasn't entirely new: when I posted my <a href="/tag/line-rider">Line Rider</a> videos to YouTube, I followed up each video with manual statistics tracking and gained some insight into how a video becomes popular on YouTube. However, that only gave me a very narrow view of the community and its dynamics.
</p>

<p>
Since then though, things have changed a lot. YouTube now has a <a href="http://code.google.com/apis/youtube/overview.html">public API</a> as well as pre-made libraries to use. With these, it becomes very easy to collect statistics and perform your own analysis. So, armed with Python, I set out to investigate YouTube's ubiquitous 'related videos' feature.
</p>

<p>
<a href="http://blog.strutta.com/blog/six-degrees-of-youtube"><img src="/files/youtube-degrees/six-degrees.png" alt="Six Degrees of YouTube" /></a>
</p>

<p>
<!--break-->
</p>

<p>
I found it interesting to analyse a big site through their own API rather than screen scraping. Traditionally, one first tries to collect as much data as possible, but the resulting data set can become very unwieldy. In this case, I already had full access and I could focus on exactly which queries I wanted to run, how to aggregate my data, and which measures to focus on.
</p>

<p>
The results revealed some interesting conclusions. My big write-up can be found on the Strutta Blog, aptly titled <a href="http://blog.strutta.com/blog/six-degrees-of-youtube">Six Degrees of YouTube</a>.
</p>

</div></div>
]]></content>
  </entry>
  
</feed>
