<?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://dyn4j.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dyn4j.org/" rel="alternate" type="text/html" /><updated>2025-10-31T12:36:04-04:00</updated><id>https://dyn4j.org/feed.xml</id><title type="html">dyn4j</title><subtitle>A 100% Java 2D collision detection and physics engine. Designed to be fast, stable, extensible, and easy to use. dyn4j is free for use in commercial and non-commercial applications</subtitle><author><name>William Bittle</name></author><entry><title type="html">Version 5.0.2</title><link href="https://dyn4j.org/2024/03/version-5-0-2/" rel="alternate" type="text/html" title="Version 5.0.2" /><published>2024-03-16T01:00:00-04:00</published><updated>2024-03-16T01:00:00-04:00</updated><id>https://dyn4j.org/2024/03/version-5-0-2</id><content type="html" xml:base="https://dyn4j.org/2024/03/version-5-0-2/"><![CDATA[<p>This update focuses on a few minor performance improvements, fixes and enhancements.</p>

<p>NOTE: <a href="https://github.com/dyn4j/dyn4j/issues/275">#275</a> reduced the default number of velocity/position solver iterations. If this causes an issue, you can manually set the solver iterations back to 10 and 10 which were the old defaults using <code class="language-html highlighter-rouge">Settings.setVelocityConstraintSolverIterations</code> and <code class="language-html highlighter-rouge">Settings.setPositionConstraintSolverIterations</code>.  This change was made based on testing where it was found that a lower number of iterations reduces jitter and the overall system energy without significant fidelity loss.  The performance improvement was a side effect.</p>

<p>NOTE: <a href="https://github.com/dyn4j/dyn4j/issues/287">#287</a> will now wake the body when certain changes are made to it.  Please review the issue for the details. If you relied on the body staying at rest in these situations, just use <code class="language-html highlighter-rouge">setAtRest(true)</code> after making the change.</p>

<p>See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for all the detail.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This update focuses on a few minor performance improvements, fixes and enhancements.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/gears.jpg" /><media:content medium="image" url="https://dyn4j.org/assets/gears.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Version 5.0.1</title><link href="https://dyn4j.org/2022/12/version-5-0-1/" rel="alternate" type="text/html" title="Version 5.0.1" /><published>2022-12-31T00:00:00-05:00</published><updated>2022-12-31T00:00:00-05:00</updated><id>https://dyn4j.org/2022/12/version-5-0-1</id><content type="html" xml:base="https://dyn4j.org/2022/12/version-5-0-1/"><![CDATA[<p>This update fixes a few issues discovered while enhancing the samples project. This update does change the behavior of the <code class="language-html highlighter-rouge">SolvedContact.isSolved</code> method and removes the code that resets the <code class="language-html highlighter-rouge">enabled</code> and <code class="language-html highlighter-rouge">tangentVelocity</code> properties on the <code class="language-html highlighter-rouge">ContactConstraint</code>.</p>

<p>See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for all the detail.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This update fixes a few issues discovered while enhancing the samples project. This update does change the behavior of the SolvedContact.isSolved method and removes the code that resets the enabled and tangentVelocity properties on the ContactConstraint.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/gears.jpg" /><media:content medium="image" url="https://dyn4j.org/assets/gears.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Version 5.0.0</title><link href="https://dyn4j.org/2022/12/version-5-0-0/" rel="alternate" type="text/html" title="Version 5.0.0" /><published>2022-12-23T00:00:00-05:00</published><updated>2022-12-23T00:00:00-05:00</updated><id>https://dyn4j.org/2022/12/version-5-0-0</id><content type="html" xml:base="https://dyn4j.org/2022/12/version-5-0-0/"><![CDATA[<p>This major update overhauls the <code class="language-html highlighter-rouge">org.dyn4j.dynamics.joint</code> package.  This update allows the creation of new joints with an arbitrary number of bodies (whereas only one-body or two-body joints were possible before).  This release sees all joints inherit from standard interfaces for things like limits, motors, and springs with the intent to make the API surface identical among all the joints.</p>

<p>A major change from previous versions are the settings for motors and springs.  Before, you would disable these features by setting their values to zero or some default value.  Now, these features are enabled/disabled using specific <code class="language-html highlighter-rouge">set{feature}Enabled</code> methods.  You can still set those settings to zero/defaults, which effectively disables them, but you lose the original value.  For example, if you want your spring stiffness to be 10.0 and you want to toggle it on or off based on user interaction, you have to store the 10.0 somewhere, then set it to zero to disable the spring, then set it back to 10.0 to re-enable the spring with the correct value.  These new methods allow you to set it to 10.0 and then toggle the behavior on/off separately.</p>

<p>Another major change from previous versions is on the WheelJoint and PrismaticJoint.  These joints have had their body arguments reversed so that the given axis of allowed motion is fixed to the first body rather than the second.  This shouldn’t impact existing code too much, but generally you’d need to reverse the first and second body and potentially negate the axis you had before.</p>

<p>Apart from these breaking changes, many of the joints see new features to help reduce the number of joints between bodies to achieve a desired effect.  For example, before you would need to use both a <code class="language-html highlighter-rouge">WeldJoint</code> and a <code class="language-html highlighter-rouge">RevoluteJoint</code> if you wanted an angular spring with limits - this is now possible with only a <code class="language-html highlighter-rouge">WeldJoint</code>.  Another example is if you wanted a linear spring with prismatic motion, you’d need to use both a <code class="language-html highlighter-rouge">PrismaticJoint</code> and a <code class="language-html highlighter-rouge">DistanceJoint</code> - now you can just use the <code class="language-html highlighter-rouge">PrismaticJoint</code>.</p>

<p>The class documentation for all joints has been revised to be clearer and reflect all the changes described above.</p>

<p>As always with a major release, all deprecated APIs have been removed.</p>

<p>See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for all the detail.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This major update overhauls the org.dyn4j.dynamics.joint package. This update allows the creation of new joints with an arbitrary number of bodies (whereas only one-body or two-body joints were possible before). This release sees all joints inherit from standard interfaces for things like limits, motors, and springs with the intent to make the API surface identical among all the joints.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/pages/joints/revolute-joint.png" /><media:content medium="image" url="https://dyn4j.org/assets/pages/joints/revolute-joint.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Simple Polygon Simplification</title><link href="https://dyn4j.org/2021/06/2021-06-10-simple-polygon-simplification/" rel="alternate" type="text/html" title="Simple Polygon Simplification" /><published>2021-06-10T01:36:59-04:00</published><updated>2021-06-10T01:36:59-04:00</updated><id>https://dyn4j.org/2021/06/simple-polygon-simplification</id><content type="html" xml:base="https://dyn4j.org/2021/06/2021-06-10-simple-polygon-simplification/"><![CDATA[<p>When simulating physical interaction between objects, there are many tradeoffs used to keep performance acceptable for realtime scenarios.  The geometry of an object, or it’s shape, is no exception.  To accelerate calculations, collision detection systems will enforce rules about what types of shapes are supported - a key example is <a href="https://en.wikipedia.org/wiki/Convex_polygon">Convexity</a>.  Not all objects are convex, and you might even argue that most aren’t, but we can combine these <em>convex</em> pieces to create <em>non-convex</em> objects.</p>

<p>Part of the problem is the translation from a complex shape to a collection of convex pieces.  There are many algorithms that take <a href="https://en.wikipedia.org/wiki/Simple_polygon">simple polygons</a> and break them down into convex pieces (called convex decomposition), but often the output is not ideal for realtime simulation.  It’s common for small and/or thin convex shapes to be generated on top of the shear number of shapes.  That said, it’s not so much a problem with decomposition, but more with the input to these algorithms.</p>

<p>To help with this problem, version 4.2.0 of <a href="https://github.com/dyn4j/dyn4j">dyn4j</a> added a new feature called polygon simplification.  This is the process of altering the input to the convex decomposition to produce better results.</p>

<h2 id="concept" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#concept">
          #
        </a>
    </span>
    Concept
</h2>

<p>The concept here is simple: to improve the quality of collision shapes by adding a pre-processing step <em>before</em> the convex decomposition process.  This pre-processing step, called polygon simplification, reduces the number of vertices in the source polygon.  The goal of this process is NOT to keep the input the same, but rather to alter the shape to remove <strong>insignificant</strong> features.</p>

<p>Consider the following example in Figure 1.  For the most part it’s a pretty standard shape, but there’s a little piece of it that sticks out.  A possible decomposition of the shape is also shown: two shapes, one triangle and one polygon.</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon1.png" class="figure-img img-fluid rounded" alt="Figure 1: A very simple, simple polygon and a possible convex decomposition" />
  <figcaption class="figure-caption text-center">
    Figure 1: A very simple, simple polygon and a possible convex decomposition 
    
  </figcaption>
</figure>

<p>While this is a perfectly valid decomposition, it’s not great for physical simulation.  The green triangle is small and thin which can cause this object to behave oddly with other objects.  Things like sticking together during collision come to mind.</p>

<p>The question is whether the green bit is even necessary for realistic collision.  It might be, but often it’s not, so what happens if we just ignore the green triangle?  You could <em>post-process</em> the convex decomposition and remove problem shapes based on area, length, or other criteria, but you run the risk of removing shapes internal to the overall decomposition.</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon3.png" class="figure-img img-fluid rounded" alt="Figure 2: Another sample non-convex shape with a possible convex decomposition" />
  <figcaption class="figure-caption text-center">
    Figure 2: Another sample non-convex shape with a possible convex decomposition 
    
  </figcaption>
</figure>

<p>Consider figure 2 of a non-convex polygon that we’ve decomposed.  If you simply <em>remove</em> the purple triangle you’ll leave a gap which is probably worse than leaving the problem triangle in.  Is the purple triangle providing any tangible benefit to physical simulation?  No, it’s just noise.  We need a way to pre-process the simple polygon to simplify it further, then decompose it.</p>

<blockquote>
  <p>NOTE: While this is a poor example of a convex decomposition, it’s a perfectly valid one.  Let’s imagine the purple and green shapes were combined, does the additional information of that extra feature provide value to physical simulation?  Again, it’ll depend on your use-case, but I’d argue that it has no value and only slows down the simulation.</p>
</blockquote>

<blockquote>
  <p>NOTE: While polygon simplification and convex decomposition are great, it’s no substitute for manually designed collision shapes.</p>
</blockquote>

<h2 id="algorithms" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#algorithms">
          #
        </a>
    </span>
    Algorithms
</h2>

<p>There are a number of algorithms out there to do polygon simplification but I’ll be focusing on three in particular:</p>

<ul>
  <li>Vertex Cluster Reduction</li>
  <li>Douglas-Peucker</li>
  <li>Visvalingam</li>
</ul>

<p>All of these attempt to remove insignificant features of an input simple polygon.  Consider the input of these algorithms an ordered list of vertices of the simple polygon in clock-wise or counter clock-wise winding.</p>

<h3 id="vertex-cluster-reduction" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#vertex-cluster-reduction">
          #
        </a>
    </span>
    Vertex Cluster Reduction
</h3>

<p>By far the simplest algorithm to implement and understand.  The basic concept is to remove <strong>adjacent</strong> vertices that are too close given a minimal distance.  The algorithm is \(O(n)\) complexity and can be used as a first pass before employing another algorithm.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="c1">// NOT INTENDED TO BE SOURCE CODE - MORE PSUEDO CODE</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="nf">reduce</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">vertices</span><span class="o">,</span> <span class="kt">double</span> <span class="n">minDistance</span><span class="o">)</span> <span class="o">{</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">simplified</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;;</span>
    <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>

    <span class="nc">Vertex</span> <span class="n">ref</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
    <span class="n">simplified</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">ref</span><span class="o">);</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
        <span class="nc">Vertex</span> <span class="n">v0</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">ref</span><span class="o">.</span><span class="na">distance</span><span class="o">(</span><span class="n">v0</span><span class="o">)</span> <span class="o">&gt;</span> <span class="n">minDistance</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// set the new reference vertex</span>
            <span class="n">ref</span> <span class="o">=</span> <span class="n">v0</span><span class="o">;</span>
            <span class="c1">// add it to the simplified polygon</span>
            <span class="n">simplified</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">v0</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="c1">// if it's smaller, then we ignore the vertex</span>
        <span class="c1">// and continue</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="n">simplified</span><span class="o">;</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If we applied this to our second example above, we’d remove one of the red points and end up with the following simple polygon and it’s respective decomposition:</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon4.png" class="figure-img img-fluid rounded" alt="Figure 3: A simple polygon, the simplified version, the convex decomposition" />
  <figcaption class="figure-caption text-center">
    Figure 3: A simple polygon, the simplified version, the convex decomposition 
    
  </figcaption>
</figure>

<p>Nice! But the algorithm can’t catch cases like figure 4 where the vertices are not clustered.  Vertices P1, P3, P4, and P6 provide little value to the overall structure of the shape.  Unfortunately, decomposing this will produce an unnecessary amount of elements.  For this we need something more complex.</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon5.png" class="figure-img img-fluid rounded" alt="Figure 4: A simple polygon and a possible convex decomposition" />
  <figcaption class="figure-caption text-center">
    Figure 4: A simple polygon and a possible convex decomposition 
    
  </figcaption>
</figure>

<h3 id="douglas-peucker" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#douglas-peucker">
          #
        </a>
    </span>
    Douglas-Peucker
