<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Home on Arjo Nagelhout</title><link>https://arjonagelhout.nl/</link><description>Recent content in Home on Arjo Nagelhout</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 07 Apr 2026 01:00:00 +0000</lastBuildDate><atom:link href="https://arjonagelhout.nl/index.xml" rel="self" type="application/rss+xml"/><item><title>Basis splines</title><link>https://arjonagelhout.nl/writings/2026-04-07_basis_splines/</link><pubDate>Tue, 07 Apr 2026 01:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2026-04-07_basis_splines/</guid><description>&lt;!-- Run bash compile.sh to compile all images into svgs -->
&lt;p>How are smooth and complex curves and curved surfaces constructed in computers for computer-aided design (CAD)? With splines!&lt;/p>
&lt;p>Compared to simpler definitions such as arcs, polynomials, lines, planes, spheres and cylinders, splines allow creating smooth, complex curves and surfaces from a set of control points. Because of this, splines are unmissable in creating organic shapes in products and architecture.&lt;/p></description></item><item><title>Minimal sudoku solver using DFS in C++</title><link>https://arjonagelhout.nl/writings/2026-04-06_sudoku/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2026-04-06_sudoku/</guid><description>&lt;p>Sometimes I take the time to sit down and solve some sudoku puzzles. I never got into more advanced techniques, such as X-wings, so as I was staring at a seemingly impossible sudoku passage, I wondered; How hard can it be to write an algorithm for this?&lt;/p>
&lt;p>While the internet is full of sudoku solvers, the potential thrill of finding a solution myself made me refrain from looking up known techniques. Admittedly, I did get stuck at one point attempting to traverse &lt;em>all possible paths&lt;/em> towards a solution, which is a much larger search space than filling in cells sequentially. A quick look at a video of the backtracking algorithm in action got me back on track, but now I can&amp;rsquo;t claim to have solved it fully on my own.&lt;/p></description></item><item><title>Tree traversal without using the built-in stack in C++</title><link>https://arjonagelhout.nl/writings/2026-01-26_tree_traversal_without_the_stack/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2026-01-26_tree_traversal_without_the_stack/</guid><description>&lt;p>I have written these code snippets too often, so I might as well share them. The reason for not using recursion &amp;ndash; and thus relying on the built-in stack &amp;ndash; is that for large values for N, the stack trace will become hard to debug and read, and you might create a stack overflow if the stack size is set too conservatively.&lt;/p>
&lt;p>The functions are templated, but they don&amp;rsquo;t have to be.&lt;/p></description></item><item><title>Importing IFC Geometry</title><link>https://arjonagelhout.nl/writings/2026-01-25_ifc_import/</link><pubDate>Sun, 25 Jan 2026 19:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2026-01-25_ifc_import/</guid><description>&lt;p>In order for my 3D modeling application to be useful in the workflow of an architect, I need to be able to import and export document interchange formats such as IFC and STEP.&lt;/p>
&lt;p>For that reason I wrote an IFC reader, which consists of an EXPRESS parser, an EXPRESS runtime, a STEP parser, a STEP reader and IFC-specific logic for iterating over the hierarchy and e.g. extracting all geometry.&lt;/p>



&lt;figure class="">
 
 

 
 
 
 &lt;img src="https://arjonagelhout.nl/writings/2026-01-25_ifc_import/images/ifc_hu_62b7b1fff9e29e90.webp" width="1400" height="873">
 


 &lt;figcaption>Part of the imported geometry, currently only importing IfcFacetedBrep.&lt;/figcaption>

