<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://www.wedesoft.de/atom.xml" rel="self" type="application/atom+xml" /><link href="https://www.wedesoft.de/" rel="alternate" type="text/html" /><updated>2026-03-30T22:19:37+01:00</updated><id>https://www.wedesoft.de/atom.xml</id><title type="html">Jan Wedekind</title><subtitle>Software engineering, game development, simulation.</subtitle><author><name>Jan Wedekind</name></author><entry><title type="html">Installing Pytorch with AMD ROCm on GNU/Linux</title><link href="https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install/" rel="alternate" type="text/html" title="Installing Pytorch with AMD ROCm on GNU/Linux" /><published>2026-03-24T00:00:00+00:00</published><updated>2026-03-24T00:00:00+00:00</updated><id>https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install/"><![CDATA[<p>Quickly sharing my notes on how to install drivers for ROCm and Pytorch for machine learning on AMD GPUs:</p>

<p>First <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html">install ROCm and the AMD GPU driver</a>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># Install ROCm</span>
wget https://repo.radeon.com/amdgpu-install/7.2.1/ubuntu/noble/amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt <span class="nb">install</span> ./amdgpu-install_7.2.1.70201-1_all.deb
<span class="c"># Install AMD driver</span>
wget https://repo.radeon.com/amdgpu-install/7.2.1/ubuntu/noble/amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt <span class="nb">install</span> ./amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="s2">"linux-headers-</span><span class="si">$(</span><span class="nb">uname</span> <span class="nt">-r</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>amdgpu-dkms</code></pre></figure>

<p>Then as shown in this <a href="https://www.reddit.com/r/comfyui/comments/1njjp79/complete_rocm_70_pytorch_280_installation_guide/">Reddit post</a>, I installed install triton, torch, torchvision, and torchaudio from <a href="https://repo.radeon.com/rocm/manylinux/">https://repo.radeon.com/rocm/manylinux/</a>.</p>

<p>Then I tried the following program to train a neural network to imitate an XOR gate.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">torch</span>
<span class="kn">import</span> <span class="n">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>
<span class="kn">import</span> <span class="n">torch.optim</span> <span class="k">as</span> <span class="n">optim</span>

<span class="c1"># Check if GPU is available
</span><span class="n">device</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">device</span><span class="p">(</span><span class="sh">"</span><span class="s">cuda</span><span class="sh">"</span> <span class="k">if</span> <span class="n">torch</span><span class="p">.</span><span class="n">cuda</span><span class="p">.</span><span class="nf">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="sh">"</span><span class="s">cpu</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># XOR data
</span><span class="n">X</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">tensor</span><span class="p">([[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="n">Y</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">tensor</span><span class="p">([[</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">]],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>

<span class="c1"># Define the neural network
</span><span class="k">class</span> <span class="nc">XORNet</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">(</span><span class="n">XORNet</span><span class="p">,</span> <span class="n">self</span><span class="p">).</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">sigmoid</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Sigmoid</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">relu</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">fc1</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">sigmoid</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">fc2</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">x</span>

<span class="c1"># Initialize the network, loss function and optimizer
</span><span class="n">model</span> <span class="o">=</span> <span class="nc">XORNet</span><span class="p">().</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="n">criterion</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">BCELoss</span><span class="p">()</span>
<span class="n">optimizer</span> <span class="o">=</span> <span class="n">optim</span><span class="p">.</span><span class="nc">SGD</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="nf">parameters</span><span class="p">(),</span> <span class="n">lr</span><span class="o">=</span><span class="mf">0.1</span><span class="p">)</span>

<span class="c1"># Training loop
</span><span class="k">for</span> <span class="n">epoch</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">):</span>
    <span class="n">model</span><span class="p">.</span><span class="nf">train</span><span class="p">()</span>
    <span class="n">optimizer</span><span class="p">.</span><span class="nf">zero_grad</span><span class="p">()</span>
    <span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span><span class="n">X</span><span class="p">)</span>
    <span class="n">loss</span> <span class="o">=</span> <span class="nf">criterion</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">Y</span><span class="p">)</span>
    <span class="n">loss</span><span class="p">.</span><span class="nf">backward</span><span class="p">()</span>
    <span class="n">optimizer</span><span class="p">.</span><span class="nf">step</span><span class="p">()</span>

    <span class="nf">if </span><span class="p">(</span><span class="n">epoch</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="mi">1000</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Epoch [</span><span class="si">{</span><span class="n">epoch</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">/10000], Loss: </span><span class="si">{</span><span class="n">loss</span><span class="p">.</span><span class="nf">item</span><span class="p">()</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Test the model
</span><span class="n">model</span><span class="p">.</span><span class="nf">eval</span><span class="p">()</span>
<span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">no_grad</span><span class="p">():</span>
    <span class="n">predictions</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span><span class="n">X</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Predictions:</span><span class="sh">"</span><span class="p">,</span> <span class="n">predictions</span><span class="p">.</span><span class="nf">round</span><span class="p">())</span></code></pre></figure>

<p>However I got the following error (using Torch 2.9.1 and ROCm 7.2.0).</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">RuntimeError: CUDA error: HIPBLAS_STATUS_INVALID_VALUE when calling <span class="sb">`</span>hipblasLtMatmulAlgoGetHeuristic<span class="o">(</span> ltHandle, computeDesc.descriptor<span class="o">()</span>, Adesc.descriptor<span class="o">()</span>, Bdesc.descriptor<span class="o">()</span>, Cdesc.descriptor<span class="o">()</span>, Cdesc.descriptor<span class="o">()</span>, preference.descriptor<span class="o">()</span>, 1, &amp;heuristicResult, &amp;returnedResult<span class="o">)</span><span class="sb">`</span></code></pre></figure>

<p>Then I found AMD’s information on <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/3rd-party/pytorch-install.html#use-a-wheels-package">how to install Pytorch with ROCm support</a>.
Basically you need to install the nightly build:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">pip3 <span class="nb">install</span> <span class="nt">--pre</span> torch torchvision torchaudio <span class="nt">--index-url</span> https://download.pytorch.org/whl/nightly/rocm7.2</code></pre></figure>

<p>Now the XOR test works!</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">python3 xor.py
<span class="c"># Epoch [1000/10000], Loss: 0.0342</span>
<span class="c"># Epoch [2000/10000], Loss: 0.0114</span>
<span class="c"># Epoch [3000/10000], Loss: 0.0066</span>
<span class="c"># Epoch [4000/10000], Loss: 0.0046</span>
<span class="c"># Epoch [5000/10000], Loss: 0.0035</span>
<span class="c"># Epoch [6000/10000], Loss: 0.0028</span>
<span class="c"># Epoch [7000/10000], Loss: 0.0024</span>
<span class="c"># Epoch [8000/10000], Loss: 0.0020</span>
<span class="c"># Epoch [9000/10000], Loss: 0.0018</span>
<span class="c"># Epoch [10000/10000], Loss: 0.0016</span>
<span class="c"># Predictions: tensor([[0.],</span>
<span class="c">#         [1.],</span>
<span class="c">#         [1.],</span>
<span class="c">#         [0.]], device='cuda:0')</span></code></pre></figure>

<p>Enjoy!</p>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Quickly sharing my notes on how to install drivers for ROCm and Pytorch for machine learning on AMD GPUs:]]></summary></entry><entry><title type="html">Volumetric Clouds with Clojure and LWJGL</title><link href="https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds/" rel="alternate" type="text/html" title="Volumetric Clouds with Clojure and LWJGL" /><published>2026-01-27T00:00:00+00:00</published><updated>2026-01-27T00:00:00+00:00</updated><id>https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds/"><![CDATA[<p>Procedural generation of volumetric clouds using different types of noise</p>

<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/volumetric_clouds/main.html">Clojure Civitas</a>)</em></p>

<h2 id="dependencies">Dependencies</h2>

<p>To download the required libraries, we use a <code class="language-plaintext highlighter-rouge">deps.edn</code> file with the following content:
Replace the <em>natives-linux</em> classifier with <em>natives-macos</em> or <em>natives-windows</em> as required.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w">
 </span><span class="p">{</span><span class="w">
  </span><span class="n">org.clojure/clojure</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.12.3"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.scicloj/noj</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"2-beta18"</span><span class="p">}</span><span class="w">
  </span><span class="n">midje/midje</span><span class="w">                          </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.10.10"</span><span class="p">}</span><span class="w">
  </span><span class="n">generateme/fastmath</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.0.0-alpha4"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl$natives-linux</span><span class="w">        </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-opengl</span><span class="w">               </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-glfw</span><span class="w">                 </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w">   </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">comb/comb</span><span class="w">                            </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.0.0"</span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>We are going to import the following methods and namespaces:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.math</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">PI</span><span class="w"> </span><span class="n">sqrt</span><span class="w"> </span><span class="n">cos</span><span class="w"> </span><span class="n">sin</span><span class="w"> </span><span class="n">tan</span><span class="w"> </span><span class="n">to-radians</span><span class="w"> </span><span class="n">pow</span><span class="w"> </span><span class="n">floor</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">midje.sweet</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="n">facts</span><span class="w"> </span><span class="n">tabular</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">roughly</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.vector</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">vec3</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">mult</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">div</span><span class="w"> </span><span class="n">mag</span><span class="w"> </span><span class="n">dot</span><span class="w"> </span><span class="n">normalize</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.matrix</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">mat-&gt;float-array</span><span class="w"> </span><span class="n">mulm</span><span class="w">
                                   </span><span class="n">rotation-matrix-3d-x</span><span class="w"> </span><span class="n">rotation-matrix-3d-y</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dtype</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.tensor</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tensor</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype.functional</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dfn</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tablecloth.api</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tc</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">scicloj.tableplot.v1.plotly</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">plotly</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.libs.buffered-image</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">bufimg</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">comb.template</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">template</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nb">import</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL11</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl</span><span class="w"> </span><span class="n">BufferUtils</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.glfw</span><span class="w"> </span><span class="n">GLFW</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL</span><span class="w"> </span><span class="n">GL11</span><span class="w"> </span><span class="n">GL12</span><span class="w"> </span><span class="n">GL13</span><span class="w"> </span><span class="n">GL15</span><span class="w"> </span><span class="n">GL20</span><span class="w"> </span><span class="n">GL30</span><span class="w"> </span><span class="n">GL32</span><span class="w"> </span><span class="n">GL42</span><span class="p">])</span></code></pre></figure>

<h2 id="worley-noise">Worley noise</h2>

<p><a href="https://en.wikipedia.org/wiki/Worley_noise">Worley noise</a> is a type of structured noise which is defined for each pixel using the distance to the nearest seed point.</p>

<h3 id="noise-parameters">Noise parameters</h3>

<p>First we define a function to create parameters of the noise.</p>

<ul>
  <li><strong>size</strong> is the size of each dimension of the noise array</li>
  <li><strong>divisions</strong> is the number of subdividing cells in each dimension</li>
  <li><strong>dimensions</strong> is the number of dimensions</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-noise-params</span><span class="w">
  </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w">
  </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="no">:divisions</span><span class="w"> </span><span class="n">divisions</span><span class="w"> </span><span class="no">:cellsize</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">})</span></code></pre></figure>

<p>Here is a corresponding <a href="https://github.com/marick/Midje">Midje</a> test.
Note that ideally you practise <a href="https://martinfowler.com/bliki/TestDrivenDevelopment.html">Test Driven Development (TDD)</a>, i.e. you start with writing one failing test.
Because this is a Clojure notebook, the unit tests are displayed after the implementation.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="s">"Noise parameter initialisation"</span><span class="w">
      </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">})</span></code></pre></figure>

<h3 id="2d-and-3d-vectors">2D and 3D vectors</h3>

<p>Next we need a function which allows us to create 2D or 3D vectors depending on the number of input parameters.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">vec-n</span><span class="w">
  </span><span class="p">([</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
  </span><span class="p">([</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Generic vector function for creating 2D and 3D vectors"</span><span class="w">
       </span><span class="p">(</span><span class="nf">vec-n</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">vec-n</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span></code></pre></figure>

<h3 id="random-points">Random points</h3>

<p>The following method generates a random point in a cell specified by the cell indices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-point-in-cell</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-seq</span><span class="w"> </span><span class="p">(</span><span class="nf">repeatedly</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))</span><span class="w">
        </span><span class="n">dimensions</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">indices</span><span class="p">))</span><span class="w"> </span><span class="n">cellsize</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">random-seq</span><span class="p">)))))</span></code></pre></figure>

<p>We test the method by replacing the random function with a deterministic function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Place random point in a cell"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">s</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">1</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">7.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">11.0</span><span class="w"> </span><span class="mf">7.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)))</span></code></pre></figure>

<p>We can now use the <code class="language-plaintext highlighter-rouge">random-point</code> method to generate a grid of random points.
The grid is represented using a tensor from the <a href="https://cnuernber.github.io/dtype-next/">dtype-next</a> library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-points</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w">
                           </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">random-point-in-cell</span><span class="w"> </span><span class="n">params</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Greate grid of random points"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">params-2d</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
             </span><span class="n">params-3d</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">3</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">s</span><span class="p">))]</span><span class="w">
           </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">14.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-3d</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-3d</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">22.0</span><span class="w"> </span><span class="mf">14.0</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))))</span></code></pre></figure>

<p>Here is a scatter plot showing one random point placed in each cell.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">points</span><span class="w">  </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">scatter</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="n">points</span><span class="p">)</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">second</span><span class="w"> </span><span class="n">points</span><span class="p">)})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Random points"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})))</span></code></pre></figure>

<p><img src="/pics/randompoints.png" alt="random points" /></p>

<h3 id="modular-distance">Modular distance</h3>

<p>In order to get a periodic noise array, we need to component-wise wrap around distance vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">mod-vec</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="p">]}</span><span class="w"> </span><span class="n">v</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">size2</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="n">wrap</span><span class="w">  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">size2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">size2</span><span class="p">)))]</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">wrap</span><span class="w"> </span><span class="n">v</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Wrap around components of vector to be within -size/2..size/2"</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">-3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">-5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span></code></pre></figure>

<p>Using the <code class="language-plaintext highlighter-rouge">mod-dist</code> function we can calculate the distance between two points in the periodic noise array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">mod-dist</span><span class="w">
  </span><span class="p">[</span><span class="n">params</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">))))</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">tabular</code> macro implemented by Midje is useful for running parametrized tests.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Wrapped distance of two points"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nf">mod-dist</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">?ax</span><span class="w"> </span><span class="n">?ay</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">?bx</span><span class="w"> </span><span class="n">?by</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?ax</span><span class="w"> </span><span class="n">?ay</span><span class="w"> </span><span class="n">?bx</span><span class="w"> </span><span class="n">?by</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="p">)</span></code></pre></figure>

<h3 id="modular-lookup">Modular lookup</h3>

<p>We also need to lookup elements with wrap around.
We recursively use <code class="language-plaintext highlighter-rouge">tensor/select</code> and then finally the tensor as a function to lookup along each axis.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">wrap-get</span><span class="w">
  </span><span class="p">[</span><span class="n">t</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">tensor/select</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">mod</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">mod</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">)))))</span></code></pre></figure>

<p>A tensor with index vectors is used to test the lookup.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Wrapped lookup of tensor values"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="n">vec2</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="p">)))</span></code></pre></figure>

<p>The following function converts a noise coordinate to the index of a cell in the random point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">division-index</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nf">floor</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Convert coordinate to division index"</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w">  </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">7.5</span><span class="p">)</span><span class="w">  </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">1</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">-1</span><span class="p">)</span></code></pre></figure>

<h3 id="getting-indices-of-neighbours">Getting indices of Neighbours</h3>

<p>The following function determines the neighbouring indices of a cell recursing over each dimension.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">neighbours</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">v</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">delta</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">into</span><span class="w"> </span><span class="p">[(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="n">delta</span><span class="p">)]</span><span class="w"> </span><span class="n">v</span><span class="p">))</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">]))</span><span class="w">
            </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">neighbours</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w"> </span><span class="p">)</span><span class="w">
    </span><span class="p">[[]]))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Get neighbouring indices"</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">-1</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="p">]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">0</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">11</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">11</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">11</span><span class="p">]])</span></code></pre></figure>

<h3 id="sampling-worley-noise">Sampling Worley noise</h3>

<p>Using above functions one can now implement Worley noise.
For each pixel the distance to the closest seed point is calculated.
This is achieved by determining the distance to each random point in all neighbouring cells and then taking the minimum.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">worley-noise</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-points</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
        </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">coords</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">center</span><span class="w">   </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">coords</span><span class="p">)</span><span class="w">
                  </span><span class="n">division</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">division-index</span><span class="w"> </span><span class="n">params</span><span class="p">)</span><span class="w"> </span><span class="n">center</span><span class="p">)]</span><span class="w">
              </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">min</span><span class="w">
                     </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">neighbour</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">neighbours</span><span class="w"> </span><span class="n">division</span><span class="p">)]</span><span class="w">
                          </span><span class="p">(</span><span class="nf">mod-dist</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">center</span><span class="p">))</span><span class="w">
                                    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">wrap-get</span><span class="w"> </span><span class="n">random-points</span><span class="w"> </span><span class="n">neighbour</span><span class="p">))))))</span><span class="w">
        </span><span class="no">:double</span><span class="p">))))</span></code></pre></figure>

<p>Here a 256 × 256 Worley noise tensor is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">worley</span><span class="w"> </span><span class="p">(</span><span class="nf">worley-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span></code></pre></figure>

<p>The values are inverted and normalised to be between 0 and 255.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">worley-norm</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">worley</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">worley</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">worley</span><span class="p">)</span><span class="w"> </span><span class="n">worley</span><span class="p">)))</span></code></pre></figure>

<p>Finally one can display the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/worley256.png" alt="Worley noise" /></p>

<h2 id="perlin-noise">Perlin noise</h2>

<p><a href="https://adrianb.io/2014/08/09/perlinnoise.html">Perlin noise</a> is generated by choosing a random gradient vector at each cell corner.
The noise tensor’s intermediate values are interpolated with a continuous function, utilizing the gradient at the corner points.</p>

<h3 id="random-gradients">Random gradients</h3>

<p>The 2D or 3D gradients are generated by creating a vector where each component is set to a random number between -1 and 1.
Random vectors are generated until the vector length is greater 0 and lower or equal to 1.
The vector then is normalized and returned.
Random vectors outside the unit circle or sphere are discarded in order to achieve a uniform distribution on the surface of the unit circle or sphere.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-gradient</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">loop</span><span class="w"> </span><span class="p">[</span><span class="n">args</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
        </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-vector</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w">
              </span><span class="n">vector-length</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="n">random-vector</span><span class="p">)]</span><span class="w">
          </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">and</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;</span><span class="w"> </span><span class="n">vector-length</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="n">vector-length</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w">
            </span><span class="p">(</span><span class="nf">div</span><span class="w"> </span><span class="n">random-vector</span><span class="w"> </span><span class="n">vector-length</span><span class="p">)</span><span class="w">
            </span><span class="p">(</span><span class="nf">recur</span><span class="w"> </span><span class="n">args</span><span class="p">)))))</span></code></pre></figure>

