245 lines
No EOL
24 KiB
HTML
245 lines
No EOL
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang='en'><head><meta charset='utf-8' /><meta name='pinterest' content='nopin' /><link href='../../../../static/css/style.css' rel='stylesheet' type='text/css' /><link href='../../../../static/css/print.css' rel='stylesheet' type='text/css' media='print' /><title>Recursive Midpoint Displacement / Steve Losh</title></head><body><header><a id='logo' href='https://stevelosh.com/'>Steve Losh</a><nav><a href='../../../index.html'>Blog</a> - <a href='https://stevelosh.com/projects/'>Projects</a> - <a href='https://stevelosh.com/photography/'>Photography</a> - <a href='https://stevelosh.com/links/'>Links</a> - <a href='https://stevelosh.com/rss.xml'>Feed</a></nav></header><hr class='main-separator' /><main id='page-blog-entry'><article><h1><a href='index.html'>Recursive Midpoint Displacement</a></h1><p class='date'>Posted on March 7th, 2016.</p><script defer="" src="../../../../static/js/terrain/jquery.js" type="text/javascript"></script>
|
|
|
|
<script defer="" src="../../../../static/js/terrain/three.min.js"></script>
|
|
|
|
<script defer="" src="../../../../static/js/terrain/TrackballControls.js"></script>
|
|
|
|
<script defer="" src="../../../../static/js/terrain/terrain2.js"></script>
|
|
|
|
<p>In the <a href="../../02/midpoint-displacement/index.html">last post</a> 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.</p>
|
|
|
|
<p>The full series of posts so far:</p>
|
|
|
|
<ul>
|
|
<li><a href="../../02/midpoint-displacement/index.html">Midpoint Displacement</a></li>
|
|
<li><a href="index.html">Recursive Midpoint Displacement</a></li>
|
|
<li><a href="../../06/diamond-square/index.html">Diamond Square</a></li>
|
|
</ul>
|
|
|
|
<ol class="table-of-contents"><li><a href="index.html#s1-multi-dimensional-arrays">Multi-Dimensional Arrays</a></li><li><a href="index.html#s2-iteration">Iteration</a></li><li><a href="index.html#s3-updating-the-heightmaps">Updating the Heightmaps</a></li><li><a href="index.html#s4-slicing-heightmaps">Slicing Heightmaps</a></li><li><a href="index.html#s5-updating-the-algorithm">Updating the Algorithm</a></li><li><a href="index.html#s6-result">Result</a></li></ol>
|
|
|
|
<h2 id="s1-multi-dimensional-arrays"><a href="index.html#s1-multi-dimensional-arrays">Multi-Dimensional Arrays</a></h2>
|
|
|
|
<p>Last week when looking at something unrelated I came across the <a href="https://github.com/scijs/ndarray">ndarray</a>
|
|
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 <code>Float64Array</code> objects as the underlying storage because they're much
|
|
more efficient than the vanilla Javascript arrays and they're fairly well
|
|
supported.</p>
|
|
|
|
<p>It also adds <a href="https://github.com/scijs/ndarray#slicing">slicing</a>, which is a lot like Common Lisp's <a href="http://clhs.lisp.se/Body/26_glo_d.htm#displaced_array">displaced
|
|
arrays</a>. 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.</p>
|
|
|
|
<h2 id="s2-iteration"><a href="index.html#s2-iteration">Iteration</a></h2>
|
|
|
|
<p>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:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> do-ndarray <span class="paren2">[<span class="code">vars array-form & body</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">array-var <span class="paren4">(<span class="code">gensym <span class="string">"array"</span></span>)</span>
|
|
build
|
|
<span class="paren4">(<span class="code">fn build <span class="paren5">[<span class="code">vars n</span>]</span>
|
|
<span class="paren5">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren6">(<span class="code">empty? vars</span>)</span>
|
|
`<span class="paren6">(<span class="code">do ~@body</span>)</span>
|
|
`<span class="paren6">(<span class="code">do-times ~<span class="paren1">(<span class="code">first vars</span>)</span> <span class="paren1">(<span class="code">aget <span class="paren2">(<span class="code">.-shape ~array-var</span>)</span> ~n</span>)</span>
|
|
~<span class="paren1">(<span class="code">build <span class="paren2">(<span class="code">rest vars</span>)</span> <span class="paren2">(<span class="code">inc n</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>]</span>
|
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">~array-var ~array-form</span>]</span>
|
|
~<span class="paren4">(<span class="code">build vars 0</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> do-ndarray-el <span class="paren2">[<span class="code">element array-form & body</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">index <span class="paren4">(<span class="code">gensym <span class="string">"index"</span></span>)</span>
|
|
array <span class="paren4">(<span class="code">gensym <span class="string">"array"</span></span>)</span></span>]</span>
|
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">~array ~array-form</span>]</span>
|
|
<span class="paren4">(<span class="code">do-times ~index <span class="paren5">(<span class="code">.-length <span class="paren6">(<span class="code">.-data ~array</span>)</span></span>)</span>
|
|
<span class="paren5">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren6">[<span class="code">~element <span class="paren1">(<span class="code">aget <span class="paren2">(<span class="code">.-data ~array</span>)</span> ~index</span>)</span></span>]</span>
|
|
~@body</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Now we can easily iterate over the indices:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code">do-ndarray <span class="paren2">[<span class="code">x y</span>]</span> my-ndarray
|
|
<span class="paren2">(<span class="code">console.log <span class="string">"Array["</span> x <span class="string">"]["</span> y <span class="string">"] is: "</span>
|
|
<span class="paren3">(<span class="code">.get my-ndarray x y</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Or just over the items if we don't need their indices:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code">do-ndarray-el item my-ndarray
|
|
<span class="paren2">(<span class="code">console.log item</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>These macros should work for ndarrays of any number of dimensions, and will
|
|
compile into ugly but fast Javascript <code>for</code> loops.</p>
|
|
|
|
<h2 id="s3-updating-the-heightmaps"><a href="index.html#s3-updating-the-heightmaps">Updating the Heightmaps</a></h2>
|
|
|
|
<p>To start we'll need to update the heightmap functions to work with ndarrays
|
|
instead of the normal JS arrays.</p>
|
|
|
|
<p>Start with a few functions to calculate sizes/incides:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-resolution <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">aget heightmap.shape 0</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-last-index <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">dec <span class="paren3">(<span class="code">heightmap-resolution heightmap</span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-center-index <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">midpoint 0 <span class="paren3">(<span class="code">heightmap-last-index heightmap</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Support for reading/writing:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-get <span class="paren2">[<span class="code">heightmap x y</span>]</span>
|
|
<span class="paren2">(<span class="code">.get heightmap x y</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-get-safe <span class="paren2">[<span class="code">heightmap x y</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">last <span class="paren4">(<span class="code">heightmap-last-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">and <span class="paren5">(<span class="code"><= 0 x last</span>)</span>
|
|
<span class="paren5">(<span class="code"><= 0 y last</span>)</span></span>)</span>
|
|
<span class="paren4">(<span class="code">heightmap-get heightmap x y</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-set! <span class="paren2">[<span class="code">heightmap x y val</span>]</span>
|
|
<span class="paren2">(<span class="code">.set heightmap x y val</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> heightmap-set-if-unset! <span class="paren2">[<span class="code">heightmap x y val</span>]</span>
|
|
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">== 0 <span class="paren4">(<span class="code">heightmap-get heightmap x y</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap x y val</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Normalization:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> normalize <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">max <span class="paren4">(<span class="code">- Infinity</span>)</span>
|
|
min Infinity</span>]</span>
|
|
<span class="paren3">(<span class="code">do-ndarray-el el heightmap
|
|
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">< max el</span>)</span> <span class="paren5">(<span class="code">set! max el</span>)</span></span>)</span>
|
|
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">> min el</span>)</span> <span class="paren5">(<span class="code">set! min el</span>)</span></span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">span <span class="paren5">(<span class="code">- max min</span>)</span></span>]</span>
|
|
<span class="paren4">(<span class="code">do-ndarray <span class="paren5">[<span class="code">x y</span>]</span> heightmap
|
|
<span class="paren5">(<span class="code">heightmap-set! heightmap x y
|
|
<span class="paren6">(<span class="code">/ <span class="paren1">(<span class="code">- <span class="paren2">(<span class="code">heightmap-get heightmap x y</span>)</span> min</span>)</span>
|
|
span</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Creation:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> make-heightmap <span class="paren2">[<span class="code">exponent</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">resolution <span class="paren4">(<span class="code">+ <span class="paren5">(<span class="code">Math.pow 2 exponent</span>)</span> 1</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">heightmap <span class="paren5">(<span class="code">ndarray <span class="paren6">(<span class="code">new Float64Array <span class="paren1">(<span class="code">* resolution resolution</span>)</span></span>)</span>
|
|
<span class="paren6">[<span class="code">resolution resolution</span>]</span></span>)</span></span>]</span>
|
|
<span class="paren4">(<span class="code">set! heightmap.exponent exponent</span>)</span>
|
|
<span class="paren4">(<span class="code">set! heightmap.resolution resolution</span>)</span>
|
|
<span class="paren4">(<span class="code">set! heightmap.last <span class="paren5">(<span class="code">dec resolution</span>)</span></span>)</span>
|
|
heightmap</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>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.</p>
|
|
|
|
<h2 id="s4-slicing-heightmaps"><a href="index.html#s4-slicing-heightmaps">Slicing Heightmaps</a></h2>
|
|
|
|
<p>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:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> top-left-corner <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">center <span class="paren4">(<span class="code">heightmap-center-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">-> heightmap
|
|
<span class="paren4">(<span class="code">.lo 0 0</span>)</span>
|
|
<span class="paren4">(<span class="code">.hi <span class="paren5">(<span class="code">inc center</span>)</span> <span class="paren5">(<span class="code">inc center</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> top-right-corner <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">center <span class="paren4">(<span class="code">heightmap-center-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">-> heightmap
|
|
<span class="paren4">(<span class="code">.lo center 0</span>)</span>
|
|
<span class="paren4">(<span class="code">.hi <span class="paren5">(<span class="code">inc center</span>)</span> <span class="paren5">(<span class="code">inc center</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> bottom-left-corner <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">center <span class="paren4">(<span class="code">heightmap-center-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">-> heightmap
|
|
<span class="paren4">(<span class="code">.lo 0 center</span>)</span>
|
|
<span class="paren4">(<span class="code">.hi <span class="paren5">(<span class="code">inc center</span>)</span> <span class="paren5">(<span class="code">inc center</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> bottom-right-corner <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">center <span class="paren4">(<span class="code">heightmap-center-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">-> heightmap
|
|
<span class="paren4">(<span class="code">.lo center center</span>)</span>
|
|
<span class="paren4">(<span class="code">.hi <span class="paren5">(<span class="code">inc center</span>)</span> <span class="paren5">(<span class="code">inc center</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>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 <code>0</code>, <code>0</code>), but that uses the
|
|
appropriate part of the original array as the data storage.</p>
|
|
|
|
<h2 id="s5-updating-the-algorithm"><a href="index.html#s5-updating-the-algorithm">Updating the Algorithm</a></h2>
|
|
|
|
<p>Now we can turn the algorithm into a recursive version. With the slicing
|
|
functions it's pretty simple. Initializing the corners is still trivial:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> mpd-init-corners <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">last <span class="paren4">(<span class="code">heightmap-last-index heightmap</span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap 0 0 <span class="paren4">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap 0 last <span class="paren4">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap last 0 <span class="paren4">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap last last <span class="paren4">(<span class="code">rand</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>The meat of the algorithm looks long, but is mostly just calculating all the
|
|
appropriate numbers with readable names:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> mpd-displace <span class="paren2">[<span class="code">heightmap spread spread-reduction</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">last <span class="paren4">(<span class="code">heightmap-last-index heightmap</span>)</span>
|
|
c <span class="paren4">(<span class="code">midpoint 0 last</span>)</span>
|
|
|
|
<span class="comment">; Get the values of the corners
|
|
</span> bottom-left <span class="paren4">(<span class="code">heightmap-get heightmap 0 0</span>)</span>
|
|
bottom-right <span class="paren4">(<span class="code">heightmap-get heightmap last 0</span>)</span>
|
|
top-left <span class="paren4">(<span class="code">heightmap-get heightmap 0 last</span>)</span>
|
|
top-right <span class="paren4">(<span class="code">heightmap-get heightmap last last</span>)</span>
|
|
|
|
<span class="comment">; Calculate the averages for the points we're going to fill
|
|
</span> top <span class="paren4">(<span class="code">average2 top-left top-right</span>)</span>
|
|
left <span class="paren4">(<span class="code">average2 bottom-left top-left</span>)</span>
|
|
bottom <span class="paren4">(<span class="code">average2 bottom-left bottom-right</span>)</span>
|
|
right <span class="paren4">(<span class="code">average2 bottom-right top-right</span>)</span>
|
|
center <span class="paren4">(<span class="code">average4 top left bottom right</span>)</span>
|
|
|
|
next-spread <span class="paren4">(<span class="code">* spread spread-reduction</span>)</span></span>]</span>
|
|
<span class="comment">; Set the four edge midpoint values
|
|
</span> <span class="paren3">(<span class="code">heightmap-set-if-unset! heightmap c 0 <span class="paren4">(<span class="code">jitter bottom spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set-if-unset! heightmap c last <span class="paren4">(<span class="code">jitter top spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set-if-unset! heightmap 0 c <span class="paren4">(<span class="code">jitter left spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set-if-unset! heightmap last c <span class="paren4">(<span class="code">jitter right spread</span>)</span></span>)</span>
|
|
|
|
<span class="comment">; Set the center value
|
|
</span> <span class="paren3">(<span class="code">heightmap-set-if-unset! heightmap c c <span class="paren4">(<span class="code">jitter center spread</span>)</span></span>)</span>
|
|
|
|
<span class="comment">; Recurse on the four corners if necessary (3x3 is the base case)
|
|
</span> <span class="paren3">(<span class="code">when-not <span class="paren4">(<span class="code">== 3 <span class="paren5">(<span class="code">heightmap-resolution heightmap</span>)</span></span>)</span>
|
|
<span class="paren4">(<span class="code">mpd-displace <span class="paren5">(<span class="code">top-left-corner heightmap</span>)</span> next-spread spread-reduction</span>)</span>
|
|
<span class="paren4">(<span class="code">mpd-displace <span class="paren5">(<span class="code">top-right-corner heightmap</span>)</span> next-spread spread-reduction</span>)</span>
|
|
<span class="paren4">(<span class="code">mpd-displace <span class="paren5">(<span class="code">bottom-left-corner heightmap</span>)</span> next-spread spread-reduction</span>)</span>
|
|
<span class="paren4">(<span class="code">mpd-displace <span class="paren5">(<span class="code">bottom-right-corner heightmap</span>)</span> next-spread spread-reduction</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>The main wrapper function is simple:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> midpoint-displacement <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">initial-spread 0.3
|
|
spread-reduction 0.55</span>]</span>
|
|
<span class="paren3">(<span class="code">mpd-init-corners heightmap</span>)</span>
|
|
<span class="paren3">(<span class="code">mpd-displace heightmap initial-spread spread-reduction</span>)</span>
|
|
<span class="paren3">(<span class="code">normalize heightmap</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<h2 id="s6-result"><a href="index.html#s6-result">Result</a></h2>
|
|
|
|
<p>The result looks the same as before, but will generate the heightmaps a lot
|
|
faster because it's operating on a <code>Float64Array</code> instead of a vanilla JS array.</p>
|
|
|
|
<div id="demo-final" class="threejs"></div>
|
|
|
|
<p>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
|
|
<a href="http://ymir.stevelosh.com/">single-page demo</a> with completed versions of the various algorithms you
|
|
can play with. <a href="http://bitbucket.org/sjl/ymir/">The code for that</a> should be a lot more readable
|
|
than the hacky code for these posts.</p>
|
|
</article></main><hr class='main-separator' /><footer><nav><a href='https://github.com/sjl/'>GitHub</a> ・ <a href='https://twitter.com/stevelosh/'>Twitter</a> ・ <a href='https://instagram.com/thirtytwobirds/'>Instagram</a> ・ <a href='https://hg.stevelosh.com/.plan/'>.plan</a></nav></footer></body></html> |