<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en" xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
  <id>urn:uuid:0ea03cc2-8001-4afd-b70d-ef1d3054ef57</id>
  <title>Christopher Crouzet</title>
      <updated>2022-05-30T12:00:00Z</updated>

  <author>
    <name>Christopher Crouzet</name>
  </author>
  <link href="https://christophercrouzet.com/feed" rel="self" type="application/atom+xml" />
  <link href="https://christophercrouzet.com" rel="alternate" type="text/html" title="Christopher Crouzet" />
  <subtitle>CG artist and software developer amongst other things. I’m working on VFX and animation movies when not globe trotting.</subtitle>

    <entry>
      <id>urn:uuid:bda2a6fb-9777-41eb-a5c7-97db9593b22e</id>
      <title>NVIDIA’s Warp for Houdini</title>
      <updated>2022-05-30T12:00:00Z</updated>

      <published>2022-05-30T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/nvidia-warp-houdini" rel="alternate" type="text/html" title="NVIDIA’s Warp for Houdini" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/nvidia-warp-houdini/warp.jpg" />

      <content type="html">
        &lt;p&gt;I’ve only found out about &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;developer.nvidia.com&#x2F;warp-python&quot;&gt;NVIDIA’s Warp&lt;&#x2F;a&gt; recently and I really like how it makes writing performant CUDA kernels accessible to Python developers, without anything being in the way to getting started quickly.&lt;&#x2F;p&gt;
&lt;p&gt;After having spent the past 3 years at work developing HDAs, tools, and workflows for &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sidefx.com&#x2F;products&#x2F;houdini&quot;&gt;Houdini&lt;&#x2F;a&gt;, the first thing that popped into my mind was to check whether a marriage between both apps would be possible. I thought it’d be neat to expose Warp to Houdini users, through the SOP context.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that it was quite simple to get &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;twitter.com&#x2F;christophercrzt&#x2F;status&#x2F;1526508992003907584&quot;&gt;a Warp kernel updating some point positions&lt;&#x2F;a&gt; but that wasn’t quite enough to be satisfying.&lt;&#x2F;p&gt;
&lt;p&gt;Indeed, that solution was all fairly hard-coded to my specific test case so the next itch was to figure out whether I could build an HDA that would allow reading and writing from&#x2F;to any geometry attribute.&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, I wanted to take this exercise as an opportunity to share how I personally like to author HDAs in Houdini. But I’ll leave this for another blog post, coming up shortly!&lt;&#x2F;p&gt;
&lt;h2&gt;The (Lack Of) Process&lt;&#x2F;h2&gt;
&lt;p&gt;Long story short, it was all fairly straightforward to put in place a working solution.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;nvidia-warp-houdini&#x2F;warp.jpg&quot; alt&#x3D;&quot;Warp&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;One trick was to use a Solver SOP node to support simulations and the caching of frames, so that we can scrub through the timeline without needing to recompute everything.&lt;&#x2F;p&gt;
&lt;p&gt;As for the rest, it was mostly a matter of converting the data back and forth between both APIs, while... uh... not making silly mistakes that would cause the node to segfault when the input topology would change? Ahem.&lt;&#x2F;p&gt;
&lt;p&gt;The HDA’s interface is somewhat similar to an Attrib Wrangle SOP node mixed with a Solver SOP, with an additional set of parameters allowing to define the inputs and outputs attributes that are to be passed to the kernel.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;nvidia-warp-houdini&#x2F;interface.png&quot; alt&#x3D;&quot;Interface&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There is also a button that allows generating the code of the kernel function, based on the parameter values from the attributes tab.&lt;&#x2F;p&gt;
&lt;p&gt;To validate the HDA and help documenting its usage, I worked on a couple of examples which aim at keeping things as simple as possible to allow users diving in and understanding what’s required to get started, instead of trying to generate quality results. So if these results look cheap and imperfect (they do!), you can blame me, not the tools.&lt;&#x2F;p&gt;
&lt;h2&gt;Example: Ripple Deformer&lt;&#x2F;h2&gt;
&lt;p&gt;The first example that I built for my tests was a simple ripple deformer. In itself, it bypasses the simulation mechanism since it builds upon the same initial point positions stored into the &lt;em&gt;rest&lt;&#x2F;em&gt; attribute, instead of reading the point positions from the previous step.&lt;&#x2F;p&gt;
&lt;p&gt;The objective here was to make sure that it was possible to change parameter values while the solver is running, even when it involves retopologizing the input mesh.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;video src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;nvidia-warp-houdini&#x2F;ripple-deformer.mp4&quot; poster&#x3D;&quot;&quot; controls&#x3D;&quot;&quot;&gt;&lt;&#x2F;video&gt;&lt;&#x2F;span&gt;&lt;span&gt;look ma, I can even change its topology and scrub through the timeline!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;# -*- coding: utf-8 -*-&lt;&#x2F;span&gt;

&lt;span&gt;import&lt;&#x2F;span&gt; warp &lt;span&gt;as&lt;&#x2F;span&gt; wp