<p>The function below serves as a Midje checker for a vector with an approximate expected value.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">roughly-vec</span><span class="w">
  </span><span class="p">[</span><span class="n">expected</span><span class="w"> </span><span class="n">error</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">actual</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">actual</span><span class="w"> </span><span class="n">expected</span><span class="p">))</span><span class="w"> </span><span class="n">error</span><span class="p">)))</span></code></pre></figure>

<p>In the following tests, the random function is again replaced with a deterministic function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Create unit vector with random direction"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-gradient</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">))</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-gradient</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)))</span></code></pre></figure>

<p>The random gradient function is then used to generate a field of random gradients.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-gradients</span><span class="w">
 </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]}]</span><span class="w">
 </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w"> </span><span class="n">random-gradient</span><span class="p">)))</span></code></pre></figure>

<p>The function is verified to correctly generate 2D and 3D random gradient fields.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Random gradients"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}))</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">})</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">})</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">)))))</span></code></pre></figure>

<p>The gradient field can be plotted with Plotly as a scatter plot of disconnected lines.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
                                </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">points</span><span class="w">    </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
                                </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">scatter</span><span class="w">   </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">point</span><span class="w"> </span><span class="n">gradient</span><span class="p">]</span><span class="w">
                                            </span><span class="p">[(</span><span class="nf">point</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
                                             </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">point</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">(</span><span class="nf">gradient</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span><span class="w">
                                             </span><span class="n">nil</span><span class="p">])</span><span class="w">
                                        </span><span class="n">points</span><span class="w"> </span><span class="n">gradients</span><span class="p">)</span><span class="w">
                             </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">point</span><span class="w"> </span><span class="n">gradient</span><span class="p">]</span><span class="w">
                                            </span><span class="p">[(</span><span class="nf">point</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
                                             </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">point</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">(</span><span class="nf">gradient</span><span class="w"> </span><span class="mi">1</span><span class="p">)))</span><span class="w">
                                             </span><span class="n">nil</span><span class="p">])</span><span class="w">
                                        </span><span class="n">points</span><span class="w"> </span><span class="n">gradients</span><span class="p">)})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Random gradients"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="s">"lines"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})))</span></code></pre></figure>

<p><img src="/pics/randomgradients.png" alt="Random gradients" /></p>

<h3 id="corner-vectors">Corner vectors</h3>

<p>The next step is to determine the vectors to the corners of the cell for a given point.
First we define a function to determine the fractional part of a number.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">frac</span><span class="w">
  </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/floor</span><span class="w"> </span><span class="n">x</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Fractional part of floating point number"</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.25</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">1.75</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.75</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">-0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span></code></pre></figure>

<p>This function can be used to determine the relative position of a point in a cell.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">cell-pos</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">frac</span><span class="w"> </span><span class="p">(</span><span class="nf">div</span><span class="w"> </span><span class="n">point</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Relative position of point in a cell"</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span></code></pre></figure>

<p>A 2 × 2 tensor of corner vectors can be computed by subtracting the corner coordinates from the point coordinates.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">corner-vectors</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">cell-pos</span><span class="w"> </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
      </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">cell-pos</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">args</span><span class="p">)))))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Compute relative vectors from cell corners to point in cell"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">corners2</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w">
             </span><span class="n">corners3</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">5</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">-0.25</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">-0.25</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners3</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)))</span></code></pre></figure>

<h3 id="extract-gradients-of-cell-corners">Extract gradients of cell corners</h3>

<p>The function below retrieves the gradient values at a cell’s corners, utilizing <code class="language-plaintext highlighter-rouge">wrap-get</code> for modular access.
The result is a 2 × 2 tensor of gradient vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">corner-gradients</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">division</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">division-index</span><span class="w"> </span><span class="n">params</span><span class="p">)</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
      </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">coords</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">wrap-get</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">division</span><span class="p">)</span><span class="w"> </span><span class="n">coords</span><span class="p">))))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Get 2x2 tensor of gradients from a larger tensor using wrap around"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
             </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w"> </span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">23</span><span class="w"> </span><span class="mi">15</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w"> </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span></code></pre></figure>

<h3 id="influence-values">Influence values</h3>

<p>The influence value is the function value of the function with the selected random gradient at a corner.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">influence-values</span><span class="w">
  </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="n">vectors</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
    </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">gradients</span><span class="p">))</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">dot</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vectors</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
    </span><span class="no">:double</span><span class="p">))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Compute influence values from corner vectors and gradients"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mi">10</span><span class="p">)))</span><span class="w">
             </span><span class="n">vectors2</span><span class="w">   </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
             </span><span class="n">influence2</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="n">vectors2</span><span class="p">)</span><span class="w">
             </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w">
             </span><span class="n">vectors3</span><span class="w">   </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">100</span><span class="p">)))</span><span class="w">
             </span><span class="n">influence3</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients3</span><span class="w"> </span><span class="n">vectors3</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">10.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">11.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence3</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">111.0</span><span class="p">))</span></code></pre></figure>

<h3 id="interpolating-the-influence-values">Interpolating the influence values</h3>

<p>For interpolation the following “ease curve” is used.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">ease-curve</span><span class="w">
  </span><span class="p">[</span><span class="n">t</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">6.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">15.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">t</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">t</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Monotonously increasing function with zero derivative at zero and one"</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.103516</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.5</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.896484</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<p>The ease curve monotonously increases in the interval from zero to one.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:t</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.025</span><span class="w"> </span><span class="mf">0.025</span><span class="p">)</span><span class="w">
                 </span><span class="no">:ease</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">ease-curve</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.025</span><span class="w"> </span><span class="mf">0.025</span><span class="p">))})</span><span class="w">
    </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Ease Curve"</span><span class="p">})</span><span class="w">
    </span><span class="p">(</span><span class="nf">plotly/layer-line</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:ease</span><span class="p">}))</span></code></pre></figure>

<p><img src="/pics/easecurve.png" alt="Ease curve" /></p>

<p>The interpolation weights are recursively calculated from the ease curve and the coordinate distances of the point to upper and lower cell boundary.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">interpolation-weights</span><span class="w">
  </span><span class="p">([</span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)))</span><span class="w">
  </span><span class="p">([</span><span class="n">pos</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">pos</span><span class="p">)</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">w1</span><span class="w">   </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">pos</span><span class="p">))</span><span class="w">
           </span><span class="n">w2</span><span class="w">   </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">pos</span><span class="p">)</span><span class="w">
           </span><span class="n">elem</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">(</span><span class="nb">butlast</span><span class="w"> </span><span class="n">pos</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="n">w1</span><span class="p">)</span><span class="w"> </span><span class="n">elem</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="n">w2</span><span class="p">)</span><span class="w"> </span><span class="n">elem</span><span class="p">)]))</span><span class="w">
     </span><span class="mf">1.0</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Interpolation weights"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">weights2</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="p">))</span><span class="w">
             </span><span class="n">weights3</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">3</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.014391</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.001662</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.882094</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.101854</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights3</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.010430</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)))</span></code></pre></figure>

<h3 id="sampling-perlin-noise">Sampling Perlin noise</h3>

<p>A Perlin noise sample is computed by</p>
<ul>
  <li>Getting the random gradients for the cell corners.</li>
  <li>Getting the corner vectors for the cell corners.</li>
  <li>Computing the influence values which have the desired gradients.</li>
  <li>Determining the interpolation weights.</li>
  <li>Computing the weighted sum of the influence values.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">perlin-sample</span><span class="w">
  </span><span class="p">[</span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">)</span><span class="w">
        </span><span class="n">vectors</span><span class="w">   </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)</span><span class="w">
        </span><span class="n">influence</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">vectors</span><span class="p">)</span><span class="w">
        </span><span class="n">weights</span><span class="w">   </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">dfn/reduce-+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="n">weights</span><span class="w"> </span><span class="n">influence</span><span class="p">))))</span></code></pre></figure>

<p>Now one can sample the Perlin noise by performing above computation for the center of each pixel.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">perlin-noise</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="n">params</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
        </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">center</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">args</span><span class="p">)))]</span><span class="w">
              </span><span class="p">(</span><span class="nf">perlin-sample</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">center</span><span class="p">)))</span><span class="w">
        </span><span class="no">:double</span><span class="p">))))</span></code></pre></figure>

<p>Here a 256 × 256 Perlin noise tensor is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin</span><span class="w"> </span><span class="p">(</span><span class="nf">perlin-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span></code></pre></figure>

<p>The values are normalised to be between 0 and 255.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin-norm</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">perlin</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">perlin</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">perlin</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">perlin</span><span class="p">))))</span></code></pre></figure>

<p>Finally one can display the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/perlin256.png" alt="Perlin noise" /></p>

<h2 id="mixing-noise-values">Mixing noise values</h2>

<h3 id="combination-of-worley-and-perlin-noise">Combination of Worley and Perlin noise</h3>

<p>You can blend Worley and Perlin noise by performing a linear combination of both.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin-worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.3</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.7</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)))</span></code></pre></figure>

<p>Here for example is the average of Perlin and Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)))</span></code></pre></figure>

<p><img src="/pics/perlinworley256.png" alt="Worley and Perlin noise" /></p>

<h3 id="interpolation">Interpolation</h3>

<p>One can linearly interpolate tensor values by recursing over the dimensions as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">interpolate</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w">  </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
          </span><span class="n">xc</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
          </span><span class="n">xf</span><span class="w"> </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="n">xc</span><span class="p">)</span><span class="w">
          </span><span class="n">x0</span><span class="w"> </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/floor</span><span class="w"> </span><span class="n">xc</span><span class="p">))]</span><span class="w">
      </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="n">xf</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">tensor</span><span class="w">      </span><span class="n">x0</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nb">*</span><span class="w">        </span><span class="n">xf</span><span class="w">  </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">x0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">)))))</span><span class="w">
    </span><span class="n">tensor</span><span class="p">))</span></code></pre></figure>

<p>Here x-, y-,  and z-ramps are used to test that interpolation works.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Interpolate values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="n">x</span><span class="p">))</span><span class="w">
             </span><span class="n">y2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
             </span><span class="n">x3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="n">x</span><span class="p">))</span><span class="w">
             </span><span class="n">y3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
             </span><span class="n">z3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">z</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">4.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">5.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">5.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">3.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">z3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">5.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.0</span><span class="p">))</span></code></pre></figure>

<h3 id="octaves-of-noise">Octaves of noise</h3>

<p>Fractal Brownian Motion is implemented by computing a weighted sum of the same base noise function using different frequencies.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">fractal-brownian-motion</span><span class="w">
  </span><span class="p">[</span><span class="n">base</span><span class="w"> </span><span class="n">octaves</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">scales</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">octaves</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">iterate</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
            </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">amplitude</span><span class="w"> </span><span class="n">scale</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">amplitude</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">base</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="n">args</span><span class="p">))))</span><span class="w">
                 </span><span class="n">octaves</span><span class="w"> </span><span class="n">scales</span><span class="p">))))</span></code></pre></figure>

<p>Here the Fractal Brownian Motion is tested using an alternating 1D function and later a 2D checkboard function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Fractal Brownian motion"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">base1</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;=</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
             </span><span class="n">base2</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">=</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/round</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="mf">2.0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/round</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))</span><span class="w">
                               </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.5</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span></code></pre></figure>

<h3 id="remapping-and-clamping">Remapping and clamping</h3>

<p>The remap function is used to map a range of values of an input tensor to a different range.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">remap</span><span class="w">
  </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">high2</span><span class="w"> </span><span class="n">low2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low1</span><span class="p">)))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Remap values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">remap</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[</span><span class="n">?value</span><span class="p">])</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
             </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">1</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">1</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">4</span><span class="w">      </span><span class="mi">2</span><span class="p">)</span></code></pre></figure>

<p>The clamp function is used to element-wise clamp values to a range.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">clamp</span><span class="w">
  </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/max</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/min</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">high</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Clamp values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[</span><span class="n">?value</span><span class="p">])</span><span class="w"> </span><span class="n">?low</span><span class="w"> </span><span class="n">?high</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low</span><span class="w"> </span><span class="n">?high</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">4</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="p">)</span></code></pre></figure>

<h3 id="generating-octaves-of-noise">Generating octaves of noise</h3>

<p>The octaves function is used to create a series of decreasing weights and normalize them so that they add up to 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">octaves</span><span class="w">
  </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">decay</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">series</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="p">(</span><span class="nb">iterate</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">decay</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w">
        </span><span class="n">sum</span><span class="w">    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="n">series</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">sum</span><span class="p">)</span><span class="w"> </span><span class="n">series</span><span class="p">)))</span></code></pre></figure>

<p>Here is an example of noise weights decreasing by 50% at each octave.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
</span><span class="c1">; [0.5333333333333333</span><span class="w">
</span><span class="c1">;  0.26666666666666666</span><span class="w">
</span><span class="c1">;  0.13333333333333333</span><span class="w">
</span><span class="c1">;  0.06666666666666667]</span></code></pre></figure>

<p>Now a noise array can be generated using octaves of noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="w"> </span><span class="n">octaves</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
    </span><span class="p">(</span><span class="nf">clamp</span><span class="w">
      </span><span class="p">(</span><span class="nf">remap</span><span class="w">
        </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">tensor</span><span class="p">)</span><span class="w">
                               </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
                                   </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">fractal-brownian-motion</span><span class="w">
                                     </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="n">tensor</span><span class="p">)</span><span class="w">
                                     </span><span class="n">octaves</span><span class="w">
                                     </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
                               </span><span class="no">:double</span><span class="p">)</span><span class="w">
        </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)))</span></code></pre></figure>

<h3 id="2d-examples">2D examples</h3>

<p>Here is an example of 4 octaves of Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/worleyoctaves.png" alt="Octaves of Worley noise" /></p>

<p>Here is an example of 4 octaves of Perlin noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">perlin-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/perlinoctaves.png" alt="Octaves of Perlin noise" /></p>

<p>Here is an example of 4 octaves of mixed Perlin and Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">perlin-worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/perlinworleyoctaves.png" alt="Octaves of mixed Perlin and Worley noise" /></p>

<h2 id="opengl-rendering">OpenGL rendering</h2>

<h3 id="opengl-initialization">OpenGL initialization</h3>

<p>In order to render the clouds we create a window and an OpenGL context.
Note that we need to create an invisible window to get an OpenGL context, even though we are not going to draw to the window</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwInit</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="mi">640</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwDefaultWindowHints</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GLFW/glfwWindowHint</span><span class="w"> </span><span class="n">GLFW/GLFW_VISIBLE</span><span class="w"> </span><span class="n">GLFW/GLFW_FALSE</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window</span><span class="w"> </span><span class="p">(</span><span class="nf">GLFW/glfwCreateWindow</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="s">"Invisible Window"</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwMakeContextCurrent</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GL/createCapabilities</span><span class="p">)</span></code></pre></figure>

<h3 id="compiling-and-linking-shader-programs">Compiling and linking shader programs</h3>

<p>The following method is used to compile a shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-shader</span><span class="w"> </span><span class="p">[</span><span class="n">source</span><span class="w"> </span><span class="n">shader-type</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateShader</span><span class="w"> </span><span class="n">shader-type</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glShaderSource</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">source</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glCompileShader</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderi</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">GL20/GL_COMPILE_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderInfoLog</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">shader</span><span class="p">))</span></code></pre></figure>

<p>The different shaders are then linked to become a program using the following method.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateProgram</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glAttachShader</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glDeleteShader</span><span class="w"> </span><span class="n">shader</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glLinkProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgrami</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">GL20/GL_LINK_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgramInfoLog</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>This method is used to perform both compilation and linking of vertex shaders and fragment shaders.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program-with-shaders</span><span class="w">
  </span><span class="p">[</span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-shaders</span><span class="w">   </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">)</span><span class="w"> </span><span class="n">vertex-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">fragment-shaders</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">)</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">(</span><span class="nb">concat</span><span class="w"> </span><span class="n">vertex-shaders</span><span class="w"> </span><span class="n">fragment-shaders</span><span class="p">))]</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>In order to pass data to LWJGL methods, we need to be able to convert arrays to Java buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">def-make-buffer</span><span class="w"> </span><span class="p">[</span><span class="n">method</span><span class="w"> </span><span class="n">create-buffer</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="o">~</span><span class="n">method</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="o">#</span><span class="p">]</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="n">create-buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="n">buffer</span><span class="o">#</span><span class="p">)))</span></code></pre></figure>

<h3 id="setup-of-vertex-data">Setup of vertex data</h3>

<p>Above macro is used to define methods for creating float, int, and byte buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-float-buffer</span><span class="w"> </span><span class="n">BufferUtils/createFloatBuffer</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-int-buffer</span><span class="w"> </span><span class="n">BufferUtils/createIntBuffer</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-byte-buffer</span><span class="w"> </span><span class="n">BufferUtils/createByteBuffer</span><span class="p">)</span></code></pre></figure>

<p>We implement a method to create a vertex array object (VAO) with a vertex buffer object (VBO) and an index buffer object (IBO).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-vao</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenVertexArrays</span><span class="p">)</span><span class="w">
        </span><span class="n">vbo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)</span><span class="w">
        </span><span class="n">ibo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">vertices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="no">:vao</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="no">:vbo</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="no">:ibo</span><span class="w"> </span><span class="n">ibo</span><span class="p">}))</span></code></pre></figure>

<p>We also define the corresponding destructor for the vertex data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">teardown-vao</span><span class="w"> </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="n">ibo</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vao</span><span class="p">))</span></code></pre></figure>

<h3 id="offscreen-rendering-to-a-texture">Offscreen rendering to a texture</h3>

<p>The following method is used to create an empty 2D RGBA floating point texture</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-texture-2d</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL42/glTexStorage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">GL30/GL_RGBA32F</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
    </span><span class="n">texture</span><span class="p">))</span></code></pre></figure>

<p>We define a method to convert a Java buffer object to a floating point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">float-buffer-&gt;array</span><span class="w">
  </span><span class="s">"Convert float buffer to float array"</span><span class="w">
  </span><span class="p">[</span><span class="n">buffer</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">.limit</span><span class="w"> </span><span class="n">buffer</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.get</span><span class="w"> </span><span class="n">buffer</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="n">result</span><span class="p">))</span></code></pre></figure>

<p>The following method copies texture data into a Java buffer and then converts it to a floating point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">read-texture-2d</span><span class="w">
  </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">BufferUtils/createFloatBuffer</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="mi">4</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glGetTexImage</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL12/GL_RGBA</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">float-buffer-&gt;array</span><span class="w"> </span><span class="n">buffer</span><span class="p">)))</span></code></pre></figure>

<p>This method sets up rendering using a specified texture as a framebuffer and then executes the body.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">framebuffer-render</span><span class="w">
  </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fbo</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenFramebuffers</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL30/glBindFramebuffer</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="n">fbo</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="o">~</span><span class="n">texture</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL32/glFramebufferTexture</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="n">GL30/GL_COLOR_ATTACHMENT0</span><span class="w">
                                  </span><span class="o">~</span><span class="n">texture</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL20/glDrawBuffers</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w">
                             </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="n">GL30/GL_COLOR_ATTACHMENT0</span><span class="p">])))</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL11/glViewport</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)</span><span class="w">
       </span><span class="o">~@</span><span class="n">body</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL30/glBindFramebuffer</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL30/glDeleteFramebuffers</span><span class="w"> </span><span class="n">fbo</span><span class="o">#</span><span class="p">)))))</span></code></pre></figure>