</h3>
<p>The Douglas-Peucker algorithm is a \(O(n log_2 n)\) complexity algorithm that actually operates on polylines instead of polygons, but can be adapted for polygons.  The basic concept is to draw a line from one point to another, check the distance to the line for all points in between, if all the points are less than a minimum distance, then remove all those points.  If there’s one point that’s more than or equal to the minimum distance, subdivide the polyline into two, and perform the same steps on each one.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
</pre></td><td class="rouge-code"><pre><span class="c1">// NOT INTENDED TO BE SOURCE CODE - MORE PSUEDO CODE</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="nf">dp</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">polyline</span><span class="o">,</span> <span class="kt">double</span> <span class="n">max</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">polyline</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
    <span class="nc">Vertex</span> <span class="n">first</span> <span class="o">=</span> <span class="n">polyline</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span>
    <span class="nc">Vertex</span> <span class="n">last</span> <span class="o">=</span> <span class="n">polyline</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span>

    <span class="kt">double</span> <span class="n">maxDistance</span><span class="o">;</span>
    <span class="kt">int</span> <span class="n">maxVertex</span><span class="o">;</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
        <span class="nc">Vertex</span> <span class="n">v</span> <span class="o">=</span> <span class="n">polyline</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
        <span class="c1">// get the distance from v to the line created by first/last</span>
        <span class="kt">double</span> <span class="n">d</span> <span class="o">=</span> <span class="n">distance</span><span class="o">(</span><span class="n">v</span><span class="o">,</span> <span class="n">first</span><span class="o">,</span> <span class="n">last</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">d</span> <span class="o">&gt;</span> <span class="n">maxDistance</span><span class="o">)</span> <span class="o">{</span>
            <span class="n">maxDistance</span> <span class="o">=</span> <span class="n">d</span><span class="o">;</span>
            <span class="n">maxVertex</span> <span class="o">=</span> <span class="n">i</span><span class="o">;</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="k">if</span> <span class="o">(</span><span class="n">maxDistance</span> <span class="o">&gt;=</span> <span class="n">max</span><span class="o">)</span> <span class="o">{</span>
        <span class="c1">// subdivide</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">one</span> <span class="o">=</span> <span class="n">dp</span><span class="o">(</span><span class="n">polyline</span><span class="o">.</span><span class="na">sublist</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="n">maxVertex</span> <span class="o">+</span> <span class="mi">1</span><span class="o">));</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">two</span> <span class="o">=</span> <span class="n">dp</span><span class="o">(</span><span class="n">polyline</span><span class="o">.</span><span class="na">sublist</span><span class="o">(</span><span class="n">maxVertex</span><span class="o">,</span> <span class="n">size</span><span class="o">));</span>
        <span class="c1">// rejoin the two (TODO without repeating the middle point)</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">simplified</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;();</span>
        <span class="n">simplified</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">one</span><span class="o">);</span>
        <span class="n">simplified</span><span class="o">.</span><span class="na">addAll</span><span class="o">(</span><span class="n">two</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">simplified</span><span class="o">;</span>
    <span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
        <span class="c1">// return only the first/last vertices</span>
        <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">simplified</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;();</span>
        <span class="n">simplified</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">first</span><span class="o">);</span>
        <span class="n">simplified</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">last</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">simplified</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If we run through this process until completion, we end up with vertices P1, P2, P3, and P6 removed - nearly half the verticies.  This results in almost half the convex shapes in the decomposition.  In addition, the parts that are left are nice and chunky too.</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon6.png" class="figure-img img-fluid rounded" alt="Figure 5: A simple polygon with one Douglas-Peucker step highlighted, the simplified version, the convex decomposition" />
  <figcaption class="figure-caption text-center">
    Figure 5: A simple polygon with one Douglas-Peucker step highlighted, the simplified version, the convex decomposition 
    
  </figcaption>
</figure>

<blockquote>
  <p>NOTE: The number of vertices reduced will be highly dependent on where the splits are since there’s no re-processing of those vertices where the splits occur.  It shouldn’t be a problem since these vertices are the farthest from the line, but it’s possible this could be an issue for the initial split.</p>
</blockquote>

<p>The first image is an example of one of the stages of the algorithm where it removed vertices P3 and P4 since they were not sufficiently far from the line between P2 and P5 (not labeled).</p>

<h3 id="visvalingam" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#visvalingam">
          #
        </a>
    </span>
    Visvalingam
</h3>
<p>Visvalingam is an alternate \(O(n log_2 n)\) algorithm that takes a <em>similar</em>, but slightly different approach.  Instead of computing the distance between the points and a line and recursively subdividing, it looks at the triangular area of each vertex, sorts them, and removes them until the only ones left are above a certain minimum <strong>area</strong>.  For example, using same shape as before, but highlighting all the triangular areas:</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon7.png" class="figure-img img-fluid rounded" alt="Figure 6: A simple polygon with each vertex's triangular area highlighted" />
  <figcaption class="figure-caption text-center">
    Figure 6: A simple polygon with each vertex's triangular area highlighted 
    
  </figcaption>
</figure>

<p>We can see from this picture that the small blue, purple, black, and red areas would clearly be elimitated due to their area, whereas the large green, black, red, and purple areas would remain.  The areas are removed by removing the vertex that creates the triangle.</p>

<p>One benefit of this algorithm is that it’s simpler - no subdivision or recombining is necessary nor recursion.  The algorithm goes something like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c1">// NOT INTENDED TO BE SOURCE CODE - MORE PSUEDO CODE</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="nf">visvalingam</span><span class="o">(</span><span class="nc">List</span><span class="o">&lt;</span><span class="nc">Vertex</span><span class="o">&gt;</span> <span class="n">vertices</span><span class="o">,</span> <span class="kt">double</span> <span class="n">minArea</span><span class="o">)</span> <span class="o">{</span>
    <span class="c1">// iterate on all input vertices O(n) and compute the area</span>
    <span class="c1">// and put them in a priority queue to sort them by area O(log n)</span>
    <span class="nc">Queue</span><span class="o">&lt;</span><span class="nc">VertexWithArea</span><span class="o">&gt;</span> <span class="n">queue</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PriorityQueue</span><span class="o">&lt;</span><span class="nc">VertexWithArea</span><span class="o">&gt;();</span>

    <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
    <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
        <span class="nc">Vertex</span> <span class="n">v0</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">size</span><span class="o">-</span><span class="mi">1</span> <span class="o">:</span> <span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="o">);</span>
        <span class="nc">Vertex</span> <span class="n">v1</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
        <span class="nc">Vertex</span> <span class="n">v2</span> <span class="o">=</span> <span class="n">vertices</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span> <span class="o">==</span> <span class="n">size</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="o">);</span>
        
        <span class="kt">double</span> <span class="n">area</span> <span class="o">=</span> <span class="n">getTriangleArea</span><span class="o">(</span><span class="n">v0</span><span class="o">,</span> <span class="n">v1</span><span class="o">,</span> <span class="n">v2</span><span class="o">);</span>
        <span class="nc">VertexWithArea</span> <span class="n">vwa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">VertexWithArea</span><span class="o">(</span><span class="n">v1</span><span class="o">,</span> <span class="n">area</span><span class="o">);</span>
        <span class="n">queue</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">vwa</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// iterate over the priority queue and eliminate vertices</span>
    <span class="k">do</span> <span class="o">{</span>
        <span class="nc">VertexWithArea</span> <span class="n">vwa</span> <span class="o">=</span> <span class="n">queue</span><span class="o">.</span><span class="na">poll</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">vwa</span><span class="o">.</span><span class="na">area</span> <span class="o">&lt;</span> <span class="n">minArea</span><span class="o">)</span> <span class="o">{</span>
            <span class="c1">// remove this vertex</span>

            <span class="c1">// NOTE: you need to recompute the area of the adjacent vertices</span>
            <span class="c1">// and have them get resorted in the priority queue (just remove them</span>
            <span class="c1">// and re-add them back or something)</span>
        <span class="o">}</span>
    <span class="o">}</span> <span class="k">while</span> <span class="o">(!</span><span class="n">queue</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">());</span>

    <span class="c1">// what's left is the simplified polygon</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This would yield the same result as the Douglas-Peucker algorithm above, but that’s not guaranteed.  Another nice attribute of this algorithm is that it’s ever evolving the result, to the point that it could be use for other purposes like <a href="https://bost.ocks.org/mike/simplify/">zoomable maps</a>.</p>

<p>The downside of this algorithm is the selection of the minimum area.  It’s much harder to select that properly than a distance metric.</p>

<h2 id="self-intersection" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#self-intersection">
          #
        </a>
    </span>
    Self-Intersection
</h2>
<p>The last topic I want to mention is that of self-intersection.  All these algorithms will reduce the simple polygon by removing vertices.  The removal of a vertex is not guaranteed to prevent self-intersections in the resulting simplified polygon.  For example, figure 7 shows a simple polygon where the smallest triangle (green area) contains another vertex (P5).  If we remove P2 (and therefore the green triangle) we’d create a self-intersection - the segment from P1 to P3 would intersect the segments connecting to P5.</p>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/sample-simple-polygon8.png" class="figure-img img-fluid rounded" alt="Figure 7: A simple polygon where the simplification methods would produce a self-intersection" />
  <figcaption class="figure-caption text-center">
    Figure 7: A simple polygon where the simplification methods would produce a self-intersection 
    
  </figcaption>
</figure>

<p>We could handle this in a straight forward way by testing all segments against the segment we’re adding (P1-P3), but that’d be pretty inefficient and take our computational complexity up to \(O(n^2)\).  If you squint really hard though, you can probably see that this is just a collision detection problem and we’ve dealt with it before.</p>

<p>Recall that for global collision detection we have a phased approach that starts with a broadphase which eliminates those pairs that cannot intersect, but allows some pairs that may not be intersecting proceed to the next phase.  The next phase, the narrowphase, performs the accurate collision detection.  We can apply the same concept here, in fact, you could even employ the very same algorithm and implementation.  Dump all the individual segments into your broadphase and then everytime you detect a vertex to remove, check the resulting line segment for collisions with the broadphase.  Then, check those potential collisions using a simple segment-vs-segment intersection test.  You can even stop the whole collision detection process when you detect the first collision.  Nice!</p>

<p>We’ve now detected a self-intersection, but what do we do about it?  Well, the choice is yours really.  I raised the white flag and just left the vertex in the resulting polygon and continued processing other vertices.  You could try to <a href="https://www.jasondavies.com/simplify/">generate a new point which removes the feature, but doesn’t create the self-intersection too</a>.</p>

<h2 id="results" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#results">
          #
        </a>
    </span>
    Results
</h2>
<p>To top things off I’d like to share some results of how well this process can work for complex simple polygons.</p>

<table class="table">
  <thead>
    <tr>
      <th>Shape</th>
      <th style="text-align: right">Original # Vertices</th>
      <th style="text-align: right">Original # Convex</th>
      <th style="text-align: right">Simplified # Vertices</th>
      <th style="text-align: right">Simplified # Convex</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Nazca Monkey</td>
      <td style="text-align: right">1204</td>
      <td style="text-align: right">444</td>
      <td style="text-align: right">304</td>
      <td style="text-align: right">124</td>
    </tr>
    <tr>
      <td>Nazca Heron</td>
      <td style="text-align: right">1036</td>
      <td style="text-align: right">370</td>
      <td style="text-align: right">115</td>
      <td style="text-align: right">43</td>
    </tr>
    <tr>
      <td>Bird</td>
      <td style="text-align: right">275</td>
      <td style="text-align: right">102</td>
      <td style="text-align: right">38</td>
      <td style="text-align: right">18</td>
    </tr>
  </tbody>
</table>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/nazca-monkey-result.png" class="figure-img img-fluid rounded" alt="Figure 8: Nazca Monkey original (red), simplified (blue), and decomposed" />
  <figcaption class="figure-caption text-center">
    Figure 8: Nazca Monkey original (red), simplified (blue), and decomposed 
    
  </figcaption>
</figure>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/nazca-heron-result.png" class="figure-img img-fluid rounded" alt="Figure 9: Nazca Heron original (red), simplified (blue), and decomposed" />
  <figcaption class="figure-caption text-center">
    Figure 9: Nazca Heron original (red), simplified (blue), and decomposed 
    
  </figcaption>
</figure>

<figure class="figure">
  <img src="/assets/posts/2021-06-10-simple-polygon-simplification/bird-result.png" class="figure-img img-fluid rounded" alt="Figure 10: Bird original (red), simplified (blue), and decomposed" />
  <figcaption class="figure-caption text-center">
    Figure 10: Bird original (red), simplified (blue), and decomposed 
    
  </figcaption>
</figure>

<h2 id="final-remarks" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#final-remarks">
          #
        </a>
    </span>
    Final Remarks
</h2>
<p>In summary, polygon simplification can be used to reduce the complexity of simple polygons to support better decompositions which supports better physical simulation.  These algorithms can be used in realtime simulations to cleanse user input, but <em>will</em> modify the input.  Choice of the minimum distances/area is the remaining hurdle to using these without user input.  One thought I have for that would be to analyze the original simple polygon and come up some metrics to automatically determine the minimum values.  Ideally all of this would be part of a <em>toolchain</em> where you can specify these parameters to achieve the most optimal result for your scenario, likely tweaking the result further manually.</p>

<h2 id="references" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#references">
          #
        </a>
    </span>
    References
</h2>

<ul>
  <li><a href="https://github.com/dyn4j/dyn4j/tree/master/src/main/java/org/dyn4j/geometry/simplify">Reference implementation for this post</a></li>
  <li><a href="http://psimpl.sourceforge.net/radial-distance.html">Vertex Cluster Reduction</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm">Douglas-Peucker (Wikipedia)</a></li>
  <li><a href="http://psimpl.sourceforge.net/douglas-peucker.html">Douglas-Peucker</a></li>
  <li><a href="https://bost.ocks.org/mike/simplify/">Visvalingam</a></li>
  <li><a href="https://www.jasondavies.com/simplify/">Visvalingam with self-intersection avoidance</a></li>
  <li><a href="https://hull-repository.worktribe.com/output/459275">Visvalingam Paper</a></li>
  <li><a href="https://breki.github.io/line-simplify.html">A source with a lot of information/links on line simplification</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Coastline_paradox">Coastline Paradox</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Fractal_dimension">Fractal Dimensions</a></li>
</ul>]]></content><author><name>William Bittle</name></author><category term="Game Development" /><category term="Blog" /><category term="dyn4j" /><category term="Game Development" /><summary type="html"><![CDATA[When simulating physical interaction between objects, there are many tradeoffs used to keep performance acceptable for realtime scenarios. The geometry of an object, or it’s shape, is no exception. To accelerate calculations, collision detection systems will enforce rules about what types of shapes are supported - a key example is Convexity. Not all objects are convex, and you might even argue that most aren’t, but we can combine these convex pieces to create non-convex objects.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/posts/2021-06-10-simple-polygon-simplification/nazca-monkey-result.png" /><media:content medium="image" url="https://dyn4j.org/assets/posts/2021-06-10-simple-polygon-simplification/nazca-monkey-result.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A Faster Broadphase in dyn4j 4.0.0</title><link href="https://dyn4j.org/2021/05/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/" rel="alternate" type="text/html" title="A Faster Broadphase in dyn4j 4.0.0" /><published>2021-05-02T01:36:59-04:00</published><updated>2021-05-02T01:36:59-04:00</updated><id>https://dyn4j.org/2021/05/a-faster-broadphase-in-dyn4j-4-0-0</id><content type="html" xml:base="https://dyn4j.org/2021/05/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/"><![CDATA[<p>Version 4.0.0 had <a href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md#v400---august-29th-2020">a lot of changes</a> to the API to support better extensibility, testability, and maintainability, but the biggest change was the 30-40% improvement in performance.  This type of of performance gain is huge and a boon for sure for all those using the library - but just how was it done?</p>

<p>If you’ve ever done performance analysis or improvements to a piece of code, you are aware that improving performance by saving one object allocation, for example, is nice, but often not very impactful.  Naturally, if you’re able to find lots of those instances, these can add up, but the most impactful performance improvements come from algorithms.  Choosing algorithms that are better suited to the problem, more efficient wrt. complexity, better cache locality, etc.</p>

<p>Specifically, this performance improvement was in the Broadphase collision detection system and how it handles detection from frame to frame.</p>

<h2 id="background" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#background">
          #
        </a>
    </span>
    Background
</h2>
<p>First a bit of background.  dyn4j includes a system for detecting collisions between objects.  This system is broken down into three main phases:</p>

<ul>
  <li>Broadphase</li>
  <li>Narrowphase</li>
  <li>Manifold Generation</li>
</ul>

<p>Why the 3-phase approach?  The narrowphase algorithms are expensive to run, so if we can reduce the possible set of colliding pairs, then we save a lot of processing time.  If we didn’t do this, we’d need to test every object with every other object.  That’s \(O(n^2)\) tests - very inefficient especially when it’s more common to <em>not</em> be colliding than they other way around.</p>

<p>Thus, the broadphase is where we detect all the <em>potential</em> pairs of colliding objects.  More specifically, it returns a set of collision pairs that <strong>may</strong> be colliding.  Those pairs that are not included in this set are <strong>definitely NOT</strong> colliding.  The pairs are handed over to the narrowphase where we find out if they truly are or aren’t colliding.  Now, how does it do this?  And how does it do it more efficiently than the narrowphase?</p>

<h2 id="broadphase-collision-detection" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#broadphase-collision-detection">
          #
        </a>
    </span>
    Broadphase Collision Detection
</h2>
<p>Broadphase collision detection algorithms center around the idea of choosing a simple bounding shape that encompasses the object.  For example, let’s imagine I have a simple square shape and I want to create a bounding shape for it.  I can use any type of shape I want, but clearly the choice will determine the efficiency of the algorithm.  In the below picture you see three different possibilities (these are the more common choices):</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/bounding-shapes.png" class="figure-img img-fluid rounded" alt="Example Bounding Shapes" />
  <figcaption class="figure-caption text-center">
    Example Bounding Shapes 
    
  </figcaption>
</figure>

<p>The first is a bounding circle.  This bounding shape has very quick collision detection - compare the squared distance between the two objects against the sum of their radii. The downside is that long shapes create huge bounding circles.  If you could guarantee that your shapes were all compact, this might be a good choice, but not for a generic collision detection system that has no idea what objects will look like.</p>

<p>The second is an Axis-Aligned Bounding Box, AABB for short.  It’s a rectangle that’s always <em>aligned</em> to the x and y axes.  This solves the problem that the bounding circle had where long objects would create large bounding shapes.  However, what happens if that long object is rotated about it’s center - it’s the same issue as the circle bounding shape.  AABB does better, but maybe not the best.  To test for collision between two AABBs we only need to compare their min/max values - 4 comparisons.</p>

<p>The last is an Oriented Bound Box, or OBB for short.  It’s a rectangle that’s always <em>oriented</em> with the shape.  If the shape is rotated at 45 degress, then the bounding rectangle is rotated at 45 degrees.  Nice!  This solves our problem of rotated long objects generating huge bounding shapes.  Not so fast - there’s a problem with this approach.  To check for collision between two OOBs you have to use more sophisticated routines.  In fact, one of those routines is effectively a narrowphase algorithm - we don’t get any benefit out of a broadphase that’s as expensive as the narrowphase.</p>

<blockquote>
  <p>You may have noticed that the bounding shapes in the image above didn’t exactly fit the object.  This was mainly to show the differences in the bounding area, but as we’ll see soon, this is actually one of the sources of the performance enhancement being described in the post.</p>
</blockquote>

<p>Having an easier collision detection proceedure isn’t all the broadphase does - we still need to contend with that \(O(n^2)\) problem.  We could brute force it by testing everything against everything, but that’ll be horribly inefficient.  What can we do?  The answer is to subdivide the problem into smaller and smaller chunks so that when we test the higher level chunk and it’s negative, we move on - there’s no reason to do any tests against objects inside that chunk.  The image below highlights this hierarchy of chunks:</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/hierarchy.png" class="figure-img img-fluid rounded" alt="A bounding shape hierarchy" />
  <figcaption class="figure-caption text-center">
    A bounding shape hierarchy 
    
  </figcaption>
</figure>

<p>For example, if I’m testing an object’s AABB against the purple region in the image above and I detect no collision, there’s no reason to test the interior regions since they can’t possibly be colliding if the purple region isn’t.  If we could build a data structure that would organize the scene in this fashion we could reduce the number of collision tests significantly.  For example, if we could organize the scene into a binary tree structure, the number of tests would then be \(O(n\log_2{n})\).</p>

<blockquote>
  <p>There are other partitioning schemes as well: grid-based, quadtree, BSPs, etc.</p>
</blockquote>

<h2 id="bounding-shape-expansion" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#bounding-shape-expansion">
          #
        </a>
    </span>
    Bounding shape Expansion
</h2>
<p>Creating a data structure to do less collision tests isn’t a trivial task.  In addition to the creation of the initial state, we also have to keep it up-to-date as those objects move and rotate over time.  It must be efficient at updating so that we don’t trade one performance issue for another.  To avoid going down the rabbit hole of describing various data structures that can be used in broadphase collision detection, let’s just assume that you have one and updates to that data structure aren’t cheap.</p>

<p>Enter bounding shape exapansion.  If you recall from the section above where we laid out a couple of options for bounding shapes, you may have noticed that the bounding shapes for the square didn’t fit around it tightly.  This was primarily to highlight the different bounding shapes, but also to draw attention to the fact that they don’t <em>have</em> to fit tightly.  Imagine I <em>expand</em> the bounding shape for a square and put that expanded bounding shape into the broadphase.  The effect is that when the square moves/rotates, I don’t have to update the expanded bounding shape unless it leaves that expanded region.</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/expanded-bounding-shape.png" class="figure-img img-fluid rounded" alt="An object in it's expanded bounding shape in frame 1 and frame 2" />
  <figcaption class="figure-caption text-center">
    An object in it's expanded bounding shape in frame 1 and frame 2 
    
  </figcaption>
</figure>

<p>In \(T = 2\), the square only moved one unit to the right and is still fully contained in the bounding AABB.  There’s no reason to update the bounding shape because it would not effect the end result.</p>

<p>Let’s review what we’re trying to do here - we want to send less pairs to the narrowphase, but we also want to reduce the number of updates to our data structure.  Sending less pairs to the narrowphase means the expensive collision detection there is run less often.  Less updates to our broadphase data structure means we do less work per frame.  If we choose tighly fitting bounding shapes we get the smallest number of collision tests in the broadphase, but for every one of those objects that’s moving/rotating, the data structure will need to be updated every frame.  If we choose super fat bounding shapes we end up doing more collision tests in the broadphase, but we don’t have to update the broadphase data structure as often (as described above).</p>

<p>The key then is to strike a balance between expanding a lot and not expanding at all.  This is a configurable property in dyn4j.</p>

<h2 id="the-new-optimization" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#the-new-optimization">
          #
        </a>
    </span>
    The New Optimization
</h2>
<p>At this point, we have a broadphase collision detection algorithm that uses a data structure to avoid the \(O(n^2)\) number of collision tests, a collision detection test that is far quicker than the narrowphase, and we’ve chosen an appropriate bounding shape expansion to avoid rebuilding te data structure every frame.  What more can be done?</p>

<p>To take a step back, think about how a simulation evolves over time:</p>

<ul>
  <li>Detect collisions</li>
  <li>Resolve collisions</li>
  <li>Move objects to new positions</li>
</ul>

<p>Or something like this:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
</pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">void</span> <span class="nf">runSimulation</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">simulationStep</span><span class="o">();</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simulationStep</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">// get ALL broadphase collision pairs</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">pairs</span> <span class="o">=</span> <span class="n">broadphase</span><span class="o">.</span><span class="na">getPairs</span><span class="o">();</span>

    <span class="c1">// filter broadphase pairs using the narrowphase</span>
    <span class="nc">Iterator</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">iterator</span> <span class="o">=</span> <span class="n">pairs</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
    <span class="k">while</span> <span class="o">(</span><span class="n">iterator</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
        <span class="nc">Pair</span> <span class="n">pair</span> <span class="o">=</span> <span class="n">iterator</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">narrowphase</span><span class="o">.</span><span class="na">detect</span><span class="o">(</span><span class="n">pair</span><span class="o">))</span> <span class="o">{</span>
            <span class="c1">// then remove it</span>
            <span class="n">iterator</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
        <span class="o">}</span>
    <span class="o">}</span>

    <span class="c1">// resolve collisions</span>
    <span class="c1">// ...</span>

    <span class="c1">// move objects to new positions</span>
    <span class="c1">// ...</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This flow would be called for every frame of the simulation.  Thus, for every frame we have to retest everything with everything.  this seems like a lot of work, but it makes sense - we need to make sure that new collisions are detected and old collisions are no longer handled.  The magic for the improved performance in dyn4j 4.0.x lies in this process.</p>

<p>Imagine that we have the following configuration on frame 1 of our simulation:</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/scene1.png" class="figure-img img-fluid rounded" alt="Frame 1 of our sample scene" />
  <figcaption class="figure-caption text-center">
    Frame 1 of our sample scene 
    
  </figcaption>
</figure>

<p>We have three squares with expanded bounding shapes (AABBs in this case).  The purple and teal expanded bounding shapes are overlapping.  The orange object’s expanded bounding shape is offset because the orange object moved last frame.  Also imagine the purple object is moving as well, but the teal object is stationary.  In this setup we have 3 objects and therefore 3 collision tests to do in the worst case (brute-force).  Purple vs. Teal, Purple vs. Orange, Teal vs. Orange.</p>

<blockquote>
  <p>We don’t test Purple vs. Teal <em>and</em> Teal vs. Purple as this would give the same result.</p>
</blockquote>

<p>Next, imagine the simulation proceeds to frame 2:</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/scene2.png" class="figure-img img-fluid rounded" alt="Frame 2 of our sample scene" />
  <figcaption class="figure-caption text-center">
    Frame 2 of our sample scene 
    
  </figcaption>
</figure>

<p>The purple and orange object have moved two units to the left while the teal object stayed stationary.  Notice that the purple object’s expanded bounding shape remained the same, but the orange object’s expanded bounding shape changed.  Now, think critically here - from the last frame to this frame, what do we <strong>really</strong> need to test?  Everything again?</p>

<p>You’ve probably already guessed it, but no we don’t.  The collision between the purple and teal objects hasn’t changed - no reason to test it again.  That said, the orange object’s expanded bounding shape <em>did</em> change, so we <em>have</em> to test that.  That means in frame 2 we only do 2 collision tests!  Orange vs. Teal and Orange vs. Purple.  Therefore, the performance enhancement we can make is to track, over time, the collisions between objects in the broadphase and only re-test those that have had their expanded bounding shapes updated.</p>

<blockquote>
  <p><strong>KEY</strong>: Therefore, the performance enhancement we can make is to track, over time, the collisions between objects in the broadphase and only re-test those that have had their expanded bounding shapes updated.</p>
</blockquote>

<p>Enhancing our code from before:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c1">// ...</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">pairs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;();</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simulationStep</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">// get only the NEW broadphase collision pairs</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">bpairs</span> <span class="o">=</span> <span class="n">broadphase</span><span class="o">.</span><span class="na">getNewPairs</span><span class="o">();</span>

    <span class="c1">// filter broadphase pairs using the narrowphase</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">Pair</span> <span class="n">pair</span> <span class="o">:</span> <span class="n">bpairs</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">narrowphase</span><span class="o">.</span><span class="na">detect</span><span class="o">(</span><span class="n">pair</span><span class="o">))</span> <span class="o">{</span>
            <span class="c1">// then track it in our cross frame set of pairs</span>
            <span class="n">pairs</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">pair</span><span class="o">);</span>
        <span class="o">}</span>
    <span class="o">}</span>
    <span class="c1">// ...</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Ok, great - this is cool and all, but how do we handle frame 3 of the simulation:</p>

