# HG changeset patch # User Steve Losh # Date 1457213190 0 # Node ID 749ec5a035335a15c9bc952c2bc36348f1ea9e40 # Parent 1f29d302c82dc8d6adbbff847f24414bf4cfa12a Recursive MPD diff -r 1f29d302c82d -r 749ec5a03533 Makefile --- 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 $@ diff -r 1f29d302c82d -r 749ec5a03533 content/blog/2016/03/recursive-midpoint-displacement.html --- /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 %} + + + + + {% 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. + +
+ +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 %} diff -r 1f29d302c82d -r 749ec5a03533 media/css/sjl.less --- 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; } diff -r 1f29d302c82d -r 749ec5a03533 media/js/terrain2.js --- /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 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 + * 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; iMath.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=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=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 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 diff -r 1f29d302c82d -r 749ec5a03533 media/js/wisp/terrain2.wisp --- /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 :