<p>We also create a method to set up the layout of the vertex buffer.
Our vertex data is only going to contain 3D coordinates of points.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-point-attribute</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">point-attribute</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="n">point-attribute</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w">
                                </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="n">point-attribute</span><span class="p">)))</span></code></pre></figure>

<p>We are going to use a simple background quad to perform volumetric rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-quad-vao</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                               </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                                </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                               </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">])</span><span class="w">
        </span><span class="n">indices</span><span class="w">  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">])]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">)))</span></code></pre></figure>

<p>We now have all definitions ready to implement rendering of an image.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">render-array</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">make-texture-2d</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">framebuffer-render</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="w"> </span><span class="o">~@</span><span class="n">body</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">read-texture-2d</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="p">)))))</span></code></pre></figure>

<p>The following method creates a program and the quad VAO and sets up the memory layout.
The program and VAO are then used to render a single pixel.
Using this method we can write unit tests for OpenGL shaders!</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-pixel</span><span class="w">
  </span><span class="p">[</span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">     </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>We are going to use a simple vertex shader to simply pass through the points from the vertex buffer without any transformations.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-passthrough</span><span class="w">
</span><span class="s">"#version 130
in vec3 point;
void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>The following fragment shader is used to test rendering white pixels.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-test</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
void main()
{
  fragColor = vec4(1, 1, 1, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We can now render a single white RGBA pixel using the graphics card.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-test</span><span class="p">])</span><span class="w">
</span><span class="c1">; [1.0, 1.0, 1.0, 1.0]</span></code></pre></figure>

<h2 id="volumetric-clouds">Volumetric Clouds</h2>

<h3 id="mocks-and-probing-shaders">Mocks and probing shaders</h3>

<p>The following fragment shader creates a 3D checkboard pattern serving as a mock function below.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-mock</span><span class="w">
</span><span class="s">"#version 130
float noise(vec3 idx)
{
  ivec3 v = ivec3(floor(idx.x), floor(idx.y), floor(idx.z)) % 2;
  return ((v.x == 1) == (v.y == 1)) == (v.z == 1) ? 1.0 : 0.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>We can test this mock function using the following probing shader.
Note that we are using the <code class="language-plaintext highlighter-rouge">template</code> macro of the <code class="language-plaintext highlighter-rouge">comb</code> Clojure library to generate the probing shader code from a template.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float noise(vec3 idx);
void main()
{
  fragColor = vec4(noise(vec3(&lt;%= x %&gt;, &lt;%= y %&gt;, &lt;%= z %&gt;)));
}"</span><span class="p">))</span></code></pre></figure>

<p>Here multiple tests are run to test that the mock implements a checkboard pattern correctly.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test noise mock"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                  </span><span class="p">[</span><span class="n">noise-mock</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-probe</span><span class="w"> </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="p">)])</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<h3 id="octaves-of-noise-1">Octaves of noise</h3>

<p>We now implement a shader for 3D Fractal Brownian motion.
Note that we can use the template macro to generate code for an arbitrary number of octaves.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">octaves</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float noise(vec3 idx);
float octaves(vec3 idx)
{
  float result = 0.0;
&lt;% (doseq [multiplier octaves] %&gt;
  result += &lt;%= multiplier %&gt; * noise(idx);
  idx *= 2.0;
&lt;%= ) %&gt;
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>Again we use a probing shader to test the shader function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">octaves-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float octaves(vec3 idx);
void main()
{
  fragColor = vec4(octaves(vec3(&lt;%= x %&gt;, &lt;%= y %&gt;, &lt;%= z %&gt;)));
}"</span><span class="p">))</span></code></pre></figure>

<p>A few unit tests with one or two octaves are sufficient to drive development of the shader function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test octaves of noise"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                    </span><span class="p">[</span><span class="n">noise-mock</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">?octaves</span><span class="p">)</span><span class="w">
                                     </span><span class="p">(</span><span class="nf">octaves-probe</span><span class="w"> </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?x</span><span class="w">  </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="w"> </span><span class="n">?octaves</span><span class="w">  </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w">     </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w">     </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="p">]</span><span class="w">     </span><span class="mf">0.5</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<h3 id="shader-for-intersecting-a-ray-with-a-box">Shader for intersecting a ray with a box</h3>

<p>The following shader implements intersection of a ray with an axis-aligned box.
The shader function returns the distance of the near and far intersection with the box.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ray-box</span><span class="w">
</span><span class="s">"#version 130
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction)
{
  vec3 inv_dir = 1.0 / direction;
  vec3 smin = (box_min - origin) * inv_dir;
  vec3 smax = (box_max - origin) * inv_dir;
  vec3 s1 = min(smin, smax);
  vec3 s2 = max(smin, smax);
  float s_near = max(max(s1.x, s1.y), s1.z);
  float s_far = min(min(s2.x, s2.y), s2.z);
  if (isinf(s_near) || isinf(s_far))
    return vec2(0.0, 0.0);
  else
    return vec2(max(s_near, 0.0), max(0.0, s_far));
}"</span><span class="p">)</span></code></pre></figure>

<p>The probing shader returns the near and far distance in the red and green channel of the fragment color.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ray-box-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">ox</span><span class="w"> </span><span class="n">oy</span><span class="w"> </span><span class="n">oz</span><span class="w"> </span><span class="n">dx</span><span class="w"> </span><span class="n">dy</span><span class="w"> </span><span class="n">dz</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
void main()
{
  vec3 box_min = vec3(-1, -1, -1);
  vec3 box_max = vec3(1, 1, 1);
  vec3 origin = vec3(&lt;%= ox %&gt;, &lt;%= oy %&gt;, &lt;%= oz %&gt;);
  vec3 direction = vec3(&lt;%= dx %&gt;, &lt;%= dy %&gt;, &lt;%= dz %&gt;);
  fragColor = vec4(ray_box(box_min, box_max, origin, direction), 0, 0);
}"</span><span class="p">))</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">ray-box</code> shader is tested with different ray origins and directions.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test intersection of ray with box"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">juxt</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="nb">second</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                              </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="p">(</span><span class="nf">ray-box-probe</span><span class="w"> </span><span class="n">?ox</span><span class="w"> </span><span class="n">?oy</span><span class="w"> </span><span class="n">?oz</span><span class="w"> </span><span class="n">?dx</span><span class="w"> </span><span class="n">?dy</span><span class="w"> </span><span class="n">?dz</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?ox</span><span class="w"> </span><span class="n">?oy</span><span class="w"> </span><span class="n">?oz</span><span class="w"> </span><span class="n">?dx</span><span class="w"> </span><span class="n">?dy</span><span class="w"> </span><span class="n">?dz</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">   </span><span class="mi">2</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">])</span></code></pre></figure>

<h3 id="shader-for-light-transfer-through-clouds">Shader for light transfer through clouds</h3>

<p>We test the light transfer through clouds using constant density fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fog</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">v</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
float fog(vec3 idx)
{
  return &lt;%= v %&gt;;
}"</span><span class="p">))</span></code></pre></figure>

<p>Volumetric rendering involves sampling cloud density along a ray and multiplying the transmittance values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-transfer</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">noise</span><span class="w"> </span><span class="n">step</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
#define STEP &lt;%= step %&gt;
float &lt;%= noise %&gt;(vec3 idx);
float in_scatter(vec3 point, vec3 direction);
float shadow(vec3 point);
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval)
{
  vec4 result = vec4(0, 0, 0, 0);
  for (float t = interval.x + 0.5 * STEP; t &lt; interval.y; t += STEP) {
    vec3 point = origin + direction * t;
    float density = &lt;%= noise %&gt;(point);
    float transmittance = exp(-density * STEP);
    vec3 color = vec3(in_scatter(point, direction) * shadow(point));
    result.rgb += color * (1.0 - result.a) * (1.0 - transmittance);
    result.a = 1.0 - (1.0 - result.a) * transmittance;
  };
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>For now we also assume isotropic scattering of light in all directions.
This is a placeholder for introducing Mie scattering later.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w">
</span><span class="s">"#version 130
float in_scatter(vec3 point, vec3 direction)
{
  return 1.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>Finally we assume that there is no shadow.
This is a placeholder for introducing cloud shadows later.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">no-shadow</span><span class="w">
</span><span class="s">"#version 130
float shadow(vec3 point)
{
  return 1.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>We can now test the color and opacity of the cloud using the following probing shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-transfer-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
void main()
{
  vec3 origin = vec3(0, 0, 0);
  vec3 direction = vec3(1, 0, 0);
  vec2 interval = vec2(&lt;%= a %&gt;, &lt;%= b %&gt;);
  fragColor = cloud_transfer(origin, direction, interval);
}"</span><span class="p">))</span></code></pre></figure>

<p>We also introduce a Midje checker for requiring a vector to have an approximate value.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">roughly-vector</span><span class="w">
  </span><span class="p">[</span><span class="n">expected</span><span class="w"> </span><span class="n">error</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">actual</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nb">and</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">expected</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">actual</span><span class="p">))</span><span class="w">
           </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">)))</span><span class="w"> </span><span class="n">actual</span><span class="w"> </span><span class="n">expected</span><span class="p">))</span><span class="w">
               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">error</span><span class="p">)))))</span></code></pre></figure>

<p>A few tests are performed to check that there is opacity and that the step size does not affect the result in constant fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test cloud transfer"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                  </span><span class="p">[(</span><span class="nf">fog</span><span class="w"> </span><span class="n">?density</span><span class="p">)</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"fog"</span><span class="w"> </span><span class="n">?step</span><span class="p">)</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">cloud-transfer-probe</span><span class="w"> </span><span class="n">?a</span><span class="w"> </span><span class="n">?b</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vector</span><span class="w"> </span><span class="n">?result</span><span class="w"> </span><span class="mi">1</span><span class="n">e-3</span><span class="p">))</span><span class="w">
         </span><span class="n">?a</span><span class="w"> </span><span class="n">?b</span><span class="w"> </span><span class="n">?step</span><span class="w"> </span><span class="n">?density</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">     </span><span class="mf">0.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">     </span><span class="mf">1.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.5</span><span class="w">   </span><span class="mf">1.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.5</span><span class="w">   </span><span class="mf">0.5</span><span class="w">      </span><span class="p">[</span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="p">])</span></code></pre></figure>

<h3 id="rendering-of-fog-box">Rendering of fog box</h3>

<p>The following fragment shader is used to render an image of a box filled with fog.</p>

<ul>
  <li>The pixel coordinate and the resolution of the image are used to determine a viewing direction which also gets rotated using the rotation matrix and normalized.</li>
  <li>The origin of the camera is set at a specified distance to the center of the box and rotated as well.</li>
  <li>The ray box function is used to determine the near and far intersection points of the ray with the box.</li>
  <li>The cloud transfer function is used to sample the cloud density along the ray and determine the overall opacity and color of the fog box.</li>
  <li>The background is a mix of blue color and a small blob of white where the viewing direction points to the light source.</li>
  <li>The opacity value of the fog is used to overlay the fog color over the background.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="w">
</span><span class="s">"#version 130
uniform vec2 resolution;
uniform vec3 light;
uniform mat3 rotation;
uniform float focal_length;
uniform float distance;
out vec4 fragColor;
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
void main()
{
  vec3 direction =
    normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
  vec3 origin = rotation * vec3(0, 0, -distance);
  vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), origin, direction);
  vec4 transfer = cloud_transfer(origin, direction, interval);
  vec3 background = mix(vec3(0.125, 0.125, 0.25), vec3(1, 1, 1),
                        pow(dot(direction, light), 1000.0));
  fragColor = vec4(background * (1.0 - transfer.a) + transfer.rgb, 1.0);
}"</span><span class="p">)</span></code></pre></figure>

<p>Uniform variables are parameters that remain constant throughout the shader execution, unlike vertex input data.
Here we use the following uniform variables:</p>
<ul>
  <li><strong>resolution</strong>: a 2D vector containing the window pixel width and height</li>
  <li><strong>light:</strong> a 3D unit vector pointing to the light source</li>
  <li><strong>rotation:</strong> a 3x3 rotation matrix to rotate the camera around the origin</li>
  <li><strong>focal_length:</strong> the ratio of camera focal length to pixel size of the virtual camera</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-fog-uniforms</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">rotation</span><span class="w">     </span><span class="p">(</span><span class="nf">mulm</span><span class="w"> </span><span class="p">(</span><span class="nf">rotation-matrix-3d-y</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">40.0</span><span class="p">))</span><span class="w">
                           </span><span class="p">(</span><span class="nf">rotation-matrix-3d-x</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">-20.0</span><span class="p">)))</span><span class="w">
        </span><span class="n">focal-length</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">width</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">tan</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">30.0</span><span class="p">)))</span><span class="w">
        </span><span class="n">light</span><span class="w">        </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"resolution"</span><span class="p">)</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                      </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniformMatrix3fv</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"rotation"</span><span class="p">)</span><span class="w"> </span><span class="n">true</span><span class="w">
                             </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">mat-&gt;float-array</span><span class="w"> </span><span class="n">rotation</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"focal_length"</span><span class="p">)</span><span class="w"> </span><span class="n">focal-length</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))</span></code></pre></figure>

<p>The following function sets up the shader program, the vertex array object, and the uniform variables.
Then <code class="language-plaintext highlighter-rouge">GL11/glDrawElements</code> draws the background quad used for performing volumetric rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-fog</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-sources</span><span class="w"> </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"fog"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                          </span><span class="p">(</span><span class="nf">fog</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="p">]</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">              </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w">
                    </span><span class="p">(</span><span class="nf">setup-fog-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>We also need to convert the floating point array to a tensor and then to a <code class="language-plaintext highlighter-rouge">BufferedImage</code>.
The one-dimensional array gets converted to a tensor and then reshaped to a 3D tensor containing width × height RGBA values.
The RGBA data is converted to BGR data and then multiplied with 255 and clamped.
Finally the tensor is converted to a <code class="language-plaintext highlighter-rouge">BufferedImage</code>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">rgba-array-&gt;bufimg</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">data</span><span class="w">
      </span><span class="n">tensor/-&gt;tensor</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">[</span><span class="n">height</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="mi">4</span><span class="p">])</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/select</span><span class="w"> </span><span class="no">:all</span><span class="w"> </span><span class="no">:all</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w">
      </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="nf">clamp</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="n">bufimg/tensor-&gt;image</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the volumetric fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w"> </span><span class="p">(</span><span class="nf">render-fog</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/fog.jpg" alt="volumetric fog" /></p>

<h3 id="rendering-of-3d-noise">Rendering of 3D noise</h3>

<p>This method converts a floating point array to a buffer and initialises a 3D texture with it.
It is also necessary to set the texture parameters for interpolation and wrapping.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">float-array-&gt;texture3d</span><span class="w">
  </span><span class="p">[</span><span class="n">data</span><span class="w"> </span><span class="n">size</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="w">  </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">data</span><span class="p">)</span><span class="w">
        </span><span class="n">texture</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL12/glTexImage3D</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL30/GL_R32F</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="mi">0</span><span class="w">
                       </span><span class="n">GL11/GL_RED</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_WRAP_R</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="n">texture</span><span class="p">))</span></code></pre></figure>

<p>Here a mixture of 3D Perlin and Worley noise is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.3</span><span class="w"> </span><span class="p">(</span><span class="nf">perlin-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">3</span><span class="p">)))</span><span class="w">
                    </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.7</span><span class="w"> </span><span class="p">(</span><span class="nf">worley-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">3</span><span class="p">)))))</span></code></pre></figure>

<p>The noise is normalised to be between 0 and 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-3d-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">noise3d</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">noise3d</span><span class="p">)))</span><span class="w">
                          </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">noise3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">noise3d</span><span class="p">))))</span></code></pre></figure>

<p>Then the noise data is converted to a 3D texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-texture</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array-&gt;texture3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/-&gt;float-array</span><span class="w"> </span><span class="n">noise-3d-norm</span><span class="p">)</span><span class="w"> </span><span class="mi">32</span><span class="p">))</span></code></pre></figure>

<p>Instead of a constant density fog, we can use the noise as a density function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-shader</span><span class="w">
</span><span class="s">"#version 130
uniform sampler3D noise3d;
float noise(vec3 idx)
{
  return texture(noise3d, idx).r;
}"</span><span class="p">)</span></code></pre></figure>

<p>We also set the uniform sampler to texture slot 0 and bind the noise texture to that slot.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-noise-uniforms</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">setup-fog-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"noise3d"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">noise-texture</span><span class="p">))</span></code></pre></figure>

<p>Similar to the fog example above, we define a method to render the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-noise</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">cloud-shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-sources</span><span class="w"> </span><span class="p">(</span><span class="nb">concat</span><span class="w"> </span><span class="n">cloud-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="p">])</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">              </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w">
                    </span><span class="p">(</span><span class="nf">setup-noise-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>Now we can render the mixture of 3D Perlin and Worley noise using a step size of 0.01.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/noise3d.jpg" alt="3D noise" /></p>

<h3 id="remap-and-clamp-3d-noise">Remap and clamp 3D noise</h3>

<p>We define a method to map a range of input values to a range of output values and clamp the result.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-clamp</span><span class="w">
</span><span class="s">"#version 130
float remap_clamp(float value, float low1, float high1, float low2, float high2)
{
  float t = (value - low1) / (high1 - low1);
  return clamp(low2 + t * (high2 - low2), low2, high2);
}"</span><span class="p">)</span></code></pre></figure>

<p>A probing shader is used to test the remap_clamp function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float remap_clamp(float value, float low1, float high1, float low2, float high2);
void main()
{
  fragColor = vec4(remap_clamp(&lt;%= value %&gt;,
                               &lt;%= low1 %&gt;, &lt;%= high1 %&gt;,
                               &lt;%= low2 %&gt;, &lt;%= high2 %&gt;));
}"</span><span class="p">))</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">remap_clamp</code> is tested using a parametrized tests.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Remap and clamp input parameter values"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w">
                      </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                      </span><span class="p">[</span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-probe</span><span class="w"> </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="p">)]))</span><span class="w">
             </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">0.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mf">2.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mf">3.0</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">0.0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">4</span><span class="w">      </span><span class="mf">2.0</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mf">2.0</span><span class="p">)</span></code></pre></figure>

<p>We use the <code class="language-plaintext highlighter-rouge">remap-noise</code> method to map the 3D noise to the output range.
The base noise function and the remapping parameters are template parameters.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-noise</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">base</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
float &lt;%= base %&gt;(vec3 idx);
float remap_clamp(float value, float low1, float high1, float low2, float high2);
float remap_noise(vec3 idx)
{
  return remap_clamp(&lt;%= base %&gt;(idx), &lt;%= low1 %&gt;, &lt;%= high1 %&gt;, 0.0, &lt;%= high2 %&gt;);
}"</span><span class="p">))</span></code></pre></figure>

<p>We are going to use the following value as the upper value of the cloud density.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-strength</span><span class="w"> </span><span class="mf">6.5</span><span class="p">)</span></code></pre></figure>

