<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Alvin Philips&apos; Blog</title><description>A collection of insights, tutorials, and ramblings.</description><link>https://alvinphilips.com/</link><item><title>Using Yarn and Italian Music to Design a Racetrack</title><link>https://alvinphilips.com/blog/designing-switchgrass/</link><guid isPermaLink="true">https://alvinphilips.com/blog/designing-switchgrass/</guid><description>Switchgrass arose from one of my Game Level Design assignments. We were tasked with creating a racetrack for an arcade racing game. Later, we used the same racetracks to design and program AI opponent...</description><pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;Switchgrass arose from one of my &lt;a href=&quot;https://www.sheridancollege.ca/programs/game-level-design&quot;&gt;Game Level Design&lt;/a&gt; assignments. We were tasked with creating a racetrack for an arcade racing game. Later, we used the same racetracks to design and program AI opponents for a programming course.&lt;/p&gt;
&lt;p&gt;As I am a massive fan of &lt;a href=&quot;https://en.wikipedia.org/wiki/Burnout_3:_Takedown&quot;&gt;Burnout 3: Takedown&lt;/a&gt;, something that my friends and most of my professors (who referenced it during their slides!!) know all too well, this was one of my favourite level design assignments.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a modified and expanded version of the project summary for &lt;a href=&quot;/projects/switchgrass-ai/&quot;&gt;Switchgrass&lt;/a&gt;, which I chose to separate due to length.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Brief&lt;/h3&gt;
&lt;p&gt;We were required to design racetracks in Maya that we brought into Unity, with provided car and camera controls (that we weren&apos;t allowed to modify).
This assignment also required the use of several, distinct paths, namely:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Regular Path&lt;/strong&gt;: The longest path, considered the &apos;default&apos;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shortcut&lt;/strong&gt;: A skill-based shortcut that — if executed correctly — allowed for significant time savings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alternate/Failure&lt;/strong&gt;: Either a split from the main/shortcut OR a consequence of failing to perform the shortcut.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Challenges&lt;/h3&gt;
&lt;p&gt;One aspect that I found immediately limiting (but later appreciated) was the time requirement. A single lap (out of 3) had to be about 30 seconds on the &apos;regular path&apos;.&lt;/p&gt;
&lt;p&gt;Another point of friction was the Maya requirement. I&apos;d been a firm Blenderer since about 2013 (after moving up from SketchUp after several years) and wasn&apos;t too fond of paid, subscription-based software. This assignment changed my perspective on it, however, and I grew to really enjoy the workflow, and especially the plethora of vertex manipulation tools. If only it didn&apos;t cost one billion dollars.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;One of my racetrack iterations, in Maya.&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h2&gt;Design Methodology&lt;/h2&gt;
&lt;p&gt;Arcade racers are like, my thing, so I took this assignment very seriously. Due to two loosely-coincidental factors, the first being that the provided car was red, and the second being that I was watching &lt;a href=&quot;https://www.imdb.com/title/tt1478839/&quot;&gt;The Art of Racing in the Rain&lt;/a&gt; on repeat, being one of newest comfort movies, I decided early on that &lt;em&gt;the car was going to be a Ferrari&lt;/em&gt;. This was of course, not at all asked for in the brief.&lt;/p&gt;
&lt;h3&gt;Research&lt;/h3&gt;
&lt;p&gt;Partly due to being in theatre as a kid, and also because I like to really &apos;immerse&apos; myself into any project I dive into, I started by exploring &lt;em&gt;the feeling&lt;/em&gt; I wanted to evoke in the player as they played my racetrack.&lt;/p&gt;
&lt;p&gt;Since I&apos;d decided on the Ferrari theme early on, I enqueued my favourite Italian-American songs (Louis Prima, Tony Bennett, Bobby Darin, and Dean Martin). I listened to this playlist on repeat for a couple of days.&lt;/p&gt;
&lt;p&gt;I also found two (2) versions of &lt;a href=&quot;https://en.wikipedia.org/wiki/Quando_m%27innamoro&quot;&gt;Quando M&apos;Innamoro&lt;/a&gt;, the song Humperdinck&apos;s &lt;em&gt;A Man Without Love&lt;/em&gt; is based on, that I really enjoyed. These were the recordings by The Sandpipers and Anna Identici.&lt;/p&gt;
&lt;p&gt;What ensued was five hours of listening to these two songs as I played Burnout 3&apos;s &lt;em&gt;Road Rage&lt;/em&gt; mode (in a red car, of course). Something that stood out for me immediately is how I felt myself entering a flow state during the turns. I knew I wanted a very flowy racetrack, with curves and undulating &apos;waves&apos;, and this led me to explore more nature-based areas.&lt;/p&gt;
&lt;h3&gt;Shortcut and Setting&lt;/h3&gt;
&lt;p&gt;Since I had some yarn (for crochet) at my desk in our lab and like using physical tools to aid my design process, I played around with a piece of string, molding it as I imagined myself driving over it, adding turns at the right moment, with short pauses in between. This was also when I thought of having my shortcut be a jump over one of these curved sections, earning you a decent time-save if you were able to perform it correctly.&lt;/p&gt;
&lt;p&gt;Knowing this, the Grand Canyon seemed like a no-brainer, and I imagined driving over some more unpaved roads and also having this long straight bridge section, inspired by designs from state park posters.&lt;/p&gt;
&lt;p&gt;We were also given a set of texture-mapped &apos;base&apos; pieces we could use to build our racetrack. This was like a stripped down modular kit, and being able to quickly try different layouts really helped in my design process.&lt;/p&gt;
&lt;h3&gt;Level Design Document (LDD)&lt;/h3&gt;
&lt;p&gt;Given these constraints and a rough idea of what I wanted to do, I submitted the following as my initial LDD:&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe src=&quot;/docs/gld/Switchgrass_LDD.pdf&quot; height=&quot;800px&quot; width=&quot;100%&quot; title=&quot;Embedded PDF Viewer&quot;&amp;gt;
&amp;lt;p&amp;gt;Your browser does not support iframes. &amp;lt;a href=&quot;/docs/gld/Switchgrass_LDD.pdf&quot;&amp;gt;Download the PDF&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Constructing the Racetrack (and Iteration)&lt;/h2&gt;
&lt;p&gt;This part was the longest and most time-consuming, for obvious reasons. The detailed process of going from conceptual design to actual, playable track and the countless hours of playtesting and iteration would require a lot more room than I had planned for this series.&lt;/p&gt;
&lt;p&gt;I let the rest of this article marinate as a draft as I considered my options, finally settling on a series of short &lt;strong&gt;Challenge -&amp;gt; Solution (or Hack)&lt;/strong&gt; items, with the hope that some of this may prove useful to you.&lt;/p&gt;
&lt;h3&gt;Challenge 1: Player Controller and Camera&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;Our little &apos;Ferrari&apos;&amp;lt;/center&amp;gt;
&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p&gt;The first challenge had to do with the provided (and immutable) vehicle controller and camera. If you&apos;re familiar with the Three C&apos;s (3C) framework of Character, Camera, and Controls, here&apos;s my not-at-all biased take on them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Character (Car): Utterly primitive, all-or-nothing steering and throttle (-1, 0, or 1)&lt;/li&gt;
&lt;li&gt;Camera: A simple follow camera with zero collision/clipping avoidance&lt;/li&gt;
&lt;li&gt;Controls: Bad enough to make you chuck your gamepad at the wall&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The steering was particularly frustrating, because unlike Burnout (or literally any arcade racer post Y2K era), you had very little control over how the car handled. Well &lt;em&gt;almost&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There were two key &apos;secrets&apos; I discovered:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The controls weren&apos;t half bad when using a gamepad. I always have atleast one controller on my desk (usually two, a DualSense and Xbox Controller, from my work playtesting and making a controller-specific glyph sytem for Furlastic Duo).&lt;/li&gt;
&lt;li&gt;On keyboard, if you &apos;feathered&apos; the A and D keys just right, you could take advantage of the vehicle&apos;s ability to skid to get a nice feeling turn. However, this did require having a limited collection of angles that felt good to drive on.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These discoveries allowed me to design a level that was playable on either gamepad or keyboard, with slightly varying skill ceilings, while keeping the &apos;flowy&apos; feel I wanted intact.&lt;/p&gt;
&lt;p&gt;Also, the camera limitation ended up being a bit of a nice design constraint. Since I wanted a canyon but clipping into the wall during normal gameplay was unacceptable, I used flat lower sections (near the road) on the canyon meshes, with them tapering upwards. This helped give them a nice look, with very simple, flat-shaded colours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR #1: Know your enemies (evil player controllers and cameras)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Challenge 2: Track Length&lt;/h3&gt;
&lt;p&gt;Second, however, had to do with with the actual lap times. After settling on basic turn metrics, including angles and their respective lengths that felt right, I first made a rough, completely flat &apos;greybox-ish&apos; version of my primary path to test out timing.&lt;/p&gt;
&lt;p&gt;I found that since the controller was both much slower than most arcade games and harder to steer, the initial layout I&apos;d planned on was just too long. I had to shrink a few areas to maintain the thirty second lap target. Many sections had the feel and pacing I wanted in my design so this was a careful craft of picking what was essential to my experience.&lt;/p&gt;
&lt;p&gt;Here are some of the changes from the rough yarn prototype to a playable version in Unity:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I was able to keep most of what I cared about, but did have to get rid of the first turn, a choice that had some repercussions later (see Challenge #4).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR #2: Kill your babies, but know them well enough to keep what&apos;s core&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Challenge 3: Physics and Collisions&lt;/h3&gt;
&lt;p&gt;Next, since I dealt with a range of inclines and also had overlapping jumps, my actual track mesh, although low-poly, was simply not enough for my &apos;walls&apos;. The &apos;fix&apos; was simply to ensure my walls (from my invisible collision mesh) were tagged as such explicitly and were completely upright, because of how this system was set up.&lt;/p&gt;
&lt;p&gt;As a neat side effect, this allowed me to be quite creative with my visual wall geometry (the canyon), specifically using carefully angled faces so that I could get pretty looking lighting despite the extremely low polycount of the meshes and default shaders for this prototype.&lt;/p&gt;
&lt;p&gt;Here&apos;s the collision mesh, visualized in magenta:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Additionally, I did not want to punish the player too harshly for failing a turn (I&apos;m not &lt;em&gt;THAT&lt;/em&gt; evil), so with the exception of the second hairpin (that I wanted to keep as a &apos;skill test&apos;), I used smooth wall curves with runoff areas, coloured in red.&lt;/p&gt;
&lt;p&gt;This prevented stopping the user abruptly mid-race, perhaps the cardinal sin in fast-paced arcade racing games. It wouldn&apos;t let them maintain a perfect lap time, but it provided some opportunity to regain lost time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR #3: Optimize your collision meshes for &lt;em&gt;players&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Challenge 4: First Hairpin Difficulty&lt;/h3&gt;
&lt;p&gt;Finally, as a consequence of shortening my racetrack to meet the time constraint, I introduced the first hairpin turn much sooner. This turn felt really good, and is probably my favourite turn in the track, because I designed it so that if you follow the racing line just right, you get this nice little view of the bridge.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The issue, however, was that playtest-after-playtest, players complained about this turn being too hard too soon. Suggestions included simplifying the turn, moving the start point, and removing it entirely. These were all fine options, and offered with the bestest of intentions, but my gut told me that I was right about this turn so I kept the track exactly as is.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I just added some tyres. The actual collision mesh did not change at all. Quite remarkably, all complaints about this turn ended after this little change, and I even got praise for the &apos;improvement&apos;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR #4: &lt;s&gt;Ignore Player Feedback because you know better&lt;/s&gt; Providing apparent safety is often enough&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion and Next Steps&lt;/h2&gt;
&lt;p&gt;While I skimmed over the actual creation of the track for brevity, I hope that this expedition into a slightly different design process proves useful to you. Even if you dislike Burnout 3 (i am heartbroken &amp;lt;/3) and Italian music, I encourage experimenting with physical media in your creative processes.&lt;/p&gt;
&lt;p&gt;I almost always have some yarn, rubber bands, and a deck of cards within an arm&apos;s reach, as well as some paper and a nice pencil (I am particular to the now-discontinued &lt;strong&gt;Paper Mate &lt;em&gt;American Classic&lt;/em&gt;&lt;/strong&gt; that I own a sizable-but-dwindling supply of).&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;Look at him zip around!&amp;lt;/center&amp;gt;
&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;/blog/racing-lines-ai/&quot;&gt;next article&lt;/a&gt; outlines how I handled the logical racetrack representation for my track so AI vehicles (like the one above) could race on it.&lt;/p&gt;
&lt;h3&gt;BONUS (Desk Reveal!!)&lt;/h3&gt;
&lt;p&gt;Thanks for reading! Since you made it all this way, here&apos;s a little sneak peek of my desk from around the time I was working on Switchgrass:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;Pardon the cable (mis)management :P&amp;lt;/center&amp;gt;&lt;/p&gt;
</content:encoded></item><item><title>Type-safe Templated Vector Math in C++</title><link>https://alvinphilips.com/blog/template-your-vectors/</link><guid isPermaLink="true">https://alvinphilips.com/blog/template-your-vectors/</guid><description>I&apos;ve worked on several game and engine projects over the years, and have often been constrained to not using external libraries (self-imposed or not). I find myself rewriting a simple math library for...</description><pubDate>Sun, 28 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;I&apos;ve worked on several game and engine projects over the years, and have often been constrained to not using external libraries (self-imposed or not).&lt;/p&gt;
&lt;p&gt;I find myself rewriting a simple math library for almost every such project, usually consisting of Vectors, sometimes Quaternions, and occasionally Matrices. Over the years, I&apos;ve honed my personal approach, making small improvements with each iteration.&lt;/p&gt;
&lt;p&gt;A pain point of mine has been rewriting similar code for my Vectors, based on type (usually floats and some kind of int), and dimension (2D to 4D most of the time).&lt;/p&gt;
&lt;p&gt;While working on my current engine project, I wanted to do things differently, using a template-based approach.&lt;/p&gt;
&lt;h2&gt;Why shouldn&apos;t I just use a library?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You probably should.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I recommend and love the popular &lt;a href=&quot;https://github.com/g-truc/glm&quot;&gt;glm&lt;/a&gt; (for C++) and &lt;a href=&quot;https://nalgebra.rs/&quot;&gt;nalgebra&lt;/a&gt; for Rust.&lt;/p&gt;
&lt;p&gt;The only reasons I can think of where you&apos;d want to do it yourself are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You&apos;ve been given a constraint - academic, professional, some funky new hardware or software environment&lt;/li&gt;
&lt;li&gt;You want to learn how things work :P&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For this project, I found myself in &lt;em&gt;both&lt;/em&gt; camps, so allons-y!&lt;/p&gt;
&lt;h2&gt;Constraints&lt;/h2&gt;
&lt;p&gt;For this project, the following constraints exist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No external libraries (anything in the standard library is fair game)&lt;/li&gt;
&lt;li&gt;Target C++17, must be cross-platform (Windows and macOS)&lt;/li&gt;
&lt;li&gt;Strong type-safety (vector sizes and types should be known at compile time)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To set expectations early, here&apos;s what this article is &lt;strong&gt;NOT&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A production-ready implementation that can be used as-is (&lt;em&gt;you should really use a library if you can&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;A SIMD-focused implementation (although a significant portion was auto-vectorized in my limited analysis on &lt;a href=&quot;https://godbolt.org/&quot;&gt;Compiler Explorer&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A step-by-step guide on Templates, Unions, and C++ building blocks&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;EDIT (2026-01-23): Benchmarking Results&lt;/h2&gt;
&lt;p&gt;I decided to see how far our little vector library could go, when compared to more battle-worn libraries. Using &lt;a href=&quot;https://github.com/RasterDuck/vectormathbench&quot;&gt;this benchmark&lt;/a&gt; with mostly game-related math libraries, I added a (slightly modified) version of the vector implementation described in this article.&lt;/p&gt;
&lt;p&gt;Note that this is a simple (and limited) artificial benchmark and any results should be taken with a grain of salt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Processor&lt;/strong&gt;: Intel 285K @ 3.7GHz
&lt;strong&gt;Platform&lt;/strong&gt;: Microsoft Windows 11 Home 10.0.26200 N/A Build 26200
&lt;strong&gt;Config&lt;/strong&gt;: AVX2 Release on MSVC&lt;/p&gt;
&lt;p&gt;With that disclaimer aside, I was fairly satisfied with the results, as you can see below (&lt;code&gt;iris::math&lt;/code&gt;, in bold). Perhaps I&apos;ll share the draft I have on my project naming conventions soon.
You can also access &lt;a href=&quot;https://github.com/alvinphilips/vectormathbench&quot;&gt;my fork of the suite&lt;/a&gt; and run it for yourself.&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;i am prepared&amp;lt;/summary&amp;gt;&lt;/p&gt;
&lt;h3&gt;&amp;lt;center&amp;gt;Vector Math Benchmark Results (AVX2)&amp;lt;/center&amp;gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ns/op&lt;/th&gt;
&lt;th&gt;op/s&lt;/th&gt;
&lt;th&gt;err%&lt;/th&gt;
&lt;th&gt;total&lt;/th&gt;
&lt;th&gt;benchmark&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.45&lt;/td&gt;
&lt;td&gt;2,243,542,679.48&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;td&gt;6.12&lt;/td&gt;
&lt;td&gt;Store int (reference &apos;no-op&apos;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,714,369,970.21&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.21&lt;/td&gt;
&lt;td&gt;SimpleMath::Vector2 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.83&lt;/td&gt;
&lt;td&gt;1,206,091,546.38&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;td&gt;11.64&lt;/td&gt;
&lt;td&gt;SimpleMath::Vector3 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,711,959,270.25&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.23&lt;/td&gt;
&lt;td&gt;SimpleMath::Vector4 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,478,077,491.36&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;td&gt;5.66&lt;/td&gt;
&lt;td&gt;glm::vec2 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,710,846,262.20&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.24&lt;/td&gt;
&lt;td&gt;glm::vec3 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,489,337,113.89&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;5.63&lt;/td&gt;
&lt;td&gt;glm::vec4 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,479,973,746.77&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;5.69&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec2 addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,701,580,789.86&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.26&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec3 addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,703,061,426.53&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.27&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec4 addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.41&lt;/td&gt;
&lt;td&gt;2,468,506,731.24&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;5.70&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec2d addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.78&lt;/td&gt;
&lt;td&gt;1,274,161,231.64&lt;/td&gt;
&lt;td&gt;0.7%&lt;/td&gt;
&lt;td&gt;11.03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec3d addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,690,558,191.07&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;td&gt;8.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;iris::math::Vec4d addition&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,710,549,154.08&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.23&lt;/td&gt;
&lt;td&gt;DirectX::XMFLOAT2 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;td&gt;1,246,191,237.56&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;11.31&lt;/td&gt;
&lt;td&gt;DirectX::XMFLOAT3 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,702,247,130.14&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.25&lt;/td&gt;
&lt;td&gt;DirectX::XMFLOAT4 addition without Loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.41&lt;/td&gt;
&lt;td&gt;2,467,564,988.05&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;td&gt;5.67&lt;/td&gt;
&lt;td&gt;Vectormath::Vector2 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,702,567,927.81&lt;/td&gt;
&lt;td&gt;0.7%&lt;/td&gt;
&lt;td&gt;8.27&lt;/td&gt;
&lt;td&gt;Vectormath::Vector3 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,710,337,496.98&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;td&gt;8.25&lt;/td&gt;
&lt;td&gt;Vectormath::Vector4 addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,473,288,209.32&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;td&gt;5.67&lt;/td&gt;
&lt;td&gt;move::vec2f addition (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,712,618,430.95&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.21&lt;/td&gt;
&lt;td&gt;move::vec3f addition (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,710,353,644.71&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;td&gt;8.23&lt;/td&gt;
&lt;td&gt;move::vec4f addition (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,486,217,379.27&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;td&gt;5.69&lt;/td&gt;
&lt;td&gt;move::vec2d addition (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,708,940,650.76&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;td&gt;8.21&lt;/td&gt;
&lt;td&gt;move::vec3d addition (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,502,909,765.89&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;5.62&lt;/td&gt;
&lt;td&gt;move::vec4d addition (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,491,335,068.47&lt;/td&gt;
&lt;td&gt;0.9%&lt;/td&gt;
&lt;td&gt;5.64&lt;/td&gt;
&lt;td&gt;rtm::vector4f addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;2,479,731,817.11&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;td&gt;5.64&lt;/td&gt;
&lt;td&gt;rtm::vector4d addition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.94&lt;/td&gt;
&lt;td&gt;126,018,873.38&lt;/td&gt;
&lt;td&gt;3.9%&lt;/td&gt;
&lt;td&gt;113.87&lt;/td&gt;
&lt;td&gt;Complex operation 1 with SimpleMath::Vector[x]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,395,630,651.46&lt;/td&gt;
&lt;td&gt;3.7%&lt;/td&gt;
&lt;td&gt;6.01&lt;/td&gt;
&lt;td&gt;Complex operation 1 with glm::vec[x]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;1,174,060,624.03&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;td&gt;12.03&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 1 with iris::math&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.96&lt;/td&gt;
&lt;td&gt;1,036,713,463.00&lt;/td&gt;
&lt;td&gt;1.5%&lt;/td&gt;
&lt;td&gt;13.75&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 1 with iris::math (double)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.74&lt;/td&gt;
&lt;td&gt;364,842,903.00&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;38.80&lt;/td&gt;
&lt;td&gt;Complex operation 1 with DXM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.33&lt;/td&gt;
&lt;td&gt;430,076,166.48&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;td&gt;32.65&lt;/td&gt;
&lt;td&gt;Complex operation 1 with DXM w/out loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,361,197,614.76&lt;/td&gt;
&lt;td&gt;2.0%&lt;/td&gt;
&lt;td&gt;6.02&lt;/td&gt;
&lt;td&gt;Complex operation 1 with Vectormath&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;1,669,618,203.58&lt;/td&gt;
&lt;td&gt;0.9%&lt;/td&gt;
&lt;td&gt;8.45&lt;/td&gt;
&lt;td&gt;Complex operation 1 with move::math (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.92&lt;/td&gt;
&lt;td&gt;1,092,631,515.03&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;13.00&lt;/td&gt;
&lt;td&gt;Complex operation 1 with move::math (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18.87&lt;/td&gt;
&lt;td&gt;52,985,662.87&lt;/td&gt;
&lt;td&gt;0.8%&lt;/td&gt;
&lt;td&gt;264.70&lt;/td&gt;
&lt;td&gt;Complex operation 2 with SimpleMath::Vector3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,721,804,988.22&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.16&lt;/td&gt;
&lt;td&gt;Complex operation 2 with glm::vec3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,717,131,061.56&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.22&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 2 with iris::math::Vec3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,710,832,249.09&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;8.22&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 2 with iris::math::Vec3d&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.71&lt;/td&gt;
&lt;td&gt;586,098,312.20&lt;/td&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;23.99&lt;/td&gt;
&lt;td&gt;Complex operation 2 with DXM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.93&lt;/td&gt;
&lt;td&gt;1,073,290,249.20&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;td&gt;13.11&lt;/td&gt;
&lt;td&gt;Complex operation 2 with DXM w/out loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.46&lt;/td&gt;
&lt;td&gt;683,847,236.76&lt;/td&gt;
&lt;td&gt;0.2%&lt;/td&gt;
&lt;td&gt;20.56&lt;/td&gt;
&lt;td&gt;Complex operation 2 with Vectormath&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;1,715,777,345.73&lt;/td&gt;
&lt;td&gt;0.7%&lt;/td&gt;
&lt;td&gt;8.21&lt;/td&gt;
&lt;td&gt;Complex operation 2 with move::math (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;td&gt;1,703,203,829.76&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;8.24&lt;/td&gt;
&lt;td&gt;Complex operation 2 with move::math (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.91&lt;/td&gt;
&lt;td&gt;524,425,999.70&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;26.87&lt;/td&gt;
&lt;td&gt;Complex operation 3 with SimpleMath::Vector4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,382,298,606.65&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;td&gt;5.90&lt;/td&gt;
&lt;td&gt;Complex operation 3 with glm::vec4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,371,708,204.57&lt;/td&gt;
&lt;td&gt;2.3%&lt;/td&gt;
&lt;td&gt;6.06&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 3 with iris::math::Vec4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,377,005,412.50&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Complex operation 3 with iris::math::Vec4d&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;1,668,038,597.81&lt;/td&gt;
&lt;td&gt;0.7%&lt;/td&gt;
&lt;td&gt;8.42&lt;/td&gt;
&lt;td&gt;Complex operation 3 with DXM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.61&lt;/td&gt;
&lt;td&gt;1,644,878,927.85&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;8.64&lt;/td&gt;
&lt;td&gt;Complex operation 3 with DXM w/out loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.11&lt;/td&gt;
&lt;td&gt;898,842,061.20&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;td&gt;15.76&lt;/td&gt;
&lt;td&gt;Complex operation 3 with Vectormath&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;1,670,635,891.60&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;td&gt;8.47&lt;/td&gt;
&lt;td&gt;Complex operation 3 with move::math (float)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,383,204,561.79&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;5.97&lt;/td&gt;
&lt;td&gt;Complex operation 3 with move::math (double)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;td&gt;2,398,342,589.80&lt;/td&gt;
&lt;td&gt;2.4%&lt;/td&gt;
&lt;td&gt;5.99&lt;/td&gt;
&lt;td&gt;Complex operation 3 with rtm::vector4f&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;1,661,089,474.72&lt;/td&gt;
&lt;td&gt;1.1%&lt;/td&gt;
&lt;td&gt;8.48&lt;/td&gt;
&lt;td&gt;Complex operation 3 with rtm::vector4d&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Design&lt;/h1&gt;
&lt;p&gt;I wanted to approach this from a top-down perspective, focusing on the API I wanted, worrying about the implementation later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vec2 a = {2.3f, 4.1f};
Vec2 b = Vec2::UnitX(); // Vec2(1.0f, 0.0f);

a.x // 2.3f
b.y // 0.0f
a.z // ERROR: Vec2 does not have a z-component

Vec2 c = a + b; // Vec2(3.3f, 4.1f);
a += b; // a = Vec2(3.3f, 4.1f);
float d = a.Dot(b); // 3.3f

Vec3 e = {1, 2, 3};
e.z // 3.0f (Fine, e has a z-component)

e += b; // ERROR: You can&apos;t add Vec2s and Vec3s directly

Vec2Int f = Vec2Int::UnitY(); // Vec2Int(0, 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;center&amp;gt;Example API&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;h3&gt;What do these (Vec2, Vec3, Vec2Int) all look like then???&lt;/h3&gt;
&lt;p&gt;If you don&apos;t mind spoilers, they&apos;re all just aliases, as so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Typedefs
using Vec2 = Vector&amp;lt;float, 2&amp;gt;;
using Vec3 = Vector&amp;lt;float, 3&amp;gt;;
using Vec4 = Vector&amp;lt;float, 4&amp;gt;;
using Vec2Int = Vector&amp;lt;int32_t, 2&amp;gt;;
using Vec3Int = Vector&amp;lt;int32_t, 3&amp;gt;;
using Vec4Int = Vector&amp;lt;int32_t, 4&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was first exposed to this idea five-or-so years ago, while working on my first sorta-serious game engine project, &lt;a href=&quot;https://github.com/polarvoid/moon-engine/&quot;&gt;moon engine&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was using the &lt;a href=&quot;https://nalgebra.rs/&quot;&gt;nalgebra&lt;/a&gt; library, and was surprised to learn that all their vectors (and matrices) were built on top of a single &lt;a href=&quot;https://docs.rs/nalgebra/latest/nalgebra/base/struct.Matrix.html&quot;&gt;Matrix&lt;/a&gt; struct.&lt;/p&gt;
&lt;h3&gt;Okay... So what&apos;s &lt;code&gt;Vector&amp;lt;???, ???&amp;gt;&lt;/code&gt; like?&lt;/h3&gt;
&lt;p&gt;While we&apos;ll have to modify this signature as we go forward, the basic definition is simply:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N&amp;gt;
struct Vector;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things to note here briefly are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;this would technically allow us to create vectors of &lt;em&gt;any&lt;/em&gt; type&lt;/li&gt;
&lt;li&gt;we can have n-dimensional vectors, where n is a non-negative integer (this would include zero)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While these are definitely fascinating concepts if you&apos;re mathematically-inclined, we&apos;re focusing on a &lt;strong&gt;simple&lt;/strong&gt; game-specific library where concepts such as a Vec3 of &lt;code&gt;std::string&lt;/code&gt; and zero-dimensional vectors aren&apos;t too useful, so we&apos;ll fix this eventually.&lt;/p&gt;
&lt;h3&gt;On &lt;code&gt;constexpr&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Inspired by a rewatch of &lt;a href=&quot;https://www.youtube.com/watch?v=HMB9oXFobJc&quot;&gt;this talk&lt;/a&gt;, another goal for me on this project was maximizing &lt;code&gt;constexpr&lt;/code&gt; use where appropriate.&lt;/p&gt;
&lt;p&gt;As such, &lt;em&gt;most&lt;/em&gt; of the code is &lt;code&gt;constexpr&lt;/code&gt;, including all constructors, operators, and &lt;em&gt;most&lt;/em&gt; additional operations.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Basic Implementation&lt;/h1&gt;
&lt;h2&gt;Helpers&lt;/h2&gt;
&lt;p&gt;A small helper to store the size of a Vector called &lt;code&gt;size&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr static size_t size = N;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Storage (a.k.a. I still want access via .x, .y, etc.)&lt;/h2&gt;
&lt;p&gt;This is one of the bigger gotchas when it comes to switching to a single templated struct to store our vectors. We lose the freedom of having simple element-based access by default.&lt;/p&gt;
&lt;p&gt;While separately you could do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Vec2 { float x, y; };
struct Vec3 { float x, y, z; };
struct Vec4 { float x, y, z, w; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s no easy way to do the same with our shiny new &lt;code&gt;Vector&amp;lt;T, N&amp;gt;&lt;/code&gt; struct. Well, &lt;em&gt;kinda&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The trick is to use even more templates!&lt;/p&gt;
&lt;p&gt;Instead of handing storage on our &lt;code&gt;Vector&lt;/code&gt; directly, we&apos;ll use another templated struct, &lt;code&gt;VecData&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;What is &lt;code&gt;VecData&lt;/code&gt;?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;VecData&lt;/code&gt; is a simple templated struct like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N&amp;gt;
struct VecData {
    T data[N];

    constexpr VecData(): data{} {}
    explicit constexpr VecData(const T value) : data{} {
        for (size_t i = 0; i &amp;lt; N; i++) {
            data[i] = value;
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then update our &lt;code&gt;Vector&lt;/code&gt; definition as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N&amp;gt;
struct Vector : VecData&amp;lt;T, N&amp;gt; {
    using storage_type = VecData&amp;lt;T, N&amp;gt;; // another helper
    // magic for now...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;But that didn&apos;t help...?&lt;/h3&gt;
&lt;p&gt;Although we now have somewhere to store our data, we aren&apos;t really any better off in terms of dimension-specific fields.&lt;/p&gt;
&lt;p&gt;However, we can make use of &lt;a href=&quot;https://en.wikipedia.org/wiki/Generic_programming#Template_specialization&quot;&gt;Template Specializations&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;With the constraint that we&apos;ll provide better support for 2-to-4 dimensional vectors, which are what we&apos;ll mostly be working with anyway, we can do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// For Vec2, Vec2Int, etc.
template &amp;lt;typename T&amp;gt;
struct VecData&amp;lt;T, 2&amp;gt; {
    union {
        T data[2];
        struct { T x, y; };
    };
    constexpr VecData(): data{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0)} {}
    explicit constexpr VecData(const T value): data{value, value} {}
}

// For Vec3, Vec3Int, etc.
template &amp;lt;typename T&amp;gt;
struct VecData&amp;lt;T, 3&amp;gt; {
    union {
        T data[3];
        struct { T x, y, z; };
    };
    constexpr VecData(): data{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0)} {}
    explicit constexpr VecData(const T value): data{value, value, value} {}
}

// For Vec4, Vec4Int, etc.
template &amp;lt;typename T&amp;gt;
struct VecData&amp;lt;T, 4&amp;gt; {
    union {
        T data[4];
        struct { T x, y, z, w; };
    };
    constexpr VecData(): data{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0)} {}
    explicit constexpr VecData(const T value): data{value, value, value, value} {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our actual storage occurs via this anonymous union (my beloved &amp;lt;3) of a fixed-size array and nameless struct containing our individual, named values.&lt;/p&gt;
&lt;p&gt;NOTE: This is &lt;em&gt;technically&lt;/em&gt; UB land (in C++, it&apos;s been well-defined in C for ages) but this is &lt;em&gt;extremely common&lt;/em&gt; in real-world usage (because of the benefits) and is an extension provided by most of the common compilers. I&apos;ve tested this on MSVC and clang personally, which fits my needs. Yours may vary.&lt;/p&gt;
&lt;h3&gt;What about defining all members specifically?&lt;/h3&gt;
&lt;p&gt;Can&apos;t let anything past you, can I? We&apos;ll get to this a little later but supporting the following is essential:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vec2 = {2, 3.2f}; // Exactly two parameters that convert to float
Vec3Int = {4, 12, 32}; // Exactly three parameters that convert to int32_t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does get into slightly more complicated territory though, so I&apos;m going to hold off on the explanation for now.&lt;/p&gt;
&lt;h2&gt;Constructors&lt;/h2&gt;
&lt;p&gt;Our two primary constructs are fairly straightforward, as you&apos;d expect:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Vector : VecData&amp;lt;T, N&amp;gt; {
    ...
    constexpr Vector() : storage_type() {}
    explicit constexpr Vector(T value) : storage_type(value) {}
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use our &lt;code&gt;storage_type&lt;/code&gt; helper to make things a little easier to read.&lt;/p&gt;
&lt;p&gt;We&apos;ll get into our third, &lt;strong&gt;secret&lt;/strong&gt; constructor later. &lt;em&gt;Hope you&apos;re excited!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Element-wise Accessors&lt;/h2&gt;
&lt;p&gt;Accessors via the &lt;code&gt;[]&lt;/code&gt; operator, both const and not provide index-based access to the underlying data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr T&amp;amp; operator[](size_t i) { return this-&amp;gt;data[i]; }
constexpr const T&amp;amp; operator[](size_t i) const { return this-&amp;gt;data[i];}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep things simple and fast, there are no checks here.&lt;/p&gt;
&lt;h2&gt;Arithmetic Operations&lt;/h2&gt;
&lt;p&gt;At a minimum, I want to support the following operations (where A, B, and C are vectors):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C = A + B; // Addition
C = A - B; // Subtraction
C = A * B; // Element-wise Multiplication
C = A / B; // Element-wise Division
C = A * scalar; // Multiply with a scalar
C = A / scalar; // Division by a scalar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As well as their their in-place cousins (+=, /=, etc.)&lt;/p&gt;
&lt;p&gt;This is fairly straightforward, as a simple loop that iterates and operates over each element:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Add two Vectors
constexpr Vector&amp;amp; operator+=(const Vector&amp;amp; v) {
    for (size_t i = 0; i &amp;lt; size; i++) {
        this-&amp;gt;data[i] += v.data[i]; // Similar for -, *, /
    }
    return *this;
}

// Multiply by a scalar of T
constexpr Vector operator*(const T value) const {
    auto result = *this;
    result *= value;
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Dot Product &amp;lt;3&lt;/h2&gt;
&lt;p&gt;Like many others, I &lt;strong&gt;love&lt;/strong&gt; the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dot_product&quot;&gt;Dot product&lt;/a&gt; -- it is my favourite vector operation of all time (along with the 2D &lt;em&gt;Wedge&lt;/em&gt; product).&lt;/p&gt;
&lt;p&gt;Part of its charm lies in how simple it really is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;multiply each corresponding component of two vectors&lt;/li&gt;
&lt;li&gt;sum the results&lt;/li&gt;
&lt;li&gt;profit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In our library, dot products are performed with a simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto scalar = A.Dot(B);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implementation isn&apos;t much more complicated than the previous operations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Inside Vector:
constexpr T Dot(const Vector&amp;amp; v) const {
    auto result = static_cast&amp;lt;T&amp;gt;(0);
    for (size_t i = 0; i &amp;lt; size; i++) {
        result += this-&amp;gt;data[i] * v.data[i];
    }

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cross Product&lt;/h2&gt;
&lt;p&gt;Unlike the wonderful dot product -- that works for all n-dimensional pairs of vectors (or sequences of numbers) -- the Cross-Product is only defined for 3D ones (and 7D??). Since I can only personally visualize up to six dimensions, implementation of a 7D cross-product is left to the reader.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// OK: 3D Cross Product
using V3 = Vector&amp;lt;T, 3&amp;gt;; // for any type T
V3 A;
V3 B;

V3 C = A.Cross(B);

// NOT OK: 4D Cross Product?
using V4 = Vector&amp;lt;T, 4&amp;gt;; // for any type T
V4 X;
V4 Y;
auto Z = X.Cross(Y); // ERROR: Cross() isn&apos;t defined because N != 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As such, this acts as our first major roadblock. We have a templated Vector that could be of &lt;em&gt;any&lt;/em&gt; size, but we only want the Cross Product to be available on Vectors where &lt;code&gt;N&lt;/code&gt; (our dimensions) is 3 (and 7 if you&apos;re up for it).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t M = N, typename = std::enable_if_t&amp;lt;M == 3&amp;gt;&amp;gt;
constexpr Vector&amp;lt;T, M&amp;gt; Cross(const Vector&amp;lt;T, M&amp;gt;&amp;amp; other) const {
    static_assert(M &amp;lt;= N); // We allow Vec4.Cross&amp;lt;3&amp;gt;() but NOT Vec2.Cross&amp;lt;3&amp;gt;()
    return {
        this-&amp;gt;data[1] * other.data[2] - this-&amp;gt;data[2] * other.data[1],
        this-&amp;gt;data[2] * other.data[0] - this-&amp;gt;data[0] * other.data[2],
        this-&amp;gt;data[0] * other.data[1] - this-&amp;gt;data[1] * other.data[0]
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;M&lt;/code&gt;?? &lt;code&gt;enable_if_t&lt;/code&gt;?? &lt;code&gt;static_assert&lt;/code&gt;?? What&apos;s all this then?&lt;/h3&gt;
&lt;p&gt;If you&apos;re new to the land of template metaprogramming, this can be a bit much at first, but it&apos;s fairly straightforward once you understand the individual parts.&lt;/p&gt;
&lt;p&gt;First, we add an additional template parameter &lt;code&gt;M&lt;/code&gt; that defaults to our own dimensions (&lt;code&gt;N&lt;/code&gt;). We then make sure that this is equal to 3.&lt;/p&gt;
&lt;p&gt;This takes advantage of &lt;a href=&quot;https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error&quot;&gt;SFINAE&lt;/a&gt; as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;auto r1 = vec2.Cross(another_vec2); // NOT allowed, M = N = 2, 2 != 3
auto r2 = vec3.Cross(another_vec3); // Allowed, M = N = 3, 3 == 3
auto r3 = vec4.Cross&amp;lt;3&amp;gt;(another_vec3); // Allowed, M = 3, 3 == 3
// Here the final component of the Vec4 is ignored, acting as if it were a Vec3.
// This is safe because for performance reasons, or otherwise, we may want to use a 4D Vector as if it were a 3D one, ignoring the w

// However, not that the following is not permitted:
auto r3 = vec2.Cross&amp;lt;3&amp;gt;(another_vec3); // NOT allowed, although M = 3, 3 == 3, static_assert fails
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four (or more) component vectors may still use the Cross Product, provided they explicitly override &lt;code&gt;M&lt;/code&gt;, ensuring any such usage is intentional. The &lt;code&gt;static_assert&lt;/code&gt; simply makes sure that we&apos;re not trying to do cross products on a Vector with fewer than three components.&lt;/p&gt;
&lt;h2&gt;2D Wedge Product&lt;/h2&gt;
&lt;p&gt;Similarly, I find myself using the 2D Wedge product (a.k.a. perpendicular dot product) frequently, such as in my &lt;a href=&quot;https://alvinphilips.com/blog/racing-lines-ai/&quot;&gt;racetrack sector representation article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The math for this is quite beautiful:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a.x * b.y - b.x * a.y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation is as such, with similar tricks to the Cross Product:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t M = N, typename = std::enable_if_t&amp;lt;M == 2&amp;gt;&amp;gt;
constexpr T Wedge(const Vector&amp;lt;T, M&amp;gt;&amp;amp; other) const {
    static_assert(M &amp;lt;= N);
    return this-&amp;gt;data[0] * other.data[1] - this-&amp;gt;data[1] * other.data[0];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Length and Normalizing&lt;/h2&gt;
&lt;p&gt;Finally, the other common operations we need to handle have to do with size and normalizing.&lt;/p&gt;
&lt;p&gt;Things get a little challenging here, for a few key reasons.&lt;/p&gt;
&lt;p&gt;Firstly, the idea of normalizing (scaling a vector by the inverse of its length), ensuring that all elements fall within the -1 to 1 range only really makes sense if we can represent such values. As such, the idea of normalizing &lt;code&gt;Vec2Int&lt;/code&gt;s and such needs some addressing.&lt;/p&gt;
&lt;h3&gt;Normalized Types&lt;/h3&gt;
&lt;p&gt;There&apos;s rarely a perfect solution and my choice was to convert Vectors to a type that can represent these values when normalizing.&lt;/p&gt;
&lt;p&gt;As such it means the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;original type&lt;/th&gt;
&lt;th&gt;normalized type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;double&lt;/td&gt;
&lt;td&gt;double&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anything else&lt;/td&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hence, a &lt;code&gt;Vec3Int&lt;/code&gt; would transform into a &lt;code&gt;Vec3&lt;/code&gt; when normalized.&lt;/p&gt;
&lt;p&gt;So, we need a way to represent this programmatically. Taking advantage of another type trait, we can use the following as another helper in our &lt;code&gt;Vector&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using normalized_t_type = std::conditional_t&amp;lt;std::is_floating_point_v&amp;lt;T&amp;gt;, T, float&amp;gt;;
using normalized_type = Vector&amp;lt;normalized_t_type, N&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, if T is a floating-point value (such as float or double), it keeps the same type, defaulting to float if not for all other types.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;normalized_t_type&lt;/code&gt; and &lt;code&gt;normalized_type&lt;/code&gt; are simple aliases that make expressing this a lot easier.&lt;/p&gt;
&lt;h3&gt;Measuring Length&lt;/h3&gt;
&lt;p&gt;Since the length of a vector is simply the square root of the sum of it&apos;s squared elements (which is analogous to a dot product with itself), we can separate the two steps, in a sense:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;LengthSquared() that performs the Dot product with itself and retains its type&lt;/li&gt;
&lt;li&gt;Length() that performs the square root and returns a &lt;code&gt;normalized_t_type&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;LengthSquared()&lt;/h4&gt;
&lt;p&gt;For this library, I chose to not handle overflow, to keep things simple, but you may need to.
Since our beloved Dot product already handles the logic, we can simply use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr T LengthSquared() const {
    return Dot(*this);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The squared length is often useful in its own right, since square roots can be expensive. It&apos;s often used in circle-to-circle collision detection, for example, where squared distances are sufficient.&lt;/p&gt;
&lt;h4&gt;Length()&lt;/h4&gt;
&lt;p&gt;Now, all we need is a Length function that takes the square root right?&lt;/p&gt;
&lt;p&gt;Well, yeah, but we run into a particularly frustrating problem here: &lt;code&gt;std::sqrt&lt;/code&gt; is not constexpr. Not in C++17, and not until C++26?!?!&lt;/p&gt;
&lt;p&gt;This trickles up, so normalizing and everything else that requires the length can no longer be constexpr, unless we write our own square root function that &lt;em&gt;is&lt;/em&gt;. I chose not to for this specific project, largely to ensure the implementation was technically sound and identical to std::sqrt for all valid cases (float, double, and long double?).&lt;/p&gt;
&lt;p&gt;With a solemn disposition, here&apos;s our function. The disgraceful, non-constexpr member of the family:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;normalized_t_type Length() const {
    // NOTE: std::sqrt is NOT constexpr in C++17
    return std::sqrt(static_cast&amp;lt;normalized_t_type&amp;gt;(this-&amp;gt;LengthSquared()));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Normalize, Finally&lt;/h3&gt;
&lt;p&gt;We have all the tools to normalize vectors now:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;normalized_type Normalize() const {
    normalized_type result = *this;
    normalized_t_type length = Length();

    if (length == static_cast&amp;lt;normalized_t_type&amp;gt;(0)) {
        return result;
    }

    return result / length;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that because our Length() isn&apos;t constexpr, this won&apos;t be either. The zero-check is simply to avoid pesky division-by-zero errors.&lt;/p&gt;
&lt;p&gt;With that we&apos;re done with the core of our vector implementation!&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Practical Improvements&lt;/h1&gt;
&lt;h2&gt;Gatekeeping Vectors&lt;/h2&gt;
&lt;p&gt;As mentioned previously, we don&apos;t actually want anything to be a &lt;code&gt;Vector&lt;/code&gt;. We can achieve this protection by using a similar trick to our earlier approach with Cross products.&lt;/p&gt;
&lt;p&gt;First, we add the following helper:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N&amp;gt;
using is_valid_vec_t = std::enable_if_t&amp;lt;std::is_arithmetic_v&amp;lt;T&amp;gt; &amp;amp;&amp;amp; (N &amp;gt; 0)&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then update our structs as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Declaration
template &amp;lt;typename T, size_t N, typename = is_valid_vec_t&amp;lt;T, N&amp;gt;&amp;gt;
struct VecData;

// Definition
template &amp;lt;typename T, size_t N, typename&amp;gt;
struct VecData { ... }

// Specializations for 2D, 3D, and 4D Vectors
template &amp;lt;typename T&amp;gt;
struct VecData&amp;lt;T, 2, is_valid_vec_t&amp;lt;T, 2&amp;gt;&amp;gt; // similarly for 3-and-4D Vectors
{...}

// Vector Implementation
template &amp;lt;typename T, size_t N, typename = is_valid_vec_t&amp;lt;T, N&amp;gt;&amp;gt;
struct Vector : VecData&amp;lt;T, N&amp;gt; { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So no more &lt;code&gt;Vector&amp;lt;std::string, 0&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Secret&lt;/em&gt; Constructor&lt;/h2&gt;
&lt;p&gt;It&apos;s finally here!!&lt;/p&gt;
&lt;p&gt;Keep in mind that we&apos;ll still have to do the actual work in &lt;code&gt;VecData&lt;/code&gt;, and we have a helper structure called &lt;code&gt;Initializer&lt;/code&gt; to set things up. We&apos;ll go over it after we look at the constructor on our &lt;code&gt;Vector&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It can look fairly complicated, so I&apos;ll break it down once you&apos;ve had a minute to take all of it in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename... Args, typename = std::enable_if_t&amp;lt;sizeof...(Args) == N
    &amp;amp;&amp;amp; (std::is_convertible_v&amp;lt;Args, T&amp;gt; &amp;amp;&amp;amp; ...)
    &amp;amp;&amp;amp; (std::is_floating_point_v&amp;lt;T&amp;gt; || (!std::is_floating_point_v&amp;lt;Args&amp;gt; &amp;amp;&amp;amp; ...))&amp;gt;&amp;gt;
constexpr Vector(Args... args)
    : storage_type(typename storage_type::Initializer({static_cast&amp;lt;T&amp;gt;(args)...})) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;That&apos;s a lot of dot dot dots&lt;/h3&gt;
&lt;p&gt;Unlike in &lt;a href=&quot;https://www.imdb.com/title/tt0795421/&quot;&gt;Mamma Mia!&lt;/a&gt;, the &lt;code&gt;...&lt;/code&gt;&apos;s here serve a very different purpose.&lt;/p&gt;
&lt;p&gt;Here, they&apos;re used in a few key ways, depending on context:&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;typename... Args&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This is a &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/parameter_pack.html&quot;&gt;variadic template parameter&lt;/a&gt; that allows multiple arguments to be packed into &lt;code&gt;Args&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This allows us to pass in as many arguments as we&apos;d like. However, this isn&apos;t &lt;em&gt;quite&lt;/em&gt; what we want.&lt;/p&gt;
&lt;p&gt;We want to ensure the following as well:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We accept &lt;strong&gt;exactly&lt;/strong&gt; as many parameters as elements we can hold (that is count(args) == N)&lt;/li&gt;
&lt;li&gt;We only accept values that are either &lt;code&gt;T&lt;/code&gt; or can be converted to &lt;code&gt;T&lt;/code&gt; (like ints can be converted to floats, for example).&lt;/li&gt;
&lt;li&gt;We &lt;em&gt;don&apos;t&lt;/em&gt; allow a double to be passed in for int (but we allow the converse).&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;&lt;code&gt;sizeof...(Args)&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The first piece in our &lt;code&gt;std::enable_if_t&lt;/code&gt; ensure that we have exactly N parameters, like we wanted.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sizeof...&lt;/code&gt; operator, like its cousin &lt;code&gt;sizeof&lt;/code&gt; (that returns sizes of types in bytes) gives us the count of the variadic template argument pack, &lt;code&gt;Args&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;(std::is_convertible_v&amp;lt;Args, T&amp;gt; &amp;amp;&amp;amp; ...)&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This is a &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/fold.html&quot;&gt;fold expression&lt;/a&gt;, available since C++17 that makes all of this a lot easier to work with.&lt;/p&gt;
&lt;p&gt;We check each parameter in &lt;code&gt;Args&lt;/code&gt; to make sure it is convertible to T, and logically AND the results.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;(std::is_floating_point_v&amp;lt;T&amp;gt; || (!std::is_floating_point_v&amp;lt;Args&amp;gt; &amp;amp;&amp;amp; ...))&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Similarly, if we&apos;re not a floating point type, we require all arguments to also not be floating point values.&lt;/p&gt;
&lt;p&gt;With these pieces in place, we can now accept the right number and kind of arguments in our constructor.&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;static_cast&amp;lt;T&amp;gt;(args)...&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Here we use another fold expression to cast all of our arguments to &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Well what&apos;s &lt;code&gt;storage_type::Initializer&lt;/code&gt; really?&lt;/h3&gt;
&lt;p&gt;Okay, I guess it&apos;s time to raise the curtains. The final piece of our secret constructor magic lies in our &lt;code&gt;VecData&lt;/code&gt; structs.&lt;/p&gt;
&lt;p&gt;The primary template contains the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Initializer { T data[N]; };
explicit constexpr VecData(Initializer init) : data{} {
    for (size_t i = 0; i &amp;lt; N; i++) {
        this-&amp;gt;data[i] = init.data[i];
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fairly straightforward, an array of &lt;code&gt;N&lt;/code&gt; elements that we accept as a parameter. Similarly, the specializations are updated as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// VecData&amp;lt;T, 2, is_valid_vec&amp;lt;T, 2&amp;gt;&amp;gt;
struct Initializer { T x, y; };
explicit constexpr VecData(Initializer init) : data{init.x, init.y} {}

// VecData&amp;lt;T, 3, is_valid_vec&amp;lt;T, 3&amp;gt;&amp;gt;
struct Initializer { T x, y, z; };
explicit constexpr VecData(Initializer init) : data{init.x, init.y, init.z} {}

// VecData&amp;lt;T, 4, is_valid_vec&amp;lt;T, 4&amp;gt;&amp;gt;
struct Initializer { T x, y, z, w; };
explicit constexpr VecData(Initializer init) : data{init.x, init.y, init.z, init.w} {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We forward our &lt;code&gt;Args&lt;/code&gt;, each casted to &lt;code&gt;T&lt;/code&gt;, into our &lt;code&gt;VecData&amp;lt;T, N&amp;gt;::Initializer&lt;/code&gt; ensuring we use the correct constructor.&lt;/p&gt;
&lt;h2&gt;Constants&lt;/h2&gt;
&lt;p&gt;If you recall our initial API, you might recall the use of &lt;code&gt;Vec2::UnitX()&lt;/code&gt; and &lt;code&gt;Vec2Int::UnitY()&lt;/code&gt; and such.&lt;/p&gt;
&lt;p&gt;I find these particularly useful to express the following: 0. Zero Vector (all elements are zero)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One Vector (all elements are one)&lt;/li&gt;
&lt;li&gt;Unit Vectors for each available Axis&lt;/li&gt;
&lt;li&gt;Direction Vectors (Up, Down, Right, Forward, etc.)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Zero and One Vectors are straightforward and simply:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static constexpr Vector Zero() { return Vector(); }
static constexpr Vector One() { return Vector(static_cast&amp;lt;T&amp;gt;(1)); }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The direction vectors prove challenging due to their reliance on dimensionality, yet again:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vec2::UnitX() // Vec2(1, 0);
Vec2::UnitZ() // ERROR: No z-component
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While we could&apos;ve &lt;em&gt;technically&lt;/em&gt; used more conditional template magic here, I wanted a simple, clean approach that didn&apos;t muddy our beloved &lt;code&gt;Vector&lt;/code&gt; struct with a host of conditional logic. I also knew that I wanted these to be &lt;code&gt;static&lt;/code&gt; and &lt;code&gt;constexpr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hence the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N, typename Vec&amp;gt;
struct VecDirections {};

template &amp;lt;typename T, typename Vec&amp;gt;
struct VecDirections&amp;lt;T, 2, Vec&amp;gt; {
    static constexpr Vec UnitX() { return Vec{static_cast&amp;lt;T&amp;gt;(1), static_cast&amp;lt;T&amp;gt;(0)}; }
    static constexpr Vec UnitY() { return Vec{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(1)}; }

    static constexpr Vec Right() { return UnitX(); }
    static constexpr Vec Left() { return -UnitX(); }
    static constexpr Vec Up() { return UnitY(); }
    static constexpr Vec Down() { return -UnitY(); }
};

template &amp;lt;typename T, typename Vec&amp;gt;
struct VecDirections&amp;lt;T, 3, Vec&amp;gt; {
    static constexpr Vec UnitX() { return Vec{static_cast&amp;lt;T&amp;gt;(1), static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0)}; }
    static constexpr Vec UnitY() { return Vec{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(1), static_cast&amp;lt;T&amp;gt;(0)}; }
    static constexpr Vec UnitZ() { return Vec{static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(0), static_cast&amp;lt;T&amp;gt;(1)}; }

    static constexpr Vec Right() { return UnitX(); }
    static constexpr Vec Left() { return -UnitX(); }
    static constexpr Vec Up() { return UnitY(); }
    static constexpr Vec Down() { return -UnitY(); }
    static constexpr Vec Forward() { return UnitZ(); }
    static constexpr Vec Back() { return -UnitZ(); }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;center&amp;gt;That&apos;s all of it!&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;So we can now do &lt;code&gt;VecDirections&amp;lt;float, 3, Vector&amp;lt;float, 3&amp;gt;&amp;gt;::Forward()&lt;/code&gt; and it works!!&lt;/p&gt;
&lt;p&gt;Well, that&apos;s annoying to type out and not quite the lovely &lt;code&gt;Vec3::Forward()&lt;/code&gt; we wanted though...&lt;/p&gt;
&lt;p&gt;We can finally update our &lt;code&gt;Vector&lt;/code&gt; struct definition (for the last time, I promise) as such:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename T, size_t N, typename = is_valid_vec_t&amp;lt;T, N&amp;gt;&amp;gt;
struct Vector : VecData&amp;lt;T, N&amp;gt;, VecDirections&amp;lt;T, N, Vector&amp;lt;T, N&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The observant few of you may notice that we&apos;re passing in itself(?) to its parent &lt;code&gt;VecDirections&lt;/code&gt;. This is a case of &lt;a href=&quot;https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern&quot;&gt;Curiously Recurring Template Pattern (CRTP)&lt;/a&gt;, a name I only recently discovered, despite using the pattern in my work for several years now. It lets us use the templated &lt;code&gt;Vector&lt;/code&gt; in &lt;code&gt;VecDirections&lt;/code&gt; without creating a circular dependency.&lt;/p&gt;
&lt;h2&gt;Converting, Truncating, Projecting, and Expanding&lt;/h2&gt;
&lt;p&gt;For our final stretch, I wanted to go over a few common helper methods I added to my &lt;code&gt;Vector&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Converting Types&lt;/h3&gt;
&lt;p&gt;A common use case might be to convert a &lt;code&gt;Vec3&lt;/code&gt; to a &lt;code&gt;Vec3Int&lt;/code&gt; or vice-versa, and it helps to have an easy way to do so.&lt;/p&gt;
&lt;p&gt;We use some more template magic and checking to do so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;typename U, typename = std::enable_if_t&amp;lt;std::is_convertible_v&amp;lt;T, U&amp;gt;&amp;gt;&amp;gt;
explicit constexpr operator Vector&amp;lt;U, N&amp;gt;() const {
    Vector&amp;lt;U, N&amp;gt; result;
    for (size_t i = 0; i &amp;lt; N; i++) {
        result[i] = static_cast&amp;lt;U&amp;gt;(this-&amp;gt;data[i]);
    }
    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Truncating Vectors&lt;/h3&gt;
&lt;p&gt;Another common operation is creating a 3D Vector from a 4D one. This is useful if we are multiplying 3D vectors via 4x4 homogeneous transformation matrices, for example.&lt;/p&gt;
&lt;p&gt;Here&apos;s what that looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t M = N - 1, typename = std::enable_if_t&amp;lt;(M &amp;gt; 1)&amp;gt;&amp;gt;
constexpr Vector&amp;lt;T, M&amp;gt; Truncate() const {
    static_assert(M &amp;lt;= N); // Ensure we&apos;re not going out-of-range
    Vector&amp;lt;T, M&amp;gt; result;

    for (size_t i = 0; i &amp;lt; M; i++) {
        result[i] = this-&amp;gt;data[i];
    }

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Defaulting to one dimension less, we allow truncating a larger vector to a positive integer &lt;code&gt;M&lt;/code&gt;, such that &lt;code&gt;0 &amp;lt; M &amp;lt;= N&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Projecting Vectors&lt;/h3&gt;
&lt;p&gt;Similarly common is transforming a 4D vector into a 3D one by taking its last component (the &lt;code&gt;w&lt;/code&gt;) and dividing each of the other elements with it.&lt;/p&gt;
&lt;p&gt;It&apos;s implemented similarly to truncating:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;template &amp;lt;size_t M = N - 1, typename = std::enable_if_t&amp;lt;(M &amp;gt; 0)&amp;gt;&amp;gt;
constexpr Vector&amp;lt;T, M&amp;gt; Project() const {
    static_assert(M &amp;lt; N); // Ensure we&apos;re smaller
    Vector&amp;lt;T, M&amp;gt; result;
    const auto w = this-&amp;gt;data[size - 1];

    const auto inv_w = w == static_cast&amp;lt;T&amp;gt;(0) ? static_cast&amp;lt;T&amp;gt;(0) : static_cast&amp;lt;T&amp;gt;(1) / w;

    for (size_t i = 0; i &amp;lt; M; i++) {
        result[i] = this-&amp;gt;data[i] * inv_w;
    }

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We similarly allow projecting to a smaller vector, of size &lt;code&gt;M&lt;/code&gt;, such that &lt;code&gt;0 &amp;lt; M &amp;lt; N&lt;/code&gt;. Note that I could have defined this purely for 4D vectors (like we did the Cross product), but chose to keep this one generalizable.&lt;/p&gt;
&lt;h3&gt;Expanding Vectors&lt;/h3&gt;
&lt;p&gt;Similar to truncating, I find myself needing to expand a smaller dimensional vector to a larger one frequently.
This time I chose to make it a single element larger, always.&lt;/p&gt;
&lt;p&gt;Here&apos;s the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constexpr Vector&amp;lt;T, N + 1&amp;gt; Expand(const T value = static_cast&amp;lt;T&amp;gt;(0)) const {
    Vector&amp;lt;T, N + 1&amp;gt; result;

    for (size_t i = 0; i &amp;lt; size; i++) {
        result[i] = this-&amp;gt;data[i];
    }
    result[size] = value;

    return result;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;Closing Remarks&lt;/h1&gt;
&lt;p&gt;That was quite the journey, and I thank you for reading if you made it all the way. &lt;a href=&quot;mailto:contact@alvinphilips.com&quot;&gt;Reach out&lt;/a&gt; if you find any errors, want to suggest improvements or suggestions (I&apos;m always looking for more to dive into), or just to liven my inbox.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;may&lt;/em&gt; get around to making this a bit of a series, with articles such as &lt;code&gt;Quaternions&lt;/code&gt;, &lt;code&gt;Matrices&lt;/code&gt;, and more.&lt;/p&gt;
&lt;p&gt;Till we meet again,&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Fin.&lt;/em&gt;&lt;/h2&gt;
</content:encoded></item><item><title>Stack-based Menu Systems</title><link>https://alvinphilips.com/blog/stack-based-menus/</link><guid isPermaLink="true">https://alvinphilips.com/blog/stack-based-menus/</guid><description>UI layout and rendering has always fascinated me, and one of the patterns I seem to always return to while making my games is the good-old Stack-based Menu system. I&apos;ve used this in several of my game...</description><pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Overview&lt;/h1&gt;
&lt;p&gt;UI layout and rendering has always fascinated me, and one of the patterns I seem to always return to while making my games is the good-old Stack-based Menu system.&lt;/p&gt;
&lt;p&gt;I&apos;ve used this in several of my games, including &lt;a href=&quot;https://alvinphilips.com/games/furlastic-duo/&quot;&gt;Furlastic Duo&lt;/a&gt;, &lt;a href=&quot;https://alvinphilips.com/games/axis-shift/&quot;&gt;Axis Shift&lt;/a&gt;, and most recently, &lt;a href=&quot;https://alvinphilips.com/games/jellyfish/&quot;&gt;JellyFish&lt;/a&gt;, as well as several personal projects.&lt;/p&gt;
&lt;h2&gt;What&apos;s a Stack?&lt;/h2&gt;
&lt;p&gt;A &lt;a href=&quot;https://en.wikipedia.org/wiki/Stack_(abstract_data_type)&quot;&gt;Stack&lt;/a&gt; is a linear data structure that uses a &lt;strong&gt;Last In First Out&lt;/strong&gt; (&lt;strong&gt;LIFO&lt;/strong&gt;) methodology. That is, the last element in is the first that&apos;s removed. A stack of plates is a commonly cited analogy. The last element is also called the &lt;strong&gt;top&lt;/strong&gt; of the stack.&lt;/p&gt;
&lt;p&gt;Adding and Removing elements are called &lt;strong&gt;Pushing&lt;/strong&gt; and &lt;strong&gt;Popping&lt;/strong&gt;, respectively. If represented as an array, a &lt;code&gt;Push&lt;/code&gt; would (assuming there&apos;s space) add an element to the end of the array, after the current last element. Similarly, a &lt;code&gt;Pop&lt;/code&gt; operation (assuming it&apos;s not empty) would remove this last element, returning it.&lt;/p&gt;
&lt;p&gt;There&apos;s a third common operation, &lt;code&gt;Peek&lt;/code&gt;, which as the name implies, allows you to check the last element sneakily (&lt;em&gt;not really&lt;/em&gt;) without removing it from memory. This ends up being &lt;em&gt;extremely useful&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Why a Stack?&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;Stack&lt;/strong&gt; ends up representing most common UI navigation patterns extremely well. Navigating to another page, for example, is as simple as &lt;code&gt;Push&lt;/code&gt;-ing a new page on the stack. Similarly, returning to a previous one is a simple &lt;code&gt;Pop&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As long as a single menu is interactable at a given time (the menu on top of the stack), this makes for a simple but fairly versatile system.&lt;/p&gt;
&lt;p&gt;For similar reasons, stacks are also often used to track Undo/Redo states, page navigation at both the browser and sometimes website level, and windowing systems.&lt;/p&gt;
&lt;h2&gt;Why make a Menu System to begin with?&lt;/h2&gt;
&lt;p&gt;Well, there&apos;s a few reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I like having a central system for most of my menus (HUD is usually separate) as it makes tracking things extremely easy.&lt;/li&gt;
&lt;li&gt;Separate menus that share a common interface promote less rigid patterns with dozens of references all over the place.&lt;/li&gt;
&lt;li&gt;I want each Menu to handle a single, presentation-related task, rather than core logic directly.&lt;/li&gt;
&lt;li&gt;Debugging Menu state is as easy as logging current Menu stack.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Implementation Overview&lt;/h1&gt;
&lt;p&gt;Over time, I&apos;ve developed a rough framework for how I like to set up my menu systems. For the purposes of this article, I&apos;ll focus on a concrete Unity implementation, but it&apos;s pretty similar in whatever.&lt;/p&gt;
&lt;p&gt;To ensure brevity, I&apos;ll work within the following constraints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;UI Toolkit&lt;/strong&gt;: exclusively target Unity&apos;s &apos;modern&apos; (albeit half-baked) UI solution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Singleton-based MenuManager&lt;/strong&gt;: while Singleton hate is often warranted, I know that this will truly be a single instance deal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base Menu Class&lt;/strong&gt;: common functionality is shared/abstracted&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Menu&lt;/h2&gt;
&lt;p&gt;The base &lt;code&gt;Menu&lt;/code&gt; class is fairly straightfoward, with public &lt;code&gt;Show()&lt;/code&gt;, &lt;code&gt;Hide()&lt;/code&gt;, and &lt;code&gt;Refresh()&lt;/code&gt; functions, as well as an abstract &lt;code&gt;BuildUI()&lt;/code&gt; that inheritors implement.&lt;/p&gt;
&lt;p&gt;A skeleton &lt;code&gt;Menu&lt;/code&gt; might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract class Menu : MonoBehaviour
{
    private void Initialize() { } // Lazy Initialization
    protected abstract void BuildUI();
    public void Refresh() { } // Calls Initialize() and BuildUI()
    public virtual void Show() { } // Calls Refresh()
    public virtual void Hide() { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Initialization&lt;/h3&gt;
&lt;p&gt;I like to have the option of lazily initializing the Menu, if required. This consists of setting up a reference to the UI Document, and usually, stylesheet setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[RequireComponent(typeof(UIDocument))]
public abstract class Menu : MonoBehaviour
{
    [SerializeField] protected StyleSheet style;

    protected UIDocument Document { get; private set; }
    protected VisualElement Root =&amp;gt; Document.rootVisualElement;

    private bool _isInitialized;

    private void Initialize()
    {
        if (_isInitialized) return;
        Document = GetComponent&amp;lt;UIDocument&amp;gt;();
        if (style is not null &amp;amp;&amp;amp; !Root.styleSheets.Contains(style))
        {
            Root.styleSheets.Add(style);
        }
        _isInitialized = true;
    }
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;RequireComponent&lt;/code&gt; makes Editor setup easier, and prevents misconfiguration. I tend to use the &lt;code&gt;RequireComponent&lt;/code&gt; + &lt;code&gt;GetComponent&lt;/code&gt; pattern fairly frequently in my work.&lt;/p&gt;
&lt;h3&gt;Refresh&lt;/h3&gt;
&lt;p&gt;To ensure up-to-date information is displayed, Menus have a public &lt;code&gt;Refresh()&lt;/code&gt; method that rebuilds UI after initializing the Menu, if required.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void Refresh()
{
    Initialize();
    BuildUI();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Showing and Hiding Menus&lt;/h3&gt;
&lt;p&gt;Showing and hiding Menus is similarly straightforward, although I like having Unity Events (that I can configure in-editor) for when I show or hide a Menu.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public virtual void Show()
{
    var wasActive = gameObject.activeSelf;
    gameObject.SetActive(true);
    Refresh();

    if (wasActive) return;
    onMenuShow?.Invoke();
}

public virtual void Hide()
{
    if (!gameObject.activeSelf) return;

    gameObject.SetActive(false);
    onMenuHide?.Invoke();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MenuManager&lt;/h2&gt;
&lt;p&gt;At it&apos;s core, the &lt;code&gt;MenuManager&lt;/code&gt; is basically just:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MenuManager : PersistentSingleton&amp;lt;MenuManager&amp;gt;
{
    private const int MAX_MENU_STACK_DEPTH = 32;
    private readonly Stack&amp;lt;Menu&amp;gt; _menuStack = new(MAX_MENU_STACK_DEPTH);

    public void Push(Menu menu) { }
    public bool Pop() { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;PersistentSingleton?&lt;/h3&gt;
&lt;p&gt;When I do use Singletons, I like having a single (or a few, in this case) templated Singleton implementations for consistent access.&lt;/p&gt;
&lt;p&gt;This is what my &lt;code&gt;PersistentSingleton&lt;/code&gt; looks like in &lt;a href=&quot;https://alvinphilips.com/games/axis-shift/&quot;&gt;Axis Shift&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract class PersistentSingleton&amp;lt;T&amp;gt; : Singleton&amp;lt;T&amp;gt; where T : Component
{
    protected override void Awake()
    {
        if (_instance is null)
        {
            _instance = this as T;
            DontDestroyOnLoad(gameObject);
        }
        else if (_instance != this)
        {
            Debug.LogWarning($&quot;Duplicate instance of {typeof(T)} destroyed!&quot;, this);
            Destroy(gameObject);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similarly, &lt;code&gt;Singleton&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract class Singleton&amp;lt;T&amp;gt; : MonoBehaviour where T : Component
{
    protected static T _instance;

    public static T Instance
    {
        get
        {
            _instance ??= FindAnyObjectByType&amp;lt;T&amp;gt;();
            if (_instance is not null) return _instance;

            var go = new GameObject(typeof(T).Name);
            _instance = go.AddComponent&amp;lt;T&amp;gt;();
            return _instance;
        }
    }

    public static bool HasInstance() =&amp;gt; _instance is not null;

    protected virtual void Awake()
    {
        if (_instance is null)
        {
            _instance = this as T;
        }
        else if (_instance != this)
        {
            Debug.LogWarning($&quot;Duplicate instance of {typeof(T)} destroyed!&quot;, this);
            Destroy(gameObject);
        }
    }

    protected void OnDestroy()
    {
        if (_instance == this) _instance = null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Showing a new Menu&lt;/h3&gt;
&lt;p&gt;Pushing a Menu simply adds it on top of the Stack, if there&apos;s space, and shows it, hiding the menu underneath:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void PushMenu(Menu menu)
{
    if (_menuStack.Count &amp;gt;= MAX_MENU_STACK_DEPTH)
    {
        Debug.LogWarning($&quot;Menu stack limit reached! Cannot push new menu {menu}&quot;);
        return;
    }

    menu.Show();

    if (_menuStack.TryPeek(out var currentMenu))
    {
        currentMenu.Hide();
    }

    _menuStack.Push(menu);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notably, I &lt;code&gt;Show()&lt;/code&gt; the new Menu before calling &lt;code&gt;Hide()&lt;/code&gt; on the previous one to prevent popping. An intermediary screen (I&apos;ve used a full-screen Canvas in the past) or a transition could also go here.&lt;/p&gt;
&lt;h3&gt;Returning to a Previous Menu&lt;/h3&gt;
&lt;p&gt;Similarly, Popping the newest Menu off the &lt;strong&gt;Stack&lt;/strong&gt; returns us to the previous state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public bool Pop()
{
    if (!_menuStack.TryPop(out var oldMenu)) return false;

    if (_menuStack.TryPeek(out var currentMenu)) currentMenu.Show();

    oldMenu.Hide();
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also returns a boolean, indicating whether the operation was successful.&lt;/p&gt;
&lt;h2&gt;Creating a Menu&lt;/h2&gt;
&lt;p&gt;Here&apos;s a simplified Main Menu:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MainMenu : Menu
{
    protected override void BuildUI()
    {
        var buttons = Root.Q(&quot;Buttons&quot;);
        buttons.Clear();

        var newGameButton = CreateMainMenuButton(buttons, &quot;START GAME&quot;, &quot;Start a New Game&quot;);
        newGameButton.Focus();
        newGameButton.ApplyClickCallbacks(_ =&amp;gt;
        {
            GameManager.Instance.ChangeState(new PlayState());
            newGameButton.SetEnabled(false);
        });

        var optionsButton = CreateMainMenuButton(buttons, &quot;SETTINGS&quot;, &quot;Modify Game Settings&quot;);
        optionsButton.SetEnabled(false);

        var creditsButton = CreateMainMenuButton(buttons, &quot;CREDITS&quot;, &quot;Game Credits&quot;);
        creditsButton.SetEnabled(false);

        var quitButton = CreateMainMenuButton(buttons, &quot;EXIT TO DESKTOP&quot;, &quot;Quit Game&quot;);
        quitButton.ApplyClickCallbacks(_ =&amp;gt; Application.Quit());
    }

    private Button CreateMainMenuButton(VisualElement buttonContainer, string label, string tooltip = null)
    {
        var buttonHelp = Root.Q&amp;lt;Label&amp;gt;(&quot;ButtonHelp&quot;);

        var button = buttonContainer.Create&amp;lt;Button&amp;gt;(&quot;main-menu_option&quot;);
        button.text = label;

        button.RegisterCallback&amp;lt;FocusEvent&amp;gt;(_ =&amp;gt; buttonHelp.text = tooltip);

        return button;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The callbacks do need to be tracked and discarded when required to prevent a memory leak, but this makes for a pretty flexible system overall.&lt;/p&gt;
&lt;h1&gt;Is that it?&lt;/h1&gt;
&lt;p&gt;Well, &lt;em&gt;not quite&lt;/em&gt;. It varies by game, and I do slightly different things each time, like Build Version Text for &lt;a href=&quot;https://alvinphilips.com/games/axis-shift/&quot;&gt;Axis Shift&lt;/a&gt; and Adaptive Controller-aware Sprites for &lt;a href=&quot;https://alvinphilips.com/games/furlastic-duo/&quot;&gt;Furlastic Duo&lt;/a&gt;. I also usually allow optional async (via UniTask).&lt;/p&gt;
&lt;p&gt;Adding async capabilities (and making it thread-safe) are left as exercises to the reader.&lt;/p&gt;
</content:encoded></item><item><title>Representing Racetracks with Sectors</title><link>https://alvinphilips.com/blog/racing-lines-ai/</link><guid isPermaLink="true">https://alvinphilips.com/blog/racing-lines-ai/</guid><description>For Switchgrass, I wanted to use a simple, sector-based approach to represent a model of the track for our AI-driven friends (or enemies) to follow. As I was designing both the racetrack and the AI, I...</description><pubDate>Sun, 26 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;For &lt;a href=&quot;https://alvinphilips.com/projects/switchgrass-ai/&quot;&gt;Switchgrass&lt;/a&gt;, I wanted to use a simple, sector-based approach to represent a model of the track for our AI-driven friends (or enemies) to follow.&lt;/p&gt;
&lt;p&gt;As I was designing both the racetrack and the AI, I had a fair bit of control over the entire process. For the AI implementation, I first skimmed my copy of &lt;a href=&quot;https://www.gameaipro.com/&quot;&gt;Game AI Pro&lt;/a&gt; (I&apos;d been gifted the harcover versions by my old professor!!), specifically &lt;a href=&quot;https://www.gameaipro.com/GameAIPro/GameAIPro_Chapter38_An_Architecture_Overview_for_AI_in_Racing_Games.pdf&quot;&gt;the overview on Racing AI Architecture&lt;/a&gt; which gave me a good starting point for this project.&lt;/p&gt;
&lt;p&gt;After some light reading and exploring a few common solutions, I settled on the following constraints:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;2D Track Representation&lt;/strong&gt;: simplifies math required while sufficient for my racetrack&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Piecewise-Linear Racing Line&lt;/strong&gt;: this gave good results, partly due to the way AI forces were being applied&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Player-based Learning&lt;/strong&gt;: I wanted to leverage a Player&apos;s &apos;run&apos; data to find an optimal line&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple Editor Tooling&lt;/strong&gt;: my racetrack was still in progress and I needed to be able to make changes&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Racetrack Representation&lt;/h2&gt;
&lt;p&gt;Here&apos;s what the track representation I ended up using looks like in practice. For simpilicity&apos;s sake, this only covers the primary path (so no shortcuts!) and also visualizes the optimal racing line generated from my personal runs.&lt;/p&gt;
&lt;p&gt;&amp;lt;center&amp;gt;&lt;/p&gt;
&lt;p&gt;
Sectors are outlined in white, with the blue &lt;a href=&quot;https://en.wikipedia.org/wiki/Racing_line&quot;&gt;racing line&lt;/a&gt; overlaid.&lt;/p&gt;
&lt;p&gt;&amp;lt;/center&amp;gt;&lt;/p&gt;
&lt;p&gt;Note that the physical (mesh) track and logical racetrack used by AI are separate but overlaid. This is to achieve maximum control without having to rely on complex face-tagging systems and also being independent of mesh density.&lt;/p&gt;
&lt;h3&gt;TrackNode&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;TrackNode&lt;/code&gt; consists of a single &apos;node&apos; of the track. It stores a width and the point of the &apos;racing line&apos; on it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class TrackNode : MonoBehaviour
{
    public float width = 10;
    [Range(-1, 1)] public float racingLine;
    public TrackNode next;
    public TrackNode prev;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This component is assigned to GameObjects and their Transforms are used to represent the Track.&lt;/p&gt;
&lt;p&gt;TrackNodes form a &lt;a href=&quot;https://en.wikipedia.org/wiki/Doubly_linked_list&quot;&gt;doubly-linked list&lt;/a&gt; and include utilities to calculate length recursively, and 2D point-in-Quad testing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EDIT (2025-02-13): Linked Lists Galore&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As I was editing this article to add support for an ordered sequence of articles (see links at the bottom of this page), I used a similar approach to represent this relation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface ArticleSchema {
    // ...
    prevArticle?: string; // slug of the previous article
    nextArticle?: string; // slug of the next article
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linked lists are really neat in this aspect, and they&apos;ve saved me more than once in a pinch.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Calculate Track Length&lt;/h3&gt;
&lt;p&gt;I wanted to dynamically calculate track length so I had greater flexibility with shortcuts later, as well as to avoid accidentally using old, invalid data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void CalculateLength()
{
    // Start Node
    if (prev is null)
    {
        length = 0;
        return;
    }

    // Need to (re)calculate the length of previous nodes
    if (prev.length &amp;lt; 0)
    {
        prev.CalculateLength();
    }

    var toPrev = Vector3.Distance(GetRacingLinePoint(), prev.GetRacingLinePoint());
    length = prev.length + toPrev;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The member variable &lt;code&gt;length&lt;/code&gt; was initialized to &lt;code&gt;-1&lt;/code&gt; and updated as necessary.&lt;/p&gt;
&lt;p&gt;The performance-junkies among you may grumble, since this is obviously inferior to a linear approach and could exceed the maximum stack depth on sufficiently complicated tracks. As someone who also enjoys pushing hardware and tools to their limit, these are valid complaints.&lt;/p&gt;
&lt;p&gt;For this prototype, however, I chose to use it regardless due to the ease of use, especially trivial track modifications and having shortcuts that &apos;just work&apos;. Also, since the racing line could be player-defined, via tracking a &apos;run&apos;, this recursive approach gave me good results.&lt;/p&gt;
&lt;h3&gt;Sectors&lt;/h3&gt;
&lt;p&gt;A &apos;Sector&apos; is defined as the area between the current TrackNode and the previous one. To track which sector an AI racer (or the Player) was in, I used a simple point in quad check.&lt;/p&gt;
&lt;p&gt;For this simple approach to work, however, meant track sectors (the space between two consecutive &lt;code&gt;TrackNodes&lt;/code&gt;) were limited to being convex. This was fine, as concave track sectors would provide subpar results for AI steering logic, anyway.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public bool SectorContains(Vector3 testPosition)
{
    // Get corner points (top left, top right, bottom left, bottom right) and test point in 2D
    ....

    // Calculate edge vectors
    var left = tl2D - bl2D;
    var right = tr2D - br2D;
    var top = tr2D - tl2D;
    var bottom = br2D - bl2D;

    // Bounds check using 2D &apos;Cross&apos; (Wedge) product
    return left.Cross(test2D - bl2D) &amp;lt; 0
            &amp;amp;&amp;amp; right.Cross(test2D - tr2D) &amp;gt; 0
            &amp;amp;&amp;amp; top.Cross(test2D - tr2D) &amp;lt; 0
            &amp;amp;&amp;amp; bottom.Cross(test2D - br2D) &amp;gt; 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Alternate Paths and Shortcuts&lt;/h2&gt;
&lt;p&gt;To implement &apos;fairer&apos; AI behaviour, I used the simplest trick in the book -- a railroad-lever style shortcut. This is only activated when the Player has a sufficient lead, ensuring fairer-feeling gameplay. Since the AI was trained on player runs (mine, in particular), it was a fairly fearsome opponent.&lt;/p&gt;
&lt;h3&gt;Branched Track Node&lt;/h3&gt;
&lt;p&gt;A variant of &lt;code&gt;TrackNode&lt;/code&gt; that holds both a &apos;normal&apos; and &apos;alternate&apos; next node. The next node can then be toggled on-demand.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public override bool TryGetNextNode(out TrackNode nextNode)
{
    if (!_useAlternate || alternate is null)
    {
        return base.TryGetNextNode(out nextNode);
    }

    nextNode = alternate;
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Other than some helpful gizmo overrides, it was fairly simple.&lt;/p&gt;
&lt;h3&gt;Probabilistic Track Node&lt;/h3&gt;
&lt;p&gt;I also wanted to support a &apos;failure&apos; path, since the payoff from the shortcut was too good, this would make it a more high-risk situation.&lt;/p&gt;
&lt;p&gt;This was simply a &lt;code&gt;BranchedTrackNode&lt;/code&gt; with fuzzy logic. Uses a simple cutoff to decide whether to use the regular or alternate next node.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public override bool TryGetNextNode(out TrackNode nextNode)
{
    // When asked for the next node, pick either the &apos;regular&apos; one or alternate, based on a set probability.
    SetUseAlternate(Random.value &amp;lt; alternateProbability);
    return base.TryGetNextNode(out nextNode);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Editor Tooling&lt;/h2&gt;
&lt;p&gt;A combination of vertex snapping and custom editor tooling made setting up this track relatively easy.&lt;/p&gt;
&lt;p&gt;The Track Node Editor tool has the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extend the track by holding &lt;code&gt;E&lt;/code&gt; at the end node&lt;/li&gt;
&lt;li&gt;Insert a node between two selected nodes by pressing &lt;code&gt;I&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To make development easier, I bound this tool to the &lt;code&gt;T&lt;/code&gt; key (if a &lt;code&gt;TrackNode&lt;/code&gt; was selected).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;I enjoyed dipping my toes into racetrack-representation, and it serves as a possible precursor to my eventual &lt;a href=&quot;https://en.wikipedia.org/wiki/Burnout_3:_Takedown&quot;&gt;Burnout 3: Takedown&lt;/a&gt; inspired spiritual successor. The decision to spend some time making a simple editor tool paid off, and it&apos;s something I plan to do a lot more of in the future.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;As you can see, we have a fair bit to implement in order to actually have our AI drivers do more than do donuts. I plan to write a third (and final?) post to complete this series.&lt;/p&gt;
&lt;p&gt;Until then,&lt;/p&gt;
&lt;h4&gt;Ciao.&lt;/h4&gt;
&lt;hr /&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;G. Biasillo. “Representing a race track for AI.” In AI Game Programming Wisdom, edited by Steve Rabin&lt;/li&gt;
&lt;li&gt;Simon Tomlinson and Nic Melder. &quot;An Architecture Overview for AI in Racing Games.&quot; In Game AI Pro,
edited by Steve Rabin&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item></channel></rss>