<figure class="figure">
  <img src="/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/scene3.png" class="figure-img img-fluid rounded" alt="Frame 3 of our sample scene" />
  <figcaption class="figure-caption text-center">
    Frame 3 of our sample scene 
    
  </figcaption>
</figure>

<p>In this frame, we see that the teal and orange object’s bounding shapes have not moved, but the purple object’s has.  As a result, we only test Purple vs. Teal and Purple vs. Orange and find neither is colliding.  If the contract for our broadphase is to <em>only</em> return potentially colliding pairs, it would return <strong>nothing</strong>.  But how would we know that the collision between Purple and Teal is no longer happening?</p>

<p>It turns out the answer is pretty simple, as you go through <strong>ALL</strong> the stored pairs, remove those who’s bounding shapes are no longer colliding:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="c1">// ...</span>
<span class="nc">List</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">pairs</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;();</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">simulationStep</span><span class="o">()</span> <span class="o">{</span>
    <span class="c1">// get only the NEW broadphase collision pairs</span>
    <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">bpairs</span> <span class="o">=</span> <span class="n">broadphase</span><span class="o">.</span><span class="na">getNewPairs</span><span class="o">();</span>
    
    <span class="c1">// add them to the set of pairs from the last frame</span>
    <span class="c1">// obviously you'll need to make sure you don't add</span>
    <span class="c1">// a pair that already exists in the pair list from </span>
    <span class="c1">// last frame - I'm not doing that here for simplicity</span>
    <span class="k">for</span> <span class="o">(</span><span class="nc">Pair</span> <span class="n">pair</span> <span class="o">:</span> <span class="n">bpairs</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">pairs</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">pair</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="c1">// filter out those that are no longer colliding</span>
    <span class="nc">Iterator</span><span class="o">&lt;</span><span class="nc">Pair</span><span class="o">&gt;</span> <span class="n">allIterator</span> <span class="o">=</span> <span class="n">pairs</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
    <span class="k">while</span> <span class="o">(</span><span class="n">allIterator</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
        <span class="nc">Pair</span> <span class="n">pair</span> <span class="o">=</span> <span class="n">allIterator</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
        <span class="k">if</span> <span class="o">(!</span><span class="n">pair</span><span class="o">.</span><span class="na">isBoundingShapeOverlapping</span><span class="o">())</span> <span class="o">{</span>
            <span class="c1">// then remove it</span>
            <span class="n">allIterator</span><span class="o">.</span><span class="na">remove</span><span class="o">();</span>
            <span class="k">continue</span><span class="o">;</span>
        <span class="o">}</span>

        <span class="c1">// filter broadphase pairs using the narrowphase</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">narrowphase</span><span class="o">.</span><span class="na">detect</span><span class="o">(</span><span class="n">pair</span><span class="o">))</span> <span class="o">{</span>
            <span class="c1">// then continue processing the collision...</span>
        <span class="o">}</span>
    <span class="o">}</span>
<span class="o">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You might say at this point, “wait a minute, we’re doing collision tests with the expanded bounding shapes again - isn’t that even worse?”  It turns out no, the key being that we’re not doing it for ALL pairs, only those that are currently tracked as potentially overlapping.  We’ve changed the algorithm from \(O(n\log_2{n})\), for example, to \(O(m\log_2{n})\), where \(m\) is the number of objects who’s expanded AABBs have been updated.</p>

<h2 id="evaluation" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#evaluation">
          #
        </a>
    </span>
    Evaluation
</h2>
<p>Let’s evaluate the improvement with some numbers and assumptions.  Assume we have a scene with 5000 objects.  Of those 5000 objects, 500 are moving every frame.  Of those 500 moving objects, 50 of them need to update their expanded shapes every frame.  Assume that of all 5000 objects we have 1000 collisions.  Finally, assume that our broadphase has complexity \(n\log_2{n}\) to do all tests.</p>

<table class="table">
  <thead>
    <tr>
      <th>Scenario</th>
      <th style="text-align: right">Formula</th>
      <th style="text-align: right">Tests</th>
      <th style="text-align: right">%</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>No enhancement with or without bounding shape expansion.</td>
      <td style="text-align: right">\(5000\log_2{5000}\)</td>
      <td style="text-align: right">61,438</td>
      <td style="text-align: right">-</td>
    </tr>
    <tr>
      <td>With enhancement but without bounding shape expansion.</td>
      <td style="text-align: right">\(500\log_2{5000} + 1000\)</td>
      <td style="text-align: right">7,143</td>
      <td style="text-align: right">87%</td>
    </tr>
    <tr>
      <td>With enhancement and bounding shape expansion.</td>
      <td style="text-align: right">\(50\log_2{5000} + 1000\)</td>
      <td style="text-align: right">1,614</td>
      <td style="text-align: right">97%</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>The \(+ 1000\) in the above forumlas account for re-testing of overlap to account for objects moving out of collision (as described in the Frame 3 discussion in previous section).</p>
</blockquote>

<p>From scenario 1 to 3 that’s nearly a 100% decrease in the number of tests - wow!  Clearly, the numbers I chose were to highlight the improvement, but the gains will be highly dependent on your simulation.  You could think of scenario 2 being the worst case for this enhancement and scenario 3 being the best case.  These numbers are also dependent on the value used to expand the bounding shapes and the ratio of static vs. moving shapes in your scene.  If all 5000 objects were moving and we chose an expansion value that forced an update for all objects every frame, then we’re worse off than where we started.  Also keep in mind that the storage and maintenance of the frame to frame collision data has overhead.</p>

<blockquote>
  <p>dyn4j saw anywhere between 30-40% <em>raw performance</em> increase implementing this simple enhancement (measuring the same simulations before/after the enhancement)</p>
</blockquote>

<p>A positive side effect of tracking the collisions from frame to frame is that we can reduce allocation by storing the results and reusing those objects for the next frame.  In addition, we can build new API surface that enables callers to query this information outside of the standard listener pattern giving them more opportunity to do things in a simpler manner.</p>]]></content><author><name>William Bittle</name></author><category term="Collision Detection" /><category term="Game Development" /><category term="Blog" /><category term="dyn4j" /><category term="Collision Detection" /><category term="Game Development" /><summary type="html"><![CDATA[Version 4.0.0 had a lot of changes to the API to support better extensibility, testability, and maintainability, but the biggest change was the 30-40% improvement in performance. This type of of performance gain is huge and a boon for sure for all those using the library - but just how was it done?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/scene1.png" /><media:content medium="image" url="https://dyn4j.org/assets/posts/2021-05-02-a-faster-broadphase-in-dyn4j-4-0-0/scene1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">WordPress to GitHub Pages</title><link href="https://dyn4j.org/2021/04/wordpress-to-github-pages/" rel="alternate" type="text/html" title="WordPress to GitHub Pages" /><published>2021-04-30T01:36:59-04:00</published><updated>2021-04-30T01:36:59-04:00</updated><id>https://dyn4j.org/2021/04/wordpress-to-github-pages</id><content type="html" xml:base="https://dyn4j.org/2021/04/wordpress-to-github-pages/"><![CDATA[<p>In my last post where I talked about the reasons for moving the dyn4j site to GitHub Pages and off of WordPress, I mentioned another post to describe the technical details of the move - this is it!  Seriously though, it wasn’t trivial, but that’s in part due to some self-imposed (inflicted?) constraints.</p>

<figure class="figure">
  <img src="/assets/posts/2021-04-30-wordpress-to-github-pages/wordpress.png" class="figure-img img-fluid rounded" alt="WordPress" />
  <figcaption class="figure-caption text-center">
    WordPress 
    
    <small><a href="https://wordpress.org/">Credit</a></small>
    
  </figcaption>
</figure>

<p>To start, I wanted the site to be something like what I had before - I liked the layout of the landing page and blog portion of the site.  If you were less concerned about what the site looked like you could install an off-the-shelf layout/theme.  GitHub Pages actually has a few to choose from to give you a kickstart.  I also wanted it to be fast and low maintenance which meant using CDNs for any linked libraries, the number of linked libraries should be low, and use of liquid templates to compartmentalize components for reuse.  Probably not surprising, as the move progressed I found issues, enforced new constraints, and had to learn a lot.</p>

<p>Full disclosure - I’ve been a web developer for 15 years so a lot of the basic concepts are familiar to me - Jekyll, Ruby, Liquid templates, etc. were all new to me.  I was already familiar with markdown as well.</p>

<blockquote>
  <p>The dyn4j.org website is a <a href="https://github.com/dyn4j/dyn4j.github.io">public repo</a> on GitHub.  Reference the source for an additional resource when going through your conversion.</p>
</blockquote>

<h2 id="getting-started" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#getting-started">
          #
        </a>
    </span>
    Getting Started
</h2>
<p>To get started you’ll need to <a href="https://jekyllrb.com/docs/">install the jekyll tooling</a> which includes ruby.  For development of the site I went with Visual Studio Code which has good support for markdown, yml, css, sass, JavaScript, and so on.  I ended up using all of these in some way and having syntax highlighting was really important.</p>

<figure class="figure">
  <img src="/assets/posts/2021-04-30-wordpress-to-github-pages/jekyll.png" class="figure-img img-fluid rounded" alt="Jekyll" />
  <figcaption class="figure-caption text-center">
    Jekyll 
    
    <small><a href="https://jekyllrb.com/">Credit</a></small>
    
  </figcaption>
</figure>

<p>After that I went through the <a href="https://jekyllrb.com/docs/step-by-step/01-setup/">step by step tutorial</a> to get a better understanding of the platform.  I’ll confess, I didn’t go through all of it right away, I focused mostly on the inital build of the site, index.html, includes, layouts, and front matter.</p>

<blockquote>
  <p>The key with Jekyll and GitHub pages is that it’s a framework for building static web sites.  You build out your content, pages, JavaScript, etc. and Jekyll combines everything to make static html pages.  This is what makes Jekyll sites really fast.</p>
</blockquote>

<p>I also followed the <a href="https://docs.github.com/en/pages">GitHub guides</a> to get a repo setup properly and cloned it locally.</p>

<blockquote>
  <p>Refer to the GitHub Page documentation to understand what things are not supported. For example, not all Jekyll plugins are supported.</p>
</blockquote>

<h2 id="build-the-skeleton" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#build-the-skeleton">
          #
        </a>
    </span>
    Build the Skeleton
</h2>
<p>My first goal was to build the basic skeleton of the site and the landing page.  I wanted to choose an existing template, but they all seemed OK - nothing really wrong with them, just not what I wanted so I decided to download a few example jekyll themes: (<a href="http://jekyllthemes.org/themes/no-style-please/">no-style-please</a> and <a href="http://jekyllthemes.org/themes/creative-theme/">creative theme</a>) as references.  I also decided on <a href="https://getbootstrap.com/docs/5.0/getting-started/introduction/">Bootstrap 5</a> for the basic UI components and then set after it.</p>

<figure class="figure">
  <img src="/assets/posts/2021-04-30-wordpress-to-github-pages/bootstrap.png" class="figure-img img-fluid rounded" alt="Bootstrap 5" />
  <figcaption class="figure-caption text-center">
    Bootstrap 5 
    
    <small><a href="https://getbootstrap.com/">Credit</a></small>
    
  </figcaption>
</figure>

<p>This stage quickly devolved into fixing all the things I didn’t like about the old site and feverishly testing on all different screen sizes.  I spent a lot of time here.  I cleaned up verbiage, images, links, pretty much everything.  I’m happy I spent the time there, but it was a huge time suck.</p>

<p>For the animation I used <a href="https://michalsnik.github.io/aos/">AOS</a> since it had the capability to wait for the user to scroll before animating.  I also included the <a href="http://docs.mathjax.org/en/latest/index.html">MathJax</a> library since I have a few blog posts that are math heavy.</p>

<p>So at this point I had the landing page done and a decent pattern to follow for the rest of the site.</p>

<h2 id="build-the-blog" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#build-the-blog">
          #
        </a>
    </span>
    Build the Blog
</h2>
<p>The next step was to get the blog portion of the site going. For that I referenced the no-style-please theme to understand how to display them, where to put the posts, and so on.  I made it my own and modeled it after the existing site with a <a href="https://superdevresources.com/tag-cloud-jekyll/">tag cloud</a>, <a href="https://stackoverflow.com/questions/24191711/archive-jekyll-posts-by-year">grouped by year</a>, and <a href="https://gist.github.com/Phlow/a0e3fa686eb259fe7f76">category links</a> sections.  There are some limitations here that I didn’t really like, but it was a small price to pay.  For example, to create a link to a specific tag or category - not possible - instead you can create a page with <em>all</em> your posts grouped by tag/category and link to that page with an anchor to the correct location on the page.  Not huge deal - no one reads these anyway do they?  The other thing that wasn’t straight forward was the <a href="https://jekyllrb.com/docs/pagination/">paging</a> for the main blog landing page - I didn’t want a hundred blog posts to all be on the same page that the user lands on.</p>

<p>I built it out while using the same posts from the no-style-please theme first to get all the includes and layouts the way I wanted them.</p>

<figure class="figure">
  <img src="/assets/posts/2021-04-30-wordpress-to-github-pages/tagcloud.png" class="figure-img img-fluid rounded" alt="Tag Cloud Example" />
  <figcaption class="figure-caption text-center">
    Tag Cloud Example 
    
  </figcaption>
</figure>

<h2 id="moving-the-blog-posts" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#moving-the-blog-posts">
          #
        </a>
    </span>
    Moving the Blog Posts
</h2>
<p>For this step, I needed something to export all the posts from the WordPress site and convert them to Jekyll markdown.  For that I installed the <a href="https://wordpress.org/plugins/jekyll-exporter/">Jekyll Exporter</a> plugin into my WordPress site.  A couple of good things about this is that it also exported pages and assets (images).  The conversion was pretty good too.</p>

<p>That said there were some problems.  The first was the <code class="language-html highlighter-rouge">date</code> front matter data point.  It was in the format ISO format that you’d expect, but Jekyll needs the date formatted like <a href="https://jekyllrb.com/docs/front-matter/#predefined-variables-for-posts">YYYY-MM-DD HH:MM:SS +/-TTTT</a>.  This only matters if you want your posts sorted properly… (lot’s of eyerolling ensued). The second problem was that the exported/converted posts left a lot of HTML in them and I really wanted to move to entirely markdown.  I understand why it couldn’t convert those parts, they were complex HTML code, so I decided to roll my sleeves up and update all the posts.  This was very time consuming and I was very VERY bored.  I decided while I was modifying each post to review them as well.  This, while still boring, was very satisfying.  For example, going back through the constrained dynamics posts, I found a number of minor mistakes and added more clarifying content.  I also decided to change all the hard coded images of math to Latex for better maintainability.  Another issue was that the exported content didn’t properly change the path to images and those needed to be updated and migrated to the site’s assets folder manually.  There are <a href="https://stackoverflow.com/questions/19331362/using-an-image-caption-in-markdown-jekyll">couple of things you can do here</a> to make it easier to references images.  I also reviewed all links and cleaned those up.</p>

<blockquote>
  <p><strong>TIP</strong>: The date format from the Jekyll Exporter does not match what <a href="https://jekyllrb.com/docs/front-matter/#predefined-variables-for-posts">Jekyll is looking for</a>.  That said, as far as I can tell, GitHub Pages doesn’t care what I put in for the timezone offset - it just completely ignores it and considers the datetime is in UTC…</p>
</blockquote>

<p>All in all, I spent WAY too much time here.  I’m happy with the end result, but can’t imagine what I would have done if I had more than 60 posts (and most of my posts were short announcements for dyn4j releases - fixing the larger posts was brutal).</p>

<blockquote>
  <p><strong>TIP</strong>: There’s a gotcha here.  The Jekyll Exporter tool exports your posts as markdown files with front matter.  as part of the front matter there’s an <code class="language-html highlighter-rouge">id</code> field which I’m assuming was the original id from WordPress.  However, when you try to do <code class="language-html highlighter-rouge">post.id</code> you will get the slugified name of the post - the <code class="language-html highlighter-rouge">id</code> property of the post object cannot be overridden.</p>
</blockquote>

<h2 id="moving-the-comments" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#moving-the-comments">
          #
        </a>
    </span>
    Moving the Comments
</h2>
<p>I spent a lot of time thinking about whether to move comments or not.  GitHub Pages, being a static site host, meant that I wouldn’t be able to accept <em>more</em> comments unless I developed a different solution.  I had around 500 comments total - all moderated so they were quality.  I decided to move them over and make the decision whether to accept new comments later.  Jekyll has a way to generate static content from <a href="https://jekyllrb.com/docs/datafiles/">data</a>.  This is the mechanism we’ll use for comments.</p>

<blockquote>
  <p>To save you some time, I probably went the long way.  What I describe below is what I did.  After all that work I found <a href="https://damieng.com/blog/2018/05/28/wordpress-to-jekyll-comments#exporting-comments-from-wordpress">this</a>.  Somehow I overlooked this, so I don’t know what the output is like, but I’d probably start with that tool  if I were to do it over again.</p>
</blockquote>

<p>Moving the comments was a lot harder.  There wasn’t a nice exporter like there was for posts so I decided to just export them myself using the WordPress APIs.  To do so, I signed into WordPress admin and executed the following script(s):</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
</pre></td><td class="rouge-code"><pre><span class="kd">let</span> <span class="nx">promises</span> <span class="o">=</span> <span class="p">[];</span>

<span class="nx">promises</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/wp-json/wp/v2/posts?per_page=100&amp;page=1</span><span class="dl">'</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">){</span>
    <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="p">{</span>
                <span class="na">id</span><span class="p">:</span> <span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
                <span class="na">guid</span><span class="p">:</span> <span class="nx">item</span><span class="p">.</span><span class="nx">guid</span>
            <span class="p">};</span>
        <span class="p">});</span>
    <span class="p">});</span>