&lt;/figure>
&lt;p>See the following code snippet for the API of iterating over the IFC file.&lt;/p></description></item><item><title>The one and only codebase</title><link>https://arjonagelhout.nl/writings/2026-01-25_the_one_and_only_codebase/</link><pubDate>Sun, 25 Jan 2026 13:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2026-01-25_the_one_and_only_codebase/</guid><description>&lt;p>A little while back, my contract ended with Treble Technologies, for whom I had been writing (parts of) a geometry processing pipeline for automated mesh simplification and mesh cleanup in C++.&lt;/p>
&lt;p>This was my first real out-of-university job &amp;ndash; apart from some freelance work, an internship and my VR startup &amp;ndash; and I was ecstatic about the opportunity. I traveled back-and-forth from the Netherlands to Reykjavík, Iceland, and spent all day thinking about geometric problems, reading about data structures and finding novel papers.&lt;/p></description></item><item><title>VDB - the better octree</title><link>https://arjonagelhout.nl/writings/2025-08-18_voxels/</link><pubDate>Mon, 18 Aug 2025 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2025-08-18_voxels/</guid><description>&lt;p>I have implemented a voxel data structure similar to an octree, but with a larger branching factor (still powers of 2), that contains a hashmap at the root of the tree. It takes inspiration from the paper &lt;a href="https://www.museth.org/Ken/Publications_files/Museth_TOG13.pdf">&amp;ldquo;VDB: High-Resolution Sparse Volumes with Dynamic Topology&amp;rdquo; by Ken Museth&lt;/a>.&lt;/p>
&lt;p>&lt;img src="images/vdb_2.jpg" alt="Voxels">&lt;/p>
&lt;p>The image shows the mesh graph voxelized into a tree containing depths 2, 3 and 4 (&lt;code>VDB&amp;lt;2, 3, 4&amp;gt;&lt;/code>). Depth 2 gives 64 children, 3 gives 512 children, and 4 gives 4096 children. See the following overview:&lt;/p></description></item><item><title>Simple undo history for a mesh graph using diffs</title><link>https://arjonagelhout.nl/writings/2025-08-14_mesh_graph_undo/</link><pubDate>Thu, 14 Aug 2025 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2025-08-14_mesh_graph_undo/</guid><description>&lt;p>Undo is a fundamental feature in many software applications, and my app ShapeReality - 3D Modeling is no exception. Due to the ubiquity of undo, one might expect implementing it to be a solved problem, but there commonly isn&amp;rsquo;t a one-size-fits-all approach.&lt;/p>
&lt;p>This post will go into how I implemented undo for my mesh graph data structure, and how this fits into the complete undo history of an application containing multiple data structures and contexts. These principles should be applicable to other applications and data structures too.&lt;/p></description></item><item><title>An editable non-manifold polygon mesh data structure</title><link>https://arjonagelhout.nl/writings/2025-07-28_editable_mesh/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2025-07-28_editable_mesh/</guid><description>&lt;p>One core data structure in my 3D modeling application is a non-manifold polygon mesh data structure. This data structure is a graph of &lt;code>Vertices&lt;/code>, &lt;code>Edges&lt;/code> and &lt;code>Faces&lt;/code>. It takes inspiration from &lt;a href="https://developer.blender.org/docs/features/objects/mesh/bmesh/">Blender&amp;rsquo;s BMesh data structure&lt;/a>, the Radial Edge Structure (see &lt;a href="https://docs.nvidia.com/smlib/manual/smlib/topology/index.html">Nvidia SMLib - Topology documentation&lt;/a>) and the paper &lt;a href="https://asmedigitalcollection.asme.org/computingengineering/article-abstract/1/4/356/471622/Partial-Entity-Structure-A-Compact-Boundary?redirectedFrom=fulltext">Partial Entity Structure: A Compact Boundary Representation for Non-Manifold Geometric Modeling by Lee et al.&lt;/a>, but arrives at a simpler design that still has the desired feature set.&lt;/p></description></item><item><title>iOS app launched: 3D modeling on iPhone and iPad</title><link>https://arjonagelhout.nl/writings/2025-07-28_ios_app_released/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2025-07-28_ios_app_released/</guid><description>&lt;p>For the past 2 months I have been working on a 3D modeling app for iOS devices using C++ and Swift, and I just released it on iOS.&lt;/p>
&lt;p>This release is the product of a self-proposed bet with a friend that I would have to pay him €1000 if I did not launch some application he could install and use within 3 months.&lt;/p>
&lt;p>It supports:&lt;/p>
&lt;ul>
&lt;li>Non-manifold polygon meshes with loose vertices and edges (see &lt;a href="https://arjonagelhout.nl/writings/2025-07-28_editable_mesh/">An editable non-manifold polygon mesh data structure&lt;/a>)&lt;/li>
&lt;li>Inserting vertices, edges and faces&lt;/li>
&lt;li>Extruding edges and faces&lt;/li>
&lt;li>Deleting elements&lt;/li>
&lt;li>Duplicating elements&lt;/li>
&lt;li>Multi element selection&lt;/li>
&lt;li>Flipping face normals&lt;/li>
&lt;li>Insert primitive shapes&lt;/li>
&lt;li>Saving and loading to and from .obj (note that all geometry will be merged into one mesh)&lt;/li>
&lt;li>Orbit camera viewport navigation&lt;/li>
&lt;/ul>
&lt;h2 id="timelapse-demo-of-using-the-app">Timelapse demo of using the app &lt;a class="header-link" href="#timelapse-demo-of-using-the-app">#&lt;/a>&lt;/h2>&lt;p>