&lt;span&gt;@wp.kernel&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;run&lt;&#x2F;span&gt;(&lt;span&gt;
    P_array: wp.array(&lt;span&gt;dtype&#x3D;wp.vec3&lt;&#x2F;span&gt;),
    count_array: wp.array(&lt;span&gt;dtype&#x3D;wp.int32&lt;&#x2F;span&gt;),
    amplitude_array: wp.array(&lt;span&gt;dtype&#x3D;wp.float32&lt;&#x2F;span&gt;),
    frequency_array: wp.array(&lt;span&gt;dtype&#x3D;wp.float32&lt;&#x2F;span&gt;),
    rest_array: wp.array(&lt;span&gt;dtype&#x3D;wp.vec3&lt;&#x2F;span&gt;),
    time: wp.float32,
    time_step: wp.float32,
&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    tid &#x3D; wp.tid()

    &lt;span&gt;# Detail attributes.&lt;&#x2F;span&gt;
    count &#x3D; count_array[&lt;span&gt;0&lt;&#x2F;span&gt;]
    amplitude &#x3D; amplitude_array[&lt;span&gt;0&lt;&#x2F;span&gt;]
    frequency &#x3D; frequency_array[&lt;span&gt;0&lt;&#x2F;span&gt;]

    &lt;span&gt;# Point attributes.&lt;&#x2F;span&gt;
    P &#x3D; P_array[tid]
    rest &#x3D; rest_array[tid]

    &lt;span&gt;# Compute the ripple deformation.&lt;&#x2F;span&gt;
    d &#x3D; wp.length(P)
    h &#x3D; sin(-d * frequency + time) * amplitude
    P &#x3D; wp.vec3(rest[&lt;span&gt;0&lt;&#x2F;span&gt;], rest[&lt;span&gt;1&lt;&#x2F;span&gt;] + h, rest[&lt;span&gt;2&lt;&#x2F;span&gt;])

    &lt;span&gt;# Write out the point attributes.&lt;&#x2F;span&gt;
    P_array[tid] &#x3D; P

    &lt;span&gt;# Keep track of how many kernels have been run.&lt;&#x2F;span&gt;
    count_array[&lt;span&gt;0&lt;&#x2F;span&gt;] &#x3D; count + &lt;span&gt;1&lt;&#x2F;span&gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2&gt;Example: Bouncing Ball&lt;&#x2F;h2&gt;
&lt;p&gt;This time, I wanted to try out a &lt;em&gt;real&lt;&#x2F;em&gt; simulation. Something that builds up upon the values computed in the previous step. So, uh... a bouncing ball, anyone?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;video src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;nvidia-warp-houdini&#x2F;bouncing-ball.mp4&quot; poster&#x3D;&quot;&quot; controls&#x3D;&quot;&quot;&gt;&lt;&#x2F;video&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;# -*- coding: utf-8 -*-&lt;&#x2F;span&gt;

&lt;span&gt;import&lt;&#x2F;span&gt; warp &lt;span&gt;as&lt;&#x2F;span&gt; wp

&lt;span&gt;@wp.kernel&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;run&lt;&#x2F;span&gt;(&lt;span&gt;
    P_array: wp.array(&lt;span&gt;dtype&#x3D;wp.vec3&lt;&#x2F;span&gt;),
    v_array: wp.array(&lt;span&gt;dtype&#x3D;wp.vec3&lt;&#x2F;span&gt;),
    pscale_array: wp.array(&lt;span&gt;dtype&#x3D;wp.float32&lt;&#x2F;span&gt;),
    bounce_array: wp.array(&lt;span&gt;dtype&#x3D;wp.float32&lt;&#x2F;span&gt;),
    drag_array: wp.array(&lt;span&gt;dtype&#x3D;wp.float32&lt;&#x2F;span&gt;),
    time: wp.float32,
    time_step: wp.float32,
&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    tid &#x3D; wp.tid()

    &lt;span&gt;# Point attributes.&lt;&#x2F;span&gt;
    P &#x3D; P_array[tid]
    v &#x3D; v_array[tid]
    pscale &#x3D; pscale_array[tid]
    bounce &#x3D; bounce_array[tid]
    drag &#x3D; drag_array[tid]

    radius &#x3D; pscale * &lt;span&gt;0.5&lt;&#x2F;span&gt;

    &lt;span&gt;# Apply the drag force.&lt;&#x2F;span&gt;
    v &#x3D; v * (&lt;span&gt;1.0&lt;&#x2F;span&gt; - drag * time_step)

    &lt;span&gt;# Bounce the particle if it went under the ground.&lt;&#x2F;span&gt;
    &lt;span&gt;if&lt;&#x2F;span&gt; P[&lt;span&gt;1&lt;&#x2F;span&gt;] &amp;lt; radius:
        P &#x3D; wp.vec3(P[&lt;span&gt;0&lt;&#x2F;span&gt;], radius, P[&lt;span&gt;2&lt;&#x2F;span&gt;])
        v &#x3D; wp.vec3(v[&lt;span&gt;0&lt;&#x2F;span&gt;], -v[&lt;span&gt;1&lt;&#x2F;span&gt;] * bounce, v[&lt;span&gt;2&lt;&#x2F;span&gt;])

    &lt;span&gt;# Apply the gravitational force.&lt;&#x2F;span&gt;
    v &#x3D; v + wp.vec3(&lt;span&gt;0.0&lt;&#x2F;span&gt;, -&lt;span&gt;9.81&lt;&#x2F;span&gt; * time_step, &lt;span&gt;0.0&lt;&#x2F;span&gt;)

    &lt;span&gt;# Solve the position.&lt;&#x2F;span&gt;
    P &#x3D; P + v * time_step

    &lt;span&gt;# Write out the point attributes.&lt;&#x2F;span&gt;
    P_array[tid] &#x3D; P
    v_array[tid] &#x3D; v
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2&gt;Closing Notes&lt;&#x2F;h2&gt;
&lt;p&gt;This HDA is quite experimental and has &lt;strong&gt;not&lt;&#x2F;strong&gt; been thoroughly tested, so use at your own risk!&lt;&#x2F;p&gt;
&lt;p&gt;Oh, and it’s Python 3 only, so you might need at least Houdini 19.&lt;&#x2F;p&gt;
&lt;p&gt;If you scrolled all the way down just to find where to get the HDA, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;nvidia-warp-houdini&quot;&gt;here is the link on GitHub&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:b258ae18-f8f8-424c-bbba-3bd4d2c9fc16</id>
      <title>Seamless and Collaborative Generation of Infinite Worlds</title>
      <updated>2022-04-26T12:00:00Z</updated>

      <published>2022-04-26T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/infinite-world" rel="alternate" type="text/html" title="Seamless and Collaborative Generation of Infinite Worlds" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/infinite-world/tile-blending.jpg" />

      <content type="html">
        &lt;p&gt;I am worried about what some of the tech giants truly envision when it comes to the concepts of metaverse and of web 3.0, but if there is one thing that I like in these conversations, it’s the idea of having some sort of open worlds available to connect us with other people.&lt;&#x2F;p&gt;
&lt;p&gt;In 1999, we had this virtual world in France named &lt;em&gt;Le Deuxième Monde&lt;&#x2F;em&gt; (&lt;em&gt;The Second World&lt;&#x2F;em&gt;), and I remember finding it to be a wonderful and captivating experience. I met some lovely people there with whom I’m still connected to this day, and others who even helped me getting started in 3D, which eventually kickstarted my career in the animation and VFX film industry.&lt;&#x2F;p&gt;
&lt;p&gt;Wouldn’t it be cool if we had some interactive experiences like that today, in worlds that would be authored by their users, and where we could somehow satisfy our innate desires to explore an unknown land and to form communities?&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Design Objectives&lt;&#x2F;h2&gt;
&lt;p&gt;One thing that I learned during my time as a technical artist, it’s that you usually don’t want to take away control from the users by fully automatizing some systems. Every tool, every feature, should always allow users to override their behaviour and to drive the result in whichever direction they like.&lt;&#x2F;p&gt;
&lt;p&gt;That’s also true for such open worlds. Yes, we might want to be able to generate them to some extent, but how cool would it be if users could also author their little piece of land however they’d like?&lt;&#x2F;p&gt;
&lt;p&gt;So that’s what I decided to tackle during the 4 days of the Easter break!&lt;&#x2F;p&gt;
&lt;p&gt;The goal was to build a quick proof of concept in Houdini where an infinite world can be created based on a set of tiles that are provided, and where users can focus on getting their own tile to look good in a standalone fashion, while having the confidence that they would seamlessly blend with the rest of the world.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;terrain.jpg&quot; alt&#x3D;&quot;Terrain&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;terrain&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To keep the scope of this experiment small, the result shall not be expected to represent the final look of the terrain. Instead, the intention is to use it as an &lt;strong&gt;intermediate&lt;&#x2F;strong&gt; step, on top of which more procedural work can be applied, such as adding hard edges depending on the curvature, running an erosion pass, adding roads and rivers, and whatnot.&lt;&#x2F;p&gt;
&lt;h2&gt;SDF All the Things!&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Signed_distance_function&quot;&gt;Signed Distance Function&lt;&#x2F;a&gt; (SDF) is one of these techniques that is just too good to ignore.&lt;&#x2F;p&gt;
&lt;p&gt;Knowing the distance to a surface for cheap makes it well suited for algorithms involving collision detection, such as ray marching, and many fluid and rigid body solvers used in the film industry when making VFXs.&lt;&#x2F;p&gt;
&lt;p&gt;Not only that but it’s also a prime choice for procedural modeling where it makes organically combining multiple arbitrary shapes a walk in the park.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, I’m a big fan of SDFs. And so are the masterminds behind &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.shadertoy.com&quot;&gt;Shadertoy&lt;&#x2F;a&gt;, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.mediamolecule.com&#x2F;games&#x2F;dreams&quot;&gt;Dreams&lt;&#x2F;a&gt;, and &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.clayxels.com&quot;&gt;Clayxels&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The more I thought about it, the more the smooth blending property that comes with SDFs seemed like a must-have to address the needs of seamlessly connecting adjacent tiles.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;video src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;smooth-blending.webm&quot; poster&#x3D;&quot;&quot; controls&#x3D;&quot;&quot; loop&#x3D;&quot;&quot;&gt;&lt;&#x2F;video&gt;&lt;&#x2F;span&gt;&lt;span&gt;smooth blending using SDFs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The smooth blending formula can be found on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;iquilezles.org&#x2F;articles&#x2F;smin&quot;&gt;Inigo Quilez’s excellent website&lt;&#x2F;a&gt; where it is described as a function named &lt;code&gt;smin()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Porting that to Houdini was fairly straighforward.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;smooth-blending-setup.png&quot; alt&#x3D;&quot;Smooth Blending Setup&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;SDF smooth blending setup in Houdini&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Tiling&lt;&#x2F;h2&gt;
&lt;p&gt;To divide the world up into equal areas, I decided to use hexagonal tiles—it seemed like a good opportunity for me to experiment with these, but they also look much more organic while remaining simple enough to build grids with.&lt;&#x2F;p&gt;
&lt;p&gt;Generating the grid was done in two steps. First, I created a bunch of points that would represent the center of each tile, and then I instantiated one hexagon on each of these points.&lt;&#x2F;p&gt;
&lt;p&gt;There’s probably a formal definition for that approach somewhere but I found out through some lucky experimentations that, to generate these instance points, I could use... more hexagons!&lt;&#x2F;p&gt;
&lt;p&gt;For the first layer, I’d create an hexagon with a single division per edge. Then another hexagon for the second layer, this time with 2 divisions per edge, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;hex-grid-instances.png&quot; alt&#x3D;&quot;Hexagonal Grid&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;hexagonal grid with instance points&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The only trick being to know by how much to scale the hexagons for each layer but, after some empirical measurements, I figured it out to be equal to the expression &lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;84a818ceab2b1bffc82c961822268afb.svg&quot; alt&#x3D;&quot;scale &#x3D; \sqrt{(1 + i)^2 * 3}&quot;&gt; where &lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;865c0c0b4ab0e063e5caa3387c1a8741.svg&quot; alt&#x3D;&quot;i&quot;&gt; is a 0-based index representing the current layer.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;hex-grid.gif&quot; alt&#x3D;&quot;Hexagonal Grid&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;hexagonal grid generation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Safe Area&lt;&#x2F;h2&gt;
&lt;p&gt;One immediate drawback of smooth blending in the context of connecting adjacent tiles is that it actually modifies the tile’s geometry up to a specific number of voxels deep, with the actual amount of voxels directly depending on the value &lt;code&gt;k&lt;&#x2F;code&gt; used in the smooth blending formula.&lt;&#x2F;p&gt;
&lt;p&gt;So we need a sort of “safe area” to inform users that within it anything they do will be preserved as-is. However, outside of that safe zone, in a thin band near the boundaries of the tile, the terrain might change a little bit to accomodate for the smooth blending operation, and hence some edits like placing buildings and other structures might not be permitted in there—but others like roads and rivers might be alright.&lt;&#x2F;p&gt;
&lt;p&gt;Not only that but if we were to blend 2 adjacent tiles that are in direct contact, then the transition might be a bit... crude? So we need to allow for a bit of padding between the tiles in order for the blending to generate a smoother transition.&lt;&#x2F;p&gt;
&lt;p&gt;And there we have it, tiles being seamlessly connected to each other in an organic fashion while preserving their features within the safe areas.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;tile-blending.jpg&quot; alt&#x3D;&quot;Tile Blending&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;blending of 3 adjacent tiles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Ad Infinitum&lt;&#x2F;h2&gt;
&lt;p&gt;If we only had a fixed numbers of tiles as input that would never change, then we could go ahead and generate the whole terrain in one go through this approach.&lt;&#x2F;p&gt;
&lt;p&gt;However, remember that we’re after an infinite world here, so we need to be able to keep connecting new tiles to the already existing world as soon as they are being authored by the users.&lt;&#x2F;p&gt;
&lt;p&gt;For that, we need to be able to generate the seams on the go.&lt;&#x2F;p&gt;
&lt;p&gt;The approach that I went for was to subdivide the hexagonal grid to obtain another grid made of triangles, where each triangle is centered at the intersection of 3 tiles.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;cell-grid.png&quot; alt&#x3D;&quot;Cell Grid&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;cell grid&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;What this means is that, to generate the geometry for a single triangle cell, this solution requires the 3 surrounding tiles to be readily available. And to generate the geometry for a single hexagonal tile, with its seams, all the surrounding cells are required to be known.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;full-tile-blending.gif&quot; alt&#x3D;&quot;Full Tile Blending&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;blending of all the cells overlapping a single tile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The advantage of dividing each tile with these triangular cells is that their edge boundaries should pretty much line up, regardless of how much terrain sculpting was done in these areas by the users, which should make it straighforward to stitch them back together. To be honest, I’m not too sure how to explain this assumption with words but I can see pretty pictures in my mind telling me that it &lt;em&gt;should&lt;&#x2F;em&gt; be viable. Or not?&lt;&#x2F;p&gt;
&lt;p&gt;The geometry for these cells can be generated on a server as soon as new tiles are being added to the world, or they could also be generated on demand, just before a user is about to explore a new tile for the very first time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;cell-streaming.gif&quot; alt&#x3D;&quot;Cell Streaming&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;streaming of new cells&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Either way, the result is the same... a world whose boundaries can stretch to &lt;em&gt;infinity and beyond&lt;&#x2F;em&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;infinite-world&#x2F;infinite-world.png&quot; alt&#x3D;&quot;Infinite World&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;infinite world (kindof)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;What’s Next&lt;&#x2F;h2&gt;
&lt;p&gt;As mentioned before, this technique only aims at generating an intermediate representation of the terrain, so additional work would need to be done to give it a more interesting and polished look.&lt;&#x2F;p&gt;
&lt;p&gt;And by no mean is this proof of concept devoid of issues. For example, connecting adjacent tiles with the smooth function tends to create a bulge in the middle. That being said, I don’t foresee any of these issues to be a showstopper—they &lt;em&gt;should&lt;&#x2F;em&gt; all be addressable, and it &lt;em&gt;should&lt;&#x2F;em&gt; be possible to turn this approach into a reliable product.&lt;&#x2F;p&gt;
&lt;p&gt;Alas, I ran out of time to take this experiment further and need to refocus on my others, already too numerous, personal projects.&lt;&#x2F;p&gt;
&lt;p&gt;But maybe you’ll be the one to do something cool with it? If so, please share the result!&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;Yes, I’m aware that some games and platforms already provide similar experiences.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;Albeit with proper handling over the trolls and toxic behaviours. Hint: nicknames can be used but IDs should be required to be stored privately so that offenders can be held accountable for their action instead of letting them join back the world over and over again using different accounts.&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:ddefa94c-ddeb-4d9c-aaab-6858b71e231b</id>
      <title>Optimizing Pixar’s USD Compile Time</title>
      <updated>2022-04-23T12:00:00Z</updated>

      <published>2022-04-23T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/usd-compile-time" rel="alternate" type="text/html" title="Optimizing Pixar’s USD Compile Time" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/usd-compile-time/results.png" />

      <content type="html">
        &lt;p&gt;It’s been a long while since I’ve been wondering how I could possibly contribute to bringing a positive environmental impact to life by using my dev skills.&lt;&#x2F;p&gt;
&lt;p&gt;The opportunity finally showed up while doing some work on Pixar USD’s—I noticed that its codebase took around &lt;strong&gt;90 minutes&lt;&#x2F;strong&gt; to compile on my laptop (Intel i7-7700HQ, Ubuntu 20.04) using a single thread. It seemed &lt;em&gt;huge&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Even though this might not have the greatest impact in reducing energy waste, it was still an actionable item and could prove somewhat useful.&lt;&#x2F;p&gt;
&lt;p&gt;After all, USD is becoming mainstream across several industries now and with the amount of developers around the world working on improving its core, and the number of CI servers compiling the codebase over and over again before running tests, I thought... why not giving this a go? If all it does is to help with iteration times for all these developers, it’d still be a win!&lt;&#x2F;p&gt;
&lt;h2&gt;Definitely Not Zero Cost Abstractions&lt;&#x2F;h2&gt;
&lt;p&gt;I know it, you know it, we all know it—C++’s “zero cost abstractions” do in fact have a non-negligible cost, both in increased maintenance complexity but also in compile times. Right?&lt;&#x2F;p&gt;
&lt;p&gt;But still... how could a codebase possibly take 90 minutes to compile!? I was intrigued.&lt;&#x2F;p&gt;
&lt;p&gt;I fired up Aras Pranckevičius’s awesome &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;aras-p.info&#x2F;blog&#x2F;2019&#x2F;01&#x2F;16&#x2F;time-trace-timeline-flame-chart-profiler-for-Clang&quot;&gt;compiler profiler&lt;&#x2F;a&gt; and immediately got a clear picture of the problem.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;*** Expensive headers:
972063 ms: &#x2F;opt&#x2F;boost&#x2F;1.70.0&#x2F;include&#x2F;boost&#x2F;preprocessor&#x2F;iteration&#x2F;detail&#x2F;iter&#x2F;forward1.hpp (included 13959 times, avg 69 ms), included via:
  wrapTimestamp.cpp.o pyUtils.h pyInterpreter.h object.hpp object_core.hpp call.hpp arg_to_python.hpp function_handle.hpp caller.hpp  (320 ms)
  ...

904090 ms: build&#x2F;usd&#x2F;include&#x2F;pxr&#x2F;usd&#x2F;usd&#x2F;prim.h (included 369 times, avg 2450 ms), included via:
  modelAPI.cpp.o modelAPI.h apiSchemaBase.h schemaBase.h  (3114 ms)
  ...

887681 ms: build&#x2F;usd&#x2F;include&#x2F;pxr&#x2F;base&#x2F;tf&#x2F;pyObjWrapper.h (included 993 times, avg 893 ms), included via:
  aov.cpp.o aov.h types.h value.h  (1567 ms)
  ...

838714 ms: build&#x2F;usd&#x2F;include&#x2F;pxr&#x2F;usd&#x2F;usd&#x2F;object.h (included 374 times, avg 2242 ms), included via:
  wrapUtils.cpp.o wrapUtils.h  (3054 ms)
  ...

835586 ms: build&#x2F;usd&#x2F;include&#x2F;pxr&#x2F;base&#x2F;vt&#x2F;value.h (included 894 times, avg 934 ms), included via:
  aov.cpp.o aov.h types.h  (1702 ms)
  ...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Yes, that’s right. Over 900,000 ms were spent on compiling some of these headers! That’s more than &lt;strong&gt;15 minutes per header&lt;&#x2F;strong&gt; because they are being recompiled over and over again in hundreds, if not thousands, of compilation units. It’s just &lt;em&gt;nuts&lt;&#x2F;em&gt; that C++ encourages this.&lt;&#x2F;p&gt;
&lt;p&gt;Remember that C++ is based on C, and that C was designed to only have simple interfaces exposed in the header files (&lt;code&gt;.h&lt;&#x2F;code&gt;), while source files (&lt;code&gt;.c&lt;&#x2F;code&gt;) are reserved for their implementation. Header files were &lt;em&gt;never&lt;&#x2F;em&gt; meant to include actual implementation logic, and yet it’s &lt;em&gt;exactly&lt;&#x2F;em&gt; what C++ pushed for with templates. Take a few header files that include other header files that involve the STL, and there we have it—our definitely non-zero cost abstraction.&lt;&#x2F;p&gt;
&lt;h2&gt;Tackling the Issue at Its Source&lt;&#x2F;h2&gt;
&lt;p&gt;We now know that C++’s design is promoting long compile times, but what would it take then for developers to workaround this pitfall?&lt;&#x2F;p&gt;
&lt;p&gt;Well, if we’re fortunate enough to start a new C++ project from scratch, let’s just use headers how they were meant to be used in the first place, &lt;em&gt;duh&lt;&#x2F;em&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;Seriously, it’s absolutely feasible to:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;write headers that define C-like interfaces as much as possible—ban STL data types, use raw pointers, and avoid templates unless strictly necessary.&lt;&#x2F;li&gt;
&lt;li&gt;have headers that do not include other headers (with a few, rare, exceptions).&lt;&#x2F;li&gt;
&lt;li&gt;and... that’s it!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Yes, it does require a certain paradigm shift but it’s not all that unrealistic. In fact it’s exactly how Our Machinery has been doing it for their game engine, as described in their article &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;ourmachinery.com&#x2F;post&#x2F;physical-design&quot;&gt;Physical Design of The Machinery&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;After reading that article, I also have experimented with that approach in my personal work, such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;zero&quot;&gt;Zero: A Bunch of Single-File Libraries for C&#x2F;C++&lt;&#x2F;a&gt; for example, and although my experiments were on projects of a much smaller scale than The Machinery or USD, I still found it fairly challenging to undo my C++ habits at first, but it quickly became a much more natural process afterwards.&lt;&#x2F;p&gt;
&lt;p&gt;And if I managed to do it, I’m sure you can too!&lt;&#x2F;p&gt;
&lt;h2&gt;Tackling the Issue After the Fact&lt;&#x2F;h2&gt;
&lt;p&gt;Optimizing compile times on existing C++ projects without making breaking changes to their API is a whole different challenge. A much bigger one than I anticipated when I started to dive head first into doing just that for USD.&lt;&#x2F;p&gt;
&lt;p&gt;Strong from the profiling results, I knew what I needed to do: add support for &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Unity_build&quot;&gt;unity builds&lt;&#x2F;a&gt;. This way, instead of having an enormous amount of tiny compilation units, we’d have only a handful of larger ones and we won’t end up having to recompile the same expensive headers as many times.&lt;&#x2F;p&gt;
&lt;p&gt;“Easy-peasy”, I thought.&lt;&#x2F;p&gt;
&lt;p&gt;I started by modifying the project’s CMake configuration in order to make it create a new &lt;code&gt;.cpp&lt;&#x2F;code&gt; file for each module (e.g.: &lt;code&gt;pxr&#x2F;base&#x2F;arch&lt;&#x2F;code&gt;) that’d include all the other source files from the same module, and to make it compile just that one file instead of the other ones.&lt;&#x2F;p&gt;
&lt;h2&gt;Not So Fast&lt;&#x2F;h2&gt;
&lt;p&gt;Of course unity builds only work if all identifiers in the concatenated source files are unique. Which wasn’t the case in USD’s codebase.&lt;&#x2F;p&gt;
&lt;p&gt;Another less obvious issue is that some symbol resolution became ambiguous, as demonstrated in the following example:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;using&lt;&#x2F;span&gt; &lt;span&gt;namespace&lt;&#x2F;span&gt; boost;
&lt;span&gt;using&lt;&#x2F;span&gt; &lt;span&gt;namespace&lt;&#x2F;span&gt; std;

tuple foo; &lt;span&gt;&#x2F;&#x2F; is it “boost::tuple” or “std::tuple”?&lt;&#x2F;span&gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Hence the work required was two-fold:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;avoid identifiers clashing by wrapping them all in a unique namespace.&lt;&#x2F;li&gt;
&lt;li&gt;flatten all the namespaces by removing &lt;code&gt;using&lt;&#x2F;code&gt; statements and referencing each symbol with their full name.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2&gt;Early Results&lt;&#x2F;h2&gt;
&lt;p&gt;I started to do these changes through a lot of search and replace directly within my IDE, sometimes involving some advanced regular expressions, but it quickly became apparent that this wouldn’t cut it. It was both too tedious and error-prone.&lt;&#x2F;p&gt;
&lt;p&gt;I still decided to pursue this approach just enough to cover a couple of modules, as to get an idea of whether I was going in the right direction.&lt;&#x2F;p&gt;
&lt;p&gt;Many hours later spread over a few weeks, I finally finished converting the &lt;code&gt;pxr&#x2F;base&#x2F;arch&lt;&#x2F;code&gt; and &lt;code&gt;pxr&#x2F;base&#x2F;tf&lt;&#x2F;code&gt; modules. The result was quite encouraging with a &lt;strong&gt;4x speed up&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;That was 2 modules done. Cool, now onto finding a more humane approach to refactoring all of the &lt;strong&gt;55 modules&lt;&#x2F;strong&gt;, ahem.&lt;&#x2F;p&gt;
&lt;h2&gt;Using the AST&lt;&#x2F;h2&gt;
&lt;p&gt;I had to find a way to somehow automatize the refactoring. Not only that but, unlike a regular expression approach, I wanted the process to be done in a semantically robust fashion as to give me more confidence that the changes done would be valid.&lt;&#x2F;p&gt;
&lt;p&gt;Working at the AST level seemed like the obvious choice and I also kinda liked the idea of learning a new API. So I jumped onto the opportunity to get started with Clang’s AST API, yay!&lt;&#x2F;p&gt;
&lt;p&gt;“Surely it should be easy to find all the symbols in a codebase and prefix them with a namespace using the AST”, or so I thought. He he he, how naive of me.&lt;&#x2F;p&gt;
&lt;p&gt;I know it, you know it, we all know it—C++ is an (unnecessarily) complex language. And it turns out that this complexity is fairly well reflected in Clang’s AST API.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll skip the details but it turned out to be a &lt;strong&gt;LOT&lt;&#x2F;strong&gt; of pain and to be mentally draining.&lt;&#x2F;p&gt;
&lt;p&gt;It took me &lt;strong&gt;months&lt;&#x2F;strong&gt; to iterate on my refactoring tools, with many setbacks, in order to get them as close as possible to the desired result. But, despite my perseverance, I just could not figure out how to make the refactoring process 100% automatic. There were always some bits of code that needed to be manually patched here and there after running the tools.&lt;&#x2F;p&gt;
&lt;p&gt;In fact, Clang’s AST API operates after the C++ preprocessor pass has finished evaluating, meaning that any code wrapped into &lt;code&gt;#if&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;#ifdef&lt;&#x2F;code&gt; statements might be discarded and left untouched by the refactoring tools. As a result, since I’ve run these tools using an Ubuntu environment, some code paths specific to Windows, macOS, Metal, and others, are required to be manually patched.&lt;&#x2F;p&gt;
&lt;h2&gt;Final Results&lt;&#x2F;h2&gt;
&lt;p&gt;Despite all the adversity, I still managed to refactor the whole USD codebase and to make it compile using an unity build approach. Did the early results carry over?&lt;&#x2F;p&gt;
&lt;p&gt;Yeah, mostly! I mean, look at this sexy graph!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;usd-compile-time&#x2F;results.png&quot; alt&#x3D;&quot;Results&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You’ll note that the timings for g++ 11.1.0 (unity&#x3D;ON) with 8 threads are only an estimate, and that’s because my laptop runs out of memory for that one.&lt;&#x2F;p&gt;
&lt;p&gt;Overall, that’s still a speed-up of 2.7-3.5 times on my laptop. The compilation that took 90 minutes on a single thread now finishes in &lt;strong&gt;26 minutes&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Which is admittedly still a lot but it’s a &lt;em&gt;start&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;Still Want to Know More?&lt;&#x2F;h2&gt;
&lt;p&gt;You might find what you’re looking for in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;PixarAnimationStudios&#x2F;USD&#x2F;pull&#x2F;1805&quot;&gt;this pull request on GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;As for the refactoring tools, they’re available on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;pxr-usd-unity-build&quot;&gt;a GitHub repository&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;What’s Next?&lt;&#x2F;h2&gt;
&lt;p&gt;You know what would be fun? Optimizing the compile times of the LLVM project... &lt;em&gt;tee-hee&lt;&#x2F;em&gt;!&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:a3d704db-1dcc-4fe2-8aec-d2813b9af2f1</id>
      <title>Rexo: A Unit Testing Framework in C (Part 2)</title>
      <updated>2021-10-15T13:00:00Z</updated>

      <published>2021-08-08T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/rexo-part-2" rel="alternate" type="text/html" title="Rexo: A Unit Testing Framework in C (Part 2)" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/rexo-part-2/code.jpg" />

      <content type="html">
        &lt;p&gt;I revamped my C89&#x2F;C++ unit testing framework around the end of 2019 but I never really shared what’s cool about it... let’s finally address this!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;rexo&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;rexo-part-2&#x2F;code.jpg&quot; alt&#x3D;&quot;Rexo&quot;&gt;&lt;span&gt;View on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When I implemented &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;rexo&quot;&gt;Rexo&lt;&#x2F;a&gt;’s first iteration in 2018, which I already wrote about &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;rexo&quot;&gt;here&lt;&#x2F;a&gt;, I mostly set out to understand why the majority of the unit testing frameworks for C required so much boilerplate in comparison to the ones offered in C++.&lt;&#x2F;p&gt;
&lt;p&gt;The short answer is that the C standard doesn’t offer any portable way to implement automatic registration of tests.&lt;&#x2F;p&gt;
&lt;p&gt;I ended up going ahead with the usual approach but, for someone who prides himself with always aiming at providing the best user experience possible, I wasn’t happy at all with my own implementation and wanted to address it somehow.&lt;&#x2F;p&gt;
&lt;h2&gt;Automatic Test Registration&lt;&#x2F;h2&gt;
&lt;p&gt;The C frameworks &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;novaprova&#x2F;novaprova&quot;&gt;NovaProva&lt;&#x2F;a&gt; and &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;Snaipe&#x2F;Criterion&quot;&gt;Criterion&lt;&#x2F;a&gt; workaround C’s limitation by respectively reading the DWARF debug information generated during a debug compilation, and by parsing the ELF (Unix), Mach-O (macOS), and PE (Windows) executable file formats to read their data sections.&lt;&#x2F;p&gt;
&lt;p&gt;This might just be my lack of knowledge but I found both implementations to be so involved that I couldn’t fully make sense of their fine details. I was hoping for a much simpler solution.&lt;&#x2F;p&gt;
&lt;p&gt;The data section approach used by Criterion is pretty neat because it allows storing some data initialized at the file scope and then retrieve it later on, which is exactly what we were missing.&lt;&#x2F;p&gt;
&lt;p&gt;Although data sections are not part of the C&#x2F;C++ standards, it turns our that they are so important to low-level system programming that each compiler expose them in a way or another.&lt;&#x2F;p&gt;
&lt;p&gt;So I looked into this direction and... tada! I got a fairly straightforward implementation working with support for the GNU-like compilers (GCC, clang, ICC) and MSVC&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, which is pretty much the same coverage as Criterion’s, and can also be extended as needed.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Example of automatic test registration.

   This approach uses a data section ‘bar’ to store multiple static instances
   of type ‘foo’. These objects are first statically instantiated at the file
   scope and a pointer is then stored in the data section, between some given
   ‘start’ and ‘end’ locations. Objects stored this way can then be retrieved
   at runtime by iterating over all the pointers found between these ‘start’
   and ‘end’ locations.
*&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;int&lt;&#x2F;span&gt; value;
};

&lt;span&gt;&#x2F;* Implementation Details                                                     *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;if&lt;&#x2F;span&gt; defined(_MSC_VER)&lt;&#x2F;span&gt;
    __pragma(section(&lt;span&gt;&quot;bar$a&quot;&lt;&#x2F;span&gt;, read))
    __pragma(section(&lt;span&gt;&quot;bar$b&quot;&lt;&#x2F;span&gt;, read))
    __pragma(section(&lt;span&gt;&quot;bar$c&quot;&lt;&#x2F;span&gt;, read))

    __declspec(allocate(&lt;span&gt;&quot;bar$a&quot;&lt;&#x2F;span&gt;))
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;bar_begin&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; &lt;span&gt;NULL&lt;&#x2F;span&gt;;

    __declspec(allocate(&lt;span&gt;&quot;bar$c&quot;&lt;&#x2F;span&gt;))
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;bar_end&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; &lt;span&gt;NULL&lt;&#x2F;span&gt;;

    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; DEFINE_SECTION                                                     \
        __declspec(allocate(&lt;span&gt;&quot;bar$b&quot;&lt;&#x2F;span&gt;))&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;elif&lt;&#x2F;span&gt; defined(__APPLE__)&lt;&#x2F;span&gt;
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt;
    __&lt;span&gt;start_bar&lt;&#x2F;span&gt; __&lt;span&gt;asm&lt;&#x2F;span&gt;(&quot;&lt;span&gt;section&lt;&#x2F;span&gt;$&lt;span&gt;start&lt;&#x2F;span&gt;$__&lt;span&gt;DATA&lt;&#x2F;span&gt;$&lt;span&gt;bar&lt;&#x2F;span&gt;&quot;);&lt;&#x2F;span&gt;
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt;
    __&lt;span&gt;stop_bar&lt;&#x2F;span&gt; __&lt;span&gt;asm&lt;&#x2F;span&gt;(&quot;&lt;span&gt;section&lt;&#x2F;span&gt;$&lt;span&gt;end&lt;&#x2F;span&gt;$__&lt;span&gt;DATA&lt;&#x2F;span&gt;$&lt;span&gt;bar&lt;&#x2F;span&gt;&quot;);&lt;&#x2F;span&gt;

    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; DEFINE_SECTION                                                     \
        __attribute__((used,section(&lt;span&gt;&quot;__DATA,bar&quot;&lt;&#x2F;span&gt;)))&lt;&#x2F;span&gt;

    DEFINE_SECTION
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;dummy&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; &lt;span&gt;NULL&lt;&#x2F;span&gt;;
&lt;span&gt;#&lt;span&gt;elif&lt;&#x2F;span&gt; defined(__unix__)&lt;&#x2F;span&gt;
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; __&lt;span&gt;start_bar&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; __&lt;span&gt;stop_bar&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; DEFINE_SECTION                                                     \
        __attribute__((used,section(&lt;span&gt;&quot;bar&quot;&lt;&#x2F;span&gt;)))&lt;&#x2F;span&gt;

    DEFINE_SECTION
    &lt;span&gt;extern&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;dummy&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; &lt;span&gt;NULL&lt;&#x2F;span&gt;;
&lt;span&gt;#&lt;span&gt;endif&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Public API                                                                 *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;if&lt;&#x2F;span&gt; defined(_MSC_VER)&lt;&#x2F;span&gt;
    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; SECTION_BEGIN                                                      \
        (&amp;amp;bar_begin + 1)&lt;&#x2F;span&gt;
    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; SECTION_END                                                        \
        (&amp;amp;bar_end)&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;elif&lt;&#x2F;span&gt; defined(__unix__) || defined(__APPLE__)&lt;&#x2F;span&gt;
    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; SECTION_BEGIN                                                      \
        (&amp;amp;__start_bar)&lt;&#x2F;span&gt;
    &lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; SECTION_END                                                        \
        (&amp;amp;__stop_bar)&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;endif&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; REGISTER_FOO(id, value)                                                \
    static const struct foo id &#x3D; { value };                                    \
    DEFINE_SECTION                                                             \
    extern const struct foo * const id##_ptr &#x3D; &amp;amp;id;&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Usage                                                                      *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

REGISTER_FOO(b, &lt;span&gt;234&lt;&#x2F;span&gt;);
REGISTER_FOO(a, &lt;span&gt;123&lt;&#x2F;span&gt;);
REGISTER_FOO(c, &lt;span&gt;345&lt;&#x2F;span&gt;);

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(
    &lt;span&gt;void&lt;&#x2F;span&gt;
)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;foo&lt;&#x2F;span&gt; * &lt;span&gt;const&lt;&#x2F;span&gt; *&lt;span&gt;it&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    &lt;span&gt;for&lt;&#x2F;span&gt; (it &#x3D; SECTION_BEGIN; it &amp;lt; SECTION_END; ++it)
    {
        &lt;span&gt;if&lt;&#x2F;span&gt; (*it !&#x3D; &lt;span&gt;NULL&lt;&#x2F;span&gt;)
        {
            &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;%d\n&quot;&lt;&#x2F;span&gt;, (*it)-&amp;gt;value);
        }
    }

    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And... that’s it! I think that’s fairly sweet and short, at least in comparison to what I’ve seen in NovaProva and Criterion.&lt;&#x2F;p&gt;
&lt;p&gt;Rexo makes use of this automatic registration feature through the macros &lt;code&gt;RX_TEST_CASE&lt;&#x2F;code&gt; and &lt;code&gt;RX_TEST_SUITE&lt;&#x2F;code&gt;, as shown in this &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.github.io&#x2F;rexo&#x2F;guides.html#minimal&quot;&gt;minimal example&lt;&#x2F;a&gt; from the documentation.&lt;&#x2F;p&gt;
&lt;h2&gt;Designated Initializer-like Syntax With Cascading Configuration&lt;&#x2F;h2&gt;
&lt;p&gt;C99, and later C++20, introduced designated initializers which is a neat feature to default initialize a struct to zero while letting users set the value of hand-picked fields, similarly to default named function parameters for other languages.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;my_struct&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *foo;
    &lt;span&gt;int&lt;&#x2F;span&gt; bar;
    &lt;span&gt;double&lt;&#x2F;span&gt; baz;
};

&lt;span&gt;&#x2F;* Only setting the bar field leaves foo and baz to 0. *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;my_struct&lt;&#x2F;span&gt; &lt;span&gt;inst&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; {.bar &#x3D; &lt;span&gt;123&lt;&#x2F;span&gt;};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This kind of syntax is something that I’ve been looking forward to implement in Rexo to configure the test cases, suites, and fixtures. Not only because it’s a nice syntactic sugar but also because it would allow me to extend the configuration options as needed without breaking the API and requiring users to update their code.&lt;&#x2F;p&gt;
&lt;p&gt;It turns out that here again Criterion got this one sorted out. &lt;em&gt;Almost&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;From my understanding, Criterion allows to configure all the tests in a suite at once by setting configuration values on the suite itself. That’s great! On top of that, it’s still possible to override these values for each individual test case. However, this overriding operation works by replacing the whole set of configuration values instead of inheriting the ones from the parent suite and only updating the values passed to the test cases.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;TEST_SUITE(my_suite, .foo &#x3D; &lt;span&gt;&quot;hello&quot;&lt;&#x2F;span&gt;, .baz &#x3D; &lt;span&gt;1.23&lt;&#x2F;span&gt;);

TEST_CASE(my_suite, my_case, .foo &#x3D; &lt;span&gt;&quot;world&quot;&lt;&#x2F;span&gt;)
{
    &lt;span&gt;&#x2F;&#x2F; Expectation : {foo &#x3D; &quot;hello&quot;, bar &#x3D; 0, baz &#x3D; 1.23}&lt;&#x2F;span&gt;
    &lt;span&gt;&#x2F;&#x2F; Criterion   : {foo &#x3D; &quot;world&quot;, bar &#x3D; 0, baz &#x3D; 0.0}&lt;&#x2F;span&gt;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not happy with that, I decided to try taking Criterion’s approach to the next level while still supporting C89 and all C++ versions.&lt;&#x2F;p&gt;
&lt;p&gt;It took me some time to figure it out but the solution is quite simple in the end! We just need to close our eyes on all these redundant macros required to support C89 and its lack of variadic arguments.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;string.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Macros                                                                     *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; UNUSED(x) (void)(x)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; EXPAND(x) x&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; CONCAT_(a, b) a##b&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; CONCAT(a, b) CONCAT_(a, b)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_SET_MEMBER(x) (*obj) x;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_0()&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_1(_0)                                                    \
    STRUCT_SET_MEMBER(_0)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_2(_0, _1)                                                \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_1(_1)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_3(_0, _1, _2)                                            \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_2(_1, _2)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_4(_0, _1, _2, _3)                                        \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_3(_1, _2, _3)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_5(_0, _1, _2, _3, _4)                                    \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_4(_1, _2, _3, _4)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_6(_0, _1, _2, _3, _4, _5)                                \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_5(_1, _2, _3, _4, _5)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_7(_0, _1, _2, _3, _4, _5, _6)                            \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_6(_1, _2, _3, _4, _5, _6)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_UPDATE_8(_0, _1, _2, _3, _4, _5, _6, _7)                        \
    STRUCT_SET_MEMBER(_0)                                                      \
    STRUCT_UPDATE_7(_1, _2, _3, _4, _5, _6, _7)&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; STRUCT_DEFINE_UPDATE_FN(id, type, arg_count, args)                     \
    static void                                                                \
    id(type *obj)                                                              \
    {                                                                          \
        UNUSED(obj);                                                           \
        EXPAND(CONCAT(STRUCT_UPDATE_, arg_count) args)                         \
    }&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Config                                                                     *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *foo;
    &lt;span&gt;int&lt;&#x2F;span&gt; bar;
    &lt;span&gt;double&lt;&#x2F;span&gt; baz;
};

&lt;span&gt;&lt;span&gt;typedef&lt;&#x2F;span&gt; &lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;(*config_update_fn)&lt;&#x2F;span&gt;&lt;span&gt;(
    struct config *
)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config_desc&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;const&lt;&#x2F;span&gt; config_update_fn update;
};

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; CONFIG(id, arg_count, args)                                            \
    STRUCT_DEFINE_UPDATE_FN(                                                   \
        id##_config_update_fn,                                                 \
        struct config,                                                         \
        arg_count,                                                             \
        args)                                                                  \
                                                                               \
    static const struct config_desc                                            \
    id##_config_desc &#x3D; {id##_config_update_fn}&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Test Suite                                                                 *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;test_suite_desc&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *name;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config_desc&lt;&#x2F;span&gt; *&lt;span&gt;config_desc&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;
};

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_SUITE_(id, arg_count, args)                                       \
    CONFIG(id, arg_count, args)                                                \
    &lt;span&gt;&#x2F;* Test suite registration and declaration goes here. *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_SUITE_1(id, _0)                                                   \
    TEST_SUITE_(id, 1, (_0))&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_SUITE_2(id, _0, _1)                                               \
    TEST_SUITE_(id, 2, (_0, _1))&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Test Case                                                                  *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;test_case_desc&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *suite_name;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *name;
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config_desc&lt;&#x2F;span&gt; *&lt;span&gt;config_desc&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;
};

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_CASE_(suite_id, id, arg_count, args)                              \
    CONFIG(suite_id##_##id, arg_count, args)                                   \
    &lt;span&gt;&#x2F;* Test case registration and declaration goes here. *&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_CASE_1(suite_id, id, _0)                                          \
    TEST_CASE_(suite_id, id, 1, (_0))&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; TEST_CASE_2(suite_id, id, _0, _1)                                      \
    TEST_CASE_(suite_id, id, 2, (_0, _1))&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;* Usage                                                                      *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

TEST_SUITE_2(my_suite, .foo &#x3D; &lt;span&gt;&quot;hello&quot;&lt;&#x2F;span&gt;, .baz &#x3D; &lt;span&gt;1.23&lt;&#x2F;span&gt;);

TEST_CASE_1(my_suite, my_case, .foo &#x3D; &lt;span&gt;&quot;world&quot;&lt;&#x2F;span&gt;);

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(
    &lt;span&gt;void&lt;&#x2F;span&gt;
)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config&lt;&#x2F;span&gt; &lt;span&gt;config&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    &lt;span&gt;memset&lt;&#x2F;span&gt;(&amp;amp;config, &lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;sizeof&lt;&#x2F;span&gt; config);
    my_suite_config_desc.update(&amp;amp;config);
    my_suite_my_case_config_desc.update(&amp;amp;config);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;foo: %s\n&quot;&lt;&#x2F;span&gt;, config.foo);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;bar: %d\n&quot;&lt;&#x2F;span&gt;, config.bar);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;baz: %f\n&quot;&lt;&#x2F;span&gt;, config.baz);
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The trick basically consists in defining a function which contains the necessary logic to update an existing configuration instance with the given field values.&lt;&#x2F;p&gt;
&lt;p&gt;Maybe it’ll be clearer after expanding the two macros calls from the usage section above.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;* TEST_SUITE_2(my_suite, .foo&#x3D;&quot;hello&quot;, .baz&#x3D;1.23)                            *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;my_suite_config_update_fn&lt;&#x2F;span&gt;&lt;span&gt;(
    struct config *obj
)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    (&lt;span&gt;void&lt;&#x2F;span&gt;)(obj);
    (*obj).foo &#x3D; &lt;span&gt;&quot;hello&quot;&lt;&#x2F;span&gt;;
    (*obj).baz &#x3D; &lt;span&gt;1.23&lt;&#x2F;span&gt;;
}

&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config_desc&lt;&#x2F;span&gt;
&lt;span&gt;my_suite_config_desc&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; {my_suite_config_update_fn};

&lt;span&gt;&#x2F;* TEST_CASE_1(my_suite, my_case, .foo&#x3D;&quot;world&quot;)                               *&#x2F;&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;my_suite_my_case_config_update_fn&lt;&#x2F;span&gt;&lt;span&gt;(
    struct config *obj
)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    (&lt;span&gt;void&lt;&#x2F;span&gt;)(obj);
    (*obj).foo &#x3D; &lt;span&gt;&quot;world&quot;&lt;&#x2F;span&gt;;
}

&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config_desc&lt;&#x2F;span&gt;
&lt;span&gt;my_suite_my_case_config_desc&lt;&#x2F;span&gt; &#x3D;&lt;&#x2F;span&gt; {my_suite_my_case_config_update_fn};

&lt;span&gt;&#x2F;* -------------------------------------------------------------------------- *&#x2F;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(
    &lt;span&gt;void&lt;&#x2F;span&gt;
)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;config&lt;&#x2F;span&gt; &lt;span&gt;config&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    &lt;span&gt;memset&lt;&#x2F;span&gt;(&amp;amp;config, &lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;sizeof&lt;&#x2F;span&gt; config);
    my_suite_config_desc.update(&amp;amp;config);
    my_suite_my_case_config_desc.update(&amp;amp;config);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;foo: %s\n&quot;&lt;&#x2F;span&gt;, config.foo);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;bar: %d\n&quot;&lt;&#x2F;span&gt;, config.bar);
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;baz: %f\n&quot;&lt;&#x2F;span&gt;, config.baz);
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Straighforward, right?&lt;&#x2F;p&gt;
&lt;p&gt;If you’re curious to see how it looks like in action in Rexo, check out this &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.github.io&#x2F;rexo&#x2F;guides.html#runtime-configuration&quot;&gt;concrete example&lt;&#x2F;a&gt; from the documentation.&lt;&#x2F;p&gt;
&lt;h2&gt;Closing Notes&lt;&#x2F;h2&gt;
&lt;p&gt;I’m fairly happy with the current state of Rexo although there would still be some more work needed to be done in order to implement more assertion macros, to output the report summaries in different standard formats (e.g.: jUnit XML), and to provide more visual diagnostic messages, amongst other things.&lt;&#x2F;p&gt;
&lt;p&gt;But it’s still plenty enough for me to rely on it for my other projects, and maybe it would be satisfactory for yours as well? If it’s something that you’d be interested in exploring, maybe a good place to start would be to have a peek at its &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.github.io&#x2F;rexo&quot;&gt;comprehensive documentation&lt;&#x2F;a&gt;. Or you can always head straight to its &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;rexo&quot;&gt;GitHub repository&lt;&#x2F;a&gt; otherwise.&lt;&#x2F;p&gt;
&lt;p&gt;If you end up using it, please let me know how it goes!&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;Thanks to Marco Giordano for finding an issue with MSVC discarding the section items when compiling in C++ mode, and to Sy Brand for providing a solution!&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:3f9d0bae-f59b-11ea-9cfc-9fc8e4998203</id>
      <title>Wadu: Again and Again</title>
      <updated>2020-09-14T12:00:00Z</updated>

      <published>2020-09-14T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/wadu" rel="alternate" type="text/html" title="Wadu: Again and Again" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/wadu/code.jpg" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;wadu&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;wadu&#x2F;code.jpg&quot; alt&#x3D;&quot;Wadu&quot;&gt;&lt;span&gt;View on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;March 2020, me and my ≈1,500 colleagues from &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.wetafx.co.nz&quot;&gt;Weta Digital&lt;&#x2F;a&gt; were being sent home to work remotely due to the COVID-19 pandemic.&lt;&#x2F;p&gt;
&lt;p&gt;This also meant no more gym and the need to find a way to do some exercises somewhat regularly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Surely&lt;&#x2F;em&gt;, this sounded like a good pretext to build a terminal-based app that would remind me of doing specific exercises at specific times, &lt;em&gt;right&lt;&#x2F;em&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;And, &lt;em&gt;surely&lt;&#x2F;em&gt;, setting up reminders that are to be triggered every day at a given time is no big deal, &lt;em&gt;right&lt;&#x2F;em&gt;?&lt;&#x2F;p&gt;
&lt;p&gt;But wait. How about reminders that occur only once every 2 weeks? Or only during the first week of each month?&lt;&#x2F;p&gt;
&lt;p&gt;It quickly became apparent that some more thinking needed to take place and that’s when I stumbled upon the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;tools.ietf.org&#x2F;html&#x2F;rfc5545#section-3.3.10&quot;&gt;iCalendar (RFC 5545)&lt;&#x2F;a&gt; specification that describes recurrence rule objects, amongst other things.&lt;&#x2F;p&gt;
&lt;p&gt;I even found a popular Python package named &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;dateutil&#x2F;dateutil&quot;&gt;dateutil&lt;&#x2F;a&gt; that implements these recurrence rules, however...&lt;&#x2F;p&gt;
&lt;p&gt;... it wouldn’t be fun if it was too easy, or so I thought. And while skimming through the implementation of dateutil, I found some of its bits to be fairly difficult to decypher.&lt;&#x2F;p&gt;
&lt;p&gt;So, “&lt;em&gt;y’know what, bud?&lt;&#x2F;em&gt;”, I asked myself, “&lt;em&gt;the best way to learn about something is to do it yourself&lt;&#x2F;em&gt;”.&lt;&#x2F;p&gt;
&lt;p&gt;But that wasn’t all. The naïve person that I was decided that this wasn&#39;t challenging enough: “&lt;em&gt;while y’er at it, make a simpler implementation. And why not a moar optimised one, too. And with a data-oriented approach, y’know?&lt;&#x2F;em&gt;”.&lt;&#x2F;p&gt;
&lt;p&gt;At that time, I had &lt;em&gt;no&lt;&#x2F;em&gt; idea what I was getting into. I simply dived head first into the rabbit hole of recurring date events and what started as a fun exercise quickly turned into &lt;strong&gt;hell&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;Hell&lt;&#x2F;h2&gt;
&lt;p&gt;In retrospective, I can say this probably was the most challenging programming project that I’ve ever worked on. Not due to the code complexity, mind you. It’s all fairly straightforward. No, no, what made it truly difficult was making sense of that RFC 5545 specification.&lt;&#x2F;p&gt;
&lt;p&gt;Trying to implement the specification one piece at a time, without fully grasping the whole picture, was a truly inefficient approach but, at the same time, it also was the only way to build the required knowledge to move forward.&lt;&#x2F;p&gt;
&lt;p&gt;Whenever I’d end up successfully implementing one bit, I’d move onto the next one only to realize that my understanding of the specification felt apart and that my whole approach was thus wrong. Which led me to needing to rethink everything from the beginning. Every. Single. Time.&lt;&#x2F;p&gt;
&lt;p&gt;It really felt hopeless. How many times did I begin seeing the light at the end of the tunnel only to see it collapse right in front of me, forcing me to go all the way back to the starting point? I lost count.&lt;&#x2F;p&gt;
&lt;p&gt;Did I really have to be my usual stubborn masochist self and implement something that no one will ever care about, when I could have spent my time working on cool projects instead?&lt;&#x2F;p&gt;
&lt;h2&gt;Wadu&lt;&#x2F;h2&gt;
&lt;p&gt;The one thing that finally came to my rescue was stumbling upon the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;datatracker.ietf.org&#x2F;doc&#x2F;html&#x2F;draft-ietf-calext-jscalendar#section-4.3.2&quot;&gt;JSCalendar draft from IETF&lt;&#x2F;a&gt;. At last a specification that is clear, well articulated and, most importantly, that doesn’t leave space for &lt;strong&gt;ambiguities&lt;&#x2F;strong&gt; like the RFC 5545 does.&lt;&#x2F;p&gt;
&lt;p&gt;Because of these ambiguities, different libraries such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;dateutil&#x2F;dateutil&quot;&gt;dateutil&lt;&#x2F;a&gt;, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;libical&#x2F;libical&quot;&gt;libical&lt;&#x2F;a&gt;, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;dmfs&#x2F;lib-recur&quot;&gt;lib-recur&lt;&#x2F;a&gt;, and others, all advertise themselves to be RFC 5545 compliant—and rightfully so—while at the same time returning inconsistent results depending on how the specification is being interpreted.&lt;&#x2F;p&gt;
&lt;p&gt;So here I stand. With the only open source implementation that I’m aware of that is fully compliant with the JSCalendar draft while covering all the edge cases that I could find, and boasting a relatively straightforward-ish implementation.&lt;&#x2F;p&gt;
&lt;p&gt;I called it wadu and the source code is on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;wadu&quot;&gt;GitHub&lt;&#x2F;a&gt;, as always!&lt;&#x2F;p&gt;
&lt;p&gt;Hell, it’s even on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;pypi.org&#x2F;project&#x2F;wadu&quot;&gt;pypi&lt;&#x2F;a&gt; so you could go ahead and install it by running &lt;code&gt;pip install wadu&lt;&#x2F;code&gt; if you too would like to get to play with these sexy curves:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;# The 1st Friday of each month, for 10 occurrences.&lt;&#x2F;span&gt;
rule &#x3D; RecurrenceRule(MONTHLY,
                      on_week_days&#x3D;(FRIDAY(&lt;span&gt;1&lt;&#x2F;span&gt;),),
                      count&#x3D;&lt;span&gt;10&lt;&#x2F;span&gt;)
&lt;span&gt;for&lt;&#x2F;span&gt; dttm &lt;span&gt;in&lt;&#x2F;span&gt; rule:
    &lt;span&gt;print&lt;&#x2F;span&gt;(dttm)


&lt;span&gt;# Every other year on January and February, starting on a given date.&lt;&#x2F;span&gt;
rule &#x3D; RecurrenceRule(YEARLY,
                      interval&#x3D;&lt;span&gt;2&lt;&#x2F;span&gt;,
                      on_months&#x3D;(JANUARY, FEBRUARY),
                      count&#x3D;&lt;span&gt;10&lt;&#x2F;span&gt;)
start &#x3D; datetime(&lt;span&gt;1997&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;, &lt;span&gt;10&lt;&#x2F;span&gt;, hour&#x3D;&lt;span&gt;9&lt;&#x2F;span&gt;)
&lt;span&gt;for&lt;&#x2F;span&gt; dttm &lt;span&gt;in&lt;&#x2F;span&gt; rule.iterate_from(start):
    &lt;span&gt;print&lt;&#x2F;span&gt;(dttm)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2&gt;Final Notes&lt;&#x2F;h2&gt;
&lt;p&gt;If you’re wondering whether I got to implement the terminal-based app that I initially intended to write: of course not ¯\_(ツ)_&#x2F;¯&lt;&#x2F;p&gt;
&lt;p&gt;And I didn’t end up doing any exercise neither.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;― Jesus Fucking Christ. What did we learn, Palmer?&lt;br&gt;
― I don’t know sir.&lt;br&gt;
― I don’t fucking know either. I guess we learned not to do it again. I’m fucked if I know what we did.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:9b56be68-242c-11ea-9470-4f71d6c5d909</id>
      <title>Forget About Anti-Cheats, Here&#39;s How You Do It</title>
      <updated>2019-12-22T13:00:00Z</updated>

      <published>2019-12-22T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/misc/anti-cheat" rel="alternate" type="text/html" title="Forget About Anti-Cheats, Here&#39;s How You Do It" />
        <category term="misc" />
        <media:thumbnail url="https://christophercrouzet.com/blog/misc/anti-cheat/cheat.jpg" />

      <content type="html">
        &lt;p&gt;These things that are plaguing most online multiplayer games—cheating and toxicity? I think that we could address them better.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;misc&#x2F;anti-cheat&#x2F;cheat.jpg&quot; alt&#x3D;&quot;Cheat&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;I never cheated, I swear!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Bottlenecks&lt;&#x2F;h2&gt;
&lt;p&gt;In my opinion, there are two main areas that are preventing much improvement to the current situation: the &lt;strong&gt;detection process&lt;&#x2F;strong&gt; and &lt;strong&gt;offense handling&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3&gt;Detection Process&lt;&#x2F;h3&gt;
&lt;p&gt;Anti-cheat &lt;strong&gt;software&lt;&#x2F;strong&gt; solutions have some major drawbacks:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;burdening for the developers&lt;&#x2F;strong&gt;: anti-cheats can not only be complicated&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; but they&#39;re also costly to maintain since resources need to be continuously invested into researching how to block new cheats as they arise.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;burdening for the players&lt;&#x2F;strong&gt;: &lt;em&gt;client-side&lt;&#x2F;em&gt; anti-cheats can possibly affect the performance and cause issues (e.g.: freezes and crashes) to the &lt;em&gt;whole&lt;&#x2F;em&gt; player base only to fight a &lt;em&gt;minority&lt;&#x2F;em&gt; of players. This is like using a big hammer to kill a fly—legitimate players shouldn&#39;t have to pay that price due to a minority of troublemakers.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;unreliable&lt;&#x2F;strong&gt;: the truth is that no sort of anti-cheat is fully reliable. It&#39;s a relentless battle against cheat developers (cheat-making is a big business&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;sup&gt;&lt;a href&#x3D;&quot;#fn3&quot; id&#x3D;&quot;fnref3&quot;&gt;[3]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;) and it simply is technically &lt;em&gt;impossible&lt;&#x2F;em&gt; to develop a perfect anti-cheat. Worse, they can produce &lt;strong&gt;false positives&lt;&#x2F;strong&gt; and end up banning legitimate players.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Which seems to me like a lot of negatives only for suboptimal results?&lt;&#x2F;p&gt;
&lt;h3&gt;Offense Handling&lt;&#x2F;h3&gt;
&lt;p&gt;Even more importantly than the detection process is how the offences are being handled.&lt;&#x2F;p&gt;
&lt;p&gt;Banning a game account is &lt;strong&gt;NOT&lt;&#x2F;strong&gt; enough when players can simply get back into the game through another account. It&#39;s like being put in jail only to be let free if you pay a small fine of $30—why even bother? It&#39;s not as if this amounts to anything when most cheaters are probably rich kids and the monthly subscription for these cheats can be more expensive than the game itself.&lt;&#x2F;p&gt;
&lt;p&gt;As long as this issue won&#39;t be tackled properly, any effort going into fighting toxic players and cheaters will just end up being a &lt;strong&gt;waste&lt;&#x2F;strong&gt; of time, resources, energy, and trust.&lt;&#x2F;p&gt;
&lt;h2&gt;Suggestion&lt;&#x2F;h2&gt;
&lt;p&gt;What I&#39;m trying to say here is that we might be tackling these problems the wrong way around.&lt;&#x2F;p&gt;
&lt;h3&gt;Empower Humans Over Softwares&lt;&#x2F;h3&gt;
&lt;p&gt;Anti-cheat softwares are burdening and unreliable. Humans are much more reliable towards detecting cheats and don&#39;t have any negative impact on game performance and whatnot.&lt;&#x2F;p&gt;
&lt;p&gt;So how would a game developer go about building a detection process based around humans? It&#39;s simple, really—make better use of the community and &lt;strong&gt;reward&lt;&#x2F;strong&gt; them for their &lt;strong&gt;positive&lt;&#x2F;strong&gt; contributions!&lt;&#x2F;p&gt;
&lt;p&gt;When a player reports someone who is then confirmed by the game developer&#39;s staff to be a cheater, then the player receives some &lt;strong&gt;contribution points&lt;&#x2F;strong&gt;. When a given number of points is reached, the player gets some nice, exclusive, skins or whatnot. To prevent abuse of the system, any report targeted at legitimate players results in a loss of points—so you&#39;d better get it right.&lt;&#x2F;p&gt;
&lt;p&gt;Reports can then be automatically &lt;strong&gt;prioritized&lt;&#x2F;strong&gt; to help the staff to first assess the reports that are more likely to be positives, while comforting the players that their valuable reports will be assessed sooner than later if, and &lt;em&gt;only&lt;&#x2F;em&gt; if, they have a good track record.&lt;&#x2F;p&gt;
&lt;p&gt;The priority formula could be as simple as the Python snippet that is to be found at &lt;a href&#x3D;&quot;#priority-formula-code&quot;&gt;the end of this post&lt;&#x2F;a&gt;. The result of this formula, ran over a small data set, looks like this:&lt;&#x2F;p&gt;
&lt;div&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;positive reports&lt;&#x2F;th&gt;
&lt;th&gt;total reports&lt;&#x2F;th&gt;
&lt;th&gt;priority&lt;&#x2F;th&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;&#x2F;td&gt;
&lt;td&gt;93&lt;&#x2F;td&gt;
&lt;td&gt;-0.861&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;&#x2F;td&gt;
&lt;td&gt;25&lt;&#x2F;td&gt;
&lt;td&gt;-0.793&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;&#x2F;td&gt;
&lt;td&gt;8&lt;&#x2F;td&gt;
&lt;td&gt;-0.098&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;&#x2F;td&gt;
&lt;td&gt;2&lt;&#x2F;td&gt;
&lt;td&gt;-0.038&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;&#x2F;td&gt;
&lt;td&gt;1&lt;&#x2F;td&gt;
&lt;td&gt;-0.010&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;&#x2F;td&gt;
&lt;td&gt;0&lt;&#x2F;td&gt;
&lt;td&gt;+0.000&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;&#x2F;td&gt;
&lt;td&gt;1&lt;&#x2F;td&gt;
&lt;td&gt;+0.010&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;&#x2F;td&gt;
&lt;td&gt;2&lt;&#x2F;td&gt;
&lt;td&gt;+0.038&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;&#x2F;td&gt;
&lt;td&gt;12&lt;&#x2F;td&gt;
&lt;td&gt;+0.098&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;&#x2F;td&gt;
&lt;td&gt;7&lt;&#x2F;td&gt;
&lt;td&gt;+0.329&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;&#x2F;td&gt;
&lt;td&gt;27&lt;&#x2F;td&gt;
&lt;td&gt;+0.749&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;&#x2F;td&gt;
&lt;td&gt;52&lt;&#x2F;td&gt;
&lt;td&gt;+0.779&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;&#x2F;td&gt;
&lt;td&gt;53&lt;&#x2F;td&gt;
&lt;td&gt;+0.929&lt;&#x2F;td&gt;
&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;
&lt;&#x2F;table&gt;&lt;&#x2F;div&gt;
&lt;p&gt;As you can see, both the number of reports and their accuracy are being taken into account to provide a priority metric that makes sense—the more meaningful someone&#39;s contribution is, the higher priority their reports will have, and vice versa.&lt;&#x2F;p&gt;
&lt;p&gt;From there, &lt;strong&gt;client-side&lt;&#x2F;strong&gt; anti-cheats could be removed altogether, or at least be made less disruptive. This doesn&#39;t mean that there is no place for software detection at all. It&#39;d always be helpful for a game developer to be able to find cheaters without requiring any assistance from the community—this can be implemented by flagging users with a high K&#x2F;D ratio or win rate, or why not by adding a &lt;strong&gt;server-side&lt;&#x2F;strong&gt; deep learning system&lt;sup&gt;&lt;a href&#x3D;&quot;#fn4&quot; id&#x3D;&quot;fnref4&quot;&gt;[4]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; that would learn from the ingame behaviour of players that were reported.&lt;&#x2F;p&gt;
&lt;p&gt;It is important to note that any detection software should &lt;strong&gt;NOT&lt;&#x2F;strong&gt; have an authoritative decision. It should never directly ban a player, only flag them for human review.&lt;&#x2F;p&gt;
&lt;p&gt;I might be biased but this seems like a bulletproof detection system to me—I bet that any community would be happy to contribute if they&#39;d be rewarded accordingly, as long as they&#39;d be guaranteed that their efforts won&#39;t go to waste by getting the offenders banned for good.&lt;&#x2F;p&gt;
&lt;h3&gt;Keeping the Offenders Out, For Good&lt;&#x2F;h3&gt;
&lt;p&gt;If I was developing an online multiplayer game, I probably wouldn&#39;t bother too much about crafting complicated anti-cheats. But the caveat would be: &lt;strong&gt;if you get caught, you&#39;re out of my game, for good&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;How&#39;d you do that? How&#39;d you uniquely identify each player regarding on which game account they might be using? Not with an IP address, not with an HWID, but with an actual proof of identity and&#x2F;or physical address.&lt;&#x2F;p&gt;
&lt;p&gt;For example, such a verification system could require either one or a combination of the following:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;providing a scan of an official document from the government (national ID, passport, driving license, ...).&lt;&#x2F;li&gt;
&lt;li&gt;entering a verification code that was sent by the game developer through post mail to a given physical address.&lt;&#x2F;li&gt;
&lt;li&gt;taking a selfie next to a verification code that is displayed either on a phone or printed out on a piece of paper.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Of course you shouldn&#39;t have to provide a proof of identity only to play a game online. But I bet that the part of the community that truly cares about playing in a good, &lt;strong&gt;safe&lt;&#x2F;strong&gt;, environment, which I&#39;m hoping is the majority of us, would understand the benefits of providing such a proof of identity and would be fine complying with it? It could even be handled through an external, independent, platform, and be made available to all games, so you&#39;d have to go through the verification process only once.&lt;&#x2F;p&gt;
&lt;p&gt;Such a system would be &lt;strong&gt;optional&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;You could decide &lt;em&gt;not&lt;&#x2F;em&gt; to opt-in and have access to the ‘normal’ matchmaking queues, or you could opt-in and also gain access the ‘&lt;strong&gt;verified&lt;&#x2F;strong&gt;’ queues.&lt;&#x2F;p&gt;
&lt;p&gt;Once a verified offender would get caught, it means that not only their game account would be banned but also their actual &lt;strong&gt;physical identity&lt;&#x2F;strong&gt; would be blacklisted. It wouldn&#39;t be possible for that person to join a ‘verified’ queue ever again, thus ensuring that these queues become safer and safer over time.&lt;&#x2F;p&gt;
&lt;h2&gt;How Does That Sound?&lt;&#x2F;h2&gt;
&lt;p&gt;I know for one that I&#39;d be happy to be part of a community where we&#39;d fight toxicity and cheating all &lt;strong&gt;together&lt;&#x2F;strong&gt;, hands in hands with game developers, to provide the best possible online multiplayer gaming experience. Hell, the concept could probably be extended to other type of online communities.&lt;&#x2F;p&gt;
&lt;p&gt;Would you be willing to join the fight against cheating and toxicity if such a concept were to be implemented?&lt;&#x2F;p&gt;
&lt;h2&gt;Priority Formula Code&lt;&#x2F;h2&gt;
&lt;p&gt;Here is the Python snippet used to compute the priority table from above:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#! &#x2F;usr&#x2F;bin&#x2F;env python&lt;&#x2F;span&gt;
&lt;span&gt;# -*- coding: utf-8 -*-&lt;&#x2F;span&gt;

&lt;span&gt;&quot;&quot;&quot;Formula to compute a priority value according to the reports received.

The priority number fits in the range [-1.0, 1.0].

Everyone starts at 0, synonym to a neutral priority level.
&quot;&quot;&quot;&lt;&#x2F;span&gt;

_DATA_SET &#x3D; (
    (&lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;0&lt;&#x2F;span&gt;),
    (&lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;),
    (&lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;2&lt;&#x2F;span&gt;),
    (&lt;span&gt;1&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;),
    (&lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;2&lt;&#x2F;span&gt;),
    (&lt;span&gt;1&lt;&#x2F;span&gt;, &lt;span&gt;25&lt;&#x2F;span&gt;),
    (&lt;span&gt;3&lt;&#x2F;span&gt;, &lt;span&gt;8&lt;&#x2F;span&gt;),
    (&lt;span&gt;6&lt;&#x2F;span&gt;, &lt;span&gt;93&lt;&#x2F;span&gt;),
    (&lt;span&gt;7&lt;&#x2F;span&gt;, &lt;span&gt;7&lt;&#x2F;span&gt;),
    (&lt;span&gt;7&lt;&#x2F;span&gt;, &lt;span&gt;12&lt;&#x2F;span&gt;),
    (&lt;span&gt;25&lt;&#x2F;span&gt;, &lt;span&gt;27&lt;&#x2F;span&gt;),
    (&lt;span&gt;47&lt;&#x2F;span&gt;, &lt;span&gt;52&lt;&#x2F;span&gt;),
    (&lt;span&gt;52&lt;&#x2F;span&gt;, &lt;span&gt;53&lt;&#x2F;span&gt;),
)

&lt;span&gt;# Magic number—the higher, the more weight will be given to the ratio&lt;&#x2F;span&gt;
&lt;span&gt;# &#x60;positive_count &#x2F; total_count&#x60; as the number of reports increase.&lt;&#x2F;span&gt;
_THRESHOLD &#x3D; &lt;span&gt;10&lt;&#x2F;span&gt;


&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;_compute_priority&lt;&#x2F;span&gt;(&lt;span&gt;positive_count, total_count&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;if&lt;&#x2F;span&gt; total_count &#x3D;&#x3D; &lt;span&gt;0&lt;&#x2F;span&gt;:
        &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;

    x &#x3D; &lt;span&gt;float&lt;&#x2F;span&gt;(total_count) &#x2F; _THRESHOLD
    weight &#x3D; &lt;span&gt;1&lt;&#x2F;span&gt; - &lt;span&gt;1&lt;&#x2F;span&gt; &#x2F; (x * x + &lt;span&gt;1&lt;&#x2F;span&gt;)
    ratio &#x3D; (&lt;span&gt;float&lt;&#x2F;span&gt;(positive_count) &#x2F; total_count) * &lt;span&gt;2&lt;&#x2F;span&gt; - &lt;span&gt;1&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; ratio * weight


&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;_print_results&lt;&#x2F;span&gt;(&lt;span&gt;results&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&quot;+---------------------------------------------+&quot;&lt;&#x2F;span&gt;)
    &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&quot;| positive reports | total reports | priority |&quot;&lt;&#x2F;span&gt;)
    &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&quot;+------------------+---------------+----------+&quot;&lt;&#x2F;span&gt;)

    &lt;span&gt;for&lt;&#x2F;span&gt; priority, (positive_count, total_count) &lt;span&gt;in&lt;&#x2F;span&gt; results:
        &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&quot;| {:16d} | {:13d} | {:+8.3f} |&quot;&lt;&#x2F;span&gt;.&lt;span&gt;format&lt;&#x2F;span&gt;(
            positive_count, total_count, priority))

    &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&quot;+---------------------------------------------+&quot;&lt;&#x2F;span&gt;)


&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;main&lt;&#x2F;span&gt;():&lt;&#x2F;span&gt;
    results &#x3D; &lt;span&gt;sorted&lt;&#x2F;span&gt;((_compute_priority(*x), x) &lt;span&gt;for&lt;&#x2F;span&gt; x &lt;span&gt;in&lt;&#x2F;span&gt; _DATA_SET)
    _print_results(results)


&lt;span&gt;if&lt;&#x2F;span&gt; __name__ &#x3D;&#x3D; &lt;span&gt;&#39;__main__&#39;&lt;&#x2F;span&gt;:
    main()
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;technology.riotgames.com&#x2F;news&#x2F;riots-approach-anti-cheat&quot;&gt;Riot&#39;s Approach to Anti-Cheat&lt;&#x2F;a&gt;.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.pcgamer.com&#x2F;hacks-an-investigation-into-aimbot-dealers-wallhack-users-and-the-million-dollar-business-of-video-game-cheating&quot;&gt;Hacks! An investigation into the million-dollar business of video game cheating&lt;&#x2F;a&gt;&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn3&quot;&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sixthtone.com&#x2F;news&#x2F;1002377&#x2F;feeling-the-cheat-the-game-hack-sellers-working-under-the-radar&quot;&gt;Feeling the Cheat: The Game Hack Sellers Working Under the Radar&lt;&#x2F;a&gt;&lt;a href&#x3D;&quot;#fnref3&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn4&quot;&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v&#x3D;ObhK8lUfIlc&quot;&gt;GDC 2018: John McDonald (Valve) - Using Deep Learning to Combat Cheating in CSGO&lt;&#x2F;a&gt;&lt;a href&#x3D;&quot;#fnref4&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:79248776-696b-11e8-90d7-fb96bc8d1cba</id>
      <title>Houdini: Flame</title>
      <updated>2018-06-13T12:00:00Z</updated>

      <published>2018-06-13T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/flame" rel="alternate" type="text/html" title="Houdini: Flame" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/flame/flame.jpg" />

      <content type="html">
        &lt;p&gt;A while ago I did simulate a flame in Houdini based on a video reference that had a lot of character to it (see below), to the point where it almost produced a stroboscopic effect.&lt;&#x2F;p&gt;
&lt;p&gt;I was happy with the result that I obtained but alas I could not share the scene because I lost the definition of some custom digital assets that I was using. Rejoy as I have recreated it from scratch!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;274811406&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;flame&#x2F;flame.jpg&quot; alt&#x3D;&quot;Flame&quot;&gt;&lt;span&gt;&lt;&#x2F;span&gt;&lt;span&gt;Watch on Vimeo&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The effect is pretty simple to achieve. The only tricks are to provide a pulsatile temperature source but also to find a good balance between the pyro solver&#39;s shredding, which brings some of the character out but shorten the flame&#39;s height, and the buoyancy plus temperature multiplier combo. Too much of shredding and the motion becomes too frenetic, and not enough temperature&#x2F;buoyancy and the flame becomes too short.&lt;&#x2F;p&gt;
&lt;h2&gt;Download&lt;&#x2F;h2&gt;
&lt;div&gt;
&lt;p&gt;&lt;span aria-hidden&#x3D;&quot;true&quot;&gt;&lt;&#x2F;span&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;flame&#x2F;flame-01.zip&quot;&gt;&lt;strong&gt;flame-01 v1&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This file contains a scene created with &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sidefx.com&quot;&gt;SideFX&lt;&#x2F;a&gt;’s Houdini 16.5 as well as any required asset.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;h2&gt;References&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v&#x3D;aCpBT3sIPc4&amp;amp;t&#x3D;80&quot;&gt;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v&#x3D;aCpBT3sIPc4&amp;amp;t&#x3D;80&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:535dcce4-49ce-11e8-9c82-c3124dcf2f09</id>
      <title>Rexo: A Unit Testing Framework in C</title>
      <updated>2018-06-11T12:00:00Z</updated>

      <published>2018-06-11T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/rexo" rel="alternate" type="text/html" title="Rexo: A Unit Testing Framework in C" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/rexo/code.jpg" />

      <content type="html">
        &lt;p&gt;In the serie of reinventing the wheel for the sake of learning, let me introduce you to the unit testing framework!&lt;&#x2F;p&gt;
&lt;div&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A full redesign of Rexo has since been released, see &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;rexo-part-2&quot;&gt;Rexo: A Unit Testing Framework in C (Part 2)&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;rexo&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;rexo&#x2F;code.jpg&quot; alt&#x3D;&quot;Rexo&quot;&gt;&lt;span&gt;View on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Test Registration&lt;&#x2F;h2&gt;
&lt;p&gt;While looking around for unit testing frameworks written in C, I noticed that the vast majority were fairly verbose and didn’t provide any sort of automatic test registration like most of their C++ counterparts do.&lt;&#x2F;p&gt;
&lt;p&gt;There had to be some sort of additional constraints inheritent to C, but which ones? In an effort to learn more about the C language, I set out to answer this question by writing my own unit testing framework from scratch, in C89.&lt;&#x2F;p&gt;
&lt;p&gt;The reason why automatic test registration is not available in C quickly became obvious. C++ frameworks like Google Test use some clever static initialization trickery that registers each test to a global factory class with the help of some macros to nicely hide the registration logic away from the user&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But no function, not even static, can be called at the file scope in C. All that is allowed is defining simple constant values and functions, so no such trickery allowed here.&lt;&#x2F;p&gt;
&lt;p&gt;As an example, the simple code snippet below works perfectly fine in C++ but triggers a compilation error in C.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;foo&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;123&lt;&#x2F;span&gt;;
}

&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; bar &#x3D; foo();

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;printf&lt;&#x2F;span&gt;(&lt;span&gt;&quot;bar is %d\n&quot;&lt;&#x2F;span&gt;, bar);
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}

&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Some C frameworks such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;novaprova&#x2F;novaprova&quot;&gt;NovaProva&lt;&#x2F;a&gt; workaround this limitation by reading the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;DWARF&quot;&gt;DWARF debug information&lt;&#x2F;a&gt; generated during a debug compilation while &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;Snaipe&#x2F;Criterion&quot;&gt;Criterion&lt;&#x2F;a&gt; parses the generated executable file formats to read the contents of specific data sections.&lt;&#x2F;p&gt;
&lt;p&gt;I wasn’t ready to dig into such involved approaches so I picked the more standard route with this first iteration of Rexo and went for the manual registration choice with all the verbosity that it comes with.&lt;&#x2F;p&gt;
&lt;h2&gt;API Design&lt;&#x2F;h2&gt;
&lt;p&gt;Rexo provides both a diagonal API for ease of use and an orthogonal one for maximum customizability.&lt;&#x2F;p&gt;
&lt;p&gt;The diagonal API is just one function, &lt;code&gt;rxRun()&lt;&#x2F;code&gt;. It takes the suites as parameter and run them.&lt;&#x2F;p&gt;
&lt;p&gt;The orthogonal API is made of all the smaller logical parts that &lt;code&gt;rxRun()&lt;&#x2F;code&gt; uses to get the job done, that is &lt;code&gt;rxInitializeTestCaseReport()&lt;&#x2F;code&gt;, &lt;code&gt;rxTerminateTestCaseReport()&lt;&#x2F;code&gt;, &lt;code&gt;rxRunTestCase()&lt;&#x2F;code&gt;, and &lt;code&gt;rxPrintTestCaseRunSummary()&lt;&#x2F;code&gt;. If you go down this path, it means that you need to write your own function to run the tests, and it’s also up to you to use the provided orthogonal functions or here again to write your own variants... but at this point, you’re probably better off writing your own framework from scratch.&lt;&#x2F;p&gt;
&lt;h2&gt;Simple Usage&lt;&#x2F;h2&gt;
&lt;p&gt;Here’s a basic example on how to use Rexo, with a fixture and a display of a few of the available assertion macros, which are declined in &lt;code&gt;CHECK&lt;&#x2F;code&gt; and &lt;code&gt;REQUIRE&lt;&#x2F;code&gt; variants, respectively for nonfatal and fatal failures.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; RX_DEBUG_LOGGING_LEVEL&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;rexo&#x2F;rexo.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;stddef.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;stdio.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; PI 3.14159265358979323846&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Fixture&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;int&lt;&#x2F;span&gt; zero;
    &lt;span&gt;unsigned&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; one;
};

&lt;span&gt;&lt;span&gt;enum&lt;&#x2F;span&gt; RxStatus
&lt;span&gt;setUp&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;void&lt;&#x2F;span&gt; **ppFixture)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Fixture&lt;&#x2F;span&gt; *&lt;span&gt;pData&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    pData &#x3D; (struct Fixture *)&lt;span&gt;malloc&lt;&#x2F;span&gt;(&lt;span&gt;sizeof&lt;&#x2F;span&gt; *pData);
    &lt;span&gt;if&lt;&#x2F;span&gt; (pData &#x3D;&#x3D; &lt;span&gt;NULL&lt;&#x2F;span&gt;) {
        &lt;span&gt;return&lt;&#x2F;span&gt; RX_ERROR_ALLOCATION;
    }

    pData-&amp;gt;zero &#x3D; &lt;span&gt;0&lt;&#x2F;span&gt;;
    pData-&amp;gt;one &#x3D; &lt;span&gt;1&lt;&#x2F;span&gt;;

    *ppFixture &#x3D; (&lt;span&gt;void&lt;&#x2F;span&gt; *)pData;
    &lt;span&gt;return&lt;&#x2F;span&gt; RX_SUCCESS;
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;tearDown&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;void&lt;&#x2F;span&gt; *pFixture)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;free&lt;&#x2F;span&gt;(pFixture);
}

RX_TEST_CASE(testBasics)
{
    &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Fixture&lt;&#x2F;span&gt; *&lt;span&gt;pData&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    pData &#x3D; (struct Fixture *)RX_FIXTURE;

    RX_CHECK_INT_EQUAL(pData-&amp;gt;zero, &lt;span&gt;0&lt;&#x2F;span&gt;);
    RX_CHECK_UINT_EQUAL(pData-&amp;gt;one, &lt;span&gt;1&lt;&#x2F;span&gt;);
    RX_CHECK_FP_ALMOST_EQUAL(&lt;span&gt;sin&lt;&#x2F;span&gt;(PI), &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0e-6&lt;&#x2F;span&gt;);
    RX_CHECK_FP_ALMOST_EQUAL(&lt;span&gt;sin&lt;&#x2F;span&gt;(PI * &lt;span&gt;0.5&lt;&#x2F;span&gt;), &lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0e-6&lt;&#x2F;span&gt;);
    RX_CHECK_STRING_EQUAL_NO_CASE(&lt;span&gt;&quot;abc&quot;&lt;&#x2F;span&gt;, &lt;span&gt;&quot;ABC&quot;&lt;&#x2F;span&gt;);
}

RX_TEST_CASE(testFailure)
{
    &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Fixture&lt;&#x2F;span&gt; *&lt;span&gt;pData&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

    pData &#x3D; (struct Fixture *)RX_FIXTURE;

    RX_CHECK_INT_EQUAL(pData-&amp;gt;zero, &lt;span&gt;1&lt;&#x2F;span&gt;);
}

&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;RxTestCase&lt;&#x2F;span&gt; &lt;span&gt;cases&lt;&#x2F;span&gt;[]
    &#x3D;&lt;&#x2F;span&gt; {{&lt;span&gt;&quot;basics&quot;&lt;&#x2F;span&gt;, testBasics}, {&lt;span&gt;&quot;failure&quot;&lt;&#x2F;span&gt;, testFailure}};

&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;RxTestSuite&lt;&#x2F;span&gt; &lt;span&gt;suites&lt;&#x2F;span&gt;[]
    &#x3D;&lt;&#x2F;span&gt; {{&lt;span&gt;&quot;example&quot;&lt;&#x2F;span&gt;, &lt;span&gt;sizeof&lt;&#x2F;span&gt; cases &#x2F; &lt;span&gt;sizeof&lt;&#x2F;span&gt; cases[&lt;span&gt;0&lt;&#x2F;span&gt;], cases, setUp, tearDown}};

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;int&lt;&#x2F;span&gt; argc, &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; **ppArgv)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    rxRun(&lt;span&gt;sizeof&lt;&#x2F;span&gt; suites &#x2F; &lt;span&gt;sizeof&lt;&#x2F;span&gt; suites[&lt;span&gt;0&lt;&#x2F;span&gt;], suites, argc, ppArgv);
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which results in the output:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;[PASSED] &quot;example&quot; &#x2F; &quot;basics&quot; (0.009188 ms)
[FAILED] &quot;example&quot; &#x2F; &quot;failure&quot; (0.010063 ms)
.&#x2F;rexo&#x2F;tests&#x2F;example.c:57: nonfatal test failure: ‘pData-&amp;gt;zero’ is expected to be equal to ‘1’
0 &#x3D;&#x3D; 1
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2&gt;Where Is It?&lt;&#x2F;h2&gt;
&lt;p&gt;On &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;rexo&quot;&gt;GitHub&lt;&#x2F;a&gt;, as always!&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;See this &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;a&#x2F;34776737&quot;&gt;condensed example on Stackoverflow&lt;&#x2F;a&gt;.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:897e62c6-6084-11e8-ac47-a31b1af260d4</id>
      <title>Zero: Dynamic Arrays</title>
      <updated>2018-05-26T12:00:00Z</updated>

      <published>2018-05-26T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/zero-dynamic-array" rel="alternate" type="text/html" title="Zero: Dynamic Arrays" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/zero-dynamic-array/code.jpg" />

      <content type="html">
        &lt;p&gt;Dynamic arrays stand for arrays that can grow and shrink in size. They are conceptualized in C++ through the &lt;code&gt;std::vector&lt;&#x2F;code&gt; class, but no such facility is available out of the box in C, leading to countless different implementations by countless different developers. This is my contribution to it!&lt;&#x2F;p&gt;
&lt;p&gt;A trendy, minimalist, implementation comes from Sean Barrett’s &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;nothings&#x2F;stb&#x2F;blob&#x2F;master&#x2F;stretchy_buffer.h&quot;&gt;stretchy buffer&lt;&#x2F;a&gt; but I decided to go for a &lt;em&gt;slightly&lt;&#x2F;em&gt; more involved approach.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;zero-dynamic-array&#x2F;code.jpg&quot; alt&#x3D;&quot;Zero&quot;&gt;&lt;span&gt;View on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Inspired by C++ templates, the library provides a &lt;code&gt;ZR_MAKE_DYNAMIC_ARRAY()&lt;&#x2F;code&gt; macro that generates all the functions needed to manipulate a dynamic array for a given type. The library’s code is slightly abominable but it’s pretty neat on the user’s end, with all the type checking you’d get with the usual function signatures.&lt;&#x2F;p&gt;
&lt;p&gt;One cool feature, as shared by &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&#x2F;blob&#x2F;master&#x2F;include&#x2F;zero&#x2F;allocator.h&quot;&gt;Zero’s allocator library&lt;&#x2F;a&gt; and Sean Barrett’s stretchy buffers, is that these arrays are not wrapped into any sort of structure to make them work—all the bookkeeping required is instead stored in a header invisible to the user, so you can still pass your arrays around as if they were plain C arrays.&lt;&#x2F;p&gt;
&lt;p&gt;For the manipulation side of things, three main families of functions provide facilities to add new elements into an array:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;extend&lt;&#x2F;code&gt;: adds new uninitialized element(s) at a given position and returns a pointer to the first element added&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;insert&lt;&#x2F;code&gt;: adds new element(s) at a given position and initializes them by copying the given input values&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;push&lt;&#x2F;code&gt;: adds a new element at a given position and initializes it by copying the given input value&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;And a single one is dedicated to removing elements: &lt;code&gt;trim&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Each of these 4 functions come in different flavours. While the base one offers an explicit control over where the modification needs to occur, two variants exist as a shortcut to focus on the front and back ends of the arrays.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s a usage example, with error checking omitted for brevity:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; ZR_DEFINE_IMPLEMENTATION&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;zero&#x2F;dynamicarray.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

ZR_MAKE_DYNAMIC_ARRAY(IntArray, &lt;span&gt;int&lt;&#x2F;span&gt;)

&lt;span&gt;&lt;span&gt;int&lt;&#x2F;span&gt;
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;void&lt;&#x2F;span&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;int&lt;&#x2F;span&gt; *pArray;
    &lt;span&gt;int&lt;&#x2F;span&gt; *pSlice;
    &lt;span&gt;int&lt;&#x2F;span&gt; values[&lt;span&gt;2&lt;&#x2F;span&gt;];

    zrCreateIntArray(&amp;amp;pArray, &lt;span&gt;2&lt;&#x2F;span&gt;);
    pArray[&lt;span&gt;0&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;1&lt;&#x2F;span&gt;;
    pArray[&lt;span&gt;1&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;2&lt;&#x2F;span&gt;;
    &lt;span&gt;&#x2F;* pArray: {1, 2} *&#x2F;&lt;&#x2F;span&gt;

    zrResizeIntArray(&amp;amp;pArray, &lt;span&gt;4&lt;&#x2F;span&gt;);
    pArray[&lt;span&gt;2&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;7&lt;&#x2F;span&gt;;
    pArray[&lt;span&gt;3&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;8&lt;&#x2F;span&gt;;
    &lt;span&gt;&#x2F;* pArray: {1, 2, 7, 8} *&#x2F;&lt;&#x2F;span&gt;

    zrExtendIntArray(&amp;amp;pSlice, &amp;amp;pArray, &lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;2&lt;&#x2F;span&gt;);
    pSlice[&lt;span&gt;0&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;3&lt;&#x2F;span&gt;;
    pSlice[&lt;span&gt;1&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;6&lt;&#x2F;span&gt;;
    &lt;span&gt;&#x2F;* pArray: {1, 2, 3, 6, 7, 8} *&#x2F;&lt;&#x2F;span&gt;

    values[&lt;span&gt;0&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;4&lt;&#x2F;span&gt;;
    values[&lt;span&gt;1&lt;&#x2F;span&gt;] &#x3D; &lt;span&gt;5&lt;&#x2F;span&gt;;
    zrInsertIntArray(&amp;amp;pArray, &lt;span&gt;3&lt;&#x2F;span&gt;, &lt;span&gt;sizeof&lt;&#x2F;span&gt; values &#x2F; &lt;span&gt;sizeof&lt;&#x2F;span&gt; values[&lt;span&gt;0&lt;&#x2F;span&gt;], values);
    &lt;span&gt;&#x2F;* pArray: {1, 2, 3, 4, 5, 6, 7, 8} *&#x2F;&lt;&#x2F;span&gt;

    zrPushIntArrayFront(&amp;amp;pArray, &lt;span&gt;0&lt;&#x2F;span&gt;);
    zrPushIntArrayBack(&amp;amp;pArray, &lt;span&gt;9&lt;&#x2F;span&gt;);
    &lt;span&gt;&#x2F;* pArray: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} *&#x2F;&lt;&#x2F;span&gt;

    zrDestroyIntArray(pArray);

    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I’m not sure what the drawbacks of this approach are (if any) but that’s &lt;em&gt;exactly&lt;&#x2F;em&gt; why I decided to experiment with it!&lt;&#x2F;p&gt;
&lt;p&gt;If you have some feedback, please do share!&lt;&#x2F;p&gt;
&lt;p&gt;The implementation is available &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&quot;&gt;on GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:97e12598-5a52-11e8-a113-23a475002cd1</id>
      <title>Houdini: Ray Tracer in VEX</title>
      <updated>2018-05-23T12:00:00Z</updated>

      <published>2018-05-18T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/houdini-vex-ray-tracer" rel="alternate" type="text/html" title="Houdini: Ray Tracer in VEX" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/houdini-vex-ray-tracer/ray-tracer.jpg" />

      <content type="html">
        &lt;p&gt;I’ve seen many people sharing fun ray tracers experiments with Houdini’s VEX in the past,&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and it just happens that I’ve had many ~10 min bursts of time to spare due to waiting for simulation computations, so I grabbed a copy of Peter Shirley’s mini book &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;Ray-Tracing-Weekend-Minibooks-Book-ebook&#x2F;dp&#x2F;B01B5AODD8&quot;&gt;&lt;cite&gt;Ray Tracing in One Weekend&lt;&#x2F;cite&gt;&lt;&#x2F;a&gt;&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and gave it a go.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;270611556&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;houdini-vex-ray-tracer&#x2F;ray-tracer.jpg&quot; alt&#x3D;&quot;Ray Tracer in VEX&quot;&gt;&lt;span&gt;&lt;&#x2F;span&gt;&lt;span&gt;Watch on Vimeo&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’ve used Houdini facilities when possible, such as various SOP nodes to build the scene and the built-in VEX functions like &lt;code&gt;intersect()&lt;&#x2F;code&gt;, &lt;code&gt;fresnel()&lt;&#x2F;code&gt;, &lt;code&gt;reflect()&lt;&#x2F;code&gt;, &lt;code&gt;refract()&lt;&#x2F;code&gt;, and others, which allowed me to take a few shortcuts.&lt;&#x2F;p&gt;
&lt;p&gt;Hence it’s more of an adaptation rather than a 100% faithful implementation of the book, but the results are fairly similar.&lt;&#x2F;p&gt;
&lt;p&gt;Still, it’s been plenty enough to give a good introduction to ray tracing and I’m happy with that!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;houdini-vex-ray-tracer&#x2F;rendering-plane.gif&quot; alt&#x3D;&quot;Rendering Plane&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Performance-wise though... it’s not there yet. Probably a good contender for an implementation using an OpenCL SOP node?&lt;&#x2F;p&gt;
&lt;p&gt;The scene is available in the download link below, but I’ll also throw the VEX code here for quick reference:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; MAX_BOUNCES 8&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; NEAR_DISTANCE 1.0e-3&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; FAR_DISTANCE 1.0e3&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; MATERIAL_LAMBERT 0&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; MATERIAL_METAL 1&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; MATERIAL_DIELECTRIC 2&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; GEOS_INPUT_IDX 1&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; SKY_RAMP_NAME &lt;span&gt;&quot;sky&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; ALBEDO_ATTRIB_NAME &lt;span&gt;&quot;Cd&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; NORMAL_ATTRIB_NAME &lt;span&gt;&quot;N&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; MATERIAL_ATTRIB_NAME &lt;span&gt;&quot;material&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; FUZZINESS_ATTRIB_NAME &lt;span&gt;&quot;fuzziness&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;define&lt;&#x2F;span&gt; IOR_ATTRIB_NAME &lt;span&gt;&quot;ior&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Ray&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;vector&lt;&#x2F;span&gt; origin;
    &lt;span&gt;vector&lt;&#x2F;span&gt; direction;
};

&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;Hit&lt;&#x2F;span&gt; {&lt;&#x2F;span&gt;
    &lt;span&gt;int&lt;&#x2F;span&gt; prim;
    &lt;span&gt;vector&lt;&#x2F;span&gt; uvw;
    &lt;span&gt;vector&lt;&#x2F;span&gt; pos;
    &lt;span&gt;vector&lt;&#x2F;span&gt; normal;
};

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;trace&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; hitten; &lt;span&gt;export&lt;&#x2F;span&gt; Hit hit; &lt;span&gt;const&lt;&#x2F;span&gt; Ray ray)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;vector&lt;&#x2F;span&gt; pos;
    &lt;span&gt;vector&lt;&#x2F;span&gt; uvw;
    &lt;span&gt;int&lt;&#x2F;span&gt; prim;

    prim &#x3D; intersect(
        GEOS_INPUT_IDX,
        ray.origin
            + ray.direction * {NEAR_DISTANCE, NEAR_DISTANCE, NEAR_DISTANCE},
        ray.direction * {FAR_DISTANCE, FAR_DISTANCE, FAR_DISTANCE},
        pos,
        uvw);
    hitten &#x3D; prim &amp;gt; &lt;span&gt;-1&lt;&#x2F;span&gt;;
    &lt;span&gt;if&lt;&#x2F;span&gt; (hitten) {
        hit.prim &#x3D; prim;
        hit.uvw &#x3D; uvw;
        hit.pos &#x3D; pos;
        hit.normal &#x3D; primuv(
            GEOS_INPUT_IDX, NORMAL_ATTRIB_NAME, hit.prim, hit.uvw);
    }
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;evalLambertianMaterial&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; scattered;
                       &lt;span&gt;export&lt;&#x2F;span&gt; Ray scatteredRay;
                       &lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; attenuation;
                       &lt;span&gt;const&lt;&#x2F;span&gt; Ray ray;
                       &lt;span&gt;const&lt;&#x2F;span&gt; Hit hit)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;vector&lt;&#x2F;span&gt; randomPos;
    &lt;span&gt;vector&lt;&#x2F;span&gt; target;

    randomPos &#x3D; sample_sphere_uniform(&lt;span&gt;set&lt;&#x2F;span&gt;(nrandom(), nrandom(), nrandom()));
    target &#x3D; hit.pos + hit.normal + randomPos;

    attenuation &#x3D; primuv(GEOS_INPUT_IDX, ALBEDO_ATTRIB_NAME, hit.prim, hit.uvw);
    scatteredRay &#x3D; Ray(hit.pos, normalize(target - hit.pos));
    scattered &#x3D; &lt;span&gt;1&lt;&#x2F;span&gt;;
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;evalMetallicMaterial&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; scattered;
                     &lt;span&gt;export&lt;&#x2F;span&gt; Ray scatteredRay;
                     &lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; attenuation;
                     &lt;span&gt;const&lt;&#x2F;span&gt; Ray ray;
                     &lt;span&gt;const&lt;&#x2F;span&gt; Hit hit)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;vector&lt;&#x2F;span&gt; randomPos;
    &lt;span&gt;float&lt;&#x2F;span&gt; fuzziness;

    randomPos &#x3D; sample_sphere_uniform(&lt;span&gt;set&lt;&#x2F;span&gt;(nrandom(), nrandom(), nrandom()));
    fuzziness &#x3D; min(
        primuv(GEOS_INPUT_IDX, FUZZINESS_ATTRIB_NAME, hit.prim, hit.uvw), &lt;span&gt;1.0&lt;&#x2F;span&gt;);

    attenuation &#x3D; primuv(GEOS_INPUT_IDX, ALBEDO_ATTRIB_NAME, hit.prim, hit.uvw);
    scatteredRay &#x3D; Ray(
        hit.pos, reflect(ray.direction, hit.normal) + randomPos * fuzziness);
    scattered &#x3D; dot(scatteredRay.direction, hit.normal) &amp;gt; &lt;span&gt;0&lt;&#x2F;span&gt;;
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;evalDielectricMaterial&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;int&lt;&#x2F;span&gt; scattered;
                       &lt;span&gt;export&lt;&#x2F;span&gt; Ray scatteredRay;
                       &lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; attenuation;
                       &lt;span&gt;const&lt;&#x2F;span&gt; Ray ray;
                       &lt;span&gt;const&lt;&#x2F;span&gt; Hit hit)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;float&lt;&#x2F;span&gt; iDotN;
    &lt;span&gt;float&lt;&#x2F;span&gt; ior;
    &lt;span&gt;float&lt;&#x2F;span&gt; iorRatio;
    &lt;span&gt;vector&lt;&#x2F;span&gt; outNormal;
    &lt;span&gt;float&lt;&#x2F;span&gt; cosine;
    &lt;span&gt;float&lt;&#x2F;span&gt; reflectedAmount;
    &lt;span&gt;float&lt;&#x2F;span&gt; refractedAmount;

    attenuation &#x3D; {&lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0&lt;&#x2F;span&gt;};
    scattered &#x3D; &lt;span&gt;1&lt;&#x2F;span&gt;;

    iDotN &#x3D; dot(ray.direction, hit.normal);
    ior &#x3D; ior &#x3D; primuv(GEOS_INPUT_IDX, IOR_ATTRIB_NAME, hit.prim, hit.uvw);

    &lt;span&gt;if&lt;&#x2F;span&gt; (iDotN &amp;lt; &lt;span&gt;0&lt;&#x2F;span&gt;) {
        &lt;span&gt;&#x2F;&#x2F; The ray comes from outside the surface.&lt;&#x2F;span&gt;
        iorRatio &#x3D; &lt;span&gt;1.0&lt;&#x2F;span&gt; &#x2F; ior;
        outNormal &#x3D; hit.normal;
        cosine &#x3D; -iDotN &#x2F; length(ray.direction);
    } &lt;span&gt;else&lt;&#x2F;span&gt; {
        &lt;span&gt;&#x2F;&#x2F; The ray comes from inside the surface.&lt;&#x2F;span&gt;
        iorRatio &#x3D; ior;
        outNormal &#x3D; -hit.normal;
        cosine &#x3D; ior * iDotN &#x2F; length(ray.direction);
    }

    fresnel(
        ray.direction, outNormal, iorRatio, reflectedAmount, refractedAmount);
    &lt;span&gt;if&lt;&#x2F;span&gt; (nrandom() &amp;lt; reflectedAmount) {
        scatteredRay &#x3D; Ray(hit.pos, reflect(ray.direction, hit.normal));
        &lt;span&gt;return&lt;&#x2F;span&gt;;
    }

    scatteredRay &#x3D; Ray(hit.pos, refract(ray.direction, outNormal, iorRatio));
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;getColor&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; color; &lt;span&gt;const&lt;&#x2F;span&gt; Ray ray)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;int&lt;&#x2F;span&gt; i;
    Ray &lt;span&gt;stack&lt;&#x2F;span&gt;[];
    &lt;span&gt;vector&lt;&#x2F;span&gt; accumulator[];
    &lt;span&gt;int&lt;&#x2F;span&gt; hitten;
    Hit hit;

    i &#x3D; &lt;span&gt;0&lt;&#x2F;span&gt;;
    push(&lt;span&gt;stack&lt;&#x2F;span&gt;, ray);
    &lt;span&gt;while&lt;&#x2F;span&gt; (len(&lt;span&gt;stack&lt;&#x2F;span&gt;) &amp;gt; &lt;span&gt;0&lt;&#x2F;span&gt;) {
        Ray currentRay;

        currentRay &#x3D; pop(&lt;span&gt;stack&lt;&#x2F;span&gt;);
        trace(hitten, hit, currentRay);
        &lt;span&gt;if&lt;&#x2F;span&gt; (hitten &amp;amp;&amp;amp; i &amp;lt; MAX_BOUNCES) {
            &lt;span&gt;int&lt;&#x2F;span&gt; material;
            &lt;span&gt;int&lt;&#x2F;span&gt; scattered;
            Ray scatteredRay;
            &lt;span&gt;vector&lt;&#x2F;span&gt; attenuation;

            material &#x3D; prim(GEOS_INPUT_IDX, MATERIAL_ATTRIB_NAME, hit.prim);
            &lt;span&gt;if&lt;&#x2F;span&gt; (material &#x3D;&#x3D; MATERIAL_LAMBERT) {
                evalLambertianMaterial(
                    scattered, scatteredRay, attenuation, currentRay, hit);
            } &lt;span&gt;else&lt;&#x2F;span&gt; &lt;span&gt;if&lt;&#x2F;span&gt; (material &#x3D;&#x3D; MATERIAL_METAL) {
                evalMetallicMaterial(
                    scattered, scatteredRay, attenuation, currentRay, hit);
            } &lt;span&gt;else&lt;&#x2F;span&gt; &lt;span&gt;if&lt;&#x2F;span&gt; (material &#x3D;&#x3D; MATERIAL_DIELECTRIC) {
                evalDielectricMaterial(
                    scattered, scatteredRay, attenuation, currentRay, hit);
            } &lt;span&gt;else&lt;&#x2F;span&gt; {
                warning(&lt;span&gt;&quot;unsupported material&quot;&lt;&#x2F;span&gt;);
                push(accumulator, {&lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;0.078&lt;&#x2F;span&gt;, &lt;span&gt;0.576&lt;&#x2F;span&gt;});
                &lt;span&gt;break&lt;&#x2F;span&gt;;
            }

            &lt;span&gt;if&lt;&#x2F;span&gt; (scattered) {
                push(accumulator, attenuation);
                push(&lt;span&gt;stack&lt;&#x2F;span&gt;, scatteredRay);
            } &lt;span&gt;else&lt;&#x2F;span&gt; {
                push(accumulator, {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;});
            }

            ++i;
        } &lt;span&gt;else&lt;&#x2F;span&gt; {
            &lt;span&gt;float&lt;&#x2F;span&gt; v;

            v &#x3D; dot(normalize(&lt;span&gt;set&lt;&#x2F;span&gt;(
                        currentRay.direction.x, &lt;span&gt;0.0&lt;&#x2F;span&gt;, currentRay.direction.z)),
                    currentRay.direction);
            v &#x3D; currentRay.direction.y &amp;lt; &lt;span&gt;0&lt;&#x2F;span&gt; ? &lt;span&gt;0.0&lt;&#x2F;span&gt; : &lt;span&gt;1.0&lt;&#x2F;span&gt; - v;
            push(accumulator, chramp(SKY_RAMP_NAME, v));
        }
    }

    color &#x3D; {&lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0&lt;&#x2F;span&gt;};
    &lt;span&gt;for&lt;&#x2F;span&gt; (i &#x3D; &lt;span&gt;0&lt;&#x2F;span&gt;; i &amp;lt; len(accumulator); ++i) {
        color *&#x3D; accumulator[i];
    }
}

&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt;
&lt;span&gt;getCameraRay&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; Ray ray;
             &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; throughPos;
             &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; pos;
             &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; u;
             &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; v;
             &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;float&lt;&#x2F;span&gt; aperture)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    vector2 randomPos;
    &lt;span&gt;vector&lt;&#x2F;span&gt; origin;

    randomPos &#x3D; sample_circle_uniform(&lt;span&gt;set&lt;&#x2F;span&gt;(nrandom(), nrandom()));
    randomPos *&#x3D; aperture * &lt;span&gt;0.5&lt;&#x2F;span&gt;;
    origin &#x3D; pos + u * randomPos.x + v * randomPos.y;
    ray &#x3D; Ray(origin, normalize(throughPos - origin));
}

&lt;span&gt;cvex
&lt;span&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;span&gt;export&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; Cd &#x3D; {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
     &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; P &#x3D; {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
     &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; cameraPos &#x3D; {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
     &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; cameraU &#x3D; {&lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
     &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;vector&lt;&#x2F;span&gt; cameraV &#x3D; {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;1.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
     &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;float&lt;&#x2F;span&gt; cameraAperture &#x3D; &lt;span&gt;1.0&lt;&#x2F;span&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    Ray ray;

    Cd &#x3D; {&lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;, &lt;span&gt;0.0&lt;&#x2F;span&gt;};
    getCameraRay(ray, P, cameraPos, cameraU, cameraV, cameraAperture);
    getColor(Cd, ray);
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2&gt;Download&lt;&#x2F;h2&gt;
&lt;div&gt;
&lt;p&gt;&lt;span aria-hidden&#x3D;&quot;true&quot;&gt;&lt;&#x2F;span&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;houdini-vex-ray-tracer&#x2F;ray-tracer-01.zip&quot;&gt;&lt;strong&gt;ray-tracer-01 v2&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This file contains a scene created with &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sidefx.com&quot;&gt;SideFX&lt;&#x2F;a&gt;’s Houdini 16.5 as well as any required asset.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;189423315&quot;&gt;Anatolii Iudanov&lt;&#x2F;a&gt;, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;22438117&quot;&gt;Matt Ebb&lt;&#x2F;a&gt;, and others.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;Also available as “pay what you want” PDFs: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;twitter.com&#x2F;Peter_shirley&#x2F;status&#x2F;985561344555417600&quot;&gt;https:&#x2F;&#x2F;twitter.com&#x2F;Peter_shirley&#x2F;status&#x2F;985561344555417600&lt;&#x2F;a&gt;&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:df835aa2-2fe5-11e8-9bbe-4bf34c641fcc</id>
      <title>Houdini: Wind Tunnel</title>
      <updated>2018-04-04T12:00:00Z</updated>

      <published>2018-03-31T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/wind-tunnel" rel="alternate" type="text/html" title="Houdini: Wind Tunnel" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/wind-tunnel/wind-tunnel.jpg" />

      <content type="html">
        &lt;p&gt;Computer graphics can help making impossible things become reality—so how about having a heavy metallic bull sculpture being pushed away by a mere 15 m&#x2F;s breeze coming from within a wind tunnel? Definitely something worth making happen!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;262619263&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;wind-tunnel.jpg&quot; alt&#x3D;&quot;Wind tunnel&quot;&gt;&lt;span&gt;&lt;&#x2F;span&gt;&lt;span&gt;Watch on Vimeo&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Smoke Flows Breakdown&lt;&#x2F;h2&gt;
&lt;p&gt;The primary objective is to manage having the flows of smoke to look as continuous as possible.&lt;&#x2F;p&gt;
&lt;p&gt;To this purpose, the idea here is to have each flow sourced on the left side of the container from a &lt;em&gt;Tube SOP&lt;&#x2F;em&gt; whose length depends on the wind speed per simulation step rounded to the nearest voxel unit.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;coefficient &#x3D; fps * substeps * voxel &lt;span&gt;size&lt;&#x2F;span&gt;
&lt;span&gt;wind&lt;&#x2F;span&gt; &lt;span&gt;speed&lt;&#x2F;span&gt; per &lt;span&gt;step&lt;&#x2F;span&gt; &lt;span&gt;in&lt;&#x2F;span&gt; voxels &#x3D; &lt;span&gt;round&lt;&#x2F;span&gt;(&lt;span&gt;wind&lt;&#x2F;span&gt; &lt;span&gt;speed&lt;&#x2F;span&gt; &#x2F; coefficient)
adjusted &lt;span&gt;wind&lt;&#x2F;span&gt; &lt;span&gt;speed&lt;&#x2F;span&gt; &#x3D; &lt;span&gt;wind&lt;&#x2F;span&gt; &lt;span&gt;speed&lt;&#x2F;span&gt; per &lt;span&gt;step&lt;&#x2F;span&gt; &lt;span&gt;in&lt;&#x2F;span&gt; voxels * coefficient
tube length &#x3D; &lt;span&gt;wind&lt;&#x2F;span&gt; &lt;span&gt;speed&lt;&#x2F;span&gt; per &lt;span&gt;step&lt;&#x2F;span&gt; &lt;span&gt;in&lt;&#x2F;span&gt; voxels * voxel &lt;span&gt;size&lt;&#x2F;span&gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then, the &lt;em&gt;Smoke Solver DOP&lt;&#x2F;em&gt; needs to “push” the density field towards the right to leave just enough room for some more smoke to be sourced on the left side during the next simulation step.&lt;&#x2F;p&gt;
&lt;p&gt;This is done by having the velocity field of the simulation to approximately match the adjusted wind speed at all times around the left side of the container. The &lt;em&gt;Smoke Object DOP&lt;&#x2F;em&gt;’s &lt;em&gt;Wind Tunnel Direction&lt;&#x2F;em&gt; parameter helps to do precisely that but I ended up using a custom setup instead because it seems to provide slightly better results and there’s more to learn this way, heh!&lt;&#x2F;p&gt;
&lt;p&gt;Nothing too fancy in this setup anyway—the container is initialized with the wind’s velocity and then some more velocity is added at each step in the left side. The continuous pumping of fresh new velocity is required because the advection of the velocity field by itself at each simulation step causes a loss of velocity in the sourcing area that needs to be compensated.&lt;&#x2F;p&gt;
&lt;p&gt;By this point, and with the collision source plugged in, the flows look alright but some small discontinuities are still visible in the density field. Fortunately these are minor enough to be disregarded—they are bound to disappear altogether once rendered with motion blur.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;smoke-flows-density-1.png&quot; alt&#x3D;&quot;Density field&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;density field—some discontinuities are visible&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;smoke-flows-velocity-1.png&quot; alt&#x3D;&quot;Velocity field&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;velocity field—losing a bit of steam on the front&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To give a more interesting look, the linear shape of the flows is broken by adding a tiny bit of noise to the velocity source, as well as a &lt;em&gt;Gas Vortex Confinement DOP&lt;&#x2F;em&gt; to boost the swirls, and a &lt;em&gt;Gas Turbulence DOP&lt;&#x2F;em&gt; around the collision area.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;smoke-flows-density-2.png&quot; alt&#x3D;&quot;Density field&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;density field after breaking its linearity&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Finally the simulation substeps are set to 2 simply because it brings a more interesting motion.&lt;&#x2F;p&gt;
&lt;h2&gt;Dust Breakdown&lt;&#x2F;h2&gt;
&lt;p&gt;Since a bull sculpture sliding in these conditions doesn’t make any sense anyway, I decided to furthermore pursue in this direction by adding a dust effect as if some dirt were, somehow, laying on the ground.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Yup.&lt;&#x2F;p&gt;
&lt;p&gt;To emit dust whenever the bull is sliding, a volume is first generated around the bull’s feet, near the ground, with an added pinch of noise here and there. It is then multiplied by the surrounding velocity of the bull, thus nullifying its density as the bull comes to a standstill.&lt;&#x2F;p&gt;
&lt;p&gt;The rest is even more straightforward—the volume and the points scattered from it are both advected by the velocity from the smoke flows to respectively simulate the dust particles and the dust smoke.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;dust-sources.png&quot; alt&#x3D;&quot;Dust sources&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;dust sources&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;dust-advection.png&quot; alt&#x3D;&quot;Dust advection&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;dust advection&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;At render time, the dust particles and smoke are set to be mostly transparent to not distract from the main effect: the smoke flows.&lt;&#x2F;p&gt;
&lt;h2&gt;Download&lt;&#x2F;h2&gt;
&lt;div&gt;
&lt;p&gt;&lt;span aria-hidden&#x3D;&quot;true&quot;&gt;&lt;&#x2F;span&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;wind-tunnel&#x2F;wind-tunnel-01.zip&quot;&gt;&lt;strong&gt;wind-tunnel-01 v2&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This file contains a scene created with &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sidefx.com&quot;&gt;SideFX&lt;&#x2F;a&gt;’s Houdini 16.5 as well as any required asset.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;h2&gt;References&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v&#x3D;E9ZSAX56m0E&quot;&gt;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v&#x3D;E9ZSAX56m0E&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2&gt;Credits&lt;&#x2F;h2&gt;
&lt;p&gt;The bull model and its texture maps were created and shared under the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-sa&#x2F;4.0&quot;&gt;CC Attribution-ShareAlike license&lt;&#x2F;a&gt; by Zafio via &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;sketchfab.com&#x2F;models&#x2F;3nXiIkAd3IlHVNc4hqw4VmaX3tK&quot;&gt;Sketchfab&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;It’s a good exercise anyhow.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:86af32b4-fdbf-11e7-9b7b-1f8902ff2da9</id>
      <title>Zero: A Bunch of Single-File Libraries for C&#x2F;C++</title>
      <updated>2018-05-26T12:00:00Z</updated>

      <published>2018-01-20T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/zero" rel="alternate" type="text/html" title="Zero: A Bunch of Single-File Libraries for C&#x2F;C++" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/zero/code.jpg" />

      <content type="html">
        &lt;p&gt;For the needs of a project I’m working on, I had to write an &lt;strong&gt;aligned&lt;&#x2F;strong&gt; allocator on top of &lt;code&gt;malloc()&#x2F;realloc()&#x2F;free()&lt;&#x2F;code&gt; and a simple logger featuring different log levels and basic colouring. Nothing too fancy here.&lt;&#x2F;p&gt;
&lt;p&gt;It was obvious that the code had nothing specific to that project and was in fact a good fit for a more general-purpose library.&lt;&#x2F;p&gt;
&lt;p&gt;So I decided to jump into the single-file, header-only, libraries bandwagon started by Sean Barrett and his popular &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;nothings&#x2F;stb&quot;&gt;stb&lt;&#x2F;a&gt; project by releasing my own: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&quot;&gt;Zero&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;zero&#x2F;code.jpg&quot; alt&#x3D;&quot;Zero&quot;&gt;&lt;span&gt;View on GitHub&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;While developing Zero, I was influenced by the &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;ourmachinery.com&#x2F;post&#x2F;physical-design&quot;&gt;Physical Design of The Machinery&lt;&#x2F;a&gt; and chose not to have any &lt;code&gt;#include&lt;&#x2F;code&gt; dependency within the library headers. That’s right. Not even standard headers. &lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Why’s that? Because standard headers contain &lt;em&gt;thousands&lt;&#x2F;em&gt; of lines of code when counting their own dependencies and I do not want to slow down compilation times by burdening projects that include my headers into their own headers. At least, not without a good reason. Are fixed integer types or &lt;code&gt;size_t&lt;&#x2F;code&gt; a good enough reason? Not even.&lt;&#x2F;p&gt;
&lt;p&gt;Most projects target common platforms (Windows, Linux, macOS) and hence use either the ILP32, LP64, or LLP64 data models. All of these data models guarantee the &lt;code&gt;char&lt;&#x2F;code&gt; type to be 8 bits, &lt;code&gt;short&lt;&#x2F;code&gt; to be 16 bits, &lt;code&gt;int&lt;&#x2F;code&gt; to be 32 bits, and &lt;code&gt;long long&lt;&#x2F;code&gt; to be 64 bits. For people using more exotic platforms, Zero can fallback to using &lt;code&gt;&amp;lt;stdint.h&amp;gt;&lt;&#x2F;code&gt; if the macro &lt;code&gt;ZR_USE_STD_FIXED_TYPES&lt;&#x2F;code&gt; is defined. Even better, each type can be overridden individually through macros such as &lt;code&gt;ZR_INT32&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The same goes for &lt;code&gt;size_t&lt;&#x2F;code&gt;—on almost all platforms the size of &lt;code&gt;size_t&lt;&#x2F;code&gt; equals the targetted architecture, that is either 32 or 64 bits. Here again, there is a fallback macro with &lt;code&gt;ZR_USE_STD_BASIC_TYPES&lt;&#x2F;code&gt; that includes &lt;code&gt;&amp;lt;stddef.h&amp;gt;&lt;&#x2F;code&gt;, and a more granular one &lt;code&gt;ZR_SIZE_TYPE&lt;&#x2F;code&gt; to directly set the size type to be used.&lt;&#x2F;p&gt;
&lt;p&gt;Note that these custom types are only used in the public interface defined within the headers. The implementation side of things is free to include whatever file is needed, which is fine since these dependencies are to remain within a single translation unit rather than spreading all over the place through the &lt;code&gt;#include&lt;&#x2F;code&gt; mechanism.&lt;&#x2F;p&gt;
&lt;p&gt;As always, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;zero&quot;&gt;it’s all on GitHub&lt;&#x2F;a&gt;, so go get it!&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;Alright, there is a single exception with &lt;code&gt;&amp;lt;stdarg.h&amp;gt;&lt;&#x2F;code&gt; and that’s because one function uses &lt;code&gt;va_list&lt;&#x2F;code&gt; as a parameter. But in my defense, &lt;code&gt;&amp;lt;stdarg.h&amp;gt;&lt;&#x2F;code&gt; has no other dependency (on my platform, at least) and is fairly short (~100 LOC).&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:aa02ebbc-a710-11e7-a325-af1c7fa7c97d</id>
      <title>New Look and a Brand New Static Site Generator</title>
      <updated>2017-10-04T13:00:00Z</updated>

      <published>2017-10-01T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/misc/new-look" rel="alternate" type="text/html" title="New Look and a Brand New Static Site Generator" />
        <category term="misc" />
        <media:thumbnail url="https://christophercrouzet.com/blog/misc/new-look/eddi-preview.png" />

      <content type="html">
        &lt;p&gt;It seems that whenever I’m looking forward to turn a page in my life, there’s a little—but &lt;em&gt;insisting&lt;&#x2F;em&gt;—voice inside calling me to update my website. Not being a huge fan of web development, it’s something that I &lt;em&gt;truly&lt;&#x2F;em&gt; dread but then it makes me feel somehow freshened once it’s done. And boy, does releasing this new version has been rewarding for me!&lt;&#x2F;p&gt;
&lt;p&gt;I threw away all the PHP, SQL, JavaSript, and basically anything that could slow down page loadings on the client side, including analytics. I also stopped relying on &lt;abbr title&#x3D;&quot;Content Management System such as Dotclear or WordPress&quot;&gt;CMS&lt;&#x2F;abbr&gt; solutions and wrote the back-end myself, that is a static site generator called Eddi. All this for the sake of having &lt;em&gt;full&lt;&#x2F;em&gt; control over the complete process.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.github.io&#x2F;eddi&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;misc&#x2F;new-look&#x2F;eddi-preview.png&quot; alt&#x3D;&quot;Eddi’s default theme&quot;&gt;&lt;span&gt;View demo&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;eddi’s default theme&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I ended up spending &lt;em&gt;waaay&lt;&#x2F;em&gt; more time than I hoped to so towards the end I started cutting some corners with the code which translates into some small bits here and there being not so well implemented. But it’s not too shabby for a first go, overall I’m fairly happy with the end result—it’s now much easier to maintain my blog and I see the overall structure of the generator to be a solid base that I could build upon again in the future.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;What This Update Means for You&lt;&#x2F;h2&gt;
&lt;dl&gt;
&lt;dt&gt;Design&lt;&#x2F;dt&gt;
&lt;dd&gt;I somehow managed to make the design even simpler than the previous edition while also making all the content easily accessible. Gone are the days where you had to click, load, scroll down, and all over again 10+ times to see older articles. Well, no one ever bothered doing that anyways.&lt;&#x2F;dd&gt;
&lt;dt&gt;Speed&lt;&#x2F;dt&gt;
&lt;dd&gt;The server doesn’t have anymore to dynamically generate each page that the visitor is requesting, which can take a bit of time when querying databases and such. All the assets are now static which means that the HTML pages already exist and only have to be served as-is.&lt;&#x2F;dd&gt;
&lt;dt&gt;Privacy&lt;&#x2F;dt&gt;
&lt;dd&gt;No more Google Analytics, Google Fonts, or embedded videos from third-party services such as Vimeo. All the pages and resources are now served directly from my host, which means no tracking whatsoever from the tech giants.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;dd&gt;
&lt;dt&gt;Comments&lt;&#x2F;dt&gt;
&lt;dd&gt;They are gone. This is a side-effect of having a static site and not wanting to rely on third-party services. Which is a bit &lt;em&gt;sad&lt;&#x2F;em&gt;. I counted 149 comments in total (48 of them being written by myself) making it a great place to interact with you. I truly appreciated reading all the kind words, debates, critics, and whatnot, so &lt;strong&gt;thank you&lt;&#x2F;strong&gt;. At the end of the day, emails are well-suited for discussions, so you know what to do!&lt;&#x2F;dd&gt;
&lt;&#x2F;dl&gt;
&lt;p&gt;I hope you guys like it!&lt;&#x2F;p&gt;
&lt;h2&gt;Can I Has the Static Site Generator?&lt;&#x2F;h2&gt;
&lt;p&gt;Yes, you can. Eddi and its default theme are free and open-source so nothing’s stopping you from using it to build your own site. Except that this is &lt;em&gt;not&lt;&#x2F;em&gt; so accessible nor user-friendly. You’d need to be fairly tech-savvy and even then I skipped writing any sort of documentation for now so it’s a bit rough.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn3&quot; id&#x3D;&quot;fnref3&quot;&gt;[3]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can find the source in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;eddi&quot;&gt;eddi’s repository on GitHub&lt;&#x2F;a&gt; and you’ll also need &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;nodejs.org&quot;&gt;Node.js&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you still feel like giving it a spin and are hitting walls, do get in touch!&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;The content (articles) for the site are just a &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;git-scm.com&quot;&gt;&lt;abbr title&#x3D;&quot;A version control system&quot;&gt;git&lt;&#x2F;abbr&gt;&lt;&#x2F;a&gt; repository with &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Markdown&quot;&gt;&lt;abbr title&#x3D;&quot;A human-friendly markup language that can be converted to HTML&quot;&gt;Markdown&lt;&#x2F;abbr&gt;&lt;&#x2F;a&gt; files that I can &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Rsync&quot;&gt;&lt;abbr title&#x3D;&quot;A command-line tool that helps transferring and synchronizing files between two locations&quot;&gt;rsync&lt;&#x2F;abbr&gt;&lt;&#x2F;a&gt; to the remote server.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;You might want to check &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;donw.io&#x2F;post&#x2F;github-comments&quot;&gt;Replacing Disqus with Github Comments&lt;&#x2F;a&gt; for an example of what to expect when you are visiting websites using such services. And while we’re there, have a look at &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.privacytools.io&quot;&gt;privacytools.io&lt;&#x2F;a&gt;.&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn3&quot;&gt;And I probably will never write any documentation for this project because it would take me a lot of time and I don’t foresee many people interested in using Eddi.&lt;a href&#x3D;&quot;#fnref3&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:1423e932-c2ce-4195-a9cb-acb7483993de</id>
      <title>Hienoi: A 2D Particle Playground</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2017-05-09T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/hienoi" rel="alternate" type="text/html" title="Hienoi: A 2D Particle Playground" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/hienoi/particle-trail.gif" />

      <content type="html">
        &lt;p&gt;I initially started this project with the goal of providing a visual programming framework to introduce my friend to coding in a &lt;strong&gt;fun&lt;&#x2F;strong&gt; way. I could have used other existing projects such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;processing.org&quot;&gt;Processing&lt;&#x2F;a&gt; instead but then I thought it’d be a good experience for me to write my own.&lt;&#x2F;p&gt;
&lt;p&gt;So these were my 2 objectives. Making something educational that is fun to use, and honing my own skills.&lt;&#x2F;p&gt;
&lt;p&gt;I quickly reached a working prototype but then, &lt;em&gt;it happened&lt;&#x2F;em&gt;. &lt;q&gt;Hey, you know what?&lt;&#x2F;q&gt;... &lt;q&gt;How about putting the GUI and simulation modules into their own processes? And using OpenGL instead of the software renderer? And using NumPy under the hood to speed things up while preserving a user-friendly API? And allowing users to add new attributes to particles?&lt;&#x2F;q&gt; And, and, and... and I kept going like that for a while, even writing a helper library in the process, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;nani&quot;&gt;Nani&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;hienoi&#x2F;particle-trail.gif&quot; alt&#x3D;&quot;Particle trail&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;particle trail&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’m still not satisfied with the amount of functionalities provided but hey, for a &lt;em&gt;toy&lt;&#x2F;em&gt; project, this is not that bad and it has already fulfilled its main objectives. I think.&lt;&#x2F;p&gt;
&lt;p&gt;Have a go at it, grab &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;hienoi&quot;&gt;Hienoi on GitHub&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:bfcbd1dc-2ee4-4c49-a383-ec6ee1c3f98f</id>
      <title>Release All the Things!</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2017-01-13T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/release-all-the-things" rel="alternate" type="text/html" title="Release All the Things!" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/release-all-the-things/github-activity.gif" />

      <content type="html">
        &lt;p&gt;It’s been a while since I last coded anything and it’s been itching me ever since. So I paused whatever I was up to and got back onto coding a few things here and there.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;release-all-the-things&#x2F;github-activity.jpg&quot; alt&#x3D;&quot;GitHub activity&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;does not represent the time spent working on these libraries&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Despite the small size of these 4 libraries, I’ve put a lot of efforts into trying to release quality products by designing the code to the higher standard permitted by my—&lt;em&gt;humble&lt;&#x2F;em&gt;—knowledge, but also by covering most of the code with tests, polishing the documentations as much as I possibly could, and even providing benchmarks where it made sense. And &lt;em&gt;for once&lt;&#x2F;em&gt;, I’m quite happy with the results!&lt;&#x2F;p&gt;
&lt;p&gt;The libraries that I have entirely rewritten are &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;gorilla&quot;&gt;Gorilla&lt;&#x2F;a&gt;, a convenient approach to monkey patching, and &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;bana&quot;&gt;Bana&lt;&#x2F;a&gt;, a set of extensions for Autodesk Maya’s Python API.&lt;&#x2F;p&gt;
&lt;p&gt;As for the the new ones, there is &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;revl&quot;&gt;Revl&lt;&#x2F;a&gt;, which helps to benchmark code for Autodesk Maya, and &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;nani&quot;&gt;Nani&lt;&#x2F;a&gt;, an alternative approach to defining and viewing NumPy’s arrays.&lt;&#x2F;p&gt;
&lt;p&gt;It’s been a great exercise to improve my skills in designing, building, and shipping libraries, and I hope that these projects will also serve as good examples to others or simply be useful at all.&lt;&#x2F;p&gt;
&lt;p&gt;More should be coming soon but for now... get them &lt;strong&gt;all&lt;&#x2F;strong&gt;!&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:9b71c2aa-a65f-11e7-89dc-0315080ed8e5</id>
      <title>myXKCD: Sensational Bullshit</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2015-03-09T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/comics/sensational-bullshit" rel="alternate" type="text/html" title="myXKCD: Sensational Bullshit" />
        <category term="comics" />
        <media:thumbnail url="https://christophercrouzet.com/blog/comics/sensational-bullshit/myxkcd-sensational-bullshit.gif" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;comics&#x2F;sensational-bullshit&#x2F;myxkcd-sensational-bullshit.gif&quot; alt&#x3D;&quot;Sensational bullshit&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:59db8ad4-93ac-4f58-8529-63cfdbd2ef58</id>
      <title>Nested Initializer Lists for Multidimensional Arrays</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2015-01-12T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/nested-initializers" rel="alternate" type="text/html" title="Nested Initializer Lists for Multidimensional Arrays" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/nested-initializers/curly-brace-face.gif" />

      <content type="html">
        &lt;p&gt;With the release of the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;m3ta&quot;&gt;template metaprogramming library m3ta&lt;&#x2F;a&gt;, I’ve included in the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;m3ta.readthedocs.io&#x2F;&quot;&gt;documentation&lt;&#x2F;a&gt; a few examples on how some utilities might prove useful.&lt;&#x2F;p&gt;
&lt;p&gt;I find one example particularly worth sharing here because I’ve been stuck on it for some times and thought that it might be useful to someone else. That’s the nested initializers trick.&lt;&#x2F;p&gt;
&lt;p&gt;But what exactly are those initializer thingies?&lt;&#x2F;p&gt;
&lt;p&gt;Initializer lists are a way to initialise objects using a curly braces syntax &lt;code&gt;{}&lt;&#x2F;code&gt; that encloses a list of values such as &lt;code&gt;{4, 9, 8, 5}&lt;&#x2F;code&gt;. Their nested version allows for more structured syntaxes like &lt;code&gt;{{4, 9}, {8, 5}}&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Since they are well suited to define arrays, let’s see how they could be used in the context of a custom multidimensional array class that would mimic the curly braces initialization properties of the built-in arrays of C&#x2F;C++.&lt;&#x2F;p&gt;
&lt;h2&gt;The Built-In Curly Braces Syntax&lt;&#x2F;h2&gt;
&lt;p&gt;The C &amp;amp; C++ languages have always provided a special curly braces syntax for the built-in arrays that allowed to define all their elements at once.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; Single curly braces syntax.&lt;&#x2F;span&gt;
&lt;span&gt;int&lt;&#x2F;span&gt; array[&lt;span&gt;3&lt;&#x2F;span&gt;][&lt;span&gt;2&lt;&#x2F;span&gt;] &#x3D; {
    &lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;,
    &lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;,
    &lt;span&gt;4&lt;&#x2F;span&gt;, &lt;span&gt;5&lt;&#x2F;span&gt;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On top of initialising the content of the array with the values passed, this syntax also comes with two neat features for the corner cases. Firstly, any element in excess is signalled with an error at compile time, and secondly, the end of the array is filled with zeroes if less values than expected are provided.&lt;&#x2F;p&gt;
&lt;p&gt;The nested flavour of this syntax allows to write as many levels of nested curly braces as there are of dimensions defined by the array.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; Nested curly braces syntax.&lt;&#x2F;span&gt;
&lt;span&gt;&#x2F;&#x2F; 2-dimensional array -&amp;gt; 2 levels of braces&lt;&#x2F;span&gt;
&lt;span&gt;int&lt;&#x2F;span&gt; array[&lt;span&gt;3&lt;&#x2F;span&gt;][&lt;span&gt;2&lt;&#x2F;span&gt;] &#x3D; {
    {&lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;},
    {&lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;},
    {&lt;span&gt;4&lt;&#x2F;span&gt;, &lt;span&gt;5&lt;&#x2F;span&gt;}
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The difference is not only aesthetic. This last version helps localising the behaviour of the “neat features”. Namely, it provides an error message with the &lt;em&gt;precise&lt;&#x2F;em&gt; location of any set of braces defining elements in excess, and likewise fills with zeroes the &lt;em&gt;inner&lt;&#x2F;em&gt; sets of braces that have some values missing.&lt;&#x2F;p&gt;
&lt;p&gt;Otherwise, and if provided with the expected number of values, both syntaxes produce the exact same content.&lt;&#x2F;p&gt;
&lt;h2&gt;Curly Braces Initializers for All!&lt;&#x2F;h2&gt;
&lt;p&gt;Now that the behaviours that needs to be reproduced are known, let’s get onto it—but not so fast! If you are not using C++11 or above, you are doomed.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;nested-initializers&#x2F;curly-brace-face.gif&quot; alt&#x3D;&quot;Curly brace face&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Indeed, until C++11 this convenient syntax wasn’t available for user-defined types. It’s the addition of &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;en.cppreference.com&#x2F;w&#x2F;cpp&#x2F;utility&#x2F;initializer_list&quot;&gt;&lt;code&gt;std::initializer_list&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; that changed the deal.&lt;&#x2F;p&gt;
&lt;p&gt;If you are part of the cool kids, enabling the curly braces initializers for a type is done by simply defining a constructor taking a &lt;code&gt;std::initializer_list&lt;&#x2F;code&gt; argument.&lt;&#x2F;p&gt;
&lt;p&gt;In the case of the single curly braces syntax, the implementation of the constructor can actually be quite concise.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; ... T_dimensions&amp;gt;
&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;MultidimensionalArray&lt;&#x2F;span&gt;
{&lt;&#x2F;span&gt;
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;MultidimensionalArray&lt;&#x2F;span&gt;(std::initializer_list&amp;lt;T&amp;gt; values)
    {
        std::&lt;span&gt;copy&lt;&#x2F;span&gt;(values.&lt;span&gt;begin&lt;&#x2F;span&gt;(), values.&lt;span&gt;end&lt;&#x2F;span&gt;(), _data.&lt;span&gt;begin&lt;&#x2F;span&gt;());
    }
    
&lt;span&gt;private&lt;&#x2F;span&gt;:
    std::array&amp;lt;T, size()&amp;gt; _data;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This implementation wouldn’t take care of the corner cases though—what if the user writes 2 or 4 values when only 3 are expected?&lt;&#x2F;p&gt;
&lt;p&gt;What I previously called the “neat features” that are shipped with the built-in arrays are not a big deal to be implemented here.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; ... T_dimensions&amp;gt;
&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;MultidimensionalArray&lt;&#x2F;span&gt;
{&lt;&#x2F;span&gt;
    &lt;span&gt;&lt;span&gt;static_assert&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;(&lt;span&gt;sizeof&lt;&#x2F;span&gt; ... (T_dimensions) &amp;gt; &lt;span&gt;0&lt;&#x2F;span&gt;,
                  &lt;span&gt;&quot;At least one dimension needs to be defined.&quot;&lt;&#x2F;span&gt;);
    
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;constexpr&lt;&#x2F;span&gt; std::&lt;span&gt;size_t&lt;&#x2F;span&gt;
    &lt;span&gt;size&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;
    &lt;&#x2F;span&gt;{
        &lt;span&gt;return&lt;&#x2F;span&gt; m3ta::&lt;span&gt;product&lt;&#x2F;span&gt;(T_dimensions ...);
    }
    
    &lt;span&gt;MultidimensionalArray&lt;&#x2F;span&gt;(std::initializer_list&amp;lt;T&amp;gt; values)
    {
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;gt; &lt;span&gt;size&lt;&#x2F;span&gt;()) {
            std::ostringstream message;
            message &amp;lt;&amp;lt; &lt;span&gt;&quot;Elements in excess: &quot;&lt;&#x2F;span&gt;
                    &amp;lt;&amp;lt; &lt;span&gt;&quot;expected &quot;&lt;&#x2F;span&gt; &amp;lt;&amp;lt; &lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt;&amp;lt; &lt;span&gt;&quot;, &quot;&lt;&#x2F;span&gt;
                    &amp;lt;&amp;lt; &lt;span&gt;&quot;got &quot;&lt;&#x2F;span&gt; &amp;lt;&amp;lt; values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt;&amp;lt; &lt;span&gt;&quot;.&quot;&lt;&#x2F;span&gt;;
            
            &lt;span&gt;throw&lt;&#x2F;span&gt; std::&lt;span&gt;invalid_argument&lt;&#x2F;span&gt;(message.&lt;span&gt;str&lt;&#x2F;span&gt;());
        }
        
        std::&lt;span&gt;copy&lt;&#x2F;span&gt;(values.&lt;span&gt;begin&lt;&#x2F;span&gt;(), values.&lt;span&gt;end&lt;&#x2F;span&gt;(), _data.&lt;span&gt;begin&lt;&#x2F;span&gt;());
        
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt; &lt;span&gt;size&lt;&#x2F;span&gt;()) {
            std::&lt;span&gt;size_t&lt;&#x2F;span&gt; count &#x3D; std::&lt;span&gt;min&lt;&#x2F;span&gt;(&lt;span&gt;size&lt;&#x2F;span&gt;(), values.&lt;span&gt;size&lt;&#x2F;span&gt;());
            std::&lt;span&gt;uninitialized_fill&lt;&#x2F;span&gt;(
                _data.&lt;span&gt;begin&lt;&#x2F;span&gt;() + count,
                _data.&lt;span&gt;end&lt;&#x2F;span&gt;(),
                &lt;span&gt;static_cast&lt;&#x2F;span&gt;&amp;lt;T&amp;gt;(&lt;span&gt;0&lt;&#x2F;span&gt;)
            );
        }
    }
    
    &lt;span&gt;using&lt;&#x2F;span&gt; Iterator &#x3D; &lt;span&gt;typename&lt;&#x2F;span&gt; std::array&amp;lt;T, &lt;span&gt;size&lt;&#x2F;span&gt;()&amp;gt;::iterator;
    
&lt;span&gt;private&lt;&#x2F;span&gt;:
    std::array&amp;lt;T, size()&amp;gt; _data;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once again, only the single curly braces syntax is defined here, but otherwise the same “neat features” as the built-in arrays are provided.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;MultidimensionalArray&amp;lt;&lt;span&gt;float&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;, &lt;span&gt;2&lt;&#x2F;span&gt;&amp;gt; array &#x3D; {
    &lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;,
    &lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;,
    &lt;span&gt;4&lt;&#x2F;span&gt;, &lt;span&gt;5&lt;&#x2F;span&gt;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Implementing the nested curly braces variant is slightly more involved.&lt;&#x2F;p&gt;
&lt;p&gt;Firstly, a new constructor needs to be defined to accept nested initializer lists, that is for example a &lt;code&gt;std::initializer_list&amp;lt;std::initializer_list&amp;lt;float&amp;gt;&amp;gt;&lt;&#x2F;code&gt; in the case of a 2-dimensional array.&lt;&#x2F;p&gt;
&lt;p&gt;Creating a such type in a generic fashion isn’t a problem, as demonstrated with the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;m3ta&#x2F;blob&#x2F;master&#x2F;src&#x2F;m3ta&#x2F;nestedinitializerlists.h&quot;&gt;&lt;code&gt;m3ta::NestedInitializerLists&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; trait.&lt;&#x2F;p&gt;
&lt;p&gt;But one thing to know is that it isn’t possible to directly iterate over all the values held within nested &lt;code&gt;std::initializer_list&lt;&#x2F;code&gt;s—pointers to the next deeper nested level are returned until the very last level is reached, at which point the values can finally be accessed.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;m3ta::NestedInitializerListsT&amp;lt;&lt;span&gt;float&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;&amp;gt; values &#x3D; {
    {
        {&lt;span&gt;0&lt;&#x2F;span&gt;, &lt;span&gt;1&lt;&#x2F;span&gt;},
        {&lt;span&gt;2&lt;&#x2F;span&gt;, &lt;span&gt;3&lt;&#x2F;span&gt;}
    },
    {
        {&lt;span&gt;4&lt;&#x2F;span&gt;, &lt;span&gt;5&lt;&#x2F;span&gt;},
        {&lt;span&gt;6&lt;&#x2F;span&gt;, &lt;span&gt;7&lt;&#x2F;span&gt;}
    }
};

&lt;span&gt;for&lt;&#x2F;span&gt; (&lt;span&gt;auto&lt;&#x2F;span&gt; secondLevel : values) {
    &lt;span&gt;for&lt;&#x2F;span&gt; (&lt;span&gt;auto&lt;&#x2F;span&gt; thirdLevel : secondLevel) {
        &lt;span&gt;for&lt;&#x2F;span&gt; (&lt;span&gt;auto&lt;&#x2F;span&gt; value : thirdLevel) {
            &lt;span&gt;&#x2F;&#x2F; Do something with the value.&lt;&#x2F;span&gt;
        }
    }
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nesting as many for-loops as there are of levels is not something that can be done generically.&lt;&#x2F;p&gt;
&lt;h2&gt;Iterating over Nested Initializer Lists&lt;&#x2F;h2&gt;
&lt;p&gt;I initially tried to either flatten the nested lists into a linear sequence of elements or to find a trick based on &lt;code&gt;std::array&lt;&#x2F;code&gt; to automatically inherit its properties, but nothing gave me both the exact same syntax as the built-in arrays with the “neat features” that I wanted.&lt;&#x2F;p&gt;
&lt;p&gt;The solution had to pass by a recursive approach.&lt;&#x2F;p&gt;
&lt;p&gt;The values to recurse over are the number of elements per level of the nested initializer lists—that is, its &lt;em&gt;shape&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The idea kinda goes like this: from the first level, check if the number of elements is as expected, recurse over the second level, then fill the missing elements with zeroes, and repeat until the last level is reached. At this point, do something with the values.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; ... T_shape&amp;gt;
&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;NestedInitializerListsProcessor&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;&#x2F; Recursive part.&lt;&#x2F;span&gt;
&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; T_first, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; ... T_others&amp;gt;
&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;NestedInitializerListsProcessor&lt;&#x2F;span&gt;&amp;lt;&lt;&#x2F;span&gt;T, T_first, T_others ...&amp;gt;
{
    &lt;span&gt;using&lt;&#x2F;span&gt; NestedInitializerLists &#x3D;
        m3ta::NestedInitializerListsT&amp;lt;T, &lt;span&gt;1&lt;&#x2F;span&gt; + &lt;span&gt;sizeof&lt;&#x2F;span&gt; ... (T_others)&amp;gt;;
    
    &lt;span&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T_Function&amp;gt;
    &lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;void&lt;&#x2F;span&gt;
    &lt;span&gt;process&lt;&#x2F;span&gt;&lt;span&gt;(NestedInitializerLists values, T_Function function)&lt;&#x2F;span&gt;
    &lt;&#x2F;span&gt;{
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;gt; T_first) {
            &lt;span&gt;throw&lt;&#x2F;span&gt; std::&lt;span&gt;invalid_argument&lt;&#x2F;span&gt;(
                &lt;span&gt;&quot;Elements in excess within the initilizer list.&quot;&lt;&#x2F;span&gt;
            );
        }
        
        &lt;span&gt;for&lt;&#x2F;span&gt; (&lt;span&gt;auto&lt;&#x2F;span&gt; nested : values) {
            NestedInitializerListsProcessor&amp;lt;T, T_others ...&amp;gt;::
                &lt;span&gt;process&lt;&#x2F;span&gt;(nested, function);
        }
        
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt; T_first) {
            std::&lt;span&gt;size_t&lt;&#x2F;span&gt; count &#x3D;
                m3ta::Product&amp;lt;std::&lt;span&gt;size_t&lt;&#x2F;span&gt;, T_others ...&amp;gt;::value
                * (T_first - values.&lt;span&gt;size&lt;&#x2F;span&gt;());
            
            &lt;span&gt;while&lt;&#x2F;span&gt; (count-- &amp;gt; &lt;span&gt;0&lt;&#x2F;span&gt;) {
                &lt;span&gt;function&lt;&#x2F;span&gt;(&lt;span&gt;static_cast&lt;&#x2F;span&gt;&amp;lt;T&amp;gt;(&lt;span&gt;0&lt;&#x2F;span&gt;));
            }
        }
    }
};

&lt;span&gt;&#x2F;&#x2F; Last level.&lt;&#x2F;span&gt;
&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T, std::&lt;span&gt;size_t&lt;&#x2F;span&gt; T_last&amp;gt;
&lt;span&gt;&lt;span&gt;struct&lt;&#x2F;span&gt; &lt;span&gt;NestedInitializerListsProcessor&lt;&#x2F;span&gt;&amp;lt;&lt;&#x2F;span&gt;T, T_last&amp;gt;
{
    &lt;span&gt;using&lt;&#x2F;span&gt; InitializerList &#x3D; m3ta::NestedInitializerListsT&amp;lt;T, &lt;span&gt;1&lt;&#x2F;span&gt;&amp;gt;;
    
    &lt;span&gt;&lt;span&gt;template&lt;&#x2F;span&gt;&amp;lt;&lt;span&gt;typename&lt;&#x2F;span&gt; T_Function&amp;gt;
    &lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;void&lt;&#x2F;span&gt;
    &lt;span&gt;process&lt;&#x2F;span&gt;&lt;span&gt;(InitializerList values, T_Function function)&lt;&#x2F;span&gt;
    &lt;&#x2F;span&gt;{
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;gt; T_last) {
            std::ostringstream message;
            message &amp;lt;&amp;lt; &lt;span&gt;&quot;Elements in excess: &quot;&lt;&#x2F;span&gt;
                    &amp;lt;&amp;lt; &lt;span&gt;&quot;expected &quot;&lt;&#x2F;span&gt; &amp;lt;&amp;lt; T_last &amp;lt;&amp;lt; &lt;span&gt;&quot;, &quot;&lt;&#x2F;span&gt;
                    &amp;lt;&amp;lt; &lt;span&gt;&quot;got &quot;&lt;&#x2F;span&gt; &amp;lt;&amp;lt; values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt;&amp;lt; &lt;span&gt;&quot;.&quot;&lt;&#x2F;span&gt;;
            
            &lt;span&gt;throw&lt;&#x2F;span&gt; std::&lt;span&gt;invalid_argument&lt;&#x2F;span&gt;(message.&lt;span&gt;str&lt;&#x2F;span&gt;());
        }
        
        std::for_each(values.&lt;span&gt;begin&lt;&#x2F;span&gt;(), values.&lt;span&gt;end&lt;&#x2F;span&gt;(), function);
        
        &lt;span&gt;if&lt;&#x2F;span&gt; (values.&lt;span&gt;size&lt;&#x2F;span&gt;() &amp;lt; T_last) {
            std::&lt;span&gt;size_t&lt;&#x2F;span&gt; count &#x3D; T_last - values.&lt;span&gt;size&lt;&#x2F;span&gt;();
            &lt;span&gt;while&lt;&#x2F;span&gt; (count-- &amp;gt; &lt;span&gt;0&lt;&#x2F;span&gt;) {
                &lt;span&gt;function&lt;&#x2F;span&gt;(&lt;span&gt;static_cast&lt;&#x2F;span&gt;&amp;lt;T&amp;gt;(&lt;span&gt;0&lt;&#x2F;span&gt;));
            }
        }
    }
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For more genericity, this &lt;code&gt;NestedInitializerListsProcessor&lt;&#x2F;code&gt; helper takes a function that it applies on each element iterated over. This way, the caller—the &lt;code&gt;MultidimensionalArray&lt;&#x2F;code&gt; constructors here—can decide what to do with those values.&lt;&#x2F;p&gt;
&lt;p&gt;The first template specialisation is the recursive part while the one with the &lt;code&gt;T_last&lt;&#x2F;code&gt; parameter is the equivalent of what was previously implemented within the &lt;code&gt;MultidimensionalArray&lt;&#x2F;code&gt; constructor for the single curly braces case.&lt;&#x2F;p&gt;
&lt;h2&gt;Putting All the Things into the Multidimensional Array Class&lt;&#x2F;h2&gt;
&lt;p&gt;Now that accessing the values of nested initializer lists is dealt with, the &lt;code&gt;MultidimensionalArray&lt;&#x2F;code&gt; class can define the constructor for the nested curly braces.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;template&amp;lt;typename T, std::size_t ... T_dimensions&amp;gt;
class MultidimensionalArray
{
    static_assert(sizeof ... (T_dimensions) &amp;gt; 0,
                  &quot;At least one dimension needs to be defined.&quot;);
    
public:
    static constexpr std::size_t
    size()
    {
        return m3ta::product(T_dimensions ...);
    }
    
    &#x2F;&#x2F; Single curly braces syntax.
    MultidimensionalArray(m3ta::NestedInitializerListsT&amp;lt;T, 1&amp;gt; values)
    {
        initialize&amp;lt;size()&amp;gt;(values);
    }
    
    using NestedInitializerLists &#x3D;
        m3ta::NestedInitializerListsT&amp;lt;T, sizeof ... (T_dimensions)&amp;gt;;
    
    &#x2F;&#x2F; Nested curly braces syntax.
    MultidimensionalArray(NestedInitializerLists values)
    {
        initialize&amp;lt;T_dimensions ...&amp;gt;(values);
    }
    
private:
    template&amp;lt;std::size_t ... T_shape, typename T_NestedInitializerLists&amp;gt;
    void
    initialize(T_NestedInitializerLists values)
    {
        auto iterator &#x3D; _data.begin();
        NestedInitializerListsProcessor&amp;lt;T, T_shape ...&amp;gt;::
            process(
                values,
                [&amp;amp;iterator](T value) { *(iterator++) &#x3D; value; }
            );
    }
    
    std::array&amp;lt;T, size()&amp;gt; _data;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The private method &lt;code&gt;initialize()&lt;&#x2F;code&gt; defines a new template parameter &lt;code&gt;T_shape&lt;&#x2F;code&gt; which refers to the shape to expect from the initializer lists to iterate over—that is the number of values in the case of the single curly braces syntax, and the dimensions of the array for the nested syntax.&lt;&#x2F;p&gt;
&lt;p&gt;It then defines a lambda function which simply copies each value onto the &lt;code&gt;_data&lt;&#x2F;code&gt; member.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;MultidimensionalArray&lt;&#x2F;code&gt; class is now almost good to go—the nested curly braces constructor needs to be disabled when the array contains a single dimension to avoid ambiguous calls, and a pair of &lt;code&gt;begin()&#x2F;end()&lt;&#x2F;code&gt; methods need to be added to at least allow a sort of iteration. I’ll leave these as an exercise for the reader—if there is any reader left at this point? Or you can simply get the full code on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;christophercrouzet&#x2F;b840aed577071e66b50d&quot;&gt;this Gist&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;Additional Notes&lt;&#x2F;h2&gt;
&lt;p&gt;Admittedly the error messages could be improved but will hardly get nearly as good as what the compilers can generate for the built-in arrays.&lt;&#x2F;p&gt;
&lt;p&gt;Also, the conditions for these error messages are checked at run-time when, according to &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;27496004&#x2F;why-isnt-stdinitializer-list-defined-as-a-literal-type&quot;&gt;this question of mine on StackOverflow&lt;&#x2F;a&gt;, it should have been possible to retrieve the size of a &lt;code&gt;std::initializer_list&lt;&#x2F;code&gt; at compile time and turn the whole check + exception combo into a simple &lt;code&gt;static_assert&lt;&#x2F;code&gt;. Maybe an oversight from the C++ committee?&lt;&#x2F;p&gt;
&lt;p&gt;Don’t be shy and &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&quot;&gt;follow me on GitHub&lt;&#x2F;a&gt; if you want to check out the latest code that I’m sharing—it won’t always make it into a blog post.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:3222dd5a-a42c-11e7-b4a4-6bacef8dc214</id>
      <title>m3ta: A Template Metaprogramming Library</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2015-01-09T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/m3ta" rel="alternate" type="text/html" title="m3ta: A Template Metaprogramming Library" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/assets/images/thumbnail.png" />

      <content type="html">
        &lt;p&gt;For the needs of a personal C++ project heavily relying on templates, I found myself coding more and more &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Template_metaprogramming&quot;&gt;template metaprogramming&lt;&#x2F;a&gt; utilities to the point where I wrote more lines of these than of the actual project itself. The time was for a refactoring—these utilities deserved their own dedicated library.&lt;&#x2F;p&gt;
&lt;p&gt;I thought that the process would take me only a couple of days—how &lt;em&gt;naive&lt;&#x2F;em&gt; of me. Extracting some utilities that work well within a specific project and turning them into something more bulletproof with the help of an extensive set of unit tests is... quite some work. Not even talking of the documentation entirely written manually in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;ReStructuredText&quot;&gt;reStructuredText&lt;&#x2F;a&gt;, without much help from &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;sphinx-doc.org&#x2F;&quot;&gt;Sphinx&lt;&#x2F;a&gt; on the C++ front.&lt;&#x2F;p&gt;
&lt;p&gt;Nonetheless, it’s now all good to go and I am pleased to introduce you &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;m3ta&quot;&gt;m3ta&lt;&#x2F;a&gt;!&lt;&#x2F;p&gt;
&lt;p&gt;I’ve made the source code available on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;m3ta&quot;&gt;GitHub&lt;&#x2F;a&gt;. Note that this library relies on features from C++11.&lt;&#x2F;p&gt;
&lt;p&gt;What Sphinx helped me for though is to make the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;m3ta.readthedocs.io&quot;&gt;documentation&lt;&#x2F;a&gt; compatible with the beautifully themed &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;readthedocs.org&quot;&gt;readthedocs.org&lt;&#x2F;a&gt; website.&lt;&#x2F;p&gt;
&lt;h2&gt;Curious About Metaprogramming?&lt;&#x2F;h2&gt;
&lt;p&gt;If you’re new to template metaprogramming but not to C++, I encourage you to check the examples from the documentation, the source code, or even better, to research the subject on the Internet like I did.&lt;&#x2F;p&gt;
&lt;p&gt;Not only could it potentially expand your way of thinking and your overall programming skills (template metaprogramming can give a small preview of what &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Functional_programming&quot;&gt;functional programming&lt;&#x2F;a&gt; is), but it would also open the doors to a &lt;em&gt;whole&lt;&#x2F;em&gt; new set of tools for solving some problems more efficiently.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:da3a5fca-a65e-11e7-b12c-9f4fd09b0f72</id>
      <title>myXKCD: Christmas</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-12-23T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/comics/christmas" rel="alternate" type="text/html" title="myXKCD: Christmas" />
        <category term="comics" />
        <media:thumbnail url="https://christophercrouzet.com/blog/comics/christmas/myxkcd-christmas.gif" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;comics&#x2F;christmas&#x2F;myxkcd-christmas.gif&quot; alt&#x3D;&quot;Christmas&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:d4af3488-a43b-11e7-96ad-873bbde793d1</id>
      <title>Flying Things: A Rigging Showreel</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-09-09T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/flying-things" rel="alternate" type="text/html" title="Flying Things: A Rigging Showreel" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/portfolio/rigging-showreel-2014.jpg" />

      <content type="html">
        &lt;p&gt;Here’s a proof that I’ve not been doing nothing during my last 4 years of work.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;105620642&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;portfolio&#x2F;rigging-showreel-2014.jpg&quot; alt&#x3D;&quot;Rigging showreel (2014)&quot;&gt;&lt;span&gt;&lt;&#x2F;span&gt;&lt;span&gt;Watch on Vimeo&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;character rigging showreel from 2014&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Needless to say that I’ve done the rigs of the canisters and of the bouncing ball in full. For the rest you can check the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;portfolio&#x2F;christopher-crouzet-rigging-showreel-2014-breakdown.pdf&quot;&gt;shot breakdown&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:ca9b125e-a45a-11e7-96e8-f7229cc72ef8</id>
      <title>Modern Rigging Workflow: The Modular Approach</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-08-20T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/modern-rigging-workflow" rel="alternate" type="text/html" title="Modern Rigging Workflow: The Modular Approach" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/modern-rigging-workflow/the-general-problem.png" />

      <content type="html">
        &lt;p&gt;Providing solid (character) rigs that fit animators’ requirements is only a good start for a rigger aspiring to become proficient. Efficiency is the other equally important aspect so let’s discuss some leads to get there.&lt;&#x2F;p&gt;
&lt;p&gt;As a beginner I struggled to create a big picture of what could be a good rigging workflow. My only focus back then was on making my rigs robust and friendly for the animators. As soon as my object hierarchies and logic made sense, then it all sounded good enough to me.&lt;&#x2F;p&gt;
&lt;p&gt;The more I had to make some changes to the rigs I had built, the more I started to get a hint that something was wrong with my approach.&lt;&#x2F;p&gt;
&lt;p&gt;Since I would build them manually, it wasn’t straightforward to operate complex modifications on the structure. Clearly, numerous mistakes could make their way through during an update or another. I couldn’t even guarantee that the left arm of a character was the exact same as its right arm. As for propagating the changes to multiple characters, it would probably have been &lt;em&gt;really&lt;&#x2F;em&gt; challenging.&lt;&#x2F;p&gt;
&lt;h2&gt;Dismembering the Rigs into Modules&lt;&#x2F;h2&gt;
&lt;p&gt;Looking back at it now, the object hierarchy of my first rigs were quite a chaotic place—an arm here, a leg there, both most likely directly parented under the spine, while the controllers were mixed with rigging objects, deformers, previs geometries, and so on. The only neat part was the final geometry which was in its own hierarchy...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;step-1.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;From there, the first move was to encapsulate each logical part of a rig (spine, arm, leg, ...) into its own object hierarchy, without any external element sneaking in, and to make sure that each could work in a standalone fashion. Let’s call those logical parts &lt;strong&gt;rig modules&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Back at Action Synthèse, we implemented this approach by saving separately a scene containing the rig for each module. A module could then be imported, fit onto a specific character, and connected to the other modules.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;step-2.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The fitting part was a bit sketchy—adapting a &lt;em&gt;static&lt;&#x2F;em&gt; rig existing in a scene onto different characters might require to break some constraints, expressions, and hierarchies, and to recreate them only when the sizes and posing were adjusted. We added a layer of script to help with that but this whole approach is an added complexity which is highly error prone and makes it easy to break everything apart when making changes to the rig.&lt;&#x2F;p&gt;
&lt;h2&gt;Bring on the Structure&lt;&#x2F;h2&gt;
&lt;p&gt;What a rig really is, is just the internals made of pistons and cogs that connect the interface given to the animators with the final deformed geometry given to the lighting department. It is only a part of a bigger picture from which we need to differentiate multiple assembly stages.&lt;&#x2F;p&gt;
&lt;p&gt;Now there’s not just one structure possible but many of them, interchangeable depending on the needs of each studio. A common and simple variation would include those units: rig, armature, and geometry.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;step-3.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The dependencies here works only in one direction—the rig drives the armature, which in turn drives the geometry.&lt;&#x2F;p&gt;
&lt;p&gt;The armature is made of anything required to drive the deformations of the final geometry contained in the last unit. It acts like a buffer layer that usually includes a representation of the skeleton as well as any animated attribute that might end up driving shapes and other deformations.&lt;&#x2F;p&gt;
&lt;p&gt;However this structural approach is being implemented, this concept is a central piece to an efficient rigging workflow. It lays out a solid global structure for the rigs and opens the doors to much flexibility. The other building blocks just flow naturally from there.&lt;&#x2F;p&gt;
&lt;h2&gt;Ideas to Make a Good Use of the Structure&lt;&#x2F;h2&gt;
&lt;p&gt;A direct benefit of splitting the structure this way is that the final geometry now only requires to be driven by the armature unit. If the animation on the armature gets baked, it is then possible to provide the lighters with a lightweight asset, stripped out from the rig unit, that they can render.&lt;&#x2F;p&gt;
&lt;p&gt;In the cases where complex muscle or cloth simulations are involved, baking the final geometry for the lighters is the only viable option. But a simulation unit could well be wrapped with the armature before being sent for computation on a render farm for example. No need for the full rig here neither.&lt;&#x2F;p&gt;
&lt;p&gt;If the geometry unit prevents the animators from having a real-time feedback, an idea could be to add a new previs unit containing only “puppet” geometries to speed it all up. It could either replace entirely the geometry unit or work alongside with it by providing a visibility toggle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;step-4.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Is the rig part of a motion capture pipeline? Insert a new motion capture unit that drives the controllers.&lt;&#x2F;p&gt;
&lt;h2&gt;Interface and Implementation&lt;&#x2F;h2&gt;
&lt;p&gt;To really emphasize the idea of a rig being a glue between the interface for the animators and the final deformed geometry, it is possible—and recommended—to split the animation controllers from the rigging logic. The programming equivalent is to separate the data from the implementation—a core design principle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;step-5.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To perform this separation properly, it becomes important to fully grasp the logic of each rig module—what they require as input, how they process that data, and what they output.&lt;&#x2F;p&gt;
&lt;p&gt;The first advantage is that it’s easier to maintain both the interfaces and the rigs, especially when picking them up after a while or when someone else gets onto it. Indeed, like with programming, there’s more chances to understand small chunks of code, each encapsulated into routines getting a specific task done, rather than gigantic monolithic code snippets. Check out for example the idea behind the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Separation_of_concerns&quot;&gt;separations of concerns&lt;&#x2F;a&gt; design principle if you’re not convinced.&lt;&#x2F;p&gt;
&lt;p&gt;Even more importantly, it promotes reusability by allowing to swap either a rig and&#x2F;or an interface independently from the other—a same spine rig module can for example be used for a neck or a small tail, and thus by simply providing different interfaces adapted to each scenario. This could as well become convenient for when a specific shot would require to manipulate the rig in a non-conventional way.&lt;&#x2F;p&gt;
&lt;p&gt;This separation of interface and implementation doesn’t mean that any rigging system should be prohibited from within the interface. In the contrary, it’s sometimes required to build some small mechanisms to enhance the behavior of the controllers.&lt;&#x2F;p&gt;
&lt;h2&gt;Adding Some More Granularity&lt;&#x2F;h2&gt;
&lt;p&gt;To build more upon this separation of concerns, it’s possible to break each rig module in even smaller logical, self-contained parts. Let’s call them &lt;strong&gt;rig chunks&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Those allow creating some small structures that can be reused or swapped to change the behavior of a rig.&lt;&#x2F;p&gt;
&lt;p&gt;As with the rig modules forming the global structure, the chunks defining a rig module need to flow in a simple and predictable fashion.&lt;&#x2F;p&gt;
&lt;p&gt;Figuring out the right level of granularity is never easy. Starting off by turning every couple of nodes into a rig chunk under the pretext that it could possibly be reused somewhere else is definitely not the way to go and will only end up in a total &lt;em&gt;clusterfuck&lt;&#x2F;em&gt; on top of being a waste of time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;modern-rigging-workflow&#x2F;the-general-problem.png&quot; alt&#x3D;&quot;The general problem&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;credit: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;xkcd.com&#x2F;974&quot;&gt;xkcd&lt;&#x2F;a&gt; (&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-nc&#x2F;2.5&quot;&gt;CC BY-NC 2.5&lt;&#x2F;a&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Instead, a solid argument to turn a piece of rig into a reusable chunk would be to build everything as usual until a same pattern shows up again—maybe in a different rig—, in which case a refactorisation is beneficial.&lt;&#x2F;p&gt;
&lt;p&gt;As for the other good argument, it’s simple—if adding&#x2F;removing a part from a rig allows to add&#x2F;remove features such as the stretch system for a spine, then it is more likely to be a good contender for a &lt;em&gt;chunkisation&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;The Point So Far&lt;&#x2F;h2&gt;
&lt;p&gt;Remember how I said that the implementation of the rig modules was sketchy and complex because of the way we did it? As nice as those rig chunks might look on the paper, they won’t fare any better if implemented in the same way.&lt;&#x2F;p&gt;
&lt;p&gt;Obviously, relying on static pieces of rig to implement those dynamic concepts is quite limitative here. The fundamentals needs to be revised before being able to encompass all the current and possible future requirements.&lt;&#x2F;p&gt;
&lt;h2&gt;Following the Flow, Coding It All&lt;&#x2F;h2&gt;
&lt;p&gt;Each time that a concept is being isolated into a unit structure, a rig module, or a rig chunk, it really is the equivalent of creating a new function.&lt;&#x2F;p&gt;
&lt;p&gt;As such, the next step is to &lt;em&gt;entirely&lt;&#x2F;em&gt; generate the rigs through scripts. Does it freak you out? Then forget about working at &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.wetafx.co.nz&quot;&gt;Weta Digital&lt;&#x2F;a&gt; as a rigger. Just kidding, keep reading.&lt;&#x2F;p&gt;
&lt;p&gt;Everyone there, ranging from junior to senior positions, picked it up easily enough. It is really intuitive and fast to use once all there is left to do is to type a few lines to import, configure and connect the rig modules together.&lt;&#x2F;p&gt;
&lt;p&gt;It is more involved when it comes to the lower level of creating the actual logic of each rig module and chunks, but it is for the good cause. This enforces a different approach to rigging—the encapsulation of each concept tends to be even more well-defined and modular, leading to positive restructurations of the rig elements and to a cleaner code.&lt;&#x2F;p&gt;
&lt;p&gt;Over time, it reaches a point where it becomes much more straightforward to read and understand the linear sequence of instructions from a well written code than a complex dependency graph with a ton of connections to follow, which is a specialty of Maya and the likes.&lt;&#x2F;p&gt;
&lt;p&gt;That’s only the icing on the cake but the real deal is all about being finally able to dynamically build any rig on the fly without any hack as well as being freely able to define the granularity as wished. If the code is well layed out and modular enough, this allows to create whole new variations of a rig by simply overriding a few functions.&lt;&#x2F;p&gt;
&lt;p&gt;Even better is the chance it gives to provide a flexible rig builder to the riggers. No more predefined object names, orientation orders, controllers shapes, etc. Everything becomes fully customizable to the point of also being able to define different behaviors for a same rig—should this spine be stretchable or not? Should there be 8 or 10 joints?&lt;&#x2F;p&gt;
&lt;p&gt;Note that this won’t stop anyone from creating &lt;em&gt;utterly crap&lt;&#x2F;em&gt; rigs and code if the good practices are not followed.&lt;&#x2F;p&gt;
&lt;h2&gt;The Real Deal&lt;&#x2F;h2&gt;
&lt;p&gt;The time for trials and errors is now over. In my opinion everything converge towards coding somehow most of the rigging workflow, but the truth being that it’s really an unnecessary pain to do this for softwares such as Maya.&lt;&#x2F;p&gt;
&lt;p&gt;Think about it. What’s happening there is that we’re creating an intermediary and unnecessary stage where we need to translate the rig logic (mathematical operations) into rig nodes specific to a 3D software.&lt;&#x2F;p&gt;
&lt;p&gt;This can quickly become a daunting task to have to create a network of interconnected nodes only to perform a few vector&#x2F;matrice operations, especially since the code required for creating and connecting each node can be... &lt;em&gt;substantial&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;On top of that, this can cause a great performance hit. Often, transformation nodes solely exist in rigging to hold onto rotation or translation values to then drive a constraint. Such nodes still require to have their full transformation and properties to be defined and computed, and contribute to an useless clustering of the dependency graph. That’s a lot of waste of memory and processing time.&lt;&#x2F;p&gt;
&lt;p&gt;So why coding a bunch of rig nodes to process the data, when it is possible to create a much more suited piece of code that would directly process that same data optimally? Well, because we couldn’t do that easily until now—maybe with the exception of &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.sidefx.com&#x2F;docs&#x2F;houdini&#x2F;vex&quot;&gt;VEX from Houdini&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;What’s Next?&lt;&#x2F;h2&gt;
&lt;p&gt;The future is all about using the right frameworks to implement our rigging logic the way we choose to. But that wouldn’t be a good reason enough to do the switch without bringing more speed to the rigs and a good portability for the long-term run.&lt;&#x2F;p&gt;
&lt;p&gt;That’s where frameworks such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;fabricengine.com&quot;&gt;Fabric Engine&lt;&#x2F;a&gt; kick in. With their DCC-agnostic ultra-optimized dependency graph and KL language, all the tools that we need and even more are provided.&lt;&#x2F;p&gt;
&lt;p&gt;Seeing the modularity of the workflow described in this article, I strongly believe that it’s shouting to be implemented as a nodal graph with the right level of granularity.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to stay cutting edge on the rigging front, you know in which direction to head to.&lt;&#x2F;p&gt;
&lt;h2&gt;Credits&lt;&#x2F;h2&gt;
&lt;p&gt;Thanks to Frédéric Bonometti, Raffaele Fragapane and Andrea Interguglielmi, for all having influenced me at some point my approach to rigging.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:32ffa85e-a4b6-11e7-bfcf-67457cd38836</id>
      <title>Getting Started with OpenGL in C++</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-08-04T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/getting-started-with-opengl" rel="alternate" type="text/html" title="Getting Started with OpenGL in C++" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/getting-started-with-opengl/opengl-triangle.jpg" />

      <content type="html">
        &lt;p&gt;Learning OpenGL has been on my to-do list for as long as I can remember, but before getting too crazy on it, I wanted to start slowly by focusing on a simple project.&lt;&#x2F;p&gt;
&lt;p&gt;I hence began by laying out the structure of the project. I wanted to have the core to deal only with OpenGL, while having hooks with GUI toolkits—such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.qt.io&quot;&gt;Qt&lt;&#x2F;a&gt; and &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.glfw.org&quot;&gt;GLFW&lt;&#x2F;a&gt;—being built on top of it in separate modules.&lt;&#x2F;p&gt;
&lt;p&gt;For testing purposes, I figured that the core OpenGL library could be as simple as a single class containing the required methods to setup and cleanup the OpenGL context, with a &lt;code&gt;draw()&lt;&#x2F;code&gt; method for the main rendering loop.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; core&#x2F;openglrenderer.h&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;GLXW&#x2F;glxw.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;
{&lt;&#x2F;span&gt;
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;();
    
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;setup&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;draw&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;cleanup&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    
&lt;span&gt;private&lt;&#x2F;span&gt;:
    &lt;span&gt;bool&lt;&#x2F;span&gt; _setup;
    GLuint _program;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;rikusalminen&#x2F;glxw&quot;&gt;GLXW&lt;&#x2F;a&gt; is one of the many OpenGL loader available. There is no special reason why I picked this one over another one, they are all similar enough.&lt;&#x2F;p&gt;
&lt;h2&gt;The Qt 5 Struggle&lt;&#x2F;h2&gt;
&lt;p&gt;With this class prototype in place and a simple call to &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;docs.gl&#x2F;gl4&#x2F;glClearBuffer&quot;&gt;&lt;code&gt;glClearBufferfv()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; within the &lt;code&gt;draw()&lt;&#x2F;code&gt; method, I was ready to give a go at hooking up Qt 5.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; qt5&#x2F;openglsurface.h&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;QtGui&#x2F;QWindow&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;QOpenGLContext&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;OpenGLSurface&lt;&#x2F;span&gt; :&lt;&#x2F;span&gt;
    &lt;span&gt;public&lt;&#x2F;span&gt; QWindow
{
    Q_OBJECT
    
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;OpenGLSurface&lt;&#x2F;span&gt;(QScreen *screen &#x3D; &lt;span&gt;0&lt;&#x2F;span&gt;);
    &lt;span&gt;OpenGLSurface&lt;&#x2F;span&gt;(QWindow *parent);
    ~&lt;span&gt;OpenGLSurface&lt;&#x2F;span&gt;();
    
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;render&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    
&lt;span&gt;private&lt;&#x2F;span&gt;:
    QOpenGLContext *_context;
    OpenGLRenderer *_renderer;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a simplification of the actual class but that’s basically the idea behind it. The &lt;code&gt;render()&lt;&#x2F;code&gt; method gets triggered by the event system of Qt and its role is to ensure that an OpenGL context exists before calling the appropriate methods from the &lt;code&gt;OpenGLRenderer&lt;&#x2F;code&gt; class.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve implemented the full class by thoroughly following an &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;doc.qt.io&#x2F;qt-5&#x2F;qtgui-openglwindow-example.html&quot;&gt;official example from Qt&lt;&#x2F;a&gt; but it decided not to compile.&lt;&#x2F;p&gt;
&lt;p&gt;That’s not due to the tutorial itself but more with the way of how Qt deals with OpenGL.&lt;&#x2F;p&gt;
&lt;p&gt;The C++ source file implementing the &lt;code&gt;OpenGLSurface&lt;&#x2F;code&gt;’s Qt class includes the &lt;code&gt;&amp;lt;core&#x2F;openglrenderer.h&amp;gt;&lt;&#x2F;code&gt; header, which implicity also includes the GLXW header—that’s when Qt starts screaming.&lt;&#x2F;p&gt;
&lt;p&gt;Without going into the details, the &lt;code&gt;QtOpenGL&lt;&#x2F;code&gt; module (from Qt 5.2) doesn’t like mixing with external OpenGL loading libraries such as GLXW, GLEW and others.&lt;&#x2F;p&gt;
&lt;p&gt;I came to realize anyways that exposing the &lt;code&gt;&amp;lt;GLXW&#x2F;glxw.h&amp;gt;&lt;&#x2F;code&gt; file into a public header from the OpenGL core I was writing meant that any project linking to that library also had an extra compile-time dependency towards GLXW to take care of. That’s not only annoying but it also isn’t required—that dependency should be kept internal to the core library.&lt;&#x2F;p&gt;
&lt;h2&gt;Pimpling It Up&lt;&#x2F;h2&gt;
&lt;p&gt;The idea was to break this extra dependency by making any reference to GLXW and OpenGL invisible to the public. A &lt;em&gt;true&lt;&#x2F;em&gt; abstraction.&lt;&#x2F;p&gt;
&lt;p&gt;The easy solutions is to replace any reference to OpenGL typdefs such as &lt;code&gt;GLuint&lt;&#x2F;code&gt; for their actual standard types. But even though it would be fine in most cases, it could potentially break the portability of OpenGL and is &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.opengl.org&#x2F;discussion_boards&#x2F;showthread.php&#x2F;180611-The-opengl-typedefs&quot;&gt;largely&lt;&#x2F;a&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;4237040&#x2F;what-are-the-purpose-of-api-specific-typedefs-such-as-glsizei-glint-glvoid&quot;&gt;not recommended&lt;&#x2F;a&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;stackoverflow.com&#x2F;questions&#x2F;7758285&#x2F;what-is-the-impact-of-ignoring-opengl-typedefs&quot;&gt;by the Internet&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That’s where the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;en.wikibooks.org&#x2F;wiki&#x2F;C%2B%2B_Programming&#x2F;Idioms#Pointer_To_Implementation_.28pImpl.29&quot;&gt;pimpl idiom&lt;&#x2F;a&gt;—also called opaque or d pointer, and widely used in Qt—comes to the rescue.&lt;&#x2F;p&gt;
&lt;p&gt;The public interface of the &lt;code&gt;OpenGLRenderer&lt;&#x2F;code&gt; was already good to go as it was, but the &lt;code&gt;GLuint _program&lt;&#x2F;code&gt; private member was at fault—as well as the others to come.&lt;&#x2F;p&gt;
&lt;p&gt;The pimpl idiom is all about replacing the current private members of a class by pointing to a new class where the members have been moved to.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; core&#x2F;openglrenderer.h&lt;&#x2F;span&gt;

&lt;span&gt;&#x2F;&#x2F;#include &quot;openglrenderer.h&quot;  &amp;lt; Not required anymore!&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;
{&lt;&#x2F;span&gt;
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;();
    
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;setup&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;draw&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    &lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;cleanup&lt;&#x2F;span&gt;&lt;span&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    
&lt;span&gt;private&lt;&#x2F;span&gt;:
    &lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;Impl&lt;&#x2F;span&gt;;&lt;&#x2F;span&gt;
    Impl *_d;
};


&lt;span&gt;&#x2F;&#x2F; core&#x2F;openglrenderer_impl.h&lt;&#x2F;span&gt;

&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&amp;lt;GLXW&#x2F;glxw.h&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span&gt;#&lt;span&gt;include&lt;&#x2F;span&gt; &lt;span&gt;&quot;openglrenderer.h&quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;

&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;OpenGLRenderer&lt;&#x2F;span&gt;:&lt;&#x2F;span&gt;:Impl
{
&lt;span&gt;public&lt;&#x2F;span&gt;:
    &lt;span&gt;bool&lt;&#x2F;span&gt; setup;
    GLuint_program;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On top of removing dependencies, the pimpl idiom also provides the convenience of having a cleaner public API, faster compilation times, and helps preserving binary compatibility.&lt;&#x2F;p&gt;
&lt;p&gt;A more advanced definition and usage is available in 2 parts on Sutter’s Mill website: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;herbsutter.com&#x2F;gotw&#x2F;_100&quot;&gt;Compilation Firewalls&lt;&#x2F;a&gt;, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;herbsutter.com&#x2F;gotw&#x2F;_101&quot;&gt;part 2&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;GLSL Shaders&lt;&#x2F;h2&gt;
&lt;p&gt;Happy with the implementation of my OpenGL core with Qt 5 and even GLFW, I coud move onto going through the great &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.openglsuperbible.com&quot;&gt;OpenGL SuperBible&lt;&#x2F;a&gt; to learn some basics.&lt;&#x2F;p&gt;
&lt;p&gt;After writing a couple of GLSL shaders the way they show it in the first chapters, I already couldn’t take it anymore.&lt;&#x2F;p&gt;
&lt;p&gt;How could I possibly go through the entire book by wrapping &lt;em&gt;each&lt;&#x2F;em&gt; line of the shaders within double quotes while making sure they all end with a newline &lt;code&gt;\n&lt;&#x2F;code&gt; character?&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;static&lt;&#x2F;span&gt; &lt;span&gt;const&lt;&#x2F;span&gt; GLchar *vertexShaderSource[] &#x3D; {
    &lt;span&gt;&quot;#version 430 core\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;void main(void)\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;{\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;    const vec4 vertices[3] &#x3D; vec4[3](\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;        vec4( 0.25, -0.25, 0.5, 1.0),\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;        vec4(-0.25, -0.25, 0.5, 1.0),\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;        vec4( 0.25,  0.25, 0.5, 1.0)\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;    );\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;    \n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;    gl_Position &#x3D; vertices[gl_VertexID];\n&quot;&lt;&#x2F;span&gt;
    &lt;span&gt;&quot;}\n&quot;&lt;&#x2F;span&gt;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Frankly I couldn’t imagine the pros writing their shaders like this either. I had to find a solution, one involving having each of my GLSL shaders in a separate file and loading them somehow in OpenGL.&lt;&#x2F;p&gt;
&lt;h2&gt;The Dynamic Approach&lt;&#x2F;h2&gt;
&lt;p&gt;The only OpenGL function available to set the source code of a shader is &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;docs.gl&#x2F;gl4&#x2F;glShaderSource&quot;&gt;&lt;code&gt;glShaderSource()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; and it expects the source to be given as a &lt;code&gt;const GLchar **&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The obvious first move was to open a shader file and convert its content into a character buffer at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;It’s an easy task and the web crawls with examples to do just that.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;void&lt;&#x2F;span&gt; &lt;span&gt;setShaderSourceFromFile&lt;&#x2F;span&gt;&lt;span&gt;(GLuint shader, &lt;span&gt;const&lt;&#x2F;span&gt; GLchar *filename)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;{
    &lt;span&gt;std::ifstream &lt;span&gt;file&lt;&#x2F;span&gt;&lt;span&gt;(filename)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;;
    &lt;span&gt;if&lt;&#x2F;span&gt; (!file.&lt;span&gt;is_open&lt;&#x2F;span&gt;()) {
        std::cerr &amp;lt;&amp;lt; &lt;span&gt;&quot;Unable to open file &quot;&lt;&#x2F;span&gt; &amp;lt;&amp;lt; filename &amp;lt;&amp;lt; std::endl;
        &lt;span&gt;return&lt;&#x2F;span&gt;;
    }
    
    std::stringstream stream;
    stream &amp;lt;&amp;lt; file.&lt;span&gt;rdbuf&lt;&#x2F;span&gt;();
    file.&lt;span&gt;close&lt;&#x2F;span&gt;();
    
    std::string string &#x3D; stream.&lt;span&gt;str&lt;&#x2F;span&gt;();
    &lt;span&gt;const&lt;&#x2F;span&gt; &lt;span&gt;char&lt;&#x2F;span&gt; *code &#x3D; string.&lt;span&gt;c_str&lt;&#x2F;span&gt;();
    &lt;span&gt;glShaderSource&lt;&#x2F;span&gt;(shader, &lt;span&gt;1&lt;&#x2F;span&gt;, (GLchar **)&amp;amp;code, &lt;span&gt;0&lt;&#x2F;span&gt;);
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One thing to keep in mind is that if a relative filename is passed to the &lt;code&gt;std::ifstream()&lt;&#x2F;code&gt; constructor—which will most likely be the case—and that the application is started from a shell, then this can lead to problems.&lt;&#x2F;p&gt;
&lt;p&gt;Paths are defined relatively to the shell’s current directory rather than to the location of the binary, so it’s important that the shaders can be found from where the application is being started.&lt;&#x2F;p&gt;
&lt;p&gt;That’s probably one of the reasons of why there’s so many launchers out there that initialize a working environment before firing the actual application—finding resources at runtime.&lt;&#x2F;p&gt;
&lt;h2&gt;The Static Approach&lt;&#x2F;h2&gt;
&lt;p&gt;How about using an &lt;code&gt;#include&lt;&#x2F;code&gt; statement to make the shader available into the C++ code at compile-time?&lt;&#x2F;p&gt;
&lt;p&gt;The idea here is to automagically convert the content of each shader file into a multiline string literal, like the ones showcased in the OpenGL SuperBible.&lt;&#x2F;p&gt;
&lt;p&gt;That’s a parsing job simple enough for CMake and it can be wrapped in a custom target.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;add_custom_target&lt;&#x2F;span&gt;(
    shaders
    &lt;span&gt;COMMAND&lt;&#x2F;span&gt; &lt;span&gt;${CMAKE_COMMAND}&lt;&#x2F;span&gt;
        -DSOURCE_DIR&#x3D;&lt;span&gt;${CMAKE_CURRENT_SOURCE_DIR}&lt;&#x2F;span&gt;
        -DDESTINATION_DIR&#x3D;&#x2F;path&#x2F;to&#x2F;formatted&#x2F;shaders
        -DFILES&#x3D;&lt;span&gt;&quot;${shaders_files}&quot;&lt;&#x2F;span&gt;
        -P &#x2F;path&#x2F;to&#x2F;shaders.cmake
    COMMENT &lt;span&gt;&quot;Creating the files to include the GLSL shaders&quot;&lt;&#x2F;span&gt;
)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The command defined in &lt;code&gt;shaders.cmake&lt;&#x2F;code&gt; is where the main logic happens:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;separate_arguments&lt;&#x2F;span&gt;(FILES)

&lt;span&gt;foreach&lt;&#x2F;span&gt; (&lt;span&gt;file&lt;&#x2F;span&gt; &lt;span&gt;${FILES}&lt;&#x2F;span&gt;)
    &lt;span&gt;set&lt;&#x2F;span&gt;(filename &lt;span&gt;${SOURCE_DIR}&lt;&#x2F;span&gt;&#x2F;&lt;span&gt;${file}&lt;&#x2F;span&gt;)
    &lt;span&gt;get_filename_component&lt;&#x2F;span&gt;(name &lt;span&gt;${filename}&lt;&#x2F;span&gt; NAME_WE)
    
    &lt;span&gt;set&lt;&#x2F;span&gt;(source &lt;span&gt;&quot;&quot;&lt;&#x2F;span&gt;)
    
    &lt;span&gt;file&lt;&#x2F;span&gt;(STRINGS &lt;span&gt;${filename}&lt;&#x2F;span&gt; lines)
    &lt;span&gt;foreach&lt;&#x2F;span&gt; (line &lt;span&gt;${lines}&lt;&#x2F;span&gt;)
        &lt;span&gt;set&lt;&#x2F;span&gt;(source &lt;span&gt;&quot;${source}    \&quot;${line}\\n\&quot;\n&quot;&lt;&#x2F;span&gt;)
    &lt;span&gt;endforeach&lt;&#x2F;span&gt;()
    
    &lt;span&gt;file&lt;&#x2F;span&gt;(
        WRITE &lt;span&gt;${DESTINATION_DIR}&lt;&#x2F;span&gt;&#x2F;&lt;span&gt;${file}&lt;&#x2F;span&gt;
        &lt;span&gt;&quot;static const GLchar *${name}ShaderSource[] &#x3D; {\n&quot;&lt;&#x2F;span&gt;
        &lt;span&gt;&quot;${source}&quot;&lt;&#x2F;span&gt;
        &lt;span&gt;&quot;};\n&quot;&lt;&#x2F;span&gt;
    )
&lt;span&gt;endforeach&lt;&#x2F;span&gt;()
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For each shader file found, CMake will create a new C++ file in the destination directory containing a single &lt;code&gt;GLchar *&lt;&#x2F;code&gt; variable that can be directly passed to the &lt;code&gt;glShaderSource()&lt;&#x2F;code&gt; function.&lt;&#x2F;p&gt;
&lt;h2&gt;Bootstrapping It All&lt;&#x2F;h2&gt;
&lt;p&gt;At this stage everything was ready for me to get started on OpenGL for good—I could write my OpenGL code and GLSL shaders independently from anything and then preview the result within the GUI toolkit of my choice.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve decided to save a checkpoint of this progression by wrapping everything in a bootstrap that I could reuse later on as a starting point for any project. It can be found on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;opengl-bootstrap&quot;&gt;GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Since I couldn’t decide which method to use for loading the GLSL shaders, and that it after all depends on the needs of each, I’ve added in the bootstrap an option for the user to switch between the dynamic and the static approach.&lt;&#x2F;p&gt;
&lt;p&gt;Support for both GLFW and Qt 5 (with or without the QtWidgets module) is also provided out of the box.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve tested it only on Mac OS X so far but porting it to either Linux or Windows shouldn’t be a problem, if it’s not working already.&lt;&#x2F;p&gt;
&lt;p&gt;Time to get those pixels moving!&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:617de56a-a4bb-11e7-95e0-732a1cd8a5e5</id>
      <title>Why I’m Not Using the MEL and Python Command Layers of Maya</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-06-25T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/maya-command-layers" rel="alternate" type="text/html" title="Why I’m Not Using the MEL and Python Command Layers of Maya" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/maya-command-layers/meliorism.jpg" />

      <content type="html">
        &lt;p&gt;There’s a large amount of things that frustrate me in Maya. The MEL and the Python command layers being two of those that I’d rather not have to ever use again.&lt;&#x2F;p&gt;
&lt;p&gt;But first, here’s a disclaimer. My sole objective here is to &lt;em&gt;genuinely&lt;&#x2F;em&gt; share my—hopefully objective—point of view and experience in the hope that it’ll get some to think about it. I’m not saying that this is the one and only valid approach, I’m just explaining why I’ve chosen this one. Good on you if you manage to create some bulletproof and awesome scripts using the MEL or the Python command layer.&lt;&#x2F;p&gt;
&lt;h2&gt;Preface&lt;&#x2F;h2&gt;
&lt;p&gt;To draw a quick parallel, both a command layer and an API are also available in Softimage. The question of which one to use has never been asked but by the beginners just getting started.&lt;&#x2F;p&gt;
&lt;p&gt;The API has always been the obvious choice. The command layer has its use but mostly for the artists out there who want to automatize a task by copying the log from the script editor that results from their interactions with the UI.&lt;&#x2F;p&gt;
&lt;p&gt;Funnily, I must be one of the rare living soul to use the Python API of Maya for daily tasks.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; But does being alone against all necessarily mean that I’m doing it the wrong way? I &lt;em&gt;hope&lt;&#x2F;em&gt; not.&lt;&#x2F;p&gt;
&lt;h2&gt;Introducing the MEL Command Layer&lt;&#x2F;h2&gt;
&lt;p&gt;Back then, there only was 2 options to code within Maya: MEL or the C++ API. The latter is largely overkilled for most of the tasks of a TD and is also harder to access and iterate. Logically, everyone turned to MEL. But what is MEL?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;maya-command-layers&#x2F;meliorism.jpg&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The official documentation of Maya tells us that MEL is derived from a UNIX shell language. Hence why it is called a command layer. It exposes a bunch of global commands like the ones that we would type into a shell to list the content of the current directory or to move onto another directory. &lt;code&gt;ls -la&lt;&#x2F;code&gt;, &lt;code&gt;cd .&#x2F;path&#x2F;to&#x2F;other&#x2F;directory&lt;&#x2F;code&gt; and the likes. Hey wait, there’s even a &lt;code&gt;ls()&lt;&#x2F;code&gt; command in MEL too!&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been baffled more than once by the quality of the MEL code that I was seeing. It was written with incredibly terrible coding practices to the point that I was wondering if the persons knew anything about coding at all. Now I finally understand why it was the case. They’re not to blame. That’s MEL’s fault.&lt;&#x2F;p&gt;
&lt;p&gt;My own theory is that MEL has been created with the sole purpose of describing ASCII Maya scenes. It probably wasn’t intented to be used as a scripting language with which one could write any serious code with. It just happened to be the case because the community was lacking of options.&lt;&#x2F;p&gt;
&lt;p&gt;Of course I know that you’ve all made awesome scripts that can do this and that. I wrote some myself, maybe not &lt;em&gt;that&lt;&#x2F;em&gt; awesome. But it’s like trying to sprint with ice skates.&lt;&#x2F;p&gt;
&lt;p&gt;Look at it, all we’ve got is that jungle of nonsensical global commands, each spammed with a ton of confusing flags that are often incompatible with each other. Good luck at figuring out a logic there.&lt;&#x2F;p&gt;
&lt;p&gt;It gets better over time, but not because &lt;em&gt;it&lt;&#x2F;em&gt; starts to make sense. Only because &lt;em&gt;we&lt;&#x2F;em&gt;’re getting used to it. That said, even today I’m still never sure which command from &lt;code&gt;getAttr()&lt;&#x2F;code&gt;, &lt;code&gt;attributeInfo()&lt;&#x2F;code&gt;, and &lt;code&gt;attributeQuery()&lt;&#x2F;code&gt; I should be using when needing to retrieve some information about an attribute. Any hint?&lt;&#x2F;p&gt;
&lt;p&gt;The point being that those commands are not made to make sense as a whole. They all achieve something specific but don’t always play well as part of a team. It’s as if the program to write was a huge puzzle and that we were constantly looking for pieces that might hopefully cover the surface without necessarily fitting into the neighbour pieces.&lt;&#x2F;p&gt;
&lt;p&gt;Once again this becomes almost anecdotal over time so let’s move on to something more important. Knowing how complex the dependency graph in Maya is, how can referring to a node in a scene solely by its name can be a robust approach at all?&lt;&#x2F;p&gt;
&lt;h2&gt;Strings Are Not Sexy&lt;&#x2F;h2&gt;
&lt;p&gt;To put things back into context, I’m a rigger. I’m spending a lot of time dealing with hierarchies and I like writing modular rigs.&lt;&#x2F;p&gt;
&lt;p&gt;Modular rigs makes it more than likely to have a same hierarchy of nodes being reused in a same scene. Instead of prefixing the name of each of my nodes with a hundredth of characters worth of different information to ensure that their names are unique within the scene, I prefer to stay simple and call them ‘root’, ‘spine’, ‘arm’, ‘whatever’, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;From there, let’s consider a first example.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; cmds

root &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;character&#39;&lt;&#x2F;span&gt;, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
l_arm &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;l_arm&#39;&lt;&#x2F;span&gt;, parent&#x3D;root, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
r_arm &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;r_arm&#39;&lt;&#x2F;span&gt;, parent&#x3D;root, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
l_node &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;node&#39;&lt;&#x2F;span&gt;, parent&#x3D;l_arm, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
r_node &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;node&#39;&lt;&#x2F;span&gt;, parent&#x3D;r_arm, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
l_child &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;child&#39;&lt;&#x2F;span&gt;, parent&#x3D;l_node, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
r_child &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;child&#39;&lt;&#x2F;span&gt;, parent&#x3D;r_node, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;See the problem here? The variable &lt;code&gt;l_node&lt;&#x2F;code&gt; is pointing to a node named &lt;em&gt;node&lt;&#x2F;em&gt; but can suddenly become invalid simply because another node has been created in the scene, and thus even though the node pointed by &lt;code&gt;l_node&lt;&#x2F;code&gt; hasn’t been modified in any way.&lt;&#x2F;p&gt;
&lt;p&gt;Of course this snippet of code reflects a terribly poor coding practice and the issue could be avoided much easily.&lt;&#x2F;p&gt;
&lt;p&gt;But the point here is to show a weakness in referring to nodes by their names. As bad as this snippet can be, it is still made of a valid serie of calls and should work in any decent API. It should &lt;em&gt;not&lt;&#x2F;em&gt; require the developer to know how the internals of the API works to workaround an issue that shouldn’t require any workaround.&lt;&#x2F;p&gt;
&lt;p&gt;Ok, let’s move on with another example.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; cmds

&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;toFullPathName&lt;&#x2F;span&gt;(&lt;span&gt;node&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; cmds.ls(node, long&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)[&lt;span&gt;0&lt;&#x2F;span&gt;]

root &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;character&#39;&lt;&#x2F;span&gt;, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
&lt;span&gt;for&lt;&#x2F;span&gt; arm_name &lt;span&gt;in&lt;&#x2F;span&gt; [&lt;span&gt;&#39;l_arm&#39;&lt;&#x2F;span&gt;, &lt;span&gt;&#39;r_arm&#39;&lt;&#x2F;span&gt;]:
    arm &#x3D; toFullPathName(cmds.group(name&#x3D;arm_name, parent&#x3D;root, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    node &#x3D; toFullPathName(cmds.group(name&#x3D;&lt;span&gt;&#39;node&#39;&lt;&#x2F;span&gt;, parent&#x3D;arm, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    child &#x3D; toFullPathName(cmds.group(name&#x3D;&lt;span&gt;&#39;child&#39;&lt;&#x2F;span&gt;, parent&#x3D;node, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This snippet solves the previous issue by introducing a loop statement.&lt;&#x2F;p&gt;
&lt;p&gt;Also, this time each node is stored by using its full path name instead of its partial path name to ensure a false impression of robustness that will be highlighted by the next example to come.&lt;&#x2F;p&gt;
&lt;p&gt;Note here the lack of consistency within the command layer: the &lt;code&gt;group()&lt;&#x2F;code&gt; command doesn’t have any flag to retrieve the full path name when many other commands have it. Funnily this flag can sometimes be accessible in other commands but under different names such as &lt;code&gt;long&lt;&#x2F;code&gt;, &lt;code&gt;longName&lt;&#x2F;code&gt; or &lt;code&gt;fullPath&lt;&#x2F;code&gt;, probably depending on the personal preferences of the coder at that time.&lt;&#x2F;p&gt;
&lt;p&gt;As a result, we don’t know what we’ll get as a return value from the &lt;code&gt;group()&lt;&#x2F;code&gt; command: a name or a partial path name? It’s like magic and depends on the content of the scene. Yet another thing to take into consideration when coding.&lt;&#x2F;p&gt;
&lt;p&gt;If we want to consistently deal with full path names then we’re left with calling a quite ugly workaround using the &lt;code&gt;ls()&lt;&#x2F;code&gt; command.&lt;&#x2F;p&gt;
&lt;p&gt;Now, and even though the flaws listed so far are not excusable, there’s still nothing that we can’t get used to with a bit of time.&lt;&#x2F;p&gt;
&lt;p&gt;The straw that broke the camel’s back is that, as a rigger being, I often have to insert objects within a hierarchy, reparent nodes, and so on. It would for example be common for me to add a parent to each node from a hierarchy to set their local transformations to identity in a post-process pass.&lt;&#x2F;p&gt;
&lt;p&gt;A naive approach of such an operation would look like this:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; cmds

&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;toFullPathName&lt;&#x2F;span&gt;(&lt;span&gt;node&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; cmds.ls(node, long&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)[&lt;span&gt;0&lt;&#x2F;span&gt;]

nodes &#x3D; []
root &#x3D; cmds.group(name&#x3D;&lt;span&gt;&#39;character&#39;&lt;&#x2F;span&gt;, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)
&lt;span&gt;for&lt;&#x2F;span&gt; arm_name &lt;span&gt;in&lt;&#x2F;span&gt; [&lt;span&gt;&#39;l_arm&#39;&lt;&#x2F;span&gt;, &lt;span&gt;&#39;r_arm&#39;&lt;&#x2F;span&gt;]:
    arm &#x3D; toFullPathName(cmds.group(name&#x3D;arm_name, parent&#x3D;root, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    node &#x3D; toFullPathName(cmds.group(name&#x3D;&lt;span&gt;&#39;node&#39;&lt;&#x2F;span&gt;, parent&#x3D;arm, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    child &#x3D; toFullPathName(cmds.group(name&#x3D;&lt;span&gt;&#39;child&#39;&lt;&#x2F;span&gt;, parent&#x3D;node, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    nodes.extend([node, child])

&lt;span&gt;for&lt;&#x2F;span&gt; node &lt;span&gt;in&lt;&#x2F;span&gt; nodes:
    parent &#x3D; cmds.listRelatives(node, parent&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;, fullPath&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;)[&lt;span&gt;0&lt;&#x2F;span&gt;]
    buffer &#x3D; toFullPathName(cmds.group(name&#x3D;&lt;span&gt;&#39;buffer&#39;&lt;&#x2F;span&gt;, parent&#x3D;parent, empty&#x3D;&lt;span&gt;True&lt;&#x2F;span&gt;))
    cmds.parent(node, buffer)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Without knowing the API and simply by reading the code from this snippet, the result seems fairly predictable. Except it’s not. This code errors out due to trying to retrieve an object from a full path name that is not valid anymore because of the changes made to the hierarchy. You thought you that had a solid reference to your node? You &lt;em&gt;don’t&lt;&#x2F;em&gt;. You simply can’t with strings.&lt;&#x2F;p&gt;
&lt;p&gt;Some possible workarounds to this one are quite funny actually. Either we could do our parenting pass after having sorted the list of nodes from the one at the bottom of the hiearchy to the one at the top, or we could store them in a temporary set to make sure that we keep a solid reference to them.&lt;&#x2F;p&gt;
&lt;p&gt;In both cases we would still end up with invalid references if we had to use these nodes again later on in our code. The exception being if we reassign the variables each time with their updated full path name. Quite error-prone.&lt;&#x2F;p&gt;
&lt;h2&gt;So What?&lt;&#x2F;h2&gt;
&lt;p&gt;Those are only 2 basic examples that I can remember despite not having used Maya in over 1 year. Believe me, I banged my head in frustration a whole lot more than that.&lt;&#x2F;p&gt;
&lt;p&gt;So how do I feel about this command layer? Pretty insecure I have to say. Why would I be keen in using a scripting interface that exposes so many inconsistencies and flaws?&lt;&#x2F;p&gt;
&lt;p&gt;Yes it’s usable and works well if one knows all the corner cases and take care of them. But it’s not something that I want to waste my energy dealing with only to end up in a state in which I would never have the confidence that a certain variable still points to the node I’d expect it to. And that sucks. Heaps.&lt;&#x2F;p&gt;
&lt;p&gt;Those are facts that most of you probably already know and have accepted dealing with. I didn’t.&lt;&#x2F;p&gt;
&lt;p&gt;Once again, I fully respect any approach and believe they are valid as soon as the code produced is robust. I chose the Maya API back in 2010 because it was simpler for me to write such code, that’s all.&lt;&#x2F;p&gt;
&lt;p&gt;Now if you expect me to praise the Maya API, this won’t happen neither. I actually understand why not so many people dare to adventure in there—it goes straight in the lower end of the user-friendliness scale, especially its Frankenstein-like Python version, and has its own pitfalls.&lt;&#x2F;p&gt;
&lt;p&gt;But at least it has a sort of organization with its object-oriented approach and most importantly: we can reference nodes as nodes.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, I chose it because my priority was to write &lt;em&gt;robust&lt;&#x2F;em&gt; code and it felt like the less worse option at that point in time. Also I was keen to invest some of my time to make it more usable on a daily basis by &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;gorilla-and-bananas&quot;&gt;developing some extensions through monkey patching&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;If not, please raise your hands, it’ll make me feel less lonely.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:3681e804-a4c3-11e7-8914-47b499eb7e14</id>
      <title>From Monkey Patching the Maya Python API to Gorilla and Bananas</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-06-23T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/dev/gorilla-and-bananas" rel="alternate" type="text/html" title="From Monkey Patching the Maya Python API to Gorilla and Bananas" />
        <category term="dev" />
        <media:thumbnail url="https://christophercrouzet.com/blog/dev/gorilla-and-bananas/banana.jpg" />

      <content type="html">
        &lt;p&gt;Or how I went from patching the Autodesk’s Maya Python API to creating a more generic solution for extending any library in Python.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;gorilla-and-bananas&#x2F;banana.jpg&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;credit: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;furryscalyman&#x2F;532792143&quot;&gt;Matt Reinbold&lt;&#x2F;a&gt; (&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-sa&#x2F;2.0&quot;&gt;CC BY-SA 2.0&lt;&#x2F;a&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Coming from Softimage and having had previous experiences with various libraries, it was a &lt;em&gt;shock&lt;&#x2F;em&gt; when I first came face-to-face with the API of Maya.&lt;&#x2F;p&gt;
&lt;p&gt;Many like to think of it as being flexible but I mainly see it as a &lt;em&gt;pain&lt;&#x2F;em&gt; to use. It’s clunky and inconsistent at best, and lacks basic methods that would make everyone’s life easier.&lt;&#x2F;p&gt;
&lt;p&gt;Fortunately, I don’t even have the words to describe its port to Python but I thought more than once about hanging the one who believed that using the &lt;code&gt;MScriptUtil&lt;&#x2F;code&gt; abomination to retrieve values such as the scale of a &lt;code&gt;MFnTransform&lt;&#x2F;code&gt; object would be a good idea.&lt;&#x2F;p&gt;
&lt;p&gt;As a result I quickly started to hate my job until I thought that I needed to find a way to make this thing more usable. It ended up becoming the perfect playground for experimenting some monkey patching techniques in Python.&lt;&#x2F;p&gt;
&lt;h2&gt;Extending the Maya API?&lt;&#x2F;h2&gt;
&lt;p&gt;In C++, if we want to extend an external object-oriented library such as the Maya API, there’s often no much choices offered to us. Because of the static nature of the language we’ve got no way to directly modify the built-in classes. All we can do is add some patches on the side rather than in-between.&lt;&#x2F;p&gt;
&lt;p&gt;Patching on the side equals here in extending the classes through inheritance. This approach wouldn’t play so well in our case. Indeed, we would have many castings to do from the original types to our own derived types, and we would end up losing the object-oriented benefits anyways: each method that we would add to a certain class would only be available in the scope of that class and won’t be inherited by any of the built-in classes.&lt;&#x2F;p&gt;
&lt;p&gt;For an analogy, imagine that you have a system of pipes that you want to modify to provide hot water. All you can do is to derivate from what’s already out there without being able to link your extensions back onto the original system. Only your new system will have hot water, not the existing one.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;gorilla-and-bananas&#x2F;pipes.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;still no hot water on the right side!&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;If the API doesn’t provide a way to insert new methods into the existing classes at runtime, then what are our options?&lt;&#x2F;p&gt;
&lt;p&gt;We’re left with creating global functions taking as first argument a pointer or a reference to the class we want to extend. It does the job, but it’s not so object-oriented anymore even though it preserves polymorphism. Wrapping the original API into a new one could be another solution but a colossal and hard to maintain work too.&lt;&#x2F;p&gt;
&lt;p&gt;But that’s C++ and we want to use Python.&lt;&#x2F;p&gt;
&lt;h2&gt;Python to the Rescue&lt;&#x2F;h2&gt;
&lt;p&gt;Unlike with C++, &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.diveintopython.net&#x2F;getting_to_know_python&#x2F;everything_is_an_object.html&quot;&gt;everything in Python is an object&lt;&#x2F;a&gt;. Even classes, functions and numbers are objects. We can assign those objects to variables, they most likely have attributes, and we can inspect and modify them at runtime.&lt;&#x2F;p&gt;
&lt;p&gt;This concept is at the same time both incredibly powerful &lt;em&gt;and&lt;&#x2F;em&gt; dangerous.&lt;&#x2F;p&gt;
&lt;p&gt;In our case, it gives us the chance to improve the Maya Python API on the surface by allowing us to sneakily insert new methods but it also makes it easy to fuck up everything if we’re not so wise. Welcome to the monkey patching world.&lt;&#x2F;p&gt;
&lt;h2&gt;Monkey Patching ABC&lt;&#x2F;h2&gt;
&lt;p&gt;Let’s check out the basics of the technique with a serie of examples.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

dummy &#x3D; OpenMaya.MObject()
&lt;span&gt;print&lt;&#x2F;span&gt;(dummy.apiTypeStr())
&lt;span&gt;print&lt;&#x2F;span&gt;(dummy.isNull())
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here we initialize a &lt;code&gt;dummy&lt;&#x2F;code&gt; variable containing an instance of the class &lt;code&gt;MObject&lt;&#x2F;code&gt;. As it is right now, this &lt;code&gt;dummy&lt;&#x2F;code&gt; variable doesn’t hold any valid Maya object internally but that’s enough for us to play around and have access to the methods defined within the &lt;code&gt;MObject&lt;&#x2F;code&gt; class.&lt;&#x2F;p&gt;
&lt;p&gt;You’d better off checking the Maya documentation to know what methods are available to call but we can also list them at runtime with a Python’s built-in function: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;library&#x2F;functions.html#dir&quot;&gt;&lt;code&gt;dir()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

&lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;dir&lt;&#x2F;span&gt;(OpenMaya.MObject))
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As per Python’s documentation, the &lt;code&gt;dir()&lt;&#x2F;code&gt; function tries to list all the attributes of an object. Simply put, this will print here a whole bunch of names that correspond to the methods and data variables defined for the class &lt;code&gt;MObject&lt;&#x2F;code&gt;. The ones surrounded with double underscores are the internal “magic” attributes mostly generated by Python that we’re not supposed to touch—for now.&lt;&#x2F;p&gt;
&lt;p&gt;Since we said that everything in Python is an object, nothing stops us from getting the value assigned to those attributes listed previously. There’s another function built-in in Python to do just that: &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;library&#x2F;functions.html#getattr&quot;&gt;&lt;code&gt;getattr()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

&lt;span&gt;for&lt;&#x2F;span&gt; attribute &lt;span&gt;in&lt;&#x2F;span&gt; &lt;span&gt;dir&lt;&#x2F;span&gt;(OpenMaya.MObject):
    &lt;span&gt;print&lt;&#x2F;span&gt;(&lt;span&gt;&#39;%s: %s&#39;&lt;&#x2F;span&gt; % (attribute, &lt;span&gt;getattr&lt;&#x2F;span&gt;(OpenMaya.MObject, attribute)))
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The attribute &lt;code&gt;apiTypeStr&lt;&#x2F;code&gt; for example is assigned with an unbound method object. That unbound method object is a legacy from Python 2 to represent methods that haven’t been called from a class instance. But this is just an unimportant detail for this scope.&lt;&#x2F;p&gt;
&lt;p&gt;What matters is that we can indeed retrieve those attributes at runtime, and that if there’s a &lt;code&gt;getattr()&lt;&#x2F;code&gt; function, then there must be a &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;library&#x2F;functions.html#setattr&quot;&gt;&lt;code&gt;setattr()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; one.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;setattr()&lt;&#x2F;code&gt; allows us to assign new values to existing attributes but also to define new ones if they don’t exist yet. That’s precisely what we need to insert a new awesome method directly within the &lt;code&gt;MObject&lt;&#x2F;code&gt; class.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;awesomeFn&lt;&#x2F;span&gt;(&lt;span&gt;self&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;&quot;Everything is awesome! Even this %s thing.&quot;&lt;&#x2F;span&gt; % self.apiTypeStr()

&lt;span&gt;setattr&lt;&#x2F;span&gt;(OpenMaya.MObject, &lt;span&gt;&#39;awesome&#39;&lt;&#x2F;span&gt;, awesomeFn)
dummy &#x3D; OpenMaya.MObject()
&lt;span&gt;print&lt;&#x2F;span&gt;(dummy.awesome())
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That’s all it takes. Thanks to this one liner, we now have access to the &lt;code&gt;awesome()&lt;&#x2F;code&gt; method as if it has always been part of the &lt;code&gt;MObject&lt;&#x2F;code&gt; class.&lt;&#x2F;p&gt;
&lt;p&gt;But that’s also where the danger is. By inadvertance—or &lt;em&gt;madness&lt;&#x2F;em&gt;—, you could replace an existing method. This is allowed in Python since once again it is considered as a simple reassignment of a variable with another object. Of course you’ve changed the expected behavior of that method, causing a subtle bug showing up only late in the production. Congratulations, you’ve broken everything and are about to get fired.&lt;&#x2F;p&gt;
&lt;p&gt;A first option would be to prefix all your own methods with something unlikely to be ever used by the developer team of Maya. The method &lt;code&gt;awesome()&lt;&#x2F;code&gt; could for example be called &lt;code&gt;bnnAwesome()&lt;&#x2F;code&gt;.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Secondly, you could check if a method already exists, just to make sure. And if it does, then just give up. It’s better to preserve the original behavior rather than inserting your broken method that you couldn’t even figure out how to name properly.&lt;&#x2F;p&gt;
&lt;p&gt;Quizz on how to check if a method already exists? The global function &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;docs.python.org&#x2F;library&#x2F;functions.html#hasattr&quot;&gt;&lt;code&gt;hasattr()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now, there’s a last option if really, &lt;em&gt;really&lt;&#x2F;em&gt;, you have a good reason, you know what you’re doing at 2000%, and you don’t mind getting fired. Should I say that it’s highly unrecommended? Well, it is. Use at your own risks. You’ve been warned.&lt;&#x2F;p&gt;
&lt;p&gt;That last option consists in storing the existing method under a new name such as &lt;code&gt;_original_awesome()&lt;&#x2F;code&gt;. This way, the overidden method remains callable within our code if we wanted to preserve its original behavior as we should.&lt;&#x2F;p&gt;
&lt;h2&gt;Inheritance&lt;&#x2F;h2&gt;
&lt;p&gt;Since we’re inserting methods into an existing class hierarchy, this means that inheritance is automagically preserved.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;gorilla-and-bananas&#x2F;inheritance.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;inheritance diagram for the MFnTransform class&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In other words, if we insert a method &lt;code&gt;bnnAwesome()&lt;&#x2F;code&gt; in &lt;code&gt;MFnTransform&lt;&#x2F;code&gt;, then it will also be available in the all the classes defined on the right of &lt;code&gt;MFnTransform&lt;&#x2F;code&gt; from the graph above.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya, OpenMayaAnim

&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;awesomeFn&lt;&#x2F;span&gt;(&lt;span&gt;self&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;&quot;Everything is awesome!&quot;&lt;&#x2F;span&gt;

&lt;span&gt;# Patch the &#x60;MFnTransform&#x60; class.&lt;&#x2F;span&gt;
&lt;span&gt;setattr&lt;&#x2F;span&gt;(OpenMaya.MFnTransform, &lt;span&gt;&#39;awesome&#39;&lt;&#x2F;span&gt;, awesomeFn)

&lt;span&gt;# Use the method in the &#x60;MFnIkJoint&#x60; class.&lt;&#x2F;span&gt;
dummy &#x3D; OpenMayaAnim.MFnIkJoint()
&lt;span&gt;print&lt;&#x2F;span&gt;(dummy.awesome())
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that’s awesome.&lt;&#x2F;p&gt;
&lt;h2&gt;A Naive Implementation&lt;&#x2F;h2&gt;
&lt;p&gt;As effective as this code can be, this won’t take us every far and it would quickly become cumbersome if we had to manually repeat this process for each method that we’d like to patch.&lt;&#x2F;p&gt;
&lt;p&gt;The original approach that I used in production was to create a bunch of Python modules, each named as per the OpenMaya class that I wanted to patch, and each containing a list of functions to insert.&lt;&#x2F;p&gt;
&lt;p&gt;A function could be inserted either as a normal method, a class method, or a static method depending if &lt;code&gt;self&lt;&#x2F;code&gt;, &lt;code&gt;cls&lt;&#x2F;code&gt; or nothing was defined as first keyword.&lt;&#x2F;p&gt;
&lt;p&gt;It’s been really easy to use but it felt a bit hacky.&lt;&#x2F;p&gt;
&lt;p&gt;That’s why I recently came up with the idea of building a library to provide a convenient—and flexible—approach to monkey patching.&lt;&#x2F;p&gt;
&lt;h2&gt;The Real Deal: Gorilla&lt;&#x2F;h2&gt;
&lt;p&gt;With the library &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;gorilla&quot;&gt;Gorilla&lt;&#x2F;a&gt;, you can patch anything as soon as it makes sense.&lt;&#x2F;p&gt;
&lt;p&gt;Marking our awesome method as being an extension for the target class &lt;code&gt;OpenMaya.MFnTransform&lt;&#x2F;code&gt; becomes as easy as:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;import&lt;&#x2F;span&gt; gorilla
&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

&lt;span&gt;@gorilla.patch(&lt;span&gt;OpenMaya.MFnTransform&lt;&#x2F;span&gt;)&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;bnnAwesome&lt;&#x2F;span&gt;(&lt;span&gt;self&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;&quot;Everything is awesome!&quot;&lt;&#x2F;span&gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If many methods are to be patched into a same target class, it can be quicker to define them into a class and mark that class as an extension itself.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;import&lt;&#x2F;span&gt; gorilla
&lt;span&gt;from&lt;&#x2F;span&gt; maya &lt;span&gt;import&lt;&#x2F;span&gt; OpenMaya

&lt;span&gt;@gorilla.patch(&lt;span&gt;OpenMaya&lt;&#x2F;span&gt;)&lt;&#x2F;span&gt;
&lt;span&gt;&lt;span&gt;class&lt;&#x2F;span&gt; &lt;span&gt;MFnTransform&lt;&#x2F;span&gt;(&lt;span&gt;&lt;span&gt;object&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
    &lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;bnnAwesome&lt;&#x2F;span&gt;(&lt;span&gt;self&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
        &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;&quot;Everything is awesome!&quot;&lt;&#x2F;span&gt;
    
    &lt;span&gt;&lt;span&gt;def&lt;&#x2F;span&gt; &lt;span&gt;bnnMoreAwesome&lt;&#x2F;span&gt;(&lt;span&gt;self&lt;&#x2F;span&gt;):&lt;&#x2F;span&gt;
        &lt;span&gt;return&lt;&#x2F;span&gt; &lt;span&gt;&quot;Everything is even more awesome!&quot;&lt;&#x2F;span&gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Our &lt;code&gt;MFnTransform&lt;&#x2F;code&gt; class is here to be directly patched into the module &lt;code&gt;OpenMaya&lt;&#x2F;code&gt;. Since a class with that name already exists in the target module, it’s each method of our class that are going to be individually inserted into the &lt;code&gt;OpenMaya.MFnTransform&lt;&#x2F;code&gt; class while preserving the ones built-in.&lt;&#x2F;p&gt;
&lt;p&gt;To apply the actual patching, a function is provided to recursively look into all the modules from a given package for any extension marked with the &lt;code&gt;gorilla.patch()&lt;&#x2F;code&gt; decorator. The extensions found are then applied.&lt;&#x2F;p&gt;
&lt;p&gt;Properties, class methods, static methods, and whatsoever are also supported as extensions. Other features include the possibility to define what needs to be patched at runtime and how. But really, the &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;gorilla.readthedocs.io&quot;&gt;documentation of Gorilla&lt;&#x2F;a&gt; does explain all the details more nicely.&lt;&#x2F;p&gt;
&lt;p&gt;The best in all that? The code is relatively straightforward and works for both Python 2.6+ and 3.3+. Have a look by yourself, &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;gorilla&quot;&gt;get the source on GitHub&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;A Banana for Maya&lt;&#x2F;h2&gt;
&lt;p&gt;As a proof of concept for the library Gorilla, I’ve made a set of extensions for the Maya Python API v1.0.&lt;&#x2F;p&gt;
&lt;p&gt;In its current state, it’s not very furnished but it still provides a good starting point with a set of methods to easily retrieve and iterate through the nodes of a scene.&lt;&#x2F;p&gt;
&lt;p&gt;It’s—logically?—called Bana and can also be found on &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;christophercrouzet&#x2F;bana&quot;&gt;GitHub&lt;&#x2F;a&gt;. Here again, I’ve put some decent efforts in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;bana.readthedocs.io&quot;&gt;the documentation&lt;&#x2F;a&gt; if you’re curious.&lt;&#x2F;p&gt;
&lt;h2&gt;Final Note&lt;&#x2F;h2&gt;
&lt;p&gt;When I decided to adopt this approach, it was back in 2010 and I had my own—not necessarily valid—reasons to do so. &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;github.com&#x2F;LumaPictures&#x2F;pymel&quot;&gt;PyMEL&lt;&#x2F;a&gt; wasn’t shipped with Maya and I refused to use the Python command layer of Maya which is a simple 1:1 of the &lt;em&gt;atrocity&lt;&#x2F;em&gt; that MEL is.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Even today I would most probably go the same way as it served me quite well and made me proud of successfully taking on the challenge of using that Maya Python API in production.&lt;&#x2F;p&gt;
&lt;p&gt;Now, by publishing this article, I do not intend to say that monkey patching the Maya Python API is the way to go. There’s other alternatives out there that are probably better suited for the vast majority of the use cases and that won’t require any extra work on your end.&lt;&#x2F;p&gt;
&lt;p&gt;Whatever path you choose, do it because you feel comfortable using it on a daily basis and make sure that you can get your things done efficiently.&lt;&#x2F;p&gt;
&lt;p&gt;Having the monkey patching technique in hands simply gives you the bonus option of being able to extend any Python library to your wishing if you need to, and as a last resort kind of things.&lt;&#x2F;p&gt;
&lt;p&gt;Use it wisely.&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;&lt;em&gt;bnn&lt;&#x2F;em&gt; like banana.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;You can read more in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;dev&#x2F;maya-command-layers&quot;&gt;Why I’m Not Using the MEL and Python Command Layers of Maya&lt;&#x2F;a&gt;.&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:4a959bbc-a4cb-11e7-8266-1b30f506c15a</id>
      <title>A Taiwanese Monk at the Salar de Uyuni, Bolivia</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-06-19T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/travel/salar-uyuni" rel="alternate" type="text/html" title="A Taiwanese Monk at the Salar de Uyuni, Bolivia" />
        <category term="travel" />
        <media:thumbnail url="https://christophercrouzet.com/blog/travel/salar-uyuni/monk-wet-salar-uyuni-bolivia.jpg" />

      <content type="html">
        &lt;p&gt;The Salar de Uyuni, one of these surreal places that I had in mind at the time when I bought my camera only to realize that a simple Photoshop gradient would have got me the same result. Minus the Taiwanese monk.&lt;&#x2F;p&gt;
&lt;p&gt;Mid-June is the dry season in Bolivia but it’s also winter time. It can get &lt;em&gt;relatively&lt;&#x2F;em&gt; cold at an elevation of 3,500 m, and it gets even colder when you’ve got a salt flat covering a surface of 10,500 km² nearby.&lt;&#x2F;p&gt;
&lt;p&gt;Since the freezing nights in the non-heated rooms of an hostel in Uyuni were not enough, I decided to go for the next level—waking up at 3 a.m. to make it for some night shoot of the stars and most importantly for the sunrise over the Salar de Uyuni.&lt;&#x2F;p&gt;
&lt;p&gt;There were 5 crazy of us in the group to volunteer for a hypothermia experience: 3 Japaneses, a Vietnamese, and the usual French. We all got a bit disappointed when our guide Ricardo drove us to the spot only to tell us that it won’t be possible to shoot the stars this time since the light of the full moon was polluting the sky. We had to wait for another 2 hours before sunrise.&lt;&#x2F;p&gt;
&lt;p&gt;The car was heated and felt like our last rampart between us and the cold out there. The few rapid excursions to get some snaps got us frozen within seconds. When the fingers reached the point of not being able to press the shutter button anymore, it was time to get back into the car to warm up until the next attempt.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;14273438908&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;before-sunrise-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Before sunrise, Salar de Uyuni, Bolivia&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;before sunrise&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The results of our brave efforts ended up looking pale in comparison to when the light finally showed up. The 5 minutes during which the sky turned pinkish&#x2F;blue while being perfectly mirrored on a thin layer of water was simply speechless. And disturbing of simplicity too. It couldn’t look any more fake.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;christopher-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;a (Photoshopped?) French&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;14268581117&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;jeep-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Jeep, Salar de Uyuni, Bolivia&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That sunrise over the Salar de Uyuni was just for us. We were the only 5—plus Ricardo—to dare adventuring over there at that time. We felt privileged and took the usual touristic photos.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;daltons-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;A bunch of people lining up, Salar de Uyuni, Bolivia&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;the Daltons&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After such a beautiful experience, I was craving for some more. I didn’t make all the way to Uyuni, especially after &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;twitter.com&#x2F;christophercrzt&#x2F;status&#x2F;476420544519487488&quot;&gt;coming close to death around 10 time on the way from Sucre&lt;&#x2F;a&gt;, for only one sunrise. I signed up for a 1-day tour the day after. I wanted to see the dried and cracked parts of the salt flat.&lt;&#x2F;p&gt;
&lt;p&gt;This time I definitely wasn’t one of the few to have had that idea. The dozen of tour agencies in Uyuni were fully booked and soon we reached our first target—the train cemetery—accompanied by a good hundredth of other tourists.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;There I was, back into a tourist trap&lt;&#x2F;strong&gt;. After one had the idea of climbing a train to get a new Facebook profile picture, they were all onto it, proudly posing on top of those metallic carcases. Fortunately they didn’t spot the one sneakily tagging his name on a train further away or they might all have done the same.&lt;&#x2F;p&gt;
&lt;p&gt;Needless to say that taking a photo without anyone on it was close to a miracle. Panoramics were of course out of question.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;train-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Abandoned train, Salar de Uyuni, Bolivia&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;that’s life&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We were only allowed 15 min on each spot to do our things before heading to the next destination. The exception was the popular Isla of Incahuasi. It was our lunch spot.&lt;&#x2F;p&gt;
&lt;p&gt;After filling our rumen, we had 1.5 hours of free time to enjoy ourselves. Actually there wasn’t much to do. The only 2 choices were to either take photos using the immense surrounding desert as a playground or to visit the island (and its toilets) for 30 BOB. It all seemed like a well planned invitation for us to play our roles of milk cow.&lt;&#x2F;p&gt;
&lt;p&gt;Was it worth it so far? I don’t know. But fortunately a not so usual event made that day more interesting. A Taiwanese monk happened to also visit the Salar de Uyuni and was luckily sharing our car.&lt;&#x2F;p&gt;
&lt;p&gt;Ariano talks many languages, is full of energy, has his approach to peace, and likes to take photos of the road with his tablet while sitting in the passenger seat. A great man.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;14288426837&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;monk-dry-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Taiwanese Monk, Salar de Uyuni, Bolivia&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When reaching an hostel&#x2F;museum entirely made of salt, we’ve been reminded of the recent &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.dakar.com&quot;&gt;Dakar rally raid&lt;&#x2F;a&gt; that took place here in 2014. I firstly thought of the possible negative impact that such a race might have on the environment but then... well, I enjoy racing myself and selfishly couldn’t help thinking how fun it would be to rally drive on Uyuni’s salt flat.&lt;&#x2F;p&gt;
&lt;p&gt;Our driver for the day seemed to know more than me on the subject and told me that joining the Dakar was mainly a matter of having a lot of money to spend. The salar will host another race during the wet season of 2015. Maybe another time?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;14293740680&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;dakar-flags-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Flags from a previous edition of the Dakar race, Salar de Uyuni, Bolivia&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;the Dakar passed by here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The wet season is the recommended time to visit the salar. The temperatures are warmer, the entire surface of the salt flat is covered with water and there are chances to have the sky made of awesomeness: menacing gigantic clouds forming a beautiful ambiance for photography.&lt;&#x2F;p&gt;
&lt;p&gt;We didn’t have all that but we could still comfort ourselves with the highlight of the day—spending the sunset on the small area still covered by water. For the clouds, I’ll have to come back later.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;14336853369&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;salar-uyuni&#x2F;monk-wet-salar-uyuni-bolivia.jpg&quot; alt&#x3D;&quot;Taiwanese Monk, Salar de Uyuni, Bolivia&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:5aa66c0e-a65e-11e7-bfda-9bd94296848f</id>
      <title>myXKCD: CPU benchmark</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-06-01T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/comics/cpu-benchmark" rel="alternate" type="text/html" title="myXKCD: CPU benchmark" />
        <category term="comics" />
        <media:thumbnail url="https://christophercrouzet.com/blog/comics/cpu-benchmark/myxkcd-cpu-benchmark.gif" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;comics&#x2F;cpu-benchmark&#x2F;myxkcd-cpu-benchmark.gif&quot; alt&#x3D;&quot;CPU benchmark&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;100% viable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:78963e30-a4d1-11e7-becb-67b6a3fba1c8</id>
      <title>ccRig: A Rig for Softimage Back from the Dead</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-05-15T12:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/ccrig" rel="alternate" type="text/html" title="ccRig: A Rig for Softimage Back from the Dead" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/ccrig/ccrig.gif" />

      <content type="html">
        &lt;p&gt;Having the limbs producing nice curvatures at any stretch value while being configurable and robust? Those were my goals for a rig prototype that I wanted to get done. It was back in 2006. It had a good run and it’s now live again. Sort of.&lt;&#x2F;p&gt;
&lt;h2&gt;The Apogee&lt;&#x2F;h2&gt;
&lt;p&gt;One of my very first gig as a character TD was a pitch for &lt;cite&gt;The Stupid Invaders&lt;&#x2F;cite&gt;, a cartoony movie. The characters had thin arms that could be &lt;em&gt;highly&lt;&#x2F;em&gt; stretchable and bendable. The rigs were working mostly great and my job back then was to fix any eventual issue happening on a per shot basis.&lt;&#x2F;p&gt;
&lt;p&gt;That’s when I thought that I should challenge myself with a prototype of my own. It had to be able to stretch limbs without any limit and still always produce nice curvatures that are easy to control. And of course, it had to be robust—no more popping. &lt;em&gt;Ever&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;A couple of years later, in 2008, I’ve been showcasing this rig in &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;portfolio#character-rigging&quot;&gt;my first rigging showreel&lt;&#x2F;a&gt;. We could already see stretching and bending of limbs in every showreel, but I knew there was something different to this approach.&lt;&#x2F;p&gt;
&lt;p&gt;I decided to share my researches for educational purposes and released the rig for free at the same time than my showreel.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;ccrig&#x2F;ccrig.gif&quot; alt&#x3D;&quot;&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I didn’t except such a response from the community. Overwhelmed would be an understatement. Coming out from the unknown and having my work showcased on the front pages of various community websites was just unthinkable.&lt;&#x2F;p&gt;
&lt;p&gt;It helped me to gain a bit in confidence but that’s also when I understood that “success” had a price. I had to pay for the 930 GB of extra bandwith that I’ve been using for the previous 2 weeks. That was an excess of &lt;strong&gt;4750%&lt;&#x2F;strong&gt; of the quota that my web host was providing.&lt;&#x2F;p&gt;
&lt;h2&gt;Back on Track&lt;&#x2F;h2&gt;
&lt;p&gt;In November last year, with the release of my brand new website, I thought that it was about time to flush out all the old resources and start over from scrach. Who could be using this rig anyways? I mean... that rig was created with Softimage|XSI 6.5, you know!&lt;&#x2F;p&gt;
&lt;p&gt;Turns out I was wrong. The traffic logs were consistently showing requests to the old ccRig resource page that now leads to a 404 error. Maybe some naive animators thinking they’ll find the rig of their life in there. They too couldn’t be more wrong. But that’s not my problem—you want it, you got it. You can now &lt;a href&#x3D;&quot;#download&quot;&gt;download&lt;&#x2F;a&gt; it and do your things.&lt;&#x2F;p&gt;
&lt;p&gt;I’ll repeat it though. This rig was only built for &lt;em&gt;educational&lt;&#x2F;em&gt; purposes. Not that I will prevent you from doing any showreel, commercial work or whatsoever with it—no, the truth is that it is simply not suited for proper animation work.&lt;&#x2F;p&gt;
&lt;p&gt;From an animation point of view, its spine is terrible to the point of being broken and it lacks any facial rig. Now, if you’re of the stubborn&#x2F;masochist type, or if you simply like challenges, I’d still love to see what you can manage to do with it.&lt;&#x2F;p&gt;
&lt;p&gt;From a more technical aspect, the rig itself is highly outdated in the way it is being layed out and won’t serve as a good reference.&lt;&#x2F;p&gt;
&lt;p&gt;I still believe that there’s some value to the logic behind the stretchy&#x2F;curvy system of the limbs though, which is why I put it back online—for the rigging beginners out there to selectively learn some bits from that logic. And only that. Nothing else for your own sake. Don’t come complain to me afterwards if you get fired because you’re rigging like in the 60’s.&lt;&#x2F;p&gt;
&lt;h2&gt;Download&lt;&#x2F;h2&gt;
&lt;div&gt;
&lt;p&gt;&lt;span aria-hidden&#x3D;&quot;true&quot;&gt;&lt;&#x2F;span&gt; &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;ccrig&#x2F;ccRig-v0.11.zip&quot;&gt;&lt;strong&gt;ccRig v0.11&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Note that the rig is provided as it was back then. &lt;s&gt;I don’t even know if it opens in the latest versions of Softimage.&lt;&#x2F;s&gt; Thanks to Eric Thivierge, the rig apparently survived to the Autodesk era and made it through to the final version of Softimage! I can be proud of my weed.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:44e0fb5e-a4d4-11e7-b9a5-4bc945ac0036</id>
      <title>Softimage Has Been Killed, the Future of CG Softwares Is Now in TD’s Hands</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-03-07T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/softimage-is-dead" rel="alternate" type="text/html" title="Softimage Has Been Killed, the Future of CG Softwares Is Now in TD’s Hands" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/softimage-is-dead/emTopolizer.jpg" />

      <content type="html">
        &lt;p&gt;Softimage did shine in more than one’s heart until it met its fate with Autodesk. Our dear software is now dead but not our experience nor our vision. It’s now up to us to make the future brighter.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;softimage-is-dead&#x2F;softimage-splash-screen.jpg&quot; alt&#x3D;&quot;Softimage|XSI splash screen&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Softimage the Great&lt;&#x2F;h2&gt;
&lt;p&gt;When I started to learn 3D on my own as a teenager, I used to play around with Maya 2.5. It looked complicated and unintuitive, which paradoxically &lt;em&gt;excited&lt;&#x2F;em&gt; me. Everyone within the online community that I was frequenting found it &lt;em&gt;hard&lt;&#x2F;em&gt; to use. I liked the challenge. And the splashscreen too.&lt;&#x2F;p&gt;
&lt;p&gt;I kept reading good reviews about the recently released Softimage|XSI and decided to try it out one day. I gave up 30 minutes after opening it. I didn’t like the default shortcuts, I didn’t like the UI. I didn’t give it a chance.&lt;&#x2F;p&gt;
&lt;p&gt;It’s only when I got in touch with Action Synthèse in 2005 that I got back into it. I had no other choice if I wanted to land an internship anyways. For that reason I took the time to setup my own shortcuts, to get used to the UI, and to get some sensations going.&lt;&#x2F;p&gt;
&lt;p&gt;It didn’t take long, it was a &lt;em&gt;blast&lt;&#x2F;em&gt;. I realized how &lt;em&gt;stupid&lt;&#x2F;em&gt; I was to give up so easily before.&lt;&#x2F;p&gt;
&lt;p&gt;My goal was to learn rigging and it was the best tool for that. There was nothing preventing me from applying my ideas—Softimage had a fast learning curve, it was intuitive, and made perfect sense. Thanks to that in &lt;em&gt;no time&lt;&#x2F;em&gt; I felt comfortable to get some rigs going.&lt;&#x2F;p&gt;
&lt;p&gt;I never wrote any line of code before then but I thought it would be a good idea to also learn how to develop. That’s how I started to use the Softimage SDK. I struggled quite a bit at understanding how to write my code in the first place but the API itself was no problem. Here again, &lt;em&gt;everything&lt;&#x2F;em&gt; made sense, even for a total beginner like me, and I quickly got my first scatter tool going.&lt;&#x2F;p&gt;
&lt;p&gt;Some softwares are simple to use but don’t get you very far. It wasn’t the case with Softimage. Thanks to its well-thought architecture, it never got on the way when complex work was required.&lt;&#x2F;p&gt;
&lt;p&gt;On top of the quality of the software itself, both the developers and the community were forming a great symbiosis. That was a bunch of active, friendly, helpful, and innovative souls. Softimage was driven by its users. Everyone was involved.&lt;&#x2F;p&gt;
&lt;p&gt;With the implementation of the nodal graph ICE in July 2008, things quickly became out of control. More power at the reach of every artist. Everyone had a &lt;em&gt;blast&lt;&#x2F;em&gt; using it, countless of inspiring demonstrations of the tool kept emerging on the Internet. It also quickly became indispensable.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;softimage-is-dead&#x2F;softimage-ice.jpg&quot; alt&#x3D;&quot;A Softimage ICE compound&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;a Softimage ICE compound&lt;&#x2F;span&gt;&lt;span&gt;credit: &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;ioxu.com&quot;&gt;Benjamin Paschke&lt;&#x2F;a&gt; (with permission)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;The Struggles&lt;&#x2F;h2&gt;
&lt;p&gt;Eventually Autodesk felt the threat coming and perceived Softimage as a strong competitor to Maya and 3DSMax. Four months after the public release of ICE, Autodesk bought Softimage from Avid. A that point, they owned the 3 major all-rounded 3D softwares on the market. No more competition on this front.&lt;&#x2F;p&gt;
&lt;p&gt;As soon as the deal got rumored, things started to change. The community grew concerned and unsecure about the future of its software. The Softimage public mailing-list became the place of numerous &lt;em&gt;heated&lt;&#x2F;em&gt; debates on the subject.&lt;&#x2F;p&gt;
&lt;p&gt;But the community was stronger. It regained optimism when seeing the work accomplished by its users. Two of them mainly ended up doing the show and managed to open the eyes of those living on the planet Maya. They both developed plugins for ICE: &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.mootzoid.com&quot;&gt;Eric Mootz&lt;&#x2F;a&gt; with his Mootzoid nodes and &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;thiagocosta.net&quot;&gt;Thiago Costa&lt;&#x2F;a&gt; with &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;vimeo.com&#x2F;13457383&quot;&gt;Lagoa Multiphysics&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;softimage-is-dead&#x2F;emTopolizer.jpg&quot; alt&#x3D;&quot;test of emTopolizer2&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;test of emTopolizer2&lt;&#x2F;span&gt;&lt;span&gt;credit: &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;art.bt-3d.de&#x2F;&quot;&gt;Tim Borgmann&lt;&#x2F;a&gt; (with permission)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We were proud to be Softimage users even though we started to feel more and more abandoned by Autodesk after each new release. &lt;strong&gt;Softimage was marketed as a plug-in for Maya&lt;&#x2F;strong&gt;, had less features implemented, the developers eventually moved to the Maya team, and always more debates were being fired up on the list.&lt;&#x2F;p&gt;
&lt;p&gt;Meanwhile I was dealing with my own struggle in parallel.&lt;&#x2F;p&gt;
&lt;p&gt;I landed a job at &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.wetafx.co.nz&quot;&gt;Weta Digital&lt;&#x2F;a&gt; and had to learn Maya. I tried hard to approach this transition from an open-minded pespective and there was no big issue in transferring my rigging skill set. The concepts in rigging are pretty much the same everywhere. What dumbstrucked me was the process to get to that same result.&lt;&#x2F;p&gt;
&lt;p&gt;I felt like I was back to stone age. I found many of Maya’s daily tasks and the design of its API to be &lt;em&gt;retarded&lt;&#x2F;em&gt;. I spent my first weeks (months?) asking my teammates how they could possibly work with a such software. Even now, I still refuse to do in Maya a task as simple as painting some skin weights. It drives me &lt;em&gt;crazy&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;My colleagues wouldn’t understand my frustration. All they knew was Maya, they couldn’t compare it to anything else. I was being just annoying to them. I sometimes wished to be ignorant too—it would have helped with my zen.&lt;&#x2F;p&gt;
&lt;p&gt;But at the end of the day, let’s face it. As good as Softimage could be, it was a battle lost in advance. They’ve been &lt;em&gt;trapped&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h2&gt;The Proprietary Do-It-All Trap&lt;&#x2F;h2&gt;
&lt;p&gt;One size doesn’t fit all. All-rounded 3D softwares cannot please the specific requirements of each studio and each user, and that’s fair enough.&lt;&#x2F;p&gt;
&lt;p&gt;Keeping a such package up-to-date is not guaranteed neither. Developers cannot afford to focus actively and simultaneously on each area covered by this kind of software. The large codebase, the deep dependencies between each internal module, and the unique core architecture and data structures trying to accomodate each discipline are all not helping much here.&lt;&#x2F;p&gt;
&lt;p&gt;Specialized softwares are more likely to remain competitive by implementing the latest technologies in their field and even define new standards themselves. But then those all-rounded 3D packages allows for a smooth pipeline out of the box. For a company just starting its business and not having much resources yet to invest in RnD to connect the dots of a more flexible pipeline, this has always been an attractive feature.&lt;&#x2F;p&gt;
&lt;p&gt;I believe this was especially a game changer back in the 90’s when anything must have been a struggle. If a unified pipeline with a single software could make the life of its users a bit simpler, then what could be &lt;em&gt;wrong&lt;&#x2F;em&gt; with that?&lt;&#x2F;p&gt;
&lt;p&gt;That’s how most studios ended up buying a single package and decided to fill the holes by developing in-house tools. This task was furthermore eased with the developers not having to worry about implementing math, geometric and other complex libraries. It was already all there, available directly within the APIs built-in into the softwares. Developers could focus on getting their tools to work within their tight deadlines.&lt;&#x2F;p&gt;
&lt;p&gt;But that’s also where the bottleneck lies. Using those APIs meant that all the code being written would be gravitating around a proprietary software, creating &lt;em&gt;deep&lt;&#x2F;em&gt; dependencies with it.&lt;&#x2F;p&gt;
&lt;p&gt;Once a studio has invested years in developing tools, workflows, and a pipeline for a specific package, it becomes difficult to change ship. They’re stuck with it, for the best and for the worst.&lt;&#x2F;p&gt;
&lt;p&gt;In this context, there’s no point for a studio to move from one package to another, especially if it is to end up in the same situation a few years later. Since Maya has always been on top of the film industry market, there was no hope for Softimage to gain much more seats from the large studios.&lt;&#x2F;p&gt;
&lt;p&gt;Hence the decision of Autodesk to discontinue it.&lt;&#x2F;p&gt;
&lt;p&gt;That’s life but I can’t help thinking that they’ve planned it all since the begin because it kinda make sense from a business point of view: buy Softimage to get rid of the competition, move its features to Maya who’s leading the market, and kiss goodbye to &lt;em&gt;everyone&lt;&#x2F;em&gt; relying on it for their living because Autodesk can’t afford maintaining its development.&lt;&#x2F;p&gt;
&lt;p&gt;Nice hypocrital move after repeating years after years that they wouldn’t kill it.&lt;&#x2F;p&gt;
&lt;h2&gt;The Modularization Has Already Started&lt;&#x2F;h2&gt;
&lt;p&gt;Some studios grew tired of having their invested development being so closely tied to the &lt;em&gt;unknown&lt;&#x2F;em&gt; future of a software that they didn’t own.&lt;&#x2F;p&gt;
&lt;p&gt;They started to cut their bounds and to regain some control by creating their own file formats or using standardized ones. The main idea being to store their data in a generic way to make it easily transferable onto any software. The native software’s file formats were only to be used by the artists to save their work in progress before publishing.&lt;&#x2F;p&gt;
&lt;p&gt;With this in place, it was then possible to split the pipeline into logical chunks. Each department could work on the software(s) of their choice as long as they could output the data in the expected format. I’ve even seen 3 different softwares being used in the FX department on a &lt;em&gt;same&lt;&#x2F;em&gt; show. The right tool for the right job.&lt;&#x2F;p&gt;
&lt;p&gt;The way to write tools itself has also changed to be done in a more modular and portable fashion. If you’ve spent years to develop a full-on muscle system but made it highly dependant on Maya’s API, then you’ve exposed yourself to rewrite pretty much the whole thing if you decided one day to port it onto another software. Instead, the goal here is to split the core with its gateway connecting it to the target software. Got a new software? You’ve only got to write a new gateway to plug the core in.&lt;&#x2F;p&gt;
&lt;p&gt;To take full control over the destiny of a tool, some of the largest studios went for the next level. They developped stand-alone fully-featured applications. It is a tremendous work in terms of development and maintenance but in a world of closed-source softwares this is after all the &lt;em&gt;only&lt;&#x2F;em&gt; viable solution to ensure a long-term vision with an active development focused on the specific requirements of a studio.&lt;&#x2F;p&gt;
&lt;p&gt;That was &lt;em&gt;it&lt;&#x2F;em&gt;. Some pioneers did their first steps towards independance.&lt;&#x2F;p&gt;
&lt;h2&gt;TDs Can Now Shape the Future&lt;&#x2F;h2&gt;
&lt;p&gt;Ideally each studio should have adopted a such modular approach to tools development. The trend being to implement deformers, simulation nodes, rigs and other involved developments into an in-house stand-alone nodal graph platform to then glue its content with different softwares.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, not everyone could push for that. Not until &lt;em&gt;now&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;That’s where new solutions such as &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;fabricengine.com&quot;&gt;Fabric Engine&lt;&#x2F;a&gt; come in.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;softimage-is-dead&#x2F;fabric-engine.jpg&quot; alt&#x3D;&quot;Fabric Engine logo&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Fabric Engine shines at abstracting the complexity of writing portable code. It allows any developer, including TDs, to write their tools in a stand-alone nodal graph and to painlessly make them usable in every software. Code it once, use it everywhere. All this with maximum performances out of the box.&lt;&#x2F;p&gt;
&lt;p&gt;Now, imagine the talented community of TDs and developers joining their forces to build a library of tools and making them accessible to everyone on an App Store-like platform.&lt;&#x2F;p&gt;
&lt;p&gt;Tools such as Gator, Lagoa Multiphysics, emPolygonizer, Unfold 3D, and more, would be available to everyone on every package. And ICE? Well, that’s already part of Fabric Engine 2.0.&lt;&#x2F;p&gt;
&lt;p&gt;If that library of tools managed to encompass the needs in each area of CG, then the one requirement left to buy a software would be to have a hosting environment for Fabric Engine. It would become used simply as a shell where the main selling argument would be a good UI&#x2F;UX to perform efficiently daily tasks.&lt;&#x2F;p&gt;
&lt;p&gt;With Fabric Engine also providing a set of extendable modules to assist building applications, including a powerful real-time renderer and a set of extandable Python UI widgets such as an OpenGL viewport, an animation timeline, and so on, another choice would be for the users to assemble themselves their own hosting application.&lt;&#x2F;p&gt;
&lt;p&gt;How much more powerful and dynamic would our toolset become if it was directly driven by its community, if we were all contributing to shape the future of CG softwares?&lt;&#x2F;p&gt;
&lt;p&gt;If the entire userbase started to break its dependencies with today’s softwares, Autodesk would lose the control of the market and it would be our turn to kiss &lt;em&gt;them&lt;&#x2F;em&gt; goodbye.&lt;&#x2F;p&gt;
&lt;p&gt;I’d say it’s the way to go anyways. Theorically it opens the doors to a longer-term vision and its team is made of a good bunch of passionate souls. It’s worth giving it a go, let’s just hope that Autodesk won’t buy them out.&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:38c949c2-a65d-11e7-81f1-4f570e52709c</id>
      <title>myXKCD: Painter TD</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-02-28T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/comics/painter-td" rel="alternate" type="text/html" title="myXKCD: Painter TD" />
        <category term="comics" />
        <media:thumbnail url="https://christophercrouzet.com/blog/comics/painter-td/myxkcd-painter-td.gif" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;comics&#x2F;painter-td&#x2F;myxkcd-painter-td.gif&quot; alt&#x3D;&quot;Painter TD&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;I had to work hard to get there&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;

      </content>
    </entry>
    <entry>
      <id>urn:uuid:03c419ca-a4f8-11e7-88d0-dfd7dc267f01</id>
      <title>How I Got to Work in the Visual Effects Industry</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-02-05T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/cg/how-i-got-into-vfx" rel="alternate" type="text/html" title="How I Got to Work in the Visual Effects Industry" />
        <category term="cg" />
        <media:thumbnail url="https://christophercrouzet.com/blog/cg/how-i-got-into-vfx/the-hobbit.jpg" />

      <content type="html">
        &lt;p&gt;I am not sharing this story to show-off anything whatsoever. Hell, in fact I consider career achievements as fairly meaningless in one (hu)man’s life. But if we gotta do a job every day, then it’s still better to do one we like, right? So all I’m hoping to achieve here is to bring a bit of hope to the ones still seeking for that cool job. Because I’ve been there before, and it didn’t look so promising.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;how-i-got-into-vfx&#x2F;the-hobbit.jpg&quot; alt&#x3D;&quot;An unexpected journey&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;a truly unexpected journey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;The (Failed) Beginnings&lt;&#x2F;h2&gt;
&lt;p&gt;It all started back when I was a kid with my parents often shopping at the same hypermarket. It had a shelf with many computers on display that we could try out and that’s exactly what I would do. Trying them out, &lt;em&gt;every&lt;&#x2F;em&gt; single time.&lt;&#x2F;p&gt;
&lt;p&gt;I did spend many sessions like that, trying to figure out the purpose of the files found on &lt;code&gt;C:\&lt;&#x2F;code&gt; or to beat the Solitaire and other Minesweeper games.&lt;&#x2F;p&gt;
&lt;p&gt;My interest not fading away over the years, I eventually got my first own &lt;em&gt;personal&lt;&#x2F;em&gt; computer at 14.&lt;&#x2F;p&gt;
&lt;p&gt;At that time, I was away in the beautiful backcountry of France for the summer break while my brand new computer was awaiting for me back home. But I was already proactive, I was getting ready for it.&lt;&#x2F;p&gt;
&lt;p&gt;Surrounded with cows, sheep, chickens, and the mixed smell of their excrement—which I now associate with the great feeling of being in the nature—, I read a book on how to use Windows 95 like a pro.&lt;&#x2F;p&gt;
&lt;p&gt;The computer bug, I had it. I knew that’s what I wanted to do for a living with the exception of being a developer because it looked boring.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;As for the visual effects part, I was fascinated with what I was watching on TV and knew that it was all made on computers, somehow.&lt;&#x2F;p&gt;
&lt;p&gt;Soon after finally meeting with my computer, I started to mess around with 3D softwares and immediately thought that it would be the dream job for me.&lt;&#x2F;p&gt;
&lt;p&gt;However I was way behind those guys sharing their work on Internet forums. They were good, really good. But most of them didn’t have any job, so how would I?&lt;&#x2F;p&gt;
&lt;p&gt;I decided to not give up just yet. After graduating from high school, I applied for a 3D school in Paris. I travelled there, took part to the entry test, and... failed it. It wasn’t even a school with much recognition. No, it was a very accessible one, an easy bet. And I had failed its entry test nonetheless...&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;I felt miserable&lt;&#x2F;strong&gt;. At this point, I thought it was game over.&lt;&#x2F;p&gt;
&lt;h2&gt;The Spark&lt;&#x2F;h2&gt;
&lt;p&gt;So I went for the kind of job that you do when you don’t know what else to do with your life. It was so &lt;em&gt;terrible&lt;&#x2F;em&gt; that it was almost scary. I was wasting my time, I was getting more stupid by the day, everything seemed to be pointless and I was being dragged down.&lt;&#x2F;p&gt;
&lt;p&gt;But it was &lt;em&gt;exactly&lt;&#x2F;em&gt; what I needed.&lt;&#x2F;p&gt;
&lt;p&gt;It opened my eyes, it gave me a kick in the butt. I didn’t want to spend the rest of my life like this. I didn’t want to give up on my objective without even fighting for it.&lt;&#x2F;p&gt;
&lt;p&gt;I got back into teaching myself some more modelling, texturing and rendering until the day where I stumbled upon an article on the French online magazine &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.3dvf.com&quot;&gt;3DVF&lt;&#x2F;a&gt;. It was an interview of the makers of &lt;cite&gt;The Magic Roundabout&lt;&#x2F;cite&gt; movie and I knew one of those guys: Frédéric Bonometti.&lt;&#x2F;p&gt;
&lt;p&gt;6 years earlier, he very kindly taught me by email how to animate a bouncing ball and we did lose contact after that. But he was now an animation and rigging supervisor working back in my hometown Marseille!&lt;&#x2F;p&gt;
&lt;p&gt;I emailed him and he remembered me. As a true passionate, he once again did a very kind gesture, this time by inviting me to his work to give me a sneak peek in person of the behind the scenes.&lt;&#x2F;p&gt;
&lt;p&gt;I couldn’t refuse that opportunity. The moment when he showed me how he could move a cartoony dog around with the controllers that he created was probably the most fascinating thing that I’ve ever seen back then. I finally found my vocation, I wanted to become a character rigger!&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;how-i-got-into-vfx&#x2F;dog-rig.jpg&quot; alt&#x3D;&quot;Dog rig&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;After a couple of weeks spent home trying to redo what I just witnessed, Frédéric sent me a rigging exercise that I quickly resolved.&lt;&#x2F;p&gt;
&lt;p&gt;Without knowing it, it was an entry test that would lead me to step into the film industry as an intern in his company Action Synthèse.&lt;&#x2F;p&gt;
&lt;p&gt;I was 19. I had no diplomas nor any experience to help me landing a job in the industry. I already almost gave up on pursuing this carreer because of the difficulty to get in. And I didn’t know if such an opportunity would ever show up again. This was my chance, my one shot. I had to give it all, I was utterly motivated!&lt;&#x2F;p&gt;
&lt;h2&gt;The Breakout&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;how-i-got-into-vfx&#x2F;work-desk.jpg&quot; alt&#x3D;&quot;Work desk&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;CRT monitors, you said?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Breaking one of their computers during my first week there didn’t stop them from all being a truly awesome bunch of people with me.&lt;&#x2F;p&gt;
&lt;p&gt;Unlike many companies that are exploiting their interns, they made it clear since the beginning that I was there solely to &lt;em&gt;learn&lt;&#x2F;em&gt;, not to work on their projects for free. They kept their words.&lt;&#x2F;p&gt;
&lt;p&gt;Driven by their passion and their good will, they shared all their knowledge with me. Always with a smile, a great attitude and encouraging words, while never expecting anything back from me. I learned a &lt;em&gt;lot&lt;&#x2F;em&gt;. They became my best friends too.&lt;&#x2F;p&gt;
&lt;p&gt;Still today, I keep thinking that I have no idea what I would be doing if I hadn’t met Frédéric and those guys. I’m feeling immensely grateful and &lt;em&gt;lucky&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;But I also worked &lt;strong&gt;hard&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I’m never satisfied. I need to continuously extend my knowledge. I always question the status quo—especially regarding my own work. I need to go beyond what’s already existing, I want to improve things, I want to keep exploring and experimenting new ideas.&lt;&#x2F;p&gt;
&lt;p&gt;In other words, I &lt;em&gt;need&lt;&#x2F;em&gt; challenge. And if I don’t know how to tackle one of them, that’s when the fun starts. I’ll research how to get there, learning tons of interesting satellite concepts in the process. A bit like when you can’t help yourself clicking on every link found on Wikipedia to answer a question that you already forgot.&lt;&#x2F;p&gt;
&lt;p&gt;There’s no need for a school to learn or to reach your objectives. A big pool of resources like the Internet and knowledgeable people to help you getting unstuck are plenty enough.&lt;&#x2F;p&gt;
&lt;p&gt;This whole attitude eventually got me hired at Action Synthèse as a character rigger and developer for a TV series.&lt;&#x2F;p&gt;
&lt;p&gt;After a few years spent working there plus a short experience in Paris, and despite of all my hard work, I still felt unconfident about my skill set.&lt;&#x2F;p&gt;
&lt;p&gt;I couldn’t help thinking of the cutting edge companies out there that were redefining the standards, pushing the limits further away, bringing always more life into each pixel. They &lt;em&gt;had&lt;&#x2F;em&gt; to be filled with godlike superhumans. It had to be out of reach for someone like me.&lt;&#x2F;p&gt;
&lt;p&gt;But I wanted to travel a bit and decided to try my luck anyways.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks to our great education system in France, most of us still can’t converse in English even after 7 years of learning at school. I was one of them when I landed my first job interview ever. It was on the phone with &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.animallogic.com&quot;&gt;Animal Logic&lt;&#x2F;a&gt;, at 6 a.m.—time difference oblige—, I was sick as hell, and it was of course in English.&lt;&#x2F;p&gt;
&lt;p&gt;I’ve never been as stressed out as I was that time but it worked out somehow and that’s how I ended up working on the other side of the world, in Sydney, Australia.&lt;&#x2F;p&gt;
&lt;p&gt;Less than 2 years later, after finally building up a bit of confidence, I decided to go once again for the impossible and it went pretty well too. At 26 years old, I was fulfilling my initial objective to work with those guys who made movies such as &lt;cite&gt;Lord of the Rings&lt;&#x2F;cite&gt;, &lt;cite&gt;King Kong&lt;&#x2F;cite&gt;, and &lt;cite&gt;Avatar&lt;&#x2F;cite&gt; possible. I was working at &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.wetafx.co.nz&quot;&gt;Weta Digital&lt;&#x2F;a&gt; in Wellington, New Zealand.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;cg&#x2F;how-i-got-into-vfx&#x2F;weta-digital-creatures-team.jpg&quot; alt&#x3D;&quot;Weta Digital creatures team&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;where is Charlie?&lt;&#x2F;span&gt;&lt;span&gt;credit: &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.julianbutler.com&quot;&gt;Julian Butler&lt;&#x2F;a&gt; (with permission)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;I’m not so much of an artist myself but I can now proudly say that I’ve started from nothing to end up working with the most talented artists in the industry. Something that &lt;em&gt;no one&lt;&#x2F;em&gt;—especially not myself—would have thought possible only a few years before, when I was lost.&lt;&#x2F;p&gt;
&lt;p&gt;My grandfather often told me when I was a kid: &lt;q&gt;when you have something in the head, you don’t have it in the ass&lt;&#x2F;q&gt;. Another way of saying it is that I am stubborn and persistent. I wanted to work on those blockbusters, so I grabbed my chance when it came up and kept going from there.&lt;&#x2F;p&gt;
&lt;p&gt;I’m not any more special than you. At the end of the day we’re all good at doing what we enjoy doing. Your dream job is just one step away from there. Motivation, persistence, and patience are all you need.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Do it&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;Funnily enough, programming is now one of my top interest.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:27b6c344-a504-11e7-9949-2f7127ce2658</id>
      <title>How Not to Climb the Volcano Acatenango, Guatemala</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2014-01-22T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/travel/acatenango-climb" rel="alternate" type="text/html" title="How Not to Climb the Volcano Acatenango, Guatemala" />
        <category term="travel" />
        <media:thumbnail url="https://christophercrouzet.com/blog/travel/acatenango-climb/missing-summit-photo.jpg" />

      <content type="html">
        &lt;p&gt;I &lt;em&gt;love&lt;&#x2F;em&gt; physical and mental challenges. I knew that hiking Acatenango’s 3,976 m summit would be difficult but the more different persons would tell me how hard it actually was and how cold it got, the more I was getting all fired up. If they could do it, there’s no reasons why I wouldnt’t.&lt;&#x2F;p&gt;
&lt;p&gt;But a sprained ankle and other sickness convinced me to hold on for a bit. It sounded wise until I got a cold with a sore throat but couldn’t wait any longer. My flight was the day after, it was now or never.&lt;&#x2F;p&gt;
&lt;p&gt;My Italian friend climbed it the week before and recommended me to directly contact the guide without going through the usual agencies. Those agencies are the ones pocketing most of the money, while the guides doing the actual hard work get the leftovers.&lt;&#x2F;p&gt;
&lt;p&gt;My Spanish not allowing it yet, the receptionist at my hostel kindly accepted to give me a hand with the phone call. All got arranged quickly and the guy would come picking me up that same night at 11 p.m.&lt;&#x2F;p&gt;
&lt;p&gt;I had the whole day to prepare myself and started it with getting myself a good lunch. A bit of proteins here but mainly loads of carbohydrates there to build a reserve of energy that I could use during the climb. Then, I had to buy the mandatory banana bread from a shop in Antigua that I could use for a boost of energy during the effort. And finally I planned to buy the chocolate to go with it, as well as to drink some rice with milk for dinner, but first... a small nap. I would see the rest later.&lt;&#x2F;p&gt;
&lt;p&gt;I got into bed a 2.30 p.m. and got awaken an hour later by the receptionist. The guide was here and was waiting for me. The result of a misunderstanding—the receptionist herself also seemed surprised.&lt;&#x2F;p&gt;
&lt;p&gt;I packed my bag in a lightning strike and started to follow my guide, Jaime. He had no car so he did all the way from Acatenango by chicken bus, especially for me. I followed him to the bus station of Antigua and we arrived 1 hour later in his small village, at the foot of the volcano.&lt;&#x2F;p&gt;
&lt;p&gt;The sun was setting and it started to get cold. He offered me a spare room with a bed and a small dinner to eat before sleeping for a few hours.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;acatenango-climb&#x2F;resting-room.jpg&quot; alt&#x3D;&quot;The resting room&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;With the night, the dogs seemed to have become different beasts. I had one dog growling at me earlier, but now they were all constantly growling at each others to defend their territory and sometimes got into fights. One of them sounded especially violent and scary.&lt;&#x2F;p&gt;
&lt;p&gt;Despite of wearing 3 layers of merino, a down jacket and being covered deep under 2 blankets, I was &lt;em&gt;shaking&lt;&#x2F;em&gt; from the cold out there. I wanted to be on the move already to get warmer.&lt;&#x2F;p&gt;
&lt;p&gt;At 10 p.m., the roasters from all over the village decided to announce the... the... well, the &lt;em&gt;I don’t know what&lt;&#x2F;em&gt;. There was still 8 hours to go before the sunrise. But whatever they announced, they did it well. With a good endurance.&lt;&#x2F;p&gt;
&lt;p&gt;Needless to say that I didn’t sleep much when I heard Jaime getting ready at 1.15 a.m. But so was I, ready and more excited than ever! I just needed to walk out like a ninja to not raise any attention from the dogs.&lt;&#x2F;p&gt;
&lt;p&gt;No warm up, the path directly started with a fair slope which was mainly made of sand. Like at the beach, each step required a good amount of effort for little reward. I tried to focus on becoming more efficient. Small steps without sliding backwards. But the technique of the guide made the difference and he slowly but surely distanced me.&lt;&#x2F;p&gt;
&lt;p&gt;In such low temperatures, wearing wet clothes is forbidden to prevent possible future hypotermias when cooling down. But I was already sweating from the effort and decided after 15 min to have a quick first stop to remove my down jacket.&lt;&#x2F;p&gt;
&lt;p&gt;After the sandy ground, we were welcomed with a slippery one. Here again he distanced me with his daily experience of the field, but thanks to my energetic brute force I would quickly catch up with him when some charitable souls were once kind enough to plant some pieces of wood to serve as stairs.&lt;&#x2F;p&gt;
&lt;p&gt;We were back on a more practicable—but even steeper—terrain when we reached the first checkpoint with a bench to sit. He slightly slowed down to check if I would head for it. But no, I already stopped once and it was enough. This hike was my come back challenge after almost 2 years of not doing any serious sport, and I wanted to take it up like a boss.&lt;&#x2F;p&gt;
&lt;p&gt;So we kept going and he took the lead by sitting on the next bench, even though I wasn’t behind this time. I got proud. He stopped. Ok, he was probably being thoughtful of me, but it was good for the moral nonetheless. Even more so since my cardio, my breathing and my legs were all doing pretty well.&lt;&#x2F;p&gt;
&lt;p&gt;A few stops and 3 hours of walk later, I was still at the top of my form. I even found a way to heat my hands by leaving them in my pockets, against my boiling hot legs.&lt;&#x2F;p&gt;
&lt;p&gt;But then, I suddenly started to feel a cramp in each leg.&lt;&#x2F;p&gt;
&lt;p&gt;I knew that it would most probably only get worse but I didn’t say anything and kept going anyways. Two years earlier I ran 28 km with proper cramps during the last 15 km on top of a painful knee—who ended up preventing me from running ever since then.&lt;&#x2F;p&gt;
&lt;p&gt;I thought that if I could run in these conditions, then I could certainly walk with a couple of cramps. And I was sure that it wouldn’t have stopped &lt;a href&#x3D;&quot;http:&#x2F;&#x2F;www.mikehorn.com&quot;&gt;Mike Horn&lt;&#x2F;a&gt; from continuing either.&lt;&#x2F;p&gt;
&lt;p&gt;Despite me drinking even more water, eating bananas, giving myself deep tissue massages and stretching out, it quickly reached the point where both of my legs were literally tetanised. I couldn’t stand up anymore. So I sat, put back my down jacket, fitted some toe warmers in my shoes to help the blood flowing, kept drinking, got a lolipop for a quick energy rush, and was hoping to wait a bit to see if it would get any better.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; But time was playing against us.&lt;&#x2F;p&gt;
&lt;p&gt;Our headlamps were reflecting on a sea of ice crystals dropped off on the ground. The temperature was &lt;em&gt;glacial&lt;&#x2F;em&gt;. Staying put for only five minutes was enough to get us frozen to the bones. Staying longer would have meant taking the risk to get into an hypotermic state. And so we had to give up on the summit, only 1 hour away, and turned around. I hated it but it was the only wise decision to take.&lt;&#x2F;p&gt;
&lt;p&gt;I tried to leave aside my frustration and focused on moving the chopsticks that I had in place of my legs. It became a question of survival.&lt;&#x2F;p&gt;
&lt;p&gt;It just happens that despite of my level of fitness and my relatively healthy style of life, I’ve always had those kind of muscular problems since I’m a teenager. I love pushing my limits and it’s hard to accept when my body sometimes decide to fail so easily where everyone else wouldn’t have had any problem in an identical scenario. After having worked hard towards resolving these issues without any success, I’ve had to come to the evidence that we’re not equal towards our body.&lt;&#x2F;p&gt;
&lt;p&gt;But as a friend told me, for some it’s indeed way harder than for others, but it doesn’t prevent disabled persons to realize apparently impossible challenges.&lt;&#x2F;p&gt;
&lt;p&gt;All this to say that I’m quite shameful to share this experience with you, but I want to take you as witnesses here: one day, things &lt;em&gt;will&lt;&#x2F;em&gt; be different.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;acatenango-climb&#x2F;missing-summit-photo.jpg&quot; alt&#x3D;&quot;Missing summit photo&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;I suffer from Raynaud’s syndrome, which makes it even more vital for me to not let my fingers and toes drop in temperature.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:876484ea-a511-11e7-8380-7fa82ce19af6</id>
      <title>Guatemala: A Journey Full of Journeys</title>
      <updated>2021-10-09T13:00:00Z</updated>

      <published>2014-01-05T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/travel/guatemala" rel="alternate" type="text/html" title="Guatemala: A Journey Full of Journeys" />
        <category term="travel" />
        <media:thumbnail url="https://christophercrouzet.com/blog/travel/guatemala/in-the-bus.jpg" />

      <content type="html">
        &lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;in-the-bus.jpg&quot; alt&#x3D;&quot;In the bus&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Proudly, I can say that I’ve started my Latin American trip without any plan and without doing any research neither on the Internet nor in any guide. My whole idea was to show up in a place, meet with the locals and with other travellers, and decide what to do on the spot.&lt;&#x2F;p&gt;
&lt;p&gt;Less proudly, communicating with the locals turned out being harder than I hoped for—reaching level 8 in Spanish at &lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.duolingo.com&quot;&gt;Duolinguo&lt;&#x2F;a&gt; and adding a touch of French-nish didn’t cut it.&lt;&#x2F;p&gt;
&lt;h2&gt;Antigua&lt;&#x2F;h2&gt;
&lt;p&gt;I needed to start my trip from somewhere. That was an easy one, an Italian friend decided to hit Guatemala for 2 weeks during Christmas, so would I. Antigua was the meeting point.&lt;&#x2F;p&gt;
&lt;h3&gt;17 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;The plane flew over Guatemala City just when the sun was setting. Quite a beautiful introduction to the volcanoes scenery all around. But it also meant that I had to reach Antigua by night.&lt;&#x2F;p&gt;
&lt;p&gt;I still remember the day when I was in the plane in direction to Australia for the first time. It was on the 9th of February 2009. There was a French girl sitting next to me who was returning to Australia. I’m not sure what was her reason but she did her best to try to freak me out. &lt;q&gt;Beware of the deadly spiders, they like to hide under the toilet seats because they fancy humidity&lt;&#x2F;q&gt;. &lt;q&gt;They also like hiding in the grass so don’t walk barefoot&lt;&#x2F;q&gt;. &lt;q&gt;Always double–check your bed and your shoes before getting in&lt;&#x2F;q&gt;. &lt;q&gt;There’s ton of shark attacks happening, don’t swim&lt;&#x2F;q&gt;. &lt;q&gt;I’m not even talking about the many lethal jellyfishes&lt;&#x2F;q&gt;. And the list went on and on.&lt;&#x2F;p&gt;
&lt;p&gt;Many couldn’t help themselves at telling me similar stuff about Guatemala but regarding violence this time. &lt;q&gt;Don’t travel in chicken buses&lt;&#x2F;q&gt;—the local transport system—and &lt;q&gt;Don’t go out by night&lt;&#x2F;q&gt; were in the top 2 of the list.&lt;&#x2F;p&gt;
&lt;p&gt;To leave them the benefit of the doubt, and because I was exhausted after this long travel, I thought that I’d play it safe for my first move. As I was about to grab a minibus to Antigua for 80 quetzals—the local currency—, which is about 8 € or $10 USD, a Dutch girl sitting next to me in the plane offered me to jump in her private shuttle for free. Her mum already organized and paid for it in advance because she was scared that her daughter would take a chicken bus instead. It made me a bit uncomfortable not to pay anything and so I decided to give her 100 quetzals to thank her. I didn’t have any smaller change anyways, and she didn’t neither.&lt;&#x2F;p&gt;
&lt;p&gt;When the driver dropped me after leaving the girl at her hostel, he stared at me insistently. He raised his hand, the palm slightly facing up. I hastened myself to grab it and shook it while thanking him with a warm &lt;q&gt;¡Muchas gracias!&lt;&#x2F;q&gt;. For some reasons it didn’t seem to be enough for him to stop staring at me weirdly.&lt;&#x2F;p&gt;
&lt;p&gt;Only then I understood that he was expecting a small tip. Without any idea of the actual value of the currency, I gave him 50 quetzals. Only later I realized that with an average Guatemalan wage of 80 quetzals per day, I’ve been more than kind. But more importantly, I’ve beautifully played the role of a &lt;em&gt;naive&lt;&#x2F;em&gt; tourist and this had to change.&lt;&#x2F;p&gt;
&lt;p&gt;Before the conclusion of this fiasco, I agreed on a dinner with the Dutch girl to a place recommended by our shuttle driver. I decided to pick her up at her hostel to make sure that she wouldn’t get into troubles because of the &lt;q&gt;don’t get out by night&lt;&#x2F;q&gt; thing.&lt;&#x2F;p&gt;
&lt;p&gt;The receptionist at my hostel kindly gave me a map and drawed the direction. It was only a few blocks away and would be quite straightforward if only she had pointed me to the &lt;em&gt;correct&lt;&#x2F;em&gt; destination.&lt;&#x2F;p&gt;
&lt;p&gt;Instead I’ve been turning around for 30 min and ended up getting lost in a totally different area with either non–existant street names or names that wouldn’t match with the ones on the map.&lt;&#x2F;p&gt;
&lt;p&gt;This first exploration of Antigua by night left me sceptical. There was no much light in the streets, making them quite dark. Not many pedestrians were walking neither. At an almost regular interval, cars were driving by at a much reduced speed because of the paved road. And all those cars had their windows covered with a black tint to the windows.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;All this plus some extra loud firecrackers banging here and there in the streets—due to pre–Christmas celebrations that sounded more like Bagdad to me—left me with a slightly sketchy feeling.&lt;&#x2F;p&gt;
&lt;p&gt;I finally managed to find my way back to my hostel, looked myself at the address of her hostel on Internet, and made it there in no time.&lt;&#x2F;p&gt;
&lt;h3&gt;20 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;I met a lovely French couple the day before and signed in with them for a trip to the volcano Pacaya. The goal was to get to the summit for sunset.&lt;&#x2F;p&gt;
&lt;p&gt;We left at around 2 p.m., drove for about 1.5 hour and started our gentle climb. Because we were allowed to stay up there for only a short period of time, our guide made us climb slowly to make sure that we would arrive at the right moment.&lt;&#x2F;p&gt;
&lt;p&gt;Despite having our entire group refusing to climb with one of the many horses offered at the start, a few of them followed us anyways. Just in case someone changed their mind or wanted to give up. You know, a bit like a broom wagon.&lt;&#x2F;p&gt;
&lt;p&gt;When the last ascent of the summit was at our reach, we had to stop there. Climbing to the top was forbidden due to the volcano being currently active. Indeed it was continuously smoking and rumbling every 3–4 minutes.&lt;&#x2F;p&gt;
&lt;p&gt;So we just sat down there, with a nice view over other volcanoes around, and waited for the sunset. It was &lt;em&gt;beautiful&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11762321286&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;volcano-pacaya-guatemala.jpg&quot; alt&#x3D;&quot;Sunset over the Volcano Pacaya, Guatemala&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;But it wasn’t enough for our guide. He got disappointed because he hoped that we would see the red color of the lava from the volcano vent when the dark set, which didn’t happen.&lt;&#x2F;p&gt;
&lt;p&gt;We had to go back down in the dark, with a few headlamps helping with the bumpy ground. I didn’t feel the need for turning on mine, until I figured out that it wouldn’t be the best timing to injure myself just before my Italian friend would arrive in town.&lt;&#x2F;p&gt;
&lt;p&gt;So I turned on my light for the time of a very small run downhill, for the fun of it, and that’s when a deep &lt;em&gt;crack&lt;&#x2F;em&gt; sound rang out from my right ankle. &lt;em&gt;Done&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When we almost returned to our starting point, our guide stopped us and pointed back at the volcano. It was red. We could see rocks erupting from it.&lt;&#x2F;p&gt;
&lt;h3&gt;22 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;So far my frustration of not speaking Spanish was already quite high. I mean, what’s the point of going into a country if we can’t even speak with the locals? But something that I’ve never been looking forward to is heading back to any sort of school. This has just never been a good fit for me. I’m more of the type to learn things by myself. I hoped to catch up over time.&lt;&#x2F;p&gt;
&lt;p&gt;I was fortunate enough to have been assisted in this regard by the French couple for the previous few days, who both have a great level of Spanish, and I would now be able to rely on my Italian friend who just arrived and who is perfectly fluent.&lt;&#x2F;p&gt;
&lt;h2&gt;San Pedro La Laguna&lt;&#x2F;h2&gt;
&lt;h3&gt;24 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;We booked the day before one of the only place available to us on HostelWorld. Not a good idea. The hostel was rather expensive for the average price and it didn’t rhyme with better quality.&lt;&#x2F;p&gt;
&lt;p&gt;When walking in the streets, we’ve asked some guys staying at a Spanish school if they knew any better accomodation around. We’ve been pointed towards one named Hotel Peneleu.&lt;&#x2F;p&gt;
&lt;p&gt;We went to check it out. It was way cheaper, way cleaner, and the view was quite something so we booked it for the next night. Note to self: do not solely rely on the Internet.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780535216&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;sunrise-san-pedro-la-laguna-guatemala.jpg&quot; alt&#x3D;&quot;Sunrise over San Pedro La Laguna&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;On the way to that hostel, we’ve found a group of kids playing in the street. They were having a lot of fun, and they doubled up in energy and excitement when we’ve asked them if we could take them in photo. Such a great moment. They loved the photos so much that they went on for another round.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780030653&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;kids-playing.jpg&quot; alt&#x3D;&quot;Kids playing in the street&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then their mum showed up to give them some piece of advices by telling them to accept being taken in photos only in exchange of money.&lt;&#x2F;p&gt;
&lt;p&gt;The time was for Christmas celebrations. With many selling weed and mushrooms in the streets, and all those gringos—foreigners—buying them only to end up wandering in the streets completely stoned, we thought that there would be some sort of party happening.&lt;&#x2F;p&gt;
&lt;p&gt;After scanning the streets, we ended up stumbling upon where all the locals were gathering: on the place in front of the church. Not really our cup of tea.&lt;&#x2F;p&gt;
&lt;p&gt;So we kept searching and we found a nice hostel where this time all the tourists were regrouped. Not really what we were looking for neither but in lack of a better choice, we stayed.&lt;&#x2F;p&gt;
&lt;p&gt;The music was there even though not in its best form. The general dynamism wasn’t at its best neither. And to be a real party pooper, I chose to be wise and not to dance much due to my sprained ankle.&lt;&#x2F;p&gt;
&lt;p&gt;But faith decided otherwise. The DJ set a track of Daft Punk, &lt;cite&gt;Get Lucky&lt;&#x2F;cite&gt;. If there’s one thing I like to dance on, it has to be Daft Punk tracks. And so I went full on in the centre of the dance floor, battling with other guys.&lt;&#x2F;p&gt;
&lt;p&gt;I was on fire. So much that it took me a few tracks to realize that I only manage to worsen the condition of my ankle.&lt;&#x2F;p&gt;
&lt;h3&gt;25 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;On the other side of the lake is the Indian Nose. A small mountain whose silouhette looks like a profile of looking upwards. Our destination for today. We thought that we would try it on our own without going through a tour or a guide.&lt;&#x2F;p&gt;
&lt;p&gt;So we first jumped in the boat, direction San Marcos. From there we had a small lunch in one of the very few places available, and we looked for a tuk–tuk. We knew that we could drive up to a place named Santa Clara, and from there only 40 min of climb would be separating us from the viewpoint.&lt;&#x2F;p&gt;
&lt;p&gt;We looked for a tuk—tuk, tried to negotiate the price to what we’ve previously been told by the restaurant owner, and... it didn’t work out. The old man was camping on his position and so were we. So we took a ride to San Pablo instead in the hope to find better deals over there.&lt;&#x2F;p&gt;
&lt;p&gt;When arrived, the negotiation started again with the old man but didn’t go any further until a younger and more intrepid tuk–tuk driver accosted us with his vehicle. He got us a better deal, we changed ship.&lt;&#x2F;p&gt;
&lt;p&gt;That driver was definitely more sporty too. The not so stable–looking 3–wheeled machine and the bumpy, steep and curvy mountain road seemed to be like just another video game to him. And for us too, it was fun! Especially since we made it in one piece to our destination.&lt;&#x2F;p&gt;
&lt;p&gt;There a guy ran out of his house to stop us. He couldn’t help himself from smiling when he was talking, like if he could smell the good deal. He told us that he was a sort of custom for the Indian Nose land, that we had to pay a fee to get there, and that he would have to come with us. We’ve heard about the 150 quetzals fee so it sounded fine.&lt;&#x2F;p&gt;
&lt;p&gt;We went on with the plan, started a walk towards a first lookout, and from there he tried to convince us to not go to the summit because it wouldn’t be worth it. The view would be the same. We insisted to keep going. He left us and finished walking the way up with our tuk—tuk driver who felt like joining us.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11779774735&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;tuk-tuk-driver.jpg&quot; alt&#x3D;&quot;Out tuk-tuk driver&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;our tuk–tuk driver, a cool dude&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Arrived at the summit, there was a group of other gringos waiting for their guides to come back with some extra gear. Their tour included a camping overnight. And there were also some locals trying to get more money from us. I could barely understand half of what they were saying, which was enough to make me unhappy about the situation. But I couldn’t reply to them, my friend had to do all the job.&lt;&#x2F;p&gt;
&lt;p&gt;It wasn’t so much a matter of money, they were asking only 25 quetzals per person, or around $3 USD. It was more a question of principles. We already paid an entry fee and didn’t want to pay every single person asking us for money. Especially without any document supporting their arguments. The situation got worse, they became more agressive and threatened us to call the police while letting us know that we would run into troubles on the way back. Or something like that.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11779769725&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;god-rays.jpg&quot; alt&#x3D;&quot;God rays&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Our tuk–tuk driver started to wonder why the hell he followed us up there and begged us to pay the fee to avoid any problem. But instead we decided to wait until the guides of the other group came back from their extra climb. That’s how we learned that we actually were on a private land. A different one from the previous lookout. And that yes, they require visitors to pay. Well, only gringos have to pay, not the locals.&lt;&#x2F;p&gt;
&lt;p&gt;After a quick evaluation between taking a risk—as low as it might have been, if we consider that they were hopefully bluffing—of losing our gear and whatnot versus spending $3 USD, we opted for the latter.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; And here we were, in for another epic tuk—tuk ride. Downhill this time.&lt;&#x2F;p&gt;
&lt;p&gt;Actually the ride wasn’t sensational enough to finish the day. The driver—should I say &lt;em&gt;pilot&lt;&#x2F;em&gt;?—did a quick stop in San Pablo to pick up a friend of his and to let him drive all the way to San Pedro. He was around 8 years old. Eight &lt;em&gt;freakin&lt;&#x2F;em&gt; years old! At his age, I wasn’t even playing at &lt;cite&gt;Colin McRae Rally&lt;&#x2F;cite&gt; yet.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;young-driver.jpg&quot; alt&#x3D;&quot;A way too young tuk-tuk driver practising&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;a way too young tuk-tuk driver practising&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Semuc Champey&lt;&#x2F;h2&gt;
&lt;h3&gt;26 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;With that previous experience, I thought that we would be immunized against any sort of ride. I was wrong.&lt;&#x2F;p&gt;
&lt;p&gt;It all started normally. We got in the shuttle at 7 a.m., direction Antigua for a lunch stop. We took the opportunity to rush back towards our favorite lunch place, where not many gringos dare going to eat: the market place! Most cheap, &lt;em&gt;authentic&lt;&#x2F;em&gt; and possibly yummy food!&lt;&#x2F;p&gt;
&lt;p&gt;We couldn’t pass by Antigua without stopping to our second addiction in town: the banana bread shop. The delicious flavour of bananas can already be smelled from the street. When you’re around, there’s no stopping the urge of buying some. &lt;strong&gt;Best. Banana bread. Ever!&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Of course, they did things well by being located next door from the chocolate museum. A banana bread sandwich with a row of 80% salty chocolate equals to pure awesomeness.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;banana-bread-with-chocolate.jpg&quot; alt&#x3D;&quot;Banana bread with chocolate&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Back on track! Two shuttles left at 2 p.m. in direction of Semuc Champey. Somewhere half–way our shuttle decided to stop and turn around. The other one had a mechanical issue so we ran to the rescue. And ran again after picking up some cooling liquid.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780001583&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;tire-middle-road.jpg&quot; alt&#x3D;&quot;Tire in the middle of the road&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;killing some time&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Apparently every Guatemalan driver is also a mechanic. I found the front seats unmounted to allow them to dig through the layers of the engine. It took some time but eventually they got it sorted and we were back on track.&lt;&#x2F;p&gt;
&lt;p&gt;At 1 a.m. we arrived at a place named Lanquín and for some yet unknown reasons we changed vehicles. From almost comfortable and warm seats, we’ve been thrown into the back of a truck with no rooftop or whatsoever and no other way to sit than a single thin wood board. But sitting wasn’t our best bet.&lt;&#x2F;p&gt;
&lt;p&gt;We immediately rushed into the jungle in a fashion very similar to water rafting. Sitting down on that board would have only resulted in a good petrissage of our bum. The one viable option was to stand up and and hold on tight to the metallic armature surrounding the truck. It was either that or being ejected out in the jungle. And it started to rain. Lightly but consistently.&lt;&#x2F;p&gt;
&lt;p&gt;An hour later, we finally made it to our hostel and went on to sleep straight into our huts. They had a rooftop but no walls, it was a bit like sleeping in the outdoors. It felt great!&lt;&#x2F;p&gt;
&lt;h3&gt;27 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;After a short night, we went on with the main reason of our stay: a tour going through a viewpoint, the natural pools, and the exploration of a cave with a candle.&lt;&#x2F;p&gt;
&lt;p&gt;But before heading for the tour, our guide has been pushing hard for everyone to order their lunch meal straight away so they would be ready for when we’d come back. We were not really attracted by the menu and the prices but since we had no idea of the surroundings—arriving at 2 a.m. didn’t help—, and since he’s been so insistent, we followed the mass and ordered ours.&lt;&#x2F;p&gt;
&lt;p&gt;Getting to the viewpoint required a little climb. It was written &lt;em&gt;difícil&lt;&#x2F;em&gt;, but it wasn’t. What’s sure is that the view was worth it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780523626&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;semuc-champey-guatemala.jpg&quot; alt&#x3D;&quot;Semuc Champey, Guatemala&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;And then onto the river and the pools. Not carrying any swimsuit with me, I had to borrow one from a local. It was actually a thick sport short. The water was clear, and us, gringos, were mostly invading the place. I felt bad for the locals living around there.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780516846&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;semuc-champey-river-guatemala.jpg&quot; alt&#x3D;&quot;River passing through Semuc Champey, Guatemala&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This and a serie of small activities in the pools took us the entire morning. When heading back to the place we were staying at, which is also where the tour operates, we realized that there were actually plenty of locals selling real, authentic looking, food.&lt;&#x2F;p&gt;
&lt;p&gt;Anger grew in me. I already didn’t like this guide for some reasons but now I truly had a grudge against him. He deliberately hid this information from us. Instead of distributing our money to the poor locals in the need, we had to spend it in his stupid company. But now we knew.&lt;&#x2F;p&gt;
&lt;p&gt;The lunch with its ridiculously terrible &lt;code&gt;(quantity × quality) &#x2F; price&lt;&#x2F;code&gt; ratio finished, we had to get rid of all our gear and clothes. Only the swimming suit and the flip–flops had to remain. Flip–what? Alright, my minimalist gear didn’t include any of these. I was the only one. Barefoot it would be then.&lt;&#x2F;p&gt;
&lt;p&gt;Here I was, walking like a penguin for about 5 minutes on a relatively painful ground. Fortunately my feet are slightly trained with a small barefoot running experience and other “shaolin-like” trainings on sharp rocks that I happened to do once because I’ve got such stupid ideas sometimes.&lt;&#x2F;p&gt;
&lt;p&gt;The barefoot community grew when they all been asked to leave their flip–flops at the entrance of the cave. There, we’ve been given a candle each. A bit like the Olympic Games torch, my goal was to get it through without it shutting off. I’ll save you from any sort of uncontrollable suspense by letting you know that I’ve miserably failed on the way back.&lt;&#x2F;p&gt;
&lt;p&gt;The cave exploration was not so interesting for its sight. It was rather ugly. The cool side had more to do with its adventurous aspect. We had to go through a few fun obstacles and endure walking or swimming in the dark knowing that at some point we’ll hit an underwater sharp rock that will cut our skin. I definitely didn’t fail that bit, it happened.&lt;&#x2F;p&gt;
&lt;h2&gt;Flores&lt;&#x2F;h2&gt;
&lt;h3&gt;28 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;Departing at 7 a.m. from Semuc Champey back to Lanquín, I couldn’t wait to add some brand new bruises by banging again the armature at the back of the truck. My wish has been fullfiled with the help of a new driver, very keen to overtake all the other trucks in his path.&lt;&#x2F;p&gt;
&lt;p&gt;Of course, it was just the begin of the journey. We jumped in a shuttle in direction to Coban where we had to pick up some new passengers. The issue? The shuttle was overbooked. No worries, I though, this is the kind of non–issue for a group of adventurous travellers anyways. Right? Except that it ended up being one.&lt;&#x2F;p&gt;
&lt;p&gt;The shuttle dropped us all at a McDonald’s—do they think that we exclusively eat in shit places or that we want to contribute to them?—and then drove away. At its return, the 3 new passengers were finally sitting in the then empty shuttle after having waited for 2 hours outside.&lt;&#x2F;p&gt;
&lt;p&gt;Coincidentally, they were a group of 1 Peruvian and 2 Guatemalans brothers that we previously met in an other shuttle from Antigua to Lanquín. We had the occasion to discuss quite a bit with them, especially when we all had to wait for the cooling system of a shuttle to be fixed during the previous trip.&lt;&#x2F;p&gt;
&lt;p&gt;My friend and I happily greeted them. As for the others, they welcomed them in their own way: &lt;q&gt;Why did they take our seats?&lt;&#x2F;q&gt;, &lt;q&gt;We were there before&lt;&#x2F;q&gt;, &lt;q&gt;We’re tourists and they’re Guatemalans&lt;&#x2F;q&gt;, and so on. Except that they were humans like us, they were tourists like us, they paid like us, and they’ve been told to seat there. A long infantile battle started.&lt;&#x2F;p&gt;
&lt;p&gt;Being the last one to enter into the shuttle, I could only see the final outcome. The 3 of them were stacked on top of each others in the most uncomfortable way possible. All this when there was an empty space on the floor at the front of the bus. I wondered how could we have lost any value of solidarity and become so selfish in our “developed” countries of gringos. So I went for that spot on the floor and gave away my seat. It made me feel pretty happy.&lt;&#x2F;p&gt;
&lt;p&gt;A few hours later, a Brazilian girl had requested a stop for an urgent need. For some reasons the driver didn’t stop where there were signs of civilization and possible toilets around but ended up pulling somewhere with not much around. So she went for it anyways and came back crying. She had been bitten by a dog while doing her thing.&lt;&#x2F;p&gt;
&lt;p&gt;I picked up my &lt;cite&gt;SAS Survival Guide Handbook&lt;&#x2F;cite&gt; and checked for any hint of what to do in this scenario. We requested for another stop next to some habitations. I told her to thouroughly wash her wound to remove any trace of saliva, then I gave her an antiseptic to apply.&lt;&#x2F;p&gt;
&lt;p&gt;On her way back from the restrooms, she called me. I asked her how deep the wound was and how it went. The bite being at the back of the leg, she couldn’t do much and asked me to apply the antiseptic. So I executed myself and did it without thinking. Then I let her know about the small probability of rabies and told her to check with a doctor within the next few days.&lt;&#x2F;p&gt;
&lt;p&gt;For the next hour, I &lt;em&gt;hated&lt;&#x2F;em&gt; myself. I’m not a doctor but I have been through a 2–day outdoor first–aid course in New Zealand the year before. I should have known better. I kept thinking of what I should have done instead. Wash my hands, not get in direct contact with the blood of the victim, clean her wound, apply a compress and a strap to avoid infection. I pretty much did it all wrong. I was fairly relaxed and collected but I simply didn’t activate any of my brain cells. How could I forget all these basic steps when it finally mattered? This was unacceptable.&lt;&#x2F;p&gt;
&lt;p&gt;I’m not even talking of the probability of contracting rabies myself by applying some antiseptic on one of my own wound after having been in contact with her blood and not having washed my hands inbetween. I guess it would have been a good lesson.&lt;&#x2F;p&gt;
&lt;p&gt;To you, Brazilian girl: even though you haven’t been so nice with my 3 friends, I sincerely hope that you’ve recovered well.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11779729525&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;bus-crossing-river.jpg&quot; alt&#x3D;&quot;Bus crossing the river&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h3&gt;29 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;Sleeping is overrated. We woke up at 2 a.m. to get into a tour that would take us to the Tikal Mayan ruins just in time for sunset. Of course the shuttle showed up 30 min late but we arrived on time and started our walk through the ruins in the dark.&lt;&#x2F;p&gt;
&lt;p&gt;Our sunset spot was a tall pyramid overlooking the jungle. Before climbing it, our guide gave us a quick briefing, asking us to respect the silence once at the top so everyone could enjoy the melody of the jungle.&lt;&#x2F;p&gt;
&lt;p&gt;It did work. Almost. Nobody was talking, only the crowd of cameras were frenetically making noises.&lt;&#x2F;p&gt;
&lt;p&gt;I sometimes wish that digital cameras had a &lt;em&gt;newbie&lt;&#x2F;em&gt; detection algorithm. Maybe they could signal to their owner things like: &lt;q&gt;No magic here, if it looks bad with your own eyes, it won’t look any better on a photo&lt;&#x2F;q&gt;, &lt;q&gt;Nope, that small bird still won’t stand out from the tree in a photo&lt;&#x2F;q&gt;, or &lt;q&gt;Don’t mistake my flash with Superman’s laser beams, I won’t light up your subject at 5 km away&lt;&#x2F;q&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;On our watch the sun was supposed to be there but the thick layer of clouds decided otherwise. It didn’t stop the howler monkeys to shine in the surrounding jungle through their impressively loud voice. Surreal atmosphere.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;mayan-ruin-tikal-guatemala.jpg&quot; alt&#x3D;&quot;Mayan ruin in Tikal, Guatemala&quot;&gt;&lt;&#x2F;span&gt;&lt;span&gt;nope, we didn’t climb that one&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Rio Dulce&lt;&#x2F;h2&gt;
&lt;h3&gt;30 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;To not change with the good habits, the night was yet another time short. We woke up at 4.15 a.m., skipped any sort of breakfast and reached the bus terminal in Santa Elena with our Peruvian and Guatemalan friends.&lt;&#x2F;p&gt;
&lt;p&gt;After spending my last 7 quetzals into a bottle of orange juice, we jumped into the coach that didn’t seem to be of the latest generation. Most of its windows have been hit by rock impacts, probably during a street manifestation that didn’t turn so well.&lt;&#x2F;p&gt;
&lt;p&gt;But we were happy, the coach wasn’t as crowded as we thought it would be and we could easily secure ourselves a seat each. It even was comfortable and the 4 hour drive to Rio Dulce for only 65 quetzals sounded like a great deal.&lt;&#x2F;p&gt;
&lt;p&gt;This was until the coach stopped to get more passengers. Over and over again. Quickly all the seats were taken and the unlucky ones had to stand up in the middle row with their alive chicken and other marchandises.&lt;&#x2F;p&gt;
&lt;p&gt;It reached a point where there really wasn’t any space left. I could feel the leg of someone compressed against my shoulder. I couldn’t take it anymore when he started to rub his willy against me. After a few minutes, I sighed. Loudly. He giggled and gave me a break by stepping back.&lt;&#x2F;p&gt;
&lt;p&gt;Against any logic, the density of people didn’t seem to be enough to dissuade the coach manager to always take more passengers in. When the persons standing up couldn’t figure out anymore how to free up some space, the manager walked to the back and started to place each person himself in a way that made them stacked up like tetris blocks. The coach did become a real tin box of sardines. With the sweat juice that comes with it.&lt;&#x2F;p&gt;
&lt;p&gt;To make it always more challenging, someone decided to showcase all the tracks that he had on his phone. Some sort of music from the 70s with the exact same rythmn played by an accordeon on each single of his tracks. I started to get a pretty badass headache.&lt;&#x2F;p&gt;
&lt;p&gt;Meanwhile some chicks escaped from their box and were partying a bit everywhere in the bus while singing a cute &lt;em&gt;pew pew&lt;&#x2F;em&gt; song.&lt;&#x2F;p&gt;
&lt;p&gt;I thought that I would finally breathe when we reached our destination but I was wrong. The loud truck engines and the dense traffic honking like if they were playing a contest of the most annoying driver only managed to make me regret my dear coach.&lt;&#x2F;p&gt;
&lt;p&gt;My first proper intake of food and sugar of the day didn’t help to get rid of my growing headache. I had to skip the tour planned to a castle in the middle of the lake and say goodbye to my new friends.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;boat-with-friends.jpg&quot; alt&#x3D;&quot;Boat filled with friends&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When I reached my hostel by boat, in the middle of some sort of jungle, I realized that what was a simple headache turned into an illness. It was around 2 p.m. when I felt asleep, covering myself in many layers to sweat the shit out of me. When I woke up a few hours later, I was still too weak but a honey bear crawling in the hostel cheered me with his presence.&lt;&#x2F;p&gt;
&lt;h2&gt;Livingston&lt;&#x2F;h2&gt;
&lt;h3&gt;31 December 2013&lt;&#x2F;h3&gt;
&lt;p&gt;Wait, did we change continent? Livingston is only accessible by boat, and when we reached the quay the first impression was that black Africans took control of the place.&lt;&#x2F;p&gt;
&lt;p&gt;It was supposed to be the place where to spend New Year Eve but we did struggle once again at finding any partying going on. We gathered with a few other gringos and went on a mission but for the first time in my life, I missed the countdown. Not that I care much anyways.&lt;&#x2F;p&gt;
&lt;p&gt;It’s only later that we finally found some African sound with a few guests dancing in a very... sexual manner.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11779723365&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;firecrackers-livingston-guatemala.jpg&quot; alt&#x3D;&quot;Kid throwing firecrackers in the street, Livingstone, Guatemala&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;span&gt;throwing some more firecrackers...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;h2&gt;Antigua, the Return of the Vengeance&lt;&#x2F;h2&gt;
&lt;h3&gt;2 January 2014&lt;&#x2F;h3&gt;
&lt;p&gt;Back to Antigua to close the loop.&lt;&#x2F;p&gt;
&lt;p&gt;The most terrible thing so far has been for me to not be able to directly interact with the locals. I’ve only been a mere passive spectactor when my Italian friend did speak to them. This has been beyond frustration to the point that I decided to overcome my hate for school and take Spanish classes in Antigua. I decided not to leave until I could communicate properly. It would be worthless to keep travelling like this anyways.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href&#x3D;&quot;https:&#x2F;&#x2F;www.flickr.com&#x2F;photos&#x2F;christophercrouzet&#x2F;11780042393&quot;&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;guatemala&#x2F;volcano-view-from-antigua-guatemala.jpg&quot; alt&#x3D;&quot;View of volcano from Antigua, Guatemala&quot;&gt;&lt;span&gt;View on Flickr&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Banana bread, how I’ve missed you!&lt;&#x2F;p&gt;
&lt;h2&gt;Random Facts&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;going into local places where no many gringos usually go is quite an experience. It sums up to dozen of people staring and amically laughing at you, while wondering what the fuck you’re doing there.&lt;&#x2F;li&gt;
&lt;li&gt;the toilet paper? Please don’t throw it in the toilets but in the bin. It might block the toilets and you &lt;em&gt;reallly&lt;&#x2F;em&gt; don’t want that.&lt;&#x2F;li&gt;
&lt;li&gt;clothes don’t dry in a humid environment&lt;&#x2F;li&gt;
&lt;li&gt;Guatemalans love showing-off the same 3–bit Christmas song everywhere. My ears bled. A lot.&lt;&#x2F;li&gt;
&lt;li&gt;the pre–Christmas banging firecrackers celebrations actually never ended. They did survive to both Christmas and New Year Eve, to the days and the nights.&lt;&#x2F;li&gt;
&lt;li&gt;did you ever see a place where the toilet paper is only available before entering the toilets? I did. Hopefully you’re not on diarrhea, otherwise good luck at anticipating the amount of paper that you’ll need without wasting any.&lt;&#x2F;li&gt;
&lt;li&gt;I’m sure that a kid has more chances of being shot dead at his school in the USA than ending up injured at all in Guatemala.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;Only later have I been told that the omnipresent black tint on the cars is there to prevent thieves from seeing their content.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;The road between San Pedro and Santa Clara has the reputation to be a good spot for thieves to target foreigners.&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
    <entry>
      <id>urn:uuid:a555e0f8-a5ce-11e7-a66a-ab59d1485319</id>
      <title>Let It Begin</title>
      <updated>2017-10-01T13:00:00Z</updated>

      <published>2013-12-21T13:00:00Z</published>
      <author>
        <name>Christopher Crouzet</name>
      </author>
      <link href="https://christophercrouzet.com/blog/travel/let-in-begin" rel="alternate" type="text/html" title="Let It Begin" />
        <category term="travel" />
        <media:thumbnail url="https://christophercrouzet.com/blog/travel/let-in-begin/antigua-guatemala.jpg" />

      <content type="html">
        &lt;h2&gt;A Taste of Freedom&lt;&#x2F;h2&gt;
&lt;p&gt;In France we’ve got what’s called a CDI, which is a permanent work contract. In such a difficult economic context, it is seen as the Holy Grail, providing their holders with security of employment.&lt;&#x2F;p&gt;
&lt;p&gt;Not being stressed out, carrying no responsibilities, having no constraints, being able to meet with friends anytime, anywhere, benefiting from having the freedom to do what we like, including relaxing, this is what I call a VDI.&lt;&#x2F;p&gt;
&lt;p&gt;VDI stands for &lt;em&gt;Vacances à Durée Indéterminée&lt;&#x2F;em&gt;, which translates to &lt;em&gt;Open-Ended Holidays&lt;&#x2F;em&gt;. It has been my status for the last 6 months already, and needless to say that it has been great.&lt;&#x2F;p&gt;
&lt;p&gt;But it‘s now time to move on, to go for the &lt;em&gt;real&lt;&#x2F;em&gt; deal. I’ve decided to add a glimpse of adventure to my dear VDI. Here I am, now starting an &lt;em&gt;unrestricted&lt;&#x2F;em&gt; trip to Latin America!&lt;&#x2F;p&gt;
&lt;h2&gt;Paris&lt;&#x2F;h2&gt;
&lt;p&gt;But first things first. Living in the South of France, Paris was the first stop on my way.&lt;&#x2F;p&gt;
&lt;p&gt;Paris and I have always been into a love–hate relationship.&lt;&#x2F;p&gt;
&lt;p&gt;When I was a kid, I always thought that the weather forecasts were just lies. I still kinda believe that but back then I couldn’t get myself around the fact that they consistently predicted either a cloudy or a rainy weather in Paris when it was mostly sunny in the South. How could these people possibly endure living without the sun for so long, right?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;let-in-begin&#x2F;weather-france.jpg&quot; alt&#x3D;&quot;French weather forecase as I imagined it&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Then I started to work there for 2 months. Trying to find my way in the streets of Paris with my hugely and heavily unoptimized backpack lacerating my shoulders wasn’t enough, I also had to be welcomed with a cold rain that soaked me up within minutes.&lt;&#x2F;p&gt;
&lt;p&gt;I didn’t see any single pixel of blue sky during my first week, let alone the sun rays. When I finally met with the sun again, I got blinded by the &lt;em&gt;intense&lt;&#x2F;em&gt; ambient luminosity, I just couldn’t stand all that natural light anymore. Fortunately it &lt;em&gt;only&lt;&#x2F;em&gt; lasted for 15 minutes.&lt;&#x2F;p&gt;
&lt;p&gt;Not only the sky was grey but the city itself with its buildings felt quite colorless too. Even its inhabitants were dressed in a shade of grey, like if they were enjoying themselves in this moody and monotone atmosphere.&lt;&#x2F;p&gt;
&lt;p&gt;It hit the nail on the head when a pizza maker one day told me that I didn’t seem to come from Paris. I was surprised, how could he possibly figure this out? I don’t have that much of a pronounced Southerner accent, nor was I wearing anything that could betray me, so I asked the man. His answer? &lt;q&gt;Well, you’re smiling!&lt;&#x2F;q&gt; Alright... I had failed the Parisian test.&lt;&#x2F;p&gt;
&lt;p&gt;All in all, I’ve first seen Paris like a very noisy, confined, polluted and oppressing city with no much space for nature, and where their inhabitants looked like a bunch of soulless zombies living solely for their work.&lt;&#x2F;p&gt;
&lt;p&gt;For the city known as the most romantic city in the world, I surely was disappointed. It all looked too cold. Cold and grey.&lt;&#x2F;p&gt;
&lt;p&gt;But I based that judgement on day time only. I then realized that by night, Paris and its people are a whole different beast.&lt;&#x2F;p&gt;
&lt;p&gt;The city paradoxically becomes quieter but more alive. The show starts with an unecological but beautiful set of lights illuminating those Haussmanian buildings and other monuments, while the inhabitant’s souls seem to be regaining possession of their bodies.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn1&quot; id&#x3D;&quot;fnref1&quot;&gt;[1]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;With their stressful work behind, Parisians go out to enjoy the innumerable local bands, restaurants and bars available to them. The choice of food is large and potentially good but expensive.&lt;sup&gt;&lt;a href&#x3D;&quot;#fn2&quot; id&#x3D;&quot;fnref2&quot;&gt;[2]&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Even the simplest cocktail can get sometimes ridiculously expensive but it doesn’t matter anymore. Life is good again.&lt;&#x2F;p&gt;
&lt;p&gt;A trip in the city with the Velib’—the self–service bike system— finished to convince me of that chilled and magical ambiance of Paris by night. Such a pleasure for the eyes.&lt;&#x2F;p&gt;
&lt;h2&gt;Toronto&lt;&#x2F;h2&gt;
&lt;p&gt;I knew that Paris wouldn’t be cold enough to my liking so I thought &lt;q&gt;Why not visiting a friend in Toronto and see a bit of Canada for the first time?&lt;&#x2F;q&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Having never experienced negative temperatures before, I wasn’t sure if neither my gear nor my body would pass the test. But it didn’t go &lt;em&gt;toooo&lt;&#x2F;em&gt; bad.&lt;&#x2F;p&gt;
&lt;p&gt;With my layered approach—long sleeve merino base layer, merino t–shirt, merino long sleeve light vest, ultralight down feather jacket, and Gore–Tex hard shell jacket!—, I’ve proudly managed to survive to a temperature of -10°C that apparently felt like -16°C with the extra wind. Of course my merino underwear also helped.&lt;&#x2F;p&gt;
&lt;p&gt;But still, it was freezing cold. So cold that we had to adopt a walk’n’hide strategy. We regularly interrupted our outdoors walks to find refuge in shops or public transports to allow us to unfreeze and build a bit of heat before stepping back outside again.&lt;&#x2F;p&gt;
&lt;p&gt;And it wasn’t even winter at its best.&lt;&#x2F;p&gt;
&lt;p&gt;Temperatures aside, in the short 4 days that I’ve spent there, I found the city to be heartwarming. Maybe is it partly due to the Poutine dishes burrowed from their neighbors in Quebec, but mostly everyone seemed to be kind and friendly.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;let-in-begin&#x2F;poutine.jpg&quot; alt&#x3D;&quot;Poutine, a specialty from Quebec&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;OK, being a fan of &lt;em&gt;healthy&lt;&#x2F;em&gt; cuisine from the entire world, I had quite some difficulties with the Poutine but I loved the markets there—bonus points here because they sell French cheese!—and the different communities having each their own suburb as the always present Chinatown swarming with Vietnamese, Japanese and other dim sum places. Yum. Not sure if I’ll ever be able to live again in a city without good Asian restaurants.&lt;&#x2F;p&gt;
&lt;p&gt;My only disappointment after having been teased with it for days by my local friend is to not have found any place selling the famous beaver tails—not the actual tail of beavers but their pastry equivalent, a specialty around here. We tried hard though! We even went as far as to the Niagara Falls only to find out that the shop was closed.&lt;&#x2F;p&gt;
&lt;p&gt;Another thing that I kept hearing was to come back in summer for a better experience of the city. I can only be looking forward to do so!&lt;&#x2F;p&gt;
&lt;h2&gt;San Francisco&lt;&#x2F;h2&gt;
&lt;p&gt;Don’t tell the customs but my main goal was to gear up a bit in electronics for the needs of my trip. I had to leave my film cameras back home and opt for a more portable solution, which is why I picked a Fuji X100S. I never had the chance to try one before so I solely had to trust the online reviews and cross my fingers.&lt;&#x2F;p&gt;
&lt;p&gt;But San Francisco was also a chance to visit another city in the USA. After having been not really pleased with both Honolulu and Los Angeles, I was told that SF could leave me with a better impression.&lt;&#x2F;p&gt;
&lt;p&gt;And this time, I indeed wouldn’t be disappointed. Instead of the ridiculous Muscle Beach and other never ending boulevards lacking of any interest, I found myself walking in streets that had a nice vibe to them, packed with tons of cool little places to eat, and went through suburbs that each had their own identity.&lt;&#x2F;p&gt;
&lt;p&gt;I happily went on to climb all these famous roller coaster streets. Fortunately I enjoy walking uphill because I realized that a simple map is not enough in SF to deduce the most optimized route. It’s in those situations that having a good knowledge of &lt;cite&gt;Grand Theft Auto&lt;&#x2F;cite&gt; can become handy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;let-in-begin&#x2F;quickest-path-san-francisco.jpg&quot; alt&#x3D;&quot;Quickest path in San Francisco&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I’ve been impressed by how much the nearby Silicon Valley had an impact on the number of entrepreneurs trying their luck with various projects. Or maybe is it the other way around?&lt;&#x2F;p&gt;
&lt;p&gt;What’s sure is that either in the train or in coffee shops, many had their laptop open and were discussing about their startup, their ideas, the designs they were working on, or the softwares they were developing.&lt;&#x2F;p&gt;
&lt;p&gt;That’s a lot of creativity going on within that tech–savvy bubble. No wonder why the few managing to emerge out of that fierce competition end up ruling over the world.&lt;&#x2F;p&gt;
&lt;p&gt;On the other side were the ones wandering in the streets and calling them “home”. They were far from being only a few, it actually seemed more like an entire and vast community living in a parallel world where any interaction between both worlds were rare. A truly saddening sight.&lt;&#x2F;p&gt;
&lt;h2&gt;As of Now...&lt;&#x2F;h2&gt;
&lt;p&gt;I’ve just arrived in Antigua, Guatemala. I’ve got a cold and a sprained ankle.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;img src&#x3D;&quot;https:&#x2F;&#x2F;christophercrouzet.com&#x2F;blog&#x2F;travel&#x2F;let-in-begin&#x2F;antigua-guatemala.jpg&quot; alt&#x3D;&quot;Antigua, Guatemala&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;p&gt;
&lt;aside&gt;&lt;h2&gt;Footnotes&lt;&#x2F;h2&gt;&lt;ol&gt;&lt;li id&#x3D;&quot;fn1&quot;&gt;By night, even the Eiffel Tower looks nice.&lt;a href&#x3D;&quot;#fnref1&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;li id&#x3D;&quot;fn2&quot;&gt;Being renowned as a one of the countries with great food, it’s surprisingly much easier to find terrible and pricy restaurants than honest ones with good food.&lt;a href&#x3D;&quot;#fnref2&quot;&gt;&lt;span&gt;↩&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&lt;&#x2F;ol&gt;&lt;&#x2F;aside&gt;
      </content>
    </entry>
</feed>