<p>Now we can render the remapped noise values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/remap3d.jpg" alt="Remapped 3D noise" /></p>

<h3 id="octaves-of-3d-noise">Octaves of 3D noise</h3>

<p>Earlier we defined a function for creating octaves of 3D noise.
Here we create octaves of noise before remapping and clamping the values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/octaves3d.jpg" alt="Octaves of 3D noise" /></p>

<h3 id="mie-scattering">Mie scattering</h3>

<p>In-scattering of light towards the observer depends of the angle between light source and viewing direction.
Here we are going to use the phase function by Cornette and Shanks which depends on the asymmetry g and mu = cos(theta).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">mie-scatter</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 450 core
#define M_PI 3.1415926535897932384626433832795
#define ANISOTROPIC 0.25
#define G &lt;%= g %&gt;
uniform vec3 light;
float mie(float mu)
{
  return 3 * (1 - G * G) * (1 + mu * mu) /
    (8 * M_PI * (2 + G * G) * pow(1 + G * G - 2 * G * mu, 1.5));
}
float in_scatter(vec3 point, vec3 direction)
{
  return mix(1.0, mie(dot(light, direction)), ANISOTROPIC);
}"</span><span class="p">))</span></code></pre></figure>

<p>We define a probing shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">mie-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">mu</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 450 core
out vec4 fragColor;
float mie(float mu);
void main()
{
  float result = mie(&lt;%= mu %&gt;);
  fragColor = vec4(result, 0, 0, 1);
}"</span><span class="p">))</span></code></pre></figure>

<p>The shader is tested using a few values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Shader function for scattering phase function"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                    </span><span class="p">[(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="n">?g</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-probe</span><span class="w"> </span><span class="n">?mu</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="n">?result</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">))</span><span class="w">
         </span><span class="n">?g</span><span class="w">  </span><span class="n">?mu</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">-1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">2.25</span><span class="w"> </span><span class="p">(</span><span class="nf">pow</span><span class="w"> </span><span class="mf">1.25</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)))</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">2.25</span><span class="w"> </span><span class="p">(</span><span class="nf">pow</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">1.5</span><span class="p">))))</span></code></pre></figure>

<p>We can define a function to compute a particular value of the scattering phase function using the GPU.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">scatter-amount</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="p">[(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-probe</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="n">theta</span><span class="p">))])))</span></code></pre></figure>

<p>We can use this function to plot Mie scattering for different angles.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
                               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))</span><span class="w">
                                  </span><span class="p">(</span><span class="nf">scatter-amount</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))))</span><span class="w">
                           </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">361</span><span class="p">))</span><span class="w">
                   </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
                               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))</span><span class="w">
                                  </span><span class="p">(</span><span class="nf">scatter-amount</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))))</span><span class="w">
                           </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">361</span><span class="p">))</span><span class="w"> </span><span class="p">})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Mie scattering"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="s">"lines"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})</span><span class="w">
      </span><span class="n">plotly/plot</span><span class="w">
      </span><span class="p">(</span><span class="nf">assoc-in</span><span class="w"> </span><span class="p">[</span><span class="no">:layout</span><span class="w"> </span><span class="no">:yaxis</span><span class="w"> </span><span class="no">:scaleanchor</span><span class="p">]</span><span class="w"> </span><span class="s">"x"</span><span class="p">)))</span></code></pre></figure>

<p><img src="/pics/mie-plot.png" alt="Mie scattering" /></p>

<p>We replace the <code class="language-plaintext highlighter-rouge">in_scatter</code> placeholder from earlier with the Mie scattering and now the clouds look a bit more realistic.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/mie-clouds.jpg" alt="Clouds with Mie scattering" /></p>

<h3 id="self-shading-of-clouds">Self-shading of clouds</h3>

<p>Finally we can implement the shadow function by also sampling towards the light source to compute the shading value at each point.
Testing the function requires extending the <code class="language-plaintext highlighter-rouge">render-pixel</code> function to accept a function for setting the <code class="language-plaintext highlighter-rouge">light</code> uniform.
We leave this as an exercise for the interested reader 😉.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">shadow</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">noise</span><span class="w"> </span><span class="n">step</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
#define STEP &lt;%= step %&gt;
uniform vec3 light;
float &lt;%= noise %&gt;(vec3 idx);
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
float shadow(vec3 point)
{
  vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), point, light);
  float result = 1.0;
  for (float t = interval.x + 0.5 * STEP; t &lt; interval.y; t += STEP) {
    float density = &lt;%= noise %&gt;(point + t * light);
    float transmittance = exp(-density * STEP);
    result *= transmittance;
  };
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>The final result is starting to look realistic.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="p">(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">shadow</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.05</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w"> </span><span class="n">remap-clamp</span><span class="w">
                </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/self-shading.jpg" alt="Clouds with self-shading" /></p>

<h3 id="tidy-up">Tidy up</h3>

<p>Finally we free the texture, destroy the window, and terminate GLFW.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">noise-texture</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwDestroyWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwTerminate</span><span class="p">)</span></code></pre></figure>

<h2 id="further-topics">Further topics</h2>

<p>I hope you enjoyed this little tour of volumetric clouds.
Here are some references to get from a cloud prototype to more realistic clouds.</p>

<ul>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Vertical density profile</a></li>
  <li><a href="https://advances.realtimerendering.com/s2015/index.html">Powder function</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/03/20/procedural-global-cloud-cover/">Curl noise</a></li>
  <li><a href="https://ebruneton.github.io/precomputed_atmospheric_scattering/">Precomputed atmospheric scattering</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Deep opacity maps</a></li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Procedural generation of volumetric clouds using different types of noise]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/clouds.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/clouds.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Clojure in your browser</title><link href="https://www.wedesoft.de/software/2025/11/12/scittle-clojure/" rel="alternate" type="text/html" title="Clojure in your browser" /><published>2025-11-12T00:00:00+00:00</published><updated>2025-11-12T00:00:00+00:00</updated><id>https://www.wedesoft.de/software/2025/11/12/scittle-clojure</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/11/12/scittle-clojure/"><![CDATA[<p>There is a recent article on Clojure Civitas on <a href="https://clojurecivitas.org/scittle/presentations/browser_native_slides.html">using Scittle for browser native slides</a>.
<a href="https://github.com/babashka/scittle">Scittle</a> is a Clojure interpreter that runs in the browser.
It even defines a script tag that let’s you embed Clojure code in your HTML code.
Here is an example evaluating the content of an HTML textarea:</p>

<h2 id="html-code">HTML code</h2>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/scittle@0.6.22/dist/scittle.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"application/x-scittle"</span><span class="nt">&gt;</span>
<span class="p">(</span><span class="nx">defn</span> <span class="nx">run</span> <span class="p">[]</span>
  <span class="p">(</span><span class="kd">let</span> <span class="p">[</span><span class="nf">code </span><span class="p">(.</span><span class="o">-</span><span class="nf">value </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span> <span class="dl">"</span><span class="s2">code</span><span class="dl">"</span><span class="p">))</span>
        <span class="nx">output</span><span class="o">-</span><span class="nf">elem </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span> <span class="dl">"</span><span class="s2">output</span><span class="dl">"</span><span class="p">)]</span>
    <span class="p">(</span><span class="k">try</span>
      <span class="p">(</span><span class="kd">let</span> <span class="p">[</span><span class="nf">result </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nx">scittle</span><span class="p">.</span><span class="nx">core</span><span class="p">.</span><span class="nx">eval_string</span> <span class="nx">code</span><span class="p">)]</span>
        <span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">textContent</span> <span class="nx">output</span><span class="o">-</span><span class="nx">elem</span><span class="p">)</span> <span class="p">(</span><span class="nx">str</span> <span class="nx">result</span><span class="p">)))</span>
      <span class="p">(</span><span class="k">catch</span> <span class="p">:</span><span class="k">default</span> <span class="nx">e</span>
        <span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">textContent</span> <span class="nx">output</span><span class="o">-</span><span class="nx">elem</span><span class="p">)</span>
              <span class="p">(</span><span class="nx">str</span> <span class="dl">"</span><span class="s2">Error: </span><span class="dl">"</span> <span class="p">(.</span><span class="o">-</span><span class="nx">message</span> <span class="nx">e</span><span class="p">)))))))</span>

<span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">run</span> <span class="nx">js</span><span class="o">/</span><span class="nb">window</span><span class="p">)</span> <span class="nx">run</span><span class="p">)</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;textarea</span> <span class="na">id=</span><span class="s">"code"</span> <span class="na">rows=</span><span class="s">"20"</span> <span class="na">style=</span><span class="s">"width:100%;"</span><span class="nt">&gt;</span>
(defn primes [i p]
  (if (some #(zero? (mod i %)) p)
    (recur (inc i) p)
    (cons i (lazy-seq (primes (inc i) (conj p i))))))

(take 100 (primes 2 []))
<span class="nt">&lt;/textarea&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"run-button"</span> <span class="na">onclick=</span><span class="s">"run()"</span><span class="nt">&gt;</span>Run<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;pre</span> <span class="na">id=</span><span class="s">"output"</span><span class="nt">&gt;&lt;/pre&gt;</span></code></pre></figure>

<h2 id="scittle-in-your-browser">Scittle in your browser</h2>

<script src="https://cdn.jsdelivr.net/npm/scittle@0.6.22/dist/scittle.js"></script>

<script type="application/x-scittle">
(defn run []
  (let [code (.-value (js/document.getElementById "code"))
        output-elem (js/document.getElementById "output")]
    (try
      (let [result (js/scittle.core.eval_string code)]
        (set! (.-textContent output-elem) (str result)))
      (catch :default e
        (set! (.-textContent output-elem)
              (str "Error: " (.-message e)))))))

(set! (.-run js/window) run)
</script>

<textarea id="code" rows="20" style="width:100%;">
(defn primes [i p]
  (if (some #(zero? (mod i %)) p)
    (recur (inc i) p)
    (cons i (lazy-seq (primes (inc i) (conj p i))))))

(take 100 (primes 2 []))
</textarea>
<p><br />
<button id="run-button" onclick="run()">Run</button></p>
<pre id="output"></pre>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[There is a recent article on Clojure Civitas on using Scittle for browser native slides. Scittle is a Clojure interpreter that runs in the browser. It even defines a script tag that let’s you embed Clojure code in your HTML code. Here is an example evaluating the content of an HTML textarea:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/clojure.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/clojure.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">OpenGL Visualization with LWJGL</title><link href="https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon/" rel="alternate" type="text/html" title="OpenGL Visualization with LWJGL" /><published>2025-09-24T00:00:00+01:00</published><updated>2025-09-24T00:00:00+01:00</updated><id>https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon/"><![CDATA[<p>Using LWJGL’s OpenGL bindings and Fastmath to render data from NASA’s CGI Moon Kit</p>

<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/opengl_visualization/main.html">Clojure Civitas</a>)</em></p>

<h2 id="getting-dependencies">Getting dependencies</h2>

<p>First we need to get some libraries and we can use add-libs to fetch them.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">add-libs</span><span class="w"> </span><span class="p">{</span><span class="ss">'org.lwjgl/lwjgl</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl$natives-linux</span><span class="w">        </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-opengl</span><span class="w">               </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-glfw</span><span class="w">                 </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w">   </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-stb</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-stb$natives-linux</span><span class="w">    </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'generateme/fastmath</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.0.0-alpha3"</span><span class="p">}})</span><span class="w">
</span><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.java.io</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">io</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.math</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">PI</span><span class="w"> </span><span class="n">to-radians</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.vector</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">mult</span><span class="w"> </span><span class="n">normalize</span><span class="p">)])</span><span class="w">
</span><span class="p">(</span><span class="nb">import</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">javax.imageio</span><span class="w"> </span><span class="n">ImageIO</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl</span><span class="w"> </span><span class="n">BufferUtils</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.glfw</span><span class="w"> </span><span class="n">GLFW</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL</span><span class="w"> </span><span class="n">GL11</span><span class="w"> </span><span class="n">GL13</span><span class="w"> </span><span class="n">GL15</span><span class="w"> </span><span class="n">GL20</span><span class="w"> </span><span class="n">GL30</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.stb</span><span class="w"> </span><span class="n">STBImageWrite</span><span class="p">])</span></code></pre></figure>

<h2 id="creating-the-window">Creating the window</h2>

<p>Next we choose the window width and height.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="mi">640</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">1737.4</span><span class="p">)</span></code></pre></figure>

<p>We define a function to get the temporary directory.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tmpdir</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="nf">System/getProperty</span><span class="w"> </span><span class="s">"java.io.tmpdir"</span><span class="p">))</span></code></pre></figure>

<p>And then a function to get a temporary file name.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tmpname</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nf">tmpdir</span><span class="p">)</span><span class="w"> </span><span class="s">"/civitas-"</span><span class="w"> </span><span class="p">(</span><span class="nf">java.util.UUID/randomUUID</span><span class="p">)</span><span class="w"> </span><span class="s">".tmp"</span><span class="p">))</span></code></pre></figure>

<p>The following function is used to create screenshots for this article.
We read the pixels, write them to a temporary file using the STB library and then convert it to an ImageIO object.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">screenshot</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">filename</span><span class="w"> </span><span class="p">(</span><span class="nf">tmpname</span><span class="p">)</span><span class="w">
        </span><span class="n">buffer</span><span class="w">   </span><span class="p">(</span><span class="nf">java.nio.ByteBuffer/allocateDirect</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glReadPixels</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w">
                       </span><span class="n">GL11/GL_RGBA</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_BYTE</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">STBImageWrite/stbi_write_png</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">4</span><span class="w">
                                  </span><span class="n">buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">window-width</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="n">io/file</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="p">))))</span></code></pre></figure>

<p>We need to initialize the GLFW library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwInit</span><span class="p">)</span></code></pre></figure>

<p>Now we create an invisible window.
You can create a visisble window if you want to by not setting the visibility hint to false.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window</span><span class="w">
  </span><span class="p">(</span><span class="nf">do</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwDefaultWindowHints</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwWindowHint</span><span class="w"> </span><span class="n">GLFW/GLFW_VISIBLE</span><span class="w"> </span><span class="n">GLFW/GLFW_FALSE</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwCreateWindow</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="s">"Invisible Window"</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span></code></pre></figure>

<p>If you have a visible window, you can show it as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwShowWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<p>Note that if you are using a visible window, you always need to swap buffers after rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwSwapBuffers</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GLFW/glfwMakeContextCurrent</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL/createCapabilities</span><span class="p">))</span></code></pre></figure>

<h2 id="basic-rendering">Basic rendering</h2>

<h3 id="clearing-the-window">Clearing the window</h3>

<p>A simple test is to set a clear color and clear the window.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClearColor</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon0.jpg" alt="screenshot 0" /></p>

<h3 id="creating-shader-programs">Creating shader programs</h3>

<p>We define a convenience function to compile a shader and handle any errors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-shader</span><span class="w"> </span><span class="p">[</span><span class="n">source</span><span class="w"> </span><span class="n">shader-type</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateShader</span><span class="w"> </span><span class="n">shader-type</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glShaderSource</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">source</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glCompileShader</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderi</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">GL20/GL_COMPILE_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderInfoLog</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">shader</span><span class="p">))</span></code></pre></figure>

<p>We also define a convenience function to link a program and handle any errors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateProgram</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glAttachShader</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glDeleteShader</span><span class="w"> </span><span class="n">shader</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glLinkProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgrami</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">GL20/GL_LINK_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgramInfoLog</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>The following code shows a simple vertex shader which passes through vertex coordinates.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-source</span><span class="w"> </span><span class="s">"
#version 130

in vec3 point;

void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>In the fragment shader we use the pixel coordinates to output a color ramp.
The uniform variable iResolution will later be set to the window resolution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-source</span><span class="w"> </span><span class="s">"
#version 130

uniform vec2 iResolution;
out vec4 fragColor;

void main()
{
  fragColor = vec4(gl_FragCoord.xy / iResolution.xy, 0, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>Let’s compile the shaders and link the program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-source</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-source</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader</span><span class="w"> </span><span class="n">fragment-shader</span><span class="p">)))</span></code></pre></figure>

<p><strong>Note:</strong> It is beyond the topic of this talk, but you can set up a Clojure function to test an OpenGL shader function by using a probing fragment shader and rendering to a one pixel texture.
Please see my article <a href="https://www.wedesoft.de/software/2022/07/01/tdd-with-opengl/">Test Driven Development with OpenGL</a> for more information!</p>

<h3 id="creating-vertex-buffer-data">Creating vertex buffer data</h3>

<p>To provide the shader program with vertex data we are going to define just a single quad consisting of four vertices.</p>

<p>First we define a macro and use it to define convenience functions for converting arrays to LWJGL buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">def-make-buffer</span><span class="w"> </span><span class="p">[</span><span class="n">method</span><span class="w"> </span><span class="n">create-buffer</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="o">~</span><span class="n">method</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="o">#</span><span class="p">]</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="n">create-buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="n">buffer</span><span class="o">#</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-float-buffer</span><span class="w"> </span><span class="n">BufferUtils/createFloatBuffer</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-int-buffer</span><span class="w"> </span><span class="n">BufferUtils/createIntBuffer</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-byte-buffer</span><span class="w"> </span><span class="n">BufferUtils/createByteBuffer</span><span class="p">))</span></code></pre></figure>

<p>We define a simple background quad spanning the entire window.
We use normalised device coordinates (NDC) which are between -1 and 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices</span><span class="w">
  </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]))</span></code></pre></figure>

<p>The index array defines the order of the vertices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices</span><span class="w">
  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">]))</span></code></pre></figure>

<h3 id="setting-up-the-vertex-buffer">Setting up the vertex buffer</h3>

<p>We add a convenience function to setup VAO, VBO, and IBO.</p>

<ul>
  <li>We define a vertex array object (VAO) which acts like a context for the vertex and index buffer.</li>
  <li>We define a vertex buffer object (VBO) which contains the vertex data.</li>
  <li>We also define an index buffer object (IBO) which contains the index data.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-vao</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenVertexArrays</span><span class="p">)</span><span class="w">
        </span><span class="n">vbo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)</span><span class="w">
        </span><span class="n">ibo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">vertices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="no">:vao</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="no">:vbo</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="no">:ibo</span><span class="w"> </span><span class="n">ibo</span><span class="p">}))</span></code></pre></figure>

<p>Now we use the function to setup the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">))</span></code></pre></figure>

<p>The data of each vertex is defined by 3 floats (x, y, z).
We need to specify the layout of the vertex buffer object so that OpenGL knows how to interpret it.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-quad">Rendering the quad</h3>

<p>We select the program and define the uniform variable iResolution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">))</span></code></pre></figure>

<p>Since the correct VAO is already bound from the earlier example, we are now ready to draw the quad.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">screenshot</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/moon1.jpg" alt="screenshot 1" /></p>

<p>This time the quad shows a color ramp!</p>

<h3 id="finishing-up">Finishing up</h3>

<p>We only delete the program since we are going to reuse the VAO in the next example.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span></code></pre></figure>

<h2 id="rendering-a-texture">Rendering a Texture</h2>

<h3 id="getting-the-nasa-data">Getting the NASA data</h3>