&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/XG_bNcqe5zo" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure>



&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/0LDNeQGIjxo" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure>
&lt;/p></description></item><item><title>metal-experiment: The value of underengineering</title><link>https://arjonagelhout.nl/writings/2024-06-18_metal_experiment/</link><pubDate>Tue, 18 Jun 2024 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-06-18_metal_experiment/</guid><description>&lt;p>Half a year ago I embarked on the quest to build a cross-platform, general purpose game engine.
I was working on asynchronous asset loading, a reflection system, an ECS for representing the scene, serialization, a graphics API wrapper for Metal and Vulkan, but barely got some simple rendering tests on top of these systems.&lt;/p>
&lt;p>My next steps were to get cross-platform shader compilation working, and to further improve my reflection and serialization implementation.&lt;/p></description></item><item><title>A quick note about little-endianness</title><link>https://arjonagelhout.nl/writings/2024-05-23_little_endian/</link><pubDate>Thu, 23 May 2024 09:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-05-23_little_endian/</guid><description>&lt;p>&lt;strong>Little-endian&lt;/strong> means the least-significant byte is at the smallest address (on the left).&lt;/p>
&lt;p>The least-significant byte is the byte that changes the value the least, and is in binary notation on the right:&lt;/p>
&lt;div class="highlight">&lt;div style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>(binary notation)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> least-significant byte
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> │
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ┌──┴───┐
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0b0100110110011101
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The individual 8 bits that make up the byte are still in the same order. Endianness only is about the order of the &lt;em>bytes&lt;/em>.&lt;/p></description></item><item><title>The ideal XR device and operating system</title><link>https://arjonagelhout.nl/writings/2024-02-05_the_ideal_xr_device_and_operating_system/</link><pubDate>Mon, 05 Feb 2024 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-02-05_the_ideal_xr_device_and_operating_system/</guid><description>&lt;p>With the Vision Pro having made its way into the hands of people outside of Apple&amp;rsquo;s secretive labs, I want to reflect a bit on my conceptions of what an ideal XR device would look like, and to what extent the Apple Vision Pro comes close to this ideal.&lt;/p>
&lt;h2 id="defining-success">Defining success &lt;a class="header-link" href="#defining-success">#&lt;/a>&lt;/h2>&lt;p>I have identified three core use cases an XR device could fulfill:&lt;/p>
&lt;ol>
&lt;li>Provide entertainment&lt;/li>
&lt;li>Connect people&lt;/li>
&lt;li>Increase productivity&lt;/li>
&lt;/ol>
&lt;p>In order for an XR device to be able to become successful, it needs to at least deliver on one of these use cases better than a readily available alternative offering. Note that these alternative offerings can be in different product categories. A pencil and paper still compete with an iPad, regardless of technological innovativeness.&lt;/p></description></item><item><title>Apple Vision Pro and the importance of tinkering</title><link>https://arjonagelhout.nl/writings/2024-01-24_apple_vision_pro/</link><pubDate>Wed, 24 Jan 2024 01:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-01-24_apple_vision_pro/</guid><description>&lt;p>I am preemptively disappointed by the Apple Vision Pro. Not because it isn&amp;rsquo;t an absolute marvel of engineering, but because of what its operating system affords the user to do.&lt;/p>
&lt;p>For a while I have been reading the book The Innovators by Walter Isaacson, which goes into depth on the history of the personal computer and development of the internet. One thing that was common throughout the majority of the founders of these tech companies was that they grew up tinkering with hardware and later software.&lt;/p></description></item><item><title>Sunken cost-driven development - Or: why would you ever build a game engine?</title><link>https://arjonagelhout.nl/writings/2024-01-24_why_a_game_engine/</link><pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-01-24_why_a_game_engine/</guid><description>&lt;p>A question I get asked a lot is why I decided to build a game engine. All in all, there&amp;rsquo;s already great commercial engines out there, that, as long as you don&amp;rsquo;t earn any money, cost nothing.&lt;/p>
&lt;p>Unity and Unreal Engine are powerhouses with an abundance of tools and resources built on top of them.&lt;/p>
&lt;p>In addition, if commercial engines are not your cup of tea and you wish to go open source, there&amp;rsquo;s a plethora of options there as well. Godot being the most cited one, but far from the only option.&lt;/p></description></item><item><title>C++ Game engine - ECS and transform hierarchy</title><link>https://arjonagelhout.nl/writings/2024-01-20_cpp_game_engine_ecs_transform_hierarchy/</link><pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2024-01-20_cpp_game_engine_ecs_transform_hierarchy/</guid><description>&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/hIukzfXTDE4" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure>

