<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
        <title>SecondSystem</title>
        <description>SecondSystem - Florian Oetke</description>
        <link>https://second-system.de</link>
        <atom:link href="https://second-system.de/rss.xml" rel="self" type="application/rss+xml" />
        <lastBuildDate>Thu, 13 Oct 2022 12:45:24 +0000</lastBuildDate>
        <pubDate>Thu, 13 Oct 2022 12:45:24 +0000</pubDate>
        <ttl>60</ttl>


        <item>
                <title>Plate Tectonics 3</title>
                <description>&lt;p&gt;Now that we have a way to generate some (albeit a bit dull) tectonic plates, we can use an iterative algorithm that simulates plate movements and interactions, to refine it into (hopefully) realistic looking landforms.&lt;/p&gt;

&lt;p&gt;Because the simulation itself is relatively complex&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;As can be deduced from the fact that it has taken me (another) eternity to finally write it down. &quot; data-title=&quot;As can be deduced from the fact that it has taken me (another) eternity to finally write it down. &quot;&gt;[1]&lt;/sup&gt;, I’ve split it into three separate posts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Moving the plates and determining how they should interact with each other (this post)&lt;/li&gt;
  &lt;li&gt;Updating the mesh after vertices have been moved, which includes the creation and destruction of crust material on plate boundaries, as well as detecting and handling collisions between plates&lt;/li&gt;
  &lt;li&gt;Simulating large-scale plate interactions like rifting and suturing&lt;/li&gt;
&lt;/ul&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;plate-motion&quot;&gt;Plate Motion&lt;/h2&gt;

&lt;p&gt;The first aspect we will take a closer look at, is how moving the plates on the surface is implemented – which are steps 2, 4 and 5 of the algorithm outline I’ve given &lt;a href=&quot;/2022/03/01/tectonics_1#algorithm&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To recap, contrary to the work of Cortial et al., we model our plates as a collection of interconnected sub-plates, each defining an area of similar composition and properties around it. To simulate the movement of these sub-plates, each of them is treated like an infinitesimal particle (represented by the mesh vertices), that interacts with each sub-plate around it by applying forces to them, depending on their relationship&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Are they part of the same plate or do they form a plate-boundary? Which type of boundary? Which of the two plates is subducting? ... &quot; data-title=&quot;Are they part of the same plate or do they form a plate-boundary? Which type of boundary? Which of the two plates is subducting? ... &quot;&gt;[2]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;This model allows us not only to represent heterogeneous plates, that are composed of many different types of crust, but also simplifies our simulation algorithm because (at least in this step) we only need to check the surrounding vertices to calculate the forces that affect each sub-plates movements.&lt;/p&gt;

&lt;p&gt;Our goal for this part of the algorithm is to simulate the motion of each of these sub-plates/vertices/particles. Since each sub-plate is modelled as a particle, calculating their motion consists of two main steps: First determine the forces that act on each particle, and second update their acceleration, velocity and position based on these forces.&lt;/p&gt;

&lt;h3 id=&quot;step-2-identify-boundary-types&quot;&gt;Step 2: Identify boundary-types&lt;/h3&gt;