<span class="p">}));</span>

<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">7</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">promises</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/wp-json/wp/v2/comments?per_page=100&amp;page=</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">i</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">().</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="p">{</span>
                    <span class="na">id</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
                    <span class="na">post</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">post</span><span class="p">,</span>
                    <span class="na">parent</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">parent</span><span class="p">,</span>
                    <span class="na">date</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">date_gmt</span><span class="p">,</span>
                    <span class="na">content</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nx">rendered</span><span class="p">,</span>
                    <span class="na">author</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">author_name</span><span class="p">,</span>
                    <span class="na">avatars</span><span class="p">:</span> <span class="nx">e</span><span class="p">.</span><span class="nx">author_avatar_urls</span>
                <span class="p">};</span>
            <span class="p">});</span>
        <span class="p">});</span>
    <span class="p">}));</span>
<span class="p">}</span>

<span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span><span class="nx">promises</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">dataSets</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">dataSets</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
    <span class="kd">var</span> <span class="nx">postsById</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">posts</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">postsById</span><span class="p">[</span><span class="nx">posts</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">id</span><span class="p">]</span> <span class="o">=</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">guid</span><span class="p">.</span><span class="nx">rendered</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">let</span> <span class="nx">n</span> <span class="o">=</span> <span class="nx">dataSets</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">n</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">result</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="nx">dataSets</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">item</span><span class="p">.</span><span class="nx">post_guid</span> <span class="o">=</span> <span class="nx">postsById</span><span class="p">[</span><span class="nx">item</span><span class="p">.</span><span class="nx">post</span><span class="p">];</span>
            <span class="k">return</span> <span class="nx">item</span><span class="p">;</span>
        <span class="p">}));</span>
    <span class="p">}</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">result</span><span class="p">));</span>
