emacs.d/clones/lisp/stevelosh.com/blog/2016/06/diamond-square/index.html
2022-10-07 15:47:14 +02:00

458 lines
No EOL
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>Terrain Generation with Diamond Square / 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'>Terrain Generation with Diamond Square</a></h1><p class='date'>Posted on June 27th, 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/terrain3.js"></script>
<p>In the last two posts we looked at implementing the Midpoint Displacement
algorithm for procedurally generating terrain. Today we're going to look at
a similar algorithm called Diamond Square that fixes some problems with Midpoint
Displacement.</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="../../03/recursive-midpoint-displacement/index.html">Recursive Midpoint Displacement</a></li>
<li><a href="index.html">Diamond Square</a></li>
</ul>
<p>Midpoint Displacement is simple and fast, but it has some flaws. If you look at
the end result you'll probably start to notice &quot;seams&quot; in the terrain that
appear on perfectly square chunks. This happens because when you're calculating
the midpoints of the edges of each square you're only averaging two sources of
information.</p>
<p><a href="https://en.wikipedia.org/wiki/Diamond-square_algorithm">Diamond Square</a> is an algorithm that works a lot like Midpoint Displacement,
but it adds an extra step to ensure that (almost) every point uses <em>four</em>
sources of data. This reduces the visual artifacts a lot without much extra
effort.</p>
<p>Let's <a href="https://i.imgur.com/RadSf.jpg">draw the owl</a>.</p>
<ol class="table-of-contents"><li><a href="index.html#s1-overview">Overview</a></li><li><a href="index.html#s2-initialization">Initialization</a></li><li><a href="index.html#s3-square">Square</a></li><li><a href="index.html#s4-diamond">Diamond</a></li><li><a href="index.html#s5-edge-cases">Edge Cases</a></li><li><a href="index.html#s6-iteration">Iteration</a><ol><li><a href="index.html#s7-a-strided-iteration-construct">A &quot;Strided&quot; Iteration Construct</a></li><li><a href="index.html#s8-square-iteration">Square Iteration</a></li><li><a href="index.html#s9-diamond-iteration">Diamond Iteration</a></li><li><a href="index.html#s10-top-level-iteration">Top-Level Iteration</a></li></ol></li><li><a href="index.html#s11-result">Result</a></li></ol>
<h2 id="s1-overview"><a href="index.html#s1-overview">Overview</a></h2>
<p>The high-level description of Diamond Square goes something like this:</p>
<ol>
<li>Initialize the corners to random values.</li>
<li>Set the center of the heightmap to the average of the corners (plus jitter).</li>
<li>Set the midpoints of the edges of the heightmap to the average of the four
points on the &quot;diamond&quot; around them (plus jitter).</li>
<li>Repeat steps 2-4 on successively smaller chunks of the heightmap until you
bottom out at 3x3 chunks.</li>
</ol>
<h2 id="s2-initialization"><a href="index.html#s2-initialization">Initialization</a></h2>
<p>Initialization is pretty simple:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> ds-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>Just set the corners to random values, exactly like we did in Midpoint
Displacement.</p>
<div id="demo-1" class="threejs"></div>
<h2 id="s3-square"><a href="index.html#s3-square">Square</a></h2>
<p>In the &quot;square&quot; step of Diamond Square, we take a square region of the heightmap
and set the center of it to the average of the four corners (plus jitter).</p>
<pre class="lineart">
Column
0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
0 │1│ │ │ │8│ 0 │1│ │ │ │8│ 0 │1│ │ │ │8│
├─┼─┼─┼─┼─┤ ├─╲─┼─┼─╱─┤ ├─╲─┼─┼─╱─┤
1 │ │ │ │ │ │ 1 │ │╲│ │╱│ │ 1 │ │╲│ │╱│ │
R ├─┼─┼─┼─┼─┤ ├─┼─◢─◣─┼─┤ ├─┼─◢─◣─┼─┤
o 2 │ │ │◉│ │ │ 2 │ │ │◉│ │ │ 2 │ │ │3│ │ │
w ├─┼─┼─┼─┼─┤ ├─┼─◥─◤─┼─┤ ├─┼─◥─◤─┼─┤
3 │ │ │ │ │ │ 3 │ │╱│ │╲│ │ 3 │ │╱│ │╲│ │
├─┼─┼─┼─┼─┤ ├─╱─┼─┼─╲─┤ ├─╱─┼─┼─╲─┤
4 │0│ │ │ │3│ 4 │0│ │ │ │3│ 4 │0│ │ │ │3│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
</pre>
<p>The Wikipedia article calls this the &quot;diamond&quot; step, but that seems backwards to
me. We're operating on a <em>square</em> region of the heightmap, so this should be
the <em>square</em> step.</p>
<p>Since we're no longer working with heightmap &quot;slices&quot; like we were in the
previous post, we'll need a way to specify the square chunk of the heightmap
we're working on. I chose to use a center point (<code>x</code> and <code>y</code> coordinates) and
a radius:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> ds-square <span class="paren2">[<span class="code">heightmap x y radius spread</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">new-height
<span class="paren4">(<span class="code">jitter
<span class="paren5">(<span class="code">average4
<span class="paren6">(<span class="code">heightmap-get heightmap <span class="paren1">(<span class="code">- x radius</span>)</span> <span class="paren1">(<span class="code">- y radius</span>)</span></span>)</span>
<span class="paren6">(<span class="code">heightmap-get heightmap <span class="paren1">(<span class="code">- x radius</span>)</span> <span class="paren1">(<span class="code">+ y radius</span>)</span></span>)</span>
<span class="paren6">(<span class="code">heightmap-get heightmap <span class="paren1">(<span class="code">+ x radius</span>)</span> <span class="paren1">(<span class="code">- y radius</span>)</span></span>)</span>
<span class="paren6">(<span class="code">heightmap-get heightmap <span class="paren1">(<span class="code">+ x radius</span>)</span> <span class="paren1">(<span class="code">+ y radius</span>)</span></span>)</span></span>)</span>
spread</span>)</span></span>]</span>
<span class="paren3">(<span class="code">heightmap-set! heightmap x y new-height</span>)</span></span>)</span></span>)</span></span></code></pre>
<div id="demo-2" class="threejs"></div>
<h2 id="s4-diamond"><a href="index.html#s4-diamond">Diamond</a></h2>
<p>In the &quot;diamond&quot; step of Diamond Square, we take a diamond-shaped region and set
the center of it to the average of the four corners:</p>
<pre class="lineart">
Column
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
┌─┬─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┐
0 │1│ │ │ │3│ │ │ │3│ 0 │1│ │ │ │3│ │ │ │3│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─╱─╲─┼─┼─┼─┤
1 │ │ │ │ │ │ │ │ │ │ 1 │ │ │ │╱│ │╲│ │ │ │
R ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─╱─┼─┼─╲─┼─┼─┤
o 2 │ │ │3│ │ │ │4│ │ │ 2 │ │ │3│ │◉│ │4│ │ │
w ├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─╲─┼─┼─╱─┼─┼─┤
3 │ │ │ │ │ │ │ │ │ │ 3 │ │ │ │╲│ │╱│ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─╲─╱─┼─┼─┼─┤
4 │5│ │ │ │5│ │ │ │5│ 4 │5│ │ │ │5│ │ │ │5│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
5 │ │ │ │ │ │ │ │ │ │ 5 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
6 │ │ │7│ │ │ │6│ │ │ 6 │ │ │7│ │ │ │6│ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
7 │ │ │ │ │ │ │ │ │ │ 7 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
8 │9│ │ │ │7│ │ │ │7│ 8 │9│ │ │ │7│ │ │ │7│
└─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┘
Column
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
┌─┬─┬─┬─┬─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┐
0 │1│ │ │ │3│ │ │ │3│ 0 │1│ │ │ │3│ │ │ │3│
├─┼─┼─┼─╱┬╲─┼─┼─┼─┤ ├─┼─┼─┼─╱┬╲─┼─┼─┼─┤
1 │ │ │ │╱│││╲│ │ │ │ 1 │ │ │ │╱│││╲│ │ │ │
R ├─┼─┼─╱─┼▼┼─╲─┼─┼─┤ ├─┼─┼─╱─┼▼┼─╲─┼─┼─┤
o 2 │ │ │3├─▶◉◀─┤4│ │ │ 2 │ │ │3├─▶4◀─┤4│ │ │
w ├─┼─┼─╲─┼▲┼─╱─┼─┼─┤ ├─┼─┼─╲─┼▲┼─╱─┼─┼─┤
3 │ │ │ │╲│││╱│ │ │ │ 3 │ │ │ │╲│││╱│ │ │ │
├─┼─┼─┼─╲┴╱─┼─┼─┼─┤ ├─┼─┼─┼─╲┴╱─┼─┼─┼─┤
4 │5│ │ │ │5│ │ │ │5│ 4 │5│ │ │ │5│ │ │ │5│
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
5 │ │ │ │ │ │ │ │ │ │ 5 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
6 │ │ │7│ │ │ │6│ │ │ 6 │ │ │7│ │ │ │6│ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
7 │ │ │ │ │ │ │ │ │ │ 7 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
8 │9│ │ │ │7│ │ │ │7│ 8 │9│ │ │ │7│ │ │ │7│
└─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┘
</pre>
<p>Once again we'll use a center point and radius to specify the region inside the
heightmap:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> ds-diamond <span class="paren2">[<span class="code">heightmap x y radius spread</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">new-height
<span class="paren4">(<span class="code">jitter
<span class="paren5">(<span class="code">safe-average
<span class="paren6">(<span class="code">heightmap-get-safe heightmap <span class="paren1">(<span class="code">- x radius</span>)</span> y</span>)</span>
<span class="paren6">(<span class="code">heightmap-get-safe heightmap <span class="paren1">(<span class="code">+ x radius</span>)</span> y</span>)</span>
<span class="paren6">(<span class="code">heightmap-get-safe heightmap x <span class="paren1">(<span class="code">- y radius</span>)</span></span>)</span>
<span class="paren6">(<span class="code">heightmap-get-safe heightmap x <span class="paren1">(<span class="code">+ y radius</span>)</span></span>)</span></span>)</span>
spread</span>)</span></span>]</span>
<span class="paren3">(<span class="code">heightmap-set! heightmap x y new-height</span>)</span></span>)</span></span>)</span></span></code></pre>
<div id="demo-3" class="threejs"></div>
<h2 id="s5-edge-cases"><a href="index.html#s5-edge-cases">Edge Cases</a></h2>
<p>You might have noticed that we used <code>heightmap-get-safe</code> and <code>safe-average</code> in
the <code>ds-diamond</code> code. This is to handle the diamonds on the edge of the
heightmap. Those diamonds only have three points to average:</p>
<pre class="lineart">
Column
0 1 2 3 4 0 1 2 3 4
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
0 │1│ │ │ │8│ 0 │1│ │ │ │8│
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─╱┬╲
1 │ │ │ │ │ │ 1 │ │ │ │╱│││╲
R ├─┼─┼─┼─┼─┤ ├─┼─┼─╱─┼▼┤ ╲
o 2 │ │ │3│ │ │ 2 │ │ │3├─▶◉◀──?
w ├─┼─┼─┼─┼─┤ ├─┼─┼─╲─┼▲┤
3 │ │ │ │ │ │ 3 │ │ │ │╲│││╱
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─╲┴╱
4 │0│ │ │ │3│ 4 │0│ │ │ │3│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
</pre>
<p>So we just ignore the missing point:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> safe-average <span class="paren2">[<span class="code">a b c d</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">total 0 count 0</span>]</span>
<span class="paren3">(<span class="code">when a <span class="paren4">(<span class="code">add! total a</span>)</span> <span class="paren4">(<span class="code">inc! count</span>)</span></span>)</span>
<span class="paren3">(<span class="code">when b <span class="paren4">(<span class="code">add! total b</span>)</span> <span class="paren4">(<span class="code">inc! count</span>)</span></span>)</span>
<span class="paren3">(<span class="code">when c <span class="paren4">(<span class="code">add! total c</span>)</span> <span class="paren4">(<span class="code">inc! count</span>)</span></span>)</span>
<span class="paren3">(<span class="code">when d <span class="paren4">(<span class="code">add! total d</span>)</span> <span class="paren4">(<span class="code">inc! count</span>)</span></span>)</span>
<span class="paren3">(<span class="code">/ total count</span>)</span></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">&lt;= 0 x last</span>)</span>
<span class="paren5">(<span class="code">&lt;= 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></code></pre>
<p>Technically this means they have less information to work with than the rest of
the points, and you might see some slight artifacts. But in practice it's not
too bad, and it's a lot nicer than midpoint displacement where <em>every</em> line step
uses only <em>two</em> points.</p>
<p>Another way to handle this is to also wrap those edge coordinates around and
pull the point from the other side of the heightmap. This is a bit more work
but gives you an extra benefit: the heightmap will be tileable (<strong>update</strong>: like
everything where someone doesn't show you running code, it's <a href="../../08/lisp-jam-postmortem/index.html#tiling-diamond-square">not actually that
simple</a>).</p>
<h2 id="s6-iteration"><a href="index.html#s6-iteration">Iteration</a></h2>
<p>Unfortunately we can't use recursion to iterate for Diamond Square like we did
for Midpoint Displacement. We have to interleave the diamond and square steps,
performing each round of squares on the <em>entire</em> map before moving on to a round
of diamonds. If we don't, bad things will happen.</p>
<p>Let's look at an example to see why. First we perform the initial square step:</p>
<pre class="lineart">
Column
0 1 2 3 4 0 1 2 3 4
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
0 │1│ │ │ │8│ 0 │1│ │ │ │8│
├─┼─┼─┼─┼─┤ ├─╲─┼─┼─╱─┤
1 │ │ │ │ │ │ 1 │ │╲│ │╱│ │
R ├─┼─┼─┼─┼─┤ ├─┼─◢─◣─┼─┤
o 2 │ │ │ │ │ │ 2 │ │ │3│ │ │
w ├─┼─┼─┼─┼─┤ ├─┼─◥─◤─┼─┤
3 │ │ │ │ │ │ 3 │ │╱│ │╲│ │
├─┼─┼─┼─┼─┤ ├─╱─┼─┼─╲─┤
4 │0│ │ │ │3│ 4 │0│ │ │ │3│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
</pre>
<p>That's fine. Then we recurse into the first of the four diamonds:</p>
<pre class="lineart">
Column
0 1 2 3 4 0 1 2 3 4
┌─┬─┬─┬─┬─┐ ┌─┬─┬─┬─┬─┐
0 │1│ │ │ │8│ 0 │1├─▶4◀─┤8│
├─┼─┼─┼─┼─┤ ├─╲─┼▲┼─╱─┤
1 │ │ │ │ │ │ 1 │ │╲│││╱│ │
R ├─┼─┼─┼─┼─┤ ├─┼─╲┴╱─┼─┤
o 2 │ │ │3│ │ │ 2 │ │ │3│ │ │
w ├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
3 │ │ │ │ │ │ 3 │ │ │ │ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
4 │0│ │ │ │3│ 4 │0│ │ │ │3│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
</pre>
<p>Everything's still good. Then we recurse into the square:</p>
<pre class="lineart">
Column
0 1 2 3 4 0 1 2 3 4
┌─┬─┬─┬─┬─┐ ╔═════╗─┬─┐
0 │1│ │4│ │8│ 0 ║1│ │4║ │8│
├─┼─┼─┼─┼─┤ ║─◢─◣─║─┼─┤
1 │ │ │ │ │ │ 1 ║ │ │ ║ │ │
R ├─┼─┼─┼─┼─┤ ║─◥─◤─║─┼─┤
o 2 │ │ │3│ │ │ 2 ║?│ │3║ │ │
w ├─┼─┼─┼─┼─┤ ╚═════╝─┼─┤
3 │ │ │ │ │ │ 3 │ │ │ │ │ │
├─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┤
4 │0│ │ │ │3│ 4 │0│ │ │ │3│
└─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┘
</pre>
<p>But wait, there's a corner missing! We needed to complete that left-hand
diamond step to get it, but that's still waiting on the stack for this line of
recursion to bottom out.</p>
<h3 id="s7-a-strided-iteration-construct"><a href="index.html#s7-a-strided-iteration-construct">A &quot;Strided&quot; Iteration Construct</a></h3>
<p>So we need to use normal iteration. We can start by writing the code to do
a single round of diamonds or squares on the heightmap. First we'll make a nice
looping construct called <code>do-stride</code>:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> do-stride
<span class="paren2">[<span class="code">varnames start-form end-form stride-form &amp; body</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">stride <span class="paren4">(<span class="code">gensym <span class="string">&quot;stride&quot;</span></span>)</span>
start <span class="paren4">(<span class="code">gensym <span class="string">&quot;start&quot;</span></span>)</span>
end <span class="paren4">(<span class="code">gensym <span class="string">&quot;end&quot;</span></span>)</span>
build <span class="paren4">(<span class="code">fn build <span class="paren5">[<span class="code">vars</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"><i><span class="symbol">let</span></i> <span class="paren1">[<span class="code">varname <span class="paren2">(<span class="code">first vars</span>)</span></span>]</span>
`<span class="paren1">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren2">[<span class="code">~varname ~start</span>]</span>
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">&lt; ~varname ~end</span>)</span>
~<span class="paren3">(<span class="code">build <span class="paren4">(<span class="code">rest vars</span>)</span></span>)</span>
<span class="paren3">(<span class="code">recur <span class="paren4">(<span class="code">+ ~varname ~stride</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>]</span>
<span class="comment">; Fix the numbers once outside the nested loops,
</span> <span class="comment">; and then build the guts.
</span> `<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">~start ~start-form
~end ~end-form
~stride ~stride-form</span>]</span>
~<span class="paren4">(<span class="code">build varnames</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<p>This scary-looking macro builds the nested looping structure we'll need to
perform the iteration for our diamonds and squares. It abstracts away the
tedium of writing out nested <code>for</code> loops so we can focus on the rest of the
algorithm.</p>
<p><code>do-stride</code> takes a list of variables to bind, a number to start at, a number to
end before, and a body. For each variable it starts at <code>start</code>, runs <code>body</code>,
increments by <code>stride</code>, and loops until the value is at or above <code>end</code>. For
example:</p>
<ul>
<li><code>(do-stride [x] 0 5 1 (console.log x))</code> will log <code>0 1 2 3 4</code>.</li>
<li><code>(do-stride [x] 0 5 2 (console.log x))</code> will log <code>0 2 4</code>.</li>
<li><code>(do-stride [x y] 0 3 2 (console.log [x y]))</code> will log <code>[0, 0] [0, 2] [2, 0]
[2, 2]</code>.</li>
</ul>
<h3 id="s8-square-iteration"><a href="index.html#s8-square-iteration">Square Iteration</a></h3>
<p>Now that we've got this, we can iterate our square step over the heightmap for
a specific radius:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> ds-squares <span class="paren2">[<span class="code">heightmap radius spread</span>]</span>
<span class="paren2">(<span class="code">do-stride <span class="paren3">[<span class="code">x y</span>]</span> radius <span class="paren3">(<span class="code">heightmap-resolution heightmap</span>)</span> <span class="paren3">(<span class="code">* 2 radius</span>)</span>
<span class="paren3">(<span class="code">ds-square heightmap x y radius spread</span>)</span></span>)</span></span>)</span></span></code></pre>
<p>We use <code>do-stride</code> to start at the radius and loop until we fall off the
heightmap, stepping by <em>twice</em> the radius each time.</p>
<p>Because our heightmaps are always square we can use the same values for both
dimensions. <code>do-stride</code> can take zero or more variables and will just do the
right thing, so we don't need to worry about it.</p>
<p>The stride is double the radius because as we step between squares we move over
the right half of the first <em>and</em> the left half of the next:</p>
<pre class="lineart">
Column
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
╔═════════╗─┬─┬─┬─┐ ╔═════════╗─┬─┬─┬─┐
0 ║1│ │ │ │3║ │ │ │3│ 0 ║1│ │ │ │3║ │ │ │3│
║─┼─┼─┼─┼─║─┼─┼─┼─┤ ║─┼─┼─┼─┼─║─┼─┼─┼─┤
1 ║ │ │ │ │ ║ │ │ │ │ 1 ║ │ │ │ │ ║ │ │ │ │
R ║─┼─┼─┼─┼─║─┼─┼─┼─┤ radius ┼─┼─║─┼─┼─┼─┤
o 2 ║ │ │◉│ │ ║ │◉│ │ │ 2 ║◀━━▶◉◀━━━━━▶◉│ │ │
w ║─┼─┼─┼─┼─║─┼─┼─┼─┤ ║─┼─┼─ stride ┼─┼─┤
3 ║ │ │ │ │ ║ │ │ │ │ 3 ║ │ │ │ │ ║ │ │ │ │
║─┼─┼─┼─┼─║─┼─┼─┼─┤ ║─┼─┼─┼─┼─║─┼─┼─┼─┤
4 ║5│ │ │ │5║ │ │ │5│ 4 ║5│ │ │ │5║ │ │ │5│
╚═════════╝─┼─┼─┼─┤ ╚═════════╝─┼─┼─┼─┤
5 │ │ │ │ │ │ │ │ │ │ 5 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
6 │ │ │◉│ │ │ │◉│ │ │ 6 │ │ │◉│ │ │ │◉│ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
7 │ │ │ │ │ │ │ │ │ │ 7 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤ ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
8 │9│ │ │ │7│ │ │ │7│ 8 │9│ │ │ │7│ │ │ │7│
└─┴─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┴─┘
</pre>
<h3 id="s9-diamond-iteration"><a href="index.html#s9-diamond-iteration">Diamond Iteration</a></h3>
<p>The diamonds are a little more complicated.</p>
<p>First of all, in the <code>y</code> dimension we need to start at <code>0</code> instead of <code>radius</code>
(these will be the top and bottom edge cases we looked at). Also, in the <code>x</code>
direction all the even iterations need to start at <code>radius</code>, while the odd
iterations should start at <code>0</code>.</p>
<p>Hopefully a picture will make it a bit more clear:</p>
<pre class="lineart">
Column
0 1 2 3 4 5 6 7 8
┌─┬─┬─┬─┬─┬─┬─┬─┬─┐
0 │1│ │◉│ │3│ │◉│ │3│ y iteration: 0, start at x = radius
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
1 │ │ │ │ │ │ │ │ │ │
R ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
o 2 │◉│ │3│ │◉│ │4│ │◉│ y iteration: 1, start at x = 0
w ├─┼─┼─┼─┼─┼─┼─┼─┼─┤
3 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
4 │5│ │◉│ │5│ │◉│ │5│ y iteration: 2, start at x = radius
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
5 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
6 │◉│ │7│ │◉│ │6│ │◉│ y iteration: 3, start at x = 0
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
7 │ │ │ │ │ │ │ │ │ │
├─┼─┼─┼─┼─┼─┼─┼─┼─┤
8 │9│ │◉│ │7│ │◉│ │7│ y iteration: 4, start at x = radius
└─┴─┴─┴─┴─┴─┴─┴─┴─┘
</pre>
<p>The code for the diamonds is the hairiest bit of the algorithm. Make sure you
understand it before moving on. <code>shift</code> will be <code>radius</code> for even iterations
and <code>0</code> for odd ones:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> ds-diamonds <span class="paren2">[<span class="code">heightmap radius spread</span>]</span>
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">size <span class="paren4">(<span class="code">heightmap-resolution heightmap</span>)</span></span>]</span>
<span class="paren3">(<span class="code">do-stride <span class="paren4">[<span class="code">y</span>]</span> 0 size radius
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">[<span class="code">shift <span class="paren6">(<span class="code"><i><span class="symbol">if</span></i> <span class="paren1">(<span class="code">even? <span class="paren2">(<span class="code">/ y radius</span>)</span></span>)</span> radius 0</span>)</span></span>]</span>
<span class="paren5">(<span class="code">do-stride <span class="paren6">[<span class="code">x</span>]</span> shift size <span class="paren6">(<span class="code">* 2 radius</span>)</span>
<span class="paren6">(<span class="code">ds-diamond heightmap x y radius spread</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
<h3 id="s10-top-level-iteration"><a href="index.html#s10-top-level-iteration">Top-Level Iteration</a></h3>
<p>Finally we can put it all together. We initialize the corners and starting
values, then loop over smaller and smaller radii and just call <code>ds-squares</code> and
<code>ds-diamonds</code> to do the heavy lifting:</p>
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> diamond-square <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.5
center <span class="paren4">(<span class="code">heightmap-center-index heightmap</span>)</span>
size <span class="paren4">(<span class="code">aget heightmap.shape 0</span>)</span></span>]</span>
<span class="paren3">(<span class="code">ds-init-corners heightmap</span>)</span>
<span class="paren3">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren4">[<span class="code">radius center
spread initial-spread</span>]</span>
<span class="paren4">(<span class="code">when <span class="paren5">(<span class="code">&gt;= radius 1</span>)</span>
<span class="paren5">(<span class="code">ds-squares heightmap radius spread</span>)</span>
<span class="paren5">(<span class="code">ds-diamonds heightmap radius spread</span>)</span>
<span class="paren5">(<span class="code">recur <span class="paren6">(<span class="code">/ radius 2</span>)</span>
<span class="paren6">(<span class="code">* spread spread-reduction</span>)</span></span>)</span></span>)</span></span>)</span>
<span class="paren3">(<span class="code">sanitize heightmap</span>)</span></span>)</span></span>)</span></span></code></pre>
<h2 id="s11-result"><a href="index.html#s11-result">Result</a></h2>
<p>The end result looks like this:</p>
<div id="demo-final" class="threejs"></div>
<p>It looks a lot like the result from Midpoint Displacement, but without the ugly
seams around the square chunks.</p>
<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 after each step. 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, and <a href="http://bitbucket.org/sjl/ymir/">that code</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>