458 lines
36 KiB
HTML
458 lines
36 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>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 "seams" 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 "Strided" 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 "diamond" 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 "square" 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 "diamond" 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 "slices" 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 "diamond" 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"><= 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></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 "Strided" 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 & 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">"stride"</span></span>)</span>
|
|||
|
start <span class="paren4">(<span class="code">gensym <span class="string">"start"</span></span>)</span>
|
|||
|
end <span class="paren4">(<span class="code">gensym <span class="string">"end"</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">< ~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">>= 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>
|