<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://baalex.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://baalex.github.io/" rel="alternate" type="text/html" /><updated>2026-10-23T03:27:05+00:00</updated><id>https://baalex.github.io/feed.xml</id><title type="html">Notes for the unfortunate creature now reading</title><subtitle>Personal blog.</subtitle><author><name>Alexander Brandt</name></author><entry><title type="html">Harmonic oscillator, I turned jiggle physics code into a synthesizer</title><link href="https://baalex.github.io/harmonic-oscillator" rel="alternate" type="text/html" title="Harmonic oscillator, I turned jiggle physics code into a synthesizer" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://baalex.github.io/harmonic-oscillator</id><content type="html" xml:base="https://baalex.github.io/harmonic-oscillator"><![CDATA[<p>Look at this thing, isn’t it pretty?:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="n">HarmonicOscillator</span><span class="o">::</span><span class="n">Step</span><span class="p">()</span>
<span class="p">{</span>
   <span class="n">m_v</span> <span class="o">-=</span> <span class="n">m_v</span> <span class="o">*</span> <span class="n">m_zeta</span> <span class="o">+</span> <span class="n">m_x</span> <span class="o">*</span> <span class="n">m_omega</span><span class="p">;</span>
   <span class="n">m_x</span> <span class="o">+=</span> <span class="n">m_v</span><span class="p">;</span>
   <span class="k">return</span> <span class="n">m_x</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I have been fascinated by it because of how much is capable of do. It oscillates sinusoidally (and sounds good), it decays exponentially in a user defined length (one might expect frequency and decay to be tied together, not the case), amplitude can also be defined (which saves a multiplication in later stages), and… ok. There are cons like exploding at low sampling frequencies, tone frequency drifting on long durations, it always decays, but, look at it. It’s so short.</p>

<p>Here you can see it in action, with a little extra on top:</p>

<div class="flex">
	<script src="/assets/harmonic-oscillator.js"></script>
	<span>
		<label>Frequency:<input type="range" id="frequency" min="55" max="880" value="220" list="f_markers" /></label>
		<label>Decay:<input type="range" id="decay" min="1" max="5000" value="1000" list="d_markers" /></label>
		<label>Amplitude:<input type="range" id="amplitude" min="0" max="1" value="0.5" list="a_markers" step="0.01" /></label>
		<label>Sweep:<input type="range" id="sweep" min="-8" max="4" value="0" list="s_markers" step="0.01" /></label>
		<button id="play">Play</button>
		<button id="stop">Stop</button>

		<datalist id="f_markers"><option value="220"></option></datalist>
		<datalist id="d_markers"><option value="1000"></option></datalist>
		<datalist id="a_markers"><option value="0.5"></option></datalist>
		<datalist id="s_markers"><option value="0"></option></datalist>
	</span>
	<div id="canvas_container" class="block_container_w100">
		<canvas id="canvas" width="300" height="100">Html5 canvas not supported.</canvas>
	</div>
</div>

<p>Proper introduction is that it’s an <a href="https://en.wikipedia.org/wiki/Harmonic_oscillator">Harmonic Oscillator</a>, also a <a href="https://en.wikipedia.org/wiki/Mass-spring-damper_model">Mass-spring-damper Model</a>, more commonly known as “jiggle physics”, especially in video-games and related fields. Code above is a Semi-Implicit Euler implementation, I stitched it together from <a href="https://allenchou.net/2015/04/game-math-precise-control-over-numeric-springing/">Precise Control over Numeric Springing</a> by Allen Chou (2015), and <a href="https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations/">The physics behind spring animations</a> by Maxime Heckel (2020). Here is a comparison with Implicit Euler without me simplifying zeta and omega:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">SpringSemiImplicit</span><span class="p">(</span><span class="kt">float</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="kt">float</span> <span class="o">&amp;</span><span class="n">v</span><span class="p">,</span> <span class="kt">float</span> <span class="n">x_target</span><span class="p">,</span> <span class="kt">float</span> <span class="n">zeta</span><span class="p">,</span> <span class="kt">float</span> <span class="n">omega</span><span class="p">,</span> <span class="kt">float</span> <span class="n">h</span><span class="p">)</span>
<span class="p">{</span>
   <span class="n">v</span> <span class="o">+=</span> <span class="o">-</span><span class="mf">2.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">h</span> <span class="o">*</span> <span class="n">zeta</span> <span class="o">*</span> <span class="n">omega</span> <span class="o">*</span> <span class="n">v</span> <span class="o">+</span> <span class="n">h</span> <span class="o">*</span> <span class="n">omega</span> <span class="o">*</span> <span class="n">omega</span> <span class="o">*</span> <span class="p">(</span><span class="n">x_target</span> <span class="o">-</span> <span class="n">x</span><span class="p">);</span>
   <span class="n">x</span> <span class="o">+=</span> <span class="n">h</span> <span class="o">*</span> <span class="n">v</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">SpringImplicit</span><span class="p">(</span><span class="kt">float</span> <span class="o">&amp;</span><span class="n">x</span><span class="p">,</span> <span class="kt">float</span> <span class="o">&amp;</span><span class="n">v</span><span class="p">,</span> <span class="kt">float</span> <span class="n">x_target</span><span class="p">,</span> <span class="kt">float</span> <span class="n">zeta</span><span class="p">,</span> <span class="kt">float</span> <span class="n">omega</span><span class="p">,</span> <span class="kt">float</span> <span class="n">h</span><span class="p">)</span>
<span class="p">{</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">f</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">+</span> <span class="mf">2.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">h</span> <span class="o">*</span> <span class="n">zeta</span> <span class="o">*</span> <span class="n">omega</span><span class="p">;</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">oo</span> <span class="o">=</span> <span class="n">omega</span> <span class="o">*</span> <span class="n">omega</span><span class="p">;</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">hoo</span> <span class="o">=</span> <span class="n">h</span> <span class="o">*</span> <span class="n">oo</span><span class="p">;</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">hhoo</span> <span class="o">=</span> <span class="n">h</span> <span class="o">*</span> <span class="n">hoo</span><span class="p">;</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">detInv</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="p">(</span><span class="n">f</span> <span class="o">+</span> <span class="n">hhoo</span><span class="p">);</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">detX</span> <span class="o">=</span> <span class="n">f</span> <span class="o">*</span> <span class="n">x</span> <span class="o">+</span> <span class="n">h</span> <span class="o">*</span> <span class="n">v</span> <span class="o">+</span> <span class="n">hhoo</span> <span class="o">*</span> <span class="n">x_target</span><span class="p">;</span>
   <span class="k">const</span> <span class="kt">float</span> <span class="n">detV</span> <span class="o">=</span> <span class="n">v</span> <span class="o">+</span> <span class="n">hoo</span> <span class="o">*</span> <span class="p">(</span><span class="n">x_target</span> <span class="o">-</span> <span class="n">x</span><span class="p">);</span>
   <span class="n">x</span> <span class="o">=</span> <span class="n">detX</span> <span class="o">*</span> <span class="n">detInv</span><span class="p">;</span>
   <span class="n">v</span> <span class="o">=</span> <span class="n">detV</span> <span class="o">*</span> <span class="n">detInv</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Differences are stability and that I didn’t manage to tame the implicit form. If we are pursuing stability, here is an implementation that seems to tackle every single corner <a href="https://theorangeduck.com/page/spring-roll-call">Spring-It-On: The Game Developer’s Spring-Roll-Call</a> by Daniel Holden (2021). Price to pay tho, is that uses conditionals, trigonometric functions, and it’s long. However, we are doing audio here; code there is for procedural animations, and they truly need to survive wild frame-rate roller coasters.</p>

<h2 id="step-function">Step() function</h2>

<p>Here is my <code class="language-plaintext highlighter-rouge">Step()</code> function in context:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">HarmonicOscillator</span><span class="o">::</span><span class="n">Initialise</span><span class="p">(</span><span class="kt">float</span> <span class="n">sampling_frequency</span><span class="p">,</span> <span class="kt">float</span> <span class="n">frequency</span><span class="p">,</span> <span class="kt">float</span> <span class="n">decay_frames</span><span class="p">,</span> <span class="kt">float</span> <span class="n">amplitude</span><span class="p">)</span>
<span class="p">{</span>
   <span class="c1">// This Initialise() is incomplete,</span>
   <span class="c1">// m_omega, m_zeta, m_x, and m_v, are explained later</span>
   <span class="n">m_zeta</span> <span class="o">=</span> <span class="mf">2.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">m_zeta</span> <span class="o">*</span> <span class="n">m_omega</span><span class="p">;</span>
   <span class="n">m_omega</span> <span class="o">=</span> <span class="n">m_omega</span> <span class="o">*</span> <span class="n">m_omega</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">float</span> <span class="n">HarmonicOscillator</span><span class="o">::</span><span class="n">StepWithSweep</span><span class="p">(</span><span class="kt">float</span> <span class="n">sweep</span><span class="p">)</span>
<span class="p">{</span>
   <span class="n">m_v</span> <span class="o">-=</span> <span class="n">m_v</span> <span class="o">*</span> <span class="n">m_zeta</span> <span class="o">+</span> <span class="n">m_x</span> <span class="o">*</span> <span class="n">m_omega</span> <span class="o">*</span> <span class="n">sweep</span><span class="p">;</span>
   <span class="n">m_x</span> <span class="o">+=</span> <span class="n">m_v</span> <span class="o">*</span> <span class="n">sweep</span><span class="p">;</span>
   <span class="k">return</span> <span class="n">m_x</span><span class="p">;</span>

   <span class="c1">// Chou's form:</span>
   <span class="c1">// m_v += -2.0f * h * m_zeta * m_omega * m_v + h * m_omega * m_omega * (x_target - m_x);</span>
   <span class="c1">// m_x += h * m_v;</span>
   <span class="c1">// return m_x;</span>
<span class="p">}</span>

<span class="kt">float</span> <span class="n">HarmonicOscillator</span><span class="o">::</span><span class="n">Step</span><span class="p">()</span>
<span class="p">{</span>
   <span class="n">m_v</span> <span class="o">-=</span> <span class="n">m_v</span> <span class="o">*</span> <span class="n">m_zeta</span> <span class="o">+</span> <span class="n">m_x</span> <span class="o">*</span> <span class="n">m_omega</span><span class="p">;</span>
   <span class="n">m_x</span> <span class="o">+=</span> <span class="n">m_v</span><span class="p">;</span>
   <span class="k">return</span> <span class="n">m_x</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Changes from Chou’s <code class="language-plaintext highlighter-rouge">SpringSemiImplicit()</code> aren’t that many. In <code class="language-plaintext highlighter-rouge">StepWithSweep()</code> the most noticeable one is that <code class="language-plaintext highlighter-rouge">x_target</code> is zero, thus deleted, this turns a spring model into an oscillator (audio signals should oscillate around zero). Done that, expressions <code class="language-plaintext highlighter-rouge">omega * omega</code> and <code class="language-plaintext highlighter-rouge">2.0f * zeta * omega</code> are resolved at initialisation rather than every frame. Of the three <code class="language-plaintext highlighter-rouge">h</code>, time-steps, two are repurposed as sweep, and one deleted. This is what in game development is known as <code class="language-plaintext highlighter-rouge">dt</code>, delta time, generally used to counter frame-rate changes by scaling motion/animations. Here it’s scaling oscillation frequency, and only frequency (more on this later). By keeping the three original <code class="language-plaintext highlighter-rouge">h</code> we will also scale decay, and we don’t want that.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">Step()</code> only differs on assuming a sweep or time-step of 1, thus deleted.</p>

<h2 id="initialisation-function">Initialisation() function</h2>

<p>Grab a cup of something; it looks like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">HarmonicOscillator</span><span class="o">::</span><span class="n">Initialise</span><span class="p">(</span><span class="kt">float</span> <span class="n">sampling_frequency</span><span class="p">,</span> <span class="kt">float</span> <span class="n">frequency</span><span class="p">,</span> <span class="kt">float</span> <span class="n">decay_frames</span><span class="p">,</span> <span class="kt">float</span> <span class="n">amplitude</span><span class="p">)</span>
<span class="p">{</span>
   <span class="n">m_omega</span> <span class="o">=</span> <span class="p">(</span><span class="n">frequency</span> <span class="o">/</span> <span class="n">sampling_frequency</span><span class="p">)</span> <span class="o">*</span> <span class="n">M_PI</span> <span class="o">*</span> <span class="mf">2.0</span><span class="n">f</span><span class="p">;</span>
   <span class="n">m_zeta</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">-</span> <span class="n">expf</span><span class="p">(</span><span class="n">logf</span><span class="p">(</span><span class="mf">0.001</span><span class="n">f</span><span class="p">)</span> <span class="o">/</span> <span class="n">decay_frames</span><span class="p">);</span>

   <span class="n">m_x</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
   <span class="n">m_v</span> <span class="o">=</span> <span class="n">m_omega</span> <span class="o">*</span> <span class="n">amplitude</span><span class="p">;</span>

   <span class="n">m_zeta</span> <span class="o">=</span> <span class="n">m_zeta</span> <span class="o">*</span> <span class="mf">2.0</span><span class="n">f</span><span class="p">;</span>
   <span class="n">m_omega</span> <span class="o">=</span> <span class="n">m_omega</span> <span class="o">*</span> <span class="n">m_omega</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">m_omega</code> is usual frequency business, now <code class="language-plaintext highlighter-rouge">m_zeta</code> is far more interesting, it’s a non-standard half-life function to control decay. How does it work?, this talk is gold: <a href="https://www.youtube.com/watch?v=LSNQuFEDOyQ">Lerp smoothing is broken</a> by Freya Holmér (2024), she covers it under “The Exact Damper” section.</p>

<p>What happens is that in the <code class="language-plaintext highlighter-rouge">Step()</code> function, the term <code class="language-plaintext highlighter-rouge">m_v -= m_v * m_zeta</code> is exponential. By using a <code class="language-plaintext highlighter-rouge">m_zeta</code> less than 1, <code class="language-plaintext highlighter-rouge">m_v</code> will approach zero without ever reaching it, with every step being smaller than previous one. We can see it by setting <code class="language-plaintext highlighter-rouge">m_v = 1; m_zeta = 0.5</code> and iterating on it:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">m_v = 1.0 - 1.0 * 0.5</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.5 - 0.5 * 0.5</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.25 - 0.25 * 0.5</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.125 - 0.125 * 0.5</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.0625 - 0.0625 * 0.5</code></li>
</ol>

<p>It looks nice on a plot, our real problem then is to find a <code class="language-plaintext highlighter-rouge">m_zeta</code> from a desired number of steps, or audio frames. Enter this monstrosity: <code class="language-plaintext highlighter-rouge">m_zeta = 1 - exp(log(0.125) / 3)</code>, it will give us a <code class="language-plaintext highlighter-rouge">m_zeta</code> value so in 3 frames we will be near or at 0.125, and it’s: 0.5, makes sense. What about 7 frames to be near or at 0.333?, it says that a <code class="language-plaintext highlighter-rouge">m_zeta</code> of 0.14537076 is required, let’s see:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">m_v = 1.0 - 1.0 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.854 - 0.854 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.730 - 0.730 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.624 - 0.624 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.533 - 0.533 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.455 - 0.455 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.389 - 0.389 * 0.14537076</code></li>
  <li><code class="language-plaintext highlighter-rouge">m_v = 0.333 - 0.333 * 0.14537076</code></li>
</ol>

<p>Spooky. No more questions asked, it works. One wee tiny bitty problem tho, as said before, it approaches zero without ever reaching it, so to stop this the best solution is to simply do it at a tiny value. However that 0.001 in <code class="language-plaintext highlighter-rouge">m_zeta = 1.0f - expf(logf(0.001f) / decay_frames)</code> is not your typical <code class="language-plaintext highlighter-rouge">EPSILON</code>, it’s -60dB (decibels).</p>

<p>Finally, the rest. <code class="language-plaintext highlighter-rouge">m_v = m_omega * amplitude</code> is only a value to compensate for this part <code class="language-plaintext highlighter-rouge">m_v -= (...) + this.x * this.omega</code> in <code class="language-plaintext highlighter-rouge">Step()</code>, it can be zero, nothing oscillates in that case, and you can set it later.</p>

<p>One room in the elephant tho, suddenly the expression <code class="language-plaintext highlighter-rouge">m_zeta = m_zeta * 2.0f</code> lost a multiplication between the multiple <code class="language-plaintext highlighter-rouge">Initialise()</code> functions I presented here. What happened is me doing a last optimisation that Chou compensated it with a division, not a biggie. Finally, no way I’m questioning <code class="language-plaintext highlighter-rouge">m_omega = m_omega * m_omega</code> existence (nor the <code class="language-plaintext highlighter-rouge">* 2.0f</code>), my DSP brain is warning me that these are the coefficients of a filter, and filters are truly, truly spooky.</p>

<h2>***</h2>

<p>Should mankind use this?, is it just novelty code?. Good question. My main premise here is that two multiplications, two additions, and one subtraction, are doing a lot of stuff. Now, who in the XXI century is counting assembly instructions?. Also, I didn’t mention use-cases, nor results on how it explodes and when, oh well… see you later!, thanks for reading!.</p>]]></content><author><name>Alexander Brandt</name></author><category term="Audio" /><category term="Synthesizer" /><category term="Physics" /><summary type="html"><![CDATA[Look at this thing, isn’t it pretty?:]]></summary></entry><entry><title type="html">Polytopes, or: How to convert a convex shape/body defined by planes into vertices without dying in the process</title><link href="https://baalex.github.io/polytopes" rel="alternate" type="text/html" title="Polytopes, or: How to convert a convex shape/body defined by planes into vertices without dying in the process" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://baalex.github.io/polytopes</id><content type="html" xml:base="https://baalex.github.io/polytopes"><![CDATA[<p>Surprisingly isn’t that hard… as long as you’re not concerned about performance or doing it in real time. In my first, and suddenly final attempt at doing it I was able to process a quite large map in around 2 ms (in a single thread on an Intel N95). I truly was expecting something horrible measured in seconds, but no, it was just the academic papers that scared me. Though, in their defence they were concerned about million of planes from weird malformed data. In my case I’m just parsing a MAP file made by a human being in a map editor (MAP file a la Quake 1, 2, 3, Half-Life, Doom 3, Half-Life 2, that kind).</p>

<p>Taking a face-centric approach, the problem to solve revolves around finding the vertices that form a particular face, done that, what remains is to repeat the algorithm through every face of the shape. It looks like this:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">Face</span>
<span class="p">{</span>
   <span class="n">Plane</span> <span class="n">plane</span><span class="p">;</span> <span class="c1">// Where this face lies</span>
   <span class="kt">bool</span> <span class="n">visible</span><span class="p">;</span>
   <span class="n">List</span><span class="o">&lt;</span><span class="n">Vector3</span><span class="o">&gt;</span> <span class="n">vertices</span><span class="p">;</span> <span class="c1">// What we want to calculate</span>
<span class="p">};</span>

<span class="kt">void</span> <span class="n">CalculateVertices</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Face</span><span class="o">&gt;*</span> <span class="n">faces</span><span class="p">)</span>
<span class="p">{</span>
   <span class="k">for</span> <span class="p">(</span><span class="n">Face</span><span class="o">*</span> <span class="n">f</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
   <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">visible</span> <span class="o">==</span> <span class="nb">false</span><span class="p">)</span>
         <span class="k">continue</span><span class="p">;</span>

      <span class="c1">// Intersect plane where face lies against other planes</span>
      <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f2</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f2</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f2</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
      <span class="p">{</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">f</span> <span class="o">==</span> <span class="n">f2</span><span class="p">)</span> <span class="c1">// Not against ourselves</span>
            <span class="k">continue</span><span class="p">;</span>

         <span class="n">Line</span> <span class="n">line</span><span class="p">;</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">PlanePlaneIntersection</span><span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">,</span> <span class="n">f2</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">line</span><span class="p">)</span> <span class="o">==</span> <span class="nb">false</span><span class="p">)</span>
            <span class="k">continue</span><span class="p">;</span>

         <span class="c1">// Clip intersection line into a segment</span>
         <span class="c1">// (by intersecting against other planes, oh no)</span>
         <span class="n">Segment</span> <span class="n">segment</span><span class="p">;</span>
         <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f3</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f3</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f3</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
         <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">f3</span> <span class="o">==</span> <span class="n">f2</span> <span class="o">||</span> <span class="n">f3</span> <span class="o">==</span> <span class="n">f</span><span class="p">)</span>
               <span class="k">continue</span><span class="p">;</span>

            <span class="c1">// Cyrus-Beck algorithm</span>
         <span class="p">}</span>

         <span class="c1">// Extract vertices from intersection segment,</span>
         <span class="c1">// store them in 'f-&gt;vertices', also validate them, reorder, you name it</span>
      <span class="p">}</span>
   <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">PlanePlaneIntersection</code> is the typical algorithm borrowed from elsewhere that returns a line from the intersection of two planes. That done, we don’t want a line but a segment, so we clip it using Cyrus-Beck, which in my poor geometry understanding is a clever take on a line-plane intersection algorithm.</p>

<p>And you can stop reading here, I just wanted to point how the operation:</p>

<ol>
  <li>Grab one face</li>
  <li>Intersect its plane against the plane of another face</li>
  <li>Clip resulting line by (basically) intersecting it against all other planes</li>
  <li>Repeat, go to 2 iterating all faces, then to 1 iterating all faces</li>
</ol>

<p>Is absurdly fast.</p>

<p>For completeness here’s how Cyrus-Beck looks in place:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Intersect plane where face lies against other planes</span>
<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f2</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f2</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f2</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">f</span> <span class="o">==</span> <span class="n">f2</span><span class="p">)</span> <span class="c1">// Not against ourselves</span>
      <span class="k">continue</span><span class="p">;</span>

   <span class="n">Line</span> <span class="n">line</span><span class="p">;</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">PlanePlaneIntersection</span><span class="p">(</span><span class="n">f</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">,</span> <span class="n">f2</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">line</span><span class="p">)</span> <span class="o">==</span> <span class="nb">false</span><span class="p">)</span>
      <span class="k">continue</span><span class="p">;</span>

   <span class="c1">// Clip intersection line into a segment</span>
   <span class="c1">// (by intersecting against other planes, oh no)</span>
   <span class="kt">float</span> <span class="n">t_min</span> <span class="o">=</span> <span class="o">-</span><span class="n">FLT_MAX</span><span class="p">;</span>
   <span class="kt">float</span> <span class="n">t_max</span> <span class="o">=</span> <span class="n">FLT_MAX</span><span class="p">;</span>

   <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f3</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f3</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f3</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
   <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">f3</span> <span class="o">==</span> <span class="n">f2</span> <span class="o">||</span> <span class="n">f3</span> <span class="o">==</span> <span class="n">f</span><span class="p">)</span>
         <span class="k">continue</span><span class="p">;</span>

      <span class="c1">// Cyrus-Beck algorithm</span>
      <span class="k">const</span> <span class="kt">float</span> <span class="n">denom</span> <span class="o">=</span> <span class="n">Dot</span><span class="p">(</span><span class="n">f3</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">.</span><span class="n">normal</span><span class="p">,</span> <span class="n">line</span><span class="p">.</span><span class="n">normal</span><span class="p">);</span>

      <span class="k">if</span> <span class="p">(</span><span class="n">Equal</span><span class="p">(</span><span class="n">denom</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">)</span> <span class="o">==</span> <span class="nb">true</span><span class="p">)</span> <span class="c1">// Parallel</span>
      <span class="p">{</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">Dot</span><span class="p">(</span><span class="n">f3</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">.</span><span class="n">normal</span><span class="p">,</span> <span class="n">line</span><span class="p">.</span><span class="n">origin</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">f3</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">.</span><span class="n">distance</span><span class="p">)</span>
         <span class="p">{</span>
            <span class="c1">// No only line is parallel, but is also outside this plane normal,</span>
            <span class="c1">// which means that in a convex body this line doesn't contribute to</span>
            <span class="c1">// overall shape</span>
            <span class="k">goto</span> <span class="n">outside_continue</span><span class="p">;</span>
         <span class="p">}</span>

         <span class="k">continue</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="k">const</span> <span class="kt">float</span> <span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="n">f3</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">.</span><span class="n">distance</span> <span class="o">-</span> <span class="n">Dot</span><span class="p">(</span><span class="n">f3</span><span class="o">-&gt;</span><span class="n">plane</span><span class="p">.</span><span class="n">normal</span><span class="p">,</span> <span class="n">line</span><span class="p">.</span><span class="n">origin</span><span class="p">))</span> <span class="o">/</span> <span class="n">denom</span><span class="p">;</span>

      <span class="k">if</span> <span class="p">(</span><span class="n">denom</span> <span class="o">&lt;</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">)</span>
         <span class="n">t_min</span> <span class="o">=</span> <span class="n">Max</span><span class="p">(</span><span class="n">t_min</span><span class="p">,</span> <span class="n">t</span><span class="p">);</span>
      <span class="k">else</span>
         <span class="n">t_max</span> <span class="o">=</span> <span class="n">Min</span><span class="p">(</span><span class="n">t_max</span><span class="p">,</span> <span class="n">t</span><span class="p">);</span>

      <span class="k">if</span> <span class="p">(</span><span class="n">t_max</span> <span class="o">&lt;</span> <span class="n">t_min</span><span class="p">)</span> <span class="c1">// Line got trimmed out of existence</span>
         <span class="k">goto</span> <span class="n">outside_continue</span><span class="p">;</span>
   <span class="p">}</span>

   <span class="n">Segment</span> <span class="n">segment</span><span class="p">;</span>
   <span class="n">segment</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">origin</span> <span class="o">+</span> <span class="n">Scale</span><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="n">normal</span><span class="p">,</span> <span class="n">t_min</span><span class="p">);</span>
   <span class="n">segment</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">origin</span> <span class="o">+</span> <span class="n">Scale</span><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="n">normal</span><span class="p">,</span> <span class="n">t_max</span><span class="p">);</span>

   <span class="k">if</span> <span class="p">(</span><span class="n">Length</span><span class="p">(</span><span class="n">segment</span><span class="p">.</span><span class="n">a</span> <span class="o">-</span> <span class="n">segment</span><span class="p">.</span><span class="n">b</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">BIG_EPSILON</span><span class="p">)</span>
   <span class="p">{</span>
      <span class="c1">// Intersection segment has basically zero length</span>
      <span class="k">continue</span><span class="p">;</span>
   <span class="p">}</span>

   <span class="c1">// Extract vertices from intersection segment,</span>
   <span class="c1">// store them in 'f-&gt;vertices', also validate them, reorder, you name it</span>
   <span class="p">{</span>
   <span class="p">}</span>

<span class="nl">outside_continue:</span>
   <span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Bit ugly unrolled like that, innit?, it can be abstracted. Anyway, <code class="language-plaintext highlighter-rouge">Equal</code> is basically <code class="language-plaintext highlighter-rouge">fabs(x) &lt; EPSILON</code>, same intent with <code class="language-plaintext highlighter-rouge">BIG_EPSILON</code>, a small number appropriate for map editors (I’m using 0.5 since the minimum grid snap is 1 unit out there). Also, it shines the fact that we are dealing with convex shapes, as is extremely easy to remove intersections that don’t contribute to the overall shape. And finally a warning, all our faces here point outside, Cyrus-Beck will kick your dog or something if that isn’t the case (although a single condition should be enough to deal with it, may be the case that our planes come from a bad BSP splitting stage).</p>

<p>And since you are still here, there’s a little optimisation that can be done if you only need all vertices of the brush and no more than that. It exploits the fact that most of them are shared, so it only iterates over distinct pairs of faces:</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Make a bbox</span>
<span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f1</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f1</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f1</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
   <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f2</span> <span class="o">=</span> <span class="n">f1</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="n">f2</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f2</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// Important part</span>
   <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">cyrus_beck</span><span class="p">.</span><span class="n">Initialise</span><span class="p">(</span><span class="o">*</span><span class="n">f1</span><span class="p">,</span> <span class="o">*</span><span class="n">f2</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
         <span class="k">continue</span><span class="p">;</span>

      <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">Face</span><span class="o">*</span> <span class="n">f3</span> <span class="o">=</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">Data</span><span class="p">();</span> <span class="n">f3</span> <span class="o">&lt;</span> <span class="n">faces</span><span class="o">-&gt;</span><span class="n">End</span><span class="p">();</span> <span class="n">f3</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">)</span>
      <span class="p">{</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">f3</span> <span class="o">==</span> <span class="n">f2</span> <span class="o">||</span> <span class="n">f3</span> <span class="o">==</span> <span class="n">f1</span><span class="p">)</span>
            <span class="k">continue</span><span class="p">;</span>

         <span class="k">if</span> <span class="p">(</span><span class="n">cyrus_beck</span><span class="p">.</span><span class="n">Step</span><span class="p">(</span><span class="n">f3</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">goto</span> <span class="n">outside_continue</span><span class="p">;</span>
      <span class="p">}</span>

      <span class="n">Segment</span> <span class="n">segment</span> <span class="o">=</span> <span class="n">cyrus_beck</span><span class="p">.</span><span class="n">End</span><span class="p">();</span>

      <span class="n">bbox</span><span class="p">.</span><span class="n">max</span> <span class="o">=</span> <span class="n">Max</span><span class="p">(</span><span class="n">bbox</span><span class="p">.</span><span class="n">max</span><span class="p">,</span> <span class="n">Max</span><span class="p">(</span><span class="n">segment</span><span class="p">.</span><span class="n">a</span><span class="p">,</span> <span class="n">segment</span><span class="p">.</span><span class="n">b</span><span class="p">));</span>
      <span class="n">bbox</span><span class="p">.</span><span class="n">min</span> <span class="o">=</span> <span class="n">Min</span><span class="p">(</span><span class="n">bbox</span><span class="p">.</span><span class="n">min</span><span class="p">,</span> <span class="n">Min</span><span class="p">(</span><span class="n">segment</span><span class="p">.</span><span class="n">a</span><span class="p">,</span> <span class="n">segment</span><span class="p">.</span><span class="n">b</span><span class="p">));</span>

   <span class="nl">outside_continue:</span>
      <span class="k">continue</span><span class="p">;</span>
   <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

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

<p>This algorithm here is basically me trying to avoid the one proposed by Stefan Hajnoczi in <a href="https://github.com/stefanha/map-files/blob/master/MAPFiles.pdf">.MAP files file format description, algorithms, and code</a> (2001). It looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>For i = 0 to NumberOfFaces - 3
{
   For j = i to NumberOfFaces - 2
   {
      For k = j to NumberOfFaces - 1
      {
         if ( i != j != k )
         {
            legal = true;
            newVertex = GetIntersection ( i, j , k );

            for m = 0 to NumberOfFaces - 1
            {
               // Test if the point is outside the brush
               if ( DotProduct ( Faces[ m ].normal, newVertex ) + Faces[ m ].d &gt; 0 )
               {
                  legal = false;
               }
            }

            if ( legal )
            {
               Polys[ i ].AddVertex ( newVertex );
               Polys[ j ].AddVertex ( newVertex );
               Polys[ k ].AddVertex ( newVertex );
            }
         }
      }
   }
}
</code></pre></div></div>

<p>Rather similar. For most purposes it’s only one <code class="language-plaintext highlighter-rouge">for</code> more, and since brushes rarely have more than 6 faces I can imagine no performance impact at all.</p>

<p>Then there’s the algorithm written by God himself, John Carmack. Which sadly, I couldn’t understand except that he’s really careful on preserving “windings” (segments in a face?) through the entire map compilation (why not make it at a final stage?). Someone summarised it on <a href="https://quakewiki.org/wiki/Quake_Map_Format">quakewiki.org</a>:</p>

<blockquote>
  <p>Starting with the edges and vertices of a huge cube (usually ranging from -4096 to 4096 in each dimension), the edges of the cube are intersected with the planes of the faces. Upon each intersection, new edges and vertices are generated that replace some of the old ones until all planes have been used.</p>
</blockquote>

<p>So it seems that Carmack is treating brushes like marble/wood carving. That’s really calling for floating errors as it’s an iterative approach, something that Hajnoczi and I are avoiding quite elegantly.</p>

<h2>***</h2>

<p>And that’s all!, thanks for reading, see you later when I manage to write a BSP algorithm (maybe I will discover that I’ve been doing all this wrong).</p>]]></content><author><name>Alexander Brandt</name></author><category term="Algorithm" /><category term="Geometry" /><category term="Game development" /><category term="Quake-adjacent" /><summary type="html"><![CDATA[Surprisingly isn’t that hard… as long as you’re not concerned about performance or doing it in real time. In my first, and suddenly final attempt at doing it I was able to process a quite large map in around 2 ms (in a single thread on an Intel N95). I truly was expecting something horrible measured in seconds, but no, it was just the academic papers that scared me. Though, in their defence they were concerned about million of planes from weird malformed data. In my case I’m just parsing a MAP file made by a human being in a map editor (MAP file a la Quake 1, 2, 3, Half-Life, Doom 3, Half-Life 2, that kind).]]></summary></entry><entry><title type="html">Lorem ipsum</title><link href="https://baalex.github.io/lorem-ipsum" rel="alternate" type="text/html" title="Lorem ipsum" /><published>2025-12-27T00:00:00+00:00</published><updated>2025-12-27T00:00:00+00:00</updated><id>https://baalex.github.io/lorem-ipsum</id><content type="html" xml:base="https://baalex.github.io/lorem-ipsum"><![CDATA[<p>Lorem ipsum dolor sit amet consectetur adipiscing elit arcu, sed inceptos ultrices nec parturient dui euismod senectus facilisis, iaculis facilisi posuere commodo faucibus taciti magna. Commodo blandit aenean quam inceptos feugiat arcu mus turpis porta, sed massa id nisl platea torquent curae aliquet ac nunc, ultrices egestas est duis praesent morbi tellus ornare. Auctor libero nibh lectus morbi fusce venenatis hac quis, blandit rutrum curae suscipit justo volutpat mattis mollis, risus varius montes massa dui sociosqu leo.</p>

<p>Penatibus placerat mi senectus magna volutpat ligula interdum posuere velit platea ut, litora diam ad nostra dapibus laoreet eleifend pellentesque ultrices nulla viverra, dui class varius habitasse fames imperdiet aliquam egestas mauris luctus. Non hac dui senectus interdum est platea habitant proin tempor, sociis fusce ad eros neque cras quisque venenatis. Cubilia conubia purus turpis taciti luctus ligula risus imperdiet inceptos, interdum sociis ullamcorper felis quis metus scelerisque pellentesque, at feugiat ridiculus magna himenaeos faucibus per magnis.</p>]]></content><author><name>Alexander Brandt</name></author><category term="Blog" /><category term="Personal" /><summary type="html"><![CDATA[Lorem ipsum dolor sit amet consectetur adipiscing elit arcu, sed inceptos ultrices nec parturient dui euismod senectus facilisis, iaculis facilisi posuere commodo faucibus taciti magna. Commodo blandit aenean quam inceptos feugiat arcu mus turpis porta, sed massa id nisl platea torquent curae aliquet ac nunc, ultrices egestas est duis praesent morbi tellus ornare. Auctor libero nibh lectus morbi fusce venenatis hac quis, blandit rutrum curae suscipit justo volutpat mattis mollis, risus varius montes massa dui sociosqu leo.]]></summary></entry></feed>