<p>We define a function to download a file from the web.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">download</span><span class="w"> </span><span class="p">[</span><span class="n">url</span><span class="w"> </span><span class="n">target</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">with-open</span><span class="w"> </span><span class="p">[</span><span class="n">in</span><span class="w"> </span><span class="p">(</span><span class="nf">io/input-stream</span><span class="w"> </span><span class="n">url</span><span class="p">)</span><span class="w">
              </span><span class="n">out</span><span class="w"> </span><span class="p">(</span><span class="nf">io/output-stream</span><span class="w"> </span><span class="n">target</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">io/copy</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">out</span><span class="p">)))</span></code></pre></figure>

<p>If it does not exist, we download the lunar color map from the <a href="https://svs.gsfc.nasa.gov/4720/">NASA CGI Moon Kit</a>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">moon-tif</span><span class="w"> </span><span class="s">"src/opengl_visualization/lroc_color_poles_2k.tif"</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">not</span><span class="w"> </span><span class="p">(</span><span class="nf">.exists</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-tif</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">download</span><span class="w">
      </span><span class="s">"https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_poles_2k.tif"</span><span class="w">
      </span><span class="n">moon-tif</span><span class="p">)))</span></code></pre></figure>

<h3 id="create-a-texture">Create a texture</h3>

<p>Next we load the image using ImageIO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-tif</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-raster</span><span class="w"> </span><span class="p">(</span><span class="nf">.getRaster</span><span class="w"> </span><span class="n">color</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="p">(</span><span class="nf">.getWidth</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="p">(</span><span class="nf">.getHeight</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-channels</span><span class="w"> </span><span class="p">(</span><span class="nf">.getNumBands</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-pixels</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-channels</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">.getPixels</span><span class="w"> </span><span class="n">color-raster</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-pixels</span><span class="p">)</span><span class="w">
  </span><span class="p">[</span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-channels</span><span class="p">])</span><span class="w">
</span><span class="c1">; [2048 1024 3]</span></code></pre></figure>

<p>Then we create an OpenGL texture from the RGB data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">texture-color</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexImage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL11/GL_RGBA</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="mi">0</span><span class="w">
                     </span><span class="n">GL11/GL_RGB</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_BYTE</span><span class="w">
                     </span><span class="p">(</span><span class="nf">make-byte-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">byte-array</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">unchecked-byte</span><span class="w"> </span><span class="n">color-pixels</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-texture">Rendering the texture</h3>

<p>We are going to use the vertex pass through shader again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-tex</span><span class="w"> </span><span class="s">"
#version 130

in vec3 point;

void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>The fragment shader now uses the texture function to lookup color values from a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-tex</span><span class="w"> </span><span class="s">"
#version 130

uniform vec2 iResolution;
uniform sampler2D moon;
out vec4 fragColor;

void main()
{
  fragColor = texture(moon, gl_FragCoord.xy / iResolution.xy);
}"</span><span class="p">)</span></code></pre></figure>

<p>We compile and link the shaders to create a program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-tex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-tex</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-tex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-tex</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-tex-shader</span><span class="w"> </span><span class="n">fragment-tex-shader</span><span class="p">)))</span></code></pre></figure>

<p>We need to set up the layout of the vertex data again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>We set the resolution and bind the texture to the texture slot number 0.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">tex-program</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>The quad now is textured!</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon2.jpg" alt="screenshot 2" /></p>

<h3 id="finishing-up-1">Finishing up</h3>

<p>We create a convenience function to tear down the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">teardown-vao</span><span class="w"> </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="n">ibo</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vao</span><span class="p">))</span></code></pre></figure>

<p>We tear down the quad.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span></code></pre></figure>

<p>We also delete the program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">tex-program</span><span class="p">)</span></code></pre></figure>

<h2 id="render-a-3d-cube">Render a 3D cube</h2>

<h3 id="create-vertex-data">Create vertex data</h3>

<p>If we want to render a cube, we need to define 8 vertices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-cube</span><span class="w">
  </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="p">]))</span></code></pre></figure>

<p>The cube is made up of 6 quads, with 4 vertex indices per quad.
So we require 6 * 4 = 24 indices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-cube</span><span class="w">
  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w">
              </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">4</span><span class="w">
              </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">4</span><span class="w">
              </span><span class="mi">5</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w">
              </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">7</span><span class="w">
              </span><span class="mi">4</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">]))</span></code></pre></figure>

<h3 id="initialize-vertex-buffer-array">Initialize vertex buffer array</h3>

<p>We use the function from earlier to set up the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-cube</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-cube</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">))</span></code></pre></figure>

<h3 id="shader-program-mapping-texture-onto-cube">Shader program mapping texture onto cube</h3>

<p>We first define a vertex shader, which takes cube coordinates, rotates, translates, and projects them.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="s">"
#version 130

uniform float fov;
uniform float alpha;
uniform float beta;
uniform float distance;
uniform vec2 iResolution;
in vec3 point;
out vec3 vpoint;

void main()
{
  // Rotate and translate vertex
  mat3 rot_y = mat3(vec3(cos(alpha), 0, sin(alpha)),
                    vec3(0, 1, 0),
                    vec3(-sin(alpha), 0, cos(alpha)));
  mat3 rot_x = mat3(vec3(1, 0, 0),
                    vec3(0, cos(beta), -sin(beta)),
                    vec3(0, sin(beta), cos(beta)));
  vec3 p = rot_x * rot_y * point + vec3(0, 0, distance);

  // Project vertex creating normalized device coordinates
  float f = 1.0 / tan(fov / 2.0);
  float aspect = iResolution.x / iResolution.y;
  float proj_x = p.x / p.z * f;
  float proj_y = p.y / p.z * f * aspect;
  float proj_z = p.z / (2.0 * distance);

  // Output to shader pipeline.
  gl_Position = vec4(proj_x, proj_y, proj_z, 1);
  vpoint = point;
}"</span><span class="p">)</span></code></pre></figure>

<p>The fragment shader maps the texture onto the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-moon</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform sampler2D moon;
in vec3 vpoint;
out vec4 fragColor;

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

void main()
{
  fragColor = vec4(color(lonlat(vpoint)).rgb, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We compile and link the shaders.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-moon</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-moon</span><span class="w"> </span><span class="n">fragment-shader-moon</span><span class="p">)))</span></code></pre></figure>

<p>We need to set up the memory layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-cube">Rendering the cube</h3>

<p>This shader program requires setup of several uniforms and a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-moon</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">25.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">30.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">-20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>We enable back face culling to only render the front faces of the cube.
Then we clear the window and render the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glEnable</span><span class="w"> </span><span class="n">GL11/GL_CULL_FACE</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glCullFace</span><span class="w"> </span><span class="n">GL11/GL_BACK</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClearColor</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon3.jpg" alt="screenshot 3" /></p>

<p>This looks interesting but it is not a good approximation of the moon.</p>

<h3 id="finishing-up-2">Finishing up</h3>

<p>To finish up we delete the vertex data for the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-cube</span><span class="p">)</span></code></pre></figure>

<h2 id="approximating-a-sphere">Approximating a sphere</h2>

<h3 id="creating-the-vertex-data">Creating the vertex data</h3>

<p>First we partition the vertex data and convert the triplets to 8 Fastmath vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">points</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec3</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">vertices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">points</span><span class="w">
</span><span class="c1">; ([-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [1.0 1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 1.0])</span></code></pre></figure>

<p>Then we use the index array to get the coordinates of the first corner of each face resulting in 6 Fastmath vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">corners</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">corners</span><span class="w">
</span><span class="c1">; ([-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 1.0])</span></code></pre></figure>

<p>We get the first spanning vector of each face by subtracting the second corner from the first.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">u-vectors</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">j</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">)))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">u-vectors</span><span class="w">
</span><span class="c1">; ([2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0])</span></code></pre></figure>

<p>We get the second spanning vector of each face by subtracting the fourth corner from the first.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">v-vectors</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">l</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">l</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">)))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">v-vectors</span><span class="w">
</span><span class="c1">; ([0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 -2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -2.0])</span></code></pre></figure>

<p>We can now use vector math to subsample the faces and project the points onto a sphere by normalizing the vectors and multiplying with the moon radius.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="n">v</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">v</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="n">n</span><span class="p">)))))</span><span class="w"> </span><span class="n">radius</span><span class="p">)))</span></code></pre></figure>

<p>Subdividing once results in 9 corners for a cube face.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">sphere-points</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">corners</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">v-vectors</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
</span><span class="c1">; ([-1003.088357690056 -1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [0.0 -1228.5273216335077 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [1003.088357690056 -1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [-1228.5273216335077 0.0 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -1737.4]</span><span class="w">
</span><span class="c1">;  [1228.5273216335077 0.0 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [-1003.088357690056 1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [0.0 1228.5273216335077 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [1003.088357690056 1003.088357690056 -1003.088357690056])</span></code></pre></figure>

<p>We also need a function to generate the indices for the quads.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">face</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">)]</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">offset</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">face</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="n">i</span><span class="p">)]</span><span class="w">
         </span><span class="p">[</span><span class="n">offset</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">offset</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">1</span><span class="p">)])))</span></code></pre></figure>

<p>Subdividing once results in 4 quads for a cube face.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">sphere-indices</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span><span class="c1">; ([0 1 4 3] [1 2 5 4] [3 4 7 6] [4 5 8 7])</span></code></pre></figure>

<h3 id="rendering-a-coarse-approximation-of-the-sphere">Rendering a coarse approximation of the sphere.</h3>

<p>We subdivide once (n=2) and create a VAO with the data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w">
                                                  </span><span class="n">corners</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="n">v-vectors</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">6</span><span class="p">)))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-sphere</span><span class="w"> </span><span class="n">indices-sphere</span><span class="p">)))</span></code></pre></figure>

<p>The layout needs to be configured again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>The distance needs to be increased, because the points are on a sphere with the radius of the moon.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span></code></pre></figure>

<p>Rendering the mesh now results in a better approximation of a sphere.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon4.jpg" alt="screenshot 4" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-sphere</span><span class="p">)</span></code></pre></figure>

<h3 id="rendering-a-fine-approximation-of-the-sphere">Rendering a fine approximation of the sphere.</h3>

<p>To get a high quality approximation we subdivide more and create a VAO with the data. (do</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n2</span><span class="w"> </span><span class="mi">16</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="n">n2</span><span class="p">)</span><span class="w"> </span><span class="n">corners</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="n">v-vectors</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="n">n2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">6</span><span class="p">)))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-sphere-high</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>Rendering the mesh now results in a spherical mesh with a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon5.jpg" alt="screenshot 5" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-moon</span><span class="p">)</span></code></pre></figure>

<h2 id="adding-ambient-and-diffuse-reflection">Adding ambient and diffuse reflection</h2>

<p>In order to introduce lighting we add ambient and diffuse lighting to the fragment shader.
We use the ambient and diffuse lighting from the <a href="https://learnopengl.com/Lighting/Basic-Lighting">Phong shading model</a>.</p>

<ul>
  <li>The ambient light is a constant value.</li>
  <li>The diffuse light is calculated using the dot product of the light vector and the normal vector.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-moon-diffuse</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform vec3 light;
uniform float ambient;
uniform float diffuse;
uniform sampler2D moon;
in vec3 vpoint;
out vec4 fragColor;

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

void main()
{
  float phong = ambient + diffuse * max(0.0, dot(light, normalize(vpoint)));
  fragColor = vec4(color(lonlat(vpoint)) * phong, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We reuse the vertex shader from the previous example and the new fragment shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-moon-diffuse</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-diffuse</span><span class="w"> </span><span class="n">fragment-shader-diffuse</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex data layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>A normalized light vector is defined.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">light</span><span class="w"> </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">-1</span><span class="p">)))</span></code></pre></figure>

<p>Before rendering we need to set up the various uniform values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-diffuse</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"ambient"</span><span class="p">)</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"diffuse"</span><span class="p">)</span><span class="w"> </span><span class="mf">1.6</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the mesh with diffuse shading.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon6.jpg" alt="screenshot 6" /></p>

<p>Afterwards we delete the shader program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-diffuse</span><span class="p">)</span></code></pre></figure>

<h2 id="using-normal-mapping">Using normal mapping</h2>

<h3 id="load-elevation-data-into-texture">Load elevation data into texture</h3>

<p>In the final section we also want to add normal mapping in order to get realistic shading of craters.</p>

<p>The lunar elevation data is downloaded from NASA’s website.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">moon-ldem</span><span class="w"> </span><span class="s">"src/opengl_visualization/ldem_4.tif"</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">not</span><span class="w"> </span><span class="p">(</span><span class="nf">.exists</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-ldem</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">download</span><span class="w"> </span><span class="s">"https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/ldem_4.tif"</span><span class="w">
              </span><span class="n">moon-ldem</span><span class="p">)))</span></code></pre></figure>

<p>The image is read using ImageIO and the floating point elevation data is extracted.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-ldem</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-raster</span><span class="w"> </span><span class="p">(</span><span class="nf">.getRaster</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="p">(</span><span class="nf">.getWidth</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="p">(</span><span class="nf">.getHeight</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">do</span><span class="w"> </span><span class="p">(</span><span class="nf">.getPixels</span><span class="w"> </span><span class="n">ldem-raster</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="p">)</span><span class="w"> </span><span class="n">nil</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">resolution</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="n">radius</span><span class="p">)</span><span class="w"> </span><span class="n">ldem-width</span><span class="p">))</span><span class="w">
  </span><span class="p">[</span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="p">])</span><span class="w">
</span><span class="c1">; [1440 720]</span></code></pre></figure>

<p>The floating point pixel data is converted into a texture</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">texture-ldem</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexImage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL30/GL_R32F</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="mi">0</span><span class="w">
                     </span><span class="n">GL11/GL_RED</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="p">))</span></code></pre></figure>

<h3 id="create-shader-program-with-normal-mapping">Create shader program with normal mapping</h3>

<p>We reuse the vertex shader from the previous section.</p>

<p>The fragment shader this time is more involved.</p>

<ul>
  <li>A horizon matrix with normal, tangent, and bitangent vectors is computed.</li>
  <li>The elevation is sampled in four directions from the current 3D point.</li>
  <li>The elevation values are used to create two surface vectors.</li>
  <li>The cross product of the surface vectors is computed and normalized to get the normal vector.</li>
  <li>This perturbed normal vector is now used to compute diffuse lighting.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-normal</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform vec3 light;
uniform float ambient;
uniform float diffuse;
uniform float resolution;
uniform sampler2D moon;
uniform sampler2D ldem;
in vec3 vpoint;
out vec4 fragColor;

vec3 orthogonal_vector(vec3 n)
{
  vec3 b;
  if (abs(n.x) &lt;= abs(n.y)) {
    if (abs(n.x) &lt;= abs(n.z))
      b = vec3(1, 0, 0);
    else
      b = vec3(0, 0, 1);
  } else {
    if (abs(n.y) &lt;= abs(n.z))
      b = vec3(0, 1, 0);
    else
      b = vec3(0, 0, 1);
  };
  return normalize(cross(n, b));
}

mat3 oriented_matrix(vec3 n)
{
  vec3 o1 = orthogonal_vector(n);
  vec3 o2 = cross(n, o1);
  return mat3(n, o1, o2);
}

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

float elevation(vec3 p)
{
  return texture(ldem, lonlat(p)).r;
}

vec3 normal(mat3 horizon, vec3 p)
{
  vec3 pl = p + horizon * vec3(0, -1,  0) * resolution;
  vec3 pr = p + horizon * vec3(0,  1,  0) * resolution;
  vec3 pu = p + horizon * vec3(0,  0, -1) * resolution;
  vec3 pd = p + horizon * vec3(0,  0,  1) * resolution;
  vec3 u = horizon * vec3(elevation(pr) - elevation(pl), 2 * resolution, 0);
  vec3 v = horizon * vec3(elevation(pd) - elevation(pu), 0, 2 * resolution);
  return normalize(cross(u, v));
}

void main()
{
  mat3 horizon = oriented_matrix(normalize(vpoint));
  float phong = ambient + diffuse * max(0.0, dot(light, normal(horizon, vpoint)));
  fragColor = vec4(color(lonlat(vpoint)).rgb * phong, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We reuse the vertex shader from the previous example and the new fragment shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-normal</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-normal</span><span class="w"> </span><span class="n">fragment-shader-normal</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex data layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>Apart from the uniform values we also need to set up two textures this time: the color texture and the elevation texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-normal</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"resolution"</span><span class="p">)</span><span class="w"> </span><span class="n">resolution</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"ambient"</span><span class="p">)</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"diffuse"</span><span class="p">)</span><span class="w"> </span><span class="mf">1.6</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"ldem"</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE1</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the mesh with normal mapping.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon7.jpg" alt="screenshot 7" /></p>

<p>Afterwards we delete the shader program and the vertex data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-normal</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-sphere-high</span><span class="p">)</span></code></pre></figure>

<p>And the textures.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">)</span></code></pre></figure>

<h2 id="finalizing-glfw">Finalizing GLFW</h2>

<p>When we are finished, we destroy the window.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwDestroyWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<p>Finally we terminate use of the GLFW library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwTerminate</span><span class="p">)</span></code></pre></figure>

<p>I hope you liked this 3D graphics example.</p>

<p>Note that in practise you will</p>

<ul>
  <li>use higher resolution data and map the data onto texture tiles</li>
  <li>generate textures containing normal maps offline</li>
  <li>create a multiresolution map</li>
  <li>use tessellation to increase the mesh resolution</li>
  <li>use elevation data to deform the mesh</li>
</ul>

<p>Thanks to <a href="https://timothypratley.blogspot.com/p/httpswww.html">Timothy Pratley</a> for helping getting this post online.</p>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Using LWJGL’s OpenGL bindings and Fastmath to render data from NASA’s CGI Moon Kit]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/moon.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/moon.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Developing a Space Flight Simulator in Clojure</title><link href="https://www.wedesoft.de/software/2025/09/05/clojure-game/" rel="alternate" type="text/html" title="Developing a Space Flight Simulator in Clojure" /><published>2025-09-05T00:00:00+01:00</published><updated>2025-09-05T00:00:00+01:00</updated><id>https://www.wedesoft.de/software/2025/09/05/clojure-game</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/09/05/clojure-game/"><![CDATA[<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/38FGT7SWVh0" frameborder="0" allowfullscreen=""></iframe></div>

<p>In 2017 I discovered the free of charge <a href="http://orbit.medphys.ucl.ac.uk/">Orbiter 2016</a> space flight simulator which was proprietary at the time and it inspired me to develop a space flight simulator myself.
I prototyped some rigid body physics in C and later in <a href="https://www.gnu.org/software/guile/">GNU Guile</a> and also prototyped loading and rendering of Wavefront OBJ files.
I used GNU Guile (a Scheme implementation) because it has a good native interface and of course it has hygienic macros.
Eventually I got interested in Clojure because it has more generic multi-methods as well as fast hash maps and vectors.
I finally decided to develop the game for real in Clojure.
I have been developing a space flight simulator in Clojure for almost 5 years now.
While using Clojure I have come to appreciate the immutable values and safe parallelism using atoms, agents, and refs.</p>