<span class="p">});</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<blockquote>
  <p>The WordPress APIs have a limit of 100 records per page.</p>
</blockquote>

<p>You’ll need to tweak it for your scenario.  For example, I knew how many posts and comments I had so I just hard coded the number of API calls I needed to execute.  The script generated a nice JSON string in the console which I just copied and pasted into a file.</p>

<blockquote>
  <p>Not shown here, I also exported all posts to JSON in the same way for the next step.</p>
</blockquote>

<p>Jekyll acutally supports a few different formats for “data”.  One of which is JSON and at first I used the JSON generated in the previous step to feed the posts with the appropriate comments (by filtering them on the post_guid property).  But after making the decision to implement a solution for new comments, I decided to change how I was storing the comments.  I took this JSON file, read it into memory with a C# console application and converted it into a list of yml files, placed in folders named with the slugified name of the parent post (more on the why later).  Here’s the code for that:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">Program</span>
<span class="p">{</span>
    <span class="k">static</span> <span class="k">void</span> <span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">string</span> <span class="n">json</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="nf">ReadAllText</span><span class="p">(</span><span class="s">$"posts.json"</span><span class="p">);</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="n">Post</span><span class="p">&gt;</span> <span class="n">posts</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Post</span><span class="p">&gt;&gt;(</span><span class="n">json</span><span class="p">);</span>
        <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">Post</span><span class="p">&gt;</span> <span class="n">postMap</span> <span class="p">=</span> <span class="n">posts</span><span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Id</span><span class="p">);</span>

        <span class="n">json</span> <span class="p">=</span> <span class="n">File</span><span class="p">.</span><span class="nf">ReadAllText</span><span class="p">(</span><span class="s">$"comments.json"</span><span class="p">);</span>
        <span class="n">List</span><span class="p">&lt;</span><span class="n">Comment</span><span class="p">&gt;</span> <span class="n">comments</span> <span class="p">=</span> <span class="n">JsonConvert</span><span class="p">.</span><span class="n">DeserializeObject</span><span class="p">&lt;</span><span class="n">List</span><span class="p">&lt;</span><span class="n">Comment</span><span class="p">&gt;&gt;(</span><span class="n">cj</span><span class="p">);</span>

        <span class="kt">var</span> <span class="n">converter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ReverseMarkdown</span><span class="p">.</span><span class="nf">Converter</span><span class="p">();</span>
        <span class="kt">string</span> <span class="n">baseDir</span> <span class="p">=</span> <span class="n">Directory</span><span class="p">.</span><span class="nf">GetCurrentDirectory</span><span class="p">();</span>

        <span class="k">foreach</span> <span class="p">(</span><span class="n">Comment</span> <span class="n">comment</span> <span class="k">in</span> <span class="n">comments</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Post</span> <span class="n">post</span> <span class="p">=</span> <span class="n">postMap</span><span class="p">[</span><span class="n">comment</span><span class="p">.</span><span class="n">Post</span><span class="p">];</span>
            <span class="kt">string</span> <span class="n">path</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">baseDir</span><span class="p">,</span> <span class="n">post</span><span class="p">.</span><span class="n">Slug</span><span class="p">);</span>
            <span class="n">Directory</span><span class="p">.</span><span class="nf">CreateDirectory</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>

            <span class="kt">string</span> <span class="n">avatarUrl</span> <span class="p">=</span> <span class="n">comment</span><span class="p">.</span><span class="n">Avatars</span><span class="p">.</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">a</span><span class="p">.</span><span class="n">Key</span><span class="p">).</span><span class="nf">LastOrDefault</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="p">!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">a</span><span class="p">.</span><span class="n">Value</span><span class="p">)).</span><span class="n">Value</span><span class="p">;</span>
            <span class="kt">string</span> <span class="n">avatarProp</span> <span class="p">=</span> <span class="s">""</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">avatarUrl</span><span class="p">))</span>
            <span class="p">{</span>
                <span class="n">avatarProp</span> <span class="p">=</span> <span class="s">$"\navatar: </span><span class="p">{</span><span class="n">avatarUrl</span><span class="p">}</span><span class="s">"</span><span class="p">;</span>
            <span class="p">}</span>

            <span class="c1">// my wordpress content had all kinds of unicode characters I wanted to remove</span>
            <span class="kt">string</span> <span class="n">message</span> <span class="p">=</span> <span class="n">comment</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">Trim</span><span class="p">()</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;#8220;"</span><span class="p">,</span> <span class="s">"\""</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;#8221;"</span><span class="p">,</span> <span class="s">"\""</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;#8217;"</span><span class="p">,</span> <span class="s">"'"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;#8230;"</span><span class="p">,</span> <span class="s">"..."</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;#8211;"</span><span class="p">,</span> <span class="s">"-"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"\\*"</span><span class="p">,</span> <span class="s">"*"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"\""</span><span class="p">,</span> <span class="s">"\\\""</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"“"</span><span class="p">,</span> <span class="s">"'"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"”"</span><span class="p">,</span> <span class="s">"'"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;gt;"</span><span class="p">,</span> <span class="s">"&gt;"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"&amp;lt;"</span><span class="p">,</span> <span class="s">"&lt;"</span><span class="p">)</span>
                <span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="s">"\n"</span><span class="p">,</span> <span class="s">"\\n"</span><span class="p">);</span>

            <span class="c1">// match the date format Jekyll wants</span>
            <span class="kt">string</span> <span class="n">date</span> <span class="p">=</span> <span class="n">comment</span><span class="p">.</span><span class="n">Date</span><span class="p">.</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"yyyy-MM-dd HH:mm:ss zz00"</span><span class="p">);</span>

            <span class="kt">string</span> <span class="n">content</span> <span class="p">=</span> <span class="s">$"id: </span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">Id</span><span class="p">}</span><span class="s">\ndate: </span><span class="p">{</span><span class="n">date</span><span class="p">}</span><span class="s">\nauthor: </span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">Author</span><span class="p">}</span><span class="s">\nparent: </span><span class="p">{</span><span class="n">post</span><span class="p">.</span><span class="n">Slug</span><span class="p">}{</span><span class="n">avatarProp</span><span class="p">}</span><span class="s">\nmessage: \"</span><span class="p">{</span><span class="n">message</span><span class="p">}</span><span class="s">\""</span><span class="p">;</span>
            <span class="n">File</span><span class="p">.</span><span class="nf">WriteAllText</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="nf">Combine</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">Id</span><span class="p">}</span><span class="s">.yml"</span><span class="p">),</span> <span class="n">content</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I then copied+pasted the folders/files to the <code class="language-html highlighter-rouge">data/comments</code> folder in my Jekyll site and updated my templates, includes, layouts to use these.  I based my templates off of <a href="https://github.com/damieng/jekyll-blog-comments/tree/master/jekyll/_includes">these</a>.  The import of these had some problems - Jekyll warned about a couple of things and I just manually fixed those.</p>