&lt;p>I decided it would be a great idea to implement my own entity component system (ECS). Learned a lot about template metaprogramming in C++ and how to efficiently compute a transform hierarchy with a minimal amount of matrix multiplication using dirty flags and sorting.&lt;/p>
&lt;p>Next step: stress test the system with as many instances as possible. And sort the mesh renderers to automatically batch draw calls of objects that contain the same mesh and materials.&lt;/p></description></item><item><title>C++ Game engine - Texture and mesh import</title><link>https://arjonagelhout.nl/writings/2023-12-26_cpp_game_engine_texture_and_mesh_import/</link><pubDate>Tue, 26 Dec 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-12-26_cpp_game_engine_texture_and_mesh_import/</guid><description>&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/EIuShKrdMi8" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure>

&lt;p>fully from scratch is not a hill I&amp;rsquo;m willing to die on, so at least png and gltf import are now done using lodepng and cgltf respectively.&lt;/p>
&lt;p>I should probably implement quaternions so that I don&amp;rsquo;t have to look at this 3D model rotated at 90 degrees any longer.&lt;/p>
&lt;p>also, the loading of assets is all hard-coded now. Getting it into production ready scalable state will be the main challenge :)&lt;/p></description></item><item><title>C++ Game engine - Hello World</title><link>https://arjonagelhout.nl/writings/2023-12-22_cpp_game_engine_hello_world/</link><pubDate>Fri, 22 Dec 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-12-22_cpp_game_engine_hello_world/</guid><description>&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/vQLdMnY1HPw" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure>

