826 lines
No EOL
56 KiB
HTML
826 lines
No EOL
56 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 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><script type='text/javascript' async
|
|
src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML'></script><h1><a href='index.html'>Terrain Generation with Midpoint Displacement</a></h1><p class='date'>Posted on February 19th, 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/terrain1.js"></script>
|
|
|
|
<p>I'm taking the Game Engine Architecture class at Reykjavík University. My group
|
|
just finished our midterm <a href="https://www.youtube.com/watch?v=G5u79w4qiAA">project</a> where we played around with procedural
|
|
terrain generation in <a href="http://unity3d.com/">Unity</a>. It was a lot of fun and I really enjoyed
|
|
creating terrain out of numbers, so I thought I'd write up a little introduction
|
|
to a few of the algorithms.</p>
|
|
|
|
<p>In this post we'll be looking at Midpoint Displacement.</p>
|
|
|
|
<p>The full series of posts so far:</p>
|
|
|
|
<ul>
|
|
<li><a href="index.html">Midpoint Displacement</a></li>
|
|
<li><a href="../../03/recursive-midpoint-displacement/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-terrain-generation">Terrain Generation</a></li><li><a href="index.html#s2-resources-code-and-examples">Resources, Code, and Examples</a><ol><li><a href="index.html#s3-wisp">Wisp</a></li><li><a href="index.html#s4-three-js">Three.js</a></li></ol></li><li><a href="index.html#s5-heightmaps">Heightmaps</a><ol><li><a href="index.html#s6-overview">Overview</a></li><li><a href="index.html#s7-code">Code</a></li></ol></li><li><a href="index.html#s8-random-noise">Random Noise</a></li><li><a href="index.html#s9-midpoint-displacement">Midpoint Displacement</a><ol><li><a href="index.html#s10-initialize-the-corners">Initialize the Corners</a></li><li><a href="index.html#s11-set-the-edges">Set the Edges</a></li><li><a href="index.html#s12-set-the-center">Set the Center</a></li><li><a href="index.html#s13-iterate">Iterate</a></li></ol></li><li><a href="index.html#s14-result">Result</a></li></ol>
|
|
|
|
<h2 id="s1-terrain-generation"><a href="index.html#s1-terrain-generation">Terrain Generation</a></h2>
|
|
|
|
<p>Procedural generation is a pretty big field. The <a href="https://en.wikipedia.org/wiki/Procedural_generation">Wikipedia
|
|
article</a> gives a pretty brief overview. The <a href="http://pcg.wikidot.com/">Procedural Content
|
|
Generation Wiki</a> is an entire wiki devoted to procedural generation.
|
|
There's also a small but active <a href="https://www.reddit.com/r/proceduralgeneration/">subreddit</a> for interested folks.</p>
|
|
|
|
<p>Today we're just going to look at a single algorithm for generating
|
|
realistic-looking terrain called "Midpoint Displacement". It's relatively
|
|
simple (compared to other algorithms) but it produces terrain that actually
|
|
looks kind of cool.</p>
|
|
|
|
<h2 id="s2-resources-code-and-examples"><a href="index.html#s2-resources-code-and-examples">Resources, Code, and Examples</a></h2>
|
|
|
|
<p>There are a lot of resources for learning how terrain generation algorithms
|
|
work. For midpoint displacement I used the <a href="https://en.wikipedia.org/wiki/Diamond-square_algorithm">Wikipedia article</a>,
|
|
the <a href="http://pcg.wikidot.com/pcg-algorithm:midpoint-displacement-algorithm">PCG Wiki article</a>, and academic papers like <a href="http://micsymposium.org/mics_2011_proceedings/mics2011_submission_30.pdf">this
|
|
one</a>.</p>
|
|
|
|
<p>Unfortunately, while there are a <em>lot</em> of places that describe algorithms like
|
|
this, there are relatively few that explain it <em>thoroughly</em>. Academic papers in
|
|
particular tend to have a really bad case of
|
|
<a href="https://i.imgur.com/RadSf.jpg">draw-the-rest-of-the-fucking-owl-syndrome</a>. They tend to slap an equation
|
|
or two on the page and move on without showing any code.</p>
|
|
|
|
<p>As anyone who's done much programming knows, there's a big difference between
|
|
saying something like "set the midpoints of the diamonds to the average of the
|
|
corners" and making a computer actually <em>do</em> that. How do we iterate over the
|
|
array (row-major or column-major)? Does it matter (yes: if you care about cache
|
|
coherency)? What about the edges where there are fewer corners? How random
|
|
(and how fast) is our random number generator (and how much do we care)? How do
|
|
we write all this code so we can actually read it six months later?</p>
|
|
|
|
<p>So I'm going to try to do my part to improve the situation by not just
|
|
describing the algorithm, but showing <em>actual code</em> that runs and generates
|
|
a terrain.</p>
|
|
|
|
<p>The full code is <a href="https://bitbucket.org/sjl/stevelosh/src/default/static/js/terrain1.wisp">here</a>, but we'll be going through the important parts
|
|
below.</p>
|
|
|
|
<h3 id="s3-wisp"><a href="index.html#s3-wisp">Wisp</a></h3>
|
|
|
|
<p>I'm using a language called <a href="https://github.com/Gozala/wisp">Wisp</a>. It's a small, Clojure-inspired
|
|
dialect of Lisp that compiles down to vanilla Javascript.</p>
|
|
|
|
<p>I went with Javascript because it means I can actually put demos inline in this
|
|
post. Sometimes it's so much easier to understand something when you can
|
|
actually see it running.</p>
|
|
|
|
<p>I've tried to write the code so that it's readable even if you don't know Lisp.
|
|
The point of me showing you this code is not to give you something to copy and
|
|
paste. The point is that until you <em>actually write and run</em> the code, you don't
|
|
know all the edge cases and problems that can happen. So by making examples
|
|
that actually run I feel a bit more confident that I'm explaining things enough.</p>
|
|
|
|
<p>I haven't focused on performance at all. If you want a fast implementation of
|
|
this, you'll probably want to use a language with real arrays (i.e. contiguous
|
|
chunks of memory holding a bunch of unboxed floats or integers).</p>
|
|
|
|
<h3 id="s4-three-js"><a href="index.html#s4-three-js">Three.js</a></h3>
|
|
|
|
<p><a href="http://threejs.org/">Three.js</a> is a Javascript library for 3D rendering. I'm using it
|
|
here to get something on the screen you can play with. I'm not going to cover
|
|
the code to put the terrains on the screen because it's really an entirely
|
|
separate problem to <em>generating</em> the terrain, and it's mostly uninteresting
|
|
boilerplate for these little demos.</p>
|
|
|
|
<p>I haven't been able to get these demos working on my iPhone. Sorry about that,
|
|
but I'm not much of a frontend developer. You'll have to use a computer to see
|
|
them. <a href="http://bitbucket.org/sjl/stevelosh/">Pull requests</a> are welcome.</p>
|
|
|
|
<h2 id="s5-heightmaps"><a href="index.html#s5-heightmaps">Heightmaps</a></h2>
|
|
|
|
<p>Let's get started. The main data structure we're going to be using is
|
|
a <a href="https://en.wikipedia.org/wiki/Heightmap">heightmap</a>.</p>
|
|
|
|
<h3 id="s6-overview"><a href="index.html#s6-overview">Overview</a></h3>
|
|
|
|
<p>Heightmaps are essentially just big two-dimensional arrays of numbers in a given
|
|
range that represent the height of the ground at a given point. For example,
|
|
a heightmap with a small "mountain" in the middle might look like:</p>
|
|
|
|
<pre><code>[[0, 0, 2, 0, 0],
|
|
[0, 2, 5, 1, 0],
|
|
[2, 5, 9, 4, 1],
|
|
[0, 1, 4, 1, 0],
|
|
[0, 0, 1, 0, 0]]</code></pre>
|
|
|
|
<p>In practice, heightmaps are often represented as a single-dimensional array
|
|
instead to ensure that all the data is together in one big hunk of memory:</p>
|
|
|
|
<pre><code>[0, 0, 2, 0, 0,
|
|
0, 2, 5, 1, 0,
|
|
2, 5, 9, 4, 1,
|
|
0, 1, 4, 1, 0,
|
|
0, 0, 1, 0, 0]</code></pre>
|
|
|
|
<p>The type and range of the numbers in the map varies by program and programmer.
|
|
Some programs use nonzero integers, some use all the integers, some use floating
|
|
point, etc.</p>
|
|
|
|
<p>The heightmaps in this post will contain floats from <code>0.0</code> to <code>1.0</code>. I like
|
|
using floats between zero and one because it's easy to roughly visualize them in
|
|
your head (e.g. <code>0.1</code> is ten percent of the maximum height).</p>
|
|
|
|
<p>One common way to visualize heightmaps is by turning them into greyscale images
|
|
with one pixel per number, with black pixels for lowest value in the map's range
|
|
and white for the highest. The <a href="https://en.wikipedia.org/wiki/Heightmap">Wikipedia article</a> has an example of
|
|
this. This is handy because you can also write some code to <em>read</em> images, and
|
|
then you can use any image editor to draw your own heightmaps by hand.</p>
|
|
|
|
<p>We're going to visualize the heightmaps in 3D. It's a lot more fun, and they
|
|
start to actually look like real terrains.</p>
|
|
|
|
<h3 id="s7-code"><a href="index.html#s7-code">Code</a></h3>
|
|
|
|
<p>We're going to start by writing a few functions that will hide away the internal
|
|
details of how our heightmaps are implemented, so we can talk about them in
|
|
nice high-level terms for the rest of the program. We'll also need a couple of
|
|
helper functions along the way.</p>
|
|
|
|
<p>We'll start with something to create a heightmap:</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">+ 1 <span class="paren5">(<span class="code">Math.pow 2 exponent</span>)</span></span>)</span></span>]</span>
|
|
<span class="paren3">(<span class="code">l <span class="paren4">(<span class="code">+ <span class="string">"Creating "</span> resolution <span class="string">" by "</span> resolution <span class="string">" heightmap..."</span></span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">def heightmap
|
|
<span class="paren4">(<span class="code">new Array <span class="paren5">(<span class="code">* resolution resolution</span>)</span></span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">set! heightmap.resolution resolution</span>)</span>
|
|
<span class="paren3">(<span class="code">set! heightmap.exponent exponent</span>)</span>
|
|
<span class="paren3">(<span class="code">set! heightmap.last <span class="paren4">(<span class="code">- resolution 1</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">zero-heightmap heightmap</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p><code>make-heightmap</code> takes an exponent and creates a heightmap. For reasons that
|
|
will become clear later, all of our heightmaps must be square, and they'll need
|
|
to have \(2^n + 1\) rows and columns. This means we can make heightmaps of
|
|
sizes like 3x3, 5x5, 9x9, 17x17, 33x33, etc. So our function just takes the
|
|
\(n\) to use and creates a correctly-sized array.</p>
|
|
|
|
<p>(If you've ever poked around with Unity's Terrain objects you might recognize
|
|
these "powers of two plus one".)</p>
|
|
|
|
<p>We create a new array (1-dimensional) and store a few pieces of extra data on it
|
|
for later use. <code>last</code> is the index of the last row/column, so for a 3x3 array
|
|
it would be <code>2</code>.</p>
|
|
|
|
<p><code>l</code> is just a simple little logging function:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> l <span class="paren2">[<span class="code">v</span>]</span>
|
|
<span class="paren2">(<span class="code">console.log v</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>We'll need to zero out the heightmap as well:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> zero-heightmap <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">do-times i heightmap.length
|
|
<span class="paren3">(<span class="code">set! <span class="paren4">(<span class="code">aget heightmap i</span>)</span> 0.0</span>)</span></span>)</span>
|
|
heightmap</span>)</span></span></code></pre>
|
|
|
|
<p><code>zero-heightmap</code> just iterates from <code>0</code> to <code>heightmap.length</code> and sets each
|
|
element to <code>0.0</code>. Wisp doesn't have syntax to loop from 0 to some number, but
|
|
it's a Lisp, so we can make some:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> when <span class="paren2">[<span class="code">condition & body</span>]</span>
|
|
`<span class="paren2">(<span class="code"><i><span class="symbol">if</span></i> ~condition
|
|
<span class="paren3">(<span class="code">do ~@body</span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> do-times <span class="paren2">[<span class="code">varname limit & body</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">end <span class="paren4">(<span class="code">gensym</span>)</span></span>]</span>
|
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">~end ~limit</span>]</span>
|
|
<span class="paren4">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren5">[<span class="code">~varname 0</span>]</span>
|
|
<span class="paren5">(<span class="code">when <span class="paren6">(<span class="code">< ~varname ~end</span>)</span>
|
|
~@body
|
|
<span class="paren6">(<span class="code">recur <span class="paren1">(<span class="code">+ 1 ~varname</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>If you're not a Lisp person, don't sweat this too much. Just trust me that
|
|
<code>(do-times i 10 (console.log i))</code> will print the numbers <code>0</code> to <code>9</code>.</p>
|
|
|
|
<p>Now that we can create a heightmap, we'll probably want to be able to read its
|
|
elements:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> heightmap-get <span class="paren2">[<span class="code">hm x y</span>]</span>
|
|
`<span class="paren2">(<span class="code">aget ~hm <span class="paren3">(<span class="code">+ <span class="paren4">(<span class="code">* ~y <span class="paren5">(<span class="code">.-resolution ~hm</span>)</span></span>)</span> ~x</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">hm x y</span>]</span>
|
|
<span class="paren2">(<span class="code">when <span class="paren3">(<span class="code">and <span class="paren4">(<span class="code"><= 0 x hm.last</span>)</span>
|
|
<span class="paren4">(<span class="code"><= 0 y hm.last</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-get hm x y</span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p><code>heightmap-get</code> handles the nasty business of translating our human-thinkable
|
|
<code>x</code> and <code>y</code> coordinates into an index into the single-dimensional array. It's
|
|
not particularly fast (especially if the compiler won't inline it), but we're
|
|
not worried about speed for this post.</p>
|
|
|
|
<p><code>heightmap-get-safe</code> is a version of <code>heightmap-get</code> that will do bounds
|
|
checking for us and return <code>nil</code> if we ask for something out of range.
|
|
Javascript will return <code>undefined</code> if you ask for a non-existent index, but
|
|
because we're storing the array as one big line of data, using a <code>y</code> value
|
|
that's too big will actually "wrap around" to the next row, so it's safer to
|
|
just check it explicitly.</p>
|
|
|
|
<p>Of course we'll also want to be able to set values in our heightmap:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> heightmap-set! <span class="paren2">[<span class="code">hm x y val</span>]</span>
|
|
`<span class="paren2">(<span class="code">set! <span class="paren3">(<span class="code">heightmap-get ~hm ~x ~y</span>)</span> ~val</span>)</span></span>)</span>
|
|
</span></code></pre>
|
|
|
|
<p>And finally, we'll want to be able to "normalize" our array. During the course
|
|
of running our algorithm, it's possible for the values to become less than <code>0.0</code>
|
|
or greater than <code>1.0</code>. We want our heightmaps to contain only values between
|
|
zero and one, so rather than change the algorithm we can just loop through the
|
|
array at the end and "squeeze" the range down to 0 and 1 (or "stretch" it if
|
|
it ends up smaller):</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">hm</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-times i hm.length
|
|
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">[<span class="code">el <span class="paren6">(<span class="code">aget hm i</span>)</span></span>]</span>
|
|
<span class="paren5">(<span class="code">when <span class="paren6">(<span class="code">< max el</span>)</span> <span class="paren6">(<span class="code">set! max el</span>)</span></span>)</span>
|
|
<span class="paren5">(<span class="code">when <span class="paren6">(<span class="code">> min el</span>)</span> <span class="paren6">(<span class="code">set! min el</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">span <span class="paren5">(<span class="code">- max min</span>)</span></span>]</span>
|
|
<span class="paren4">(<span class="code">do-times i hm.length
|
|
<span class="paren5">(<span class="code">set! <span class="paren6">(<span class="code">aget hm i</span>)</span>
|
|
<span class="paren6">(<span class="code">/ <span class="paren1">(<span class="code">- <span class="paren2">(<span class="code">aget hm i</span>)</span> min</span>)</span>
|
|
span</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<h2 id="s8-random-noise"><a href="index.html#s8-random-noise">Random Noise</a></h2>
|
|
|
|
<p>Now that we've got a nice little interface to heightmaps we can move on to
|
|
actually making some terrain! Before we dive into midpoint displacement, let's
|
|
get a <a href="http://rampantgames.com/blog/?p=7745">black triangle</a> on the screen. We'll start by just setting every point
|
|
in the heightmap to a completely random value.</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> rand <span class="paren2">[<span class="code"></span>]</span>
|
|
<span class="paren2">(<span class="code">Math.random</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> rand-around-zero <span class="paren2">[<span class="code">spread</span>]</span>
|
|
<span class="paren2">(<span class="code">- <span class="paren3">(<span class="code">* spread <span class="paren4">(<span class="code">rand</span>)</span> 2</span>)</span> spread</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> random-noise <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">do-times i heightmap.length
|
|
<span class="paren3">(<span class="code">set! <span class="paren4">(<span class="code">aget heightmap i</span>)</span> <span class="paren4">(<span class="code">rand</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>Let's see it! Hit the "refresh" button to create and render the terrain. You
|
|
can keep hitting it to get fresh terrains.</p>
|
|
|
|
<div id="demo-random" class="threejs"></div>
|
|
|
|
<p>Not really a very fun landscape to explore, but at least we've got something
|
|
running. Onward to the real algorithm.</p>
|
|
|
|
<h2 id="s9-midpoint-displacement"><a href="index.html#s9-midpoint-displacement">Midpoint Displacement</a></h2>
|
|
|
|
<p>Midpoint displacement is a simple, fast way to generate decent-looking terrain.
|
|
It's not perfect (you'll notice patterns once you start viewing the mesh from
|
|
different angles) but it's a good place to start.</p>
|
|
|
|
<p>The general process goes like this:</p>
|
|
|
|
<ol>
|
|
<li>Initialize the four corners of the heightmap to random values.</li>
|
|
<li>Set the midpoints of each edge to the average of the two corners it's
|
|
between, plus or minus a random amount.</li>
|
|
<li>Set the center of the square to the average of those edge midpoints you just
|
|
set, again with a random jitter.</li>
|
|
<li>Recurse on the four squares inside this one, reducing the jitter.</li>
|
|
</ol>
|
|
|
|
<p>That's the standard description. Now let's draw the fucking owl.</p>
|
|
|
|
<h3 id="s10-initialize-the-corners"><a href="index.html#s10-initialize-the-corners">Initialize the Corners</a></h3>
|
|
|
|
<p>The first step is pretty easy: we just need to take our heightmap:</p>
|
|
|
|
<pre class="lineart">
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
1 │ │ │ │ │ │
|
|
R ├───┼───┼───┼───┼───┤
|
|
o 2 │ │ │ │ │ │
|
|
w ├───┼───┼───┼───┼───┤
|
|
3 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
4 │ │ │ │ │ │
|
|
└───┴───┴───┴───┴───┘
|
|
</pre>
|
|
|
|
<p>Find the corners:</p>
|
|
|
|
<pre class="lineart">
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ ◉ │ │ │ │ ◉ │
|
|
├───┼───┼───┼───┼───┤
|
|
1 │ │ │ │ │ │
|
|
R ├───┼───┼───┼───┼───┤
|
|
o 2 │ │ │ │ │ │
|
|
w ├───┼───┼───┼───┼───┤
|
|
3 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
4 │ ◉ │ │ │ │ ◉ │
|
|
└───┴───┴───┴───┴───┘
|
|
</pre>
|
|
|
|
<p>And shove random values in them:</p>
|
|
|
|
<pre class="lineart">
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 │ │ │ │ 8 │
|
|
├───┼───┼───┼───┼───┤
|
|
1 │ │ │ │ │ │
|
|
R ├───┼───┼───┼───┼───┤
|
|
o 2 │ │ │ │ │ │
|
|
w ├───┼───┼───┼───┼───┤
|
|
3 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
4 │ 0 │ │ │ │ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
</pre>
|
|
|
|
<p>(I'm using 0 to 10 instead of 0 to 1 because it's easier to read in the
|
|
ASCII-art diagrams.)</p>
|
|
|
|
<p>The code is about what you'd expect:</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">heightmap-set! heightmap 0 0 <span class="paren3">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren2">(<span class="code">heightmap-set! heightmap 0 heightmap.last <span class="paren3">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren2">(<span class="code">heightmap-set! heightmap heightmap.last 0 <span class="paren3">(<span class="code">rand</span>)</span></span>)</span>
|
|
<span class="paren2">(<span class="code">heightmap-set! heightmap heightmap.last heightmap.last <span class="paren3">(<span class="code">rand</span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> midpoint-displacement-d1 <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">mpd-init-corners heightmap</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>That was easy.</p>
|
|
|
|
<div id="demo-mpd-1" class="threejs"></div>
|
|
|
|
<h3 id="s11-set-the-edges"><a href="index.html#s11-set-the-edges">Set the Edges</a></h3>
|
|
|
|
<p>For the next step, we need to take our square heightmap with filled-in corners
|
|
and use the corners to fill in the middle element of each edge:</p>
|
|
|
|
<pre class="lineart">
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 │ │ ◉ │ │ 8 │
|
|
├───┼───┼───┼───┼───┤
|
|
1 │ │ │ │ │ │
|
|
R ├───┼───┼───┼───┼───┤
|
|
o 2 │ ◉ │ │ │ │ ◉ │
|
|
w ├───┼───┼───┼───┼───┤
|
|
3 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
4 │ 0 │ │ ◉ │ │ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
|
|
|
|
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 ├───▶ ◉ ◀───┤ 8 │
|
|
├─┬─┼───┼───┼───┼─┬─┤
|
|
1 │ │ │ │ │ │ │ │
|
|
R ├─▼─┼───┼───┼───┼─▼─┤
|
|
o 2 │ ◉ │ │ │ │ ◉ │
|
|
w ├─▲─┼───┼───┼───┼─▲─┤
|
|
3 │ │ │ │ │ │ │ │
|
|
├─┴─┼───┼───┼───┼─┴─┤
|
|
4 │ 0 ├───▶ ◉ ◀───┤ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
|
|
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 ├───▶ 5 ◀───┤ 8 │
|
|
├─┬─┼───┼───┼───┼─┬─┤
|
|
1 │ │ │ │ │ │ │ │
|
|
R ├─▼─┼───┼───┼───┼─▼─┤
|
|
o 2 │ 1 │ │ │ │5.5│
|
|
w ├─▲─┼───┼───┼───┼─▲─┤
|
|
3 │ │ │ │ │ │ │ │
|
|
├─┴─┼───┼───┼───┼─┴─┤
|
|
4 │ 0 ├───▶1.5◀───┤ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
</pre>
|
|
|
|
<p>This is why the size of the heightmap has to be \(2^n + 1\) — the \(+ 1\)
|
|
ensures that the sides are odd lengths, which makes sure they <em>have</em> a middle
|
|
element for us to set.</p>
|
|
|
|
<p>Let's make a couple of helper functions:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> jitter <span class="paren2">[<span class="code">value spread</span>]</span>
|
|
<span class="paren2">(<span class="code">+ value <span class="paren3">(<span class="code">rand-around-zero spread</span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> midpoint <span class="paren2">[<span class="code">a b</span>]</span>
|
|
<span class="paren2">(<span class="code">/ <span class="paren3">(<span class="code">+ a b</span>)</span> 2</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> average2 <span class="paren2">[<span class="code">a b</span>]</span>
|
|
<span class="paren2">(<span class="code">/ <span class="paren3">(<span class="code">+ a b</span>)</span> 2</span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> average4 <span class="paren2">[<span class="code">a b c d</span>]</span>
|
|
<span class="paren2">(<span class="code">/ <span class="paren3">(<span class="code">+ a b c d</span>)</span> 4</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p><code>jitter</code> is just a nice way of saying "take this point and move it a random
|
|
amount up or down".</p>
|
|
|
|
<p>The other three functions are pretty self-explanatory. Also, two of them are
|
|
exactly the same function! There's a reason for this.</p>
|
|
|
|
<p>Lately I've started digging into <a href="http://www.amazon.com/dp/0262510871/?tag=stelos-20">SICP</a> again and watching the <a href="https://youtu.be/2Op3QLzMgSY">lectures</a>.
|
|
Abelson and Sussman practice a style of programming that's more about "growing"
|
|
a language to talk about a problem you're trying to solve. So you start with
|
|
the base programming language, and then define new terms/syntax that let you
|
|
talk about your problem at the appropriate level of detail.</p>
|
|
|
|
<p>So in this case, we've got two functions for conceptually different tasks:</p>
|
|
|
|
<ul>
|
|
<li><code>midpoint</code> finds the middle point between two points (indexes in an array in
|
|
this case).</li>
|
|
<li><code>average2</code> finds the average of two values (floating point height values
|
|
here).</li>
|
|
</ul>
|
|
|
|
<p>They happen to be implemented in the same way, but I think giving them different
|
|
names makes them easier to talk about and easier to keep straight in your head.</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 lx rx by ty spread</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">cx <span class="paren4">(<span class="code">midpoint lx rx</span>)</span>
|
|
cy <span class="paren4">(<span class="code">midpoint by ty</span>)</span>
|
|
|
|
bottom-left <span class="paren4">(<span class="code">heightmap-get heightmap lx by</span>)</span>
|
|
bottom-right <span class="paren4">(<span class="code">heightmap-get heightmap rx by</span>)</span>
|
|
top-left <span class="paren4">(<span class="code">heightmap-get heightmap lx ty</span>)</span>
|
|
top-right <span class="paren4">(<span class="code">heightmap-get heightmap rx ty</span>)</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></span>]</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap cx by <span class="paren4">(<span class="code">jitter bottom spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap cx ty <span class="paren4">(<span class="code">jitter top spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap lx cy <span class="paren4">(<span class="code">jitter left spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap rx cy <span class="paren4">(<span class="code">jitter right spread</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
|
|
<span class="paren1">(<span class="code"><i><span class="symbol">defn</span></i> midpoint-displacement-d2 <span class="paren2">[<span class="code">heightmap</span>]</span>
|
|
<span class="paren2">(<span class="code">mpd-init-corners heightmap</span>)</span>
|
|
<span class="paren2">(<span class="code">mpd-displace heightmap
|
|
0 heightmap.last
|
|
0 heightmap.last
|
|
0.1</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p><code>mpd-displace</code> is the meat of the algorithm. It takes a heightmap, the spread
|
|
(how much random "jitter" to apply after averaging the values), and four values
|
|
to define the square we're working on in the heightmap:</p>
|
|
|
|
<ul>
|
|
<li><code>lx</code>: the x coordinate for the left-hand corners</li>
|
|
<li><code>rx</code>: the x coordinate for the right-hand corners</li>
|
|
<li><code>by</code>: the y coordinate for the bottom corners</li>
|
|
<li><code>ty</code>: the y coordinate for the top corners</li>
|
|
</ul>
|
|
|
|
<p>For the first iteration we want to work on the entire heightmap, so we pass in
|
|
<code>0</code> and <code>heightmap.last</code> for the left/right and top/bottom indices.</p>
|
|
|
|
<p>Run the demo a few times and watch how the midpoints fall between the corner
|
|
points because they're averaged (with a little bit of jitter thrown in).</p>
|
|
|
|
<div id="demo-mpd-2" class="threejs"></div>
|
|
|
|
<h3 id="s12-set-the-center"><a href="index.html#s12-set-the-center">Set the Center</a></h3>
|
|
|
|
<p>The next step is to set the center element of the square to the average of those
|
|
midpoints we just set, plus a bit of jitter:</p>
|
|
|
|
<pre class="lineart">
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 │ │ 5 │ │ 8 │
|
|
├───┼───┼───┼───┼───┤
|
|
1 │ │ │ │ │ │
|
|
R ├───┼───┼───┼───┼───┤
|
|
o 2 │ 1 │ │ ◉ │ │5.5│
|
|
w ├───┼───┼───┼───┼───┤
|
|
3 │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤
|
|
4 │ 0 │ │1.5│ │ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
|
|
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 │ │ 5 │ │ 8 │
|
|
├───┼───┼─┬─┼───┼───┤
|
|
1 │ │ │ │ │ │ │
|
|
R ├───┼───┼─▼─┼───┼───┤
|
|
o 2 │ 1 ├───▶ ◉ ◀───┤5.5│
|
|
w ├───┼───┼─▲─┼───┼───┤
|
|
3 │ │ │ │ │ │ │
|
|
├───┼───┼─┴─┼───┼───┤
|
|
4 │ 0 │ │1.5│ │ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
|
|
|
|
Column
|
|
0 1 2 3 4
|
|
┌───┬───┬───┬───┬───┐
|
|
0 │ 2 │ │ 5 │ │ 8 │
|
|
├───┼───┼─┬─┼───┼───┤
|
|
1 │ │ │ │ │ │ │
|
|
R ├───┼───┼─▼─┼───┼───┤
|
|
o 2 │ 1 ├───▶3.3◀───┤5.5│
|
|
w ├───┼───┼─▲─┼───┼───┤
|
|
3 │ │ │ │ │ │ │
|
|
├───┼───┼─┴─┼───┼───┤
|
|
4 │ 0 │ │1.5│ │ 3 │
|
|
└───┴───┴───┴───┴───┘
|
|
</pre>
|
|
|
|
<p>We can add this to the displacement function pretty easily:</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 lx rx by ty spread</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">cx <span class="paren4">(<span class="code">midpoint lx rx</span>)</span>
|
|
cy <span class="paren4">(<span class="code">midpoint by ty</span>)</span>
|
|
|
|
bottom-left <span class="paren4">(<span class="code">heightmap-get heightmap lx by</span>)</span>
|
|
bottom-right <span class="paren4">(<span class="code">heightmap-get heightmap rx by</span>)</span>
|
|
top-left <span class="paren4">(<span class="code">heightmap-get heightmap lx ty</span>)</span>
|
|
top-right <span class="paren4">(<span class="code">heightmap-get heightmap rx ty</span>)</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></span>]</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap cx by <span class="paren4">(<span class="code">jitter bottom spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap cx ty <span class="paren4">(<span class="code">jitter top spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap lx cy <span class="paren4">(<span class="code">jitter left spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap rx cy <span class="paren4">(<span class="code">jitter right spread</span>)</span></span>)</span>
|
|
<span class="paren3">(<span class="code">heightmap-set! heightmap cx cy <span class="paren4">(<span class="code">jitter center spread</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>And now our center is ready to go:</p>
|
|
|
|
<div id="demo-mpd-3" class="threejs"></div>
|
|
|
|
<h3 id="s13-iterate"><a href="index.html#s13-iterate">Iterate</a></h3>
|
|
|
|
<p>To finish things off, we need to iterate so that we can fill in the rest of the
|
|
values in progressively smaller squares.</p>
|
|
|
|
<p>A 3x3 square is the base case, because its corners will be filled in to start,
|
|
and the edges and midpoint get filled in. We just call our displacement
|
|
function with the top/bottom/left/right values defining the square and we're
|
|
done.</p>
|
|
|
|
<p>A 5x5 starts with one iteration that displaces the whole map. Then it does
|
|
another "pass" that displaces each of the 3x3 "sub-squares":</p>
|
|
|
|
<pre class="lineart">
|
|
╔═══════════╗───┬───┐ ┌───┬───╔═══════════╗
|
|
║ 2 │ │ 5 ║ │ 8 │ │ 2 │ ║ 5 │ │ 8 ║
|
|
║───┼───┼───║───┼───┤ ├───┼───║───┼───┼───║
|
|
║ │ │ ║ │ │ │ │ ║ │ │ ║
|
|
║───┼───┼───║───┼───┤ ├───┼───║───┼───┼───║
|
|
║ 1 │ │3.3║ │5.5│ │ 1 │ ║3.3│ │5.5║
|
|
╚═══════════╝───┼───┤ ├───┼───╚═══════════╝
|
|
│ │ │ │ │ │ │ │ │ │ │ │
|
|
├───┼───┼───┼───┼───┤ ├───┼───┼───┼───┼───┤
|
|
│ 0 │ │1.5│ │ 3 │ │ 0 │ │1.5│ │ 3 │
|
|
└───┴───┴───┴───┴───┘ └───┴───┴───┴───┴───┘
|
|
|
|
|
|
┌───┬───┬───┬───┬───┐ ┌───┬───┬───┬───┬───┐
|
|
│ 2 │ │ 5 │ │ 8 │ │ 2 │ │ 5 │ │ 8 │
|
|
├───┼───┼───┼───┼───┤ ├───┼───┼───┼───┼───┤
|
|
│ │ │ │ │ │ │ │ │ │ │ │
|
|
╔═══════════╗───┼───┤ ├───┼───╔═══════════╗
|
|
║ 1 │ │3.3║ │5.5│ │ 1 │ ║3.3│ │5.5║
|
|
║───┼───┼───║───┼───┤ ├───┼───║───┼───┼───║
|
|
║ │ │ ║ │ │ │ │ ║ │ │ ║
|
|
║───┼───┼───║───┼───┤ ├───┼───║───┼───┼───║
|
|
║ 0 │ │1.5║ │ 3 │ │ 0 │ ║1.5│ │ 3 ║
|
|
╚═══════════╝───┴───┘ └───┴───╚═══════════╝
|
|
</pre>
|
|
|
|
<p>If we have a bigger heightmap (e.g. 9x9) we'll need three passes:</p>
|
|
|
|
<pre class="lineart">
|
|
Pass 1
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
▩▩▩▩▩▩▩▩▩
|
|
|
|
|
|
Pass 2
|
|
▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩ ▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▩▩▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▩▩
|
|
|
|
|
|
Pass 3
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢ ▢▢▢▢▢▢▢▢▢
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
▩▩▩▢▢▢▢▢▢ ▢▢▩▩▩▢▢▢▢ ▢▢▢▢▩▩▩▢▢ ▢▢▢▢▢▢▩▩▩
|
|
</pre>
|
|
|
|
<p>Let's think about what's happening here.</p>
|
|
|
|
<ul>
|
|
<li>On pass <code>0</code>, we break up the grid into a 1x1 set of chunks and displace them (well, "it").</li>
|
|
<li>On pass <code>1</code>, we break up the grid into a 2x2 set of chunks and displace them.</li>
|
|
<li>On pass <code>2</code>, we break up the grid into a 4x4 set of chunks and displace them.</li>
|
|
</ul>
|
|
|
|
<p>This 9x9 heightmap was made with an exponent of 3 (\(2^3 + 1 = 9\)) and we
|
|
need 3 "passes" to finish it. This isn't a coincidence — it's the reason we
|
|
chose our heightmap resolutions to be \(2^n + 1\).</p>
|
|
|
|
<p>So at iteration \(i\), we need to displace a \(2^i\)x\(2^i\) collection of
|
|
squares. Let's go:</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">mpd-init-corners heightmap</span>)</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren3">[<span class="code">iter 0
|
|
spread 0.3</span>]</span>
|
|
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">< iter heightmap.exponent</span>)</span>
|
|
<span class="paren4">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren5">[<span class="code">chunks <span class="paren6">(<span class="code">Math.pow 2 iter</span>)</span>
|
|
chunk-width <span class="paren6">(<span class="code">/ <span class="paren1">(<span class="code">- heightmap.resolution 1</span>)</span> chunks</span>)</span></span>]</span>
|
|
<span class="paren5">(<span class="code">do-nested xchunk ychunk chunks
|
|
<span class="paren6">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren1">[<span class="code">left-x <span class="paren2">(<span class="code">* chunk-width xchunk</span>)</span>
|
|
right-x <span class="paren2">(<span class="code">+ left-x chunk-width</span>)</span>
|
|
bottom-y <span class="paren2">(<span class="code">* chunk-width ychunk</span>)</span>
|
|
top-y <span class="paren2">(<span class="code">+ bottom-y chunk-width</span>)</span></span>]</span>
|
|
<span class="paren1">(<span class="code">mpd-displace heightmap
|
|
left-x right-x bottom-y top-y
|
|
spread</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
<span class="paren4">(<span class="code">recur <span class="paren5">(<span class="code">+ 1 iter</span>)</span> <span class="paren5">(<span class="code">* spread 0.5</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
<span class="paren2">(<span class="code">normalize heightmap</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>That's a lot to take in. Let's break it down:</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">mpd-init-corners heightmap</span>)</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">loop</span></i> <span class="paren3">[<span class="code">iter 0
|
|
spread 0.3</span>]</span>
|
|
<span class="paren3">(<span class="code">when <span class="paren4">(<span class="code">< iter heightmap.exponent</span>)</span>
|
|
<span class="comment">; Process the chunks at this iteration
|
|
</span> <span class="paren4">(<span class="code">recur <span class="paren5">(<span class="code">+ 1 iter</span>)</span> <span class="paren5">(<span class="code">* spread 0.5</span>)</span></span>)</span></span>)</span></span>)</span>
|
|
<span class="paren2">(<span class="code">normalize heightmap</span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>We initialize the corners at the beginning and normalize the heightmap at the
|
|
end. Then we loop a number of times equal to the exponent we used to make the
|
|
heightmap, as described above. Each time through the loop we increment <code>iter</code>
|
|
and cut the <code>spread</code> for those points in half.</p>
|
|
|
|
<p>For each pass, we figure out what we're going to need to do:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren2">[<span class="code">chunks <span class="paren3">(<span class="code">Math.pow 2 iter</span>)</span>
|
|
chunk-width <span class="paren3">(<span class="code">/ <span class="paren4">(<span class="code">- heightmap.resolution 1</span>)</span> chunks</span>)</span></span>]</span>
|
|
<span class="comment">; Actually do it...
|
|
</span> </span>)</span></span></code></pre>
|
|
|
|
<p>Let's make a quick "nested for loop" syntax. It'll make things easier to read
|
|
in the main function and we'll need it for later algorithms too:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">defmacro</span></i> do-nested <span class="paren2">[<span class="code">xname yname width & body</span>]</span>
|
|
<span class="paren2">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren3">[<span class="code">iterations <span class="paren4">(<span class="code">gensym</span>)</span></span>]</span>
|
|
`<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">~iterations ~width</span>]</span>
|
|
<span class="paren4">(<span class="code">do-times ~xname ~iterations
|
|
<span class="paren5">(<span class="code">do-times ~yname ~iterations
|
|
~@body</span>)</span></span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>This lets us say something like <code>(do-nested x y 5 (console.log [x y]))</code> to mean:</p>
|
|
|
|
<pre><code>for (x = 0; x < 5; x++) {
|
|
for (y = 0; y < 5; y++) {
|
|
console.log([x, y]);
|
|
}
|
|
}</code></pre>
|
|
|
|
<p>And now we can see how we process the chunks:</p>
|
|
|
|
<pre><code><span class="code"><span class="paren1">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren2">[<span class="code">chunks <span class="paren3">(<span class="code">Math.pow 2 iter</span>)</span>
|
|
chunk-width <span class="paren3">(<span class="code">/ <span class="paren4">(<span class="code">- heightmap.resolution 1</span>)</span> chunks</span>)</span></span>]</span>
|
|
<span class="paren2">(<span class="code">do-nested xchunk ychunk chunks
|
|
<span class="paren3">(<span class="code"><i><span class="symbol">let</span></i> <span class="paren4">[<span class="code">left-x <span class="paren5">(<span class="code">* chunk-width xchunk</span>)</span>
|
|
right-x <span class="paren5">(<span class="code">+ left-x chunk-width</span>)</span>
|
|
bottom-y <span class="paren5">(<span class="code">* chunk-width ychunk</span>)</span>
|
|
top-y <span class="paren5">(<span class="code">+ bottom-y chunk-width</span>)</span></span>]</span>
|
|
<span class="paren4">(<span class="code">mpd-displace heightmap
|
|
left-x right-x bottom-y top-y
|
|
spread</span>)</span></span>)</span></span>)</span></span>)</span></span></code></pre>
|
|
|
|
<p>We loop over the indices of the chunks, calculate the bounds for each one, and
|
|
displace it. Simple enough, but a bit fiddly to get right. And now we've
|
|
finished displacing all the points!</p>
|
|
|
|
<p>We could have done this recursively, but I was in an iterative mood when I wrote
|
|
this. Feel free to play around with it.</p>
|
|
|
|
<div id="demo-mpd-4" class="threejs"></div>
|
|
|
|
<h2 id="s14-result"><a href="index.html#s14-result">Result</a></h2>
|
|
|
|
<p>Now that we've got a working algorithm we can play around with the values and
|
|
see what effect they have on the resulting terrain.</p>
|
|
|
|
<p><strong>Warning</strong>: don't set the exponent higher than 8 unless you want to crash this
|
|
browser tab. An exponent of <code>8</code> results in a heightmap with <code>66049</code> elements.
|
|
Going to <code>9</code> is <code>263169</code>, which most browsers seem to... dislike.</p>
|
|
|
|
<div id="demo-final" class="threejs"></div>
|
|
|
|
<div id="demo-final-controls">
|
|
<p>
|
|
<label>Exponent
|
|
<input type="number" id="input-exponent" value="5"><input>
|
|
</label>
|
|
<br>
|
|
<label>Starting Spread
|
|
<input type="number" id="input-starting-spread" value="0.3"><input>
|
|
</label>
|
|
<br>
|
|
<label>Spread Reduction Constant
|
|
<input type="number" id="input-spread-reduction" value="0.5"><input>
|
|
</label>
|
|
</p>
|
|
</div>
|
|
|
|
<p>And that's it, we've grown some mountains!</p>
|
|
|
|
<p>Now that we've got some of the boilerplate down I'm planning on doing a couple
|
|
more posts like this looking at other noise algorithms, and maybe some erosion
|
|
algorithms to simulate weathering. Let me know if you've got any specific
|
|
requests!</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> |