<p>In the beginning I decided to work on the hard parts first, which for me were 3D rendering of a planet, an atmosphere, shadows, and volumetric clouds.
I read the <a href="https://www.informit.com/store/opengl-superbible-comprehensive-tutorial-and-reference-9780672337475">OpenGL Superbible</a> to get an understanding on what functionality OpenGL provides.
When Orbiter was eventually open sourced and released unter MIT license <a href="https://github.com/orbitersim/orbiter">here</a>, I inspected the source code and discovered that about 90% of the code is graphics-related.
So starting with the graphics problems was not a bad decision.</p>

<h2 id="software-dependencies">Software dependencies</h2>

<p>The following software is used for development.
The software libraries run on both GNU/Linux and Microsoft Windows.</p>

<ul>
  <li><a href="https://clojure.org/">Clojure</a> the programming language</li>
  <li><a href="https://www.lwjgl.org/">LWJGL</a> provides Java wrappers for various libraries
    <ul>
      <li>lwjgl-opengl for 3D graphics</li>
      <li>lwjgl-glfw for windowing and input devices</li>
      <li>lwjgl-nuklear for graphical user interfaces</li>
      <li>lwjgl-stb for image I/O and using truetype fonts</li>
      <li>lwjgl-assimp to load glTF 3D models with animation data</li>
    </ul>
  </li>
  <li><a href="https://github.com/jrouwe/JoltPhysics">Jolt Physics</a> to simulate wheeled vehicles and collisions with meshes</li>
  <li><a href="https://generateme.github.io/fastmath/">Fastmath</a> for fast matrix and vector math as well as spline interpolation</li>
  <li><a href="https://github.com/weavejester/comb">Comb</a> for templating shader code</li>
  <li><a href="https://github.com/Engelberg/instaparse">Instaparse</a> to parse NASA Planetary Constant Kernel (PCK) files</li>
  <li><a href="https://github.com/clj-commons/gloss">Gloss</a> to parse NASA Double Precision Array Files (DAF)</li>
  <li><a href="https://github.com/IGJoshua/coffi">Coffi</a> as a foreign function interface</li>
  <li><a href="https://github.com/clojure/core.memoize">core.memoize</a> for least recently used caching of function results</li>
  <li><a href="https://commons.apache.org/proper/commons-compress/">Apache Commons Compress</a> to read map tiles from tar files</li>
  <li><a href="https://github.com/metosin/malli">Malli</a> to add schemas to functions</li>
  <li><a href="https://github.com/levand/immuconf">Immuconf</a> to load the configuration file</li>
  <li><a href="https://github.com/weavejester/progrock">Progrock</a> a progress bar for long running builds</li>
  <li><a href="https://github.com/clj-commons/claypoole">Claypoole</a> to implement parallel for loops</li>
  <li><a href="https://github.com/marick/Midje">Midje</a> for test-driven development</li>
  <li><a href="https://clojure.org/guides/tools_build">tools.build</a> to build the project</li>
  <li><a href="https://github.com/clojure-goes-fast/clj-async-profiler">clj-async-profiler</a> Clojure profiler creating flame graphs</li>
  <li><a href="https://github.com/fzakaria/slf4j-timbre">slf4j-timbre</a> Java logging implementation for Clojure</li>
</ul>

<p>The <em>deps.edn</em> file contains operating system dependent LWJGL bindings.
For example on GNU/Linux the <em>deps.edn</em> file contains the following:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-opengl</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-glfw</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-nuklear</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-nuklear$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-stb</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-stb$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-assimp</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-assimp$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}}</span><span class="w">
        </span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span></code></pre></figure>

<p>In order to manage the different dependencies for Microsoft Windows, a separate Git branch is maintained.</p>

<h2 id="atmosphere-rendering">Atmosphere rendering</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/q9aWd_14qhA" frameborder="0" allowfullscreen=""></iframe></div>

<p>For the atmosphere, <a href="https://ebruneton.github.io/precomputed_atmospheric_scattering/">Bruneton’s precomputed atmospheric scattering</a> was used.
The implementation uses a 2D transmittance table, a 2D surface scattering table, a 4D Rayleigh scattering, and a 4D Mie scattering table.
The tables are computed using several iterations of numerical integration.
Higher order functions for integration over a sphere and over a line segment were implemented in Clojure.
Integration over a ray in 3D space (using fastmath vectors) was implemented as follows for example:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">integral-ray</span><span class="w">
  </span><span class="s">"Integrate given function over a ray in 3D space"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">ray</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="no">:double</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:double</span><span class="p">]]</span><span class="w"> </span><span class="no">:some</span><span class="p">]]</span><span class="w"> </span><span class="no">:some</span><span class="p">]}</span><span class="w">
  </span><span class="p">[{</span><span class="no">::keys</span><span class="w"> </span><span class="p">[</span><span class="n">origin</span><span class="w"> </span><span class="n">direction</span><span class="p">]}</span><span class="w"> </span><span class="n">steps</span><span class="w"> </span><span class="n">distance</span><span class="w"> </span><span class="n">fun</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">stepsize</span><span class="w">      </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">distance</span><span class="w"> </span><span class="n">steps</span><span class="p">)</span><span class="w">
        </span><span class="n">samples</span><span class="w">       </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="n">stepsize</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">steps</span><span class="p">))</span><span class="w">
        </span><span class="n">interpolate</span><span class="w">   </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="n">origin</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">direction</span><span class="w"> </span><span class="n">s</span><span class="p">)))</span><span class="w">
        </span><span class="n">direction-len</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="n">direction</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">stepsize</span><span class="w"> </span><span class="n">direction-len</span><span class="p">)))</span><span class="w"> </span><span class="n">samples</span><span class="p">))))</span></code></pre></figure>

<p>Precomputing the atmospheric tables takes several hours even though <a href="https://clojuredocs.org/clojure.core/pmap">pmap</a> was used.
When sampling the multi-dimensional functions, <em>pmap</em> was used as a top-level loop and <em>map</em> was used for interior loops.
Using <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html">java.nio.ByteBuffer</a> the floating point values were converted to a byte array and then written to disk using a <a href="https://clojuredocs.org/clojure.java.io/output-stream">clojure.java.io/output-stream</a>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">floats-&gt;bytes</span><span class="w">
  </span><span class="s">"Convert float array to byte buffer"</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">floats</span><span class="w"> </span><span class="n">float-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w">           </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">float-data</span><span class="p">)</span><span class="w">
        </span><span class="n">byte-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">.order</span><span class="w"> </span><span class="p">(</span><span class="nf">ByteBuffer/allocate</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">4</span><span class="p">))</span><span class="w"> </span><span class="n">ByteOrder/LITTLE_ENDIAN</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="p">(</span><span class="nf">.asFloatBuffer</span><span class="w"> </span><span class="n">byte-buffer</span><span class="p">)</span><span class="w"> </span><span class="n">float-data</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">.array</span><span class="w"> </span><span class="n">byte-buffer</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">spit-bytes</span><span class="w">
  </span><span class="s">"Write bytes to a file"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">non-empty-string</span><span class="w"> </span><span class="n">bytes?</span><span class="p">]</span><span class="w"> </span><span class="no">:nil</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">String</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="o">^</span><span class="n">bytes</span><span class="w"> </span><span class="n">byte-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">with-open</span><span class="w"> </span><span class="p">[</span><span class="n">out</span><span class="w"> </span><span class="p">(</span><span class="nf">io/output-stream</span><span class="w"> </span><span class="n">file-name</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.write</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="n">byte-data</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">spit-floats</span><span class="w">
  </span><span class="s">"Write floating point numbers to a file"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">non-empty-string</span><span class="w"> </span><span class="n">seqable?</span><span class="p">]</span><span class="w"> </span><span class="no">:nil</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">String</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="o">^</span><span class="n">floats</span><span class="w"> </span><span class="n">float-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">spit-bytes</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="p">(</span><span class="nf">floats-&gt;bytes</span><span class="w"> </span><span class="n">float-data</span><span class="p">)))</span></code></pre></figure>

<p>When launching the game, the lookup tables get loaded and copied into OpenGL textures.
Shader functions are used to lookup and interpolate values from the tables.
When rendering the planet surface or the space craft, the atmosphere essentially gets superimposed using ray tracing.
After rendering the planet, a background quad is rendered to display the remaining part of the atmosphere above the horizon.</p>

<h2 id="templating-opengl-shaders">Templating OpenGL shaders</h2>

<p>It is possible to make programming with OpenGL shaders more flexible by using a templating library such as <em>Comb</em>.
The following shader defines multiple octaves of noise on a base noise function:</p>

<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="cp">#version 410 core
</span>
<span class="kt">float</span> <span class="o">&lt;%=</span> <span class="n">base</span><span class="o">-</span><span class="n">function</span> <span class="o">%&gt;</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">idx</span><span class="p">);</span>

<span class="kt">float</span> <span class="o">&lt;%=</span> <span class="n">method</span><span class="o">-</span><span class="n">name</span> <span class="o">%&gt;</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">idx</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">float</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span>
<span class="o">&lt;%</span> <span class="p">(</span><span class="n">doseq</span> <span class="p">[</span><span class="n">multiplier</span> <span class="n">octaves</span><span class="p">]</span> <span class="o">%&gt;</span>
  <span class="n">result</span> <span class="o">+=</span> <span class="o">&lt;%=</span> <span class="n">multiplier</span> <span class="o">%&gt;</span> <span class="o">*</span> <span class="o">&lt;%=</span> <span class="n">base</span><span class="o">-</span><span class="n">function</span> <span class="o">%&gt;</span><span class="p">(</span><span class="n">idx</span><span class="p">);</span>
  <span class="n">idx</span> <span class="o">*=</span> <span class="mi">2</span><span class="p">;</span>
<span class="o">&lt;%</span> <span class="p">)</span> <span class="o">%&gt;</span>
  <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>One can then for example define the function <em>fbm_noise</em> using octaves of the base function <em>noise</em> as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="s">"Shader function to sum octaves of noise"</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">method-name</span><span class="w"> </span><span class="n">base-function</span><span class="w"> </span><span class="n">octaves</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">slurp</span><span class="w"> </span><span class="s">"resources/shaders/core/noise-octaves.glsl"</span><span class="p">)))</span><span class="w">

</span><span class="c1">; ...</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fbm-noise-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="s">"fbm_noise"</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="p">[</span><span class="mf">0.57</span><span class="w"> </span><span class="mf">0.28</span><span class="w"> </span><span class="mf">0.15</span><span class="p">]))</span></code></pre></figure>

<h2 id="planet-rendering">Planet rendering</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/Ce3oWQflYOY" frameborder="0" allowfullscreen=""></iframe></div>

<p>To render the planet, <a href="https://visibleearth.nasa.gov/collection/1484/blue-marble">NASA Bluemarble</a> data, <a href="https://earthobservatory.nasa.gov/features/NightLights/page3.php">NASA Blackmarble</a> data, and <a href="https://www.ngdc.noaa.gov/mgg/topo/gltiles.html">NASA Elevation</a> data was used.
The images were converted to a multi resolution pyramid of map tiles.
The following functions were implemented for color map tiles and for elevation tiles:</p>

<ul>
  <li>a function to load and cache map tiles of given 2D tile index and level of detail</li>
  <li>a function to extract a pixel from a map tile</li>
  <li>a function to extract the pixel for a specific longitude and latitude</li>
</ul>

<p>The functions for extracting a pixel for given longitude and latitude then were used to generate a cube map with a quad tree of tiles for each face.
For each tile, the following files were generated:</p>

<ul>
  <li>A daytime texture</li>
  <li>A night time texture</li>
  <li>An image of 3D vectors defining a surface mesh</li>
  <li>A water mask</li>
  <li>A normal map</li>
</ul>

<p>Altogether 655350 files were generated.
Because the Steam ContentBuilder does not support a large number of files, each row of tile data was aggregated into a tar file.
The <em>Apache Commons Compress</em> library allows you to open a tar file to get a list of entries and then perform random access on the contents of the tar file.
A Clojure LRU cache was used to maintain a cache of open tar files for improved performance.</p>

<p>At run time, a future is created, which returns an updated tile tree, a list of tiles to drop, and a path list of the tiles to load into OpenGL.
When the future is realized, the main thread deletes the OpenGL textures from the drop list, and then uses the path list to get the new loaded images from the tile tree, load them into OpenGL textures, and create an updated tile tree with the new OpenGL textures added.
The following functions to manipulate quad trees were implemented to realize this:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-add</span><span class="w">
  </span><span class="s">"Add tiles to quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="no">:map</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">tiles</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">add-title-to-quadtree</span><span class="w"> </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="p">[</span><span class="nb">path</span><span class="w"> </span><span class="n">tile</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">assoc-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="nb">path</span><span class="w"> </span><span class="n">tile</span><span class="p">))</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="nb">vector</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">tiles</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-extract</span><span class="w">
  </span><span class="s">"Extract a list of tiles from quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]]</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">get-in</span><span class="w"> </span><span class="n">tree</span><span class="p">)</span><span class="w"> </span><span class="n">paths</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-drop</span><span class="w">
  </span><span class="s">"Drop tiles specified by path list from quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="n">dissoc-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-update</span><span class="w">
  </span><span class="s">"Update tiles with specified paths using a function with optional arguments from lists"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]</span><span class="w"> </span><span class="n">fn?</span><span class="w"> </span><span class="p">[</span><span class="no">:*</span><span class="w"> </span><span class="no">:any</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">arglists</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">update-tile-in-quadtree</span><span class="w">
            </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="p">[</span><span class="nb">path</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]]</span><span class="w">
            </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">update-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="nb">path</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">map</span><span class="w"> </span><span class="nb">list</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">arglists</span><span class="p">)))</span></code></pre></figure>

<h2 id="other-topics">Other topics</h2>

<h3 id="solar-system">Solar system</h3>

<p>The astronomy code for getting the position and orientation of planets was implemented according to the <a href="https://rhodesmill.org/skyfield/">Skyfield</a> Python library.
The Python library in turn is based on the <a href="https://naif.jpl.nasa.gov/naif/index.html">SPICE</a> toolkit of the NASA JPL.
The JPL basically provides sequences of <a href="https://en.wikipedia.org/wiki/Chebyshev_polynomials">Chebyshev polynomials</a> to interpolate positions of Moon and planets as well as the orientation of the Moon as binary files.
Reference coordinate systems and orientations of other bodies are provided in text files which consist of human and machine readable sections.
The binary files were parsed using <em>Gloss</em> (see <a href="https://github.com/clj-commons/gloss/wiki/Introduction">Wiki</a> for some examples) and the text files using <em>Instaparse</em>.</p>

<h3 id="jolt-bindings">Jolt bindings</h3>

<p>The required Jolt functions for wheeled vehicle dynamics and collisions with meshes were wrapped in C functions and compiled into a shared library.
The <em>Coffi</em> Clojure library (which is a wrapper for Java’s new Foreign Function &amp; Memory API) was used to make the C functions and data types usable in Clojure.</p>

<p>For example the following code implements a call to the C function <em>add_force</em>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">defcfn</span><span class="w"> </span><span class="n">add-force</span><span class="w">
  </span><span class="s">"Apply a force in the next physics update"</span><span class="w">
  </span><span class="n">add_force</span><span class="w"> </span><span class="p">[</span><span class="no">::mem/int</span><span class="w"> </span><span class="no">::vec3</span><span class="p">]</span><span class="w"> </span><span class="no">::mem/void</span><span class="p">)</span></code></pre></figure>

<p>Here <em>::vec3</em> refers to a custom composite type defined using basic types.
The memory layout, serialisation, and deserialisation for <em>::vec3</em> are defined as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vec3-struct</span><span class="w">
  </span><span class="p">[</span><span class="no">::mem/struct</span><span class="w">
   </span><span class="p">[[</span><span class="no">:x</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]</span><span class="w">
    </span><span class="p">[</span><span class="no">:y</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]</span><span class="w">
    </span><span class="p">[</span><span class="no">:z</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]]])</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/c-layout</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">_vec3</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mem/c-layout</span><span class="w"> </span><span class="n">vec3-struct</span><span class="p">))</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/serialize-into</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">obj</span><span class="w"> </span><span class="n">_vec3</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">arena</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mem/serialize-into</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="no">:z</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">2</span><span class="p">)}</span><span class="w"> </span><span class="n">vec3-struct</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">arena</span><span class="p">))</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/deserialize-from</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">segment</span><span class="w"> </span><span class="n">_vec3</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">mem/deserialize-from</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">vec3-struct</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="p">(</span><span class="no">:x</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:y</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:z</span><span class="w"> </span><span class="n">result</span><span class="p">))))</span></code></pre></figure>

<h3 id="performance">Performance</h3>

<p>The <em>clj-async-profiler</em> was used to create flame graphs visualising the performance of the game.
In order to get reflection warnings for Java calls without sufficient type declarations, <em>*warn-on-reflection*</em> was set to <em>true</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">set!</span><span class="w"> </span><span class="n">*warn-on-reflection*</span><span class="w"> </span><span class="n">true</span><span class="p">)</span></code></pre></figure>

<p>Furthermore to discover missing declarations of numerical types, <em>*unchecked-math*</em> was set to <em>:warn-on-boxed</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">set!</span><span class="w"> </span><span class="n">*unchecked-math*</span><span class="w"> </span><span class="no">:warn-on-boxed</span><span class="p">)</span></code></pre></figure>

<p>To reduce garbage collector pauses, the ZGC low-latency garbage collector for the JVM was used.
The following section in <em>deps.edn</em> ensures that the ZGC garbage collector is used when running the project with <em>clj -M:run</em>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span><span class="w">
 </span><span class="no">:aliases</span><span class="w"> </span><span class="p">{</span><span class="no">:run</span><span class="w"> </span><span class="p">{</span><span class="no">:jvm-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-Xms2g"</span><span class="w"> </span><span class="s">"-Xmx4g"</span><span class="w"> </span><span class="s">"--enable-native-access=ALL-UNNAMED"</span><span class="w"> </span><span class="s">"-XX:+UseZGC"</span><span class="w">
                            </span><span class="s">"--sun-misc-unsafe-memory-access=allow"</span><span class="p">]</span><span class="w">
                 </span><span class="no">:main-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-m"</span><span class="w"> </span><span class="s">"sfsim.core"</span><span class="p">]}}}</span></code></pre></figure>

<p>The option to use ZGC is also specified in the Packr JSON file used to deploy the application.</p>

<h3 id="building-the-project">Building the project</h3>

<p>In order to build the map tiles, atmospheric lookup tables, and other data files using <em>tools.build</em>, the project source code was made available in the <em>build.clj</em> file using a <em>:local/root</em> dependency:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span><span class="w">
 </span><span class="no">:aliases</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
           </span><span class="no">:build</span><span class="w"> </span><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="n">io.github.clojure/tools.build</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"0.10.10"</span><span class="p">}</span><span class="w">
                          </span><span class="n">sfsim/sfsim</span><span class="w"> </span><span class="p">{</span><span class="no">:local/root</span><span class="w"> </span><span class="s">"."</span><span class="p">}}</span><span class="w">
                   </span><span class="no">:ns-default</span><span class="w"> </span><span class="n">build</span><span class="w">
                   </span><span class="no">:exec-fn</span><span class="w"> </span><span class="n">all</span><span class="w">
                   </span><span class="no">:jvm-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-Xms2g"</span><span class="w"> </span><span class="s">"-Xmx4g"</span><span class="w"> </span><span class="s">"--sun-misc-unsafe-memory-access=allow"</span><span class="p">]}}}</span></code></pre></figure>

