749ec5a03533

Recursive MPD
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sat, 05 Mar 2016 21:26:30 +0000
parents 1f29d302c82d
children a52d61eb7e85
branches/tags (none)
files Makefile content/blog/2016/03/recursive-midpoint-displacement.html media/css/sjl.less media/js/terrain2.js media/js/wisp/terrain2.js media/js/wisp/terrain2.wisp

Changes

--- a/Makefile	Sun Feb 28 11:52:32 2016 +0000
+++ b/Makefile	Sat Mar 05 21:26:30 2016 +0000
@@ -1,22 +1,15 @@
-.PHONY: all clean generate regen serve deploy
+.PHONY: clean generate regen serve deploy
 
 wisps := $(shell ffind --literal '.wisp')
 javascripts := $(subst .wisp,.js,$(wisps))
-
-all: media/js/terrain1.js
+bundles := $(subst /wisp/,/,$(javascripts))
 
-media/js/wisp/%.js: media/js/wisp/%.wisp
-	cat $< | wisp > $@
-
-media/js/terrain1.js: $(javascripts)
-	browserify media/js/wisp/terrain1.js -o $@
+generate: $(bundles)
+	hyde -g -s .
 
 clean:
 	rm -rf ./deploy
 
-generate: all
-	hyde -g -s .
-
 serve:
 	hyde -w -s . -k
 
@@ -24,3 +17,12 @@
 
 deploy: generate
 	rsync -avz ./deploy/ sl:/var/www/stevelosh.com