&lt;p>This is just a &amp;ldquo;hello world&amp;rdquo;, but I did some work to make it cross-platform / platform agnostic.&lt;/p>
&lt;p>It uses zero external dependencies, so no SDL, GLFW or GLM, which made it a bit harder.&lt;/p>
&lt;p>Next step: import mesh and texture support&lt;/p>
&lt;p>It&amp;rsquo;s tempting to also immediately write a backend for Vulkan and support for Android, but then I&amp;rsquo;ll never come around building more than a cube :)&lt;/p></description></item><item><title>My product failed - Reflections on building a VR application</title><link>https://arjonagelhout.nl/writings/2023-09-03_vr_application_reflections/</link><pubDate>Sun, 03 Sep 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-09-03_vr_application_reflections/</guid><description>&lt;p>My product failed. But the idea behind it doesn&amp;rsquo;t die. It&amp;rsquo;s just its current iteration that does.&lt;/p>
&lt;h2 id="the-rose-colored-start">The rose-colored start &lt;a class="header-link" href="#the-rose-colored-start">#&lt;/a>&lt;/h2>&lt;p>After my repeated experience with building prototypes, together with the experience creating AR and VR experiences for the Ministry of Defence, I felt confident enough to try taking the prototype for &lt;a href="https://arjonagelhout.nl/writings/2021-03-12_xr_designer/">&amp;ldquo;XR designer&amp;rdquo;&lt;/a> to the next step. I didn&amp;rsquo;t want to get stuck at creating software that only &amp;ldquo;proved a concept&amp;rdquo;. Because you can&amp;rsquo;t really prove a concept until you&amp;rsquo;ve fully sat it out. Similar to how an idea is worth nothing, a prototype isn&amp;rsquo;t worth that much either and only becomes valuable in the light of it turning into an actual product. Although this depends on your definition of at which level something stops being a prototype.&lt;/p></description></item><item><title>Rendering in C++ and Vulkan because I outgrew Unity</title><link>https://arjonagelhout.nl/writings/2023-08-18_vulkan_rendering/</link><pubDate>Fri, 18 Aug 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-08-18_vulkan_rendering/</guid><description>&lt;p>Over the summer I decided it was time to let go of Unity due to a variety of reasons, so I picked up the famous Game Engine Architecture book, the Vulkan specification and got to work.&lt;/p>
&lt;p>This is my first venture into writing code in C++, as opposed to C#, which went as good as you might expect. I abandoned this project after a certain point, because the abstractions I built didn&amp;rsquo;t make that much sense and I didn&amp;rsquo;t really know how to progress further.&lt;/p></description></item><item><title>Spatial user interface demo on Meta Quest Pro</title><link>https://arjonagelhout.nl/writings/2023-07-07_spatial_user_interface_demo_meta_quest_pro/</link><pubDate>Fri, 07 Jul 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-07-07_spatial_user_interface_demo_meta_quest_pro/</guid><description>&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/8HyNpeISALs" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure></description></item><item><title>Placing trees in AR in Innovation Space</title><link>https://arjonagelhout.nl/writings/2023-06-23_placing_trees/</link><pubDate>Fri, 23 Jun 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-06-23_placing_trees/</guid><description>&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/cbIDRB1u04s" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure></description></item><item><title>A 1000 commits later...</title><link>https://arjonagelhout.nl/writings/2023-03-28_a_1000_commits_later/</link><pubDate>Tue, 28 Mar 2023 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2023-03-28_a_1000_commits_later/</guid><description>&lt;p>Just today I reached the milestone of 1000 commits for the ShapeReality codebase. Because of this, I&amp;rsquo;m taking the opportunity to reflect on my how I have developed, both on a personal and professional level.&lt;/p>
&lt;p>To summarise, I have become a way better programmer, but I still have so many things to learn on a professional level and in terms of domain expertise. Software cannot be created in isolation and requires one to deeply understand the domain of users or clients.&lt;/p></description></item><item><title>XR Designer - A prototype for an augmented reality design app</title><link>https://arjonagelhout.nl/writings/2021-03-12_xr_designer/</link><pubDate>Sat, 12 Mar 2022 21:26:27 +0200</pubDate><guid>https://arjonagelhout.nl/writings/2021-03-12_xr_designer/</guid><description>&lt;h2 id="the-briefing">The briefing &lt;a class="header-link" href="#the-briefing">#&lt;/a>&lt;/h2>&lt;p>In 2018, the Municipality of Eindhoven has created a pact for improving social-economic problems in Woensel-Zuid, one of the city districts of Eindhoven. It focuses on &amp;ldquo;creating opportunities for &lt;strong>children and young people&lt;/strong>, encouraging entrepreneurship, &lt;strong>improving climate resilience&lt;/strong> and tackling crime and safety related issues.&amp;rdquo;&lt;/p>
&lt;p>In order to reach these goals, getting young people to care about and engage in improving their own neighbourhood was deemed beneficial.
So, we were tasked with inspiring this younger demographic through &amp;ldquo;new methods and technologies such as gamification or digital tools.&amp;rdquo;&lt;/p></description></item><item><title>UrbanAR - An Augmented Reality design tool for urban design and planning</title><link>https://arjonagelhout.nl/writings/2021-01-03_urbanar/</link><pubDate>Sun, 03 Jan 2021 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2021-01-03_urbanar/</guid><description>&lt;p>See the project page &lt;a href="https://urbanar.arjonagelhout.nl/">https://urbanar.arjonagelhout.nl/&lt;/a> for more information.&lt;/p>
&lt;h2 id="project-description">Project Description &lt;a class="header-link" href="#project-description">#&lt;/a>&lt;/h2>&lt;p>We want to make the urban design and planning process of our cities more fun and engaging: to make our cities more of a collaborative effort and better for everyone. Currently, municipalities are using old methods to get input from citizens, but we can change that!&lt;/p>
&lt;p>That’s why we set out to create UrbanAR. An intuitive app in which you can design your dream neighbourhood, a new park or crazy art piece - all right in front of you using augmented reality. The app also allows you to share your ideas with the world! Compete in contests and get inspired by others!&lt;/p></description></item><item><title>3D Island Generator in Processing</title><link>https://arjonagelhout.nl/writings/2019-10-01_procedural_island/</link><pubDate>Tue, 01 Oct 2019 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/writings/2019-10-01_procedural_island/</guid><description>&lt;p>GitHub: &lt;a href="https://github.com/ArjoNagelhout/3D_Island_Generator_In_Processing">https://github.com/ArjoNagelhout/3D_Island_Generator_In_Processing&lt;/a>&lt;/p>