<p>One interesting thing here is that I wanted to convert these to markdown too.  I tried to use the <a href="https://github.com/mysticmind/reversemarkdown-net">ReverseMarkdown</a> NuGet package, but the generated markdown didn’t render properly, so I just left the comments as is.</p>

<p>I also considered going through all the comments and fixing links, images, formatting, etc. and quickly gave up on that idea.  The output was good enough so I just let it all as is - there are just too many to go through.</p>

<h2 id="moving-pages" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#moving-pages">
          #
        </a>
    </span>
    Moving Pages
</h2>
<p>In WordPress you can create pages as well as posts/comments.  In dyn4j, there were only a few and these were converted via the Jekyll Exporter tool mentioned earlier, but just like posts, they still had a lot of HTML embeded in them.  Another issue is that they were horribly out of date.  So I decided to build these mostly from the ground up.  I used the existing pages for a good outline for content, but pretty much rewrote all of them in markdown.  I think the result is so much better - up to date documentation, more code samples, better references to classes in the library and so on.  I was also able to consolidate some pages and skip the conversion entirely.  I converted 4 pages of 5.</p>

<h2 id="code-highlighting" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#code-highlighting">
          #
        </a>
    </span>
    Code Highlighting
</h2>
<p>One thing I spent a lot of time on was code highlighting. It was very hard to understand how this works.  Basically, you write your code comments in single or triple tick marks and Jekyll parses the content and emits HTML <em>ready</em> for highlighting (using Rouge).  But to actually highlight the code you must add in a CSS file.  You can find a bunch of options.  <a href="https://bnhr.xyz/2017/03/25/add-syntax-highlighting-to-your-jekyll-site-with-rouge.html#create-a-css-file-for-the-highlighting-style-you-want">The process is described in detail here</a>, but the key that I kept missing was that you run a command to generate the CSS of choice, then you just include that in your <code class="language-html highlighter-rouge"><span class="nt">&lt;head&gt;</span></code>.</p>

<h2 id="custom-domain" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#custom-domain">
          #
        </a>
    </span>
    Custom Domain
</h2>
<p>No what’s really cool about all this is that you can configure GitHub Pages with a custom domain.  You can even enable HTTPS!  I decided at the start of this process that I would do this, but it turned out to be really frustrating for a number of reasons.  The first reason is due to the way DNS works - you change something and everything is broken for a while (a hour for example) until the change propagates.  I think in total dyn4j.org was probably down for 10 hours while I fiddled with this.  The second reason was due to my host through which I purchased the domain long ago - their tools are just so buggy.  Pages not loading, not allowing me to do things but not telling me why, etc.  A lot of this was trial and error.</p>

<p>I just realized I went on a rant and you don’t care - you just want to know what to do.  The first thing you should do is read the <a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site">official GitHub Pages documentation</a> and follow that.  It really is good documentation.</p>