+
+media/js/wisp/%.js: media/js/wisp/%.wisp
+	cat $< | wisp > $@
+
+media/js/terrain1.js: $(javascripts)
+	browserify media/js/wisp/terrain1.js -o $@
+
+media/js/terrain2.js: $(javascripts)
+	browserify media/js/wisp/terrain2.js -o $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/content/blog/2016/03/recursive-midpoint-displacement.html	Sat Mar 05 21:26:30 2016 +0000
@@ -0,0 +1,268 @@
+    {% extends "_post.html" %}
+
+    {% load mathjax %}
+
+    {% hyde
+        title: "Recursive Midpoint Displacement"
+        snip: "A cleaner version."
+        created: 2016-03-07 14:30:00
+    %}
+
+    {% block extra_js %}
+        <script data-cfasync="false" src="/media/js/three.min.js"></script>
+        <script data-cfasync="false" src="/media/js/TrackballControls.js"></script>
+
+        <script data-cfasync="false" src="/media/js/terrain2.js"></script>
+    {% endblock extra_js %}
+
+    {% block article %}
+
+In the [last post][mpd] we looked at implementing the Midpoint Displacement
+algorithm.  I ended up doing the last step iteratively, which works, but isn't
+the cleanest way to do it.  Before moving on to other algorithms I wanted to
+clean things up by using a handy library.
+
+[mpd]: /blog/2016/02/midpoint-displacement/
+
+[TOC]
+
+## Multi-Dimensional Arrays
+
+Last week when looking at something unrelated I came across the [ndarray][]
+library ("nd" stands for "n-dimensional").  This is a little wrapper around
+standard Javascript arrays to add easy multi-dimensional indexing.  We're going
+to to use `Float64Array` objects as the underlying storage because they're much
+more efficient than the vanilla Javascript arrays and they're fairly well
+supported.
+
+It also adds [slicing][], which is a lot like Common Lisp's [displaced
+arrays][disp-arr].  It lets you create a new "array" object with a different
+"shape" that doesn't have any actual storage of its own, but instead refers back
+to the original array's data.  This is perfect for implementing Midpoint
+Displacement with recursion.
+
+[ndarray]: https://github.com/scijs/ndarray
+[slicing]: https://github.com/scijs/ndarray#slicing
+[disp-arr]: http://clhs.lisp.se/Body/26_glo_d.htm#displaced_array
+
+## Iteration
+
+We're still going to need to iterate over ndarrays at certain points (e.g. when
+normalizing them) so let's make some helpful macros to do the annoying busywork
+for us:
+
+    :::clojure
+    (defmacro do-ndarray [vars array-form & body]
+      (let [array-var (gensym "array")
+            build
+            (fn build [vars n]
+              (if (empty? vars)
+                `(do ~@body)
+                `(do-times ~(first vars) (aget (.-shape ~array-var) ~n)
+                   ~(build (rest vars) (inc n)))))]
+        `(let [~array-var ~array-form]
+           ~(build vars 0))))
+
+    (defmacro do-ndarray-el [element array-form & body]
+      (let [index (gensym "index")
+            array (gensym "array")]
+        `(let [~array ~array-form]
+           (do-times ~index (.-length (.-data ~array))
+             (let [~element (aget (.-data ~array) ~index)]
+               ~@body)))))
+
+Now we can easily iterate over the indices:
+
+    :::clojure
+    (do-ndarray [x y] my-ndarray
+      (console.log "Array[" x "][" y "] is: "
+                   (.get my-ndarray x y)))
+
+Or just over the items if we don't need their indices:
+
+    :::clojure
+    (do-ndarray-el item my-ndarray
+      (console.log item))
+
+These macros should work for ndarrays of any number of dimensions, and will
+compile into ugly but fast Javascript `for` loops.
+
+## Updating the Heightmaps
+
+To start we'll need to update the heightmap functions to work with ndarrays
+instead of the normal JS arrays.
+
+Start with a few functions to calculate sizes/incides:
+
+    :::clojure
+    (defn heightmap-resolution [heightmap]
+      (aget heightmap.shape 0))
+
+    (defn heightmap-last-index [heightmap]
+      (dec (heightmap-resolution heightmap)))
+
+    (defn heightmap-center-index [heightmap]
+      (midpoint 0 (heightmap-last-index heightmap)))
+
+Support for reading/writing:
+
+    :::clojure
+    (defn heightmap-get [heightmap x y]
+      (.get heightmap x y))
+
+    (defn heightmap-get-safe [heightmap x y]
+      (let [last (heightmap-last-index heightmap)]
+        (when (and (<= 0 x last)
+                   (<= 0 y last))
+          (heightmap-get heightmap x y))))
+
+    (defn heightmap-set! [heightmap x y val]
+      (.set heightmap x y val))
+
+    (defn heightmap-set-if-unset! [heightmap x y val]
+      (when (== 0 (heightmap-get heightmap x y))
+        (heightmap-set! heightmap x y val)))
+
+Normalization:
+
+    :::clojure
+    (defn normalize [heightmap]
+      (let [max (- Infinity)
+            min Infinity]
+        (do-ndarray-el el heightmap
+          (when (< max el) (set! max el))
+          (when (> min el) (set! min el)))
+        (let [span (- max min)]
+          (do-ndarray [x y] heightmap
+            (heightmap-set! heightmap x y
+                            (/ (- (heightmap-get heightmap x y) min)
+                               span))))))
+
+Creation:
+
+    :::clojure
+    (defn make-heightmap [exponent]
+      (let [resolution (+ (Math.pow 2 exponent) 1)]
+        (let [heightmap (ndarray (new Float64Array (* resolution resolution))
+                                 [resolution resolution])]
+          (set! heightmap.exponent exponent)
+          (set! heightmap.resolution resolution)
+          (set! heightmap.last (dec resolution))
+          heightmap)))
+
+I'm not going to go through all the code here line-by-line because it's mostly
+just a simple update of the last post.
+
+## Slicing Heightmaps
+
+Part of the Midpoint Displacement is "repeat the process on the four corner
+squares of this one", and with ndarray we can make getting those corners much
+simpler:
+
+    :::clojure
+    (defn top-left-corner [heightmap]
+      (let [center (heightmap-center-index heightmap)]
+        (-> heightmap
+          (.lo 0 0)
+          (.hi (inc center) (inc center)))))
+
+    (defn top-right-corner [heightmap]
+      (let [center (heightmap-center-index heightmap)]
+        (-> heightmap
+          (.lo center 0)
+          (.hi (inc center) (inc center)))))
+
+    (defn bottom-left-corner [heightmap]
+      (let [center (heightmap-center-index heightmap)]
+        (-> heightmap
+          (.lo 0 center)
+          (.hi (inc center) (inc center)))))
+
+    (defn bottom-right-corner [heightmap]
+      (let [center (heightmap-center-index heightmap)]
+        (-> heightmap
+          (.lo center center)
+          (.hi (inc center) (inc center)))))
+
+Each of these will return a "slice" of the underlying ndarray that looks and
+acts like a fresh array (e.g. its indices start at `0`, `0`), but that uses the
+appropriate part of the original array as the data storage.
+
+## Updating the Algorithm
+
+Now we can turn the algorithm into a recursive version.  With the slicing
+functions it's pretty simple.  Initializing the corners is still trivial:
+
+    :::clojure
+    (defn mpd-init-corners [heightmap]
+      (let [last (heightmap-last-index heightmap)]
+        (heightmap-set! heightmap 0    0    (rand))
+        (heightmap-set! heightmap 0    last (rand))
+        (heightmap-set! heightmap last 0    (rand))
+        (heightmap-set! heightmap last last (rand))))
+
+The meat of the algorithm looks long, but is mostly just calculating all the
+appropriate numbers with readable names:
+
+    :::clojure
+    (defn mpd-displace [heightmap spread spread-reduction]
+      (let [last (heightmap-last-index heightmap)
+            c (midpoint 0 last)
+
+            ; Get the values of the corners
+            bottom-left  (heightmap-get heightmap 0    0)
+            bottom-right (heightmap-get heightmap last 0)
+            top-left     (heightmap-get heightmap 0    last)
+            top-right    (heightmap-get heightmap last last)
+
+            ; Calculate the averages for the points we're going to fill
+            top    (average2 top-left top-right)
+            left   (average2 bottom-left top-left)
+            bottom (average2 bottom-left bottom-right)
+            right  (average2 bottom-right top-right)
+            center (average4 top left bottom right)
+
+            next-spread (* spread spread-reduction)]
+        ; Set the four edge midpoint values
+        (heightmap-set-if-unset! heightmap c    0    (jitter bottom spread))
+        (heightmap-set-if-unset! heightmap c    last (jitter top spread))
+        (heightmap-set-if-unset! heightmap 0    c    (jitter left spread))
+        (heightmap-set-if-unset! heightmap last c    (jitter right spread))
+
+        ; Set the center value
+        (heightmap-set-if-unset! heightmap c    c    (jitter center spread))
+
+        ; Recurse on the four corners if necessary (3x3 is the base case)
+        (when-not (== 3 (heightmap-resolution heightmap))
+          (mpd-displace (top-left-corner heightmap) next-spread spread-reduction)
+          (mpd-displace (top-right-corner heightmap) next-spread spread-reduction)
+          (mpd-displace (bottom-left-corner heightmap) next-spread spread-reduction)
+          (mpd-displace (bottom-right-corner heightmap) next-spread spread-reduction))))
+
+The main wrapper function is simple:
+
+    :::clojure
+    (defn midpoint-displacement [heightmap]
+      (let [initial-spread 0.3
+            spread-reduction 0.55]
+        (mpd-init-corners heightmap)
+        (mpd-displace heightmap initial-spread spread-reduction)
+        (normalize heightmap)))
+
+## Result
+
+The result looks the same as before, but will generate the heightmaps a lot
+faster because it's operating on a `Float64Array` instead of a vanilla JS array.
+
+<div id="demo-final" class="threejs"></div>
+
+The code for these blog posts is a bit of a mess because I've been copy/pasting
+to show the partially-completed demos.  To fix that I've created a little
+[single-page demo][ymir] with completed versions of the various algorithms you
+can play with.  [The code for that][ymir-code] should be a lot more readable
+than the hacky code for these posts.
+
+[ymir]: http://ymir.stevelosh.com/
+[ymir-code]: http://bitbucket.org/sjl/ymir/
+
+    {% endblock article %}
--- a/media/css/sjl.less	Sun Feb 28 11:52:32 2016 +0000
+++ b/media/css/sjl.less	Sat Mar 05 21:26:30 2016 +0000
@@ -44,6 +44,8 @@
         font-size: 14px;
     }
     div.threejs {
+        margin-bottom: 16px;
+
         canvas {
             border: 1px solid #222222;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/media/js/terrain2.js	Sat Mar 05 21:26:30 2016 +0000
@@ -0,0 +1,783 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+{
+    var _ns_ = {
+        id: 'demo',
+        doc: void 0
+    };
+    var ndarray = require('ndarray');
+}
+var width = exports.width = 610;
+var height = exports.height = 400;
+var wireframe = exports.wireframe = true;
+var wireframeWidth = exports.wireframeWidth = 1.2;
+var terrainHeight = exports.terrainHeight = 50;
+var terrainSize = exports.terrainSize = 100;
+void 0;
+void 0;
+void 0;
+var inc = exports.inc = function inc(x) {
+    return x + 1;
+};
+var dec = exports.dec = function dec(x) {
+    return x - 1;
+};
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+var midpoint = exports.midpoint = function midpoint(a, b) {
+    return (a + b) / 2;
+};
+var average2 = exports.average2 = function average2(a, b) {
+    return (a + b) / 2;
+};
+var average4 = exports.average4 = function average4(a, b, c, d) {
+    return (a + b + c + d) / 4;
+};
+var safeAverage = exports.safeAverage = function safeAverage(a, b, c, d) {
+    return function () {
+        var totalø1 = 0;
+        var countø1 = 0;
+        a ? (function () {
+            totalø1 = totalø1 + a;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        b ? (function () {
+            totalø1 = totalø1 + b;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        c ? (function () {
+            totalø1 = totalø1 + c;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        d ? (function () {
+            totalø1 = totalø1 + d;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        return totalø1 / countø1;
+    }.call(this);
+};
+var rand = exports.rand = function rand() {
+    return Math.random();
+};
+var randAroundZero = exports.randAroundZero = function randAroundZero(spread) {
+    return spread * rand() * 2 - spread;
+};
+var jitter = exports.jitter = function jitter(value, spread) {
+    return value + randAroundZero(spread);
+};
+var heightmapResolution = exports.heightmapResolution = function heightmapResolution(heightmap) {
+    return heightmap.shape[0];
+};
+var heightmapLastIndex = exports.heightmapLastIndex = function heightmapLastIndex(heightmap) {
+    return dec(heightmapResolution(heightmap));
+};
+var heightmapCenterIndex = exports.heightmapCenterIndex = function heightmapCenterIndex(heightmap) {
+    return midpoint(0, heightmapLastIndex(heightmap));
+};
+var heightmapGet = exports.heightmapGet = function heightmapGet(heightmap, x, y) {
+    return heightmap.get(x, y);
+};
+var heightmapGetSafe = exports.heightmapGetSafe = function heightmapGetSafe(heightmap, x, y) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        return 0 <= x && x <= lastø1 && (0 <= y && y <= lastø1) ? (function () {
+            return heightmapGet(heightmap, x, y);
+        })() : void 0;
+    }.call(this);
+};
+var heightmapSet = exports.heightmapSet = function heightmapSet(heightmap, x, y, val) {
+    return heightmap.set(x, y, val);
+};
+var heightmapSetIfUnset = exports.heightmapSetIfUnset = function heightmapSetIfUnset(heightmap, x, y, val) {
+    return 0 == heightmapGet(heightmap, x, y) ? (function () {
+        return heightmapSet(heightmap, x, y, val);
+    })() : void 0;
+};
+var normalize = exports.normalize = function normalize(heightmap) {
+    return function () {
+        var maxø1 = 0 - Infinity;
+        var minø1 = Infinity;
+        (function () {
+            var array2ø1 = heightmap;
+            return function () {
+                var G__3ø1 = array2ø1.data.length;
+                return function loop() {
+                    var recur = loop;
+                    var index1ø1 = 0;
+                    do {
+                        recur = index1ø1 < G__3ø1 ? (function () {
+                            (function () {
+                                var elø1 = array2ø1.data[index1ø1];
+                                maxø1 < elø1 ? (function () {
+                                    return maxø1 = elø1;
+                                })() : void 0;
+                                return minø1 > elø1 ? (function () {
+                                    return minø1 = elø1;
+                                })() : void 0;
+                            }.call(this));
+                            return loop[0] = inc(index1ø1), loop;
+                        })() : void 0;
+                    } while (index1ø1 = loop[0], recur === loop);
+                    return recur;
+                }.call(this);
+            }.call(this);
+        }.call(this));
+        return function () {
+            var spanø1 = maxø1 - minø1;
+            return function () {
+                var array4ø1 = heightmap;
+                return function () {
+                    var G__5ø1 = array4ø1.shape[0];
+                    return function loop() {
+                        var recur = loop;
+                        var xø1 = 0;
+                        do {
+                            recur = xø1 < G__5ø1 ? (function () {
+                                (function () {
+                                    var G__6ø1 = array4ø1.shape[1];
+                                    return function loop() {
+                                        var recur = loop;
+                                        var yø1 = 0;
+                                        do {
+                                            recur = yø1 < G__6ø1 ? (function () {
+                                                (function () {
+                                                    return heightmapSet(heightmap, xø1, yø1, (heightmapGet(heightmap, xø1, yø1) - minø1) / spanø1);
+                                                })();
+                                                return loop[0] = inc(yø1), loop;
+                                            })() : void 0;
+                                        } while (yø1 = loop[0], recur === loop);
+                                        return recur;
+                                    }.call(this);
+                                }.call(this));
+                                return loop[0] = inc(xø1), loop;
+                            })() : void 0;
+                        } while (xø1 = loop[0], recur === loop);
+                        return recur;
+                    }.call(this);
+                }.call(this);
+            }.call(this);
+        }.call(this);
+    }.call(this);
+};
+var makeHeightmap = exports.makeHeightmap = function makeHeightmap(exponent) {
+    return function () {
+        var resolutionø1 = Math.pow(2, exponent) + 1;
+        return function () {
+            var heightmapø1 = ndarray(new Float64Array(resolutionø1 * resolutionø1), [
+                resolutionø1,
+                resolutionø1
+            ]);
+            heightmapø1.exponent = exponent;
+            heightmapø1.resolution = resolutionø1;
+            heightmapø1.last = dec(resolutionø1);
+            return heightmapø1;
+        }.call(this);
+    }.call(this);
+};
+var topLeftCorner = exports.topLeftCorner = function topLeftCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(0, 0).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var topRightCorner = exports.topRightCorner = function topRightCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(centerø1, 0).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var bottomLeftCorner = exports.bottomLeftCorner = function bottomLeftCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(0, centerø1).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var bottomRightCorner = exports.bottomRightCorner = function bottomRightCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(centerø1, centerø1).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var mpdInitCorners = exports.mpdInitCorners = function mpdInitCorners(heightmap) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        heightmapSet(heightmap, 0, 0, rand());
+        heightmapSet(heightmap, 0, lastø1, rand());
+        heightmapSet(heightmap, lastø1, 0, rand());
+        return heightmapSet(heightmap, lastø1, lastø1, rand());
+    }.call(this);
+};
+var mpdDisplace = exports.mpdDisplace = function mpdDisplace(heightmap, spread, spreadReduction) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        var cø1 = midpoint(0, lastø1);
+        var bottomLeftø1 = heightmapGet(heightmap, 0, 0);
+        var bottomRightø1 = heightmapGet(heightmap, lastø1, 0);
+        var topLeftø1 = heightmapGet(heightmap, 0, lastø1);
+        var topRightø1 = heightmapGet(heightmap, lastø1, lastø1);
+        var topø1 = average2(topLeftø1, topRightø1);
+        var leftø1 = average2(bottomLeftø1, topLeftø1);
+        var bottomø1 = average2(bottomLeftø1, bottomRightø1);
+        var rightø1 = average2(bottomRightø1, topRightø1);
+        var centerø1 = average4(topø1, leftø1, bottomø1, rightø1);
+        var nextSpreadø1 = spread * spreadReduction;
+        heightmapSetIfUnset(heightmap, cø1, 0, jitter(bottomø1, spread));
+        heightmapSetIfUnset(heightmap, cø1, lastø1, jitter(topø1, spread));
+        heightmapSetIfUnset(heightmap, 0, cø1, jitter(leftø1, spread));
+        heightmapSetIfUnset(heightmap, lastø1, cø1, jitter(rightø1, spread));
+        heightmapSetIfUnset(heightmap, cø1, cø1, jitter(centerø1, spread));
+        return !(3 == heightmapResolution(heightmap)) ? (function () {
+            heightmapSetIfUnset(heightmap, cø1, 0, jitter(bottomø1, spread));
+            heightmapSetIfUnset(heightmap, cø1, lastø1, jitter(topø1, spread));
+            heightmapSetIfUnset(heightmap, 0, cø1, jitter(leftø1, spread));
+            heightmapSetIfUnset(heightmap, lastø1, cø1, jitter(rightø1, spread));
+            heightmapSetIfUnset(heightmap, cø1, cø1, jitter(centerø1, spread));
+            mpdDisplace(topLeftCorner(heightmap), nextSpreadø1, spreadReduction);
+            mpdDisplace(topRightCorner(heightmap), nextSpreadø1, spreadReduction);
+            mpdDisplace(bottomLeftCorner(heightmap), nextSpreadø1, spreadReduction);
+            return mpdDisplace(bottomRightCorner(heightmap), nextSpreadø1, spreadReduction);
+        })() : void 0;
+    }.call(this);
+};
+var midpointDisplacement = exports.midpointDisplacement = function midpointDisplacement(heightmap) {
+    return function () {
+        var initialSpreadø1 = 0.3;
+        var spreadReductionø1 = 0.55;
+        mpdInitCorners(heightmap);
+        mpdDisplace(heightmap, initialSpreadø1, spreadReductionø1);
+        return normalize(heightmap);
+    }.call(this);
+};
+var makeDirectionalLight = exports.makeDirectionalLight = function makeDirectionalLight() {
+    return function () {
+        var lightø1 = new THREE.DirectionalLight(16777215, 1);
+        lightø1.position.set(100, 0, 150);
+        return lightø1;
+    }.call(this);
+};
+var makeCamera = exports.makeCamera = function makeCamera() {
+    return function () {
+        var cameraø1 = new THREE.PerspectiveCamera(55, width / height, 0.1, 1000);
+        cameraø1.position.set(0, -100, 150);
+        return cameraø1;
+    }.call(this);
+};
+var makeRenderer = exports.makeRenderer = function makeRenderer() {
+    return function () {
+        var rendererø1 = new THREE.WebGLRenderer({ 'antialias': false });
+        rendererø1.setClearColor(16777215);
+        rendererø1.setSize(width, height);
+        rendererø1.setPixelRatio(2);
+        return rendererø1;
+    }.call(this);
+};
+var makeGeometry = exports.makeGeometry = function makeGeometry(heightmap) {
+    return function () {
+        var resolutionø1 = heightmap.shape[0];
+        var geometryø1 = new THREE.PlaneGeometry(terrainSize, terrainSize, resolutionø1 - 1, resolutionø1 - 1);
+        return geometryø1;
+    }.call(this);
+};
+var makeControls = exports.makeControls = function makeControls(camera, renderer) {
+    return function () {
+        var controlsø1 = new THREE.TrackballControls(camera, renderer.domElement);
+        controlsø1.rotateSpeed = 1.4;
+        controlsø1.zoomSpeed = 0.5;
+        controlsø1.staticMoving = true;
+        controlsø1.dynamicDampingFactor = 0.3;
+        return controlsø1;
+    }.call(this);
+};
+var makePlane = exports.makePlane = function makePlane(geometry) {
+    return function () {
+        var materialø1 = new THREE.MeshLambertMaterial({
+            'wireframe': wireframe,
+            'wireframeLinewidth': wireframeWidth,
+            'color': 47872
+        });
+        return new THREE.Mesh(geometry, materialø1);
+    }.call(this);
+};
+var attachToDom = exports.attachToDom = function attachToDom(renderer, elName, refreshFn) {
+    return function () {
+        var containerø1 = document.getElementById(elName);
+        var settingsø1 = document.createElement('div');
+        var refreshButtonø1 = document.createElement('button');
+        var buttonTextø1 = document.createTextNode('Refresh');
+        var cancelScrollø1 = function (e) {
+            return e.preventDefault();
+        };
+        refreshButtonø1.onclick = refreshFn;
+        renderer.domElement.onmousewheel = cancelScrollø1;
+        renderer.domElement.addEventListener('MozMousePixelScroll', cancelScrollø1, false);
+        refreshButtonø1.appendChild(buttonTextø1);
+        containerø1.appendChild(renderer.domElement);
+        containerø1.appendChild(settingsø1);
+        return settingsø1.appendChild(refreshButtonø1);
+    }.call(this);
+};
+var updateGeometry = exports.updateGeometry = function updateGeometry(geometry, heightmap) {
+    (function loop() {
+        var recur = loop;
+        var iø1 = 0;
+        do {
+            recur = iø1 < geometry.vertices.length ? (function () {
+                geometry.vertices[iø1].z = terrainHeight * heightmap.data[iø1];
+                return loop[0] = iø1 + 1, loop;
+            })() : void 0;
+        } while (iø1 = loop[0], recur === loop);
+        return recur;
+    }.call(this));
+    geometry.computeVertexNormals();
+    return geometry;
+};
+var makeFinal = exports.makeFinal = function makeFinal(elementId) {
+    var scene = new THREE.Scene();
+    scene.add(new THREE.AxisHelper(100));
+    var clock = new THREE.Clock();
+    var camera = makeCamera();
+    var renderer = makeRenderer();
+    var geometry = void 0;
+    var plane = void 0;
+    scene.add(makeDirectionalLight());
+    scene.add(new THREE.AmbientLight(16777215, 0.05));
+    var refresh = function refresh() {
+        return function () {
+            var heightmapø1 = makeHeightmap(6);
+            console.log('Generating terrain...');
+            (function () {
+                var G__7ø1 = new Date().getTime();
+                var G__9ø1 = (function () {
+                    return midpointDisplacement(heightmapø1);
+                })();
+                var G__8ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__8ø1 - G__7ø1) + 'ms.');
+                return G__9ø1;
+            }.call(this));
+            console.log('Rebuilding geometry...');
+            (function () {
+                var G__10ø1 = new Date().getTime();
+                var G__12ø1 = (function () {
+                    geometry = makeGeometry(heightmapø1);
+                    return updateGeometry(geometry, heightmapø1);
+                })();
+                var G__11ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__11ø1 - G__10ø1) + 'ms.');
+                return G__12ø1;
+            }.call(this));
+            console.log('Rebuilding plane...');
+            return function () {
+                var G__13ø1 = new Date().getTime();
+                var G__15ø1 = (function () {
+                    scene.remove(plane);
+                    plane = makePlane(geometry);
+                    return scene.add(plane);
+                })();
+                var G__14ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__14ø1 - G__13ø1) + 'ms.');
+                return G__15ø1;
+            }.call(this);
+        }.call(this);
+    };
+    attachToDom(renderer, elementId, refresh);
+    var controls = makeControls(camera, renderer);
+    var render = function render() {
+        return function () {
+            var deltaø1 = clock.getDelta();
+            requestAnimationFrame(render);
+            controls.update(deltaø1);
+            return renderer.render(scene, camera);
+        }.call(this);
+    };
+    refresh();
+    render();
+    return void 0;
+};
+var run = exports.run = function run() {
+    return makeFinal('demo-final');
+};
+$(run);
+
+
+},{"ndarray":4}],2:[function(require,module,exports){
+"use strict"
+
+function iota(n) {
+  var result = new Array(n)
+  for(var i=0; i<n; ++i) {
+    result[i] = i
+  }
+  return result
+}
+
+module.exports = iota
+},{}],3:[function(require,module,exports){
+/**
+ * Determine if an object is Buffer
+ *
+ * Author:   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
+ * License:  MIT
+ *
+ * `npm install is-buffer`
+ */
+
+module.exports = function (obj) {
+  return !!(obj != null &&
+    (obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor)
+      (obj.constructor &&
+      typeof obj.constructor.isBuffer === 'function' &&
+      obj.constructor.isBuffer(obj))
+    ))
+}
+
+},{}],4:[function(require,module,exports){
+var iota = require("iota-array")
+var isBuffer = require("is-buffer")
+
+var hasTypedArrays  = ((typeof Float64Array) !== "undefined")
+
+function compare1st(a, b) {
+  return a[0] - b[0]
+}
+
+function order() {
+  var stride = this.stride
+  var terms = new Array(stride.length)
+  var i
+  for(i=0; i<terms.length; ++i) {
+    terms[i] = [Math.abs(stride[i]), i]
+  }
+  terms.sort(compare1st)
+  var result = new Array(terms.length)
+  for(i=0; i<result.length; ++i) {
+    result[i] = terms[i][1]
+  }
+  return result
+}
+
+function compileConstructor(dtype, dimension) {
+  var className = ["View", dimension, "d", dtype].join("")
+  if(dimension < 0) {
+    className = "View_Nil" + dtype
+  }
+  var useGetters = (dtype === "generic")
+
+  if(dimension === -1) {
+    //Special case for trivial arrays
+    var code =
+      "function "+className+"(a){this.data=a;};\
+var proto="+className+".prototype;\
+proto.dtype='"+dtype+"';\
+proto.index=function(){return -1};\
+proto.size=0;\
+proto.dimension=-1;\
+proto.shape=proto.stride=proto.order=[];\
+proto.lo=proto.hi=proto.transpose=proto.step=\
+function(){return new "+className+"(this.data);};\
+proto.get=proto.set=function(){};\
+proto.pick=function(){return null};\
+return function construct_"+className+"(a){return new "+className+"(a);}"
+    var procedure = new Function(code)
+    return procedure()
+  } else if(dimension === 0) {
+    //Special case for 0d arrays
+    var code =
+      "function "+className+"(a,d) {\
+this.data = a;\
+this.offset = d\
+};\
+var proto="+className+".prototype;\
+proto.dtype='"+dtype+"';\
+proto.index=function(){return this.offset};\
+proto.dimension=0;\
+proto.size=1;\
+proto.shape=\
+proto.stride=\
+proto.order=[];\
+proto.lo=\
+proto.hi=\
+proto.transpose=\
+proto.step=function "+className+"_copy() {\
+return new "+className+"(this.data,this.offset)\
+};\
+proto.pick=function "+className+"_pick(){\
+return TrivialArray(this.data);\
+};\
+proto.valueOf=proto.get=function "+className+"_get(){\
+return "+(useGetters ? "this.data.get(this.offset)" : "this.data[this.offset]")+
+"};\
+proto.set=function "+className+"_set(v){\
+return "+(useGetters ? "this.data.set(this.offset,v)" : "this.data[this.offset]=v")+"\
+};\
+return function construct_"+className+"(a,b,c,d){return new "+className+"(a,d)}"
+    var procedure = new Function("TrivialArray", code)
+    return procedure(CACHED_CONSTRUCTORS[dtype][0])
+  }
+
+  var code = ["'use strict'"]
+
+  //Create constructor for view
+  var indices = iota(dimension)
+  var args = indices.map(function(i) { return "i"+i })
+  var index_str = "this.offset+" + indices.map(function(i) {
+        return "this.stride[" + i + "]*i" + i
+      }).join("+")
+  var shapeArg = indices.map(function(i) {
+      return "b"+i
+    }).join(",")
+  var strideArg = indices.map(function(i) {
+      return "c"+i
+    }).join(",")
+  code.push(
+    "function "+className+"(a," + shapeArg + "," + strideArg + ",d){this.data=a",
+      "this.shape=[" + shapeArg + "]",
+      "this.stride=[" + strideArg + "]",
+      "this.offset=d|0}",
+    "var proto="+className+".prototype",
+    "proto.dtype='"+dtype+"'",
+    "proto.dimension="+dimension)
+
+  //view.size:
+  code.push("Object.defineProperty(proto,'size',{get:function "+className+"_size(){\
+return "+indices.map(function(i) { return "this.shape["+i+"]" }).join("*"),
+"}})")
+
+  //view.order:
+  if(dimension === 1) {
+    code.push("proto.order=[0]")
+  } else {
+    code.push("Object.defineProperty(proto,'order',{get:")
+    if(dimension < 4) {
+      code.push("function "+className+"_order(){")
+      if(dimension === 2) {
+        code.push("return (Math.abs(this.stride[0])>Math.abs(this.stride[1]))?[1,0]:[0,1]}})")
+      } else if(dimension === 3) {
+        code.push(
+"var s0=Math.abs(this.stride[0]),s1=Math.abs(this.stride[1]),s2=Math.abs(this.stride[2]);\
+if(s0>s1){\
+if(s1>s2){\
+return [2,1,0];\
+}else if(s0>s2){\
+return [1,2,0];\
+}else{\
+return [1,0,2];\
+}\
+}else if(s0>s2){\
+return [2,0,1];\
+}else if(s2>s1){\
+return [0,1,2];\
+}else{\
+return [0,2,1];\
+}}})")
+      }
+    } else {
+      code.push("ORDER})")
+    }
+  }
+
+  //view.set(i0, ..., v):
+  code.push(
+"proto.set=function "+className+"_set("+args.join(",")+",v){")
+  if(useGetters) {
+    code.push("return this.data.set("+index_str+",v)}")
+  } else {
+    code.push("return this.data["+index_str+"]=v}")
+  }
+
+  //view.get(i0, ...):
+  code.push("proto.get=function "+className+"_get("+args.join(",")+"){")
+  if(useGetters) {
+    code.push("return this.data.get("+index_str+")}")
+  } else {
+    code.push("return this.data["+index_str+"]}")
+  }
+
+  //view.index:
+  code.push(
+    "proto.index=function "+className+"_index(", args.join(), "){return "+index_str+"}")
+
+  //view.hi():
+  code.push("proto.hi=function "+className+"_hi("+args.join(",")+"){return new "+className+"(this.data,"+
+    indices.map(function(i) {
+      return ["(typeof i",i,"!=='number'||i",i,"<0)?this.shape[", i, "]:i", i,"|0"].join("")
+    }).join(",")+","+
+    indices.map(function(i) {
+      return "this.stride["+i + "]"
+    }).join(",")+",this.offset)}")
+
+  //view.lo():
+  var a_vars = indices.map(function(i) { return "a"+i+"=this.shape["+i+"]" })
+  var c_vars = indices.map(function(i) { return "c"+i+"=this.stride["+i+"]" })
+  code.push("proto.lo=function "+className+"_lo("+args.join(",")+"){var b=this.offset,d=0,"+a_vars.join(",")+","+c_vars.join(","))
+  for(var i=0; i<dimension; ++i) {
+    code.push(
+"if(typeof i"+i+"==='number'&&i"+i+">=0){\
+d=i"+i+"|0;\
+b+=c"+i+"*d;\
+a"+i+"-=d}")
+  }
+  code.push("return new "+className+"(this.data,"+
+    indices.map(function(i) {
+      return "a"+i
+    }).join(",")+","+
+    indices.map(function(i) {
+      return "c"+i
+    }).join(",")+",b)}")
+
+  //view.step():
+  code.push("proto.step=function "+className+"_step("+args.join(",")+"){var "+
+    indices.map(function(i) {
+      return "a"+i+"=this.shape["+i+"]"
+    }).join(",")+","+
+    indices.map(function(i) {
+      return "b"+i+"=this.stride["+i+"]"
+    }).join(",")+",c=this.offset,d=0,ceil=Math.ceil")
+  for(var i=0; i<dimension; ++i) {
+    code.push(
+"if(typeof i"+i+"==='number'){\
+d=i"+i+"|0;\
+if(d<0){\
+c+=b"+i+"*(a"+i+"-1);\
+a"+i+"=ceil(-a"+i+"/d)\
+}else{\
+a"+i+"=ceil(a"+i+"/d)\
+}\
+b"+i+"*=d\
+}")
+  }
+  code.push("return new "+className+"(this.data,"+
+    indices.map(function(i) {
+      return "a" + i
+    }).join(",")+","+
+    indices.map(function(i) {
+      return "b" + i
+    }).join(",")+",c)}")
+
+  //view.transpose():
+  var tShape = new Array(dimension)
+  var tStride = new Array(dimension)
+  for(var i=0; i<dimension; ++i) {
+    tShape[i] = "a[i"+i+"]"
+    tStride[i] = "b[i"+i+"]"
+  }
+  code.push("proto.transpose=function "+className+"_transpose("+args+"){"+
+    args.map(function(n,idx) { return n + "=(" + n + "===undefined?" + idx + ":" + n + "|0)"}).join(";"),
+    "var a=this.shape,b=this.stride;return new "+className+"(this.data,"+tShape.join(",")+","+tStride.join(",")+",this.offset)}")
+
+  //view.pick():
+  code.push("proto.pick=function "+className+"_pick("+args+"){var a=[],b=[],c=this.offset")
+  for(var i=0; i<dimension; ++i) {
+    code.push("if(typeof i"+i+"==='number'&&i"+i+">=0){c=(c+this.stride["+i+"]*i"+i+")|0}else{a.push(this.shape["+i+"]);b.push(this.stride["+i+"])}")
+  }
+  code.push("var ctor=CTOR_LIST[a.length+1];return ctor(this.data,a,b,c)}")
+
+  //Add return statement
+  code.push("return function construct_"+className+"(data,shape,stride,offset){return new "+className+"(data,"+
+    indices.map(function(i) {
+      return "shape["+i+"]"
+    }).join(",")+","+
+    indices.map(function(i) {
+      return "stride["+i+"]"
+    }).join(",")+",offset)}")
+
+  //Compile procedure
+  var procedure = new Function("CTOR_LIST", "ORDER", code.join("\n"))
+  return procedure(CACHED_CONSTRUCTORS[dtype], order)
+}
+
+function arrayDType(data) {
+  if(isBuffer(data)) {
+    return "buffer"
+  }
+  if(hasTypedArrays) {
+    switch(Object.prototype.toString.call(data)) {
+      case "[object Float64Array]":
+        return "float64"
+      case "[object Float32Array]":
+        return "float32"
+      case "[object Int8Array]":
+        return "int8"
+      case "[object Int16Array]":
+        return "int16"
+      case "[object Int32Array]":
+        return "int32"
+      case "[object Uint8Array]":
+        return "uint8"
+      case "[object Uint16Array]":
+        return "uint16"
+      case "[object Uint32Array]":
+        return "uint32"
+      case "[object Uint8ClampedArray]":
+        return "uint8_clamped"
+    }
+  }
+  if(Array.isArray(data)) {
+    return "array"
+  }
+  return "generic"
+}
+
+var CACHED_CONSTRUCTORS = {
+  "float32":[],
+  "float64":[],
+  "int8":[],
+  "int16":[],
+  "int32":[],
+  "uint8":[],
+  "uint16":[],
+  "uint32":[],
+  "array":[],
+  "uint8_clamped":[],
+  "buffer":[],
+  "generic":[]
+}
+
+;(function() {
+  for(var id in CACHED_CONSTRUCTORS) {
+    CACHED_CONSTRUCTORS[id].push(compileConstructor(id, -1))
+  }
+});
+
+function wrappedNDArrayCtor(data, shape, stride, offset) {
+  if(data === undefined) {
+    var ctor = CACHED_CONSTRUCTORS.array[0]
+    return ctor([])
+  } else if(typeof data === "number") {
+    data = [data]
+  }
+  if(shape === undefined) {
+    shape = [ data.length ]
+  }
+  var d = shape.length
+  if(stride === undefined) {
+    stride = new Array(d)
+    for(var i=d-1, sz=1; i>=0; --i) {
+      stride[i] = sz
+      sz *= shape[i]
+    }
+  }
+  if(offset === undefined) {
+    offset = 0
+    for(var i=0; i<d; ++i) {
+      if(stride[i] < 0) {
+        offset -= (shape[i]-1)*stride[i]
+      }
+    }
+  }
+  var dtype = arrayDType(data)
+  var ctor_list = CACHED_CONSTRUCTORS[dtype]
+  while(ctor_list.length <= d+1) {
+    ctor_list.push(compileConstructor(dtype, ctor_list.length-1))
+  }
+  var ctor = ctor_list[d+1]
+  return ctor(data, shape, stride, offset)
+}
+
+module.exports = wrappedNDArrayCtor
+
+},{"iota-array":2,"is-buffer":3}]},{},[1]);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/media/js/wisp/terrain2.js	Sat Mar 05 21:26:30 2016 +0000
@@ -0,0 +1,404 @@
+{
+    var _ns_ = {
+        id: 'demo',
+        doc: void 0
+    };
+    var ndarray = require('ndarray');
+}
+var width = exports.width = 610;
+var height = exports.height = 400;
+var wireframe = exports.wireframe = true;
+var wireframeWidth = exports.wireframeWidth = 1.2;
+var terrainHeight = exports.terrainHeight = 50;
+var terrainSize = exports.terrainSize = 100;
+void 0;
+void 0;
+void 0;
+var inc = exports.inc = function inc(x) {
+    return x + 1;
+};
+var dec = exports.dec = function dec(x) {
+    return x - 1;
+};
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+void 0;
+var midpoint = exports.midpoint = function midpoint(a, b) {
+    return (a + b) / 2;
+};
+var average2 = exports.average2 = function average2(a, b) {
+    return (a + b) / 2;
+};
+var average4 = exports.average4 = function average4(a, b, c, d) {
+    return (a + b + c + d) / 4;
+};
+var safeAverage = exports.safeAverage = function safeAverage(a, b, c, d) {
+    return function () {
+        var totalø1 = 0;
+        var countø1 = 0;
+        a ? (function () {
+            totalø1 = totalø1 + a;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        b ? (function () {
+            totalø1 = totalø1 + b;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        c ? (function () {
+            totalø1 = totalø1 + c;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        d ? (function () {
+            totalø1 = totalø1 + d;
+            return countø1 = inc(countø1);
+        })() : void 0;
+        return totalø1 / countø1;
+    }.call(this);
+};
+var rand = exports.rand = function rand() {
+    return Math.random();
+};
+var randAroundZero = exports.randAroundZero = function randAroundZero(spread) {
+    return spread * rand() * 2 - spread;
+};
+var jitter = exports.jitter = function jitter(value, spread) {
+    return value + randAroundZero(spread);
+};
+var heightmapResolution = exports.heightmapResolution = function heightmapResolution(heightmap) {
+    return heightmap.shape[0];
+};
+var heightmapLastIndex = exports.heightmapLastIndex = function heightmapLastIndex(heightmap) {
+    return dec(heightmapResolution(heightmap));
+};
+var heightmapCenterIndex = exports.heightmapCenterIndex = function heightmapCenterIndex(heightmap) {
+    return midpoint(0, heightmapLastIndex(heightmap));
+};
+var heightmapGet = exports.heightmapGet = function heightmapGet(heightmap, x, y) {
+    return heightmap.get(x, y);
+};
+var heightmapGetSafe = exports.heightmapGetSafe = function heightmapGetSafe(heightmap, x, y) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        return 0 <= x && x <= lastø1 && (0 <= y && y <= lastø1) ? (function () {
+            return heightmapGet(heightmap, x, y);
+        })() : void 0;
+    }.call(this);
+};
+var heightmapSet = exports.heightmapSet = function heightmapSet(heightmap, x, y, val) {
+    return heightmap.set(x, y, val);
+};
+var heightmapSetIfUnset = exports.heightmapSetIfUnset = function heightmapSetIfUnset(heightmap, x, y, val) {
+    return 0 == heightmapGet(heightmap, x, y) ? (function () {
+        return heightmapSet(heightmap, x, y, val);
+    })() : void 0;
+};
+var normalize = exports.normalize = function normalize(heightmap) {
+    return function () {
+        var maxø1 = 0 - Infinity;
+        var minø1 = Infinity;
+        (function () {
+            var array2ø1 = heightmap;
+            return function () {
+                var G__3ø1 = array2ø1.data.length;
+                return function loop() {
+                    var recur = loop;
+                    var index1ø1 = 0;
+                    do {
+                        recur = index1ø1 < G__3ø1 ? (function () {
+                            (function () {
+                                var elø1 = array2ø1.data[index1ø1];
+                                maxø1 < elø1 ? (function () {
+                                    return maxø1 = elø1;
+                                })() : void 0;
+                                return minø1 > elø1 ? (function () {
+                                    return minø1 = elø1;
+                                })() : void 0;
+                            }.call(this));
+                            return loop[0] = inc(index1ø1), loop;
+                        })() : void 0;
+                    } while (index1ø1 = loop[0], recur === loop);
+                    return recur;
+                }.call(this);
+            }.call(this);
+        }.call(this));
+        return function () {
+            var spanø1 = maxø1 - minø1;
+            return function () {
+                var array4ø1 = heightmap;
+                return function () {
+                    var G__5ø1 = array4ø1.shape[0];
+                    return function loop() {
+                        var recur = loop;
+                        var xø1 = 0;
+                        do {
+                            recur = xø1 < G__5ø1 ? (function () {
+                                (function () {
+                                    var G__6ø1 = array4ø1.shape[1];
+                                    return function loop() {
+                                        var recur = loop;
+                                        var yø1 = 0;
+                                        do {
+                                            recur = yø1 < G__6ø1 ? (function () {
+                                                (function () {
+                                                    return heightmapSet(heightmap, xø1, yø1, (heightmapGet(heightmap, xø1, yø1) - minø1) / spanø1);
+                                                })();
+                                                return loop[0] = inc(yø1), loop;
+                                            })() : void 0;
+                                        } while (yø1 = loop[0], recur === loop);
+                                        return recur;
+                                    }.call(this);
+                                }.call(this));
+                                return loop[0] = inc(xø1), loop;
+                            })() : void 0;
+                        } while (xø1 = loop[0], recur === loop);
+                        return recur;
+                    }.call(this);
+                }.call(this);
+            }.call(this);
+        }.call(this);
+    }.call(this);
+};
+var makeHeightmap = exports.makeHeightmap = function makeHeightmap(exponent) {
+    return function () {
+        var resolutionø1 = Math.pow(2, exponent) + 1;
+        return function () {
+            var heightmapø1 = ndarray(new Float64Array(resolutionø1 * resolutionø1), [
+                resolutionø1,
+                resolutionø1
+            ]);
+            heightmapø1.exponent = exponent;
+            heightmapø1.resolution = resolutionø1;
+            heightmapø1.last = dec(resolutionø1);
+            return heightmapø1;
+        }.call(this);
+    }.call(this);
+};
+var topLeftCorner = exports.topLeftCorner = function topLeftCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(0, 0).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var topRightCorner = exports.topRightCorner = function topRightCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(centerø1, 0).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var bottomLeftCorner = exports.bottomLeftCorner = function bottomLeftCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(0, centerø1).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var bottomRightCorner = exports.bottomRightCorner = function bottomRightCorner(heightmap) {
+    return function () {
+        var centerø1 = heightmapCenterIndex(heightmap);
+        return heightmap.lo(centerø1, centerø1).hi(inc(centerø1), inc(centerø1));
+    }.call(this);
+};
+var mpdInitCorners = exports.mpdInitCorners = function mpdInitCorners(heightmap) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        heightmapSet(heightmap, 0, 0, rand());
+        heightmapSet(heightmap, 0, lastø1, rand());
+        heightmapSet(heightmap, lastø1, 0, rand());
+        return heightmapSet(heightmap, lastø1, lastø1, rand());
+    }.call(this);
+};
+var mpdDisplace = exports.mpdDisplace = function mpdDisplace(heightmap, spread, spreadReduction) {
+    return function () {
+        var lastø1 = heightmapLastIndex(heightmap);
+        var cø1 = midpoint(0, lastø1);
+        var bottomLeftø1 = heightmapGet(heightmap, 0, 0);
+        var bottomRightø1 = heightmapGet(heightmap, lastø1, 0);
+        var topLeftø1 = heightmapGet(heightmap, 0, lastø1);
+        var topRightø1 = heightmapGet(heightmap, lastø1, lastø1);
+        var topø1 = average2(topLeftø1, topRightø1);
+        var leftø1 = average2(bottomLeftø1, topLeftø1);
+        var bottomø1 = average2(bottomLeftø1, bottomRightø1);
+        var rightø1 = average2(bottomRightø1, topRightø1);
+        var centerø1 = average4(topø1, leftø1, bottomø1, rightø1);
+        var nextSpreadø1 = spread * spreadReduction;
+        heightmapSetIfUnset(heightmap, cø1, 0, jitter(bottomø1, spread));
+        heightmapSetIfUnset(heightmap, cø1, lastø1, jitter(topø1, spread));
+        heightmapSetIfUnset(heightmap, 0, cø1, jitter(leftø1, spread));
+        heightmapSetIfUnset(heightmap, lastø1, cø1, jitter(rightø1, spread));
+        heightmapSetIfUnset(heightmap, cø1, cø1, jitter(centerø1, spread));
+        return !(3 == heightmapResolution(heightmap)) ? (function () {
+            heightmapSetIfUnset(heightmap, cø1, 0, jitter(bottomø1, spread));
+            heightmapSetIfUnset(heightmap, cø1, lastø1, jitter(topø1, spread));
+            heightmapSetIfUnset(heightmap, 0, cø1, jitter(leftø1, spread));
+            heightmapSetIfUnset(heightmap, lastø1, cø1, jitter(rightø1, spread));
+            heightmapSetIfUnset(heightmap, cø1, cø1, jitter(centerø1, spread));
+            mpdDisplace(topLeftCorner(heightmap), nextSpreadø1, spreadReduction);
+            mpdDisplace(topRightCorner(heightmap), nextSpreadø1, spreadReduction);
+            mpdDisplace(bottomLeftCorner(heightmap), nextSpreadø1, spreadReduction);
+            return mpdDisplace(bottomRightCorner(heightmap), nextSpreadø1, spreadReduction);
+        })() : void 0;
+    }.call(this);
+};
+var midpointDisplacement = exports.midpointDisplacement = function midpointDisplacement(heightmap) {
+    return function () {
+        var initialSpreadø1 = 0.3;
+        var spreadReductionø1 = 0.55;
+        mpdInitCorners(heightmap);
+        mpdDisplace(heightmap, initialSpreadø1, spreadReductionø1);
+        return normalize(heightmap);
+    }.call(this);
+};
+var makeDirectionalLight = exports.makeDirectionalLight = function makeDirectionalLight() {
+    return function () {
+        var lightø1 = new THREE.DirectionalLight(16777215, 1);
+        lightø1.position.set(100, 0, 150);
+        return lightø1;
+    }.call(this);
+};
+var makeCamera = exports.makeCamera = function makeCamera() {
+    return function () {
+        var cameraø1 = new THREE.PerspectiveCamera(55, width / height, 0.1, 1000);
+        cameraø1.position.set(0, -100, 150);
+        return cameraø1;
+    }.call(this);
+};
+var makeRenderer = exports.makeRenderer = function makeRenderer() {
+    return function () {
+        var rendererø1 = new THREE.WebGLRenderer({ 'antialias': false });
+        rendererø1.setClearColor(16777215);
+        rendererø1.setSize(width, height);
+        rendererø1.setPixelRatio(2);
+        return rendererø1;
+    }.call(this);
+};
+var makeGeometry = exports.makeGeometry = function makeGeometry(heightmap) {
+    return function () {
+        var resolutionø1 = heightmap.shape[0];
+        var geometryø1 = new THREE.PlaneGeometry(terrainSize, terrainSize, resolutionø1 - 1, resolutionø1 - 1);
+        return geometryø1;
+    }.call(this);
+};
+var makeControls = exports.makeControls = function makeControls(camera, renderer) {
+    return function () {
+        var controlsø1 = new THREE.TrackballControls(camera, renderer.domElement);
+        controlsø1.rotateSpeed = 1.4;
+        controlsø1.zoomSpeed = 0.5;
+        controlsø1.staticMoving = true;
+        controlsø1.dynamicDampingFactor = 0.3;
+        return controlsø1;
+    }.call(this);
+};
+var makePlane = exports.makePlane = function makePlane(geometry) {
+    return function () {
+        var materialø1 = new THREE.MeshLambertMaterial({
+            'wireframe': wireframe,
+            'wireframeLinewidth': wireframeWidth,
+            'color': 47872
+        });
+        return new THREE.Mesh(geometry, materialø1);
+    }.call(this);
+};
+var attachToDom = exports.attachToDom = function attachToDom(renderer, elName, refreshFn) {
+    return function () {
+        var containerø1 = document.getElementById(elName);
+        var settingsø1 = document.createElement('div');
+        var refreshButtonø1 = document.createElement('button');
+        var buttonTextø1 = document.createTextNode('Refresh');
+        var cancelScrollø1 = function (e) {
+            return e.preventDefault();
+        };
+        refreshButtonø1.onclick = refreshFn;
+        renderer.domElement.onmousewheel = cancelScrollø1;
+        renderer.domElement.addEventListener('MozMousePixelScroll', cancelScrollø1, false);
+        refreshButtonø1.appendChild(buttonTextø1);
+        containerø1.appendChild(renderer.domElement);
+        containerø1.appendChild(settingsø1);
+        return settingsø1.appendChild(refreshButtonø1);
+    }.call(this);
+};
+var updateGeometry = exports.updateGeometry = function updateGeometry(geometry, heightmap) {
+    (function loop() {
+        var recur = loop;
+        var iø1 = 0;
+        do {
+            recur = iø1 < geometry.vertices.length ? (function () {
+                geometry.vertices[iø1].z = terrainHeight * heightmap.data[iø1];
+                return loop[0] = iø1 + 1, loop;
+            })() : void 0;
+        } while (iø1 = loop[0], recur === loop);
+        return recur;
+    }.call(this));
+    geometry.computeVertexNormals();
+    return geometry;
+};
+var makeFinal = exports.makeFinal = function makeFinal(elementId) {
+    var scene = new THREE.Scene();
+    scene.add(new THREE.AxisHelper(100));
+    var clock = new THREE.Clock();
+    var camera = makeCamera();
+    var renderer = makeRenderer();
+    var geometry = void 0;
+    var plane = void 0;
+    scene.add(makeDirectionalLight());
+    scene.add(new THREE.AmbientLight(16777215, 0.05));
+    var refresh = function refresh() {
+        return function () {
+            var heightmapø1 = makeHeightmap(6);
+            console.log('Generating terrain...');
+            (function () {
+                var G__7ø1 = new Date().getTime();
+                var G__9ø1 = (function () {
+                    return midpointDisplacement(heightmapø1);
+                })();
+                var G__8ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__8ø1 - G__7ø1) + 'ms.');
+                return G__9ø1;
+            }.call(this));
+            console.log('Rebuilding geometry...');
+            (function () {
+                var G__10ø1 = new Date().getTime();
+                var G__12ø1 = (function () {
+                    geometry = makeGeometry(heightmapø1);
+                    return updateGeometry(geometry, heightmapø1);
+                })();
+                var G__11ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__11ø1 - G__10ø1) + 'ms.');
+                return G__12ø1;
+            }.call(this));
+            console.log('Rebuilding plane...');
+            return function () {
+                var G__13ø1 = new Date().getTime();
+                var G__15ø1 = (function () {
+                    scene.remove(plane);
+                    plane = makePlane(geometry);
+                    return scene.add(plane);
+                })();
+                var G__14ø1 = new Date().getTime();
+                console.log('Elapsed time: ' + (G__14ø1 - G__13ø1) + 'ms.');
+                return G__15ø1;
+            }.call(this);
+        }.call(this);
+    };
+    attachToDom(renderer, elementId, refresh);
+    var controls = makeControls(camera, renderer);
+    var render = function render() {
+        return function () {
+            var deltaø1 = clock.getDelta();
+            requestAnimationFrame(render);
+            controls.update(deltaø1);
+            return renderer.render(scene, camera);
+        }.call(this);
+    };
+    refresh();
+    render();
+    return void 0;
+};
+var run = exports.run = function run() {
+    return makeFinal('demo-final');
+};
+$(run);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFub255bW91cy53aXNwIl0sIm5hbWVzIjpbIl9uc18iLCJpZCIsImRvYyIsIndpZHRoIiwiZXhwb3J0cyIsImhlaWdodCIsIndpcmVmcmFtZSIsIndpcmVmcmFtZVdpZHRoIiwidGVycmFpbkhlaWdodCIsInRlcnJhaW5TaXplIiwiaW5jIiwieCIsImRlYyIsIm1pZHBvaW50IiwiYSIsImIiLCJhdmVyYWdlMiIsImF2ZXJhZ2U0IiwiYyIsImQiLCJzYWZlQXZlcmFnZSIsInRvdGFsw7gxIiwiY291bnTDuDEiLCJyYW5kIiwiTWF0aCIsInJhbmRvbSIsInJhbmRBcm91bmRaZXJvIiwic3ByZWFkIiwiaml0dGVyIiwidmFsdWUiLCJoZWlnaHRtYXBSZXNvbHV0aW9uIiwiaGVpZ2h0bWFwIiwic2hhcGUiLCJoZWlnaHRtYXBMYXN0SW5kZXgiLCJoZWlnaHRtYXBDZW50ZXJJbmRleCIsImhlaWdodG1hcEdldCIsInkiLCJnZXQiLCJoZWlnaHRtYXBHZXRTYWZlIiwibGFzdMO4MSIsImhlaWdodG1hcFNldCIsInZhbCIsInNldCIsImhlaWdodG1hcFNldElmVW5zZXQiLCJub3JtYWxpemUiLCJtYXjDuDEiLCJJbmZpbml0eSIsIm1pbsO4MSIsImVsw7gxIiwic3BhbsO4MSIsInjDuDEiLCJ5w7gxIiwibWFrZUhlaWdodG1hcCIsImV4cG9uZW50IiwicmVzb2x1dGlvbsO4MSIsInBvdyIsImhlaWdodG1hcMO4MSIsIm5kYXJyYXkiLCJGbG9hdDY0QXJyYXkiLCJyZXNvbHV0aW9uIiwibGFzdCIsInRvcExlZnRDb3JuZXIiLCJjZW50ZXLDuDEiLCJsbyIsImhpIiwidG9wUmlnaHRDb3JuZXIiLCJib3R0b21MZWZ0Q29ybmVyIiwiYm90dG9tUmlnaHRDb3JuZXIiLCJtcGRJbml0Q29ybmVycyIsIm1wZERpc3BsYWNlIiwic3ByZWFkUmVkdWN0aW9uIiwiY8O4MSIsImJvdHRvbUxlZnTDuDEiLCJib3R0b21SaWdodMO4MSIsInRvcExlZnTDuDEiLCJ0b3BSaWdodMO4MSIsInRvcMO4MSIsImxlZnTDuDEiLCJib3R0b23DuDEiLCJyaWdodMO4MSIsIm5leHRTcHJlYWTDuDEiLCJtaWRwb2ludERpc3BsYWNlbWVudCIsImluaXRpYWxTcHJlYWTDuDEiLCJzcHJlYWRSZWR1Y3Rpb27DuDEiLCJtYWtlRGlyZWN0aW9uYWxMaWdodCIsImxpZ2h0w7gxIiwiVEhSRUUiLCJEaXJlY3Rpb25hbExpZ2h0IiwicG9zaXRpb24uc2V0IiwibWFrZUNhbWVyYSIsImNhbWVyYcO4MSIsIlBlcnNwZWN0aXZlQ2FtZXJhIiwibWFrZVJlbmRlcmVyIiwicmVuZGVyZXLDuDEiLCJXZWJHTFJlbmRlcmVyIiwic2V0Q2xlYXJDb2xvciIsInNldFNpemUiLCJzZXRQaXhlbFJhdGlvIiwibWFrZUdlb21ldHJ5IiwiZ2VvbWV0cnnDuDEiLCJQbGFuZUdlb21ldHJ5IiwibWFrZUNvbnRyb2xzIiwiY2FtZXJhIiwicmVuZGVyZXIiLCJjb250cm9sc8O4MSIsIlRyYWNrYmFsbENvbnRyb2xzIiwiZG9tRWxlbWVudCIsInJvdGF0ZVNwZWVkIiwiem9vbVNwZWVkIiwic3RhdGljTW92aW5nIiwiZHluYW1pY0RhbXBpbmdGYWN0b3IiLCJtYWtlUGxhbmUiLCJnZW9tZXRyeSIsIm1hdGVyaWFsw7gxIiwiTWVzaExhbWJlcnRNYXRlcmlhbCIsIk1lc2giLCJhdHRhY2hUb0RvbSIsImVsTmFtZSIsInJlZnJlc2hGbiIsImNvbnRhaW5lcsO4MSIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJzZXR0aW5nc8O4MSIsImNyZWF0ZUVsZW1lbnQiLCJyZWZyZXNoQnV0dG9uw7gxIiwiYnV0dG9uVGV4dMO4MSIsImNyZWF0ZVRleHROb2RlIiwiY2FuY2VsU2Nyb2xsw7gxIiwiZSIsInByZXZlbnREZWZhdWx0Iiwib25jbGljayIsImRvbUVsZW1lbnQub25tb3VzZXdoZWVsIiwiZG9tRWxlbWVudC5hZGRFdmVudExpc3RlbmVyIiwiYXBwZW5kQ2hpbGQiLCJ1cGRhdGVHZW9tZXRyeSIsImnDuDEiLCJ2ZXJ0aWNlcy5sZW5ndGgiLCJ2ZXJ0aWNlcyIsInoiLCJkYXRhIiwiY29tcHV0ZVZlcnRleE5vcm1hbHMiLCJtYWtlRmluYWwiLCJlbGVtZW50SWQiLCJzY2VuZSIsIlNjZW5lIiwiYWRkIiwiQXhpc0hlbHBlciIsImNsb2NrIiwiQ2xvY2siLCJwbGFuZSIsIkFtYmllbnRMaWdodCIsInJlZnJlc2giLCJyZW1vdmUiLCJjb250cm9scyIsInJlbmRlciIsImRlbHRhw7gxIiwiZ2V0RGVsdGEiLCJyZXF1ZXN0QW5pbWF0aW9uRnJhbWUiLCJ1cGRhdGUiLCJydW4iLCIkIl0sIm1hcHBpbmdzIjoiO0lBQUEsSUFBQ0EsSSxHQUFEO0FBQUEsUUFBQUMsRSxFQUFJLE1BQUo7QUFBQSxRQUFBQyxHLEVBQUEsSyxDQUFBO0FBQUEsTTs7O0FBSUEsSUFBS0MsS0FBQSxHQUFBQyxPQUFBLENBQUFELEtBQUEsR0FBTSxHQUFYLEM7QUFDQSxJQUFLRSxNQUFBLEdBQUFELE9BQUEsQ0FBQUMsTUFBQSxHQUFPLEdBQVosQztBQUNBLElBQUtDLFNBQUEsR0FBQUYsT0FBQSxDQUFBRSxTQUFBLEcsSUFBTCxDO0FBQ0EsSUFBS0MsY0FBQSxHQUFBSCxPQUFBLENBQUFHLGNBQUEsR0FBZ0IsR0FBckIsQztBQUNBLElBQUtDLGFBQUEsR0FBQUosT0FBQSxDQUFBSSxhQUFBLEdBQWUsRUFBcEIsQztBQUNBLElBQUtDLFdBQUEsR0FBQUwsT0FBQSxDQUFBSyxXQUFBLEdBQWEsR0FBbEIsQzs7OztBQW9CQSxJQUFNQyxHQUFBLEdBQUFOLE9BQUEsQ0FBQU0sR0FBQSxHQUFOLFNBQU1BLEdBQU4sQ0FBV0MsQ0FBWCxFQUNFO0FBQUEsV0FBR0EsQ0FBSCxHQUFLLENBQUw7QUFBQSxDQURGLEM7QUFHQSxJQUFNQyxHQUFBLEdBQUFSLE9BQUEsQ0FBQVEsR0FBQSxHQUFOLFNBQU1BLEdBQU4sQ0FBV0QsQ0FBWCxFQUNFO0FBQUEsV0FBR0EsQ0FBSCxHQUFLLENBQUw7QUFBQSxDQURGLEM7Ozs7Ozs7OztBQXdFQSxJQUFNRSxRQUFBLEdBQUFULE9B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O0FBQUEsUyxLQUFOLEMsSUFBQTtBQUFBLEtBREYsQztJQU1DaUQsT0FBRCxHO0lBQ0NHLE1BQUQsRzs7Q0F4Q0YsQztBQTRDQSxJQUFNSyxHQUFBLEdBQUEzSixPQUFBLENBQUEySixHQUFBLEdBQU4sU0FBTUEsR0FBTixHQUNFO0FBQUEsV0FBQ2xCLFNBQUQsQ0FBWSxZQUFaO0FBQUEsQ0FERixDO0FBR0NtQixDQUFELENBQUdELEdBQUgiLCJzb3VyY2VzQ29udGVudCI6WyIobnMgZGVtb1xuICAoOnJlcXVpcmUgW25kYXJyYXldKSlcblxuOyBDb25zdGFudHMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuKGRlZiB3aWR0aCA2MTApXG4oZGVmIGhlaWdodCA0MDApXG4oZGVmIHdpcmVmcmFtZSB0cnVlKVxuKGRlZiB3aXJlZnJhbWUtd2lkdGggMS4yKVxuKGRlZiB0ZXJyYWluLWhlaWdodCA1MClcbihkZWYgdGVycmFpbi1zaXplIDEwMClcblxuOyBHZW5lcmFsIFV0aWxpdGllcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuKGRlZm1hY3JvIHdoZW4gW2NvbmRpdGlvbiAmIGJvZHldXG4gIGAoaWYgfmNvbmRpdGlvblxuICAgICAoZG8gfkBib2R5KSkpXG5cbihkZWZtYWNybyB3aGVuLW5vdCBbY29uZGl0aW9uICYgYm9keV1cbiAgYCh3aGVuIChub3QgfmNvbmRpdGlvbilcbiAgICAgfkBib2R5KSlcblxuKGRlZm1hY3JvIC0+IFsmIG9wZXJhdGlvbnNdXG4gIChyZWR1Y2VcbiAgICAoZm4gW2Zvcm0gb3BlcmF0aW9uXVxuICAgICAgKGNvbnMgKGZpcnN0IG9wZXJhdGlvbilcbiAgICAgICAgICAgIChjb25zIGZvcm0gKHJlc3Qgb3BlcmF0aW9uKSkpKVxuICAgIChmaXJzdCBvcGVyYXRpb25zKVxuICAgIChyZXN0IG9wZXJhdGlvbnMpKSlcblxuXG4oZGVmbiBpbmMgW3hdXG4gICgrIHggMSkpXG5cbihkZWZuIGRlYyBbeF1cbiAgKC0geCAxKSlcblxuXG4oZGVmbWFjcm8gZG8tdGltZXMgW3Zhcm5hbWUgbGltaXQgJiBib2R5XVxuICAobGV0IFtlbmQgKGdlbnN5bSldXG4gICAgYChsZXQgW35lbmQgfmxpbWl0XVxuICAgICAgIChsb29wIFt+dmFybmFtZSAwXVxuICAgICAgICAgKHdoZW4gKDwgfnZhcm5hbWUgfmVuZClcbiAgICAgICAgICAgfkBib2R5XG4gICAgICAgICAgIChyZWN1ciAoaW5jIH52YXJuYW1lKSkpKSkpKVxuXG4oZGVmbWFjcm8gZG8tc3RyaWRlIFt2YXJuYW1lcyBzdGFydC1mb3JtIGVuZC1mb3JtIHN0cmlkZS1mb3JtICYgYm9keV1cbiAgKGxldCBbc3RyaWRlIChnZW5zeW0gXCJzdHJpZGVcIilcbiAgICAgICAgc3RhcnQgKGdlbnN5bSBcInN0YXJ0XCIpXG4gICAgICAgIGVuZCAoZ2Vuc3ltIFwiZW5kXCIpXG4gICAgICAgIGJ1aWxkIChmbiBidWlsZCBbdmFyc11cbiAgICAgICAgICAgICAgICAoaWYgKGVtcHR5PyB2YXJzKVxuICAgICAgICAgICAgICAgICAgYChkbyB+QGJvZHkpXG4gICAgICAgICAgICAgICAgICAobGV0IFt2YXJuYW1lIChmaXJzdCB2YXJzKV1cbiAgICAgICAgICAgICAgICAgICAgYChsb29wIFt+dmFybmFtZSB+c3RhcnRdXG4gICAgICAgICAgICAgICAgICAgICAgICh3aGVuICg8IH52YXJuYW1lIH5lbmQpXG4gICAgICAgICAgICAgICAgICAgICAgICAgfihidWlsZCAocmVzdCB2YXJzKSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAocmVjdXIgKCsgfnZhcm5hbWUgfnN0cmlkZSkpKSkpKSldXG4gICAgOyBGaXggdGhlIG51bWJlcnMgb25jZSBvdXRzaWRlIHRoZSBuZXN0ZWQgbG9vcHMsXG4gICAgOyBhbmQgdGhlbiBidWlsZCB0aGUgZ3V0cy5cbiAgICBgKGxldCBbfnN0YXJ0IH5zdGFydC1mb3JtXG4gICAgICAgICAgIH5lbmQgfmVuZC1mb3JtXG4gICAgICAgICAgIH5zdHJpZGUgfnN0cmlkZS1mb3JtXVxuICAgICAgIH4oYnVpbGQgdmFybmFtZXMpKSkpXG5cblxuKGRlZm1hY3JvIGRvLW5kYXJyYXkgW3ZhcnMgYXJyYXktZm9ybSAmIGJvZHldXG4gIChsZXQgW2FycmF5LXZhciAoZ2Vuc3ltIFwiYXJyYXlcIilcbiAgICAgICAgYnVpbGQgKGZuIGJ1aWxkIFt2YXJzIG5dXG4gICAgICAgICAgICAgICAgKGlmIChlbXB0eT8gdmFycylcbiAgICAgICAgICAgICAgICAgIGAoZG8gfkBib2R5KVxuICAgICAgICAgICAgICAgICAgYChkby10aW1lcyB+KGZpcnN0IHZhcnMpIChhZ2V0ICguLXNoYXBlIH5hcnJheS12YXIpIH5uKVxuICAgICAgICAgICAgICAgICAgICAgfihidWlsZCAocmVzdCB2YXJzKSAoaW5jIG4pKSkpKV1cbiAgICBgKGxldCBbfmFycmF5LXZhciB+YXJyYXktZm9ybV1cbiAgICAgICB+KGJ1aWxkIHZhcnMgMCkpKSlcblxuKGRlZm1hY3JvIGRvLW5kYXJyYXktZWwgW2VsZW1lbnQgYXJyYXktZm9ybSAmIGJvZHldXG4gIChsZXQgW2luZGV4IChnZW5zeW0gXCJpbmRleFwiKVxuICAgICAgICBhcnJheSAoZ2Vuc3ltIFwiYXJyYXlcIildXG4gICAgYChsZXQgW35hcnJheSB+YXJyYXktZm9ybV1cbiAgICAgICAoZG8tdGltZXMgfmluZGV4ICguLWxlbmd0aCAoLi1kYXRhIH5hcnJheSkpXG4gICAgICAgICAobGV0IFt+ZWxlbWVudCAoYWdldCAoLi1kYXRhIH5hcnJheSkgfmluZGV4KV1cbiAgICAgICAgICAgfkBib2R5KSkpKSlcblxuXG4oZGVmbWFjcm8gaW5jISBbcGxhY2VdXG4gIGAoc2V0ISB+cGxhY2UgKGluYyB+cGxhY2UpKSlcblxuKGRlZm1hY3JvIGFkZCEgW3BsYWNlIGFtb3VudF1cbiAgYChzZXQhIH5wbGFjZSAoKyB+cGxhY2UgfmFtb3VudCkpKVxuXG5cbihkZWZtYWNybyBsIFsmIGZvcm1zXVxuICBgKGNvbnNvbGUubG9nIH5AZm9ybXMpKVxuXG4oZGVmbWFjcm8gdGltZSBbJiBib2R5XVxuICAobGV0IFtzdGFydCAoZ2Vuc3ltKVxuICAgICAgICBlbmQgKGdlbnN5bSlcbiAgICAgICAgcmVzdWx0IChnZW5zeW0pXVxuICAgIGAobGV0IFt+c3RhcnQgKC5nZXRUaW1lIChuZXcgRGF0ZSkpXG4gICAgICAgICAgIH5yZXN1bHQgKGRvIH5AYm9keSlcbiAgICAgICAgICAgfmVuZCAoLmdldFRpbWUgKG5ldyBEYXRlKSldXG4gICAgICAgKGwgKCsgXCJFbGFwc2VkIHRpbWU6IFwiICgtIH5lbmQgfnN0YXJ0KSBcIm1zLlwiKSlcbiAgICAgICB+cmVzdWx0KSkpXG5cblxuKGRlZm4gbWlkcG9pbnQgW2EgYl1cbiAgKC8gKCsgYSBiKSAyKSlcblxuKGRlZm4gYXZlcmFnZTIgW2EgYl1cbiAgKC8gKCsgYSBiKSAyKSlcblxuKGRlZm4gYXZlcmFnZTQgW2EgYiBjIGRdXG4gICgvICgrIGEgYiBjIGQpIDQpKVxuXG4oZGVmbiBzYWZlLWF2ZXJhZ2UgW2EgYiBjIGRdXG4gIChsZXQgW3RvdGFsIDAgY291bnQgMF1cbiAgICAod2hlbiBhIChhZGQhIHRvdGFsIGEpIChpbmMhIGNvdW50KSlcbiAgICAod2hlbiBiIChhZGQhIHRvdGFsIGIpIChpbmMhIGNvdW50KSlcbiAgICAod2hlbiBjIChhZGQhIHRvdGFsIGMpIChpbmMhIGNvdW50KSlcbiAgICAod2hlbiBkIChhZGQhIHRvdGFsIGQpIChpbmMhIGNvdW50KSlcbiAgICAoLyB0b3RhbCBjb3VudCkpKVxuXG5cbjsgUmFuZG9tbmVzcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbihkZWZuIHJhbmQgW11cbiAgKE1hdGgucmFuZG9tKSlcblxuKGRlZm4gcmFuZC1hcm91bmQtemVybyBbc3ByZWFkXVxuICAoLSAoKiBzcHJlYWQgKHJhbmQpIDIpIHNwcmVhZCkpXG5cbihkZWZuIGppdHRlciBbdmFsdWUgc3ByZWFkXVxuICAoKyB2YWx1ZSAocmFuZC1hcm91bmQtemVybyBzcHJlYWQpKSlcblxuXG47IEhlaWdodG1hcCBIZWxwZXJzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4oZGVmbiBoZWlnaHRtYXAtcmVzb2x1dGlvbiBbaGVpZ2h0bWFwXVxuICAoYWdldCBoZWlnaHRtYXAuc2hhcGUgMCkpXG5cbihkZWZuIGhlaWdodG1hcC1sYXN0LWluZGV4IFtoZWlnaHRtYXBdXG4gIChkZWMgKGhlaWdodG1hcC1yZXNvbHV0aW9uIGhlaWdodG1hcCkpKVxuXG4oZGVmbiBoZWlnaHRtYXAtY2VudGVyLWluZGV4IFtoZWlnaHRtYXBdXG4gIChtaWRwb2ludCAwIChoZWlnaHRtYXAtbGFzdC1pbmRleCBoZWlnaHRtYXApKSlcblxuXG4oZGVmbiBoZWlnaHRtYXAtZ2V0IFtoZWlnaHRtYXAgeCB5XVxuICAoLmdldCBoZWlnaHRtYXAgeCB5KSlcblxuKGRlZm4gaGVpZ2h0bWFwLWdldC1zYWZlIFtoZWlnaHRtYXAgeCB5XVxuICAobGV0IFtsYXN0IChoZWlnaHRtYXAtbGFzdC1pbmRleCBoZWlnaHRtYXApXVxuICAgICh3aGVuIChhbmQgKDw9IDAgeCBsYXN0KVxuICAgICAgICAgICAgICAgKDw9IDAgeSBsYXN0KSlcbiAgICAgIChoZWlnaHRtYXAtZ2V0IGhlaWdodG1hcCB4IHkpKSkpXG5cbihkZWZuIGhlaWdodG1hcC1zZXQhIFtoZWlnaHRtYXAgeCB5IHZhbF1cbiAgKC5zZXQgaGVpZ2h0bWFwIHggeSB2YWwpKVxuXG4oZGVmbiBoZWlnaHRtYXAtc2V0LWlmLXVuc2V0ISBbaGVpZ2h0bWFwIHggeSB2YWxdXG4gICh3aGVuICg9PSAwIChoZWlnaHRtYXAtZ2V0IGhlaWdodG1hcCB4IHkpKVxuICAgIChoZWlnaHRtYXAtc2V0ISBoZWlnaHRtYXAgeCB5IHZhbCkpKVxuXG5cbihkZWZuIG5vcm1hbGl6ZSBbaGVpZ2h0bWFwXVxuICAobGV0IFttYXggKC0gSW5maW5pdHkpXG4gICAgICAgIG1pbiBJbmZpbml0eV1cbiAgICAoZG8tbmRhcnJheS1lbCBlbCBoZWlnaHRtYXBcbiAgICAgICh3aGVuICg8IG1heCBlbCkgKHNldCEgbWF4IGVsKSlcbiAgICAgICh3aGVuICg+IG1pbiBlbCkgKHNldCEgbWluIGVsKSkpXG4gICAgKGxldCBbc3BhbiAoLSBtYXggbWluKV1cbiAgICAgIChkby1uZGFycmF5IFt4IHldIGhlaWdodG1hcFxuICAgICAgICAoaGVpZ2h0bWFwLXNldCEgaGVpZ2h0bWFwIHggeVxuICAgICAgICAgICAgICAgICAgICAgICAgKC8gKC0gKGhlaWdodG1hcC1nZXQgaGVpZ2h0bWFwIHggeSkgbWluKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BhbikpKSkpKVxuXG5cbihkZWZuIG1ha2UtaGVpZ2h0bWFwIFtleHBvbmVudF1cbiAgKGxldCBbcmVzb2x1dGlvbiAoKyAoTWF0aC5wb3cgMiBleHBvbmVudCkgMSldXG4gICAgKGxldCBbaGVpZ2h0bWFwIChuZGFycmF5IChuZXcgRmxvYXQ2NEFycmF5ICgqIHJlc29sdXRpb24gcmVzb2x1dGlvbikpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgIFtyZXNvbHV0aW9uIHJlc29sdXRpb25dKV1cbiAgICAgIChzZXQhIGhlaWdodG1hcC5leHBvbmVudCBleHBvbmVudClcbiAgICAgIChzZXQhIGhlaWdodG1hcC5yZXNvbHV0aW9uIHJlc29sdXRpb24pXG4gICAgICAoc2V0ISBoZWlnaHRtYXAubGFzdCAoZGVjIHJlc29sdXRpb24pKVxuICAgICAgaGVpZ2h0bWFwKSkpXG5cblxuKGRlZm4gdG9wLWxlZnQtY29ybmVyIFtoZWlnaHRtYXBdXG4gIChsZXQgW2NlbnRlciAoaGVpZ2h0bWFwLWNlbnRlci1pbmRleCBoZWlnaHRtYXApXVxuICAgICgtPiBoZWlnaHRtYXBcbiAgICAgICgubG8gMCAwKVxuICAgICAgKC5oaSAoaW5jIGNlbnRlcikgKGluYyBjZW50ZXIpKSkpKVxuXG4oZGVmbiB0b3AtcmlnaHQtY29ybmVyIFtoZWlnaHRtYXBdXG4gIChsZXQgW2NlbnRlciAoaGVpZ2h0bWFwLWNlbnRlci1pbmRleCBoZWlnaHRtYXApXVxuICAgICgtPiBoZWlnaHRtYXBcbiAgICAgICgubG8gY2VudGVyIDApXG4gICAgICAoLmhpIChpbmMgY2VudGVyKSAoaW5jIGNlbnRlcikpKSkpXG5cbihkZWZuIGJvdHRvbS1sZWZ0LWNvcm5lciBbaGVpZ2h0bWFwXVxuICAobGV0IFtjZW50ZXIgKGhlaWdodG1hcC1jZW50ZXItaW5kZXggaGVpZ2h0bWFwKV1cbiAgICAoLT4gaGVpZ2h0bWFwXG4gICAgICAoLmxvIDAgY2VudGVyKVxuICAgICAgKC5oaSAoaW5jIGNlbnRlcikgKGluYyBjZW50ZXIpKSkpKVxuXG4oZGVmbiBib3R0b20tcmlnaHQtY29ybmVyIFtoZWlnaHRtYXBdXG4gIChsZXQgW2NlbnRlciAoaGVpZ2h0bWFwLWNlbnRlci1pbmRleCBoZWlnaHRtYXApXVxuICAgICgtPiBoZWlnaHRtYXBcbiAgICAgICgubG8gY2VudGVyIGNlbnRlcilcbiAgICAgICguaGkgKGluYyBjZW50ZXIpIChpbmMgY2VudGVyKSkpKSlcblxuXG47IE1pZHBvaW50IERpc3BsYWNlbWVudCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4oZGVmbiBtcGQtaW5pdC1jb3JuZXJzIFtoZWlnaHRtYXBdXG4gIChsZXQgW2xhc3QgKGhlaWdodG1hcC1sYXN0LWluZGV4IGhlaWdodG1hcCldXG4gICAgKGhlaWdodG1hcC1zZXQhIGhlaWdodG1hcCAwICAgIDAgICAgKHJhbmQpKVxuICAgIChoZWlnaHRtYXAtc2V0ISBoZWlnaHRtYXAgMCAgICBsYXN0IChyYW5kKSlcbiAgICAoaGVpZ2h0bWFwLXNldCEgaGVpZ2h0bWFwIGxhc3QgMCAgICAocmFuZCkpXG4gICAgKGhlaWdodG1hcC1zZXQhIGhlaWdodG1hcCBsYXN0IGxhc3QgKHJhbmQpKSkpXG5cbihkZWZuIG1wZC1kaXNwbGFjZSBbaGVpZ2h0bWFwIHNwcmVhZCBzcHJlYWQtcmVkdWN0aW9uXVxuICAobGV0IFtsYXN0IChoZWlnaHRtYXAtbGFzdC1pbmRleCBoZWlnaHRtYXApXG4gICAgICAgIGMgKG1pZHBvaW50IDAgbGFzdClcblxuICAgICAgICBib3R0b20tbGVmdCAgKGhlaWdodG1hcC1nZXQgaGVpZ2h0bWFwIDAgICAgMClcbiAgICAgICAgYm90dG9tLXJpZ2h0IChoZWlnaHRtYXAtZ2V0IGhlaWdodG1hcCBsYXN0IDApXG4gICAgICAgIHRvcC1sZWZ0ICAgICAoaGVpZ2h0bWFwLWdldCBoZWlnaHRtYXAgMCAgICBsYXN0KVxuICAgICAgICB0b3AtcmlnaHQgICAgKGhlaWdodG1hcC1nZXQgaGVpZ2h0bWFwIGxhc3QgbGFzdClcblxuICAgICAgICB0b3AgICAgKGF2ZXJhZ2UyIHRvcC1sZWZ0IHRvcC1yaWdodClcbiAgICAgICAgbGVmdCAgIChhdmVyYWdlMiBib3R0b20tbGVmdCB0b3AtbGVmdClcbiAgICAgICAgYm90dG9tIChhdmVyYWdlMiBib3R0b20tbGVmdCBib3R0b20tcmlnaHQpXG4gICAgICAgIHJpZ2h0ICAoYXZlcmFnZTIgYm90dG9tLXJpZ2h0IHRvcC1yaWdodClcbiAgICAgICAgY2VudGVyIChhdmVyYWdlNCB0b3AgbGVmdCBib3R0b20gcmlnaHQpXG5cbiAgICAgICAgbmV4dC1zcHJlYWQgKCogc3ByZWFkIHNwcmVhZC1yZWR1Y3Rpb24pXVxuICAgIChoZWlnaHRtYXAtc2V0LWlmLXVuc2V0ISBoZWlnaHRtYXAgYyAgICAwICAgIChqaXR0ZXIgYm90dG9tIHNwcmVhZCkpXG4gICAgKGhlaWdodG1hcC1zZXQtaWYtdW5zZXQhIGhlaWdodG1hcCBjICAgIGxhc3QgKGppdHRlciB0b3Agc3ByZWFkKSlcbiAgICAoaGVpZ2h0bWFwLXNldC1pZi11bnNldCEgaGVpZ2h0bWFwIDAgICAgYyAgICAoaml0dGVyIGxlZnQgc3ByZWFkKSlcbiAgICAoaGVpZ2h0bWFwLXNldC1pZi11bnNldCEgaGVpZ2h0bWFwIGxhc3QgYyAgICAoaml0dGVyIHJpZ2h0IHNwcmVhZCkpXG4gICAgKGhlaWdodG1hcC1zZXQtaWYtdW5zZXQhIGhlaWdodG1hcCBjICAgIGMgICAgKGppdHRlciBjZW50ZXIgc3ByZWFkKSlcbiAgICAod2hlbi1ub3QgKD09IDMgKGhlaWdodG1hcC1yZXNvbHV0aW9uIGhlaWdodG1hcCkpXG4gICAgICAobXBkLWRpc3BsYWNlICh0b3AtbGVmdC1jb3JuZXIgaGVpZ2h0bWFwKSBuZXh0LXNwcmVhZCBzcHJlYWQtcmVkdWN0aW9uKVxuICAgICAgKG1wZC1kaXNwbGFjZSAodG9wLXJpZ2h0LWNvcm5lciBoZWlnaHRtYXApIG5leHQtc3ByZWFkIHNwcmVhZC1yZWR1Y3Rpb24pXG4gICAgICAobXBkLWRpc3BsYWNlIChib3R0b20tbGVmdC1jb3JuZXIgaGVpZ2h0bWFwKSBuZXh0LXNwcmVhZCBzcHJlYWQtcmVkdWN0aW9uKVxuICAgICAgKG1wZC1kaXNwbGFjZSAoYm90dG9tLXJpZ2h0LWNvcm5lciBoZWlnaHRtYXApIG5leHQtc3ByZWFkIHNwcmVhZC1yZWR1Y3Rpb24pKSkpXG5cbihkZWZuIG1pZHBvaW50LWRpc3BsYWNlbWVudCBbaGVpZ2h0bWFwXVxuICAobGV0IFtpbml0aWFsLXNwcmVhZCAwLjMgXG4gICAgICAgIHNwcmVhZC1yZWR1Y3Rpb24gMC41NV1cbiAgICAobXBkLWluaXQtY29ybmVycyBoZWlnaHRtYXApXG4gICAgKG1wZC1kaXNwbGFjZSBoZWlnaHRtYXAgaW5pdGlhbC1zcHJlYWQgc3ByZWFkLXJlZHVjdGlvbilcbiAgICAobm9ybWFsaXplIGhlaWdodG1hcCkpKVxuXG5cbjsgVGhyZWUuanMgSGVscGVycyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbihkZWZuIG1ha2UtZGlyZWN0aW9uYWwtbGlnaHQgW11cbiAgKGxldCBbbGlnaHQgKG5ldyBUSFJFRS5EaXJlY3Rpb25hbExpZ2h0IDB4ZmZmZmZmIDEpXVxuICAgIChsaWdodC5wb3NpdGlvbi5zZXQgMTAwIDAgMTUwKVxuICAgIGxpZ2h0KSlcblxuKGRlZm4gbWFrZS1jYW1lcmEgW11cbiAgKGxldCBbY2FtZXJhIChuZXcgVEhSRUUuUGVyc3BlY3RpdmVDYW1lcmFcbiAgICAgICAgICAgICAgICAgICAgNTUsXG4gICAgICAgICAgICAgICAgICAgICgvIHdpZHRoIGhlaWdodClcbiAgICAgICAgICAgICAgICAgICAgMC4xLFxuICAgICAgICAgICAgICAgICAgICAxMDAwKV1cbiAgICAoY2FtZXJhLnBvc2l0aW9uLnNldCAwIC0xMDAgMTUwKVxuICAgIGNhbWVyYSkpXG5cbihkZWZuIG1ha2UtcmVuZGVyZXIgW11cbiAgKGxldCBbcmVuZGVyZXIgKG5ldyBUSFJFRS5XZWJHTFJlbmRlcmVyIHs6YW50aWFsaWFzIGZhbHNlfSldXG4gICAgKHJlbmRlcmVyLnNldENsZWFyQ29sb3IgMHhmZmZmZmYpXG4gICAgKHJlbmRlcmVyLnNldFNpemUgd2lkdGggaGVpZ2h0KVxuICAgIChyZW5kZXJlci5zZXRQaXhlbFJhdGlvIDIpXG4gICAgcmVuZGVyZXIpKVxuXG4oZGVmbiBtYWtlLWdlb21ldHJ5IFtoZWlnaHRtYXBdXG4gIChsZXQgW3Jlc29sdXRpb24gKGFnZXQgaGVpZ2h0bWFwLnNoYXBlIDApXG4gICAgICAgIGdlb21ldHJ5IChuZXcgVEhSRUUuUGxhbmVHZW9tZXRyeVxuICAgICAgICAgICAgICAgICAgICAgIHRlcnJhaW4tc2l6ZVxuICAgICAgICAgICAgICAgICAgICAgIHRlcnJhaW4tc2l6ZVxuICAgICAgICAgICAgICAgICAgICAgICgtIHJlc29sdXRpb24gMSlcbiAgICAgICAgICAgICAgICAgICAgICAoLSByZXNvbHV0aW9uIDEpKV1cbiAgICBnZW9tZXRyeSkpXG5cbihkZWZuIG1ha2UtY29udHJvbHMgW2NhbWVyYSByZW5kZXJlcl1cbiAgKGxldCBbY29udHJvbHMgKG5ldyBUSFJFRS5UcmFja2JhbGxDb250cm9scyBjYW1lcmEgcmVuZGVyZXIuZG9tRWxlbWVudCldXG4gICAgKHNldCEgY29udHJvbHMucm90YXRlU3BlZWQgMS40KVxuICAgIChzZXQhIGNvbnRyb2xzLnpvb21TcGVlZCAwLjUpXG4gICAgKHNldCEgY29udHJvbHMuc3RhdGljTW92aW5nIHRydWUpXG4gICAgKHNldCEgY29udHJvbHMuZHluYW1pY0RhbXBpbmdGYWN0b3IgMC4zKVxuICAgIGNvbnRyb2xzKSlcblxuKGRlZm4gbWFrZS1wbGFuZSBbZ2VvbWV0cnldXG4gIChsZXQgW21hdGVyaWFsIChuZXcgVEhSRUUuTWVzaExhbWJlcnRNYXRlcmlhbFxuICAgICAgICAgICAgICAgICAgICAgIHs6d2lyZWZyYW1lIHdpcmVmcmFtZVxuICAgICAgICAgICAgICAgICAgICAgICA6d2lyZWZyYW1lTGluZXdpZHRoIHdpcmVmcmFtZS13aWR0aFxuICAgICAgICAgICAgICAgICAgICAgICA6Y29sb3IgMHgwMGJiMDB9KV1cbiAgICAobmV3IFRIUkVFLk1lc2ggZ2VvbWV0cnkgbWF0ZXJpYWwpKSlcblxuXG4oZGVmbiBhdHRhY2gtdG8tZG9tIFtyZW5kZXJlciBlbC1uYW1lIHJlZnJlc2gtZm5dXG4gIChsZXQgW2NvbnRhaW5lciAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQgZWwtbmFtZSlcbiAgICAgICAgc2V0dGluZ3MgKGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQgXCJkaXZcIilcbiAgICAgICAgcmVmcmVzaC1idXR0b24gKGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQgXCJidXR0b25cIilcbiAgICAgICAgYnV0dG9uLXRleHQgKGRvY3VtZW50LmNyZWF0ZVRleHROb2RlIFwiUmVmcmVzaFwiKVxuICAgICAgICBjYW5jZWwtc2Nyb2xsIChmbiBbZV0gKC5wcmV2ZW50RGVmYXVsdCBlKSldXG4gICAgKHNldCEgcmVmcmVzaC1idXR0b24ub25jbGljayByZWZyZXNoLWZuKVxuICAgIChzZXQhIHJlbmRlcmVyLmRvbUVsZW1lbnQub25tb3VzZXdoZWVsIGNhbmNlbC1zY3JvbGwpXG4gICAgKHJlbmRlcmVyLmRvbUVsZW1lbnQuYWRkRXZlbnRMaXN0ZW5lciBcIk1vek1vdXNlUGl4ZWxTY3JvbGxcIiBjYW5jZWwtc2Nyb2xsIGZhbHNlKVxuICAgICguYXBwZW5kQ2hpbGQgcmVmcmVzaC1idXR0b24gYnV0dG9uLXRleHQpXG4gICAgKC5hcHBlbmRDaGlsZCBjb250YWluZXIgcmVuZGVyZXIuZG9tRWxlbWVudClcbiAgICAoLmFwcGVuZENoaWxkIGNvbnRhaW5lciBzZXR0aW5ncylcbiAgICAoLmFwcGVuZENoaWxkIHNldHRpbmdzIHJlZnJlc2gtYnV0dG9uKSkpXG5cblxuKGRlZm4gdXBkYXRlLWdlb21ldHJ5IFtnZW9tZXRyeSBoZWlnaHRtYXBdXG4gIChsb29wIFtpIDBdXG4gICAgKGlmICg8IGkgZ2VvbWV0cnkudmVydGljZXMubGVuZ3RoKVxuICAgICAgKGRvIChzZXQhICguLXogKGFnZXQgZ2VvbWV0cnkudmVydGljZXMgaSkpXG4gICAgICAgICAgICAgICAgKCogdGVycmFpbi1oZWlnaHQgKGFnZXQgKC4tZGF0YSBoZWlnaHRtYXApIGkpKSlcbiAgICAgICAgKHJlY3VyICgrIGkgMSkpKSkpXG4gIChnZW9tZXRyeS5jb21wdXRlVmVydGV4Tm9ybWFscylcbiAgZ2VvbWV0cnkpXG5cblxuOyBNYWluIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuKGRlZm4gbWFrZS1maW5hbCBbZWxlbWVudC1pZF1cbiAgKGRlZiBzY2VuZSAobmV3IFRIUkVFLlNjZW5lKSlcbiAgKHNjZW5lLmFkZCAobmV3IFRIUkVFLkF4aXNIZWxwZXIgMTAwKSlcblxuICAoZGVmIGNsb2NrIChuZXcgVEhSRUUuQ2xvY2spKVxuICAoZGVmIGNhbWVyYSAobWFrZS1jYW1lcmEpKVxuICAoZGVmIHJlbmRlcmVyIChtYWtlLXJlbmRlcmVyKSlcblxuICAoZGVmIGdlb21ldHJ5KVxuICAoZGVmIHBsYW5lKVxuXG4gIChzY2VuZS5hZGQgKG1ha2UtZGlyZWN0aW9uYWwtbGlnaHQpKVxuICAoc2NlbmUuYWRkIChuZXcgVEhSRUUuQW1iaWVudExpZ2h0IDB4ZmZmZmZmIDAuMDUpKVxuXG4gIChkZWZuIHJlZnJlc2ggW11cbiAgICAobGV0IFtoZWlnaHRtYXAgKG1ha2UtaGVpZ2h0bWFwIDYpXVxuICAgICAgKGwgXCJHZW5lcmF0aW5nIHRlcnJhaW4uLi5cIilcbiAgICAgICh0aW1lIChtaWRwb2ludC1kaXNwbGFjZW1lbnQgaGVpZ2h0bWFwKSlcblxuICAgICAgKGwgXCJSZWJ1aWxkaW5nIGdlb21ldHJ5Li4uXCIpXG4gICAgICAodGltZVxuICAgICAgICAoc2V0ISBnZW9tZXRyeSAobWFrZS1nZW9tZXRyeSBoZWlnaHRtYXApKVxuICAgICAgICAodXBkYXRlLWdlb21ldHJ5IGdlb21ldHJ5IGhlaWdodG1hcCkpXG5cbiAgICAgIChsIFwiUmVidWlsZGluZyBwbGFuZS4uLlwiKVxuICAgICAgKHRpbWVcbiAgICAgICAgKHNjZW5lLnJlbW92ZSBwbGFuZSlcbiAgICAgICAgKHNldCEgcGxhbmUgKG1ha2UtcGxhbmUgZ2VvbWV0cnkpKVxuICAgICAgICAoc2NlbmUuYWRkIHBsYW5lKSkpKVxuXG4gIChhdHRhY2gtdG8tZG9tIHJlbmRlcmVyIGVsZW1lbnQtaWQgcmVmcmVzaClcbiAgKGRlZiBjb250cm9scyAobWFrZS1jb250cm9scyBjYW1lcmEgcmVuZGVyZXIpKVxuXG4gIChkZWZuIHJlbmRlciBbXVxuICAgIChsZXQgW2RlbHRhIChjbG9jay5nZXREZWx0YSldXG4gICAgICAocmVxdWVzdEFuaW1hdGlvbkZyYW1lIHJlbmRlcilcbiAgICAgICgudXBkYXRlIGNvbnRyb2xzIGRlbHRhKVxuICAgICAgKHJlbmRlcmVyLnJlbmRlciBzY2VuZSBjYW1lcmEpKSlcblxuICAocmVmcmVzaClcbiAgKHJlbmRlcilcblxuICBuaWwpXG5cbihkZWZuIHJ1biBbXVxuICAobWFrZS1maW5hbCBcImRlbW8tZmluYWxcIikpXG5cbigkIHJ1bilcblxuXG47IHZpbTogbHcrPWRvLXRpbWVzIGx3Kz1kby1uZXN0ZWQgOlxuIl19
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/media/js/wisp/terrain2.wisp	Sat Mar 05 21:26:30 2016 +0000
@@ -0,0 +1,376 @@
+(ns demo
+  (:require [ndarray]))
+
+; Constants -------------------------------------------------------------------
+(def width 610)
+(def height 400)
+(def wireframe true)
+(def wireframe-width 1.2)
+(def terrain-height 50)
+(def terrain-size 100)
+
+; General Utilities -----------------------------------------------------------
+(defmacro when [condition & body]
+  `(if ~condition
+     (do ~@body)))
+
+(defmacro when-not [condition & body]
+  `(when (not ~condition)
+     ~@body))
+
+(defmacro -> [& operations]
+  (reduce
+    (fn [form operation]
+      (cons (first operation)
+            (cons form (rest operation))))
+    (first operations)
+    (rest operations)))
+
+
+(defn inc [x]
+  (+ x 1))
+
+(defn dec [x]
+  (- x 1))
+
+
+(defmacro do-times [varname limit & body]
+  (let [end (gensym)]
+    `(let [~end ~limit]
+       (loop [~varname 0]
+         (when (< ~varname ~end)
+           ~@body
+           (recur (inc ~varname)))))))
+
+(defmacro do-stride [varnames start-form end-form stride-form & body]
+  (let [stride (gensym "stride")
+        start (gensym "start")
+        end (gensym "end")
+        build (fn build [vars]
+                (if (empty? vars)
+                  `(do ~@body)
+                  (let [varname (first vars)]
+                    `(loop [~varname ~start]
+                       (when (< ~varname ~end)
+                         ~(build (rest vars))
+                         (recur (+ ~varname ~stride)))))))]
+    ; Fix the numbers once outside the nested loops,
+    ; and then build the guts.
+    `(let [~start ~start-form
+           ~end ~end-form
+           ~stride ~stride-form]
+       ~(build varnames))))
+
+
+(defmacro do-ndarray [vars array-form & body]
+  (let [array-var (gensym "array")
+        build (fn build [vars n]
+                (if (empty? vars)
+                  `(do ~@body)
+                  `(do-times ~(first vars) (aget (.-shape ~array-var) ~n)
+                     ~(build (rest vars) (inc n)))))]
+    `(let [~array-var ~array-form]
+       ~(build vars 0))))
+
+(defmacro do-ndarray-el [element array-form & body]
+  (let [index (gensym "index")
+        array (gensym "array")]
+    `(let [~array ~array-form]
+       (do-times ~index (.-length (.-data ~array))
+         (let [~element (aget (.-data ~array) ~index)]
+           ~@body)))))
+
+
+(defmacro inc! [place]
+  `(set! ~place (inc ~place)))
+
+(defmacro add! [place amount]
+  `(set! ~place (+ ~place ~amount)))
+
+
+(defmacro l [& forms]
+  `(console.log ~@forms))
+
+(defmacro time [& body]
+  (let [start (gensym)
+        end (gensym)
+        result (gensym)]
+    `(let [~start (.getTime (new Date))
+           ~result (do ~@body)
+           ~end (.getTime (new Date))]
+       (l (+ "Elapsed time: " (- ~end ~start) "ms."))
+       ~result)))
+
+
+(defn midpoint [a b]
+  (/ (+ a b) 2))
+
+(defn average2 [a b]
+  (/ (+ a b) 2))
+
+(defn average4 [a b c d]
+  (/ (+ a b c d) 4))
+
+(defn safe-average [a b c d]
+  (let [total 0 count 0]
+    (when a (add! total a) (inc! count))
+    (when b (add! total b) (inc! count))
+    (when c (add! total c) (inc! count))
+    (when d (add! total d) (inc! count))
+    (/ total count)))
+
+
+; Randomness ------------------------------------------------------------------
+(defn rand []
+  (Math.random))
+
+(defn rand-around-zero [spread]
+  (- (* spread (rand) 2) spread))
+
+(defn jitter [value spread]
+  (+ value (rand-around-zero spread)))
+
+
+; Heightmap Helpers -----------------------------------------------------------
+(defn heightmap-resolution [heightmap]
+  (aget heightmap.shape 0))
+
+(defn heightmap-last-index [heightmap]
+  (dec (heightmap-resolution heightmap)))
+
+(defn heightmap-center-index [heightmap]
+  (midpoint 0 (heightmap-last-index heightmap)))
+
+
+(defn heightmap-get [heightmap x y]
+  (.get heightmap x y))
+
+(defn heightmap-get-safe [heightmap x y]
+  (let [last (heightmap-last-index heightmap)]
+    (when (and (<= 0 x last)
+               (<= 0 y last))
+      (heightmap-get heightmap x y))))
+
+(defn heightmap-set! [heightmap x y val]
+  (.set heightmap x y val))
+
+(defn heightmap-set-if-unset! [heightmap x y val]
+  (when (== 0 (heightmap-get heightmap x y))
+    (heightmap-set! heightmap x y val)))
+
+
+(defn normalize [heightmap]
+  (let [max (- Infinity)
+        min Infinity]
+    (do-ndarray-el el heightmap
+      (when (< max el) (set! max el))
+      (when (> min el) (set! min el)))
+    (let [span (- max min)]
+      (do-ndarray [x y] heightmap
+        (heightmap-set! heightmap x y
+                        (/ (- (heightmap-get heightmap x y) min)
+                           span))))))
+
+
+(defn make-heightmap [exponent]
+  (let [resolution (+ (Math.pow 2 exponent) 1)]
+    (let [heightmap (ndarray (new Float64Array (* resolution resolution))
+                             [resolution resolution])]
+      (set! heightmap.exponent exponent)
+      (set! heightmap.resolution resolution)
+      (set! heightmap.last (dec resolution))
+      heightmap)))
+
+
+(defn top-left-corner [heightmap]
+  (let [center (heightmap-center-index heightmap)]
+    (-> heightmap
+      (.lo 0 0)
+      (.hi (inc center) (inc center)))))
+
+(defn top-right-corner [heightmap]
+  (let [center (heightmap-center-index heightmap)]
+    (-> heightmap
+      (.lo center 0)
+      (.hi (inc center) (inc center)))))
+
+(defn bottom-left-corner [heightmap]
+  (let [center (heightmap-center-index heightmap)]
+    (-> heightmap
+      (.lo 0 center)
+      (.hi (inc center) (inc center)))))
+
+(defn bottom-right-corner [heightmap]
+  (let [center (heightmap-center-index heightmap)]
+    (-> heightmap
+      (.lo center center)
+      (.hi (inc center) (inc center)))))
+
+
+; Midpoint Displacement -------------------------------------------------------
+(defn mpd-init-corners [heightmap]
+  (let [last (heightmap-last-index heightmap)]
+    (heightmap-set! heightmap 0    0    (rand))
+    (heightmap-set! heightmap 0    last (rand))
+    (heightmap-set! heightmap last 0    (rand))
+    (heightmap-set! heightmap last last (rand))))
+
+(defn mpd-displace [heightmap spread spread-reduction]
+  (let [last (heightmap-last-index heightmap)
+        c (midpoint 0 last)
+
+        bottom-left  (heightmap-get heightmap 0    0)
+        bottom-right (heightmap-get heightmap last 0)
+        top-left     (heightmap-get heightmap 0    last)
+        top-right    (heightmap-get heightmap last last)
+
+        top    (average2 top-left top-right)
+        left   (average2 bottom-left top-left)
+        bottom (average2 bottom-left bottom-right)
+        right  (average2 bottom-right top-right)
+        center (average4 top left bottom right)
+
+        next-spread (* spread spread-reduction)]
+    (heightmap-set-if-unset! heightmap c    0    (jitter bottom spread))
+    (heightmap-set-if-unset! heightmap c    last (jitter top spread))
+    (heightmap-set-if-unset! heightmap 0    c    (jitter left spread))
+    (heightmap-set-if-unset! heightmap last c    (jitter right spread))
+    (heightmap-set-if-unset! heightmap c    c    (jitter center spread))
+    (when-not (== 3 (heightmap-resolution heightmap))
+      (mpd-displace (top-left-corner heightmap) next-spread spread-reduction)
+      (mpd-displace (top-right-corner heightmap) next-spread spread-reduction)
+      (mpd-displace (bottom-left-corner heightmap) next-spread spread-reduction)
+      (mpd-displace (bottom-right-corner heightmap) next-spread spread-reduction))))
+
+(defn midpoint-displacement [heightmap]
+  (let [initial-spread 0.3 
+        spread-reduction 0.55]
+    (mpd-init-corners heightmap)
+    (mpd-displace heightmap initial-spread spread-reduction)
+    (normalize heightmap)))
+
+
+; Three.js Helpers ------------------------------------------------------------
+(defn make-directional-light []
+  (let [light (new THREE.DirectionalLight 0xffffff 1)]
+    (light.position.set 100 0 150)
+    light))
+
+(defn make-camera []
+  (let [camera (new THREE.PerspectiveCamera
+                    55,
+                    (/ width height)
+                    0.1,
+                    1000)]
+    (camera.position.set 0 -100 150)
+    camera))
+
+(defn make-renderer []
+  (let [renderer (new THREE.WebGLRenderer {:antialias false})]
+    (renderer.setClearColor 0xffffff)
+    (renderer.setSize width height)
+    (renderer.setPixelRatio 2)
+    renderer))
+
+(defn make-geometry [heightmap]
+  (let [resolution (aget heightmap.shape 0)
+        geometry (new THREE.PlaneGeometry
+                      terrain-size
+                      terrain-size
+                      (- resolution 1)
+                      (- resolution 1))]
+    geometry))
+
+(defn make-controls [camera renderer]
+  (let [controls (new THREE.TrackballControls camera renderer.domElement)]
+    (set! controls.rotateSpeed 1.4)
+    (set! controls.zoomSpeed 0.5)
+    (set! controls.staticMoving true)
+    (set! controls.dynamicDampingFactor 0.3)
+    controls))
+
+(defn make-plane [geometry]
+  (let [material (new THREE.MeshLambertMaterial
+                      {:wireframe wireframe
+                       :wireframeLinewidth wireframe-width
+                       :color 0x00bb00})]
+    (new THREE.Mesh geometry material)))
+
+
+(defn attach-to-dom [renderer el-name refresh-fn]
+  (let [container (document.getElementById el-name)
+        settings (document.createElement "div")
+        refresh-button (document.createElement "button")
+        button-text (document.createTextNode "Refresh")
+        cancel-scroll (fn [e] (.preventDefault e))]
+    (set! refresh-button.onclick refresh-fn)
+    (set! renderer.domElement.onmousewheel cancel-scroll)
+    (renderer.domElement.addEventListener "MozMousePixelScroll" cancel-scroll false)
+    (.appendChild refresh-button button-text)
+    (.appendChild container renderer.domElement)
+    (.appendChild container settings)
+    (.appendChild settings refresh-button)))
+
+
+(defn update-geometry [geometry heightmap]
+  (loop [i 0]
+    (if (< i geometry.vertices.length)
+      (do (set! (.-z (aget geometry.vertices i))
+                (* terrain-height (aget (.-data heightmap) i)))
+        (recur (+ i 1)))))
+  (geometry.computeVertexNormals)
+  geometry)
+
+
+; Main ------------------------------------------------------------------------
+(defn make-final [element-id]
+  (def scene (new THREE.Scene))
+  (scene.add (new THREE.AxisHelper 100))
+
+  (def clock (new THREE.Clock))
+  (def camera (make-camera))
+  (def renderer (make-renderer))
+
+  (def geometry)
+  (def plane)
+
+  (scene.add (make-directional-light))
+  (scene.add (new THREE.AmbientLight 0xffffff 0.05))
+
+  (defn refresh []
+    (let [heightmap (make-heightmap 6)]
+      (l "Generating terrain...")
+      (time (midpoint-displacement heightmap))
+
+      (l "Rebuilding geometry...")
+      (time
+        (set! geometry (make-geometry heightmap))
+        (update-geometry geometry heightmap))
+
+      (l "Rebuilding plane...")
+      (time
+        (scene.remove plane)
+        (set! plane (make-plane geometry))
+        (scene.add plane))))
+
+  (attach-to-dom renderer element-id refresh)
+  (def controls (make-controls camera renderer))
+
+  (defn render []
+    (let [delta (clock.getDelta)]
+      (requestAnimationFrame render)
+      (.update controls delta)
+      (renderer.render scene camera)))
+
+  (refresh)
+  (render)
+
+  nil)
+
+(defn run []
+  (make-final "demo-final"))
+
+($ run)
+
+
+; vim: lw+=do-times lw+=do-nested :