&lt;a name=''>&lt;/a>
&lt;figure>
 &lt;div class="youtube-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
 &lt;iframe src="https://www.youtube.com/embed/980rI9fI958" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
 &lt;/div>&lt;/figure></description></item><item><title>Memory</title><link>https://arjonagelhout.nl/memory/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/memory/</guid><description>&lt;div class="container" id="memory_app">
&lt;h2>App&lt;/h2>
&lt;div class="container" id="memory_app_settings">
&lt;h3>Settings&lt;/h3>
You can either use your own sequence, or get a randomly generated sequence. 
&lt;p>&lt;label for="use_sequence">Use sequence&lt;/label>
&lt;input id="use_sequence" type="checkbox" checked/>&lt;/p>
&lt;fieldset>
&lt;legend>Sequence&lt;/legend>
&lt;p>
Values must be comma separated. In order to make a number reversed, prefix it with &lt;code>r&lt;/code>. 
Example input:
&lt;p>&lt;code>4, 4, 5, 5, 6, 7, 8, r4, r5, r6, r7, r8&lt;/code>&lt;/p>
&lt;/p>
&lt;label for="sequence">Sequence&lt;/label>
&lt;input id="sequence" placeholder="4, 4, 5, 5, 6, 7, 8, r4, r5, r6, r7, r8" type="text" value="4, 4, 5, 5, 6, 7, 8, r4, r5, r6, r7, r8" />
&lt;/fieldset>
&lt;fieldset>
&lt;legend>Random&lt;/legend>
&lt;p>&lt;label for="amount">Amount&lt;/label>
&lt;input id="amount" placeholder="10" type="number" value="20"/>&lt;/p></description></item><item><title>Memory grid</title><link>https://arjonagelhout.nl/memory_grid/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://arjonagelhout.nl/memory_grid/</guid><description>&lt;div class="container" id="memory_app">
&lt;h2>App&lt;/h2>
&lt;div class="container" id="memory_app_settings">
&lt;h3>Settings&lt;/h3>
&lt;label for="amount">Amount&lt;/label>
&lt;input id="amount" placeholder="10" type="number" value="20"/>
&lt;fieldset>
&lt;legend>Timing&lt;/legend>
&lt;label for="time_before">Time before (in seconds)&lt;/label>
&lt;input id="time_before" placeholder="1.0" type="number" value="1.0"/>
&lt;p>&lt;label for="time_visible">Duration visible (in seconds)&lt;/label>
&lt;input id="time_visible" placeholder="1.0" type="number" value="1.0"/>&lt;/p>
&lt;p>&lt;label for="time_after">Time after (in seconds)&lt;/label>
&lt;input id="time_after" placeholder="1.0" type="number" value="1.0"/>&lt;/p>
&lt;/fieldset>
&lt;p>&lt;button id="start" onclick="start()">Start&lt;/button>&lt;/p>
&lt;/div>
&lt;div class="container" id="memory_app_during_run">
&lt;div id="memory_app_content">
&lt;/div>
&lt;div class="content_wrapper">
&lt;div id="board" class="board">
&lt;div id="cell_0" class="cell" onClick="onClickCell(0)">7&lt;/div>
&lt;div id="cell_1" class="cell" onClick="onClickCell(1)">8&lt;/div>
&lt;div id="cell_2" class="cell" onClick="onClickCell(2)">9&lt;/div>
&lt;div id="cell_3" class="cell" onClick="onClickCell(3)">4&lt;/div>
&lt;div id="cell_4" class="cell" onClick="onClickCell(4)">5&lt;/div>
&lt;div id="cell_5" class="cell" onClick="onClickCell(5)">6&lt;/div>
&lt;div id="cell_6" class="cell" onClick="onClickCell(6)">1&lt;/div>
&lt;div id="cell_7" class="cell" onClick="onClickCell(7)">2&lt;/div>
&lt;div id="cell_8" class="cell" onClick="onClickCell(8)">3&lt;/div>
&lt;/div>
&lt;/div>
&lt;button id="submit_answer" onclick="submitAnswer()" style="background-color:green">Submit&lt;/button>
&lt;button id="stop" onclick="init()" style="background-color:red">Stop&lt;/button>
&lt;/div>
&lt;/div>
&lt;style>
*{
 box-sizing: border-box;
}
.board{
 margin:auto;
 display: grid;
 width: 18rem;
 height: 18rem;
 grid-template-columns: auto auto auto;
 grid-template-rows: auto auto auto;
}
.cell{
 width: 100%;
 height: 100%;
 border: 1px solid black;
 background-color:white;
 color: black;
 padding: 0.5rem;
}
.filled{
 background-color:blue;
 color:white;
}
.content_wrapper{
 height: 20rem;
}
.container{
 padding: 1rem;
 border: 1px solid black;
}
input{
 max-width:100%;
}
#memory_app{
 background-color: gainsboro;
}
#memory_app_settings{
 background-color: gray;
}
#memory_app_during_run{
 background-color: darkgray;
}
.hide{
 display:none !important;
 visibility:hidden !important;
}
#memory_app_content{
 font-size:1.5rem;
 text-align:center;
 height: 2rem;
}
&lt;/style>
&lt;script>
SETTINGS_DIV = document.getElementById("memory_app_settings");
DURING_RUN_DIV = document.getElementById("memory_app_during_run");
//
// settings:
ELEMENT_AMOUNT = document.getElementById("amount");
ELEMENT_TIME_BEFORE = document.getElementById("time_before");
ELEMENT_TIME_VISIBLE = document.getElementById("time_visible");
ELEMENT_TIME_AFTER = document.getElementById("time_after");
//
const ANSWER_RESULT_DELAY_IN_SECONDS = 2;
const EMPTY_BOARD = [false, false, false, false, false, false, false, false, false];
TIMEOUT_ID = null;
//
// BOARD CELLS
CELLS = [
 document.getElementById("cell_0"),
 document.getElementById("cell_1"),
 document.getElementById("cell_2"),
 document.getElementById("cell_3"),
 document.getElementById("cell_4"),
 document.getElementById("cell_5"),
 document.getElementById("cell_6"),
 document.getElementById("cell_7"),
 document.getElementById("cell_8"),
];
//
BOARD = document.getElementById("board");
SUBMIT_BUTTON = document.getElementById("submit_answer");
CONTENT = document.getElementById("memory_app_content");
//
const STATES = {
 WAIT_BEFORE: 'WAIT_BEFORE',
 SHOW_PATTERN: 'SHOW_PATTERN',
 WAIT_AFTER: 'WAIT_AFTER',
 INPUT_ANSWER: 'INPUT_ANSWER',
 ANSWER_RESULT: 'ANSWER_RESULT',
 SHOW_SCORE: 'SHOW_SCORE',
};
//
SETTINGS = {};
STATE = {};
// 
// what are the states?
// 1. countdown
// 2. show pattern
// 3. wait after
// 4. answer
//
function toggleCell(pattern, index) {
 pattern[index] = !pattern[index];
 setPattern(pattern); // updates the pattern
}
// key mapping is:
// 7 8 9 0 1 2
// 4 5 6 -> 3 4 5
// 1 2 3 6 7 8
//
function numberToIndex(number) {
 switch (number) {
 case 1: return 6;
 case 2: return 7;
 case 3: return 8;
 case 4: return 3;
 case 5: return 4;
 case 6: return 5;
 case 7: return 0;
 case 8: return 1;
 case 9: return 2;
 default: return NaN;
 }
}
//
function onClickCell(index) {
 // only react to keyboard input if we're in the input answer state
 if (STATE.state != STATES.INPUT_ANSWER) {
 return;
 }
 toggleCell(STATE.userInputPattern, index);
}
//
function onKey(key) {
 // only react to keyboard input if we're in the input answer state
 if (STATE.state != STATES.INPUT_ANSWER) {
 return;
 }
 //
 number = Number(key);
 if (number == NaN) {
 return;
 }
 index = numberToIndex(number);
 toggleCell(STATE.userInputPattern, index);
}
//
// generates a pattern of shape bool[9]
function generateRandomPattern() {
 result = [];
 for (let i = 0; i &lt; 9; ++i) {
 result.push(Math.random() >= 0.5);
 }
 return result;
}
//
function setCssClass(element, className, enable) {
 if (enable) {
 element.classList.add(className);
 } else {
 element.classList.remove(className);
 }
}
//
function patternsAreEqual(lhs, rhs) {
 for (let i = 0; i &lt; 9; ++i) {
 if (lhs[i] !== rhs[i]) {
 return false;
 }
 }
 return true;
}
//
// create pattern html
function setPattern(pattern) {
 for (let i = 0; i &lt; 9; ++i) {
 setCssClass(CELLS[i], "filled", pattern[i]);
 }
}
// 
function setIsRunning(isRunning) {
 setCssClass(SETTINGS_DIV, "hide", isRunning);
 setCssClass(DURING_RUN_DIV, "hide", !isRunning);
}
// 
function init() {
 clearTimeout(TIMEOUT_ID);
 TIMEOUT_ID = null;
 setIsRunning(false);
}
//
function start() {
 setIsRunning(true);
 //
 // first retrieve the settings
 amount = Math.floor(Number(ELEMENT_AMOUNT.value));
 timeBefore = Number(ELEMENT_TIME_BEFORE.value);
 timeVisible = Number(ELEMENT_TIME_VISIBLE.value);
 timeAfter = Number(ELEMENT_TIME_AFTER.value);
 //
 SETTINGS = {
 amount: amount,
 timeBefore: timeBefore,
 timeVisible: timeVisible,
 timeAfter: timeAfter,
 };
 //
 STATE = {
 state: STATES.WAIT_BEFORE,
 testIndex: 0, // which test we're at right now
 correctAnswers: 0,
 incorrectAnswers: 0,
 correctPattern: [...EMPTY_BOARD],
 userInputPattern: [...EMPTY_BOARD],
 };
 onStateChange();
}
//
function onStateChange() {
 nextState = {...STATE};
 console.log(STATE);
 switch (STATE.state) {
 case STATES.WAIT_BEFORE:
 CONTENT.innerHTML = "...";
 setCssClass(BOARD, "hide", true);
 setCssClass(SUBMIT_BUTTON, "hide", true);
 nextState.state = STATES.SHOW_PATTERN;
 nextState.correctPattern = generateRandomPattern();
 TIMEOUT_ID = setTimeout(function() { STATE = nextState; onStateChange()}, SETTINGS.timeBefore * 1000);
 break;
 case STATES.SHOW_PATTERN:
 CONTENT.innerHTML = "";
 setCssClass(BOARD, "hide", false);
 setPattern(STATE.correctPattern);
 nextState.state = STATES.WAIT_AFTER;
 TIMEOUT_ID = setTimeout(function() { STATE = nextState; onStateChange()}, SETTINGS.timeVisible * 1000);
 break;
 case STATES.WAIT_AFTER:
 CONTENT.innerHTML = "...";
 setCssClass(BOARD, "hide", true);
 nextState.state = STATES.INPUT_ANSWER;
 TIMEOUT_ID = setTimeout(function() { STATE = nextState; onStateChange()}, SETTINGS.timeAfter * 1000);
 break;
 case STATES.INPUT_ANSWER:
 CONTENT.innerHTML = "Input answer:";
 setCssClass(BOARD, "hide", false);
 setCssClass(SUBMIT_BUTTON, "hide", false);
 STATE.userInputPattern = [...EMPTY_BOARD];
 setPattern(STATE.userInputPattern);
 break;
 case STATES.ANSWER_RESULT:
 //
 nextState.testIndex += 1;
 if (nextState.testIndex === SETTINGS.amount) {
 // we're done
 nextState.state = STATES.SHOW_SCORE;
 } else {
 nextState.state = STATES.WAIT_BEFORE;
 }
 TIMEOUT_ID = setTimeout(function() { STATE = nextState; onStateChange() }, ANSWER_RESULT_DELAY_IN_SECONDS * 1000);
 break;
 case STATES.SHOW_SCORE:
 CONTENT.innerHTML = "Test complete: " + STATE.correctAnswers + "/" + SETTINGS.amount + " correct";
 break;
 }
}
// 
function submitAnswer() {
 setCssClass(SUBMIT_BUTTON, "hide", true);
 setCssClass(BOARD, "hide", true);
 nextState = {...STATE};
 //
 // check equality
 correct = patternsAreEqual(STATE.correctPattern, STATE.userInputPattern);
 if (correct) {
 nextState.correctAnswers += 1;
 CONTENT.innerHTML = "&lt;p style='color:green'> Correct!&lt;/p></description></item></channel></rss>