<p>The first step is to configure your GitHub repo with the custom domain name.  It won’t work, but that’s ok.  You need to do this first so that another repo doesn’t steal your domain when you finally point your domain at GitHub IPs.  Once that’s complete, then you need to log into the host for your domain and update DNS.  I followed exactly what was in the GitHub Pages documentation and it <em>nearly</em> worked.  For my host, I had to create <code class="language-html highlighter-rouge">A</code> records (<a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain">step 7 of the official docs</a>) but I also had to create <code class="language-html highlighter-rouge">A</code> records using “@” as the source.  I also had to use the apex domain in GitHub pages, NOT the <code class="language-html highlighter-rouge">www</code> prefixed domain.</p>

<blockquote>
  <p><strong>TIP</strong>: If you want both the apex domain and <code class="language-html highlighter-rouge">www</code> subdomain to work with HTTPS, set the repo custom domain to the apex domain.  For example, if you own <code class="language-html highlighter-rouge">example.com</code>, set the repo’s custom domain to <code class="language-html highlighter-rouge">example.com</code>.  Then setup the <code class="language-html highlighter-rouge">CNAME</code> and <code class="language-html highlighter-rouge">A</code> records as defined in the docs.</p>
</blockquote>

<blockquote>
  <p><strong>TIP</strong>: Every host may have different ways to setup DNS.  In my case I had to add 4 more <code class="language-html highlighter-rouge">A</code> records using “@” instead of my apex domain pointing to GitHub IPs.</p>
</blockquote>

<blockquote>
  <p><strong>TIP</strong>: Be patient and expect your site to be down for a while.  DNS takes time to propagate and you want to make sure you are testing accurately.  After making changes in DNS, give it at least an hour (that was the default TTL on my host).</p>
</blockquote>

<blockquote>
  <p>See <a href="https://github.community/t/does-github-pages-support-https-for-www-and-subdomains/10360/75">here</a> for more help with custom domain setup.</p>
</blockquote>

<p>This was the final setup (not sure if I needed both the @ and dyn4j.org <code class="language-html highlighter-rouge">A</code> records, but I was pretty frustrated at this point and decided to leave it alone since it was working)</p>

<table class="table">
  <thead>
    <tr>
      <th>Type</th>
      <th>Source</th>
      <th>Destination</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>A</td>
      <td>@</td>
      <td>185.199.108.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>@</td>
      <td>185.199.109.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>@</td>
      <td>185.199.110.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>@</td>
      <td>185.199.111.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>dyn4j.org</td>
      <td>185.199.108.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>dyn4j.org</td>
      <td>185.199.109.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>dyn4j.org</td>
      <td>185.199.110.153</td>
    </tr>
    <tr>
      <td>A</td>
      <td>dyn4j.org</td>
      <td>185.199.111.153</td>
    </tr>
    <tr>
      <td>CNAME</td>
      <td>www</td>
      <td>dyn4j.github.io</td>
    </tr>
  </tbody>
</table>

<h2 id="accepting-new-comments" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#accepting-new-comments">
          #
        </a>
    </span>
    Accepting New Comments
</h2>
<p>Like I’ve mentioned before, the whole point of Jekyll is to produce a lightning fast static site generated from non-static data (like posts, comments, pages, templates, etc.)  So how can we accept new comments?  It was a great question and I found a <a href="https://haacked.com/archive/2018/06/24/comments-for-jekyll-blogs/">solution</a>.  It’s actually quite straight forward:</p>

<ul>
  <li>Create a comment form that will post data to an Azure Function</li>
  <li>Setup an Azure account ($200 free credit)</li>
  <li>Create an Azure Function to accept comments</li>
  <li>Add the necessary code to the function to create a branch on your site repo, create a comment yml file, and submit a pull request</li>
</ul>

<figure class="figure">
  <img src="/assets/posts/2021-04-30-wordpress-to-github-pages/azure.png" class="figure-img img-fluid rounded" alt="Azure" />
  <figcaption class="figure-caption text-center">
    Azure 
    
    <small><a href="https://azure.microsoft.com/">Credit</a></small>
    
  </figcaption>
</figure>

<p>You may recall from above that I left the “why” out when I was discussing moving away from a giant JSON file for comments - this is it.  If you have a giant JSON file all PRs will modify that file and merging could become really troublesome.  Instead, if you break out each comment into it’s own file, then each comment is independent and you can approve PRs in any order without conflicts.</p>

<p>It’s by no means a perfect solution, but it actually turns out to be really nice.  A commenter submits the form, you get it in PR form where you can modify it if needed, you can use the PR as a moderation avenue (be sure to manually delete the source branch).  When you approve the PR and it get’s merged, GitHub automatically regenerates your site and deploys it and the comment is now visible!  Cool!</p>

<blockquote>
  <p><strong>TIP</strong>: There’s a nice cost calculator for Azure you can use to project cost.  For something like dyn4j.org I calculated the cost at less than $1/month and probably $0 for most months.</p>
</blockquote>

<p>Here’s the function code, adapted from the link above for .NET Core 3.1:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="codehilite"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
</pre></td><td class="rouge-code"><pre><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">JekyllBlogCommentsAzureFunction</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="nc">MissingRequiredValue</span> <span class="p">{</span> <span class="p">}</span> <span class="c1">// Placeholder for missing required form values</span>
    <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">validPathChars</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">@"[^a-zA-Z0-9-]"</span><span class="p">);</span> <span class="c1">// Valid characters when mapping from the blog post slug to a file path</span>
    <span class="k">static</span> <span class="k">readonly</span> <span class="n">Regex</span> <span class="n">validEmail</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Regex</span><span class="p">(</span><span class="s">@"^[^@\s]+@[^@\s]+\.[^@\s]+$"</span><span class="p">);</span> <span class="c1">// Simplest form of email validation</span>

    <span class="p">[</span><span class="nf">FunctionName</span><span class="p">(</span><span class="s">"PostComment"</span><span class="p">)]</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">HttpResponseMessage</span><span class="p">&gt;</span> <span class="nf">Run</span><span class="p">([</span><span class="nf">HttpTrigger</span><span class="p">(</span><span class="n">AuthorizationLevel</span><span class="p">.</span><span class="n">Anonymous</span><span class="p">,</span> <span class="s">"post"</span><span class="p">)]</span> <span class="n">HttpRequestMessage</span> <span class="n">request</span><span class="p">,</span> <span class="n">ILogger</span> <span class="n">logger</span><span class="p">,</span> <span class="n">ExecutionContext</span> <span class="n">context</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">form</span> <span class="p">=</span> <span class="k">await</span> <span class="n">request</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">ReadAsFormDataAsync</span><span class="p">();</span>

        <span class="c1">// Make sure the site posting the comment is the correct site.</span>
        <span class="kt">var</span> <span class="n">commentSite</span> <span class="p">=</span> <span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"CommentWebsiteUrl"</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">commentSite</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">!</span><span class="nf">AreSameSites</span><span class="p">(</span><span class="n">commentSite</span><span class="p">,</span> <span class="n">form</span><span class="p">[</span><span class="s">"comment-site"</span><span class="p">]))</span>
        <span class="p">{</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">LogInformation</span><span class="p">(</span><span class="s">$"</span><span class="p">{</span><span class="n">commentSite</span><span class="p">}</span><span class="s"> and </span><span class="p">{</span><span class="n">form</span><span class="p">[</span><span class="s">"comment-site"</span><span class="p">]}</span><span class="s"> do not match"</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="nf">CreateErrorResponse</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">BadRequest</span><span class="p">,</span> <span class="s">"Please make sure you post this to your own Jekyll comments receiever."</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="nf">TryCreateCommentFromForm</span><span class="p">(</span><span class="n">form</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">comment</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">errors</span><span class="p">))</span>
            <span class="k">await</span> <span class="nf">CreateCommentAsPullRequest</span><span class="p">(</span><span class="n">comment</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">errors</span><span class="p">.</span><span class="nf">Any</span><span class="p">())</span>
            <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="nf">CreateErrorResponse</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">BadRequest</span><span class="p">,</span> <span class="n">String</span><span class="p">.</span><span class="nf">Join</span><span class="p">(</span><span class="s">"\n"</span><span class="p">,</span> <span class="n">errors</span><span class="p">));</span>

        <span class="k">if</span> <span class="p">(!</span><span class="n">Uri</span><span class="p">.</span><span class="nf">TryCreate</span><span class="p">(</span><span class="n">form</span><span class="p">[</span><span class="s">"redirect"</span><span class="p">],</span> <span class="n">UriKind</span><span class="p">.</span><span class="n">Absolute</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">redirectUri</span><span class="p">))</span>
            <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="nf">CreateResponse</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">OK</span><span class="p">);</span>

        <span class="kt">var</span> <span class="n">response</span> <span class="p">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">CreateResponse</span><span class="p">(</span><span class="n">HttpStatusCode</span><span class="p">.</span><span class="n">Redirect</span><span class="p">);</span>
        <span class="n">response</span><span class="p">.</span><span class="n">Headers</span><span class="p">.</span><span class="n">Location</span> <span class="p">=</span> <span class="n">redirectUri</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">AreSameSites</span><span class="p">(</span><span class="kt">string</span> <span class="n">commentSite</span><span class="p">,</span> <span class="kt">string</span> <span class="n">postedCommentSite</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">Uri</span><span class="p">.</span><span class="nf">TryCreate</span><span class="p">(</span><span class="n">commentSite</span><span class="p">,</span> <span class="n">UriKind</span><span class="p">.</span><span class="n">Absolute</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">commentSiteUri</span><span class="p">)</span>
            <span class="p">&amp;&amp;</span> <span class="n">Uri</span><span class="p">.</span><span class="nf">TryCreate</span><span class="p">(</span><span class="n">postedCommentSite</span><span class="p">,</span> <span class="n">UriKind</span><span class="p">.</span><span class="n">Absolute</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">postedCommentSiteUri</span><span class="p">)</span>
            <span class="p">&amp;&amp;</span> <span class="n">commentSiteUri</span><span class="p">.</span><span class="n">Host</span><span class="p">.</span><span class="nf">Equals</span><span class="p">(</span><span class="n">postedCommentSiteUri</span><span class="p">.</span><span class="n">Host</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">OrdinalIgnoreCase</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">PullRequest</span><span class="p">&gt;</span> <span class="nf">CreateCommentAsPullRequest</span><span class="p">(</span><span class="n">Comment</span> <span class="n">comment</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// Create the Octokit client</span>
        <span class="kt">var</span> <span class="n">github</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">GitHubClient</span><span class="p">(</span><span class="k">new</span> <span class="nf">ProductHeaderValue</span><span class="p">(</span><span class="s">"PostCommentToPullRequest"</span><span class="p">),</span>
            <span class="k">new</span> <span class="n">Octokit</span><span class="p">.</span><span class="n">Internal</span><span class="p">.</span><span class="nf">InMemoryCredentialStore</span><span class="p">(</span><span class="k">new</span> <span class="nf">Credentials</span><span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"GitHubToken"</span><span class="p">))));</span>

        <span class="c1">// Get a reference to our GitHub repository</span>
        <span class="kt">var</span> <span class="n">repoOwnerName</span> <span class="p">=</span> <span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"PullRequestRepository"</span><span class="p">).</span><span class="nf">Split</span><span class="p">(</span><span class="sc">'/'</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">repo</span> <span class="p">=</span> <span class="k">await</span> <span class="n">github</span><span class="p">.</span><span class="n">Repository</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="n">repoOwnerName</span><span class="p">[</span><span class="m">0</span><span class="p">],</span> <span class="n">repoOwnerName</span><span class="p">[</span><span class="m">1</span><span class="p">]);</span>

        <span class="c1">// Create a new branch from the default branch</span>
        <span class="kt">var</span> <span class="n">defaultBranch</span> <span class="p">=</span> <span class="k">await</span> <span class="n">github</span><span class="p">.</span><span class="n">Repository</span><span class="p">.</span><span class="n">Branch</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="n">repo</span><span class="p">.</span><span class="n">DefaultBranch</span><span class="p">);</span>
        <span class="kt">var</span> <span class="n">newBranch</span> <span class="p">=</span> <span class="k">await</span> <span class="n">github</span><span class="p">.</span><span class="n">Git</span><span class="p">.</span><span class="n">Reference</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="k">new</span> <span class="nf">NewReference</span><span class="p">(</span><span class="s">$"refs/heads/comment-</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="n">defaultBranch</span><span class="p">.</span><span class="n">Commit</span><span class="p">.</span><span class="n">Sha</span><span class="p">));</span>

        <span class="c1">// Create a new file with the comments in it</span>
        <span class="kt">var</span> <span class="n">fileRequest</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">CreateFileRequest</span><span class="p">(</span><span class="s">$"Comment by </span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">author</span><span class="p">}</span><span class="s"> on </span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">post_id</span><span class="p">}</span><span class="s">"</span><span class="p">,</span> <span class="k">new</span> <span class="nf">SerializerBuilder</span><span class="p">().</span><span class="nf">Build</span><span class="p">().</span><span class="nf">Serialize</span><span class="p">(</span><span class="n">comment</span><span class="p">),</span> <span class="n">newBranch</span><span class="p">.</span><span class="n">Ref</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Committer</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Committer</span><span class="p">(</span><span class="n">comment</span><span class="p">.</span><span class="n">author</span><span class="p">,</span> <span class="n">comment</span><span class="p">.</span><span class="n">email</span> <span class="p">??</span> <span class="n">Environment</span><span class="p">.</span><span class="nf">GetEnvironmentVariable</span><span class="p">(</span><span class="s">"CommentFallbackCommitEmail"</span><span class="p">)</span> <span class="p">??</span> <span class="s">"redacted@example.com"</span><span class="p">,</span> <span class="n">comment</span><span class="p">.</span><span class="n">date</span><span class="p">)</span>
        <span class="p">};</span>
        <span class="k">await</span> <span class="n">github</span><span class="p">.</span><span class="n">Repository</span><span class="p">.</span><span class="n">Content</span><span class="p">.</span><span class="nf">CreateFile</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="s">$"_data/comments/</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">post_id</span><span class="p">}</span><span class="s">/</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">id</span><span class="p">}</span><span class="s">.yml"</span><span class="p">,</span> <span class="n">fileRequest</span><span class="p">);</span>

        <span class="c1">// Create a pull request for the new branch and file</span>
        <span class="k">return</span> <span class="k">await</span> <span class="n">github</span><span class="p">.</span><span class="n">Repository</span><span class="p">.</span><span class="n">PullRequest</span><span class="p">.</span><span class="nf">Create</span><span class="p">(</span><span class="n">repo</span><span class="p">.</span><span class="n">Id</span><span class="p">,</span> <span class="k">new</span> <span class="nf">NewPullRequest</span><span class="p">(</span><span class="n">fileRequest</span><span class="p">.</span><span class="n">Message</span><span class="p">,</span> <span class="n">newBranch</span><span class="p">.</span><span class="n">Ref</span><span class="p">,</span> <span class="n">defaultBranch</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">Body</span> <span class="p">=</span> <span class="s">$"avatar: &lt;img src=\"</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">avatar</span><span class="p">}</span><span class="s">\" width=\"64\" height=\"64\" /&gt;\n\n</span><span class="p">{</span><span class="n">comment</span><span class="p">.</span><span class="n">message</span><span class="p">}</span><span class="s">"</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">static</span> <span class="kt">object</span> <span class="nf">ConvertParameter</span><span class="p">(</span><span class="kt">string</span> <span class="n">parameter</span><span class="p">,</span> <span class="n">Type</span> <span class="n">targetType</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="n">String</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">parameter</span><span class="p">)</span>
            <span class="p">?</span> <span class="k">null</span>
            <span class="p">:</span> <span class="n">TypeDescriptor</span><span class="p">.</span><span class="nf">GetConverter</span><span class="p">(</span><span class="n">targetType</span><span class="p">).</span><span class="nf">ConvertFrom</span><span class="p">(</span><span class="n">parameter</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Try to create a Comment from the form.  Each Comment constructor argument will be name-matched</span>
    <span class="c1">/// against values in the form. Each non-optional arguments (those that don't have a default value)</span>
    <span class="c1">/// not supplied will cause an error in the list of errors and prevent the Comment from being created.</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="c1">/// &lt;param name="form"&gt;Incoming form submission as a &lt;see cref="NameValueCollection"/&gt;.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="comment"&gt;Created &lt;see cref="Comment"/&gt; if no errors occurred.&lt;/param&gt;</span>
    <span class="c1">/// &lt;param name="errors"&gt;A list containing any potential validation errors.&lt;/param&gt;</span>
    <span class="c1">/// &lt;returns&gt;True if the Comment was able to be created, false if validation errors occurred.&lt;/returns&gt;</span>
    <span class="k">private</span> <span class="k">static</span> <span class="kt">bool</span> <span class="nf">TryCreateCommentFromForm</span><span class="p">(</span><span class="n">NameValueCollection</span> <span class="n">form</span><span class="p">,</span> <span class="k">out</span> <span class="n">Comment</span> <span class="n">comment</span><span class="p">,</span> <span class="k">out</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span> <span class="n">errors</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">var</span> <span class="n">constructor</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">Comment</span><span class="p">).</span><span class="nf">GetConstructors</span><span class="p">()[</span><span class="m">0</span><span class="p">];</span>
        <span class="kt">var</span> <span class="n">values</span> <span class="p">=</span> <span class="n">constructor</span><span class="p">.</span><span class="nf">GetParameters</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">ToDictionary</span><span class="p">(</span>
                <span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span>
                <span class="n">p</span> <span class="p">=&gt;</span> <span class="nf">ConvertParameter</span><span class="p">(</span><span class="n">form</span><span class="p">[</span><span class="n">p</span><span class="p">.</span><span class="n">Name</span><span class="p">],</span> <span class="n">p</span><span class="p">.</span><span class="n">ParameterType</span><span class="p">)</span> <span class="p">??</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">HasDefaultValue</span> <span class="p">?</span> <span class="n">p</span><span class="p">.</span><span class="n">DefaultValue</span> <span class="p">:</span> <span class="k">new</span> <span class="nf">MissingRequiredValue</span><span class="p">())</span>
            <span class="p">);</span>

        <span class="n">errors</span> <span class="p">=</span> <span class="n">values</span><span class="p">.</span><span class="nf">Where</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="n">p</span><span class="p">.</span><span class="n">Value</span> <span class="k">is</span> <span class="n">MissingRequiredValue</span><span class="p">).</span><span class="nf">Select</span><span class="p">(</span><span class="n">p</span> <span class="p">=&gt;</span> <span class="s">$"Form value missing for </span><span class="p">{</span><span class="n">p</span><span class="p">.</span><span class="n">Key</span><span class="p">}</span><span class="s">"</span><span class="p">).</span><span class="nf">ToList</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">values</span><span class="p">[</span><span class="s">"email"</span><span class="p">]</span> <span class="k">is</span> <span class="kt">string</span> <span class="n">s</span> <span class="p">&amp;&amp;</span> <span class="p">!</span><span class="n">validEmail</span><span class="p">.</span><span class="nf">IsMatch</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>
            <span class="n">errors</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="s">"email not in correct format"</span><span class="p">);</span>

        <span class="n">comment</span> <span class="p">=</span> <span class="n">errors</span><span class="p">.</span><span class="nf">Any</span><span class="p">()</span> <span class="p">?</span> <span class="k">null</span> <span class="p">:</span> <span class="p">(</span><span class="n">Comment</span><span class="p">)</span><span class="n">constructor</span><span class="p">.</span><span class="nf">Invoke</span><span class="p">(</span><span class="n">values</span><span class="p">.</span><span class="n">Values</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">());</span>
        <span class="k">return</span> <span class="p">!</span><span class="n">errors</span><span class="p">.</span><span class="nf">Any</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">/// &lt;summary&gt;</span>
    <span class="c1">/// Represents a Comment to be written to the repository in YML format.</span>
    <span class="c1">/// &lt;/summary&gt;</span>
    <span class="k">private</span> <span class="k">class</span> <span class="nc">Comment</span>
    <span class="p">{</span>
        <span class="k">public</span> <span class="nf">Comment</span><span class="p">(</span><span class="kt">string</span> <span class="n">post_id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">message</span><span class="p">,</span> <span class="kt">string</span> <span class="n">author</span><span class="p">,</span> <span class="kt">string</span> <span class="n">email</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span> <span class="n">Uri</span> <span class="n">url</span> <span class="p">=</span> <span class="k">null</span><span class="p">,</span> <span class="kt">string</span> <span class="n">avatar</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="n">post_id</span> <span class="p">=</span> <span class="n">validPathChars</span><span class="p">.</span><span class="nf">Replace</span><span class="p">(</span><span class="n">post_id</span><span class="p">,</span> <span class="s">"-"</span><span class="p">);</span>
            <span class="k">this</span><span class="p">.</span><span class="n">message</span> <span class="p">=</span> <span class="n">message</span><span class="p">;</span>
            <span class="k">this</span><span class="p">.</span><span class="n">author</span> <span class="p">=</span> <span class="n">author</span><span class="p">;</span>
            <span class="k">this</span><span class="p">.</span><span class="n">email</span> <span class="p">=</span> <span class="n">email</span><span class="p">;</span>
            <span class="k">this</span><span class="p">.</span><span class="n">url</span> <span class="p">=</span> <span class="n">url</span><span class="p">;</span>

            <span class="n">date</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">UtcNow</span><span class="p">;</span>
            <span class="n">id</span> <span class="p">=</span> <span class="k">new</span> <span class="p">{</span> <span class="k">this</span><span class="p">.</span><span class="n">post_id</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">author</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">message</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="n">date</span> <span class="p">}.</span><span class="nf">GetHashCode</span><span class="p">().</span><span class="nf">ToString</span><span class="p">(</span><span class="s">"x8"</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">Uri</span><span class="p">.</span><span class="nf">TryCreate</span><span class="p">(</span><span class="n">avatar</span><span class="p">,</span> <span class="n">UriKind</span><span class="p">.</span><span class="n">Absolute</span><span class="p">,</span> <span class="k">out</span> <span class="n">Uri</span> <span class="n">avatarUrl</span><span class="p">))</span>
                <span class="k">this</span><span class="p">.</span><span class="n">avatar</span> <span class="p">=</span> <span class="n">avatarUrl</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="p">[</span><span class="n">YamlIgnore</span><span class="p">]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">post_id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

        <span class="k">public</span> <span class="kt">string</span> <span class="n">id</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
        <span class="k">public</span> <span class="n">DateTime</span> <span class="n">date</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">author</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
        <span class="p">[</span><span class="n">YamlIgnore</span><span class="p">]</span>
        <span class="k">public</span> <span class="kt">string</span> <span class="n">email</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

        <span class="p">[</span><span class="nf">YamlMember</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">))]</span>
        <span class="k">public</span> <span class="n">Uri</span> <span class="n">avatar</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

        <span class="p">[</span><span class="nf">YamlMember</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="kt">string</span><span class="p">))]</span>
        <span class="k">public</span> <span class="n">Uri</span> <span class="n">url</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>

        <span class="k">public</span> <span class="kt">string</span> <span class="n">message</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now, you do lose spam filtering, but like the source link mentioned, a simple “Are you sure?” dialog seems to do the trick and like previously mentioned you have the moderation via PRs anyway.  I really wanted to put in place some networking restrictions on the Azure side, but you can’t.  The comment form is submitted from the client and it goes directly to Azure.</p>

<p>Another cool thing I saw being done on the comment form is the automatic generation of avatar URLs for GitHub, Gravitar, and Twitter.  I’m not sure what their solutions were, but mine was just a <a href="https://github.com/dyn4j/dyn4j.github.io/blob/main/js/comment.js">simple (vanilla) JavaScript file</a> that takes the username field and generates an avatar URL based on the format.  I’d forgotten how annoying it is to write vanilla JavaScript…</p>

<h2 id="summary" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#summary">
          #
        </a>
    </span>
    Summary
</h2>
<p>While the conversion took more effort than I was expecting, I’ve very happy with the results.  It actually forced me to go back and revise pages and posts that were in need of some updates.  It also allowes me to maintain the site entirely out of GitHub and drop the hosting I was paying for as well.  Other benefits include speed and full control of the UI.  All this while retaining the capability to blog and receive comments.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Blog" /><category term="dyn4j" /><summary type="html"><![CDATA[In my last post where I talked about the reasons for moving the dyn4j site to GitHub Pages and off of WordPress, I mentioned another post to describe the technical details of the move - this is it! Seriously though, it wasn’t trivial, but that’s in part due to some self-imposed (inflicted?) constraints.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/posts/2021-04-30-wordpress-to-github-pages/jekyll.png" /><media:content medium="image" url="https://dyn4j.org/assets/posts/2021-04-30-wordpress-to-github-pages/jekyll.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">dyn4j.org Moved to GitHub Pages</title><link href="https://dyn4j.org/2021/04/dyn4j-org-moved-to-github-pages/" rel="alternate" type="text/html" title="dyn4j.org Moved to GitHub Pages" /><published>2021-04-26T01:36:59-04:00</published><updated>2021-04-26T01:36:59-04:00</updated><id>https://dyn4j.org/2021/04/dyn4j-org-moved-to-github-pages</id><content type="html" xml:base="https://dyn4j.org/2021/04/dyn4j-org-moved-to-github-pages/"><![CDATA[<p>After a long stint on <a href="https://wordpress.org/">WordPress</a> I finally was feed up - not with WordPress so much as I explain below.  WordPress has been a great solution for the time I’ve been using it.  It made things really easy to setup and manage and it’s improved significantly over the years.  However, there’s always been a few thorns in my side with this solution.</p>

<p>To be clear, with dyn4j.org I was looking for both a nice project website to highlight the maturity that I strive for in library and to serve as a blogging platform for some of the difficult features of the library.</p>

<figure class="figure">
  <img src="/assets/posts/2021-04-26-dyn4j-org-moved-to-github-pages/octojekyll.png" class="figure-img img-fluid rounded" alt="GitHub + Jekyll" />
  <figcaption class="figure-caption text-center">
    GitHub + Jekyll 
    
    <small><a href="https://jekyllrb.com/">Credit</a></small>
    
  </figcaption>
</figure>

<h2 id="thorn-1-hosting" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-1-hosting">
          #
        </a>
    </span>
    Thorn #1: Hosting
</h2>
<p>Hosting of WordPress should be easy and straight forward.  When I first took the deep dive into the platform, it was.  My host has nice GUI tools to install it and get a domain pointed to it - great!  Overtime, however, is where things went down hill.</p>

<p>The real nail in the coffin for this move, was more a <em>host</em> problem than a <em>WordPress</em> problem.  They make no attempts to keep you off of aging infrastructure and then charge you to move to equivalent, better supported infrastructure.  In my particular case, I’d just renewed for a few years when I realized that my hosting was on some ancient infrastructure that was stuck on PHP 5.x or something and that I’d have to renew <em>again</em> to move.  What?!  I couldn’t believe it.  The whole reason I found out about this was because WordPress was asking me to upgrade, but that upgrade required a newer version of PHP.</p>

<p>Another thing to mention is the ubiquity of more robust solutions and hosting (read: cloud) out there these days.  For example, in my day job I frequently use the Microsoft stack, deploying to <a href="https://azure.microsoft.com/en-us/">Azure</a> - their tools are really nice to work with.  Want to move to .NET Core 3.1?  No problem, create a new App Service Plan and move everything over - no need to deal with support.</p>

<h2 id="thorn-2-upgrades" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-2-upgrades">
          #
        </a>
    </span>
    Thorn #2: Upgrades
</h2>
<p>It’s probably already clear by now, but upgrades were also a nusiance.  Tons of <a href="https://wordpress.org/support/article/upgrading-wordpress-extended-instructions/">warnings</a> about “Take a backup because everything could be broken after this” never gave me much hope, but generally speaking it worked well.  The issue is that WordPress has moved on past the PHP version supported the infrastructure I was on.  Sticking around on this old infrastructure and old PHP version just gave me the chills.</p>

<h2 id="thorn-3-security" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-3-security">
          #
        </a>
    </span>
    Thorn #3: Security
</h2>
<p>One really strange issue that has occurred, on a number of occasions, has been where someone modified (still not sure how) my <a href="https://www.wordfence.com/learn/removing-malicious-redirects-site/">.htaccess files and redirected incoming traffic to <em>lovely</em> pharmaceutical sites</a>.  As a maintainer, you’d never notice unless you google-searched a page for the site and tried to click the google result - it only redirected in those scenarios.  Needless to say, that was very frustrating and I spent A LOT of time hardening my security setup (changed passwords, added MFA, changed FTP usernames and passwords, etc.).  This isn’t how I want to be spending my evenings.</p>

<h2 id="thorn-4-security" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-4-security">
          #
        </a>
    </span>
    Thorn #4: Security
</h2>
<p>Yet another cost if you host yourself is SSL.  It’s not like dyn4j.org needs it for anything (no there aren’t any custom made t-shirts or hats for you to buy), but there’s been a lot of talk from browsers about showing nasty messages when you navigate to a site without HTTPS.  Paying for this just so a browser doesn’t tag my site as a scam, just seemed like an internet tax.</p>

<h2 id="thorn-5-customizability" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-5-customizability">
          #
        </a>
    </span>
    Thorn #5: Customizability
</h2>
<p>WordPress is super customizable, has tons of OOB themes, free themes, paid themes, plugins, etc. It really is a nice ecosystem.  But this strength is also a great weakness - I really don’t want to spend my precious free time trying to understand the internals of WordPress just so I can customize a page or two.  This made it feel very rigid - you either stuck with the plugins/themes you have installed or you learn WordPress development - and I feel like this made it really stagnant.</p>

<h2 id="thorn-6-performance" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#thorn-6-performance">
          #
        </a>
    </span>
    Thorn #6: Performance
</h2>
<p>This is more a knock on my hosting provider - it just always felt slow for what was really a static site with a few infrequent blog posts.  I was on the basic of basic plans to reduce cost, but even then there really isn’t that much going on with this site to warrant the lack of performance.</p>

<h2 id="replacement-decision" class="linked-heading">
    <span class="heading-anchor-wrapper">
        <a class="heading-anchor" aria-hidden="true" href="#replacement-decision">
          #
        </a>
    </span>
    Replacement Decision
</h2>
<p>I just kept asking myself, “Why do I keep paying for (putting up with) this…?”  The only answer I could think of was a lack of time to make the move.  I finally decided, after my hosting provider told me, “You need to pay up if you want new infrastructure” that now was the time to move - even if I my sleep would suffer.</p>

<p>Research initially focused on a replacement host, but the thought of doing the same thing, just on a different host, made me start searching for alternatives.  It was then that I discovered GitHub Pages, albeit <a href="https://www.smashingmagazine.com/2014/08/build-blog-jekyll-github-pages/">much later</a> than the rest of the internet it seems…</p>

<p>GitHub Pages offers a really neat way of hosting a site.  You build your site using a public repo and GitHub will use whatever branch you choose to serve it up.  There are two primary offerings, a static site built however you want, and a <a href="https://jekyllrb.com/">Jekyll</a> based solution.</p>

<p>The key concern, in either offering, was that the site is effectively static - i.e. no user interaction or other server components would be possible.  Thinking about the dyn4j.org use-case though, the only user-interaction “needed” (though <a href="https://github.com/dyn4j/dyn4j/discussions">GitHub Discussions</a> might be an interesting alternative) was post commenting.  At first, I considered whether I needed commenting at all, but as a I went through the exercise of designing the new site, I found a really cool <a href="https://haacked.com/archive/2018/06/24/comments-for-jekyll-blogs/">work around</a>.  At this point I was pretty much sold, GitHub Pages would allow me to do what I’m already doing but would solve all of my thorns:</p>

<ul>
  <li><strong>Hosting</strong> - GitHub is the host and it’s free!  Give some money to a charity or your choosing!</li>
  <li><strong>Upgrades</strong> - You can build whatever you want and it’s all static - you only need to support browser capabilities which have an incredible history of backwards compatibility.</li>
  <li><strong>Security</strong> - GitHub doesn’t host anything, they only host the repo’s statically generated content.</li>
  <li><strong>SSL</strong> - GitHub will issue a cert for you - unbelievable…</li>
  <li><strong>Customizability</strong> - You can build a site with HTML, CSS, and JavaScript or go down the Jekyll path.  If you’ve done web development in your career it’ll be nothing.  Jekyll did have a bit of a learning curve, but nothing like WordPress.</li>
  <li><strong>Performance</strong> - GitHub only publishes a static site and it is lightning fast.  I can’t even tell you how much more professional a site looks when it’s fast.</li>
</ul>

<blockquote>
  <p>There is one caveat here with Upgrades when using Jekyll.  While GitHub pages supports Jekyll natively, it only supports a limited subset of plugins and features.  At the time of this writing I installed the latest version of the tooling and this worked fine, but only time will tell.  You can avoid this risk, compromising some maintainability, by building a static site with just HTML, CSS, and JavaScript.</p>
</blockquote>

<p>You can see the end result at <a href="https://dyn4j.org/">dyn4j.org</a> and it’s <a href="https://github.com/dyn4j/dyn4j.github.io">accompanying repo</a>.  I’ll do another post on the technical elements of the move and the various issues I ran into.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Blog" /><category term="dyn4j" /><summary type="html"><![CDATA[After a long stint on WordPress I finally was feed up - not with WordPress so much as I explain below. WordPress has been a great solution for the time I’ve been using it. It made things really easy to setup and manage and it’s improved significantly over the years. However, there’s always been a few thorns in my side with this solution.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/posts/2021-04-26-dyn4j-org-moved-to-github-pages/octojekyll.png" /><media:content medium="image" url="https://dyn4j.org/assets/posts/2021-04-26-dyn4j-org-moved-to-github-pages/octojekyll.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Version 4.1.0</title><link href="https://dyn4j.org/2021/02/version-4-1-0/" rel="alternate" type="text/html" title="Version 4.1.0" /><published>2021-02-04T00:36:59-05:00</published><updated>2021-02-04T00:36:59-05:00</updated><id>https://dyn4j.org/2021/02/version-4-1-0</id><content type="html" xml:base="https://dyn4j.org/2021/02/version-4-1-0/"><![CDATA[<p>This release sees some huge performance improvements for CCD (around 10x for large worlds). Along with the performance enhancements came improvements to CCD as a whole, a more generic broadphase, lots of additional unit tests and greatly improved test quality.</p>

<p>That said, it definitely has some breaking changes. All deprecated API has been removed as well.</p>

<p>See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for all the detail.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This release sees some huge performance improvements for CCD (around 10x for large worlds). Along with the performance enhancements came improvements to CCD as a whole, a more generic broadphase, lots of additional unit tests and greatly improved test quality.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/gage.jpg" /><media:content medium="image" url="https://dyn4j.org/assets/gage.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Version 4.0.2</title><link href="https://dyn4j.org/2020/10/version-4-0-2/" rel="alternate" type="text/html" title="Version 4.0.2" /><published>2020-10-15T01:36:24-04:00</published><updated>2020-10-15T01:36:24-04:00</updated><id>https://dyn4j.org/2020/10/version-4-0-2</id><content type="html" xml:base="https://dyn4j.org/2020/10/version-4-0-2/"><![CDATA[<p>This version includes a few minor bug fixes for 4.0.1.  See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for more details.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This version includes a few minor bug fixes for 4.0.1.  See the release notes for more details.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/gears.jpg" /><media:content medium="image" url="https://dyn4j.org/assets/gears.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Version 4.0.1</title><link href="https://dyn4j.org/2020/09/version-4-0-1/" rel="alternate" type="text/html" title="Version 4.0.1" /><published>2020-09-25T01:46:47-04:00</published><updated>2020-09-25T01:46:47-04:00</updated><id>https://dyn4j.org/2020/09/version-4-0-1</id><content type="html" xml:base="https://dyn4j.org/2020/09/version-4-0-1/"><![CDATA[<p>This version includes a few minor bug fixes for 4.0.0.  See the <a onclick="javascript:pageTracker._trackPageview('/outgoing/github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md');" href="https://github.com/dyn4j/dyn4j/blob/master/RELEASE-NOTES.md">release notes</a> for more details.</p>]]></content><author><name>William Bittle</name></author><category term="News" /><category term="Release" /><category term="dyn4j" /><summary type="html"><![CDATA[This version includes a few minor bug fixes for 4.0.0.  See the release notes for more details.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://dyn4j.org/assets/gears.jpg" /><media:content medium="image" url="https://dyn4j.org/assets/gears.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>