summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJedidiah Barber <contact@jedbarber.id.au>2022-11-27 01:21:53 +1300
committerJedidiah Barber <contact@jedbarber.id.au>2022-11-27 01:21:53 +1300
commit8e36b81714320657b7d06973daa4e769bb17de46 (patch)
tree6a9289d4e458ebcd8c4e7b9c42de0a90da1e701c
parente75f04ce0747169b0f77aa55518982409cf9a876 (diff)
Added ASCII fluid simulator article
-rw-r--r--project/assets/img/marching_squares.pngbin0 -> 59358 bytes
-rw-r--r--project/assets/robots.txt4
-rw-r--r--project/assets/vid/endoh1_column.jpgbin0 -> 70112 bytes
-rw-r--r--project/assets/vid/endoh1_column.mp4bin0 -> 1193216 bytes
-rw-r--r--project/assets/vid/fluid_tanada.jpgbin0 -> 56063 bytes
-rw-r--r--project/assets/vid/fluid_tanada.mp4bin0 -> 1668509 bytes
-rw-r--r--project/complexity.yml6
-rw-r--r--project/context/articles.json6
-rw-r--r--project/context/tags.json1
-rw-r--r--project/templates/fluidsim.xhtml537
-rw-r--r--project/templates/tags/physics.xhtml10
-rw-r--r--project/templates/vidframe/base_preview.xhtml60
-rw-r--r--project/templates/vidframe/base_video.xhtml26
-rw-r--r--project/templates/vidframe/base_vidframe.xhtml17
-rw-r--r--project/templates/vidframe/endoh1_column_preview.xhtml9
-rw-r--r--project/templates/vidframe/endoh1_column_video.xhtml11
-rw-r--r--project/templates/vidframe/fluid_tanada_preview.xhtml9
-rw-r--r--project/templates/vidframe/fluid_tanada_video.xhtml11
18 files changed, 707 insertions, 0 deletions
diff --git a/project/assets/img/marching_squares.png b/project/assets/img/marching_squares.png
new file mode 100644
index 0000000..6ca2d66
--- /dev/null
+++ b/project/assets/img/marching_squares.png
Binary files differ
diff --git a/project/assets/robots.txt b/project/assets/robots.txt
new file mode 100644
index 0000000..5d00a8e
--- /dev/null
+++ b/project/assets/robots.txt
@@ -0,0 +1,4 @@
+
+User-agent: *
+Disallow: /vidframe/
+
diff --git a/project/assets/vid/endoh1_column.jpg b/project/assets/vid/endoh1_column.jpg
new file mode 100644
index 0000000..4d90d97
--- /dev/null
+++ b/project/assets/vid/endoh1_column.jpg
Binary files differ
diff --git a/project/assets/vid/endoh1_column.mp4 b/project/assets/vid/endoh1_column.mp4
new file mode 100644
index 0000000..75c684f
--- /dev/null
+++ b/project/assets/vid/endoh1_column.mp4
Binary files differ
diff --git a/project/assets/vid/fluid_tanada.jpg b/project/assets/vid/fluid_tanada.jpg
new file mode 100644
index 0000000..ad9a586
--- /dev/null
+++ b/project/assets/vid/fluid_tanada.jpg
Binary files differ
diff --git a/project/assets/vid/fluid_tanada.mp4 b/project/assets/vid/fluid_tanada.mp4
new file mode 100644
index 0000000..5cc0db0
--- /dev/null
+++ b/project/assets/vid/fluid_tanada.mp4
Binary files differ
diff --git a/project/complexity.yml b/project/complexity.yml
index 7efaa87..64eeb99 100644
--- a/project/complexity.yml
+++ b/project/complexity.yml
@@ -15,6 +15,7 @@ unexpanded_templates:
- "contributions.xhtml"
- "deckconv.xhtml"
- "fltkada.xhtml"
+ - "fluidsim.xhtml"
- "grasp.xhtml"
- "index.xhtml"
- "integral.xhtml"
@@ -38,9 +39,14 @@ unexpanded_templates:
- "tags/library.xhtml"
- "tags/maps.xhtml"
- "tags/mathematics.xhtml"
+ - "tags/physics.xhtml"
- "tags/politics.xhtml"
- "tags/programming.xhtml"
- "tags/transport.xhtml"
- "tags/utility.xhtml"
- "tags/videogames.xhtml"
+ - "vidframe/endoh1_column_preview.xhtml"
+ - "vidframe/endoh1_column_video.xhtml"
+ - "vidframe/fluid_tanada_preview.xhtml"
+ - "vidframe/fluid_tanada_video.xhtml"
diff --git a/project/context/articles.json b/project/context/articles.json
index e08c95c..e1a0c45 100644
--- a/project/context/articles.json
+++ b/project/context/articles.json
@@ -1,6 +1,12 @@
[
{
+ "title": "ASCII Fluid Simulator",
+ "anchor": "/fluidsim.xhtml",
+ "taglist": ["application", "physics", "programming"],
+ "postdate": "26/11/2022"
+ },
+ {
"title": "Number Wall Visualiser",
"anchor": "/numberwall.xhtml",
"taglist": ["application", "mathematics", "programming"],
diff --git a/project/context/tags.json b/project/context/tags.json
index 8910e07..323b09f 100644
--- a/project/context/tags.json
+++ b/project/context/tags.json
@@ -9,6 +9,7 @@
"library",
"maps",
"mathematics",
+ "physics",
"politics",
"programming",
"transport",
diff --git a/project/templates/fluidsim.xhtml b/project/templates/fluidsim.xhtml
new file mode 100644
index 0000000..5537712
--- /dev/null
+++ b/project/templates/fluidsim.xhtml
@@ -0,0 +1,537 @@
+
+{%- extends "base_math.xhtml" -%}
+
+
+
+{%- block title -%}ASCII Fluid Simulator{%- endblock -%}
+
+
+
+{%- block footer -%}{{ math_footer ("fluidsim.xhtml") }}{%- endblock -%}
+
+
+
+{%- block content %}
+<h4>ASCII Fluid Simulator</h4>
+
+<p>Git repository: <a href="/cgi-bin/cgit.cgi/fluid-sim">Link</a><br />
+Yusuke Endoh's original:
+<a href="https://web.archive.org/web/20190127192827/http://www.ioccc.org/2012/endoh1/hint.html"
+class="external">IOCCC</a> <a href="https://invidious.namazso.eu/watch?v=QMYfkOtYYlg" class="external">
+Invidious</a> <a href="https://www.youtube.com/watch?v=QMYfkOtYYlg" class="external">Youtube</a></p>
+
+<h5>26/11/2022</h5>
+
+
+<h5>Overview</h5>
+
+<p>One of the submissions for the 2012 International Obfuscated C Code Contest was
+a 23, 30, or 31 line piece of seeming
+<a href="https://web.archive.org/web/20201111232320/http://ioccc.org/2012/endoh1/endoh1.c"
+class="external">nonsense code</a> that did... something. When fed appropriately sized text as
+input, all characters except for instances of '#' would melt and flow as if liquid. It was a fluid
+simulator that worked with ASCII text in the terminal.</p>
+
+<p>While it didn't win, it did receive an honorable mention and has also attracted the occasional
+<a href="https://hackaday.com/2015/03/07/animated-ascii-fluid-dynamics-simulator-is-retro-cool/"
+class="external">article</a> over the years since then. Often with commenters in awe of how it
+does what it does. Because it is damned cool.</p>
+
+<div class="figure">
+ <!-- In an ideal world this object would be 806x490, but... -->
+ <object type="application/xhtml+xml" data="/vidframe/endoh1_column_preview.xhtml"
+ width="806" height="500">
+ </object>
+ <div class="figcaption">Endoh's fluid simulator with input column.txt, monochrome version</div>
+</div>
+
+<p>So it's doing some sort of black magic to accomplish this, right? Well, yes, but also no.
+Condensing and obfuscating the code to fit in 23 lines as in the original monochrome version is
+definitely some impressive magic. But actually simulating a fluid in textual form is surprisingly
+easy. What's going on is it's running a simplified version of
+<a href="https://en.wikipedia.org/wiki/Smoothed-particle_hydrodynamics" class="external">
+smoothed-particle hydrodynamics</a> which involves nothing more complicated than addition,
+subtraction, multiplication, division, exponentiation, and square roots. And technically also
+complex numbers, because they tend to be the most accessible way to deal with two dimensional
+values. Nothing too difficult.</p>
+
+<p>To prove how easy it is, the rest of this article is going to detail what you need to know so
+that you too can write your very own comprehensible knockoff version.</p>
+
+
+<h5>Reading Input</h5>
+
+<p>First thing to note is what we are dealing with. Every particle is stored as a structure
+containing five values:</p>
+
+<ul>
+ <li><i>Position</i>, a two dimensional quantity, typically a complex number.</li>
+ <li><i>Is_Wall</i>, a flag denoting whether or not that particle is a solid wall or not.</li>
+ <li><i>Density</i>, a one dimensional real valued quantity.</li>
+ <li><i>Acceleration</i>, another complex number.</li>
+ <li><i>Velocity</i>, yet another complex number.</li>
+</ul>
+
+<p>These particles can be stored in whatever convenient data structure you like. The most natural is
+probably a vector since the program isn't sure how much input it is going to be given, but an array
+can also be made to work.</p>
+
+<p>Reading in the input is just done with standard text I/O functions. But here is a very important
+detail to note: Each character of ASCII input actually denotes two particles, one just above the
+other in position. That is, while the input is usually a textfile with no more than 25 rows, once it
+is read into the program you will have double the number of particles spread across an 80x50 field.
+The display algorithm will scale it back to 25 later on.</p>
+
+<p>If you do not double up the input like this, your simulator will still work, but it won't look
+quite right. Details like how high particles bounce will be off.</p>
+
+<p>Also note that by typical terminal conventions, coordinates (1,1) are at the top left corner and
+numbers increase as you go down and right. Following this convention will make reading in input
+easier, but will have some implications for gravity.</p>
+
+
+<h5>Various Constants</h5>
+
+<p>Before going into the physics calculations, there are a handful of useful constants that are
+going to be needed:</p>
+
+<ul>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>R</mi></math>, a radius distance from a
+ given particle, set here to be 2</li>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>m</mi></math>, the mass of a given
+ particle, set as 1</li>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mn>0</mn>
+ </msub>
+ </math>, a density constant used in calculating interaction forces, set as 1.5</li>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>G</mi></math>, the gravitational
+ constant, which will be set to a magnitude of 1</li>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>P</mi></math>, the pressure parameter,
+ which will be set to 4</li>
+ <li><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>&#x03BC;<!-- μ --></mi></math>, the
+ viscosity parameter, which will be set to 8</li>
+</ul>
+
+<p>Note that while the gravitational constant magnitude is set to 1, care still has to be taken to
+make sure the value is oriented in the correct direction. If terminal conventions are being followed
+for axis orientation then the value will actually be the complex number
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mn>0</mn>
+ <mo>+</mo>
+ <mi>i</mi>
+ </mrow>
+</math>. Endoh avoids this issue by switching the axes.</p>
+
+<p>Also note that while the documentation for Endoh's simulator claims the gravitational, pressure,
+and viscosity parameters are set to 1, 4, 8 as they are here, there is actually an error in his
+makefile. As a result the viscosity constant is set equal to the pressure constant, causing them
+to take on values of 1, 4, 4 by default instead.</p>
+
+
+<h5>Calculating Density</h5>
+
+<p>The density of each particle can be calculated by:</p>
+
+<div class="precontain"><div class="mathblock">
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mi>i</mi>
+ </msub>
+ <mo>=</mo>
+ <mrow>
+ <munder>
+ <mo>&#x2211;<!-- ∑ --></mo>
+ <mrow>
+ <mi>j</mi>
+ <mo>&#x2208;<!-- ∈ --></mo>
+ <msub>
+ <mi>&#x03A9;<!-- Ω --></mi>
+ <msub>
+ <mi>r</mi>
+ <mi>i</mi>
+ </msub>
+ </msub>
+ </mrow>
+ </munder>
+ <msub>
+ <mi>m</mi>
+ <mi>j</mi>
+ </msub>
+ <mi>W</mi>
+ <mo stretchy="false">(</mo>
+ <mo fence="false" stretchy="false">&#x007C;<!-- | --></mo>
+ <msub>
+ <mi>r</mi>
+ <mi>i</mi>
+ </msub>
+ <mo>&#x2212;<!-- − --></mo>
+ <msub>
+ <mi>r</mi>
+ <mi>j</mi>
+ </msub>
+ <mo fence="false" stretchy="false">&#x007C;<!-- | --></mo>
+ <mo stretchy="false">)</mo>
+ </mrow>
+ </math><br /><br />
+ where<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>&#x03C1;<!-- ρ --></mi>
+ </math>
+ is particle density<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>r</mi>
+ </math>
+ is particle position<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msub>
+ <mi>&#x03A9;<!-- Ω --></mi>
+ <mi>r</mi>
+ </msub>
+ </math>
+ is the circular area around the position
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>r</mi>
+ </math>
+ with a radius equal to
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>R</mi>
+ </math><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>W</mi>
+ </math>
+ is the kernel function defined here as<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mi>W</mi>
+ <mo stretchy="false">(</mo>
+ <mi>d</mi>
+ <mo stretchy="false">)</mo>
+ </mrow>
+ <mo>=</mo>
+ <msup>
+ <mrow>
+ <mo stretchy="false">(</mo>
+ <mfrac>
+ <mi>d</mi>
+ <mi>R</mi>
+ </mfrac>
+ <mo>&#x2212;<!-- − --></mo>
+ <mn>1</mn>
+ <mo stretchy="false">)</mo>
+ </mrow>
+ <mn>2</mn>
+ </msup>
+ </math><br /><br />
+ and<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>m</mi>
+ <mo>,</mo>
+ <mi>R</mi>
+ </math>
+ are the constants defined earlier
+</div></div>
+
+<p>Or in other words, for each particle, find all the other particles within a radius of 2, weight
+the distance between the two by the <math xmlns="http://www.w3.org/1998/Math/MathML"><mi>W</mi>
+</math> function, multiply by the particle mass of 1, and sum up all these intermediate results.
+That's the particle's density.</p>
+
+<p>Particles that are part of a solid wall get an additional density of 9, so don't forget to add
+that too.</p>
+
+<p>It is recommended that you do this step before you render any output results since you may want
+to use density values to color the output. Endoh's simulator doesn't bother, however.</p>
+
+
+<h5>Calculating Acceleration</h5>
+
+<p>The acceleration of each non-wall particle is the sum of two components, gravity and interaction
+forces. Gravity is very simple as it is just the gravitational constant already defined earlier. The
+interaction forces acting on each particle can be calculated by:</p>
+
+<div class="precontain"><div class="mathblock">
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msubsup>
+ <mi>a</mi>
+ <mi>i</mi>
+ <mrow>
+ <mi>i</mi>
+ <mi>n</mi>
+ <mi>t</mi>
+ <mi>e</mi>
+ <mi>r</mi>
+ <mi>a</mi>
+ <mi>c</mi>
+ <mi>t</mi>
+ </mrow>
+ </msubsup>
+ <mo>=</mo>
+ <mrow>
+ <munder>
+ <mo>&#x2211;<!-- ∑ --></mo>
+ <mrow>
+ <mi>j</mi>
+ <mo>&#x2208;<!-- ∈ --></mo>
+ <msub>
+ <mi>&#x03A9;<!-- Ω --></mi>
+ <msub>
+ <mi>r</mi>
+ <mi>i</mi>
+ </msub>
+ </msub>
+ </mrow>
+ </munder>
+ <mfrac>
+ <mrow>
+ <mn>1</mn>
+ <mo>&#x2212;<!-- − --></mo>
+ <mi>q</mi>
+ <mo stretchy="false">(</mo>
+ <mi>i</mi>
+ <mo>,</mo>
+ <mi>j</mi>
+ <mo stretchy="false">)</mo>
+ </mrow>
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mi>j</mi>
+ </msub>
+ </mfrac>
+ <mo>&it;</mo>
+ <mo stretchy="false">[</mo>
+ <mi>P</mi>
+ <mo>&it;</mo>
+ <mo stretchy="false">(</mo>
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mi>i</mi>
+ </msub>
+ <mo>+</mo>
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mi>j</mi>
+ </msub>
+ <mo>&#x2212;<!-- − --></mo>
+ <mn>2</mn>
+ <mo>&it;</mo>
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mn>0</mn>
+ </msub>
+ <mo stretchy="false">)</mo>
+ <mo>&it;</mo>
+ <mo stretchy="false">(</mo>
+ <msub>
+ <mi>r</mi>
+ <mi>i</mi>
+ </msub>
+ <mo>&#x2212;<!-- − --></mo>
+ <msub>
+ <mi>r</mi>
+ <mi>j</mi>
+ </msub>
+ <mo stretchy="false">)</mo>
+ <mo>&#x2212;<!-- − --></mo>
+ <mi>&#x03BC;<!-- μ --></mi>
+ <mo>&it;</mo>
+ <mo stretchy="false">(</mo>
+ <msub>
+ <mi>v</mi>
+ <mi>i</mi>
+ </msub>
+ <mo>&#x2212;<!-- − --></mo>
+ <msub>
+ <mi>v</mi>
+ <mi>j</mi>
+ </msub>
+ <mo stretchy="false">)</mo>
+ <mo stretchy="false">]</mo>
+ </mrow>
+ </math><br /><br />
+ where<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msup>
+ <mi>a</mi>
+ <mrow>
+ <mi>i</mi>
+ <mi>n</mi>
+ <mi>t</mi>
+ <mi>e</mi>
+ <mi>r</mi>
+ <mi>a</mi>
+ <mi>c</mi>
+ <mi>t</mi>
+ </mrow>
+ </msup>
+ </math>
+ is the interaction force acting on a particle<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>&#x03C1;<!-- ρ --></mi>
+ </math>
+ is particle density<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>r</mi>
+ </math>
+ is particle position<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <msub>
+ <mi>&#x03A9;<!-- Ω --></mi>
+ <mi>r</mi>
+ </msub>
+ </math>
+ is the circular area around the position
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>r</mi>
+ </math>
+ with a radius equal to
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>R</mi>
+ </math><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>v</mi>
+ </math>
+ is particle velocity<br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>q</mi>
+ </math>
+ is a function defined for convenience as<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mrow>
+ <mi>q</mi>
+ <mo stretchy="false">(</mo>
+ <mi>x</mi>
+ <mo>,</mo>
+ <mi>y</mi>
+ <mo stretchy="false">)</mo>
+ </mrow>
+ <mo>=</mo>
+ <mfrac>
+ <mrow>
+ <mo stretchy="false">&#x007C;<!-- | --></mo>
+ <msub>
+ <mi>r</mi>
+ <mi>x</mi>
+ </msub>
+ <mo>&#x2212;<!-- − --></mo>
+ <msub>
+ <mi>r</mi>
+ <mi>y</mi>
+ </msub>
+ <mo stretchy="false">&#x007C;<!-- | --></mo>
+ </mrow>
+ <mi>R</mi>
+ </mfrac>
+ </math><br /><br />
+ and<br /><br />
+ <math xmlns="http://www.w3.org/1998/Math/MathML">
+ <mi>P</mi>
+ <mo>,</mo>
+ <msub>
+ <mi>&#x03C1;<!-- ρ --></mi>
+ <mn>0</mn>
+ </msub>
+ <mo>,</mo>
+ <mi>&#x03BC;<!-- μ --></mi>
+ <mo>,</mo>
+ <mi>R</mi>
+ </math>
+ are the constants defined earlier
+</div></div>
+
+<p>A lot to take in, I know. A bit too much to rephrase in plain english this time, even though it
+is a simplified form compared to the usual smoothed-particle hydrodynamics formula. But as promised,
+once you get past all the variables there is still only elementary math involved.</p>
+
+<p>So, the gravity plus the interaction force. That's the particle's acceleration.</p>
+
+<p>Acceleration of non-wall particles can be ignored since they will never move and the value won't
+have any affect on other calculations.</p>
+
+
+<h5>Updating Particle Positions</h5>
+
+<p>As implemented in Endoh's simulator this is by far the easiest step. For each non-wall particle,
+merely divide the acceleration value by 10 then add that to the velocity value. Then add the
+velocity to the particle's position. The division by 10 is an arbitrary scaling factor to make
+things work in the space a standard 80x25 size terminal gives us.</p>
+
+<p>If you want to go the extra mile and add collision detection between moving particles and wall
+particles, this would be the step to implement it. I haven't bothered. Partially because collision
+detection is tricky, but also because doing so would result in visibly different behaviour.</p>
+
+
+<h5>Display Rendering</h5>
+
+<p>Believe it or not, this is actually the most complicated part of this whole thing. It might be
+fairly easy to explain in concept, but the actual implementation is more finnicky than any of the
+previous steps.</p>
+
+<p>The algorithm in use here is called <a href="https://en.wikipedia.org/wiki/Marching_squares"
+class="external">Marching Squares</a>. You calculate whether there are any particles present at the
+corners of each character cell on an 80x25 terminal, then use those results to look up which
+character should go in that cell from a predetermined mapping of cases to output characters. Some
+experimentation may be required to find characters that look close enough, as the ASCII character
+set was not made with this in mind.</p>
+
+<p>Be sure to scale the vertical axis back down by a factor of 2 before you perform these
+calculations in order to take the input doubling into account.</p>
+
+<div class="figure">
+ <img src="/img/marching_squares.png"
+ alt="Marching squares algorithm cell cases"
+ height="525"
+ width="525" />
+ <div class="figcaption">The 16 possibilities for rendering cells, with blue for empty space and
+ purple for filled areas</div>
+</div>
+
+<p>Various small tricks with bitwise-or operations can be used to make this process more efficient,
+but going into those is beyond the scope of this article.</p>
+
+<p>To clear the screen and reset the cursor between output renders,
+<a href="https://en.wikipedia.org/wiki/ANSI_escape_code" class="external">ANSI escape codes</a> are
+used. They are also used if you want to add color to the output, with an escape code inserted before
+each character to change the color according to the average density of the particles contributing to
+that cell. Since the escape codes to do this are quite long, the resulting string will get decently
+large. It won't affect the size when displayed to the terminal, however.</p>
+
+<p>In my experience, the average density in a given cell usually remains in the range 0-20 unless in
+the middle of a thick solid wall. Scaling the colors used to that range tends to work well.</p>
+
+
+<h5>Putting It All Together</h5>
+
+<p>The entire program consists of reading in the input, then looping through calculating density,
+rendering output, calculating acceleration, and updating the particle positions. That's really it.
+The only other thing you will probably want to do is have some step to get rid of particles should
+their position go too far off-screen. Both to avoid unnecessary computation and the potential for
+values to overflow and cause an error.</p>
+
+<p>The calculation and update steps are all embarrassingly parallel, so if you're looking for places
+to improve things beyond adding color then there would be a good place to start. Neither Endoh's
+implementation nor mine do anything with that, but that is more due to lack of need to put in the
+effort.</p>
+
+<div class="figure">
+ <!-- In an idea world this object would be 806x490, but... -->
+ <object type="application/xhtml+xml" data="/vidframe/fluid_tanada_preview.xhtml"
+ width="806" height="500">
+ </object>
+ <div class="figcaption">My fluid simulator with input tanada.txt</div>
+</div>
+
+<p>Now, was this entire exercise ultimately pointless, since it is concerned with ASCII rendering
+done by intentionally obfuscated code? Yes. Was it nonetheless interesting and fun? Also yes.</p>
+
+<p>Thank you Daniele Venier for your informative, if incomplete,
+<a href="http://asymptoticbits.com/posts/ascii-liquid/" class="external">article</a> on this same
+topic which let me avoid having to analyse the IOCCC entry from scratch myself. Although really, it
+probably would have still been easier to construct something from first principles anyway.</p>
+
+{% endblock -%}
+
+
diff --git a/project/templates/tags/physics.xhtml b/project/templates/tags/physics.xhtml
new file mode 100644
index 0000000..c88b7e5
--- /dev/null
+++ b/project/templates/tags/physics.xhtml
@@ -0,0 +1,10 @@
+
+{%- extends "tags/base_tag.xhtml" -%}
+
+
+
+{%- block title -%}{{ tag_title ("Physics") }}{%- endblock -%}
+{%- block content %}{{ tag_content ("Physics") }}{% endblock -%}
+{%- block footer -%}{{ tag_footer ("physics.xhtml") }}{%- endblock -%}
+
+
diff --git a/project/templates/vidframe/base_preview.xhtml b/project/templates/vidframe/base_preview.xhtml
new file mode 100644
index 0000000..eaf0bea
--- /dev/null
+++ b/project/templates/vidframe/base_preview.xhtml
@@ -0,0 +1,60 @@
+
+{%- extends "vidframe/base_vidframe.xhtml" -%}
+
+
+
+{%- block style %}
+ body {
+ background-color: black;
+ }
+ a {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+ img.thumb {
+ opacity: 0.5;
+ }
+ div.playbutton {
+ opacity: 1.0;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ }
+ svg {
+ position: absolute;
+ fill: #fff;
+ width: 100px;
+ height: 100px;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ }
+ div.playbutton:hover svg,
+ div.playbutton:focus svg {
+ fill: #17e617;
+ }
+{% endblock -%}
+
+
+
+{%- block video %}
+<div class="playbutton">
+ <a href="{%- block vidlink -%}{%- endblock -%}">
+ <img class="thumb" src="{%- block thumbnail -%}{%- endblock -%}"
+ alt="Click to load video"
+ width="100%"
+ height="100%" />
+ <svg:svg version="1.1" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 311.69 311.69">
+ <svg:path d="M155.84,0A155.85,155.85,0,1,0,311.69,155.84,155.84,155.84,0,0,0,155.84,
+ 0Zm0,296.42A140.58,140.58,0,1,1,296.42,155.84,140.58,140.58,0,0,1,155.84,296.42Z"/>
+ <svg:polygon points="218.79 155.84 119.22 94.34 119.22 217.34 218.79 155.84"/>
+ </svg:svg>
+ </a>
+</div>
+{% endblock -%}
+
+
diff --git a/project/templates/vidframe/base_video.xhtml b/project/templates/vidframe/base_video.xhtml
new file mode 100644
index 0000000..f298705
--- /dev/null
+++ b/project/templates/vidframe/base_video.xhtml
@@ -0,0 +1,26 @@
+
+{%- extends "vidframe/base_vidframe.xhtml" -%}
+
+
+
+{%- block style %}
+ body {
+ margin: 0;
+ padding: 0;
+ }
+{% endblock -%}
+
+
+
+{%- block video %}
+<div>
+ <object type="video/mp4"
+ data="{%- block vidfile -%}{%- endblock -%}"
+ width="{%- block vidwidth -%}{%- endblock -%}"
+ height="{%- block vidheight -%}{%- endblock -%}">
+ <a href="{%- block backup -%}{%- endblock -%}">Download video</a>
+ </object>
+</div>
+{% endblock -%}
+
+
diff --git a/project/templates/vidframe/base_vidframe.xhtml b/project/templates/vidframe/base_vidframe.xhtml
new file mode 100644
index 0000000..4f2e00e
--- /dev/null
+++ b/project/templates/vidframe/base_vidframe.xhtml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC
+ "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
+ "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <title></title>
+ <style type="text/css">
+{% block style -%}{%- endblock %}
+ </style>
+</head>
+<body>
+{% block video -%}
+{%- endblock %}
+</body>
+</html>
diff --git a/project/templates/vidframe/endoh1_column_preview.xhtml b/project/templates/vidframe/endoh1_column_preview.xhtml
new file mode 100644
index 0000000..9a444e6
--- /dev/null
+++ b/project/templates/vidframe/endoh1_column_preview.xhtml
@@ -0,0 +1,9 @@
+
+{%- extends "vidframe/base_preview.xhtml" -%}
+
+
+
+{%- block vidlink -%}/vidframe/endoh1_column_video.xhtml{%- endblock -%}
+{%- block thumbnail -%}/vid/endoh1_column.jpg{%- endblock -%}
+
+
diff --git a/project/templates/vidframe/endoh1_column_video.xhtml b/project/templates/vidframe/endoh1_column_video.xhtml
new file mode 100644
index 0000000..3cebb18
--- /dev/null
+++ b/project/templates/vidframe/endoh1_column_video.xhtml
@@ -0,0 +1,11 @@
+
+{%- extends "vidframe/base_video.xhtml" -%}
+
+
+
+{%- block vidfile -%}/vid/endoh1_column.mp4{%- endblock -%}
+{%- block vidwidth -%}806{%- endblock -%}
+{%- block vidheight -%}490{%- endblock -%}
+{%- block backup -%}/vid/endoh1_column.mp4{%- endblock -%}
+
+
diff --git a/project/templates/vidframe/fluid_tanada_preview.xhtml b/project/templates/vidframe/fluid_tanada_preview.xhtml
new file mode 100644
index 0000000..9e27878
--- /dev/null
+++ b/project/templates/vidframe/fluid_tanada_preview.xhtml
@@ -0,0 +1,9 @@
+
+{%- extends "vidframe/base_preview.xhtml" -%}
+
+
+
+{%- block vidlink -%}/vidframe/fluid_tanada_video.xhtml{%- endblock -%}
+{%- block thumbnail -%}/vid/fluid_tanada.jpg{%- endblock -%}
+
+
diff --git a/project/templates/vidframe/fluid_tanada_video.xhtml b/project/templates/vidframe/fluid_tanada_video.xhtml
new file mode 100644
index 0000000..4328290
--- /dev/null
+++ b/project/templates/vidframe/fluid_tanada_video.xhtml
@@ -0,0 +1,11 @@
+
+{%- extends "vidframe/base_video.xhtml" -%}
+
+
+
+{%- block vidfile -%}/vid/fluid_tanada.mp4{%- endblock -%}
+{%- block vidwidth -%}806{%- endblock -%}
+{%- block vidheight -%}490{%- endblock -%}
+{%- block backup -%}/vid/fluid_tanada.mp4{%- endblock -%}
+
+