&lt;p&gt;The forces that each sub-plate applies to its direct neighbors depend on their distance to each other and how they relate to each other. The latter of which is based on the five boundary types, we’ve discussed earlier:&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_divergent.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_divergent.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Divergent boundary&lt;/b&gt;: Two plates moving away from each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_constructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_co.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_co.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (CO)&lt;/b&gt;: An oceanic and a continental plate colliding with each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Oceanic-continental_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_oo.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_oo.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (OO)&lt;/b&gt;: Two oceanic plates colliding with each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Oceanic-oceanic_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_transform.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_transform.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Transform boundary&lt;/b&gt;: Two plates moving past each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_conservative_plate_boundary_opposite_directions.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_cc.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_cc.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (CC)&lt;/b&gt;: Two continental plates colliding with each other &lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;Based on these types, we define the following enum, which we’ll use to annotate each edge with the necessary information:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class Boundary_type : int8_t {
	joined,            // both vertices belong to the same plate
	ridge              // divergent boundary
	subducting_origin, // the origin vertex is subducted under the dest vertex
	subducting_dest,   // the dest vertex is subducted under the origin vertex
	transform,         // transform boundary
	collision,         // CC convergent boundary
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As we can see, there are two minor differences to the boundary types used by the theoretical model. The first is, that we need an additional type &lt;code&gt;joined&lt;/code&gt;, for edges that connect to vertices that are part of the same plate. And the second is that convergent boundaries are labeled a bit differently. As we’ll see later, CO ad OO boundaries are handled by the same code path, so we don’t have to differentiate them here. But we still need two values for these types of boundaries because we have to encode which of the two vertices is subducted. We could encode this by storing the &lt;code&gt;Boundary_type&lt;/code&gt; for both directions of the edge. But since the other boundary types don’t care about the direction, we can instead utilize that every directed edge has fixed a “preferred direction”, which is the first edge of its corresponding quad-edge, which means that &lt;code&gt;e.origin()&lt;/code&gt; and &lt;code&gt;e.dest()&lt;/code&gt; are well-defined for every undirected &lt;code&gt;Edge e&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What this comes down to is, that we’ve to iterate over each edge&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;both primal and dual &quot; data-title=&quot;both primal and dual &quot;&gt;[3]&lt;/sup&gt; in our mesh and assign them a &lt;code&gt;Boundary_type&lt;/code&gt; based on the properties of the two vertices that they connect.&lt;/p&gt;

&lt;p&gt;This step follows a couple of simple rules and mostly depends on the distance between the vertices &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;d&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.69444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and their converging-velocity &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.43056em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, that is the component of their combined velocities that moves them towards each other (or apart for negative values):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Initially, all edges start as &lt;code&gt;transform&lt;/code&gt; boundaries&lt;/li&gt;
  &lt;li&gt;If the two vertices reference the same plate id, they are set to &lt;code&gt;joined&lt;/code&gt; and if that ever changes or the edge is invalidated it’s transitioned back to &lt;code&gt;transform&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;If a &lt;code&gt;transform&lt;/code&gt; boundary is separating (&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;&amp;lt;&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;0.05&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v &amp;lt;= -0.05&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.5782em;vertical-align:-0.0391em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.72777em;vertical-align:-0.08333em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;0.05&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1500&lt;/mn&gt;&lt;mi&gt;k&lt;/mi&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;d&amp;gt;=1500km&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.73354em;vertical-align:-0.0391em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.69444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;km&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;) the edge becomes a &lt;code&gt;ridge&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;If a &lt;code&gt;transform&lt;/code&gt; boundary is converging (&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;&amp;gt;&lt;/mo&gt;&lt;mn&gt;0.1&lt;/mn&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v &amp;gt; 0.1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.5782em;vertical-align:-0.0391em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.64444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;0.1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;), we need to determine which of the two sub-plates would subduct&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This is usually the denser of the two plates. So, the oceanic plate of OC boundaries and the older of the two for OO boundaries. The exception to this are collisions between two continental sub-plates, where none of the two is dense enough to subduct, and they will collide instead &quot; data-title=&quot;This is usually the denser of the two plates. So, the oceanic plate of OC boundaries and the older of the two for OO boundaries. The exception to this are collisions between two continental sub-plates, where none of the two is dense enough to subduct, and they will collide instead &quot;&gt;[4]&lt;/sup&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;code&gt;subducting_origin&lt;/code&gt; if &lt;code&gt;e.origin()&lt;/code&gt; would be subducted&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;subducting_dest&lt;/code&gt; if &lt;code&gt;e.dest()&lt;/code&gt; would be subducted&lt;/li&gt;
      &lt;li&gt;&lt;code&gt;collision&lt;/code&gt; if none of the two plates can be subducted because both are continental plates&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Finally, if &lt;code&gt;collision&lt;/code&gt;, &lt;code&gt;subducting_origin&lt;/code&gt; or &lt;code&gt;subducting_dest&lt;/code&gt; boundaries cease to converge or if &lt;code&gt;ridge&lt;/code&gt; boundaries start to converge again, they become a &lt;code&gt;transform&lt;/code&gt; boundary once again&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;step-4-calculate-all-forces-acting-on-the-sub-plates&quot;&gt;Step 4: Calculate all forces acting on the sub-plates&lt;/h3&gt;

&lt;p&gt;Now that we know the relationship between all connected vertices, we can calculate the forces that act on them.&lt;/p&gt;

&lt;p&gt;In our model, all forces are caused by edges. Hence, the &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mrow&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{force}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1.1718799999999998em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9774399999999999em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10764em;&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;orce&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.26344em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.2355em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; that acts on a given vertex, is just the sum of the forces caused by each of its connected edges. To calculate these, we iterate over all edges, calculate the force it applies to its &lt;code&gt;dest()&lt;/code&gt; and &lt;code&gt;origin()&lt;/code&gt; — based on its &lt;code&gt;Boundary_type&lt;/code&gt; — and keep a running total of all forces per vertex.&lt;/p&gt;

&lt;h4 id=&quot;boundary_typejoined&quot;&gt;Boundary_type::joined&lt;/h4&gt;
&lt;p&gt;Vertices with this boundary type are part of the same plate. Since plates should keep their overall shape across time steps but still be slightly deformable on collisions, they are approximated as soft bodies.&lt;/p&gt;

&lt;p&gt;To achieve this, we model all &lt;code&gt;Boundary_type::joined&lt;/code&gt; as damped springs, that apply a force to the two connected vertices, which aims to keep their distance constant. The combination of these damped springs on both primal and dual edges, combined with the restriction of vertices to a 2D surface and prevention of self-intersections and other artifacts, is sufficient to achieve the overall effect of a slightly deformable solid. Because the springs try to maintain their initial length, collisions behave elastic by default. But more forceful collisions will cause modifications in the mesh topology, which invalidates previously stored distance information, causing a more plastic collision response.&lt;/p&gt;

&lt;p&gt;To calculate the force, we utilize a standard damped spring equation&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The spring constants (k_compressed, k_stretched and damping), as well as other such constants in this post, are configurable and determine how &amp;quot;soft&amp;quot; the tectonic plates behave. &quot; data-title=&quot;The spring constants (k_compressed, k_stretched and damping), as well as other such constants in this post, are configurable and determine how &amp;quot;soft&amp;quot; the tectonic plates behave. &quot;&gt;[5]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto k_compressed = 1e-4f;
const auto k_stretched  = 5e-5f;
const auto damping      = 2e-6f;

const auto difference   = positions[dest] - positions[origin];
const auto distance     = length(difference);
const auto direction    = difference / distance;
const auto displacement = target_distances[e] - distance;

const auto relative_velocity = dot(velocities[origin] - velocities[dest], direction);

const auto k     = displacement &amp;gt; 0.f ? k_compressed : k_stretched;
const auto force = direction * (displacement * k + relative_velocity * damping);

forces[origin] -= force;
forces[dest]   += force;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;boundary_typeridge&quot;&gt;Boundary_type::ridge&lt;/h4&gt;
&lt;p&gt;Ridges are boundaries where two plats separate and mantel material wells upwards, forming new oceanic crust. This upwelling of material also pushes the two plates further apart, speeding up their separation.&lt;/p&gt;

&lt;p&gt;Not only is this one of the main forces that keeps the Wilson-Cycle of plate subduction going&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The minor one to be precise. The main forces (to my knowledge) being the Slab-Pull force at subduction boundaries, which we will look at next, and convection currents in the mantle.&quot; data-title=&quot;The minor one to be precise. The main forces (to my knowledge) being the Slab-Pull force at subduction boundaries, which we will look at next, and convection currents in the mantle.&quot;&gt;[6]&lt;/sup&gt;, but it also helps to stabilize the simulation, since it causes plates that started separating to keep doing so, instead of bouncing back or oscillate.&lt;/p&gt;

&lt;p&gt;This force is pretty easy to model, since we just need to apply a small constant force to both vertices, that pushes them apart:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto ridge_push_force = 2e-8f;

const auto origin_to_dest = normalized(positions[dest] - positions[origin]);

forces[origin] -= origin_to_dest * ridge_push_force;
forces[dest]   += origin_to_dest * ridge_push_force;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;boundary_typesubducting_origin&quot;&gt;Boundary_type::subducting_origin&lt;/h4&gt;
&lt;p&gt;The main force for the Wilson-Cycle in our simulation is the Slab-Pull force, that acts on subduction boundaries and pulls the subducting plate closer.&lt;/p&gt;

&lt;p&gt;When a piece of crust is pushed underneath another plate, it isn’t actually hot enough to melt. Instead, it undergoes a complex process of partial melting, which causes the volcanism we can observe at the surface and also increases its density. Driven by this increased density, it starts to sink into the mantle, pulling the rest of the plate it’s still connected to down with it.&lt;/p&gt;

&lt;p&gt;Like the ridge-push above, this is again just a constant force. But contrary to ridges it doesn’t act on both vertices but just on the one that is belongs to the subducting plate. Which is of course the reason why we’ve split this boundary into two distinct types. Hence, except for the fact to which vertex the force is added, the calculation is identical for both &lt;code&gt;Boundary_type::subducting_origin&lt;/code&gt; and &lt;code&gt;Boundary_type::subducting_dest&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto slab_pull_force = 5e-8f;

// Boundary_type::subducting_origin
forces[origin] += normalized(positions[dest] - positions[origin]) * slab_pull_force;

// Boundary_type::subducting_dest
forces[dest] += normalized(positions[origin] - positions[dest]) * slab_pull_force;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;boundary_typetransform-and-boundary_typecollision&quot;&gt;Boundary_type::transform and Boundary_type::collision&lt;/h4&gt;

&lt;p&gt;The final two boundary types we need to handle are quite similar and actually use the exact same code, since they both apply a force to prevent two plates from intersecting. The only difference is that &lt;code&gt;collision&lt;/code&gt; boundaries are converging much more forcefully, which might result in one plate suturing onto the other.&lt;/p&gt;

&lt;p&gt;Similar to the others, this boils down to a force applied in the direction of the boundary. Since the goal is to prevent the two plates from coming closer than they currently are, without affecting lateral motion or separation, the amount of force depends on the converging velocity between the two vertices. That is, the part of their velocity that acts to move them closer together, so exactly what we intend to negate.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto difference          = positions[dest] - positions[origin];
const auto distance            = length(difference);
const auto direction           = difference / distance;
const auto converging_velocity = dot(velocities[origin]-velocities[dest], direction);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If this converging velocity is greater than one, so if the two vertices are moving closer together, we need to apply a collision-response force.&lt;/p&gt;

&lt;p&gt;To fully negate that part of their velocity, we would have to apply a force of &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mfrac&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mrow&gt;&lt;mtext&gt;dt&lt;/mtext&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\frac{\vec{v}}{\text{dt} \cdot 2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1.2388em;vertical-align:-0.345em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.8937999999999999em;&quot;&gt;&lt;span style=&quot;top:-2.6550000000000002em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;dt&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.394em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord accent mtight&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-2.714em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.714em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal mtight&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-2.714em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.714em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.20772em;&quot;&gt;&lt;span class=&quot;overlay mtight&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.345em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; to both vertices. But since this force is applied to all vertices that theoretically could collide, a response that that would be too strict and prevent nearly all movement. Instead, we need to modulate the force depending on the current distance between the two vertices and the closest distance that we want to allow. This can be achieved using a simple quadratic falloff like this:&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msup&gt;&lt;mrow&gt;&lt;mo fence=&quot;true&quot;&gt;(&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mtext&gt;max&lt;/mtext&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;0&lt;/mn&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mtext&gt;distance&lt;/mtext&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mtext&gt; &lt;/mtext&gt;&lt;mtext&gt;min_distance&lt;/mtext&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mtext&gt;min_distance&lt;/mtext&gt;&lt;/mfrac&gt;&lt;mo fence=&quot;true&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;f(x) = \frac{1}{1 + \left (\frac{\text{max}(0,\:\text{distance}\: -\: \text{min\_distance})}{\text{min\_distance}}\right ) ^2}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10764em;&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:3.215468em;vertical-align:-1.894028em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.32144em;&quot;&gt;&lt;span style=&quot;top:-2.11em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.354008em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;minner&quot;&gt;&lt;span class=&quot;minner&quot;&gt;&lt;span class=&quot;mopen delimcenter&quot; style=&quot;top:0em;&quot;&gt;&lt;span class=&quot;delimsizing size2&quot;&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.0519999999999998em;&quot;&gt;&lt;span style=&quot;top:-2.6550000000000002em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;min_distance&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.527em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;max&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mopen mtight&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;mpunct mtight&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace mtight&quot; style=&quot;margin-right:0.26022222222222224em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;distance&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace mtight&quot; style=&quot;margin-right:0.26022222222222224em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace mtight&quot; style=&quot;margin-right:0.26022222222222224em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;min_distance&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose mtight&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.5619999999999999em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose delimcenter&quot; style=&quot;top:0em;&quot;&gt;&lt;span class=&quot;delimsizing size2&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.3540079999999999em;&quot;&gt;&lt;span style=&quot;top:-3.6029em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.584008em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.354008em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.031008em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.354008em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.894028em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/07/graph_collision_response.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/07/graph_collision_response.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Plot of the falloff equation with a minimum distance of 8km.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto collision_min_distance = 8'000.f; // meter

if(converging_velocity &amp;gt; 0.f) {
	const auto d = std::max(0.f, dist - collision_min_distance) / collision_min_distance;
	const auto alpha = std::clamp(1.f / (1.f + d * d), 0.f, 1.f);
	const auto force = direction * (alpha * converging_velocity / delta_time / 2.f);

	forces[origin] -= force;
	forces[dest]   += force;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;step-5-simulate-movement&quot;&gt;Step 5: Simulate movement&lt;/h3&gt;

&lt;p&gt;Now that we know what forces are acting on each sub-plate, we &lt;em&gt;just&lt;/em&gt; need to simulate their movement. The motion of each particle follows the well-known Newtonian equations of motion, which we will approximate numerically using Verlet integration because of a couple of key advantages:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Similar computational complexity to implicit/explicit Euler integration, but with much better numeric stability&lt;/li&gt;
  &lt;li&gt;Easy to implement, since we only need to keep track of the current and previous position&lt;/li&gt;
  &lt;li&gt;Constraints are trivially easy to implement, by just modifying the calculated new positions. Which is important for us because we have to constrain each vertex to keep it on the sphere’s surface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To update each vertex position &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{p}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.9084399999999999em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.15216em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; we use:&lt;/p&gt;

&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mrow&gt;&lt;mo fence=&quot;true&quot;&gt;∥&lt;/mo&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mtext&gt;dt&lt;/mtext&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mfrac&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mtext&gt;force&lt;/mtext&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mtext&gt;mass&lt;/mtext&gt;&lt;/mfrac&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msup&gt;&lt;mtext&gt;dt&lt;/mtext&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mtext&gt;drag&lt;/mtext&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo fence=&quot;true&quot;&gt;∥&lt;/mo&gt;&lt;/mrow&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mtext&gt;radius&lt;/mtext&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{p}_t = \left \| \vec{p}_{t-1} + (\vec{v}_{t-1}\cdot \text{dt} + \frac{\vec{\text{force}}}{\text{mass}}\cdot \text{dt}^2)\cdot (1-\text{drag} \cdot dt) \right \| \cdot \text{radius}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.9084399999999999em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.15216em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.2805559999999999em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:3.0120299999999998em;vertical-align:-1.250025em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;minner&quot;&gt;&lt;span class=&quot;mopen&quot;&gt;&lt;span class=&quot;delimsizing mult&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.7620049999999998em;&quot;&gt;&lt;span style=&quot;top:-2.5660049999999996em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;delimsizinginner delim-size1&quot;&gt;&lt;span&gt;∥&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.1640049999999995em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;height:1.8160299999999998em;width:0.556em;&quot;&gt;&lt;svg width='0.556em' height='1.8160299999999998em' style='width:0.556em' viewBox='0 0 556 1816' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M145 0 H188 V1816 H145z M145 0 H188 V1816 H145zM367 0 H410 V1816 H367z M367 0 H410 V1816 H367z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.972035em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;delimsizinginner delim-size1&quot;&gt;&lt;span&gt;∥&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.250025em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.15216em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.301108em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.208331em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.20772em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.301108em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.208331em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;dt&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.65444em;&quot;&gt;&lt;span style=&quot;top:-2.314em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;mass&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.677em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9774399999999999em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;force&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.26344em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.2355em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.686em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;dt&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.8984479999999999em;&quot;&gt;&lt;span style=&quot;top:-3.1473400000000002em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;drag&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;&lt;span class=&quot;delimsizing mult&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.7620049999999998em;&quot;&gt;&lt;span style=&quot;top:-2.5660049999999996em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;delimsizinginner delim-size1&quot;&gt;&lt;span&gt;∥&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.1640049999999995em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;height:1.8160299999999998em;width:0.556em;&quot;&gt;&lt;svg width='0.556em' height='1.8160299999999998em' style='width:0.556em' viewBox='0 0 556 1816' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M145 0 H188 V1816 H145z M145 0 H188 V1816 H145zM367 0 H410 V1816 H367z M367 0 H410 V1816 H367z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.972035em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8160299999999996em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;delimsizinginner delim-size1&quot;&gt;&lt;span&gt;∥&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.250025em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.69444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;radius&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

&lt;p&gt;Where &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;dt&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.69444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the delta time between the two steps, &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mi&gt;f&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mrow&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{force}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1.1718799999999998em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.9774399999999999em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10764em;&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;orce&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.26344em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.2355em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the sum of all forces acting on the sub-plate, &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;m&lt;/mi&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;mass&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.43056em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ma&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ss&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;g&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;drag&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.8888799999999999em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; are constants and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{v}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.714em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.20772em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the velocity, which is calculated after the positions are updated using:&lt;/p&gt;

&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msub&gt;&lt;mover accent=&quot;true&quot;&gt;&lt;mi&gt;p&lt;/mi&gt;&lt;mo&gt;⃗&lt;/mo&gt;&lt;/mover&gt;&lt;mrow&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;d&lt;/mi&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\vec{v}_t = \frac{\vec{p}_t-\vec{p}_{t-1}}{dt}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.864em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.20772em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.2805559999999999em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:2.077em;vertical-align:-0.686em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.391em;&quot;&gt;&lt;span style=&quot;top:-2.314em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.677em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.15216em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.2805559999999999em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord accent&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.714em;&quot;&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;p&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;accent-body&quot; style=&quot;left:-0.15216em;&quot;&gt;&lt;span class=&quot;overlay&quot; style=&quot;height:0.714em;width:0.471em;&quot;&gt;&lt;svg width='0.471em' height='0.714em' style='width:0.471em' viewBox='0 0 471 714' preserveAspectRatio='xMinYMin'&gt;&lt;path d='M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5
3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11
10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63
-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1
-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59
H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359
c-16-25.333-24-45-24-59z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.19444em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.301108em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.208331em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.686em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

&lt;p&gt;Of course, because each sub-plate moves independent of the others, there will be some problems like self-intersections, which we’ll need to clean up afterwards. But that is a topic for another blog post.&lt;/p&gt;

&lt;figure class=&quot;captioned_image &quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/07/plate_movement.mp4&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Tectonic plates moving, colliding and being subducted.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;elevation&quot;&gt;Elevation&lt;/h2&gt;

&lt;p&gt;Now that we have continental and oceanic plates moving about, we can also use that information to finally pile up some mountains and create some actual terrain.&lt;/p&gt;

&lt;h3 id=&quot;6-update-elevation&quot;&gt;6. Update elevation&lt;/h3&gt;
&lt;p&gt;Mountain formation in reality is a complex process, involving 3D interactions and folding between parts of the crust with varying compositions and densities. But in our model this is simplified quite a bit and only depends on the sub-plates velocities and their &lt;code&gt;Boundary_type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In fact, there are only two types of boundaries that are relevant for mountain formation:&lt;/p&gt;

&lt;h4 id=&quot;boundary_typecollision&quot;&gt;Boundary_type::collision&lt;/h4&gt;

&lt;p&gt;During collisions, the crust material between the two plates is pushed closer together. Since both plates are not dense enough to subduct they instead pile up, which forms large mountain ranges and plateaus, like the Alps or Mount Everest.&lt;/p&gt;

&lt;p&gt;To simulate this process, each edge on a collision boundary is check and if the two plates are still converging, the crust around the boundary is lifted by an amount based on the converging velocity and its distance to the boundary. Since this affects not just the two vertices, that are part of the boundary, but also the crust that is further away, a flood-fill is used to recursively visit all neighboring vertices of the plate until the elevation change becomes insignificant.&lt;/p&gt;

&lt;p&gt;My initial implementation actually contained a more general approach, that compared the volume of the Voronoi cell of &lt;em&gt;every&lt;/em&gt; vertex, including internal ones, between time-steps and updated the elevation to preserve their volume, also taking the isostatic equilibrium into account. My hope was, that this would better simulate the effects of collisions further from the boundary. Alas, the resulting terrain was much more messy and noisy than the current implementation. But that is definitely an approach I plan to revisit once the simulation is stable and produces satisfactory results.&lt;/p&gt;

&lt;h4 id=&quot;boundary_typesubducting_&quot;&gt;Boundary_type::subducting_*&lt;/h4&gt;

&lt;p&gt;The other type of boundary that contributes to mountain formation are subduction zones. In the partial melting process of the subducted crust, water and other foreign materials, that were pulled down with the bedrock, disrupt the equilibrium inside the upper mantle, which causes a sharp increase in volcanism on the un-subducted plate above. This process creates the long thin mountain ranges near subduction boundaries, like the Andes and the islands around the &lt;a href=&quot;https://en.wikipedia.org/wiki/Ring_of_Fire&quot;&gt;ring of fire&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The simulation for this is currently quite similar to that for collision boundaries, except that only one of the plates is lifted up, while the other one is pulled down, and that the position of the elevation increase is slightly different, i.e. a smaller area and slightly inland from the actual boundary.&lt;/p&gt;

&lt;h3 id=&quot;9-simulate-erosion&quot;&gt;9. Simulate Erosion&lt;/h3&gt;
&lt;p&gt;The elevation-code described above mostly adds elevation. So, if left to its own devices, the whole planet would be one huge mountain range. To balance this out, we need an additional system that reduces the elevation, which is precisely what the erosion simulation does.&lt;/p&gt;

&lt;p&gt;This part is more or less copied as is from the paper of Cortial et al., with just some minor additions. Eventually, once we have a climate simulation to generate temperatures, precipitation and watersheds, we’ll extend this to a more interesting/faithful/complete erosion system. But for now, this simple simulation is good enough to counterbalance the mountain formation.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto ocean_elevation =  -6'000.f; // meter
const auto max_elevation   =  10'000.f; // meter
const auto min_elevation   = -10'000.f; // meter

const auto oceanic_dampening  = 6e-5f; // meter/year
const auto sediment_accretion = 3e-6f; // meter/year
const auto simple_erosion     = 8e-5f; // meter/year

const auto sink_amount   = 0.006f; // meter/year
const auto sink_begin    = 2'000'000.f; // years
const auto sink_duration = 2'000'000.f; // years

if(types[v] == Crust_type::continental) {
    // Move elevation of continental crust towards 0m above sea level.
    // Since this is based on the current elevation, larger mountains are eroded faster.
	elevation -= elevation / max_elevation * simple_erosion * dt;

} else if(elevation &amp;gt; -6'000.f) {
    // Oceanic ridges are initially less dense than the surrounding sea floor and
    //   cool and sink over time, which is (crudely) simulated here
	const auto age = now - created[v];
	if(age &amp;gt; sink_begin &amp;amp;&amp;amp; age &amp;lt; sink_duration + sink_begin) {
		elevation = std::max(ocean_elevation, elevation - sink_amount * dt);
	}

    // Ocean floor above the height of the abyssal plane is pushed down
	const auto x      = elevation / ocean_elevation;
	const auto factor = (1.f - elevation / min_elevation) * smootherstep(0.f, 0.2f, x);
	const auto delta  = std::max(0.f, factor * oceanic_dampening * dt);
	elevation         = std::max(ocean_elevation, elevation - delta);

} else {
    // Ocean floor below the abyssal plane (e.g. from old inactive subduction zones)
    //   is slowly filled up by sediment.
	elevation += sediment_accretion * dt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Now that we’ve got some movement going and elevation elevating, we’ve actually already covered half of the algorithm:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add vertices where more details are needed and remove vertices unessential ones&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;color:#c6ffb3&quot;&gt;✔ Compare the properties of neighboring sub-plates to determine if they belong to the same tectonic plate or fall into one of the boundary zones discussed above (transform, divergent, convergent CC/OC/OO)&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;Create new oceanic crust at divergent boundaries&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;color:#c6ffb3&quot;&gt;✔ Calculate all forces acting on the sub-plates&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;color:#c6ffb3&quot;&gt;✔ Calculate the acceleration of all sub-plates, based on the forces acting on them, and utilize Verlet integration to calculate their new velocities and positions&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;color:#c6ffb3&quot;&gt;✔ Update each sub-plate’s elevation, if they are part of a subduction or collision zone&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;Check for collisions and resolve them&lt;/li&gt;
  &lt;li&gt;Check and restore Delaunay condition, if required &lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This step also searches for triangles whose height is much smaller or larger than their base and tries to normalize them because triangles like this could negatively impact the simulation over time.&quot; data-title=&quot;This step also searches for triangles whose height is much smaller or larger than their base and tries to normalize them because triangles like this could negatively impact the simulation over time.&quot;&gt;[7]&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;&lt;span style=&quot;color:#c6ffb3&quot;&gt;✔ Simulate erosion&lt;/span&gt;&lt;/li&gt;
  &lt;li&gt;Combine colliding continental plates and split plates that have gotten too large&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All that’s left for the last two posts now, is to clean up the mess we left behind in step 5. Which means cleaning up inconsistencies in the mesh and handling collisions caused by the plate’s movement (7. and 8.) as well as creating/destroying crust (1. and 3.) and simulating large-scale plate interactions (10.).&lt;/p&gt;
</description>
                <link>https://second-system.de/2022/09/04/tectonics_3</link>
                <guid>https://second-system.de/2022/09/04/tectonics_3</guid>
                <pubDate>Sun, 04 Sep 2022 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>Plate Tectonics 2</title>
                <description>&lt;p&gt;The first aspect we will have to take a closer look at, is how we can generate our starting point, i.e. the initial state before the simulation is run.&lt;/p&gt;

&lt;p&gt;We’ve already discussed the first half of that in a &lt;a href=&quot;/2021/05/02/world_creation&quot;&gt;previous post&lt;/a&gt;, where we’ve generated a simple triangle mesh of a sphere. So, I’ll first recap the most important part of that and then dive into how we can populate that mesh with the information we need for the actual simulation, like plate-IDs, plate-velocities and elevations.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;creating-the-mesh&quot;&gt;Creating the mesh&lt;/h2&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_points.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;(A) Uniformly distributed points (Fibonacci Sphere)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;height:12em; display: table-cell; vertical-align: middle;&quot;&gt;
To recap, what we need is a set of random points, that are uniformly distributed on the surface of a sphere, where each point represents part of a tectonic plate. To generate uniformly distributed points&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;i.e. no obvious poles, patterns or other concentrations of points in certain areas. &quot; data-title=&quot;i.e. no obvious poles, patterns or other concentrations of points in certain areas. &quot;&gt;[1]&lt;/sup&gt;, we utilize the golden angle to create a Fibonacci Sphere, by placing points walking downwards from one of the poles and rotating &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;ϕ&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2\pi \cdot (2-\phi)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.64444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;π&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ϕ&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; radians&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;= approx. 137.507... degrees&quot; data-title=&quot;= approx. 137.507... degrees&quot;&gt;[2]&lt;/sup&gt; each step (image A).
&lt;/p&gt;
&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_shaded.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;(B) Shaded triangulation of these points&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;height:12em; display: table-cell; vertical-align: middle;&quot;&gt;
Once we have a set of points, the next step is to generate a triangle mesh from them. There are many possible ways to triangulate a set of points, but the one we’ll use is the Delaunay triangulation, because it avoids long thin triangles, that could be problematic during the simulation, and also because its dual-mesh is the Voronoi diagram of the mesh, which we’ll utilize for other parts of the simulation. While there are more efficient algorithms to generate a Delaunay triangulation of a set of points, since our points all lay on the surface of a sphere, the easiest approach is to generate the convex hull of the points (using the QuickHull algorithm), which in this case coincides with the Delaunay triangulation of the points (image B).
&lt;/p&gt;
&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_noise.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;(C) Shaded triangulation, with more vertices and a small random perturbation&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;height:12em; display: table-cell; vertical-align: middle;&quot;&gt;
The algorithm described above distributes the points completely uniformly. But for a more natural look, it’s actually preferable to sacrifice some uniformity to achieve a less predictable structure.

Luckily, all that’s required to achieve this, is to offset each point by a random amount (image C). And as long as that offset is smaller than our step-size, the points are still guaranteed to be far enough apart.

&lt;/p&gt;
&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This gives use the following code to generate our mesh:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Define the data layer that contains the vertex positions in 3D Cartesian coordinates
constexpr auto position_info = Layer_info&amp;lt;Vec3, Layer_type::vertex&amp;gt;(&quot;position&quot;);

// Acquire all necessary resources from the world structure
auto mesh        = world.lock_mesh();
auto [positions] = world.lock_layer(position_info);
auto rand        = world.lock_random();

// Create N vertices
mesh-&amp;gt;add_vertices(vertex_count);

// Pre-calculate the golden angle and the step-size for calculating y,
//   so we just need to multiply them with i in the loop
constexpr auto golden_angle = 2.f * std::numbers::pi_v&amp;lt;float&amp;gt; * (2.f - std::numbers::phi_v&amp;lt;float&amp;gt;);
const auto     step_size    = 2.f / (vertices - 1);

for(std::int32_t i = 0; i &amp;lt; vertices; i++) {
	const auto offset = perturbation &amp;gt; 0 ? rand-&amp;gt;uniform(0.f, perturbation) : 0.f;
	auto       ii    = std::min(i + offset, vertices - 1.f);

	// Calculate the x/y/z position of the current vertex on the unit sphere
	const auto y     = 1.f - step_size * ii;
	const auto r     = std::sqrt(1 - y * y);
	const auto theta = golden_angle * ii;
	const auto x     = std::cos(theta) * r;
	const auto z     = std::sin(theta) * r;

	// Set the vertex position (multiplying with the radius of our sphere)
	positions[Vertex(i)] = Vec3{x, y, z} * radius;
}

// Triangulate the generated points
auto  qh      = quickhull::QuickHull&amp;lt;float&amp;gt;{};
auto  hull    = qh.getConvexHull(&amp;amp;positions.begin()-&amp;gt;x, positions.size(), false, true);
auto&amp;amp; indices = hull.getIndexBuffer();

for(std::size_t i = 0; i &amp;lt; indices.size(); i += 3) {
	mesh-&amp;gt;add_face(indices[i], indices[i+1], indices[i+2]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;generating-the-first-tectonic-plates&quot;&gt;Generating the first tectonic plates&lt;/h2&gt;

&lt;p&gt;Besides the mesh itself, there are also a couple of values the simulation uses, that we need to initialize to create the initial set of tectonic plates. As already mentioned, these values are attached to the meshes vertices to describe the properties of the area around the vertex and are defined as follows in the code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class Crust_type : int8_t { none, oceanic, continental };

// Position of the vertex in space (initialized by the process above)
constexpr auto layer_position   = Layer_info&amp;lt;Vec3,       Layer_type::vertex&amp;gt;(&quot;position&quot;);
// Integer ID of the plate, the vertex belongs to
constexpr auto layer_plate_id   = Layer_info&amp;lt;int32_t,    Layer_type::vertex&amp;gt;(&quot;plate_id&quot;);
// Dominant type of the crust in this area
constexpr auto layer_plate_type = Layer_info&amp;lt;Plate_type, Layer_type::vertex&amp;gt;(&quot;crust_type&quot;);
// Velocity of this piece of crust
constexpr auto layer_velocity   = Layer_info&amp;lt;Vec3,       Layer_type::vertex&amp;gt;(&quot;velocity&quot;);
// Height of this piece of crust above/below the (arbitrary) sea level
constexpr auto layer_elevation  = Layer_info&amp;lt;float,      Layer_type::vertex&amp;gt;(&quot;elevation&quot;);
// The timestamp at which the crust has formed
constexpr auto layer_created    = Layer_info&amp;lt;float,      Layer_type::vertex&amp;gt;(&quot;crust_created&quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The process of generating the values itself is actually incredibly simple. First we pick &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.68333em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; random vertices as seed-points to create &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.68333em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; tectonic plates, assign them each a random elevation/velocity/… and finally use a flood-fill type algorithm to grow them in all directions, until all vertices are assigned to a plate.&lt;/p&gt;

&lt;h3 id=&quot;initialize-seed-vertices&quot;&gt;Initialize seed vertices&lt;/h3&gt;

&lt;p&gt;First we’ll need to pick a couple of random points that will be the seeds from which we then grow our tectonic plates. The number of plates we want to generate, as well as what percentage of these will be oceanic/continental crust, is defined by a set of parameters. Then we just loop &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.68333em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; times, choosing a random vertex&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Using the helper function random_vertex, that also checks if the vertex is still unused and retries a couple of times until a suitable vertex is found. &quot; data-title=&quot;Using the helper function random_vertex, that also checks if the vertex is still unused and retries a couple of times until a suitable vertex is found. &quot;&gt;[3]&lt;/sup&gt; in each iteration and assigning it a random initial state (ID of the plate, type of crust, age, elevation and its initial velocity).&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto count       = rand.uniform(plates_min, plates_max);
const auto ocean_count = static_cast&amp;lt;int&amp;gt;(std::floor(count * ocean_prop));
		
auto next_id       = std::int32_t(1);
auto open_vertices = std::vector&amp;lt;Vertex&amp;gt;();
open_vertices.reserve(static_cast&amp;lt;std::size_t&amp;gt;(count) * 10u);

for(auto i = 0; i &amp;lt; count; i++) {
	// Try to find a free vertex to use as a seed point
	if(auto seed_vertex = random_vertex(mesh, rng, [&amp;amp;ids = ids](auto v) { return ids[v] == 0; })) {
		const auto ocean = i &amp;lt; ocean_count; // First X plates are oceanic, rest is continental
		
		ids[*seed_vertex]   = next_id++;
		types[*seed_vertex] = ocean ? Crust_type::oceanic : Crust_type::continental;
		// Assign other properties based on crust type
		// ...

		// Remember the seed vertices, so we can use them for the flood-fill
		open_vertices.emplace_back(*seed_vertex);
	}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Most of the properties are pretty straightforward. The ID and crust type are directly defined in the code above. And the crusts age and elevation are then randomly chosen from a range of acceptable values for the given type of crust. The only property that is a bit more complicated to compute is the crusts initial velocity. Because I’ve opted to use a simple Cartesian coordinate system, the velocity is a 3D vector that could point in any direction. But, since the vertices are restrained to the sphere’s surface, the velocity should ideally be locally flat, i.e. perpendicular to the surface normal at any given position. Since our mesh is a sphere, we can derive the surface normal by normalizing the position vector of the current vertex. Based on this vector, we can then construct an orthonormal basis at this point, as shown in the code below&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;While technically a single vector is not enough to define such a coordinate system, that is not a problem here, since the movement direction itself is random and we just need to ensure that it's perpendicular to the normal. &quot; data-title=&quot;While technically a single vector is not enough to define such a coordinate system, that is not a problem here, since the movement direction itself is random and we just need to ensure that it's perpendicular to the normal. &quot;&gt;[4]&lt;/sup&gt;. We can then generate a random 2D vector from an angle and a target velocity and transform it into the coordinate basis we just defined.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Generate random 2D velocity vector
const auto angle  = rng.uniform(0.2f, 2.f * 3.141f);
const auto speed  = rng.uniform(0.01f, 1.f) * (ocean ? max_ocean_speed : max_continent_speed);
const auto vel_2d = Vec2{std::sin(angle), std::cos(angle)} * speed;

// Build 3D coordinate system at current vertex
const auto normal        = normalized(positions[*seed_vertex]);
const auto tangent       = cross(normal, Vec3(-normal.z, normal.x, normal.y));
const auto bitangent     = cross(normal, tangent);

// Transform the 2D vector to lay in the 3D plane defined by the tangent and bitangent
velocities[*seed_vertex] = vel_2d.x*tangent + vel_2d.y*bitangent;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;flood-fill&quot;&gt;Flood Fill&lt;/h3&gt;

&lt;p&gt;The final step of the process is then to iteratively grow each plate from its initial seed, until all vertices are assigned to a tectonic plate.&lt;/p&gt;

&lt;p&gt;We already have a list of the seed vertices, that we memorized in the previous step. So, all we have to do now is iterate over each of these vertices neighbors, copy their properties to these neighbors, if they are still uninitialized, and repeat that recursively until there are no uninitialized vertices left.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto new_opened_vertices = std::vector&amp;lt;Vertex&amp;gt;();
new_opened_vertices.reserve(open_vertices.size());

// Iterate until the last iteration didn't contain any new, formally unassigned, vertices
while(!open_vertices.empty()) {
	for(auto&amp;amp; seed : open_vertices) {
		for(Vertex n : v.neighbors(mesh)) {
			if(types[n] == Crust_type::none) {
				ids[n]           = ids[seed];
				types[n]         = types[seed];
				elevations[n]    = elevations[seed] + /* ... */;
				created_times[n] = created_times[seed] + /* ... */;
				velocities[n]    = normalized_velocity(positions[n], velocities[seed]);

				// Remember as starting point for next iteration of outer loop
				new_opened_vertices.emplace_back(n);
			}
		}
	}

	// Iterate again with the new points,
	// but in a random order to avoid growing the first plates larger than the rest
	rng.shuffle(new_opened_vertices);
	std::swap(new_opened_vertices, open_vertices);
	new_opened_vertices.clear();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The implementation is again relatively straightforward. We are using a secondary vertex-list, that we populate with the new vertices we initialized in this loop. Iterate over the previous list of seeds, to add each of their neighbors to their plate and remember them for the next iteration. And after each iteration of the outer loop, we swap the two vertex-lists and continue until we had one iteration where no new vertices have been assigned, so we know we are done. The only aspect we have to consider, is that this algorithm favors plates that are visited earlier because they tend to have more unassigned neighbors to “convert” to their plate. Since the first seed also adds its converted neighbors to the front of the next-iterations list, this effect could disproportionately grow the first couple of plates. To counteract this, we need to randomize the order of the vertices in each iteration, so it cancels out over multiple iterations. And as an added bonus, this also tends to generate less circular and more natural-looking shapes.&lt;/p&gt;

&lt;p&gt;The process of assigning a new vertex to a seed’s plate is, as can be seen above, again pretty simple. Most of the properties are copied directly from the original plate, applying small perturbations to both the crust formation time&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Depending on the crusts type we either slightly increase or decrease its age, to get a more natural-looking distribution. &quot; data-title=&quot;Depending on the crusts type we either slightly increase or decrease its age, to get a more natural-looking distribution. &quot;&gt;[5]&lt;/sup&gt; and its elevation&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Small random noise added to continental crust &quot; data-title=&quot;Small random noise added to continental crust &quot;&gt;[6]&lt;/sup&gt;. The only property that needs additional consideration is (again) the velocity. Because the velocity vector should still be perpendicular to the new vertices surface normal, we can’t just copy it from the original vertex. Instead, we need to re-normalize it using the following function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Vec3 normalized_velocity(const Vec3&amp;amp; position, const Vec3&amp;amp; velocity) {
	constexpr auto dt = 100.f;
	
	auto next_position = position + velocity * dt;                     // Move position one step with given velocity
	next_position      = normalized(next_position) * length(position); // Normalize new position back to sphere surface
	return (next_position - position) / dt;                            // Calculate actual velocity
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fixed_height&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/06/sphere_plates.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;The generated planet, with the area around each vertex highlighted in the plate's color. Shades of blue for oceanic sub-plates and shades of green for continental ones&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/06/sphere_plate_directions.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;The generated planet with arrows indicating each sub-plate's movement direction&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;With that out of the way, in the following posts we’ll look into the simulation algorithm, that iterates from this initial state to generate our terrain.&lt;/p&gt;

</description>
                <link>https://second-system.de/2022/03/19/tectonics_2</link>
                <guid>https://second-system.de/2022/03/19/tectonics_2</guid>
                <pubDate>Sat, 19 Mar 2022 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>Plate Tectonics 1</title>
                <description>&lt;p&gt;&lt;img src=&quot;/assets/images/05/spongebob-many-months-later.gif&quot; style=&quot;width:20em&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Well, that was a bit of a longer pause, than I had hoped. So much for my “best intentions of documenting my progress”…&lt;/p&gt;

&lt;p&gt;But I’ve finally managed to implement a working prove-of-concept for the plate simulation to generate terrain, also it’s still a bit rough around the edges.&lt;/p&gt;

&lt;figure class=&quot;captioned_image full_width&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/05/plate_simulation.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Plate simulation (5x speed-up), consisting of about 2'500 triangles&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While there are still many problems that need more work&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;i.a. continents loosing too much area during collisions, missing details on coastlines and oceanic ridges, no shallow/continental-plate oceans, weird looking elongates plates and unnatural looking splitting of large plates &quot; data-title=&quot;i.a. continents loosing too much area during collisions, missing details on coastlines and oceanic ridges, no shallow/continental-plate oceans, weird looking elongates plates and unnatural looking splitting of large plates &quot;&gt;[1]&lt;/sup&gt;, the general approach appears to work, at least.&lt;/p&gt;

&lt;p&gt;But one thing that I noticed now is that the current tooling to visualize the results is not enough to test and debug the algorithm. So, before I continue working on the algorithm itself, I’ve decided to refactor and improve the editor to better visualize the generated data and allow users to modify the intermediary results. But first, it’s probably a good idea to document how the current implementation of the plate simulation works&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;before I forget how it works and would have to wrangle that knowledge from the code itself... &quot; data-title=&quot;before I forget how it works and would have to wrangle that knowledge from the code itself... &quot;&gt;[2]&lt;/sup&gt;.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;The plate simulation in its current form is heavily inspired by the work of Cortial et al. in their 2019 paper &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02136820/file/2019-Procedural-Tectonic-Planets.pdf&quot;&gt;Procedural Tectonic Planets&lt;/a&gt;. However, there are some aspects where my implementation diverges. Either because the paper didn’t provide details about a specific part of their implementation or because I think it will fare better, with my specific goals in mind.&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/Procedural Tectonic Planets.gif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/Procedural Tectonic Planets.gif&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Procedural Tectonic Planets&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As the algorithm is relatively complex, I’ve split this post into multiple entries. In this one, we’ll look at how the tectonic plates are modelled and get an overview of the simulation on an abstract level and the following posts will then go into more detail about&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The generation of the initial state (i.e., the planet and its plates, that we use as input for the actual simulation)&lt;/li&gt;
  &lt;li&gt;How plates are moved across the sphere’s surface and how that effects the elevation&lt;/li&gt;
  &lt;li&gt;How collisions and intersection between plates are resolved and when/how new crust is generated&lt;/li&gt;
  &lt;li&gt;When and how plates are merged or split (i.e. &lt;a href=&quot;https://en.wikipedia.org/wiki/Suture_(geology)&quot;&gt;suturing&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Rift&quot;&gt;rifting&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;enough-plate-tectonics-to-be-dangerous-interesting&quot;&gt;Enough plate tectonics to be &lt;del&gt;dangerous&lt;/del&gt; interesting&lt;/h2&gt;

&lt;p&gt;But first, let’s recap what I want to achieve with the plate simulation. The main focus of this step is to generate high-level terrain features, that look as realistic as possible&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;i.e. mountain ranges near (former) plate-boundaries, matching terrain on previously connected continents and in general, a distribution of landmasses that is more or less similar to earths &quot; data-title=&quot;i.e. mountain ranges near (former) plate-boundaries, matching terrain on previously connected continents and in general, a distribution of landmasses that is more or less similar to earths &quot;&gt;[3]&lt;/sup&gt;, at a resolution of about 1000 km² per data-point. Of course, for any practical use-case, as well as features like rivers, we’ll need a much higher resolution. My hope is that (similar to &lt;a href=&quot;https://undiscoveredworlds.blogspot.com&quot;&gt;Undiscovered Worlds&lt;/a&gt; and the followup paper by &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02967067/document&quot;&gt;Cortial et al. from 2020 “Procedural-Tectonic-Planets”&lt;/a&gt;) I will be able to later generate a more detailed small-scale view, based on the output of this high-level simulation.&lt;/p&gt;

&lt;p&gt;To achieve these realistic results, I planned to stray as little as possible into the fun and easy, but unpredictable and boring world of random noise. Instead, I wanted to try to stay as close as reasonably possible to the physical reality&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;At least as far as my limited understanding of it allows&quot; data-title=&quot;At least as far as my limited understanding of it allows&quot;&gt;[4]&lt;/sup&gt;. The problem with that is that plate tectonics is actually a quite complex 3D problem&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;who would have thought?&quot; data-title=&quot;who would have thought?&quot;&gt;[5]&lt;/sup&gt; and &lt;em&gt;a bit difficult&lt;/em&gt; to observe at the time-scales I’m interested in. So, I obviously can’t really hope to accurately simulate even a couple of years of plate movement on my target hardware, much less the millions of years I’ll need.&lt;/p&gt;

&lt;p&gt;Luckily, I only really want to fool humans. So, the algorithm doesn’t have to be perfect, but only &lt;em&gt;slightly&lt;/em&gt; more capable than the average observer and (as others have already demonstrated) I should be able to ignore a surprising amount of the more complicated aspects and only focus on some of the more obvious high-level features.&lt;/p&gt;

&lt;h3 id=&quot;plate-interactions-and-the-wilson-cycle&quot;&gt;Plate-Interactions and the Wilson Cycle&lt;/h3&gt;

&lt;p&gt;The one thing I definitely can’t avoid to model are the tectonic plates themselves. So first, I should probably summarize what I understand about the physical processes, before I explain how I simplified them for my model&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;As my knowledge about this topic is still anything but profound, these explanations should definitely be taken with more than a grain of salt. &quot; data-title=&quot;As my knowledge about this topic is still anything but profound, these explanations should definitely be taken with more than a grain of salt. &quot;&gt;[6]&lt;/sup&gt;.&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/Plate_tectonics_map.gif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/Plate_tectonics_map.gif&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Tectonic activity map of the Earth&lt;sup&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/File:Plate_tectonics_map.gif&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Tectonic plates are disjointed pieces of the Earth’s crust, that “float” on top of the mantel. Their movement is driven by convection currents in the upper mantle, that are in-turn influenced by the properties and interactions of the plates above. A common misconception&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;or at least one I had&quot; data-title=&quot;or at least one I had&quot;&gt;[7]&lt;/sup&gt; is imagining the plates as solid ridged stone slabs, that float on top of liquid magma. Also, this a nice simple visualization, it’s important for their interactions to understand that both the crust and the mantel are actually solids. It is only over these long geological time-scales that the mantle behaves similar to a high-viscosity fluid. But over these time-scales, the crust also exhibits less rigid and more elastic properties, which enables some of the deformations we observe.&lt;/p&gt;

&lt;p&gt;A nice simple model, that nonetheless contains all the high-level aspects I’m interested in, is the Wilson Cycle, that describes the formation and breakup of supercontinents as a series of stages.&lt;/p&gt;
&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/05/wilson_cycle.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Visualization of the Wilson Cycle&lt;sup&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=I_q3sAcuzIY&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One essential factor in this model is the density of the crust that makes up the tectonic plate. While their composition is again quite complex, the crust can be classified into two general types: Continental crust, that has a relatively high density and makes up most of the dry landmass, and oceanic crust, that has a comparatively high density and makes up most of the deep ocean floor. Because the crust floats on top of the mantle, its density not only determines “how high” it floats, but also how it behaves when it gets thicker or thinner and how it reacts to collisions with other plates.&lt;/p&gt;

&lt;p&gt;It’s important to note here, that this distinction is not as cut and dry as one might hope. Instead, it’s more of a categorization, based on the density of the average composition of the crust in an area. Which means, while there are some plates that consist mostly of continental/oceanic crust, most plates consist of both (as can be seen on the map above). And It’s also not uncommon for oceanic crust to &lt;a href=&quot;https://en.wikipedia.org/wiki/Ophiolite&quot;&gt;end up on dry land&lt;/a&gt;, as well as for continental crust to &lt;a href=&quot;https://en.wikipedia.org/wiki/Continental_shelf&quot;&gt;be part of an ocean&lt;/a&gt;. But to keep the following descriptions as concise as possible, I’ll use the terms “continental plate” to describe a plate that mostly consists of low-density material in the relevant area and “oceanic crust” for high-density.&lt;/p&gt;

&lt;p&gt;Considering all this, there are five types of interactions between plates we need to model:&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_transform.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_transform.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Transform boundary&lt;/b&gt;: Two plates moving past each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_conservative_plate_boundary_opposite_directions.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_divergent.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_divergent.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Divergent boundary&lt;/b&gt;: Two plates moving away from each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_constructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_co.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_co.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (CO)&lt;/b&gt;: An oceanic and a continental plate colliding with each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Oceanic-continental_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_oo.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_oo.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (OO)&lt;/b&gt;: Two oceanic plates colliding with each other&lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Oceanic-oceanic_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height_small&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/plate_boundary_convergent_cc.svg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/plate_boundary_convergent_cc.svg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Convergent boundary (CC)&lt;/b&gt;: Two continental plates colliding with each other &lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Continental-continental_destructive_plate_boundary.svg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;Transform boundaries mostly just cause earthquakes, and are therefore not really of interest to us. So, we only have to look at convergent and divergent boundaries.&lt;/p&gt;

&lt;p&gt;Divergent boundaries are relatively simple. The two plates move apart and mantel material wells up between them, which begins to a new ocean, once they have moved sufficiently far apart.&lt;/p&gt;

&lt;p&gt;The most interesting boundaries are the convergent once, which we have to further distinguish into three subtypes based on the density/type of the involved crust:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Continental-Oceanic (CO): The type of boundary we can see in the animation above. Here the denser oceanic plate is subducted under the continental plate, forming a trench between them, and starts to sink into the mantel. Once the subduction started, the weight of the already subducted plate also pulls on the remaining part, further speeding up the process. As the water subducted with the plate lowers the melting-point of the mantle material, it begins to partially melt, causing magma to rise and a volcanic mountain range to form&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Volcanos like these are also where most of the new continental crust forms. Because the minerals that make it up, not only have a lower density but also a lower melting point, which means they tend to solidify last and such closer to the surface. &quot; data-title=&quot;Volcanos like these are also where most of the new continental crust forms. Because the minerals that make it up, not only have a lower density but also a lower melting point, which means they tend to solidify last and such closer to the surface. &quot;&gt;[8]&lt;/sup&gt;.&lt;/li&gt;
  &lt;li&gt;Oceanic-Oceanic (OO): Quite similar to CO boundaries, except that the two plates are much more similar in density. But the principle remains the same, meaning the higher-density plate subducts, which will usually be the older of the two. This type of collision forms deep oceanic trenches and island arcs.&lt;/li&gt;
  &lt;li&gt;Continental-Continental (CC): In contrast to the other two, there is no significant subduction here because both plates are too low-density to sink into the mantel, and are instead uplifted to create large mountains like the Himalayas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another interesting aspect, that is currently not implemented, is the formation of mid-plate volcanos, like the one that formed the Hawaiian islands. These are not caused by plate interactions, but are instead thought to be fed by hotspots in the mantle. Hence, they are mostly stationary and cause island arcs to form, when the crust above them moves.&lt;/p&gt;

&lt;h3 id=&quot;discretization-of-space&quot;&gt;Discretization of Space&lt;/h3&gt;
&lt;p&gt;The most obvious simplification in my model is that I’m using an extremely coarse discretization of the simulated space. In this step, I only really care about the rough shape of tectonic plates and large mountains, which doesn’t require much in terms of resolution.&lt;/p&gt;

&lt;p&gt;As I’ve already outlined in the previous blog posts, I’m modelling my planets as triangle meshes with data stored on the vertices, faces and edges, instead of a more structured/uniform grid. This allows for a dynamic adjustment of the resolution in interesting (i.e. especially active) areas, without having to store detailed data about large stretches of empty boring ocean.&lt;/p&gt;

&lt;p&gt;The simulation at the top consist of about 2’500 triangles and 1’300 Vertices, that are more or less evenly distributed on the surface (510’064’471 km²), which gives us a resolution of about 400’000 km² per Vertex.&lt;/p&gt;

&lt;p&gt;One of the next steps will be to improve the subdivision algorithm, so I’ll end up with about 1’000’000 km² in mostly empty/flat areas and 1’000 km² - 100’000 km² near the coastline and mountains.&lt;/p&gt;

&lt;h3 id=&quot;reduced-dimensionality&quot;&gt;Reduced Dimensionality&lt;/h3&gt;

&lt;p&gt;There is one more way we can reduce the space we need to simulate: reduce the number of dimensions&lt;/p&gt;

&lt;p&gt;While actual plate tectonics is a complex 3D problem — with complicated fluid dynamics and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Isostasy&quot;&gt;isostatic equilibrium&lt;/a&gt; determining how plates interact and how high/low pieces of crust “float” — in this simplified model we can ignore most of this and only focus on the position of the plates on the surface and their elevation.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/06/sphere_plate_directions.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Visualization of the tectonic plates, consisting of multiple sub-plates, each with their own velocity, type, and elevation&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To summarize, the planet is modelled as a triangle mesh&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;encoded with the quad-edge structure discussed previously&quot; data-title=&quot;encoded with the quad-edge structure discussed previously&quot;&gt;[9]&lt;/sup&gt; where each vertex describes the properties of the crust in the surrounding area. To form tectonic plates, multiple connected vertices are grouped together by assigning them an integer ID. Or put another way, the vertices describe sub-plates, that are part of a larger tectonic plate.&lt;/p&gt;

&lt;p&gt;So, each vertex stores the following information:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The ID of the plate it belongs to&lt;/li&gt;
  &lt;li&gt;Its elevation, i.e. the average elevation of this area’s crust (negative for areas below sea level)&lt;/li&gt;
  &lt;li&gt;If it contains (mostly) continental or oceanic crust&lt;/li&gt;
  &lt;li&gt;Its velocity&lt;/li&gt;
  &lt;li&gt;When the crust formed, i.e. how old it is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since each vertex is connected by an edge to its nearest neighbors, forming a Delaunay triangulation, we can inspect this mesh to detect collisions, traverse the plates and store additional information required for the simulation on its edges.&lt;/p&gt;

&lt;p&gt;This is also one of the major differences to the work of Cortial et al., since they appear to store each plate as an individual spherical triangulation of points, instead of one coherent mesh. While keeping the plates as separate triangulations would simplify some parts of the algorithm (collision detection/resolution) my hope is that a single coherent mesh will make it easier to reuse the structure for both other simulations (water-shed, weather patterns, …) and rendering.&lt;/p&gt;

&lt;h2 id=&quot;simulation&quot;&gt;Simulation&lt;/h2&gt;

&lt;p&gt;On the simplest level, all we are doing to simulate plate tectonics is moving vertices around on the sphere’s surface and handle collisions by adding/removing vertices, changing the topology or modifying their elevation.&lt;/p&gt;

&lt;p&gt;The movement itself is extremely simple. Since the vertices already know their position and velocity, all we need to do is apply the well-known equations of motion to calculate their new position on each time-step with: &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mrow&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;msup&gt;&lt;mi&gt;t&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;x_{n+1} = x_n + v_n t + a_n t^2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.638891em;vertical-align:-0.208331em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.301108em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;mbin mtight&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.208331em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.73333em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.151392em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.76508em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.151392em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.964108em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.151392em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.8141079999999999em;&quot;&gt;&lt;span style=&quot;top:-3.063em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/spring_model.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/spring_model.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Some vertices belonging to different plates (green/blue), as well as their velocities, connections and a visualization of some of the forces that affect them: Orange spring-damper systems between vertices of the same plate and red force-generators between different plates, that pull/push vertices based on more complicated rules.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The interesting part is how we determine the vertices (i.e. sub-plates) accelerations &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;a&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;a_n&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.151392em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. This is the second part where we deviate from the work of Cortial et al., in part because of our different data model. Since all vertices are part of the same Delaunay triangulation, we can make the simplifying assumption that a vertex is only significantly affected by its direct neighbors, without introducing too many noticeable errors. Hence, we can use the same general system to calculate both the interaction between sub-plates belonging to the same tectonic plate and interactions between different plates.&lt;/p&gt;

&lt;p&gt;This system can be thought of as similar to a spring-damping model based soft-body simulation. The vertices are treated as point masses, and each edge applies a force on its origin and destination based on its properties and the type of boundary the edge represents:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Both vertices belong to the same plate: The force is calculated by a damped spring, that causes the sub-plates to retain their initial distance but allows for some deformations.&lt;/li&gt;
  &lt;li&gt;Divergent boundary: A small &lt;a href=&quot;https://en.wikipedia.org/wiki/Ridge_push&quot;&gt;ridge-push&lt;/a&gt; force is applied, that moves the vertices further apart.&lt;/li&gt;
  &lt;li&gt;Convergent boundary (CC): A force, proportional to the velocity along the convergence direction and the distance between the plates, is applied to resist the collision until the plates either separate again or are sutured together.&lt;/li&gt;
  &lt;li&gt;Convergent boundary (OC or OO): The denser of the two sub-plates is slowly subducted under the other and a force is applied to the subducting plate to simulate the &lt;a href=&quot;https://en.wikipedia.org/wiki/Slab_pull&quot;&gt;slab pull&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/05/spring_model_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/05/spring_model_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Forces are not just modelled based on the primal edges (gray lines), but also for their duals (dashed white line), which connect the faces and the left/right of the primal edge. But in this case, they are modelled as springs which connect the left and right vertices.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It should be noted that that model outlined here would not work for soft-body simulations, in the general case. Because we rely exclusively on the triangle edges to calculate forces, we are missing many springs that should normally be present in such a system, to prevent shearing and bending. However, in our case we avoid such artifacts because the individual vertices are (a) constrained to the sphere’s surface, (b) cover the whole sphere and we (c) prevent self-intersections and non-manifold geometry. This effectively limits the simulation to 2D, also the surface itself is a 3D sphere, and doesn’t allow for bending, folding or significant shearing, which would otherwise occur.&lt;/p&gt;

&lt;p&gt;However, using just the edges of the primal mesh is not sufficient because of another reason: During the simulation the mesh is continuously updated, which includes flipping edges. Since this effectively removes springs, which are potentially under compression/tension, from the model and adds new ones, between formally unconnected vertices, which would then be in a relaxed state, this would create artifacts like heavy deformation of continents or loss of land area&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Which is definitely not something I noticed just now while writing this.&quot; data-title=&quot;Which is definitely not something I noticed just now while writing this.&quot;&gt;[10]&lt;/sup&gt;. To solve this, we don’t use only primal but also dual edges, which apply forces to the two vertices opposite of the corresponding primal edge (see image above). Because of this, when an edge is flipped, the spring-parameters can be swapped between the primal and dual edge, thereby conserving the spring’s stored energy and avoiding these kinds of artifacts.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Apart from the plate movement itself, there are also some other steps we need to take care of, that will be the focus of the next posts:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The elevation of each sub-plate is updated near convergent and divergent plate boundaries, based on their separating velocities.&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;In the future I would also like to test a more generalized system, that redistributes elevation from all neighboring sub-plates based on changes in their distance. This would not just simplify the elevation calculations, but also allow us to model things like folding of plates further from the plates' boundary. But during initial testing, this appeared to be too chaotic. So, I stayed closer to the original paper for the initial implementation. &quot; data-title=&quot;In the future I would also like to test a more generalized system, that redistributes elevation from all neighboring sub-plates based on changes in their distance. This would not just simplify the elevation calculations, but also allow us to model things like folding of plates further from the plates' boundary. But during initial testing, this appeared to be too chaotic. So, I stayed closer to the original paper for the initial implementation. &quot;&gt;[11]&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Moving the vertices sometimes causes self-intersections of the mesh, that have to be fixed by removing one of the vertices. Since all triangles are restricted to the sphere’s surface and have the same winding-order, these artifacts can be detected by checking if a face has been inverted, i.e. its normal points in the opposite direction than the sphere’s surface normal.&lt;/li&gt;
  &lt;li&gt;The vertices’ movement also frequently causes the Delaunay condition to be violated, which means we have to check and possibly restore it after each iteration. Since the number of violations is usually small, this is currently done by flipping edges that don’t pass the circumference-condition until the Delaunay triangulation is restored.&lt;/li&gt;
  &lt;li&gt;And finally, there are steps that create/destroy vertices, either because of rifting/subduction or because an area needs more/less detail and thus vertices per km².&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;#&quot; id=&quot;algorithm&quot; style=&quot;visibility: hidden&quot;&gt;[Algorithm-Outline]&lt;/a&gt;
After this high-level overview, here are the detailed steps each iteration of the simulation goes through, which the next couple of posts will look at in more detail:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add vertices where more details are needed and remove vertices unessential ones&lt;/li&gt;
  &lt;li&gt;Compare the properties of neighboring sub-plates to determine if they belong to the same tectonic plate or fall into one of the boundary zones discussed above (transform, divergent, convergent CC/OC/OO)&lt;/li&gt;
  &lt;li&gt;Create new oceanic crust at divergent boundaries&lt;/li&gt;
  &lt;li&gt;Calculate all forces acting on the sub-plates&lt;/li&gt;
  &lt;li&gt;Calculate the acceleration of all sub-plates, based on the forces acting on them, and utilize Verlet integration to calculate their new velocities and positions&lt;/li&gt;
  &lt;li&gt;Update each sub-plate’s elevation, if they are part of a subduction or collision zone&lt;/li&gt;
  &lt;li&gt;Check for collisions and resolve them&lt;/li&gt;
  &lt;li&gt;Check and restore Delaunay condition, if required &lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This step also searches for triangles whose height is much smaller or larger than their base and tries to normalize them, because triangles like this could negatively impact the simulation over time.&quot; data-title=&quot;This step also searches for triangles whose height is much smaller or larger than their base and tries to normalize them, because triangles like this could negatively impact the simulation over time.&quot;&gt;[12]&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;Simulate erosion&lt;/li&gt;
  &lt;li&gt;Combine colliding continental plates and split plates that have gotten too large&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;To summarize, the algorithm is heavily based on the 2019 paper &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02136820/file/2019-Procedural-Tectonic-Planets.pdf&quot;&gt;Procedural Tectonic Planets&lt;/a&gt; by Cortial et al., with the following noteworthy modifications:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The plates are not modelled as individual triangulation, but instead the complete planet forms a coherent triangle mesh, whose topology is continuously updated during the simulation. My hope here is that this will be more suitable for later simulations and allow for an easier integration with other parts of the system, even though it sadly makes the actual plate simulation a bit more difficult.&lt;/li&gt;
  &lt;li&gt;Since the paper’s focus is the interactions between plates, the implementation of the movement itself was sadly not describes in any detail. Because of this — and enabled by the different triangulation approach — I’ve chosen to model the plate’s movement similar to a particle-based soft-body simulation. This should also allow for more plastic behavior and interesting deformations of plates during collisions.&lt;/li&gt;
  &lt;li&gt;The last difference, that I’ll discuss in one of the following posts, is that collisions between plates are handled differently. Instead of resolving collisions immediately when they are detected, the different representation allows us to derive stresses at plate boundaries and ongoing collision and thus solve collisions over multiple iterations, which should (hopefully) generate more natural-looking results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That should be about it for this high-level overview of the plate simulation. And in the next post we’ll look in more detail at how the initial planet is generated, before the actual simulation starts.&lt;/p&gt;

</description>
                <link>https://second-system.de/2022/03/01/tectonics_1</link>
                <guid>https://second-system.de/2022/03/01/tectonics_1</guid>
                <pubDate>Tue, 01 Mar 2022 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>Modifying Meshes</title>
                <description>&lt;p&gt;This will (hopefully) be the last post about geometry and the Mesh-API, before we can start with the actual generation algorithms.&lt;/p&gt;

&lt;p&gt;We’ve already defined a basic data structure for our geometry, implemented algorithms to traverse it and, in the last post, constructed a spherical Delaunay triangulation of random points, distributed evenly on a sphere.&lt;/p&gt;

&lt;p&gt;So, the only part of the API that is still missing are functions to modify existing meshes. The three fundamental operations we’ll define for this are:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Mesh {
  public:
	// ...
	
	void   flip    (Edge e);
	Edge   split   (Edge e, float split_point);
	Vertex collapse(Edge e, float join_point);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;edge-flip&quot;&gt;Edge Flip&lt;/h2&gt;

&lt;p&gt;The first of the three operations is the edge flip, that we’ll need later to restore the Delaunay condition of our mesh after vertices have been moved&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;we will look at that in more detail later, but the basic algorithm we'll be using is: Calculate the circumcircle of each face, check if there is another vertex (of a neighboring face) that lies inside this circle and if there is one flip the edge we share with its face.&quot; data-title=&quot;we will look at that in more detail later, but the basic algorithm we'll be using is: Calculate the circumcircle of each face, check if there is another vertex (of a neighboring face) that lies inside this circle and if there is one flip the edge we share with its face.&quot;&gt;[1]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;The effect of an edge flip is relatively straightforward. It just takes the edge, that is passed to it, and rotates it counterclockwise in the quad formed by the four surrounding vertices, as seen below:&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/flip/initial.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/04/flip/initial.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Initial mesh before flip&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/flip/final.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/04/flip/final.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Mesh after &lt;code class=&quot;highlighter-rouge&quot;&gt;flip(e)&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;What we’ve ultimately done here is we removed the passed edge, to transform the two neighboring faces into a quad, and then created a new edge between the previously unconnected vertices to split it into two triangles again. From this, we also see that we can’t flip edges at the boundary of our mesh, since those only have a single “real” face.&lt;/p&gt;

&lt;p&gt;Our implementation is further simplified by the fact that we don’t need to handle multiple cases, like did for &lt;code&gt;add_face()&lt;/code&gt;. The reason is that there are only two possible ways to split a quad, which means the result is always unambiguous, regardless of which part of a quad-edge we pass as the argument. That means &lt;code&gt;flip(e)&lt;/code&gt;, &lt;code&gt;flip(e.rot())&lt;/code&gt;, &lt;code&gt;flip(e.sym())&lt;/code&gt; and &lt;code&gt;flip(e.inv_rot())&lt;/code&gt; are all equivalent, and we can just normalize the input with &lt;code&gt;e = e.base()&lt;/code&gt; and ignore all other cases, like flipping dual edges.&lt;/p&gt;

&lt;p&gt;As always, there are two parts to this operation: Updating the primal mesh and its dual counterpart:&lt;/p&gt;

&lt;h3 id=&quot;primal-mesh&quot;&gt;Primal Mesh&lt;/h3&gt;

&lt;p&gt;The update of the primal mesh is pretty straightforward. We just need to remove &lt;code&gt;e&lt;/code&gt; and &lt;code&gt;e.sym()&lt;/code&gt; from their old and add them to their new edge-rings.&lt;/p&gt;

&lt;p&gt;Because we need to traverse the mesh during our modification, we have to be careful that we only override the data &lt;em&gt;after&lt;/em&gt; we’ve read the required information. Therefore, we have to load and cache all values before we can write the new values to the &lt;code&gt;Mesh&lt;/code&gt; member variables. So to make our lives easier and the code more readable, we use &lt;code&gt;new_origin_next&lt;/code&gt; to memorize the new values and only apply them at the end using a simple class like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;template &amp;lt;typename Value, std::size_t N&amp;gt;
class Edge_changes {
  public:
	// Array-Access-Operator reserves space for the new value
	//   and returns a reference to it
	Value&amp;amp; operator[](Edge e) {
		assert(next_idx_ &amp;lt; int(new_values_.size()));
		auto&amp;amp; e = new_values_[next_idx_++];
		e.first = e.index();
		return e.second;
	}

	// Apply the recorded changes to the given vector
	void apply(std::vector&amp;lt;Value&amp;gt;&amp;amp; out) {
		for(int i = 0; i &amp;lt; next_idx_; i++) {
			auto&amp;amp;&amp;amp; [index, val] = new_values_[i];
			out[index]          = val;
		}
	}

  private:
	std::array&amp;lt;std::pair&amp;lt;uint32_t, Value&amp;gt;, N&amp;gt; new_values_;
	int                                       next_idx_ = 0;
};

// Later instanciated with
auto new_origin_next = Edge_changes&amp;lt;Edge, 6&amp;gt;{};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows us to write the following code, to update all affected primal edges:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Remove e and e.sym() from their current edge-rings
new_origin_next[e.origin_prev(mesh)]       = e.origin_next(mesh);
new_origin_next[e.sym().origin_prev(mesh)] = e.sym().origin_next(mesh);

// Add e to its new edge-ring
new_origin_next[e.dest_next(mesh)] = e;
new_origin_next[e]                 = e.origin_prev(mesh).sym();

// Add e.sym() to its new edge-ring
new_origin_next[e.origin_next(mesh).sym()] = e.sym();
new_origin_next[e.sym()]                   = e.dest_prev(mesh);

// ...

// Check if the original origin/dest vertices referenced the flipped edge
//   and set them to one of the remaining edges, if required
if(vertex_edges_[e.origin(mesh)] == e)
	vertex_edges_[e.origin(mesh)] = e.origin_next(mesh);
if(vertex_edges_[e.dest(mesh)] == e.sym())
	vertex_edges_[e.dest(mesh)] = e.sym().origin_next(mesh);

// Update the origin
primal_edge_origin_[e.index()] = e.origin_prev(mesh).dest(mesh);
primal_edge_origin_[e.sym().index()] = e.origin_next(mesh).dest(mesh);

// Apply the changes
new_origin_next.apply(primal_edge_next_);
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;dual-mesh&quot;&gt;Dual Mesh&lt;/h3&gt;

&lt;p&gt;Updating the dual mesh might look more complex at first because the flip also changes which faces are neighboring each other, but it’s actually simpler, because the edge-rings around faces are much more restricted. Since every face has exactly three outgoing edges and all flips are identical, we can just look at our diagram and see that we only need to swap two dual edges from the rings around &lt;code&gt;e.left()&lt;/code&gt; and &lt;code&gt;e.right()&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/flip/initial_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/04/flip/initial_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Initial mesh before flip, with the two edges of the dual mesh that have to be moved highlighted.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/flip/final_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/04/flip/final_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Mesh after the &lt;code class=&quot;highlighter-rouge&quot;&gt;flip(e)&lt;/code&gt;. The dual edge &lt;code class=&quot;highlighter-rouge&quot;&gt;de1&lt;/code&gt; has been moved to the left and &lt;code class=&quot;highlighter-rouge&quot;&gt;de2&lt;/code&gt; to the right face, after &lt;code class=&quot;highlighter-rouge&quot;&gt;e.rot()&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;e.inv_rot()&lt;/code&gt; respectively.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Move edge from left face to right face
const auto edge_from_left      = e.inv_rot().origin_next(mesh);
const auto edge_from_left_prev = e.rot().origin_prev(mesh);
new_dual_origin_next[edge_from_left]      = e.rot();
new_dual_origin_next[e.rot()]             = edge_from_left_prev;
new_dual_origin_next[edge_from_left_prev] = edge_from_left;

// Move edge from right face to left face
const auto edge_from_right      = e.rot().origin_next(mesh);
const auto edge_from_right_prev = e.inv_rot().origin_prev(mesh);
new_dual_origin_next[edge_from_right]      = e.inv_rot();
new_dual_origin_next[e.inv_rot())          = edge_from_right_prev;
new_dual_origin_next[edge_from_right_prev) = edge_from_right;

// ... Update the primal mesh, as seen above ...

// Set the edges for the modified faces to one of the edges
face_edges_[e.left(mesh)] = e;
face_edges_[e.right(mesh)] = e.sym();

// Update the origin
dual_edge_origin_[edge_from_left.index()]  = e.right(mesh);
dual_edge_origin_[edge_from_right.index()] = e.left(mesh);

// Apply the changes
new_dual_origin_next.apply(primal_edge_next_);
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;edge-split-and-collapse&quot;&gt;Edge Split and Collapse&lt;/h2&gt;

&lt;p&gt;One of the key advantages of representing our map as a triangle mesh, is that we can dynamically add/remove vertices to change the local resolution of our data. That means that we can use far fewer vertices in the middle of the ocean, where we don’t care about most terrain features, and use that storage for the parts that actually interest us instead.&lt;/p&gt;

&lt;p&gt;To achieve this, we need two operations, one to add a new vertex at a given point and one to remove it again:&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;While, in theory, these two function could be implemented to work with both primal and dual edges, we'll make our life much easier by working exclusively on the primal mesh in this case. &quot; data-title=&quot;While, in theory, these two function could be implemented to work with both primal and dual edges, we'll make our life much easier by working exclusively on the primal mesh in this case. &quot;&gt;[2]&lt;/sup&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Edge split(Edge e, float split_point)&lt;/code&gt;: Takes an edge and a position on this edge &lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;represented as a floating point number to interpolate between the origin (0.0) and destination (1.0) vertex &quot; data-title=&quot;represented as a floating point number to interpolate between the origin (0.0) and destination (1.0) vertex &quot;&gt;[3]&lt;/sup&gt; and splits the edge into two new edges, creating a new vertex at the given position and inserting edges and faces left/right of the edge, as required. The returned value is the new half of the split edge, pointing from the new vertex to the original destination.&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The more common variant of this operation is a vertex-split, that functions similarly but splits a vertex into two vertices, inserting an edge between them, instead of splitting an edge. But that would complicate the definition of the position where the split should occurs, which is important for us because our vertices store far more information than just their positions. &quot; data-title=&quot;The more common variant of this operation is a vertex-split, that functions similarly but splits a vertex into two vertices, inserting an edge between them, instead of splitting an edge. But that would complicate the definition of the position where the split should occurs, which is important for us because our vertices store far more information than just their positions. &quot;&gt;[4]&lt;/sup&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Vertex collapse(Edge e, float join_point)&lt;/code&gt;: Takes an edge and a position on it and collapses the edge, merging the &lt;code&gt;origin()&lt;/code&gt; and &lt;code&gt;dest()&lt;/code&gt; into the specified location, as well as merging its &lt;code&gt;left()&lt;/code&gt;/&lt;code&gt;right()&lt;/code&gt; faces and their supporting edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As can be seen in the following image, we can interpret the operations as each other’s inverse.&lt;/p&gt;

&lt;figure class=&quot;captioned_image&quot; style=&quot;max-width: 40em; width: calc(100% - 2em)&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/split_collapse/overview.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot; style=&quot;width: calc(100% - 2em)&quot;&gt;
		&lt;img style=&quot;float:none&quot; src=&quot;/assets/images/04/split_collapse/overview.png&quot; alt=&quot;example of splitting and collapsing an edge&quot; /&gt;
	&lt;/a&gt;
&lt;figcaption&gt;

    &lt;p&gt;Executing &lt;code class=&quot;highlighter-rouge&quot;&gt;split(e, 0.25f)&lt;/code&gt; transforms the left mesh into the right one, inserting a new vertex — 25% along the way between the origin and destination of e —, two faces (F&lt;sub&gt;L&lt;/sub&gt; and F&lt;sub&gt;R&lt;/sub&gt;) and three quad-edges.&lt;/p&gt;

    &lt;p&gt;Executing &lt;code class=&quot;highlighter-rouge&quot;&gt;collapse(e', 1.f)&lt;/code&gt; reverses this operation. The edge e’ is removed and its origin and destination are collapsed into a single vertex, that keeps the position of the destination (100% along the way from origin to destination).&lt;/p&gt;

  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While the implementation is a bit more intricate because we have more edges to consider, it follows the same procedure as &lt;code&gt;flip()&lt;/code&gt;, i.e. connect the edges into valid edge-rings. Thus, I won’t go into too much detail here and focus on the preconditions and how deletions are handled, instead.&lt;/p&gt;

&lt;h3 id=&quot;preconditions-for-split&quot;&gt;Preconditions for &lt;code&gt;split()&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The precondition of &lt;code&gt;split()&lt;/code&gt; is quite simple — simpler even than for &lt;code&gt;flip()&lt;/code&gt; — because it works on any edge with at least one face, which includes boundary edges. That is also the one special case we need to handle in the implementation: Are both faces present or are we splitting a boundary edge and the left/right face is missing.&lt;/p&gt;

&lt;h3 id=&quot;preconditions-for-collapse&quot;&gt;Preconditions for &lt;code&gt;collapse()&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;The situation for &lt;code&gt;collapse()&lt;/code&gt; is quite different, however. Although, it could also function with any valid edge with at least one face, collapsing some edges might create invalid meshes. What all of these invalid cases have in common, is that &lt;code&gt;collapse()&lt;/code&gt; would have to collapse more than the left/right faces, or it would leave unconnected edges/vertices behind. So, to detect them and avoid creating these corrupted meshes, there are two conditions we need to check first.&lt;/p&gt;

&lt;h4 id=&quot;1-the-link-condition&quot;&gt;1. The Link Condition&lt;/h4&gt;

&lt;p&gt;The first thing we have to check is that the edge we want to collapse satisfies the link condition, defined in the 1998 paper “Topology Preserving Edge Contraction” by Dr. Tamal Dey et al, that guarantees that our manifold mesh will still be manifold after the &lt;code&gt;collapse()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its formal definition is, given the edge &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;e&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.43056em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; between vertices &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/p&gt;

&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mtext&gt;one-ring&lt;/mtext&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;∩&lt;/mo&gt;&lt;mtext&gt;one-ring&lt;/mtext&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mtext&gt;one-ring&lt;/mtext&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\text{one-ring}(v_1) \cap \text{one-ring}(v_2) = \text{one-ring}(e)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;one-ring&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;∩&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;one-ring&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord text&quot;&gt;&lt;span class=&quot;mord&quot;&gt;one-ring&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

&lt;p&gt;Put simply, this means that every vertex that is connected to both &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; also has to be part of the face on the left or right side of &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;e&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.43056em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. When put like this, we can also see the problem, that would arise if we ignored this condition: After we collapsed &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;e&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.43056em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, there would be multiple distinct edges between the same two vertices, &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_1&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msub&gt;&lt;mi&gt;v&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;v_2&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.58056em;vertical-align:-0.15em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.30110799999999993em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;

&lt;figure class=&quot;captioned_image&quot; style=&quot;max-width: 40em; width: calc(100% - 2em)&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/04/link_condition_pre.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot; style=&quot;margin-left: auto; margin-right: auto&quot;&gt;
		&lt;img style=&quot;float:none&quot; src=&quot;/assets/images/04/link_condition_pre.png&quot; alt=&quot;example of an edge that doesn't satisfy the link condition&quot; /&gt;
	&lt;/a&gt;
&lt;figcaption&gt;

    &lt;p&gt;An example of an edge that doesn’t satisfy the link condition. The origin and destination of e are both connected to the vertex V&lt;sub&gt;P&lt;/sub&gt;, which means the intersection of their one-rings contains the vertex V&lt;sub&gt;P&lt;/sub&gt;, that is missing in the one-ring of e.&lt;br /&gt;This means that we would have to also collapse some of the edges and faces around V&lt;sub&gt;P&lt;/sub&gt; or produce a non-manifold mesh. Which is why this case is forbidden.&lt;/p&gt;

  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To check this condition, we can just iterate over the edge-ring around &lt;code&gt;e.origin()&lt;/code&gt; and check if one of its neighboring vertices is also a neighbor of &lt;code&gt;e.sym()&lt;/code&gt;. For this, we can utilize the &lt;code&gt;Edge::origin_ccw()&lt;/code&gt; method, that returns a range of all edge in the origin-edge-ring, and the &lt;code&gt;std::find_first_of&lt;/code&gt; algorithm, that tries to find the first matching element between two ranges. Of course, when iterating over the edge-rings we have to skip the first element, which is &lt;code&gt;e&lt;/code&gt; itself. But we also need to skip the edge after that, if there is a face on that side of &lt;code&gt;e&lt;/code&gt; because these edges are still part of the left/right face.&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Iterator ranges over the two edge-rings, starting from the edge after e (the edge we are collapsing)
// We also need to skip the vertex of the neighboring face, if there is one
const auto origin_edges = e.origin_ccw(mesh).skip(left ? 2 : 1);
const auto dest_edges   = e.sym().origin_ccw(mesh).skip(right ? 2 : 1);

// Check if there is an edge in the two ranges that share a common destination vertex
const auto iter = std::find_first_of(origin_edges.begin(),
                                     origin_edges.end_iterator(),
                                     dest_edges.begin(),
                                     dest_edges.end_iterator(),
                                     [&amp;amp;](Edge oe, Edge de) { return oe.dest(mesh) == de.dest(mesh); });

if(iter != origin_edges.end()) {
	// If there is such an edge the link condition is not satisfied
	yggdrasill::make_error(out_error, YGDL_ERROR_CODE_MESH_CORRUPTION, [&amp;amp;] {
		return &quot;The link-condition is not satisfied for the edge passed to collapse()&quot;;
	});
	return no_vertex;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;2-vertices-shared-by-unconnected-faces&quot;&gt;2. Vertices Shared by Unconnected Faces&lt;/h4&gt;

&lt;p&gt;While we can find most problematic cases by checking the link condition, there are still some we need to check separately, that are caused by our less restrictive mesh definition (e.g. unconnected sub-meshes, faces connected by only one vertex, unclosed meshes, …).&lt;/p&gt;

&lt;p&gt;One of these is the “forbidden case” we excluded in &lt;code&gt;add_face()&lt;/code&gt;, but now coming from the opposite direction. That is, we can’t collapse an edge if that would result in a vertex that is shared by multiple unconnected faces.&lt;/p&gt;

&lt;p&gt;We can identify this case by checking if there are multiple boundary edges originating from &lt;code&gt;e.origin()&lt;/code&gt; or &lt;code&gt;e.dest()&lt;/code&gt; that are missing a face on the same side. Coincidentally, this also catches another minor error-case: When we collapse the last face of an unconnected sub-mesh, we would leave behind vertices without edges.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;if(e.left(mesh) != no_face &amp;amp;&amp;amp; e.right(mesh) != no_face) {
	// We only need to check this, if e is not already a boundary edge

	// We want to check if there are any edges in the given range, that don't have a right face
	auto has_right_boundary = [&amp;amp;](const auto&amp;amp; edge_range) {
		return std::any_of(edge_range.begin(), edge_range.end_iterator(),
		                   [&amp;amp;](Edge e) {return e.right(mesh) == no_face;} );
	};
	
	// ... and we want to check if that is the case around both the origin and destination of e 
	if(has_right_boundary(e.origin_ccw(mesh)) &amp;amp;&amp;amp; has_right_boundary(e.sym().origin_ccw(mesh))) {
		yggdrasill::make_error(out_error, YGDL_ERROR_CODE_MESH_CORRUPTION, [&amp;amp;] {
			return &quot;Requested edge collapse would cause a corrupted mesh (multiple unconnected faces &quot;
			       &quot;on the same vertex) or a dangling vertex&quot;;
		});
		return no_vertex;
	}

} else if(e.left(mesh) == no_face &amp;amp;&amp;amp; e.right(mesh) == no_face) {
	// The edge e is not part of any faces, which means the initial mesh is not valid to begin with
	yggdrasill::make_error(out_error, YGDL_ERROR_CODE_MESH_CORRUPTION, [&amp;amp;] {
		return &quot;Corrupted mesh. The edge that should be collapsed is not part of a triangle&quot;;
	});
	return no_vertex;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;handling-deletions&quot;&gt;Handling Deletions&lt;/h3&gt;

&lt;p&gt;Although, we now know how our data structure needs to change on &lt;code&gt;split()&lt;/code&gt; and &lt;code&gt;collapse()&lt;/code&gt;, there is one point we glossed over, which we briefly touched on when we first defined our mesh structure: Deleting elements of our mesh (vertices, faces and edges)&lt;/p&gt;

&lt;p&gt;As we have seen above, collapsing an edge effectively deletes it, as well as its destination, bordering faces and some of their edges. But that is a problem because we can’t just directly delete part of our &lt;code&gt;Mesh&lt;/code&gt; structure since we store all data of our elements in continuous vectors. So if we would just delete e.g. an edge from this structure, we would need to move all following values to close the gap and keep everything contiguous. However, that would not just be extremely inefficient for larger meshes, but also disturb our IDs. Remember that the ID of an element is also the index in the corresponding data-vector&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Or at least derived from it, in case of edges.&quot; data-title=&quot;Or at least derived from it, in case of edges.&quot;&gt;[5]&lt;/sup&gt;. So if the index would change, that in turn would also change its ID, which we use &lt;em&gt;everywhere&lt;/em&gt; to reference it. Which means we can’t do that and need an alternative solution.&lt;/p&gt;

&lt;p&gt;Luckily, we already reserved one special ID for each of the element types, to represent a missing or invalid element (&lt;code&gt;no_edge&lt;/code&gt;/&lt;code&gt;no_vertex&lt;/code&gt;/&lt;code&gt;no_face&lt;/code&gt;), which we can utilize now. So, when we have to delete an element, we will instead memorize its ID in a vector (called a free-list) and set all its values in the &lt;code&gt;Mesh&lt;/code&gt; to this invalid state. And then when we later need to create new vertices, edges or faces, we can check this free-list first before allocating new storage, to fill in these “holes” the deletions left behind.&lt;/p&gt;

&lt;p&gt;Of course, if we never really delete elements but just mark them as deleted, we must take care that they are never referenced or returned from any of our function. Following the algorithm outlined above, there already should never be any part of the mesh that references the vertices/edges/faces that were deleted, since these references were overridden by new values. So, the traversal operations should already work as expected, without any further work from our part. But where we need to do extra work is when we iterate over &lt;em&gt;all&lt;/em&gt; vertices/faces/edges in the mesh. These operations would normally just iterate over all indices and return the corresponding IDs. Thus, we must add an extra check in the iterator, that reads one of the elements values from &lt;code&gt;Mesh&lt;/code&gt; and checks whether it is set to a valid value.&lt;/p&gt;

&lt;p&gt;Although, this means extra work on a relatively common operation, a linear read and comparison of 32-Bit integers should not impact us much&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;And some algorithms might even safely skip this check, as long as they only write data to the invalid cell and don't affect other parts.&quot; data-title=&quot;And some algorithms might even safely skip this check, as long as they only write data to the invalid cell and don't affect other parts.&quot;&gt;[6]&lt;/sup&gt;. Hence, at least for the expected use-case, where meshes don’t shrink over time&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;So that only relatively few elements are marked as deleted at any time&quot; data-title=&quot;So that only relatively few elements are marked as deleted at any time&quot;&gt;[7]&lt;/sup&gt;, this should be pretty efficient.&lt;/p&gt;

&lt;h2 id=&quot;handling-topological-changes-in-data-layers&quot;&gt;Handling Topological Changes in Data Layers&lt;/h2&gt;

&lt;p&gt;Before we can (finally) close this chapter, there is one last aspect that deserves attention: How the data layers react to changes&lt;/p&gt;

&lt;p&gt;As already mentioned, we’ll store all our data in layers that bind values of various types to parts of our mesh (vertices, faces and directed or undirected edges of the primal/dual mesh). So, when we modify the mesh itself, that will obviously affect the layer values for elements we added or deleted. But it might also invalidate information that was used to compute the values, e.g. if we &lt;code&gt;flip()&lt;/code&gt; an edge no parts of the mesh are deleted, but some edges will be connected to entirely different vertices.&lt;/p&gt;

&lt;p&gt;Hence, we’ll need a way to automatically modify layers if the mesh is changed, which is actually one of the reasons why we’ve defined the overarching &lt;code&gt;World&lt;/code&gt; structure that contains both the mesh and all layers. So, when one of the modification operations of the mesh is called, we can just access the layers through our parent &lt;code&gt;World&lt;/code&gt;, lock them for modification and apply any necessary changes to them.&lt;/p&gt;

&lt;p&gt;Of course, that begs the question of &lt;em&gt;what&lt;/em&gt; changes would be necessary. When we modify the mesh, we already know which parts of the mesh will be affected in what way. And through the &lt;code&gt;World&lt;/code&gt; class and the &lt;code&gt;Layer_info&lt;/code&gt; objects stored there, we can also determine &lt;em&gt;which&lt;/em&gt; layers contain values that are linked to these affected elements. But the decision &lt;em&gt;how&lt;/em&gt; values should be changed depends on the semantics of the concrete layer, which we’ll store in the &lt;code&gt;Layer_info&lt;/code&gt; as two enumerations, one for each major &lt;em&gt;type&lt;/em&gt; of topological change&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Since I've currently only worked on simulating plate tectonics, the behavior and possible values are based solely on the requirements of that (as well as some educated guesses) and an incomplete understanding of the problem domain, and are as such obviously subject to future change. &quot; data-title=&quot;Since I've currently only worked on simulating plate tectonics, the behavior and possible values are based solely on the requirements of that (as well as some educated guesses) and an incomplete understanding of the problem domain, and are as such obviously subject to future change. &quot;&gt;[8]&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id=&quot;deletion-and-modification&quot;&gt;Deletion and Modification&lt;/h3&gt;

&lt;p&gt;The first (and easier) of these two covers the case when data is completely invalidated. Either because the part of the mesh that is referenced has been deleted or because the &lt;code&gt;origin()&lt;/code&gt;/&lt;code&gt;dest()&lt;/code&gt; of an edge changed. And the enum that describes the possible reactions is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class On_mesh_change {
	keep,            // The value is not changed
	reset_affected,  // The value is reset to its initial value (normally 0)
	reset_all,       // ALL values of the layer are reset
	remove_layer     // The layer is deleted entirely
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;creation-and-merging&quot;&gt;Creation and Merging&lt;/h3&gt;

&lt;p&gt;The second enumeration is a &lt;em&gt;tad&lt;/em&gt; more complicated because it’s not used for invalidated or removed elements, but to calculate entirely new values.&lt;/p&gt;

&lt;p&gt;This one is used every time a new element is inserted (new vertex, faces and edges) by &lt;code&gt;split()&lt;/code&gt;, to interpolate its value based on its two neighbors&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Or only a single neighbor for both sides of the interpolation, if there is no meaningful second neighbor, e.g. for the new faces or the new central edge on a split&quot; data-title=&quot;Or only a single neighbor for both sides of the interpolation, if there is no meaningful second neighbor, e.g. for the new faces or the new central edge on a split&quot;&gt;[9]&lt;/sup&gt;, or when &lt;code&gt;collapse()&lt;/code&gt; merged the two vertices of an edge into one&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The faces and edges are not merged but either remain unchanged or are removed/invalidated, so they are not affected by this setting. &quot; data-title=&quot;The faces and edges are not merged but either remain unchanged or are removed/invalidated, so they are not affected by this setting. &quot;&gt;[10]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class Interpolation {
	dont_care,
	reset_affected,
	reset_all,
	remove_layer,
	keep_src,
	keep_dest,
	lerp,
	slerp,
	min_value,
	max_value,
	min_weight,
	max_weight
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because the behavior might not be obvious from their names alone, it’s perhaps better to show what their result would be for different inputs&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;... means the value doesn't matter for the result, i.e. is ignored, or is undefined in the result&quot; data-title=&quot;... means the value doesn't matter for the result, i.e. is ignored, or is undefined in the result&quot;&gt;[11]&lt;/sup&gt;:&lt;/p&gt;

&lt;div style=&quot;overflow: auto hidden&quot;&gt;
  &lt;div style=&quot;min-width:46em&quot;&gt;

    &lt;table class=&quot;bordered&quot;&gt;
      &lt;thead&gt;
        &lt;tr&gt;
          &lt;th&gt;Name&lt;/th&gt;
          &lt;th&gt;Description&lt;/th&gt;
          &lt;th&gt;Src&lt;/th&gt;
          &lt;th&gt;Dest&lt;/th&gt;
          &lt;th&gt;Weight&lt;/th&gt;
          &lt;th&gt;Result&lt;/th&gt;
        &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;dont_care&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Don’t set the value&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;reset_affected&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Reset to initial value (usually 0)&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.0&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;reset_all&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Reset all values in the layer&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.0&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;remove_layer&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Remove the entire layer&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;keep_src&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Always keep the source value&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;keep_dest&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Always keep the destination value&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;lerp&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Linearly interpolate between the two values&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
          &lt;td&gt;0.5&lt;/td&gt;
          &lt;td&gt;0.1+0.5*(0.9-0.1) = 0.5&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;slerp&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Linearly interpolate on a sphere &lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This works by first linearly interpolating the two values, normalizing the result and multiplying it with the linearly interpolated length of the two initial values. Because of this, this value only makes sense for vectors (Vec2 and Vec3) &quot; data-title=&quot;This works by first linearly interpolating the two values, normalizing the result and multiplying it with the linearly interpolated length of the two initial values. Because of this, this value only makes sense for vectors (Vec2 and Vec3) &quot;&gt;[12]&lt;/sup&gt;&lt;/td&gt;
          &lt;td&gt;(1,0,0)&lt;/td&gt;
          &lt;td&gt;(0,1,0)&lt;/td&gt;
          &lt;td&gt;0.5&lt;/td&gt;
          &lt;td&gt;(0.71, 0.71, 0)&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;min_value&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Keep the smaller of the two values&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;max_value&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Keep the larger of the two values&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;min_weight&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Keep the value with the smallest weight&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.6&lt;/td&gt;
          &lt;td&gt;0.1&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;max_weight&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Keep the value with the largest weight&lt;/td&gt;
          &lt;td&gt;…&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
          &lt;td&gt;0.6&lt;/td&gt;
          &lt;td&gt;0.9&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;

  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And finally, here are some examples that show how this functionality will be used in practice:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// The layer that stores the vertex positions.
// Since all points have to be located on the surface of a sphere, 
//   the interpolation has to make sure that they stay there
constexpr auto position_info  = Layer_info&amp;lt;Vec3, Layer_type::vertex&amp;gt;(&quot;position&quot;)
								.interpolation(Interpolation::slerp)
								.on_mesh_change(On_mesh_change::reset_affected);
								
// Each vertex is also assigned to a specific tectonic plate, by giving it an ID.
// Since we can't interpolate between IDs, we instead pick the one of the nearest vertex
constexpr auto layer_plate_id = Layer_info&amp;lt;std::int32_t, Layer_type::vertex&amp;gt;(&quot;plate_id&quot;)
								.interpolation(Interpolation::max_weight)
								.on_mesh_change(On_mesh_change::reset_affected);
                                        
// We also need to memorize the distance between sub-plates/vertices.
// These are assigned to the edges between vertices (independent of their direction).
// But if the topology changes we can't recompute them with the logic outlined above,
//   so instead they are reset to a known value (-1.0) and recomputed as needed.
constexpr auto layer_distance = Layer_info&amp;lt;float, Layer_type::edge_primal&amp;gt;(&quot;plate_distance&quot;)
								.initial(-1.f)
								.on_mesh_change(On_mesh_change::reset_affected)
								.interpolation(Interpolation::reset_affected);
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;That should be all fundamentals we require for now, and our current architecture looks something like this:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;We have a &lt;code&gt;Mesh&lt;/code&gt; class that describes the topology of our generated world as a Delaunay triangulation, in terms of vertices, faces and edges — i.e. a set of places and information about how they are connected — and allows us to traverse and modify it&lt;/li&gt;
  &lt;li&gt;All the actual data is stored in &lt;code&gt;Layer&lt;/code&gt; objects — whose properties are defined in &lt;code&gt;Layer_info&lt;/code&gt; structures — that contain things like the position of our vertices, their elevation above sea level, their temperature, …&lt;/li&gt;
  &lt;li&gt;Both of these are combined into a &lt;code&gt;World&lt;/code&gt; class that manages them, e.g. resizes and modifies layers if the topology changes&lt;/li&gt;
  &lt;li&gt;The code that actually generates the world consists of independent modules that are sequentially invoked on the same &lt;code&gt;World&lt;/code&gt; object to incrementally fill it with additional information and advance the simulation
    &lt;ul&gt;
      &lt;li&gt;So, the only way for these modules to interact with each other is through layers or modifying the mesh itself&lt;/li&gt;
      &lt;li&gt;These will also be the main focus of the next posts, where we’ll dive into the actual plate simulation&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</description>
                <link>https://second-system.de/2021/05/15/mesh_modifications</link>
                <guid>https://second-system.de/2021/05/15/mesh_modifications</guid>
                <pubDate>Sat, 15 May 2021 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>World mesh creation</title>
                <description>&lt;p&gt;In the last post we’ve looked at how we can store our geometry, how we can traverse the resulting mesh and how we’ll later store our actual data. But one aspect we haven’t looked at yet is how such a mesh data structure can be constructed in the first place, i.e. how we get from a soup of triangle faces to a quad-edge mesh.&lt;/p&gt;

&lt;p&gt;There are two basic operations defined in the original paper to construct and modify meshes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;MakeEdge: Creates a new edge &lt;code&gt;e&lt;/code&gt; and its corresponding quad-edge&lt;/li&gt;
  &lt;li&gt;Splice: Takes two edges &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; and either connects them or splits them apart, if they are already connected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these two operations are powerful enough to construct any quad-edge mesh and apply any modification we might want, they are not ideal for our use-case. First they are rather more complex to use, than I would like for everyday use because they require quite a bit of knowledge about topology and use concepts that are not as easy to visualize as faces in a triangle mesh. And secondly, they allow for structures that we don’t actually want to support, like non-triangular faces.&lt;/p&gt;

&lt;p&gt;So, what we will do instead is define our own operations, to make our use case as easy as possible. In this post, we will first look at just the construction of a new mesh, for which we’ll define two functions:
&lt;!--more--&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Mesh {
  public:
	// Create a new vertex, that is not connected to anything (i.e. its vertex_edge is no_edge)
	Vertex add_vertex();
	
	// Connect the three passed vertices into a new triangle face (in counterclockwise order)
	Face   add_face(Vertex a, Vertex b, Vertex c);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;While these operations are much easier to use, their implementation is also quite a bit more complex. So, to make this less theoretical, we’ll first take a look at how they will be used to construct a spherical mesh for our world, before we get into the implementation details.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-sphere&quot;&gt;Creating a Sphere&lt;/h2&gt;

&lt;p&gt;As stated in the first post, I want to initially focus on generating spherical worlds. The first step of this will be generating and simulating tectonic plates.
Of course, plate tectonics in the real world are much more complex than I can hope to simulate. But I’m not aiming for perfect, just close enough to &lt;em&gt;look&lt;/em&gt; realistic, and I’m mostly interested in the high-level features like mountain ranges and not so much in the complex formation, layering and folding of plates.&lt;/p&gt;

&lt;p&gt;My simplified model will primarily be based on the work of &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02136820/file/2019-Procedural-Tectonic-Planets.pdf&quot;&gt;Cortial et al.&lt;/a&gt;, deviating on some details where it suits my specific goals or where the paper didn’t specify enough details.&lt;/p&gt;

&lt;p&gt;The basic approach is modelling tectonic plates as a set of points on the surface of a sphere, not dissimilar to particle based fluid simulations. So, the first step is to evenly distribute a number of sampling points on the surface and then partition them into individual plates. By doing this, we simplified a complicated 3D phenomenon into a manageable 2D one, which means that we’ll lose many of the complex details — like folding of plates — but terrain features like mountain ranges and coastlines should hopefully still be retained.&lt;/p&gt;

&lt;p&gt;Not entirely coincidentally, this model maps quite well to the triangle mesh we’ve defined so far. Each of our vertices will be a sampling point — representing the properties of the voronoi cell around it — and connected through edges to its nearest neighbors. This will also simplify the simulation because when we need to calculate the interactions with every neighboring voronoi cell, we just need to iterate over all neighbors of the vertex.&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/UV_sphere.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/UV_sphere.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;UV Sphere&lt;sup&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://commons.wikimedia.org/wiki/File:UV_unwrapped_sphere.png&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/cube_sphere.jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/cube_sphere.jpg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Cube Sphere&lt;sup&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://catlikecoding.com/unity/tutorials/cube-sphere/&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/subdivided_icosahedron.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/subdivided_icosahedron.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Subdivided Icosahedron&lt;sup&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://en.wikipedia.org/wiki/File:Geodesic_icosahedral_polyhedron_example.png&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So first, we must distribute our vertices (i.e. sampling points) on the sphere’s surface. To have the ideal starting conditions for the simulation algorithms, the points should be evenly distributed on the surface. That means that the distance between the two closest vertices should be as large as possible. Perhaps surprisingly, evenly distributing points on a sphere is a quite difficult problem. But there are several common ways to define such spherical meshes, the most common of which are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;UV Sphere&lt;/strong&gt;: Uses rings of square faces to cut up the sphere, similar to the latitude and longitude lines used in cartography. While it approximates the surface of a sphere pretty well, the distribution of vertices is very uneven, especially at the poles and equator&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Cube Sphere&lt;/strong&gt;: Starts with a cube, whose faces are then tessellated, and the vertices are projected onto the surface of the sphere. The nice property of this mapping is that it enables an easy mapping of the sphere back onto the flat cube surfaces. But while it’s less unevenly distributed than UV-Spheres, the distribution is still far from uniform.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Subdivided Icosahedron&lt;/strong&gt;: This is probably the most common way to define a sphere mesh with evenly distributed vertices. The shape starts as an icosahedron with 12 vertices that loosely approximates the sphere. To get a better approximation, the next step is to subdivide its triangular faces into smaller triangles and then project each vertex onto the sphere’s surface. After a couple such subdivisions, this approximation gets relatively close to a real sphere, while the uniform distance between its vertices is conserved, which would make it a nice option for us. But a limitation of this approach is, that the number of vertices is defined by the number of subdivisions and increases quadratically (12, 42, 162, 642, 2562, …), which limits our options for the initial resolution more than I would prefer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br style=&quot;clear: both&quot; /&gt;&lt;/p&gt;

&lt;figure class=&quot;captioned_image float_right&quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot; autoplay=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_points.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Rotating Fibonacci Sphere&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The subdivided Icosahedron would probably work for us, but I don’t particularly like the restrictive vertex count options forced upon us by the recursive subdivision. Hence, we’ll instead use Fibonacci Spheres, which have a similar nice uniform distribution, but are not limited to particular vertex counts.&lt;/p&gt;

&lt;p&gt;I won’t go into too much detail about how they are computed, as &lt;a href=&quot;http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/&quot;&gt;others&lt;/a&gt; have already covered that far better than I probably could. But to put it simply, they utilize the golden spiral (aka the Fibonacci spiral) to uniformly distribute points on a surface. This is similar to the way &lt;a href=&quot;https://www.youtube.com/watch?v=1Jj-sJ78O6M&quot;&gt;some plants distribute their leaves or seeds&lt;/a&gt;, by placing each point &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;ϕ&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;2\pi \cdot (2-\phi)&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.64444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;π&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ϕ&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; radians&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;= approx. 137.507... degrees&quot; data-title=&quot;= approx. 137.507... degrees&quot;&gt;[1]&lt;/sup&gt; further along a spiral path from the center, where &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;ϕ&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\phi&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.8888799999999999em;vertical-align:-0.19444em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ϕ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; is the golden ratio. So for &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.68333em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; points in a 2D spherical coordinate system we would use something like:&lt;/p&gt;

&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;ϕ&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mfrac&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mfrac&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;(\theta, r)_i = (i \cdot 2\pi \cdot (2\phi), \frac{i}{N})&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;θ&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:1em;vertical-align:-0.25em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.64444em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;π&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:2.02252em;vertical-align:-0.686em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ϕ&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.3365200000000002em;&quot;&gt;&lt;span style=&quot;top:-2.314em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.677em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.686em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

&lt;p&gt;Now all that is left to use this on the surface of our sphere is to extend it from 2D spherical coordinates to 3D Cartesian coordinates. This can be done by cutting the sphere into circles along the Y-axis, by deriving the Y coordinate directly from the current index &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;i&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.65952em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;. Based on the radius of the sphere, we can then derive the radius of the current circle and calculate X and Z from the angle given by the equation above. So for the unit sphere with radius 1 and &lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:0.68333em;vertical-align:0em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; points we get:&lt;/p&gt;

&lt;span class=&quot;katex-display&quot;&gt;&lt;span class=&quot;katex&quot;&gt;&lt;span class=&quot;katex-mathml&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot; display=&quot;block&quot;&gt;&lt;semantics&gt;&lt;mtable rowspacing=&quot;0.2500em&quot; columnalign=&quot;right left&quot; columnspacing=&quot;0em&quot;&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;/mrow&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mfrac&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/mrow&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;/mrow&gt;&lt;/mfrac&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;msub&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;/mrow&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;msqrt&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;msubsup&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msubsup&gt;&lt;/mrow&gt;&lt;/msqrt&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;msub&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;/mrow&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;π&lt;/mi&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;−&lt;/mo&gt;&lt;mi&gt;ϕ&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;mtr&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;x&lt;/mi&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;z&lt;/mi&gt;&lt;msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;mtd&gt;&lt;mstyle scriptlevel=&quot;0&quot; displaystyle=&quot;true&quot;&gt;&lt;mrow&gt;&lt;mrow&gt;&lt;/mrow&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;mi&gt;c&lt;/mi&gt;&lt;mi&gt;o&lt;/mi&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;y&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo separator=&quot;true&quot;&gt;,&lt;/mo&gt;&lt;mi&gt;s&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;mi&gt;n&lt;/mi&gt;&lt;mo stretchy=&quot;false&quot;&gt;(&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;θ&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;mo&gt;⋅&lt;/mo&gt;&lt;msub&gt;&lt;mi&gt;r&lt;/mi&gt;&lt;mi&gt;i&lt;/mi&gt;&lt;/msub&gt;&lt;mo stretchy=&quot;false&quot;&gt;)&lt;/mo&gt;&lt;/mrow&gt;&lt;/mstyle&gt;&lt;/mtd&gt;&lt;/mtr&gt;&lt;/mtable&gt;&lt;annotation encoding=&quot;application/x-tex&quot;&gt;\begin{align*}
y_i &amp;amp;= 1 - \frac{2i}{N-1}\\

r_i &amp;amp;= \sqrt{1-y_i^2} \\

\theta_i &amp;amp;= i \cdot 2\pi \cdot (2-\phi) \\\\

(x,y,z)_i &amp;amp;= (cos(\theta_i) \cdot r_i, y_i, sin(\theta_i) \cdot r_i)
\end{align*}&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class=&quot;katex-html&quot; aria-hidden=&quot;true&quot;&gt;&lt;span class=&quot;base&quot;&gt;&lt;span class=&quot;strut&quot; style=&quot;height:9.045849999999998em;vertical-align:-4.272924999999999em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mtable&quot;&gt;&lt;span class=&quot;col-align-r&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:4.772925em;&quot;&gt;&lt;span style=&quot;top:-6.772925em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.410198em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-2.723595000000001em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;θ&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-1.2235950000000013em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:0.2764049999999987em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.04398em;&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:0em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:4.272924999999999em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;col-align-l&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:4.772925em;&quot;&gt;&lt;span style=&quot;top:-6.772925em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mopen nulldelimiter&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mfrac&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.3365200000000002em;&quot;&gt;&lt;span style=&quot;top:-2.314em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.10903em;&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.23em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;frac-line&quot; style=&quot;border-bottom-width:0.04em;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.677em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.7693300000000001em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose nulldelimiter&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-4.410198em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord sqrt&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:1.2933970000000001em;&quot;&gt;&lt;span class=&quot;svg-align&quot; style=&quot;top:-3.8em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot; style=&quot;padding-left:1em;&quot;&gt;&lt;span class=&quot;mord&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.7959080000000001em;&quot;&gt;&lt;span style=&quot;top:-2.4231360000000004em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.0448000000000004em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mtight&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.27686399999999994em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-3.2533969999999997em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.8em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;hide-tail&quot; style=&quot;min-width:1.02em;height:1.8800000000000001em;&quot;&gt;&lt;svg width='400em' height='1.8800000000000001em' viewBox='0 0 400000 1944' preserveAspectRatio='xMinYMin slice'&gt;&lt;path d='M983 90
l0 -0
c4,-6.7,10,-10,18,-10 H400000v40
H1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7
s-12,0,-12,0c-1.3,-3.3,-3.7,-11.7,-7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744
c-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30
c26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722
c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5
c53.7,-170.3,84.5,-266.8,92.5,-289.5z
M1001 80h400000v40h-400000z'/&gt;&lt;/svg&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.546603em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:-2.723595000000001em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;π&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;−&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;ϕ&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;top:0.2764049999999987em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:3.33652em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mrel&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2777777777777778em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;cos&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;θ&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.03588em;&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.03588em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mpunct&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.16666666666666666em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;mord mathnormal&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;mopen&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;θ&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mbin&quot;&gt;⋅&lt;/span&gt;&lt;span class=&quot;mspace&quot; style=&quot;margin-right:0.2222222222222222em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;mord&quot;&gt;&lt;span class=&quot;mord mathnormal&quot; style=&quot;margin-right:0.02778em;&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;msupsub&quot;&gt;&lt;span class=&quot;vlist-t vlist-t2&quot;&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.31166399999999994em;&quot;&gt;&lt;span style=&quot;top:-2.5500000000000003em;margin-left:-0.02778em;margin-right:0.05em;&quot;&gt;&lt;span class=&quot;pstrut&quot; style=&quot;height:2.7em;&quot;&gt;&lt;/span&gt;&lt;span class=&quot;sizing reset-size6 size3 mtight&quot;&gt;&lt;span class=&quot;mord mathnormal mtight&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:0.15em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;mclose&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-s&quot;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;vlist-r&quot;&gt;&lt;span class=&quot;vlist&quot; style=&quot;height:4.272924999999999em;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;

&lt;p&gt;Combining all this, the code that generates our vertex positions looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Define the data layer that contains the vertex positions in 3D Cartesian coordinates
constexpr auto position_info = Layer_info&amp;lt;Vec3, Layer_type::vertex&amp;gt;(&quot;position&quot;);

// Acquire all necessary resources from the world structure
auto mesh        = world.lock_mesh();
auto [positions] = world.lock_layer(position_info);
auto rand        = world.lock_random();

// Create N vertices
mesh-&amp;gt;add_vertices(vertex_count);

// Pre-calculate the golden angle and the step-size for calculating y,
//   so we just need to multiply them with i in the loop
constexpr auto golden_angle = 2.f * std::numbers::pi_v&amp;lt;float&amp;gt; * (2.f - std::numbers::phi_v&amp;lt;float&amp;gt;);
const auto     step_size    = 2.f / (vertices - 1);

for(std::int32_t i = 0; i &amp;lt; vertices; i++) {
	// Calculate the x/y/z position of the current vertex on the unit sphere
	const auto y     = 1.f - step_size * i;
	const auto r     = std::sqrt(1 - y * y);
	const auto theta = golden_angle * i;
	const auto x     = std::cos(theta) * r;
	const auto z     = std::sin(theta) * r;

	// Set the vertex position (multiplying with the radius of our sphere)
	positions[Vertex(i)] = Vec3{x, y, z} * radius;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Until now, we only defined our vertices as a cloud of points without any connectivity. So, the next step is to compute the Delaunay triangulation of these points. There are again a couple of ways to achieve this, but the easiest is to utilize the fact that the Delaunay triangulation of points on the surface of a sphere is identical to the convex hull of said points. Thus, all that’s left to do is import &lt;a href=&quot;https://github.com/akuukka/quickhull&quot;&gt;a library that computes the convex hull&lt;/a&gt;. The library already gives us a list of faces, that we just need to pass onto the &lt;code&gt;add_face()&lt;/code&gt; function of &lt;code&gt;Mesh&lt;/code&gt;&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;There is one special case we are ignoring here: The faces from quickhull can be in any order, but as we'll see below there are cases that add_face can't handle. To fix this ordering problem, we simply memorize all faces that couldn't be added and retry afterwards, until the mesh is complete.&quot; data-title=&quot;There is one special case we are ignoring here: The faces from quickhull can be in any order, but as we'll see below there are cases that add_face can't handle. To fix this ordering problem, we simply memorize all faces that couldn't be added and retry afterwards, until the mesh is complete.&quot;&gt;[2]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;quickhull::QuickHull&amp;lt;float&amp;gt; qh;
auto  hull    = qh.getConvexHull(&amp;amp;positions.begin()-&amp;gt;x, positions.size(), false, true);
auto&amp;amp; indices = hull.getIndexBuffer();

for(std::size_t i = 0; i &amp;lt; indices.size(); i += 3) {
	mesh-&amp;gt;add_face(indices[i], indices[i+1], indices[i+2]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;As can be seen in the images below, the vertices are distributed evenly on the surface … perhaps too evenly. In some cases we might want a more natural and less uniform look. This can be achieved easily by adding a small random offset (&lt;code&gt;0 &amp;lt;= offset &amp;lt; 1.0&lt;/code&gt;) to the loop variable &lt;code&gt;i&lt;/code&gt; before it’s used in the calculations:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const auto offset = perturbation &amp;gt; 0 ? rand-&amp;gt;uniform(0.f, perturbation) : 0.f;
auto       ii    = std::min(i + offset, vertices - 1.f);
// then use ii instead of i in the body of the loop
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image &quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_triangles.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Wireframe of the generated mesh&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image &quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_shaded.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Shaded rendering of the same sphere&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image &quot;&gt;
	&lt;div class=&quot;open_img&quot;&gt;
	&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; controls=&quot;&quot;&gt;
		&lt;source src=&quot;/assets/images/03/sphere_noise.webm&quot; type=&quot;video/webm&quot; /&gt;
	&lt;/video&gt;
	&lt;/div&gt;
  &lt;figcaption&gt;Sphere with more vertices and a small random perturbation&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h2 id=&quot;implementing-add_face&quot;&gt;Implementing add_face&lt;/h2&gt;

&lt;p&gt;As said above, implementing this operation is not as straightforward as one might hope. The main problem is that triangles could be added in (nearly) any order, and we need to update the connectivity information correctly for all possible cases. Which boils down to updating the &lt;code&gt;origin_next(e)&lt;/code&gt; references for all modified primal and dual edges, to preserve valid edge-rings, as visualized below.&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/edge_ring_primal.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/edge_ring_primal.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;origin_next(e)&lt;/code&gt; for primal edges must always form an edge-ring that contains all primal edges with the same origin vertex, in counterclockwise order.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/edge_ring_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/edge_ring_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Similarly, &lt;code class=&quot;highlighter-rouge&quot;&gt;origin_next(e)&lt;/code&gt; for dual edges must form an edge-ring counterclockwise around a face.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/edge_ring_dual_boundary.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/edge_ring_dual_boundary.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;A special case for dual edges are boundaries of unclosed geometry. At boundaries, edges miss either their left or right face, which is normally not allowed by the data structure. To bypass this restriction, we treat the outside of our shape as a single imaginary face (the only face that doesn't have to be triangular) with the highest possible ID (all bits are 1). Of course, this face also has an edge-ring, consisting of the &lt;code class=&quot;highlighter-rouge&quot;&gt;rot(e)&lt;/code&gt; of every boundary edge. While the order of the edges looks clockwise here, it's technically still counterclockwise, when seen from the perspective of the imaginary boundary face.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;figure class=&quot;captioned_image fill_black float_right half_size&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/forbidden_case.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/forbidden_case.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Forbidden case: The central vertex is already used by two faces, that are not connected by another face. When we want to add a new face that is not connected to one of the existing faces, we can't decide if it should be inserted at the top (A) or bottom (B).&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Because our mesh implementation doesn’t know about the position of vertices, we need to enforce one additional restriction on valid topologies: If multiple unconnected faces share a vertex, a new face can only be added to that vertex, if it also shares at least one edge with one of the existing faces. The problem we solve with this restriction is, that we have to know the order of the faces around a vertex, to be able to insert the edges at the correct positions in their new edge-rings. This ambiguity could&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;and normally is&quot; data-title=&quot;and normally is&quot;&gt;[3]&lt;/sup&gt; also be resolved by comparing the positions of the connected vertices. But this small restriction enables us to entirely ignore the vertex positions when working on the mesh and look at the topology only in terms of which vertices/faces are connected, without knowing how they will be laid out in space.&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;
&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When we take all these restrictions into account and simplify the problem a bit, we only have to handle 8 different cases in total to add a new face to the mesh. One for each of the three quad-edges of the new face, that could either be missing or already exist. Luckily, most of these are rotationally symmetrical, and we only need to look at 4 distinct cases, that we can identify by counting the number of preexisting edges.&lt;/p&gt;

&lt;h3 id=&quot;case-0-no-preexisting-edges&quot;&gt;Case 0: No Preexisting Edges&lt;/h3&gt;

&lt;p&gt;The simplest case — and also the first one we need when we construct a new mesh — is the situation where we don’t have any faces or edges. All we need to do in this case is create one face and three quad-edges and connect them as shown below&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Technically it's also possible to create multiple unconnected faces, that are later connected by additional faces. As long as we don't violate the restriction above and all faces are connected before any traversal operation is used, that accesses the boundary edge. Because in that case we would have multiple edge-rings for the same face (the boundary face), which violates the precondition of the traversal operations.&quot; data-title=&quot;Technically it's also possible to create multiple unconnected faces, that are later connected by additional faces. As long as we don't violate the restriction above and all faces are connected before any traversal operation is used, that accesses the boundary edge. Because in that case we would have multiple edge-rings for the same face (the boundary face), which violates the precondition of the traversal operations.&quot;&gt;[4]&lt;/sup&gt;:&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_0_primal.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_0_primal.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Primal: One edge-ring around each of the three vertices.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_0_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_0_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Dual: One edge-ring around the new face and one around the boundary edge.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h3 id=&quot;case-1-one-preexisting-edge-two-new-edges&quot;&gt;Case 1: One Preexisting Edge (two new Edges)&lt;/h3&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_1_blank.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_1_blank.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The next case is a bit more complex: One of our edges already exists. What that means is that we add our face onto another face, with which we share a single edge.&lt;/p&gt;

&lt;p&gt;The complexity here comes from the fact that we need to insert our new edges into existing edge-rings. To be exact, the complex part is finding the correct edge-ring and insert position, i.e. the edge that should be directly in front of us in the ring. In contrast, the insertion itself is relatively easy&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The code below is for primal edges, but the code for dual edges would identical except for the used edge_next array. &quot; data-title=&quot;The code below is for primal edges, but the code for dual edges would identical except for the used edge_next array. &quot;&gt;[5]&lt;/sup&gt;:&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void insert_after(Edge predecessor, Edge new_edge) {
	// Find the edge that currently comes after the predecessor,
	auto&amp;amp; predecessor_next = primal_edge_next_[predecessor.index()];
	
	// ... set that as our next edge and
	primal_edge_next_[new_edge.index()] = predecessor_next;
	
	// ... change the predecessor, so that it points to us
	predecessor_next = new_edge;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A relatively common case is that we know our successor but not our predecessor. In this case, we can just use &lt;code&gt;origin_prev(e)&lt;/code&gt; to get its predecessor and insert ourselves between them. Though, one thing we need to keep in mind, is that the traversal operations won’t work as expected if we already modified part of the topology. So, the usual procedure is that we calculate and remember all information about the current topology that we will need and only modify it afterwards, whereby we achieve a consistent view of the topology.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void insert_before(Edge successor, Edge new_edge) {
	insert_after(successor.origin_prev(*this), new_edge);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_1_primal.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_1_primal.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Primal: The right vertex is identical to the simple case, but the other two edges need to be inserted into the existing edge-ring by calling &lt;code class=&quot;highlighter-rouge&quot;&gt;insert_before()&lt;/code&gt; with &lt;code class=&quot;highlighter-rouge&quot;&gt;e1,e2&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;e3,e4&lt;/code&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_1_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_1_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Dual: The edge-ring around the new face is identical to the simple case, but the boundary edge-ring is more complicated here. Before we inserted our new face, the shared edge was a boundary edge and &lt;code class=&quot;highlighter-rouge&quot;&gt;e1&lt;/code&gt; was part of the boundary edge-ring. After the insertion that is no longer the case and &lt;code class=&quot;highlighter-rouge&quot;&gt;e2&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;e3&lt;/code&gt; should be part of this edge-ring instead. So, we have to retrieve the predecessor of &lt;code class=&quot;highlighter-rouge&quot;&gt;e1&lt;/code&gt;, let it point to &lt;code class=&quot;highlighter-rouge&quot;&gt;e2&lt;/code&gt;, which points to &lt;code class=&quot;highlighter-rouge&quot;&gt;e3&lt;/code&gt;, which finally points to the original successor of &lt;code class=&quot;highlighter-rouge&quot;&gt;e1&lt;/code&gt;.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h3 id=&quot;case-2-two-preexisting-edges-one-new-edge&quot;&gt;Case 2: Two Preexisting Edges (one new Edge)&lt;/h3&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_2_blank.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_2_blank.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This case is quite similar to the previous one, but slightly simpler because we only have one newly created quad-edge that we need to connect. Thereby, the primal edge ring around one of the vertices is already correct, and we only need to update the other two to include the new edge. Wiring up the dual edges is the only part with a bit of complexity, but that case is also quite similar to case 1.&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_2_primal.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_2_primal.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Primal: The edge-ring around the bottom right vertex is already complete and doesn't need to be modified. The only thing we need to do here is insert our new edges into the ring around the remaining two vertices. Which can be done in the same way as in the previous case.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/insert_2_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/insert_2_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Dual: Like in the last case, we have to remove the out-going edges of the new face from the boundary-ring and replace it with the new edge. The difference is that we now remove two edges (&lt;code class=&quot;highlighter-rouge&quot;&gt;e1&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;e2&lt;/code&gt;) and replace them with a single new edge (&lt;code class=&quot;highlighter-rouge&quot;&gt;e3&lt;/code&gt;).&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h3 id=&quot;case-3-three-preexisting-edges&quot;&gt;Case 3: Three Preexisting Edges&lt;/h3&gt;

&lt;p&gt;The opposite of Case 1 is also simple enough: All three vertices of our face are already connected by edges, and we currently have a hole where we want to create the face. Because all connections are already established in this case, we don’t have to connect any edge, but just create a new face and set the origin of the dual edges leaving the face accordingly&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;and possibly remove them from the boundary edge-ring, if they don't already form a distinct ring &quot; data-title=&quot;and possibly remove them from the boundary edge-ring, if they don't already form a distinct ring &quot;&gt;[6]&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id=&quot;the-edge-case7&quot;&gt;The &lt;em&gt;Edge&lt;/em&gt;-Case&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;*ba dum tss* &quot; data-title=&quot;*ba dum tss* &quot;&gt;[7]&lt;/sup&gt;&lt;/h3&gt;

&lt;p&gt;We owe the simplicity of the previous cases primarily to one assumption: If a vertex is already part of a face, we share an edge with at least one of its faces.&lt;/p&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/special_case_blank.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/special_case_blank.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Special-Case: New face between two previously unconnected faces&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;But that assumption doesn’t hold in all cases. While our restriction from the beginning does forbid all cases where a vertex is shared by more than two unconnected faces, it leaves one case open that we still need to handle: A vertex that is shared by exactly two faces. We could’ve excluded that case too, of course. But in contrast to the others, this one is actually decidable based only on the topology. And by not allowing it we would unduly restrict the construction of meshes because without this case all new faces after the first one would have to share an edge with an existing face.&lt;/p&gt;

&lt;p&gt;The reason that we’ve ignored this cases until now is that we can actually separate these concerns by splitting our function in two steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Add the face like above, ignoring this special case&lt;/li&gt;
  &lt;li&gt;Iterate over each affected vertex and fix the errors introduced by ignoring it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The specific errors that are introduced by this are unconnected edge-rings&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;multiple disjunct sets of edges, that each form an independent edge-ring&quot; data-title=&quot;multiple disjunct sets of edges, that each form an independent edge-ring&quot;&gt;[8]&lt;/sup&gt;, which would cause massive problems during traversal because we would only see one of them when we iterate with &lt;code&gt;origin_next(e)&lt;/code&gt;. Luckily, we can solve this problem relatively easily by merging the two edge-rings:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void merge_edge_ring(Edge last_edge_of_a, Edge last_edge_of_b) {
	auto&amp;amp; a_next = primal_edge_next_[last_edge_of_a.index()];
	auto&amp;amp; b_next = primal_edge_next_[last_edge_of_b.index()];
	
	std::swap(a_next, b_next);
	// Now last_edge_of_a points to the first edge in b
	//  and last_edge_of_b points to the first edge in a
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/special_case_primal.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/special_case_primal.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Primal: Next-Pointers after first step in orange and changes in red. To merge the two rings, we need to find the last edge of the new (&lt;code class=&quot;highlighter-rouge&quot;&gt;e1&lt;/code&gt;) and old edge-ring (&lt;code class=&quot;highlighter-rouge&quot;&gt;e2&lt;/code&gt;). To do that here, we can just iterate over all edges of the respective ring, looking for the first edge that has no left face.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/03/mesh_construction/special_case_dual.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/03/mesh_construction/special_case_dual.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Dual: Next-Pointers after first step in cyan and changes in red. The two edges we need for the merge operation can be determined by iterating the edge-rings, looking for one where the connecting vertex is on the left side. Or we can find them by traversing the mesh after we've found the two edges for the primal merge-operation.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Now that we have our first world mesh, we can nearly start with the generation algorithms, like simulating plate tectonics. We’re just missing one final puzzle piece, we’ll look at next, that is the modification of our mesh during the simulation.&lt;/p&gt;

</description>
                <link>https://second-system.de/2021/05/02/world_creation</link>
                <guid>https://second-system.de/2021/05/02/world_creation</guid>
                <pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>World Data Structure</title>
                <description>&lt;p&gt;Before we can get started with the actual generation/&lt;wbr /&gt;simulation algorithms we first need to decide how to store our data, i.e. how the world is represented in the data-model. Given that it’s not clear what algorithms, we will use later and what their requirements will be, we’ll want to choose an extendable model, that won’t restrict our options later.&lt;/p&gt;

&lt;p&gt;There is however one decision we need to make right now, and that is what kind of shapes we want to support. As said in the previous post, I plan to focus on spherical worlds, seeing as the real world is (nearly) spherical&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;[citation needed]&quot; data-title=&quot;[citation needed]&quot;&gt;[1]&lt;/sup&gt; and I want to be able to directly compare it with my results. Since we are — at least at the current scale — only interested in the information on the surface of our world, this means that we need an efficient way to store information for points on the surface of a sphere.&lt;/p&gt;

&lt;!--more--&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/Triangles_(spherical_geometry).jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/Triangles_(spherical_geometry).jpg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;On a Sphere, triangles can have more than one right angle &lt;sup&gt;&lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Triangles_(spherical_geometry).jpg&quot;&gt;[source]&lt;/a&gt;&lt;/sup&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Working on the surface of a sphere brings with it a number of interesting problems&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;which is the reason most sane projects try to avoid it by using a flat surface for their worlds &quot; data-title=&quot;which is the reason most sane projects try to avoid it by using a flat surface for their worlds &quot;&gt;[2]&lt;/sup&gt; because it’s not the kind of euclidean space we are used to from school, but an elliptic space. This doesn’t really affect us at human scale, but at the scale of continents we will need to take that into account when moving points on the surface and calculating distances and angles.
Another problem with using a sphere for our world is that it limits our possible data structures because a sphere can’t be projected onto a flat surface without introducing distortions or other artifacts. So, simply projecting a bitmap texture onto the sphere is not really a practical course of action, if we want to avoid those problems.&lt;/p&gt;

&lt;p&gt;But based on the other projects I’ve looked at and my requirements, a triangle mesh is the obvious choice, anyway. Structured Grids like bitmaps/2D-Arrays often suffer from artifacts, either from discretization problems or other limitations, that are less apparent on unstructured grids. A triangle mesh is also a promising data structure because it’s extremely flexible as far as the resolution is concerned, which will be required to support the relatively large detailed worlds I’m looking to generate.&lt;/p&gt;

&lt;p&gt;So, the data structure of choice will be a triangle mesh of a Delauny triangulation (as well as its dual-graph, the voronoi diagram) with a variable resolution based on local detail requirements.&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear: both&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;mesh-data-structure-quad-edges&quot;&gt;Mesh Data Structure: Quad-Edges&lt;/h2&gt;

&lt;p&gt;The next question is: How do we store this triangle mesh?&lt;/p&gt;

&lt;p&gt;The simple option would be an array of faces, each containing the connected vertices. But to actually work with the mesh we’ll need an efficient way to traverse it, i.e. answer questions like “what other vertices/faces are connected to this vertex?”. Other common data structures for triangle meshes, that solve this problem, are directed edges, winged edges and half-edges.&lt;/p&gt;

&lt;p&gt;But the data structure I’ve decided on are &lt;a href=&quot;http://www.cs.cmu.edu/afs/andrew/scs/cs/15-463/2001/pub/src/a2/quadedge.html&quot;&gt;quad-edges&lt;/a&gt;, first described by Jorge Stolfi and Leonidas J. Guibas in 1985. Their main benefit is, that they model the primal triangle mesh, as well as its dual (voronoi diagram) at the same time. This means that we can traverse both and naturally switch between them if our algorithm requires it. Furthermore, quad-edges can answer many questions about the topology in constant time and often with just a simple bit-operation or by dereferencing a single pointer. And despite all, that their memory layout can be quite compact, which will be important for larger worlds.&lt;/p&gt;

&lt;p&gt;The three main concepts are the same as for any mesh: vertices, faces and edges. Vertices are points on the surface and the element to which we will link most of our information like elevation. Two vertices can be connected by an edge, and three connected vertices form a single triangular face.&lt;/p&gt;

&lt;p&gt;In addition to this &lt;em&gt;primal mesh&lt;/em&gt;, we also want to work with its &lt;em&gt;dual&lt;/em&gt;. Here vertices and faces switch places, that is each face in the primal mesh is a vertex in the dual mesh&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The dual vertex is positioned at the circumcenter of the face. That means, depending on the shape of the triangle, the dual vertex could also lie outside of the face. &quot; data-title=&quot;The dual vertex is positioned at the circumcenter of the face. That means, depending on the shape of the triangle, the dual vertex could also lie outside of the face. &quot;&gt;[3]&lt;/sup&gt;, which are connected into voronoi cells with one of the primal vertices inside. As we can see in the image below, for each edge in the primal mesh (gray) the dual mesh contains an edge that connects the face on the left and on the right side of it. But the edges and vertices at the boundary are a bit of an &lt;em&gt;edge case&lt;/em&gt; and form voronoi cells that are infinitely large. Luckily, that is a case we can ignore for now because our sphere is a closed mesh without any holes or boundaries.&lt;/p&gt;

&lt;p&gt;The dual mesh is especially interesting for us because the voronoi cells define the area around each primal-vertex that is closer to it than any other vertex. Which is useful to calculate its sphere of influence or mass during plate simulation and will also prove useful for simulating drainage areas and rivers, later.&lt;/p&gt;

&lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/delauny_voronoi.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/delauny_voronoi.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;A Delaunay triangulation (grey) of the vertices (red) and its dual, consisting of the circumcenters of the faces (blue) and connecting edges (cyan), forming the voronoi cells.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Of these three concepts, the most important one for our quad-edge data structure is — as the name suggest — the edge. Our edges are &lt;em&gt;directed&lt;/em&gt;, which means they know which vertex they are coming from, which one they are going to and which faces are on their left/right side. Thus, we need to store a total of four vertices for each pair of connected vertices, that form a quad-edge (see image below), to model the two possible directions of both the primal and dual mesh at that point.&lt;/p&gt;

&lt;p&gt;Besides these pieces of information, we only need one other datum to describe the complete topology: Which edges start at a given vertex, i.e. the outgoing edges&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This information is different for the dual mesh. For faces we don't store the outgoing dual-edges but the primal edges that rotate counterclockwise around this face. &quot; data-title=&quot;This information is different for the dual mesh. For faces we don't store the outgoing dual-edges but the primal edges that rotate counterclockwise around this face. &quot;&gt;[4]&lt;/sup&gt;. And we store these as linked-lists of edges, where each edge knows the next edge around its origin, which is called an edge-ring.&lt;/p&gt;

&lt;p&gt;This might look like a lot of information, but as we will see, most of it is redundant and doesn’t need to stored directly, but can instead be derived from the following and stored surprisingly compactly.&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/directed_edge.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/directed_edge.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Each edge &lt;code class=&quot;highlighter-rouge&quot;&gt;e&lt;/code&gt; knows its origin/destination vertex, as well as which face is on its left/right side.&lt;br /&gt; Or origin/destination faces and left/right vertices for edges of the dual mesh.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/quad_edge.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/quad_edge.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Each edge also knows the other edges that are part of the same quad-edge, which we can access by rotating the edge counter clockwise.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/edge_ring.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/edge_ring.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Finally, each edge knows the next edge, when rotating counterclockwise around its vertex of origin, i.e. its edge-ring.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;The information above describes the complete topology of our mesh, which we can access using the following basic operations:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;origin(e)&lt;/code&gt;: Gives us the origin of an edge (either a vertex in the primal or a face in the dual mesh).&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;dest(e)&lt;/code&gt;: Gives us the destination of an edge.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;left(e)&lt;/code&gt;: Gives us the face/vertex on the left side of the edge.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;right(e)&lt;/code&gt;: Gives us the face/vertex on the right side of the edge.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;rot(e)&lt;/code&gt;: Gives us the next edge in a quad-edge. As each quad-edge consists of four edges, the result of the fourth rotation is our initial edge, again.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;sym(e)&lt;/code&gt;: Gives us the edge that points in the opposite direction. So it’s the same as rotating the edge twice.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;origin_next(e)&lt;/code&gt;: Gives us the next edge when rotating counterclockwise around the origin of an edge. Like &lt;code&gt;rot(e)&lt;/code&gt; this will loop back on itself after we have visited every outgoing edge from the origin.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also combine these into more complex operations to traverse the mesh:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;dest_next(e)&lt;/code&gt;: Same as &lt;code&gt;origin_next(e)&lt;/code&gt; but gives us the next edge around the destination instead.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;left_next(e)&lt;/code&gt;: Gives us the next edge rotating around the left face, i.e. &lt;code&gt;left(e)&lt;/code&gt; will return the same value and the origin of the returned edge is our destination.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;right_next(e)&lt;/code&gt;: Same as &lt;code&gt;left_next(e)&lt;/code&gt; but rotates around the right face.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we’ve seen, all operations above always rotate counterclockwise. So, the final operations we will define are variants of the above, that rotate in the opposite direction:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;inv_rot(e)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;origin_prev(e)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;left_prev(e)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;right_prev(e)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/origin_next.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/origin_next.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;origin_next(e)&lt;/code&gt; allows us to iterate over all edges around a vertex or face. As with all operations the direction is counterclockwise and &lt;code class=&quot;highlighter-rouge&quot;&gt;origin_prev(e)&lt;/code&gt; can be used for clockwise iteration.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/dest_next.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/dest_next.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Similarly, &lt;code class=&quot;highlighter-rouge&quot;&gt;dest_next(e)&lt;/code&gt; can be used to iterate over all edges with a given destination.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/left_right_next.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/left_right_next.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;And finally &lt;code class=&quot;highlighter-rouge&quot;&gt;left_next(e)&lt;/code&gt; can be used to iterate over all edges that are part of a given face.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;p&gt;There is one more operation defined by the paper, that we will ignore here: &lt;code&gt;flip(e)&lt;/code&gt;, which returns the edge as seen from the other side of the polygon, i.e. the origin and destination stay the same but the left and right face are swapped. This operation is useful because quad-edges can actually represent any &lt;a href=&quot;https://sinestesia.co/blog/tutorials/non-manifold-meshes-and-how-to-fix-them/&quot;&gt;manifold&lt;/a&gt; polygon&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Which means they have to locally resemble n-dimensional Euclidean space. For our use-case that mostly just means that every edge has exactly two faces (left and right). Of course, this technically also means that 2D planes are not supported (because of the edges at their boundary) but as we will see later, we can solve this by just connecting the boundary edges to each other to close the mesh &quot; data-title=&quot;Which means they have to locally resemble n-dimensional Euclidean space. For our use-case that mostly just means that every edge has exactly two faces (left and right). Of course, this technically also means that 2D planes are not supported (because of the edges at their boundary) but as we will see later, we can solve this by just connecting the boundary edges to each other to close the mesh &quot;&gt;[5]&lt;/sup&gt;, including non-triangular meshes and even non-orientable surfaces&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;e.g. Möbius strips &quot; data-title=&quot;e.g. Möbius strips &quot;&gt;[6]&lt;/sup&gt;, and don’t have an inherent preference for a specific side of the polygon. But we don’t really need that sort of flexibility for our endeavor, which is describing the topology of the surface of a sphere (and maybe later other simple shapes) and would rather trade it for some simplicity further down the road. So, what we will do instead is drop this operation and only work on one side of the polygon, defined by a consistent counterclockwise winding order.&lt;/p&gt;

&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;

&lt;p&gt;Now that we have seen how quad-edges describe the topology and what operations we need, we can start with the interesting part: Implementing it and looking for potential optimizations.&lt;/p&gt;

&lt;p&gt;First, let’s reiterate what we need to store:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;For each vertex: one outgoing edge (an edge where &lt;code&gt;origin(e)&lt;/code&gt; is our vertex)&lt;/li&gt;
  &lt;li&gt;For each face: one edge going counterclockwise around the face (an edge where &lt;code&gt;left(e)&lt;/code&gt; is our face)&lt;/li&gt;
  &lt;li&gt;For each edge:
    &lt;ol&gt;
      &lt;li&gt;Its siblings, i.e. the three other edges that are part of the same quad-edge.&lt;/li&gt;
      &lt;li&gt;The next edge, iterating counterclockwise around its origin (&lt;code&gt;origin_next(e)&lt;/code&gt;).&lt;/li&gt;
      &lt;li&gt;The origin/destination vertex and left/right face.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing we can drop immediately from that list is most of 3.3. Because each edge already knows the other edges of the quad-edge, we only really need to store the origin vertex/face of each edge. Everything else can be reconstructed from just these two, with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;destination(e) == origin(sym(e))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;left(e) == origin(inv_rot(e))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;right(e) == origin(rot(e))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could do something similar for the references to an edges siblings:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;sym(e) == rot(rot(e))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;inv_rot(e) == rot(rot(rot(e)))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, as we will see next, that doesn’t really buy us anything because we can get rid of 3.1 entirely.&lt;/p&gt;

&lt;p&gt;If we were to translate that naively to C++ it could look something like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Vertex {
	Edge* outgoing_edge;           // 1.
};
struct Face {
	Edge* ccw_edge;                // 2.
};
struct Edge {
	std::array&amp;lt;Edge*, 4&amp;gt; siblings; // 3.1.
	Edge*                next;     // 3.2.
};

// We need two separate types for edges, because the 
//   type of the origin is different for primal and dual edges
struct Primal_edge : Edge {
	Vertex* origin;                // 3.3.
};
struct Dual_edge : Edge {
	Face* origin;                  // 3.3.
};

struct Mesh {
	// The actual data, that is referenced by the pointers above
	std::vector&amp;lt;Vertex&amp;gt;      vertices;
	std::vector&amp;lt;Face&amp;gt;        faces;
	std::vector&amp;lt;Primal_edge&amp;gt; primal_edges;
	std::vector&amp;lt;Dual_edge&amp;gt;   dual_edges;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;As this just defines the topology, we still need a way to store our actual data, like vertex positions or elevations. We could just add those to the struct as additional member variables, but that would mean that we need to modify them each time we implement a new generation algorithm, which would make future expansion more difficult&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Or in other words: Such a construct would violate the open–closed principle.&quot; data-title=&quot;Or in other words: Such a construct would violate the open–closed principle.&quot;&gt;[7]&lt;/sup&gt;. Hence, instead we will give each vertex/face/edge a unique ID, that we can then later use as a key to reference our data.&lt;/p&gt;

&lt;p&gt;To define these IDs, we will just use their index position inside the &lt;code&gt;std::vector&lt;/code&gt; that contains them. For vertices and faces this will work flawlessly, but for edges we also need to differentiate between primal and dual edges. To solve this problem, we will resort to the age-old tradition of &lt;em&gt;stealing every bit that isn’t nailed down&lt;/em&gt;. To be precise, we will use the most significant bit of our ID to decide if it references a primal or dual edge&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Sadly this will leave us with a maximum of only 2'147'483'647 edges. But I think that loss will be survivable (for now).&quot; data-title=&quot;Sadly this will leave us with a maximum of only 2'147'483'647 edges. But I think that loss will be survivable (for now).&quot;&gt;[8]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;If we visualize that structure, we see another interesting effect. Because each edge always belongs to a quad-edge, if we add a new edge to our mesh, we will always need to create 4 edges (2 primal + 2 dual). So if we store and reference our edges as described above, we can find the siblings of an edge just by modifying their index, without storing any additional data.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;                       quad edge 1               quad edge 2
                           ︷                        ︷
              ╭────────────┬────────────┬────────────┬────────────┬────────────╮
primal_edges: │     e0     │     e1     │     e2     │     e3     │     ...    │
              ╰────────────┴────────────┴────────────┴────────────┴────────────╯
              ╭────────────┬────────────┬────────────┬────────────┬────────────╮
dual_edges:   │     e0     │     e1     │     e2     │     e3     │     ...    │
              ╰────────────┴────────────┴────────────┴────────────┴────────────╯

Edge-index bits:
       ╭──┬──┬──┬──┬───┬──┬──┬──┬──╮
Bit:   │31│30│29│28│...│ 3│ 2│ 1│ 0│
       ╞══╪══╪══╪══╪═══╪══╪══╪══╪══╡
Value: │ 1│ 0│ 1│ 1│...│ 1│ 0│ 0│ 1│
       ╰──┴──┴──┴──┴───┴──┴──┴──┴──╯
         ↑                        ↑ 
         0 = primal edge          0 = 1. edge of quad-edge
         1 = dual edge            1 = 3. edge of quad-edge (== sym(e) == rot(rot(e)))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The 31st bit is reserved to decide if the edge belongs to the primal mesh or its dual, and the rest is used as the index into the respective vector. But because we allocate the edges continuously, they always come in pairs inside each vector, and we can switch between them by just flipping the 0th bit of their index. What this means is that we can not only drop the &lt;code&gt;siblings&lt;/code&gt; member but that we can actually calculate &lt;code&gt;rot(e)&lt;/code&gt;, &lt;code&gt;sym(e)&lt;/code&gt; and &lt;code&gt;inv_rot(e)&lt;/code&gt; using relatively simple bit-wise math instead of chasing pointers!&lt;/p&gt;

&lt;p&gt;Since we are already stealing parts of our indices, we will reserve one more value from each as an identifier&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Which will bring our total number of possible edges down to a meager 2'147'483'645... &quot; data-title=&quot;Which will bring our total number of possible edges down to a meager 2'147'483'645... &quot;&gt;[9]&lt;/sup&gt; for invalid or unset edges, vertices and faces. These will be important later to define boundary edges or incomplete meshes during construction. But they also allow as to “delete” elements from the mesh. Because the index of an element is also used to reference it, we can’t just remove them from the vector, as that would move all later elements, changing their index. Instead, we’ll utilize the invalid IDs to leave “holes” in the vector, that we can skip during processing and fill in later with new vertices/faces/edges.&lt;/p&gt;

&lt;p&gt;And that’s it, for the most part. As a last step, we’ll just sprinkle a bit of data-oriented design over our structure, by moving our data from the &lt;code&gt;Vertex&lt;/code&gt;/&lt;code&gt;Face&lt;/code&gt;/&lt;code&gt;Edge&lt;/code&gt; struct directly into separate vectors in the Mesh. That doesn’t change much in our case, as our structs were already rather small and simple, but could be a wee bit faster for some cases&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;e.g. if we just need to follow the next-Pointer of an edge but don't need the origin of the edge &quot; data-title=&quot;e.g. if we just need to follow the next-Pointer of an edge but don't need the origin of the edge &quot;&gt;[10]&lt;/sup&gt;. And it also frees up &lt;code&gt;Face&lt;/code&gt;, &lt;code&gt;Vertex&lt;/code&gt; and &lt;code&gt;Edge&lt;/code&gt; as type names, that we can then use as type-safe ID-wrappers&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;The actual implementation follows the same structure, but is a bit more complex because it needs to handle &amp;quot;holes&amp;quot; in the vectors left by modifications. And it's a bit less readable because I'm using a thin C-API over my C++ implementation to achieve ABI stability. Furthermore, I've also left out any constructors, operators and methods here. &quot; data-title=&quot;The actual implementation follows the same structure, but is a bit more complex because it needs to handle &amp;quot;holes&amp;quot; in the vectors left by modifications. And it's a bit less readable because I'm using a thin C-API over my C++ implementation to achieve ABI stability. Furthermore, I've also left out any constructors, operators and methods here. &quot;&gt;[11]&lt;/sup&gt;. We also dropped the separate types for primal and dual edges, to further simplify the structure. However, it might be interesting to provide separate types to already distinguish them in the type-system, instead of later at runtime. But that is an extension I might look at later.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct Face {
	uint32_t id;
};
struct Vertex {
	uint32_t id;
};
struct Edge {
	uint32_t mask;
};

class Mesh {
  private:
	friend struct Edge;
	
	std::vector&amp;lt;Edge&amp;gt;   vertex_edges_;
	std::vector&amp;lt;Edge&amp;gt;   face_edges_;
	
	std::vector&amp;lt;Vertex&amp;gt; primal_edge_origin_;
	std::vector&amp;lt;Edge&amp;gt;   primal_edge_next_;
	std::vector&amp;lt;Face&amp;gt;   dual_edge_origin_;
	std::vector&amp;lt;Edge&amp;gt;   dual_edge_next_;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;operations&quot;&gt;Operations&lt;/h3&gt;

&lt;p&gt;We’ve already seen that we can implement &lt;code&gt;rot()&lt;/code&gt;, &lt;code&gt;sym()&lt;/code&gt; and &lt;code&gt;inv_rot()&lt;/code&gt; as bit-wise operations on the ID, so that is the first thing we will implement&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;All of these methods and constructors could/should of course be constexpr and are in the actual implementation. &quot; data-title=&quot;All of these methods and constructors could/should of course be constexpr and are in the actual implementation. &quot;&gt;[12]&lt;/sup&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;inline constexpr auto edge_type_bit = uint32_t(1) &amp;lt;&amp;lt; 31u;

struct Edge {
	uint32_t mask;
	
	// The default constructor sets all bits to 1,
	//   which is our representation for an invalid edge
	Edge() : mask{~uint32_t(0)} {}
	
	// And we also have constructors that take a mask 
	//   or construct a new one from an index and a bool 
	//   (i.e. set the highest bit if it's a dual edge)
	Edge(uint32_t mask) : mask{mask} {}
	Edge(bool dual, uint32_t index)
	  : mask{dual ? (index | edge_type_bit) : index} {
	}
	
	// If we need the actual index, we have to unset the highest bit
	uint32_t index()const   { return mask &amp;amp; ~edge_type_bit; }
	// And to decide if an edge belongs to the dual or primal mesh
	//   we can just shift it, so its highest bit is the only one left
	bool     is_dual()const { return mask &amp;gt;&amp;gt; 31u; }
	
	// A simple operation, we haven't talked about, which gives
	//   us the first edge of a quad-edge by unsetting both the highest and lowest bit.
	Edge base()const    { return { mask &amp;amp; ~(edge_type_bit | 1u)}; }

	// As we have seen above, the two primal/dual edges that
	//   belong to the same quad edge are always at an odd/even
	//   index and right beside each other.
	// So we just need to xor the least significant 
	//   bit to switch between them
	Edge sym()const     { return { mask ^ 1u}; }
	
	// rot and inv_rot are a bit more complex, because we need to change both bits.
	// First we always need to xor the highest bit, as rot always 
	//   alternates between dual and primal edges.
	// And we also need to change the lowest bit, which we will do
	//   with the second xor (see graphic below)
	Edge rot()const     { return {(mask ^ edge_type_bit) ^  is_dual()}; }
	Edge inv_rot()const { return {(mask ^ edge_type_bit) ^ (is_dual() ^ 1u)}; }
&lt;/code&gt;&lt;/pre&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/rot_math.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/rot_math.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p style=&quot;height:12em; display: table-cell; vertical-align: middle;&quot;&gt;
To implement the rotate operation, we need to change both the most and least significant bit. The most significant bit alternates between 0 and 1 as we alternate between primal and dual edges. But the least significant bit only changes every two steps. To implement this, its change is dependent on the most significant bit, i.e. we only alternate it if we rotate from a dual to a primal edge.
&lt;/p&gt;
&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The next steps are the &lt;code&gt;origin(e)&lt;/code&gt;/&lt;code&gt;dest(e)&lt;/code&gt;/… operations to get the surrounding vertices and faces of an edge. The functions to get the origin vertex/face are relatively simple, as we just need to check if the operation is valid for this type of edge (primal vs. dual) and access the corresponding vector in the &lt;code&gt;Mesh&lt;/code&gt; struct. And for the destination and the left/right face, we just have to rotate the edge appropriately beforehand and then get the origin of the result.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;	Vertex origin(const Mesh&amp;amp; mesh)const {
		assert(!is_dual());
		return mesh.primal_edge_origin_[mask];
	}
	Vertex dest(const Mesh&amp;amp; mesh)const {
		return sym().origin(mesh);
	}
	
	Face origin_face(const Mesh&amp;amp; mesh)const {
		assert(is_dual());
		return mesh.dual_edge_origin_[index()];
	}
	Face dest_face(const Mesh&amp;amp; mesh)const {
		return sym().origin_face(mesh);
	}
	
	Face left(const Mesh&amp;amp; mesh)const {
		return inv_rot().origin_face(mesh);
	}
	Face right(const Mesh&amp;amp; mesh)const {
		return rot().origin_face(mesh);
	}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next are the functions to actually traverse the mesh. &lt;code&gt;origin_next(e)&lt;/code&gt; is again quite simple — determine the correct vector based on the type of the edge and load the corresponding next pointer — but implementing all the others in-terms-of it is a bit more complex and perhaps needs a bit of visualization:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;	Edge origin_next(const Mesh&amp;amp; mesh)const {
		return is_dual() ? mesh.dual_edge_next_[index()]
		                 : mesh.primal_edge_next_[index()];
	}
	Edge origin_prev(const Mesh&amp;amp; mesh)const {
		return rot().origin_next(mesh).rot();
	}
	Edge dest_next(const Mesh&amp;amp; mesh)const {
		return sym().origin_next(mesh).sym();
	}
	Edge dest_prev(const Mesh&amp;amp; mesh)const {
		return inv_rot().origin_next(mesh).inv_rot();
	}
	Edge left_next(const Mesh&amp;amp; mesh)const {
		return inv_rot().origin_next(mesh).rot();
	}
	Edge left_prev(const Mesh&amp;amp; mesh)const {
		return origin_next(mesh).sym();
	}
	Edge right_next(const Mesh&amp;amp; mesh)const {
		return rot().origin_next(mesh).inv_rot();
	}
	Edge right_prev(const Mesh&amp;amp; mesh)const {
		return sym().origin_next(mesh);
	}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;figure class=&quot;captioned_image fill_black float_right&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/02/quad_edge/origin_prev.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/02/quad_edge/origin_prev.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One example for the methods above, that shows how &lt;code&gt;origin_prev()&lt;/code&gt; can be implemented in terms of &lt;code&gt;origin_next()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The key here is that we first rotate the edge, to get the dual edge that points from the right to the left face. Just like with primal edges, we can use &lt;code&gt;origin_next()&lt;/code&gt; to get the next (counterclockwise) edge around the origin, but for dual edges that origin is a face instead of a vertex. So, when we rotate our dual edge, we get the dual edge that point “through” the next edge of the right face or in other words &lt;code&gt;origin_prev()&lt;/code&gt; of our original mesh. And to get the primal edge, we are actually looking for, we then just need to rotate the dual edge again.&lt;/p&gt;

&lt;p&gt;&lt;br style=&quot;clear:both&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;higher-level-abstractions&quot;&gt;Higher Level Abstractions&lt;/h3&gt;

&lt;p&gt;Based on these relatively simple operations, we have seen so far, we can now construct higher level abstractions to navigate the topology. One operation we need relatively often is iterating over every neighbor of a given vertex&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;i.e. all vertices that share an edge with the given vertex&quot; data-title=&quot;i.e. all vertices that share an edge with the given vertex&quot;&gt;[13]&lt;/sup&gt;, which can be implemented as:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// Get any edge that points away from the vertex
auto edge = vertex.edge(mesh);
auto e    = edge;
do {
	// Get the destination vertex of the current edge
	auto v = e.dest(mesh);
	
	// Do something with v
	
	// Get the next (CCW) edge
	e = e.origin_next(mesh);
	
	// If the next edge is the one we started with,
	//   we have visited all edges and can stop
} while(e!=edge);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Precisely because this is a common operation, the actual API provides iterators and methods that simplify the above code to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;for(auto v : vertex.neighbors(mesh)) {
	// Do something with v
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One part of the API we’ve ignored so far is how we construct a mesh to begin with. And for some algorithms, we will also need to be able to modify an existing mesh. The operations we will need for this are:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Create a new triangle face that connects three vertices&lt;/li&gt;
  &lt;li&gt;Flip the central edge of two adjacent faces, i.e. remove the shared edge and replace it with a new edge between the two previously unconnected vertices&lt;/li&gt;
  &lt;li&gt;Split an edge into two edges, inserting a new vertex and new faces between them&lt;/li&gt;
  &lt;li&gt;Collapse an edge, i.e. merge two vertices and remove the edges and faces between them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But because these operations are a bit more complicated and this post is already far longer than I originally planned, we will look at that in a future post.&lt;/p&gt;

&lt;h2 id=&quot;positions-elevations-and-other-additional-information&quot;&gt;Positions, Elevations and other Additional Information&lt;/h2&gt;

&lt;p&gt;However, there is one part we still have to talk about. Everything we have looked at so far is purely concerned with the topology — which vertices/faces are connected to each other — and isn’t concerned about how it’s actually laid out in space. That is, if it can be laid out without intersecting itself, it doesn’t matter if our basic shape is a sphere, cube, plane or tesseract&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;which is quite neat, I think, and allows us a lot of flexibility in the future.&quot; data-title=&quot;which is quite neat, I think, and allows us a lot of flexibility in the future.&quot;&gt;[14]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;But even if our data structure isn’t concerned about the positions in space, we still do care about that for many applications and need a way to store them. We’ve already touched on the fact that we can use the IDs of our vertices/faces/edges to link them to additional information like elevation or temperature, and we will handle their positions in exactly the same way. Because our elements are laid out in a continuous vector in memory&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;While there might be some holes in our data, which we will discuss in the next post, there should never be more than a small percentage and we can ignore that for now.&quot; data-title=&quot;While there might be some holes in our data, which we will discuss in the next post, there should never be more than a small percentage and we can ignore that for now.&quot;&gt;[15]&lt;/sup&gt; and our IDs are based on their position, we can also just use a vector for our addition data and use the IDs to index into them.&lt;/p&gt;

&lt;p&gt;While that will work, there is a bit more complexity, to handle changes when we modify the mesh later. And a bit of validation and type-safety would also be a welcome addition. To achieve that, we will create a new &lt;code&gt;Layer&lt;/code&gt; type that stores information linked to a given part of our mesh, that looks like this&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;This is again a somewhat simplified example that ignores many aspects such as constexpr, [[nodiscard]], data-members and constructors. &quot; data-title=&quot;This is again a somewhat simplified example that ignores many aspects such as constexpr, [[nodiscard]], data-members and constructors. &quot;&gt;[16]&lt;/sup&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;enum class Layer_type {
	vertex, face, edge_primal, edge_primal_directed, edge_dual, edge_dual_directed
};
	
template &amp;lt;typename T, Layer_type Element&amp;gt;
class Layer {
  public:
	// Access the data for a specific element
	// The parameter type is Vertex, Face or Edge depending on the Layer_type
	T&amp;amp;       operator[](type&amp;lt;Element&amp;gt;);
	const T&amp;amp; operator[](type&amp;lt;Element&amp;gt;)const;
	
	size_t size() const;
	bool   empty() const;
	
	// begin() and end() so the type can be used in range-for loops
	T*       begin();
	const T* begin()const;
	T*       end();
	const T* end()const;

  private:
	// ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because &lt;code&gt;Layer&lt;/code&gt; is a class template, it can be used to store all kinds of different data types&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Although, because of other parts of the system that is currently limited to: bool, int8, int32, float and a 2D and 3D float-Vector &quot; data-title=&quot;Although, because of other parts of the system that is currently limited to: bool, int8, int32, float and a 2D and 3D float-Vector &quot;&gt;[17]&lt;/sup&gt; and can reference vertices, faces and all types of edges.&lt;/p&gt;

&lt;p&gt;One thing that might be a bit surprising at first is the number of possible types of edges in &lt;code&gt;Layer_type&lt;/code&gt;. In addition to the distinction between primal and dual edges, we also differentiate between directed and undirected edges here. While the edges in our mesh are always directed, much of the information we might want to store about them will be identical for both directions. For example, when we model plate tectonics and store the distance between neighboring vertices, we would choose &lt;code&gt;edge_primal&lt;/code&gt; instead of &lt;code&gt;edge_primal_directed&lt;/code&gt; and only require half the memory to store our data.&lt;/p&gt;

&lt;p&gt;Not all data is directly related to parts of the mesh or might be so sparse that a continuous storage doesn’t make sense. For these cases, there will also be unstructured data layers, that model a simple key-value store, which I might talk about later.&lt;/p&gt;

&lt;h2 id=&quot;world-class&quot;&gt;World Class&lt;/h2&gt;

&lt;p&gt;As already noted, the Layers might need to be updated whenever the mesh is modified, which means both are heavily intertwined. Thus, it isn’t really feasible to construct them independently of each other. Hence, we will encapsulate both in a &lt;code&gt;World&lt;/code&gt; type that manages both the &lt;code&gt;Mesh&lt;/code&gt; and any created &lt;code&gt;Layer&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class World {
  public:
	const Mesh&amp;amp; mesh() const;
	Mesh_lock   lock_mesh();

	const Dict*             layer(std::string_view id) const;
	Unstructured_layer_lock lock_layer(std::string_view id);

	template &amp;lt;typename LayerInfo&amp;gt;
	const typename LayerInfo::layer_t* layer(const LayerInfo&amp;amp; info) const;
	
	template &amp;lt;typename LayerInfo&amp;gt;
	Layer_lock&amp;lt;LayerInfo&amp;gt; lock_layer(const LayerInfo&amp;amp; info);

  private:
	// ...
};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In contrast to the types we have seen previously, the &lt;code&gt;const&lt;/code&gt; and non-&lt;code&gt;const&lt;/code&gt; getters are rather different here and also have different names. Because a complete &lt;code&gt;World&lt;/code&gt; will be a pretty large and complex object with many layers, it is not feasible to copy it often. But creating copies of the current state of a &lt;code&gt;World&lt;/code&gt; will be required later to implement an undo/redo functionality in the editor. To solve this, the &lt;code&gt;World&lt;/code&gt; class implements copy-on-write semantics for the objects it contains. That means when a &lt;code&gt;World&lt;/code&gt; is copied it still references the data of the original and only when one of them is modified the affected data — and only it — is actually copied. But to track these modifications, we need a bit of machinery, provided here by the &lt;code&gt;..._lock&lt;/code&gt; classes and &lt;code&gt;lock_...&lt;/code&gt; methods. This recording of modifications will also allow us to check when the underlying data has been modified and for example cache the textures and vertex buffers used by the renderer.&lt;/p&gt;

&lt;p&gt;Besides the &lt;code&gt;mesh()&lt;/code&gt; getter, the class also contains two getters for layers, one for simple unstructured layers — that just have a name — and one for our more complex mesh-based layers.&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;Both of the layer-getters return a pointer, because the layer might not exist, yet. But there is always a mesh, even though it might still be empty.&quot; data-title=&quot;Both of the layer-getters return a pointer, because the layer might not exist, yet. But there is always a mesh, even though it might still be empty.&quot;&gt;[18]&lt;/sup&gt; The latter is again a template, which hopefully is not that surprising because our &lt;code&gt;Layer&lt;/code&gt; was also a template. But the parameter of the method probably warrants some further explanation. Every layer has a name with which it’s referenced in the procedural generation code, but it also has additional metadata linked to it:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The type of the data that it stores (&lt;code&gt;T&lt;/code&gt; template parameter in &lt;code&gt;Layer&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;What its data is linked to in the mesh (&lt;code&gt;Layer_type&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;The initial value of its data (used both when it’s first created and when a new vertex is added to the mesh)&lt;/li&gt;
  &lt;li&gt;The range of valid values (only positive numbers, only numbers between 0 and 10, …)&lt;/li&gt;
  &lt;li&gt;Whether its data should be automatically validated against this range, to detect runtime errors&lt;/li&gt;
  &lt;li&gt;How the data should react to changes of the mesh (e.g. when an edge is split, should the value for the new vertex be interpolated between the original origin and destination, reset to the initial value, use the min/max of the values, …?)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Layers will usually be shared between multiple independent modules, that contain the actual procedural generation code. In fact, they are the main way of communication between them. Because it would be impractical to keep all of them synchronized, the system remembers all information about a layer at the time of creation. So, all later modules that want to access an existing layer only need to pass the first two points from the list above (which are validated against the stored information) and all others will be ignored.&lt;/p&gt;

&lt;p&gt;Therefore, we also introduce a new type &lt;code&gt;Layer_info&lt;/code&gt; to describe what a layer looks like and how it should behave, which can be constructed and then used to retrieve a concrete layer from the &lt;code&gt;World&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;constexpr auto position_layer = Layer_info&amp;lt;Vec3, Layer_type::vertex&amp;gt;(&quot;position&quot;);
constexpr auto distance_layer = Layer_info&amp;lt;float, Layer_type::edge_primal&amp;gt;(&quot;plate_distance&quot;)
                                .initial(-1.f);

auto [positions] = world.lock_layer(position_layer);

// Once we have the layer, we can access its data with the operator[] defined in the Layer class 
positions[Vertex(42)] = Vec3{10.f, -2.f, 3.f};
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;That is all for now, as I think this post is already a bit too long and complicated. We now just have to look at how meshes can be created and modified, before we can start with the really interesting parts.&lt;/p&gt;

</description>
                <link>https://second-system.de/2021/04/11/data_structure</link>
                <guid>https://second-system.de/2021/04/11/data_structure</guid>
                <pubDate>Sun, 11 Apr 2021 00:00:00 +0000</pubDate>
        </item>

        <item>
                <title>Yggdrasill</title>
                <description>&lt;p&gt;As the end of my studies is nigh, I’m looking into possible topics for my master thesis. Alas, I’ve grown a bit tired of writing game engines and renderers lately — who’d have thought.&lt;/p&gt;

&lt;p&gt;Luckily, this quest for new &lt;del&gt;shores&lt;/del&gt; topics was solved quickly. I’ve also developed quite an interest in procedural generation and always had a bit of a fascination for fantasy worlds and maps. So, I’ve decided to work on procedural world generation.&lt;/p&gt;

&lt;p&gt;This is obviously going to be a &lt;a href=&quot;https://dwarffortresswiki.org/index.php?title=DF2014:Fun&amp;amp;redirect=no&quot;&gt;fun&lt;/a&gt; project with much potential for interesting failures.&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;As Gordon Moore put it: If everything you try works, you aren't trying hard enough.&quot; data-title=&quot;As Gordon Moore put it: If everything you try works, you aren't trying hard enough.&quot;&gt;[1]&lt;/sup&gt; Therefore, I have the best intentions of documenting my plans, progress and interesting problems here. With best intentions I do of course mean “force myself” and with documenting I mean “dumb my thoughts here and try to make sense of it all here”.&lt;/p&gt;

&lt;p&gt;Without further ado: Welcome to this unwise cocktail of a &lt;a href=&quot;https://heredragonsabound.blogspot.com/2020/02/the-forever-project.html&quot;&gt;forever project&lt;/a&gt; and a master thesis.&lt;/p&gt;

&lt;!--more--&gt;

&lt;video loop=&quot;&quot; muted=&quot;&quot; inline=&quot;&quot; autoplay=&quot;&quot; controls=&quot;&quot; style=&quot;width:100%; max-width: 40em; display: block; margin: auto&quot;&gt;
	&lt;source src=&quot;/assets/images/01/jericho.webm&quot; type=&quot;video/webm&quot; /&gt;
&lt;/video&gt;

&lt;h2 id=&quot;related-work&quot;&gt;Related Work&lt;/h2&gt;

&lt;p&gt;There already are quite a lot of inspiring projects that procedurally generate worlds and maps with impressive results — some of which I will briefly mention here. I’ve also uncovered some scientific papers, that I might use as a basis for my project. Although, unfortunately, most of these are sadly closed source projects.&lt;/p&gt;

&lt;div class=&quot;image_list&quot;&gt;

  &lt;figure class=&quot;captioned_image fixed_height fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/dwarf-fortress.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/dwarf-fortress.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;b&gt;Dwarf Fortress&lt;/b&gt; is arguably the first thing that comes to mind, when people think about the procedural generation of game worlds. Sadly, definitive information about the generation algorithms is a bit sparse. What is known and documented is, that the generation starts with layers of noise-based fractal maps for terrain/precipitation/..., that form the initial map and are then refined by simulation steps. The main focus (and most impressive part) is, of course, the history generation and simulation of the actual game world. In contrast, the terrain generation is sadly relatively unimpressive.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height crop&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/red_blob_mapgen4.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/red_blob_mapgen4.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://www.redblobgames.com/maps/mapgen4/&quot;&gt;Amit Patel's &lt;b&gt;Mapgen4&lt;/b&gt;&lt;/a&gt; (as well as its &lt;a href=&quot;https://www.redblobgames.com/maps/mapgen2/&quot;&gt;previous iterations&lt;/a&gt;) is also quite interesting, especially since it's quite &lt;a href=&quot;http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/&quot;&gt;thoroughly&lt;/a&gt; &lt;a href=&quot;https://simblob.blogspot.com/search/label/mapgen4&quot;&gt;documented&lt;/a&gt; on his blog. The fundamental data-structure used by Magpen4 are relaxed voronoi diagrams and their dual (delaunay triangulations). This allows it to generate interesting maps without the obvious artifacts often seen with uniform grids. Elevation and generation parameters can be edited quite intuitively in Mapgen4 --- with procedural generation of rivers, precipitation, and biomes. However, the most interesting part for me personally is the rendering. It utilizes a traditional rendering pipeline with an oblique projection and more artistic lighting, to achieve a style that is quite close to a hand-drawn look.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height fill&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/dragons_abound.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/dragons_abound.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://heredragonsabound.blogspot.com&quot;&gt;Scott Turner's &lt;b&gt;Dragons Abound&lt;/b&gt;&lt;/a&gt; procedurally generates and draws fantasy maps, with quite beautiful and adaptable results that are already often near-indistinguishable from hand drawn maps. Although the main focus seems to be on drawing the maps, which informed some of the design decisions, there are also some simulation-based approaches to determine climate-zones, precipitation and rivers. There are some technical similarities to Amit Patel's approach (insofar as both use delaunay triangulations as their fundamental data-structure). In contrast, Dragons Abound seems to use a much larger number of vertices, which allows it to support smaller features and produce more natural shapes.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height crop&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/azgaar.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/azgaar.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://azgaar.github.io/Fantasy-Map-Generator/&quot;&gt;&lt;b&gt;Azgaar's Fantasy Map Generator&lt;/b&gt;&lt;/a&gt; is another fascinating project, as it not only provides a working web application to interactively generate detailed fantasy maps but is also &lt;a href=&quot;https://github.com/Azgaar/Fantasy-Map-Generator&quot;&gt;open source&lt;/a&gt;. Additionally, there is even a &lt;a href=&quot;https://azgaar.wordpress.com&quot;&gt;blog&lt;/a&gt; with interesting details about the generator’s design and innerworkings, which --- sadly --- is currently inactive. Similar to the previous two, this generator uses a voronoi diagram, but the focus seems to lie much more on the generation. The most impressive part of this project is probably the insane amount of possible customization and editing options of the generated maps.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height crop&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/undiscoveredworlds.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/undiscoveredworlds.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://undiscoveredworlds.blogspot.com&quot;&gt;JonathanCR's &lt;b&gt;Undiscovered Worlds&lt;/b&gt;&lt;/a&gt; is a procedural world generation project, inspired by Dragons Abound. But this project focuses more on generating large complete worlds, instead of drawing maps with relatively small/local scale, which is especially interesting as its close to my current plans.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height crop&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/wonderdraft.jpg&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/wonderdraft.jpg&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://www.wonderdraft.net&quot;&gt;&lt;b&gt;Wonderdraft&lt;/b&gt;&lt;/a&gt; is a quite powerful and easy to use editor to manually create fantasy maps. It doesn't really provide much in terms of procedural generation, but might be an interesting reference as far as user editing of the generated data is concerned. There might also be an interesting potential in integrating a more powerful procedural generation toolkit with it, but --- in the absence of any API or documented file format --- that seems to be unrealistic for now.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height fill_black&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/voxel_farm.gif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/voxel_farm.gif&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;&lt;a href=&quot;https://www.voxelfarm.com/index.html&quot;&gt;Miguel Cepero's &lt;b&gt;Voxel Farm&lt;/b&gt;&lt;/a&gt; is another interesting project --- and his &lt;a href=&quot;http://procworld.blogspot.com&quot;&gt;blog&lt;/a&gt; is one of the reasons I originally became interested in 3D graphics and procedural generation. In contrast to most projects on this list, it generates not just a map of a world, but an actual interactive 3D world. This is obviously far outside of any reasonable scope for my project, but some of the more abstract ideas might still be interesting.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height fill_white&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/Procedural Tectonic Planets.gif&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/Procedural Tectonic Planets.gif&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;On the academic side of things, &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02136820/file/2019-Procedural-Tectonic-Planets.pdf&quot;&gt;Cortial et al.'s 2019 paper &lt;b&gt;Procedural Tectonic Planets&lt;/b&gt;&lt;/a&gt; proposes a model to procedurally generate planets using a simplified simulation of plate tectonics. Although the authors sadly didn't provide their source code or an executable to reproduce their results, the provided images and &lt;a href=&quot;https://www.youtube.com/watch?v=GJQVl6Xld0w&quot;&gt;video&lt;/a&gt; look quite promising.&lt;/figcaption&gt;
&lt;/figure&gt;

  &lt;figure class=&quot;captioned_image fixed_height crop&quot;&gt;
	&lt;a class=&quot;open_img&quot; href=&quot;/assets/images/01/Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion.png&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; onclick=&quot;show_image_overlay(event, this)&quot;&gt;
		&lt;img style=&quot;float:none; max-height:20em&quot; src=&quot;/assets/images/01/Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion.png&quot; alt=&quot;&quot; /&gt;
	&lt;/a&gt;
  &lt;figcaption&gt;Another study worth mentioning here is &lt;a href=&quot;https://hal.inria.fr/hal-01262376/document&quot;&gt;&lt;b&gt;Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion&lt;/b&gt; by Cordonnier et al. from 2017&lt;/a&gt;. It uses tectonic uplift and hydraulic erosion data to generate plausible terrains. Although the scale is more local than my current plans, this might be an interesting approach to simulate erosion and river formation at a relatively high level, too.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/div&gt;

&lt;h2 id=&quot;project-aims&quot;&gt;Project Aims&lt;/h2&gt;

&lt;p&gt;The attentive reader might wonder: what exactly is it that I’m trying to achieve here?&lt;/p&gt;

&lt;p&gt;The main focus of my project is pretty similar to that of  Undiscovered Worlds. I’m interested in generating plausible worlds, that can be explored, used and processed further by others. Of course, I’ll also have to visualize my results in the form of maps and provide some form of user input, but that probably won’t be anything to fancy.&lt;/p&gt;

&lt;p&gt;Apart from that, I have three main aims for this project guiding my design decisions.&lt;/p&gt;

&lt;h3 id=&quot;simulation-instead-of-noise&quot;&gt;Simulation Instead of Noise&lt;/h3&gt;
&lt;p&gt;As I’m mostly interested in the world generation aspect, I want to try to reduce the amount of random input and noise to a minimum and rely on simulations instead. A model based on a large amount of complexly layered noise might generate interesting worlds, but that is entirely thanks to how that noise is modulated instead of any real meaningful capabilities of the system. And as an effect of this, the generational space of such models is inherently limited to the small subset for which the parameters have been hand-tuned. As John von Neumann famously said:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;With four parameters I can fit an elephant, and with five I can make him wiggle his trunk.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My goal is to create a more generalized system that can generate a wide variety of interesting worlds.&lt;/p&gt;

&lt;p&gt;“Interesting” of course is subjective and means many different things to different people. Personally, I think one intriguing way to try to measure “interesting” in this context is in terms of the entropy of the generated world, i.e. its information density, uncertainty or “surprisingness”&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;&amp;quot;The world is made up of four elements: Earth, Air, Fire and Water.  This is a fact well known even to Corporal Nobbs. It’s also wrong.  There’s a fifth element, and generally it’s called Surprise.&amp;quot; -- Sir Terry Pratchett &quot; data-title=&quot;&amp;quot;The world is made up of four elements: Earth, Air, Fire and Water.  This is a fact well known even to Corporal Nobbs. It’s also wrong.  There’s a fifth element, and generally it’s called Surprise.&amp;quot; -- Sir Terry Pratchett &quot;&gt;[2]&lt;/sup&gt;. On one end of the spectrum, with an entropy near zero, we have an entirely flat featureless plane. Even if beige is your favorite color and your favorite ice cream flavor is “cold”, we can probably agree that this world wouldn’t be terribly “interesting”. On the other end, with a large entropy, we end up with a comprehensively incomprehensible world — where everything appears to be random noise because nothing is correlated.&lt;/p&gt;

&lt;p&gt;Noise-based terrain (while often beautiful) would probably lie mostly on the low-entropy-end — think rolling hills, uniform mountains and general predictability — while our real world would be more on the high-end. The sweat spot I’m aiming for lies somewhere between these two — less predictable than Perlin noise, but also not as opaque and complex as the real world.&lt;/p&gt;

&lt;p&gt;In other words, what I want to achieve here is a &lt;em&gt;comprehensible&lt;/em&gt; amount of &lt;em&gt;meaningful&lt;/em&gt; information in the generated worlds. Meaning every visible feature should have a &lt;em&gt;cause&lt;/em&gt; that can be discovered by the user, e.g. matching coasts where continents split apart, canyons where rivers used to flow, settlements where they actually make sense with names based on their surroundings and history. Aside from the general technical difficulty, striking the right balance will also be a problem here: An unbroken chain of cause and effect is utterly meaningless if it’s too complex to be easily comprehensible. The generated world becomes no more interesting than one based purely on random noise.&lt;/p&gt;

&lt;h3 id=&quot;realistic-scale&quot;&gt;Realistic Scale&lt;/h3&gt;

&lt;p&gt;My second central aim for this project is the scale at which I want to generate worlds. To support physically meaningful parameters and allow for an easier comparison with the real world, I’m planning to generate spherical worlds on roughly the same scale as the earth, i.e. a radius of about 6’000 km.&lt;/p&gt;

&lt;p&gt;To support such a large scale with any amount of local details, I’m going to use a triangle mesh to store the elevation and other information. This should allow for a wide variety of resolutions based on local requirements (i.e., fewer vertices in the ocean and more near coasts and mountains). This should also solve most of the problems and artifacts, usually encountered when one tries to use uniform rectangular grids like bitmaps on a spherical surface.&lt;/p&gt;

&lt;p&gt;I’m currently planning to develop my generator in a top-down fashion: starting with the largest terrain features (like tectonic plates and mountains) and moving on to smaller scale details like rivers, caves and settlements from there. Although the dynamic resolution of a triangle mesh should lend itself well to such a generation approach, I might come to a point in the future where I need to split the system into multiple generators for different scales (e.g., erosion at the scale of a continent or mountain range vs. the scale of a local river). However, that should also mesh quite well with the triangle mesh approach, as the small-scale-generator could use the vertices from the high-level generator, refine them further and generate new information matching the constraints determined by the existing vertices. There will also be many interesting problems there, like the usual problem with discontinuities at the border between cells, of course. But that is far enough in the future that I probably shouldn’t concern myself with it, just now. That is clearly future-me’s problem, who is much more experienced than me, anyway.&lt;/p&gt;

&lt;h3 id=&quot;reusability&quot;&gt;Reusability&lt;/h3&gt;

&lt;p&gt;My final aim for this project stems from an inkling that I might have bitten off more than I can chew here. Because of that, I want to build the project in a fashion that even in its incomplete state, parts of it should still be useful or at least interesting to others. To achieve this, I’ve not just &lt;a href=&quot;https://gitlab.com/proc_world_gen&quot;&gt;open sourced (most) of the project&lt;/a&gt; but also plan to structure it as modular as possible.&lt;/p&gt;

&lt;p&gt;I already laid the foundation for this with a basic C-API for the world-model and generation passes. While I’ll initially just work on a C++API-Wrapper to use in my code, the C-API core should also allow for future interoperability with other languages like Python or C#.&lt;/p&gt;

&lt;p&gt;Based on this I plan to develop a simple graphical editor as a debugging tool and construct all the actual procedural generation passes as self-contained reusable modules, that can be loaded as plugins (.dll/.so) at runtime. So, others should (at least in theory) be able to modify (or salvage) any interesting parts, extend the system with new functionality or use it as a starting point for their own projects.&lt;/p&gt;

&lt;h2 id=&quot;outlook&quot;&gt;Outlook&lt;/h2&gt;

&lt;p&gt;With the goal in mind and the course set: what’s next?&lt;/p&gt;

&lt;p&gt;While I would really like to dive directly into exploring ideas for procgen algorithms and interesting simulation approaches, I think I should document and discuss some of the groundwork, first. To do so, the next couple of posts will mostly focus on how the world is modeled, stored, and modified in the code&lt;sup class=&quot;footnote&quot; onclick=&quot;showFootnote(this)&quot; title=&quot;In other words, the next three posts can be skipped by anyone who isn't deeply fascinated by data structures, gets bored easily (despite some colorful pictures!) or suffers from a pathological fear of graph theory.&quot; data-title=&quot;In other words, the next three posts can be skipped by anyone who isn't deeply fascinated by data structures, gets bored easily (despite some colorful pictures!) or suffers from a pathological fear of graph theory.&quot;&gt;[3]&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Having that down, there are some (hopefully a bit lighter) topics, I would like to explore:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Simulating plate tectonics to generate realistic high-level features (probably based on &lt;a href=&quot;https://hal.archives-ouvertes.fr/hal-02136820/file/2019-Procedural-Tectonic-Planets.pdf&quot;&gt;Procedural Tectonic Planets by Cortial et al.&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Extending and refining the current API&lt;/li&gt;
  &lt;li&gt;Creating a renderer to display the generated information in a compact way that is easy to decipher&lt;/li&gt;
  &lt;li&gt;Creating easy to use tools to modify the generated worlds, both for debugging and artistic inputs&lt;/li&gt;
  &lt;li&gt;Simulating large scale weather patterns to determine precipitation, average temperatures, and biomes&lt;/li&gt;
  &lt;li&gt;Model drainage basins and high-level water flow to generate river systems&lt;/li&gt;
  &lt;li&gt;Generate detail-maps for smaller areas, based on the high-level features from the coarser large-scale map&lt;/li&gt;
  &lt;li&gt;Simulate coarse erosion based on this (maybe similar to &lt;a href=&quot;https://hal.inria.fr/hal-01262376/document&quot;&gt;Large Scale Terrain Generation from Tectonic Uplift and Fluvial Erosion by Cordonnier et al.&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Simulate large scale changes in world climate (i.e., ice ages and glaciation) to reproduce some of the more interesting terrain features we have on earth: fjords, &lt;a href=&quot;https://en.wikipedia.org/wiki/Doggerland&quot;&gt;sunken landmasses&lt;/a&gt;, interesting features on continental shelves, …&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

</description>
                <link>https://second-system.de/2021/03/04/yggdrasill</link>
                <guid>https://second-system.de/2021/03/04/yggdrasill</guid>
                <pubDate>Thu, 04 Mar 2021 00:00:00 +0000</pubDate>
        </item>


</channel>
</rss>