<p>Various targets were defined to build the different components of the project.
For example the atmospheric lookup tables can be build by specifying <em>clj -T:build atmosphere-lut</em> on the command line.</p>

<p>The following section in the <em>build.clj</em> file was added to allow creating an “Uberjar” JAR file with all dependencies by specifying <em>clj -T:build uber</em> on the command-line.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">uber</span><span class="w"> </span><span class="p">[</span><span class="n">_</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/copy-dir</span><span class="w"> </span><span class="p">{</span><span class="no">:src-dirs</span><span class="w"> </span><span class="p">[</span><span class="s">"src/clj"</span><span class="p">]</span><span class="w">
               </span><span class="no">:target-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="p">})</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/compile-clj</span><span class="w"> </span><span class="p">{</span><span class="no">:basis</span><span class="w"> </span><span class="n">basis</span><span class="w">
                  </span><span class="no">:src-dirs</span><span class="w"> </span><span class="p">[</span><span class="s">"src/clj"</span><span class="p">]</span><span class="w">
                  </span><span class="no">:class-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="p">})</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/uber</span><span class="w"> </span><span class="p">{</span><span class="no">:class-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="w">
           </span><span class="no">:uber-file</span><span class="w"> </span><span class="s">"target/sfsim.jar"</span><span class="w">
           </span><span class="no">:basis</span><span class="w"> </span><span class="n">basis</span><span class="w">
           </span><span class="no">:main</span><span class="w"> </span><span class="ss">'sfsim.core</span><span class="p">}))</span></code></pre></figure>

<p>To create a Linux executable with Packr, one can then run <em>java -jar packr-all-4.0.0.jar scripts/packr-config-linux.json</em> where the JSON file has the following content:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"platform"</span><span class="p">:</span><span class="w"> </span><span class="s2">"linux64"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"jdk"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/usr/lib/jvm/jdk-24.0.2-oracle-x64"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"executable"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sfsim"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"classpath"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"target/sfsim.jar"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"mainclass"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sfsim.core"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"LICENSE"</span><span class="p">,</span><span class="w"> </span><span class="s2">"libjolt.so"</span><span class="p">,</span><span class="w"> </span><span class="s2">"venturestar.glb"</span><span class="p">,</span><span class="w"> </span><span class="s2">"resources"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"vmargs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Xms2g"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Xmx4g"</span><span class="p">,</span><span class="w"> </span><span class="s2">"XX:+UseZGC"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out-linux"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>In order to distribute the game on Steam, three depots were created:</p>

<ul>
  <li>a data depot with the operating system independent data files</li>
  <li>a Linux depot with the Linux executable and Uberjar including LWJGL’s Linux native bindings</li>
  <li>and a Windows depot with the Windows executable and an Uberjar including LWJGL’s Windows native bindings</li>
</ul>

<p>When updating a depot, the Steam ContentBuilder command line tool creates and uploads a patch in order to preserve storage space and bandwidth.</p>

<h2 id="future-work">Future work</h2>

<p>Although the hard parts are mostly done, there are still several things to do:</p>

<ul>
  <li>control surfaces and thruster graphics</li>
  <li>launchpad and runway graphics</li>
  <li>sound effects</li>
  <li>a 3D cockpit</li>
  <li>the Moon</li>
  <li>a space station</li>
</ul>

<p>It would also be interesting to make the game modable in a safe way (maybe evaluating Clojure files in a sandboxed environment?).</p>

<h2 id="conclusion">Conclusion</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/1PqmVLUt5_g" frameborder="0" allowfullscreen=""></iframe></div>

<p>You can find the <a href="https://github.com/wedesoft/sfsim">source code on Github</a>.
Currently there is only a playtest build, but if you want to get notified, when the game gets released, you can <a href="https://store.steampowered.com/app/3687560/sfsim/">wishlist it here</a>.</p>

<p>Anyway, let me know any comments and suggestions.</p>

<p>Enjoy!</p>

<h2 id="updates">Updates</h2>

<ul>
  <li>Submitted for discussion to Reddit <a href="https://www.reddit.com/r/Clojure/comments/1n9j5d6/developing_a_space_flight_simulator_in_clojure/">here</a></li>
  <li>See HackerNews discussion of this project <a href="https://news.ycombinator.com/item?id=45145794">here</a></li>
</ul>

<h2 id="related-blog-posts">Related blog posts</h2>

<ul>
  <li><a href="https://www.wedesoft.de/simulation/2025/06/06/flight-model-physics-venturestar/">Flight dynamics model for simulating Venturestar style spacecraft</a></li>
  <li><a href="https://www.wedesoft.de/software/2022/07/01/tdd-with-opengl/">Test Driven Development with OpenGL</a></li>
  <li><a href="https://www.wedesoft.de/software/2024/05/11/clojure-nuklear/">Implementing GUIs using Clojure and LWJGL Nuklear bindings</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Procedural Volumetric Clouds</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/03/20/procedural-global-cloud-cover/">Procedural generation of global cloud cover</a></li>
  <li><a href="https://www.wedesoft.de/software/2021/09/20/reversed-z-rendering/">Reversed-Z Rendering in OpenGL</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/12/25/clojure-function-schemas-with-malli/">Specifying Clojure function schemas with Malli</a></li>
  <li><a href="https://www.wedesoft.de/software/2024/07/05/clojure-instaparse/">Implement an Interpreter using Clojure Instaparse</a></li>
  <li><a href="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/">Orbits with Jolt Physics</a></li>
  <li><a href="https://www.wedesoft.de/simulation/2024/09/26/jolt-physics-engine/">Getting started with the Jolt Physics Engine</a></li>
  <li><a href="https://www.wedesoft.de/graphics/2023/09/29/blender-animate-bones-assimp/">Create Blender bones and animate and import with Assimp</a></li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/sfsim.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/sfsim.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Keyestudio Smart Home</title><link href="https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome/" rel="alternate" type="text/html" title="Keyestudio Smart Home" /><published>2025-08-30T00:00:00+01:00</published><updated>2025-08-30T00:00:00+01:00</updated><id>https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome</id><content type="html" xml:base="https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome/"><![CDATA[<p><img src="/pics/keyestudio-smarthome.jpg" alt="Keyestudio Smarthome" /></p>

<p>A few months ago I bought a Keyestudio Smart Home, assembled it and tried to program it using the Arduino IDE.
However I kept getting the following error when trying to upload a sketch to the board.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x2e
 avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x2e
 avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0x2e
</code></pre></div></div>

<p>Initially I thought it was an issue with the QinHeng Electronics CH340 serial converter driver software.
After exchanging a few emails with <a href="mailto:service@keyestudio.com">keyestudio support</a> however I was pointed out that the board type of my smart home version was not “Arduino Uno”.
The box of the control board says “Keyestudio Control Board for ESP-32” and I had to <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/docs/Arduino/arduino.html#add-the-esp32-environment-add-version-3-1-0">install version 3.1.3 of the esp32 board software</a> for being able to program the board.
I.e. the Keyestudio IoT Smart Home Kit for ESP32 is not to be confused with the Keyestudio Smart Home Kit for Arduino.</p>

<p>The documentation for the Keyestudio smart home using ESP-32 is <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/index.html">here</a>.
Also the correct version of the <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/docs/Arduino/arduino.html#resource-compression-package">smart home sketches are here</a>.
Finally you can find many sample projects in the <a href="https://www.keyestudio.com/blog/">keyestudio blog</a>.
Note that in some cases you have to adapt the io pin numbers using the smart home documentation.</p>

<p>Many thanks to Keyestudio support for helping me to get it working.</p>]]></content><author><name>Jan Wedekind</name></author><category term="hardware" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/keyestudio-smarthome.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/keyestudio-smarthome.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Orbits with Jolt Physics</title><link href="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/" rel="alternate" type="text/html" title="Orbits with Jolt Physics" /><published>2025-08-09T00:00:00+01:00</published><updated>2025-08-09T00:00:00+01:00</updated><id>https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics</id><content type="html" xml:base="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/"><![CDATA[<p>I want to simulate an orbiting spacecraft using the <a href="https://jrouwe.github.io/JoltPhysics/">Jolt Physics</a> engine (see <a href="https://wedesoft.github.io/sfsim/">sfsim homepage</a> for details).
The Jolt Physics engine solves difficult problems such as gyroscopic forces, collision detection with linear casting, and special solutions for wheeled vehicles with suspension.</p>

<p>The integration method of the Jolt Physics engine is the <a href="https://en.wikipedia.org/wiki/Semi-implicit_Euler_method">semi-implicit Euler method</a>.
The following formula shows how speed <strong>v</strong> and position <strong>x</strong> are integrated for each time step:</p>

<p><img src="/latex/latex-ad20098ed5c21ca7fd06a15740578819.png" alt="latex formula" /></p>

<p>The <a href="https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation#Gravity_field">gravitational acceleration by a planet</a> is given by:</p>

<p><img src="/latex/latex-ffbf92285485b91d12f8def635508db6.png" alt="latex formula" /></p>

<p>To test orbiting, one can set the initial conditions of the spacecraft to a <a href="https://en.wikipedia.org/wiki/Circular_orbit#Velocity">perfect circular orbit</a>:</p>

<p><img src="/latex/latex-4d8f12fdfd472cea76db061cb820993e.png" alt="latex formula" /></p>

<p>The orbital radius R was set to the Earth radius of 6378 km plus 408 km (the height of the ISS).
The Earth mass was assumed to be 5.9722e+24 kg.
For increased accuracy, the Jolt Physics library was compiled with the option <em>-DDOUBLE_PRECISION=ON</em>.</p>

<p>A full orbit was simulated using different values for the time step.
The following plot shows the height deviation from the initial orbital height over time.</p>

<p><img src="/pics/euler-height.png" alt="Orbits with symplectic Euler" /></p>

<p>When examining the data one can see that the integration method returns close to the initial after one orbit.
The orbital error of the Euler integration method looks like a sine wave.
Even for a small timestep of dt = 0.031 s, the maximum orbit deviation is 123.8 m.
The following plot shows that for increasing time steps, the maximum error grows linearly.</p>

<p><img src="/pics/euler-errors.png" alt="Euler orbit deviation as a function of time step" /></p>

<p>For time lapse simulation with a time step of 16 seconds, the errors will exceed 50 km.</p>

<p>A possible solution is to use Runge Kutta 4th order integration instead of symplectic Euler.
The 4th order Runge Kutta method can be implemented using a state vector consisting of position and speed:</p>

<p><img src="/latex/latex-f8c8f529e1df44626f23d326ae21a048.png" alt="latex formula" /></p>

<p>The derivative of the state vector consists of speed and gravitational acceleration:</p>

<p><img src="/latex/latex-312f19b81ae689393987f3b3e0669c91.png" alt="latex formula" /></p>

<p>The Runge Kutta 4th order integration method is as follows:</p>

<p><img src="/latex/latex-e92ee5fa4618b0c422a375fb31500e6a.png" alt="latex formula" /></p>

<p>The Runge Kutta method can be implemented in Clojure as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">runge-kutta</span><span class="w">
  </span><span class="s">"Runge-Kutta integration method"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="no">:some</span><span class="w"> </span><span class="no">:double</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="no">:some</span><span class="w"> </span><span class="no">:double</span><span class="p">]</span><span class="w"> </span><span class="no">:some</span><span class="p">]</span><span class="w"> </span><span class="n">add-schema</span><span class="w"> </span><span class="n">scale-schema</span><span class="p">]</span><span class="w"> </span><span class="no">:some</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="n">y0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">dy</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="nb">*</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">dt2</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
        </span><span class="n">k1</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="n">y0</span><span class="w">                </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
        </span><span class="n">k2</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt2</span><span class="w"> </span><span class="n">k1</span><span class="p">))</span><span class="w"> </span><span class="n">dt2</span><span class="p">)</span><span class="w">
        </span><span class="n">k3</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt2</span><span class="w"> </span><span class="n">k2</span><span class="p">))</span><span class="w"> </span><span class="n">dt2</span><span class="p">)</span><span class="w">
        </span><span class="n">k4</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt</span><span class="w">  </span><span class="n">k3</span><span class="p">))</span><span class="w"> </span><span class="n">dt</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="mf">6.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">[</span><span class="n">k1</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">k2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">k3</span><span class="p">)</span><span class="w"> </span><span class="n">k4</span><span class="p">])))))</span></code></pre></figure>

<p>The following code can be used to test the implementation:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="n">x</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Runge-Kutta integration method"</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">42.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">47.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">52.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">43.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">46.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">43.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="p">(</span><span class="nf">exp</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="n">e-2</span><span class="p">))</span></code></pre></figure>

<p>The Jolt Physics library allows to apply impulses to the spacecraft.
The idea is to use Runge Kutta 4th order integration to get an accurate estimate of the speed and position of the spacecraft after the next time step.
One can apply an impulse before running an Euler step so that the position after the Euler step matches the Runge Kutta estimate.
A second impulse then is used after the Euler time step to also make the speed match the Runge Kutta estimate.
Given the initial state <strong>(x(n), v(n))</strong> and the desired next state <strong>(x(n+1), v(n+1))</strong> (obtained from Runge Kutta) the formulas for the two impulses are as follows:</p>

<p><img src="/latex/latex-808b5ac1f696ea8fdba274924c73a895.png" alt="latex formula" /></p>

<p>The following code shows the implementation of the matching scheme using two speed changes in Clojure:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">matching-scheme</span><span class="w">
  </span><span class="s">"Use two custom acceleration values to make semi-implicit Euler result match a ground truth after the integration step"</span><span class="w">
  </span><span class="p">[</span><span class="n">y0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">y1</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="n">subtract</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">delta-speed0</span><span class="w"> </span><span class="p">(</span><span class="nf">scale</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="no">:position</span><span class="w"> </span><span class="n">y1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:position</span><span class="w"> </span><span class="n">y0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">scale</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y0</span><span class="p">))))</span><span class="w">
        </span><span class="n">delta-speed1</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y0</span><span class="p">))</span><span class="w"> </span><span class="n">delta-speed0</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">delta-speed0</span><span class="w"> </span><span class="n">delta-speed1</span><span class="p">]))</span></code></pre></figure>

<p>The following plot shows the height deviations observed when using Runge Kutta integration.</p>

<p><img src="/pics/rk-height.png" alt="Orbits with Runge Kutta 4th order" /></p>

<p>The following plot of maximum deviation shows that the errors are much smaller.</p>

<p><img src="/pics/rk-errors.png" alt="RK orbit deviation as a function of time step" /></p>

<p>Although the accuracy of the Runge Kutta matching scheme is higher, a loss of 40 m of height per orbit is undesirable.
Inspecting the Jolt Physics source code reveals that the double-precision setting affects position vectors but is not applied to speed and impulse vectors.
To test whether double precision speed and impulse vectors would increase the accuracy, a test implementation of the semi-implicit Euler method with Runge Kutta matching scheme was used.
The following plot shows that the orbit deviations are now much smaller.</p>

<p><img src="/pics/rk-double-height.png" alt="Orbits with Runge Kutta 4th order and double precision" /></p>

<p>The updated plot of maximum deviation shows that using double precision the error for one orbit is below 1 meter for time steps up to 40 seconds.</p>

<p><img src="/pics/rk-double-errors.png" alt="RK with double precision orbit deviation as a function of time step" /></p>

<p>I am currently looking into building a modified Jolt Physics version which uses double precision for speed and impulse vectors.
I hope that I will get the Runge Kutta 4th order matching scheme to work so that I get an integrated solution for numerically accurate orbits as well as collision and vehicle simulation.</p>

<p><strong>Update:</strong></p>

<p><a href="https://www.jrouwe.nl/">Jorrit Rouwé</a> has <a href="https://github.com/jrouwe/JoltPhysics/issues/1721">informed me</a> that he currently does not want to add <a href="https://github.com/jrouwe/JoltPhysics/discussions/1638">support for double precision speed values</a>.
He also has more detailed information about <a href="https://jrouwe.github.io/JoltPhysics/#space-simulations">using Jolt Physics for space simulation</a> on his website.</p>

<p>I have managed to get a prototype working using the moving coordinate system approach.
One can perform the Runge Kutta integration using double precision coordinates and speed vectors with the Earth at the centre of the coordinate system.
The Jolt Physics integration then happens in a coordinate system which is at the initial position and moving with the initial speed of the spaceship.
The first impulse of the matching scheme is applied and then the semi-implicit Euler integration step is performed using Jolt Physics with single precision speed vectors and impulses.
Then the second impulse is applied.
Finally the position and speed of the double precision moving coordinate system are incremented using the position and speed value of the Jolt Physics body.
The position and speed of the Jolt Physics body are then reset to zero and the next iteration begins.</p>

<p>The following plot shows the height deviations observed using this approach:</p>

<p><img src="/pics/rk-moving-height.png" alt="Orbits using moving coordinate system" /></p>

<p>The maximum errors for different time steps are shown in the following plot:</p>

<p><img src="/pics/rk-moving-errors.png" alt="Maximum errors with moving coordinate system as a function of time step" /></p>]]></content><author><name>Jan Wedekind</name></author><category term="simulation" /><summary type="html"><![CDATA[I want to simulate an orbiting spacecraft using the Jolt Physics engine (see sfsim homepage for details). The Jolt Physics engine solves difficult problems such as gyroscopic forces, collision detection with linear casting, and special solutions for wheeled vehicles with suspension.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/orbit.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/orbit.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Akima splines</title><link href="https://www.wedesoft.de/software/2025/06/14/akima-splines/" rel="alternate" type="text/html" title="Akima splines" /><published>2025-06-14T00:00:00+01:00</published><updated>2025-06-14T00:00:00+01:00</updated><id>https://www.wedesoft.de/software/2025/06/14/akima-splines</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/06/14/akima-splines/"><![CDATA[<p>Recently I was looking for spline interpolation for creating curves from a set of samples.
I knew cubic splines which are piecewise cubic polynomials fitted such that they are continuous up to the second derivative.
I almost went ahead and implemented cubic splines using a matrix solver but then I found that the <a href="https://github.com/generateme/fastmath">fastmath</a> Clojure library already provides splines.
The <a href="https://github.com/generateme/fastmath">fastmath</a> spline interpolation module is based on the <a href="https://haifengl.github.io/interpolation.html">interpolation module of the Java Smile library</a>.
I saved the interpolated samples to a text file and plotted them with <a href="http://www.gnuplot.info/">Gnuplot</a>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.interpolation</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">interpolation</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nf">use</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.java.shell</span><span class="w"> </span><span class="no">:only</span><span class="w"> </span><span class="p">[</span><span class="n">sh</span><span class="p">]])</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">9</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">py</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nf">spit</span><span class="w"> </span><span class="s">"/tmp/points.dat"</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="s">" "</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="s">"\n"</span><span class="p">))</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="n">py</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cspline</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation/cubic-spline</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="n">py</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">9.0</span><span class="w"> </span><span class="mf">0.01</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">spit</span><span class="w"> </span><span class="s">"/tmp/cspline.dat"</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="s">" "</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="s">"\n"</span><span class="p">))</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">cspline</span><span class="w"> </span><span class="n">x</span><span class="p">))))</span><span class="w">
</span><span class="p">(</span><span class="nf">sh</span><span class="w"> </span><span class="s">"gnuplot"</span><span class="w"> </span><span class="s">"-c"</span><span class="w"> </span><span class="s">"plot.gp"</span><span class="w"> </span><span class="s">"/tmp/cspline.png"</span><span class="w"> </span><span class="s">"/tmp/cspline.dat"</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">sh</span><span class="w"> </span><span class="s">"display"</span><span class="w"> </span><span class="s">"/tmp/cspline.png"</span><span class="p">)</span></code></pre></figure>

<p>I used the following Gnuplot script <em>plot.gp</em> for plotting:</p>

<figure class="highlight"><pre><code class="language-gnuplot" data-lang="gnuplot">set terminal pngcairo size 640,480
set output ARG1
set xlabel "x"
set ylabel "y"
plot ARG2 using 1:2 with lines title "spline", "/tmp/points.dat" using 1:2 with points title "points"</code></pre></figure>

<p>I used a lightweight configuration of the <em>fastmath</em> library without MKL and OpenBLAS.
See following <em>deps.edn</em>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="n">org.clojure/clojure</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.12.1"</span><span class="p">}</span><span class="w">
        </span><span class="n">generateme/fastmath</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"2.4.0"</span><span class="w"> </span><span class="no">:exclusions</span><span class="w"> </span><span class="p">[</span><span class="n">com.github.haifengl/smile-mkl</span><span class="w"> </span><span class="n">org.bytedeco/openblas</span><span class="p">]}}}</span></code></pre></figure>

<p>The result is shown in the following figure.
One can see that the spline is smooth and passes through all points, however it shows a high degree of oscillation:</p>

<p><img src="/pics/cspline.png" alt="cubic spline" /></p>

<p>However I found another spline algorithm in the <em>fastmath</em> wrappers: The <a href="https://en.wikipedia.org/wiki/Akima_spline">Akima spline</a>.
The Akima spline needs at least 5 points and it first computes the gradient of the lines connecting the points.
Then for each point it uses a weighted average of the previous and next slope value.
The slope values are weighted using the absolute difference of the previous two slopes and the next two slopes, i.e. the curvature.
The first and last two points use a special formula:
The first and last point use the next or previous slope and the second and second last point use an average of the neighbouring slopes.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.interpolation</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">interpolation</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nf">use</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.java.shell</span><span class="w"> </span><span class="no">:only</span><span class="w"> </span><span class="p">[</span><span class="n">sh</span><span class="p">]])</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">9</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">py</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nf">spit</span><span class="w"> </span><span class="s">"/tmp/points.dat"</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="s">" "</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="s">"\n"</span><span class="p">))</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="n">py</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">aspline</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation/akima-spline</span><span class="w"> </span><span class="n">px</span><span class="w"> </span><span class="n">py</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">9.0</span><span class="w"> </span><span class="mf">0.01</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">spit</span><span class="w"> </span><span class="s">"/tmp/aspline.dat"</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="s">" "</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="s">"\n"</span><span class="p">))</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">aspline</span><span class="w"> </span><span class="n">x</span><span class="p">))))</span><span class="w">
</span><span class="p">(</span><span class="nf">sh</span><span class="w"> </span><span class="s">"gnuplot"</span><span class="w"> </span><span class="s">"-c"</span><span class="w"> </span><span class="s">"plot.gp"</span><span class="w"> </span><span class="s">"/tmp/aspline.png"</span><span class="w"> </span><span class="s">"/tmp/aspline.dat"</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">sh</span><span class="w"> </span><span class="s">"display"</span><span class="w"> </span><span class="s">"/tmp/aspline.png"</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/aspline.png" alt="Akima spline" /></p>

<p>So if you have a data set which causes cubic splines to oscillate, give Akima splines a try!</p>

<p>Enjoy!</p>

<p><strong>Update:</strong> <a href="https://www.reddit.com/user/joinr/">u/joinr</a> showed, how you can use <a href="https://github.com/clojupyter/clojupyter">Clojupyter</a> to quickly <a href="https://joinr.github.io/demo.html">test a lot of splines</a>.</p>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[Recently I was looking for spline interpolation for creating curves from a set of samples. I knew cubic splines which are piecewise cubic polynomials fitted such that they are continuous up to the second derivative. I almost went ahead and implemented cubic splines using a matrix solver but then I found that the fastmath Clojure library already provides splines. The fastmath spline interpolation module is based on the interpolation module of the Java Smile library. I saved the interpolated samples to a text file and plotted them with Gnuplot.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/aspline.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/aspline.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Flight dynamics model for simulating Venturestar style spacecraft</title><link href="https://www.wedesoft.de/simulation/2025/06/06/flight-model-physics-venturestar/" rel="alternate" type="text/html" title="Flight dynamics model for simulating Venturestar style spacecraft" /><published>2025-06-06T00:00:00+01:00</published><updated>2025-06-06T00:00:00+01:00</updated><id>https://www.wedesoft.de/simulation/2025/06/06/flight-model-physics-venturestar</id><content type="html" xml:base="https://www.wedesoft.de/simulation/2025/06/06/flight-model-physics-venturestar/"><![CDATA[<p><img src="/pics/flight.jpg" alt="sfsim space flight simulator screenshot" /></p>

<p>This is an informational post on how to simulate the physics of atmospheric flight of a <a href="https://en.wikipedia.org/wiki/VentureStar">Venturestar</a> style single-stage-to-orbit space craft.
My dad <strong>Gerhard Wedekind</strong> is an experienced aerodynamics engineer and I asked him to help with making the aerodynamics of the <a href="https://wedesoft.github.io/sfsim/">sfsim</a> space flight simulator realistic to some extent.
The information in this post is a write-up of relevant formulas and approximate data he obtained from numerical simulation and estimates from aerodynamics knowledge.
The information provided in this article is for general informational purposes only and comes without any warranty, express or implied.</p>

<h2 id="simulation">Simulation</h2>

<p>Here are a few beautiful snapshots from simulation.
The first one shows a Mach box for V = 2 Ma and α = 3°.
<img src="/pics/venturestar-machbox-2-alpha-3.jpg" alt="Venturestar Mach Box for V = 2 Ma and α = 3°" /></p>

<p>The next one shows a Mach box for V = 4 Ma and α = 3°.
<img src="/pics/venturestar-machbox-4-alpha-3.jpg" alt="Venturestar Mach Box for V = 4 Ma and α = 3°" /></p>

<p>Finally here is a distribution of the pressure difference between top and bottom of wing.
<img src="/pics/venturestar-pressure-difference.jpg" alt="Venturestar Pressure Difference" /></p>

<h2 id="coordinate-systems">Coordinate systems</h2>

<p>The following drawing shows the body coordinate system (xb, yb, zb) and the wind coordinate system (xw, yw, zw).
The wind system is rotated against the body system so that the speed vector (in a stationary atmosphere) points in positive xw.</p>

<p><img src="/pics/windsystem.png" alt="coordinate systems" /></p>

<p>Note that lift, drag, and side force are defined in the wind system and not in the body system.</p>
<ul>
  <li>A positive lift force points upwards (negative zw) in the wind system.</li>
  <li>The drag force points backwards (negative xw) in the wind system.</li>
  <li>A positive side force points starboard (positive yw) in the wind system.</li>
</ul>

<p>Yaw, pitch, and roll moments on the other hand are specified in the body system.</p>

<p>A coordinate system transformation from body system to wind system can be performed using two angles:</p>
<ul>
  <li><em>α</em> is the angle of attack</li>
  <li><em>β</em> is the sideslip angle</li>
</ul>

<p>When transforming coordinates from body system to wind system, one first rotates by <em>β</em> (sideslip angle) about the body z axis (zb).
Then one rotates by <em>α</em> (angle of attack) about the new y axis.</p>

<h2 id="dynamic-pressure">Dynamic pressure</h2>

<p>The dynamic pressure <em>q</em> depends on air density <em>ρ</em> and speed <em>V</em>:
<img src="/latex/latex-61917fcda0a00517e51d15b9d1d024ad.png" alt="latex formula" /></p>

<p>Air density (and temperature) as a function of height can be obtained from <a href="https://aerostarsolutions.wordpress.com/wp-content/uploads/2011/10/fundmentals_of_airplane_flight_mechanics.pdf">Hull’s book “Fundamentals of airplane flight mechanics”</a>.</p>

<h2 id="forces">Forces</h2>

<p>Drag consists of zero-lift drag and induced drag:
<img src="/latex/latex-2232bad8d24f908e9b0f39b506502765.png" alt="latex formula" /></p>

<p>Zero-lift drag is computed using the zero-lift drag coefficient <em>CD0</em> as well as dynamic pressure <em>q</em> and the reference area <em>Sref</em>:
<img src="/latex/latex-0b96010d8bc0bf6e57bb15664a604dd1.png" alt="latex formula" />
The zero-lift drag coefficient depends on the speed of the aircraft.</p>

<p>Induced drag is determined using the lift coefficient <em>CL</em>, the Oswald factor <em>e</em>, the aspect ratio <em>Λ</em>, as well as <em>q</em> and the reference area <em>Sref</em>.
<img src="/latex/latex-8803ac71c8bba75f36658710105c6a16.png" alt="latex formula" />
The Oswald factor <em>e</em> depends on the speed of the aircraft.
The lift coefficient depends on the angle of attack <em>α</em>.</p>

<p>The aspect ratio <em>Λ</em> depends on wing span <em>b</em> and wing area <em>S</em>:
<img src="/latex/latex-fe3cbe64678f3767eb4cc1657ec76f1b.png" alt="latex formula" /></p>

<p>The lift <em>L</em> is computed using the lift coefficient <em>CL</em>, dynamic pressure <em>q</em>, and the reference area <em>Sref</em>:
<img src="/latex/latex-d1de526abf14900c78000878ac0eb836.png" alt="latex formula" /></p>

<p>The side force <em>Y</em> (and corresponding coefficient) is usually not important but we will look into it <s>later</s> in a future article.</p>

<h2 id="moments">Moments</h2>

<p>The pitching moment <em>M</em> is computed using the pitching moment coefficient <em>Cm</em>, the dynamic pressure <em>q</em>, the reference area <em>Sref</em>, and the aerodynamic chord <em>cbar</em>:
<img src="/latex/latex-4cfea729278a8de18114507d4a32ddda.png" alt="latex formula" />
The pitching moment coefficient depends on the lift coefficient <em>CL</em>, the position of the neutral point <em>XN</em>, the centre of gravity <em>xref</em>. and the aerodynamic chord <em>cbar</em>:
<img src="/latex/latex-3025283ef254f11a50012694065ba1d2.png" alt="latex formula" /></p>

<p>The yawing moment <em>N</em> is the product of the yawing moment coefficient <em>Cn</em>, the dynamic pressure <em>q</em>, the reference area <em>Sref</em>, and half the wing span <em>b</em>:
<img src="/latex/latex-63601227392b7a8574eb6ef1c009273d.png" alt="latex formula" />
The yawing moment coefficient depends on the side slip angle <em>β</em>.</p>

<p>The rolling moment <em>L</em> (using the same symbol as lift for some reason) is the product of the rolling moment coefficient <em>Cl</em>, the dynamic pressure <em>q</em>, the reference area <em>Sref</em>, and half the wing span <em>b</em>:
<img src="/latex/latex-828f453a0047e0dcce5fb1ab6a8185fa.png" alt="latex formula" />
The rolling moment coefficient depends on the angle of attack <em>α</em> and the side slip angle <em>β</em>.</p>

<h2 id="data-sheet">Data Sheet</h2>

<p>Here are the parameters for the flight model above:</p>

<p><img src="/latex/latex-6653d7d945eb7c1b0709865d4f3e68f6.png" alt="latex formula" /></p>

<p>Note that <em>xref</em> is defined in a coordinate system where <em>x=0</em> is at the intersection of the inner leading edges (wing apex).
The following picture also shows the position of the aerodynamic chord with length <em>cbar</em>.
The center of gravity is at 25% of the aerodynamic chord.</p>

<p><img src="/pics/leadingedges.jpg" alt="coordinate system origin at intersection of leading edges" /></p>

<h2 id="tables">Tables</h2>

<p>Here is a data table with information for determining the remaining coefficients depending on the airspeed in Mach (Ma).
The table shows for each speed:</p>
<ul>
  <li>a factor to determine the lift coefficient <em>CL</em></li>
  <li>the position <em>XN</em> of the neutral point relative to the aerodynamic chord (note that the center of gravity <em>xref</em> is at the 25% mark of the aerodynamic chord)</li>
  <li>the Oswald factor <em>e</em></li>
  <li>a factor to determine the rolling moment coefficient <em>Cl</em></li>
  <li>a factor to determine the yawing moment coefficient <em>Cn</em></li>
  <li>the zero-lift drag coefficient <em>CD0</em></li>
</ul>

<p><img src="/latex/latex-d98d5f5e7d65a36b5eb1a4f54ea2c065.png" alt="latex formula" /></p>

<p>The outlier of Clβα for V = 1.2 Ma (0.5971) should be ignored because the value was changing a lot with mesh resolution.</p>

<p>The speed of sound as a function of temperature <em>T</em> is <a href="https://en.wikipedia.org/wiki/Speed_of_sound">according to Wikipedia</a>:
<img src="/latex/latex-1c15e4bdc0111fe286439dd8b285f90c.png" alt="latex formula" /></p>

<p>For small values of <em>α</em>, the lift coefficient increases linearly with <em>α</em> (where <em>α</em> is specified in radians):
<img src="/latex/latex-41af46ba01973080a2950487d182dce4.png" alt="latex formula" /></p>

<p>For small values of <em>α</em> and <em>β</em>, the rolling moment coefficient increases linearly with the product of <em>α</em> and <em>β</em> (where <em>α</em> and <em>β</em> are specified in radians).
This is a particular behaviour of delta wing configurations.
If there is side slip, the wings generate different amounts of lift causing a significant roll moment:
<img src="/latex/latex-da7cfe269b344d186a1cf9f27f9a9d82.png" alt="latex formula" /></p>

<p>For small values of <em>β</em>, the yawing moment coefficient increases linearly with <em>β</em> (where <em>β</em> is specified in radians):
<img src="/latex/latex-4a15a249d8942b72ce122adae44d2119.png" alt="latex formula" /></p>

<p>The following table shows for each speed:</p>
<ul>
  <li>the value for <em>α</em> at which the linear relation of <em>CL</em> and <em>α</em> breaks down</li>
  <li>the maximum value of <em>CL</em></li>
  <li>the angle of attack where <em>CL</em> reaches its maximum</li>
  <li>the drag coefficient for 90° angle of attack</li>
</ul>

<p><img src="/latex/latex-efff13037553d99fa1fd5ba072a30932.png" alt="latex formula" /></p>

<p>Near <em>α=90°</em>, the lift and drag coefficients behave as follows:
<img src="/latex/latex-59165aaf358628c2076a0993f0f5c65e.png" alt="latex formula" /></p>

<p>At hypersonic speeds (V/Ma=10.0), lift and induced drag coefficients behave as follows:
<img src="/latex/latex-a741cdb4aa67fd93a6bd33eb85198411.png" alt="latex formula" /></p>

<p>I.e. the coefficients are stabilising at hypersonic speeds!</p>

<h2 id="control-surfaces">Control surfaces</h2>

<p>The following table shows parameters to determine different moments generated by control surfaces:</p>

<p><img src="/latex/latex-695c339e3ad705bc84153640b6975e8d.png" alt="latex formula" /></p>

<p>The side force coefficient for a given rudder angle <em>ζ</em> is:
<img src="/latex/latex-7b01dc34d3d3945446acf3a50fdb8b4f.png" alt="latex formula" />
The yawing moment coefficient for the rudder is:
<img src="/latex/latex-8f29616295d44e355625ccc9a31b2211.png" alt="latex formula" /></p>

<p>The pitching moment coefficient for flaps <em>δF</em> (down is positive) is
<img src="/latex/latex-b0add0b05a9d6d2b0247f0b5d3e140db.png" alt="latex formula" /></p>

<p>The rolling moment coefficient for ailerons with angle <em>ξ</em> (positive: port aileron up, starboard aileron down) is:
<img src="/latex/latex-f8c91df7a8d89dff80a2ea5220010a93.png" alt="latex formula" />
The yawing moment coefficient is
<img src="/latex/latex-75a4f8a8cedf8d0715bf0bcf9b6ac0ba.png" alt="latex formula" /></p>

<h2 id="angular-damping">Angular damping</h2>

<p>The formula for roll, pitch, and yaw damping moments (L, M, N) due to roll, pitch, and yaw rates (p, q, r) uses a coefficient matrix:
<img src="/latex/latex-7dd956cd68f08c2c62df30a7c106d06d.png" alt="latex formula" />
The coefficients for <em>V = 0 Ma</em> are as follows.
Note that damping moments are negligible for higher speeds.
<img src="/latex/latex-0f48c6da48eebd9ea30f9c5eee446ace.png" alt="latex formula" />
The last (10th) value in the table <em>CLq</em> is the change in the lift coefficient due to the pitch rate q.</p>

<h2 id="next-steps">Next steps</h2>

<p>Using the information, the curves for a full range of angles and speeds need to be fitted and guessed in some places.</p>

<p>Feel free to leave a comment or suggestion below.</p>

<h2 id="updates">Updates</h2>

<ul>
  <li>Found an interesting paper: <a href="https://www.researchgate.net/publication/268557220_Continuous_Aerodynamic_Modelling_of_Entry_Shapes">Dominic Dirkx, Erwin Mooij: Continuous Aerodynamic Modelling of Entry Shapes</a></li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="simulation" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/flight.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/flight.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The Connascence Software Design Metric explained using Python Code Examples</title><link href="https://www.wedesoft.de/software/2025/04/29/connascence/" rel="alternate" type="text/html" title="The Connascence Software Design Metric explained using Python Code Examples" /><published>2025-04-29T00:00:00+01:00</published><updated>2025-04-29T00:00:00+01:00</updated><id>https://www.wedesoft.de/software/2025/04/29/connascence</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/04/29/connascence/"><![CDATA[<p>The following presentation is a short introduction to the <em>connascence</em> software design metric.
Connascence is a useful tool to understand and reduce the degree of coupling in software systems and make software more modular.</p>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/bAB0XYcmM2I" frameborder="0" allowfullscreen=""></iframe></div>

<p>Click above image to watch the 10 minutes presentation.</p>

<p>You can get the slides of the presentation here: <a href="/downloads/connascence.pdf">connascence.pdf</a></p>

<p>See <a href="https://github.com/wedesoft/connascence">github.com/wedesoft/connascence</a> for source code of slides.</p>

<p>Enjoy!</p>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[The following presentation is a short introduction to the connascence software design metric. Connascence is a useful tool to understand and reduce the degree of coupling in software systems and make software more modular.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/connascence.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/connascence.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>