<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Raycasting on Max Bonnefin</title><link>https://bonnef.in/tags/raycasting/</link><description>Recent content in Raycasting on Max Bonnefin</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>max@bonnef.in (Max Bonnefin)</managingEditor><webMaster>max@bonnef.in (Max Bonnefin)</webMaster><copyright>Max Bonnefin</copyright><lastBuildDate>Sat, 04 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://bonnef.in/tags/raycasting/index.xml" rel="self" type="application/rss+xml"/><item><title>Code Reviewing My Decade-Old Raycasting Engine</title><link>https://bonnef.in/posts/raycasting-engine-code-review/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><author>max@bonnef.in (Max Bonnefin)</author><guid>https://bonnef.in/posts/raycasting-engine-code-review/</guid><description>&lt;p&gt;Somewhere around a decade ago I built a raycasting engine in Java for fun. No game engine, no OpenGL, no external libraries. Just Swing, a &lt;code&gt;BufferedImage&lt;/code&gt;, and the &lt;a href="https://lodev.org/cgtutor/raycasting.html" class="external-link" target="_blank" rel="noopener"&gt;DDA algorithm&lt;/a&gt; from Wolfenstein 3D. It had enemies with hand-drawn pixel art, animations, projectiles, A* pathfinding, a state machine, fog effects, primitive enemy AI and a Zelda-style heart system. I was proud of it at the time.&lt;/p&gt;
&lt;img
src="https://bonnef.in/images/posts/raycaster-gameplay.webp"
alt="The raycasting engine running, showing textured walls and fog"
loading="lazy"
width="600"
style="max-width: min(100%, 600px); height: auto; display: block; margin: 0 auto;"
class="responsive-image"
&gt;
&lt;p&gt;I found the source code recently and decided to review it the same way I&amp;rsquo;d review a colleague&amp;rsquo;s merge request. There&amp;rsquo;s something uncomfortable about reading your own old code. You can&amp;rsquo;t blame the author. Every shortcut, every misunderstanding, every &amp;ldquo;I&amp;rsquo;ll fix this later&amp;rdquo; that never got fixed is unambiguously yours. At the same time, it&amp;rsquo;s the clearest possible measure of how far you&amp;rsquo;ve come. The architecture is surprisingly competent in places. The bugs are spectacular in others.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;
The Setup
&lt;a class="heading-link" href="#the-setup"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;22 Java files across 7 packages. No build system. No Maven, no Gradle, not even Ant. Just raw &lt;code&gt;.java&lt;/code&gt; files in a &lt;code&gt;src/&lt;/code&gt; directory, run from whatever IDE I was using at the time. The level data is a PNG image painted in &lt;a href="https://www.gimp.org/" class="external-link" target="_blank" rel="noopener"&gt;GIMP&lt;/a&gt; where pixel colours map to tile types: black for walls, grey for pillars, white for open space. The &lt;code&gt;.xcf&lt;/code&gt; source file is still in the &lt;code&gt;resources/&lt;/code&gt; folder alongside hand-drawn enemy sprites with their &lt;a href="https://www.aseprite.org/" class="external-link" target="_blank" rel="noopener"&gt;Aseprite&lt;/a&gt; files.&lt;/p&gt;
&lt;img
src="https://bonnef.in/images/posts/raycaster-level-map.webp"
alt="The level map PNG showing walls and open spaces as pixel colours"
loading="lazy"
width="400"
style="max-width: min(100%, 400px); height: auto; display: block; margin: 0 auto;"
class="responsive-image"
&gt;
&lt;p&gt;The architecture is a classic game engine pattern: a fixed-timestep game loop in &lt;code&gt;GamePanel&lt;/code&gt; targeting 60 FPS, a stack-based state machine (&lt;code&gt;MenuState&lt;/code&gt; → &lt;code&gt;PlayState&lt;/code&gt; → &lt;code&gt;DeathState&lt;/code&gt;), and a &lt;code&gt;Screen&lt;/code&gt; class that handles all the raycasting. Entities inherit from &lt;code&gt;GameObject&lt;/code&gt; with AABB collision. Clean enough. If I saw this package structure in a merge request I&amp;rsquo;d nod approvingly.&lt;/p&gt;
&lt;h2 id="the-raycaster"&gt;
The Raycaster
&lt;a class="heading-link" href="#the-raycaster"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Screen&lt;/code&gt; class is where it gets interesting. Three rendering passes per frame:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Floor and ceiling: for each row, calculate the distance from camera, sample the appropriate texel from the floor/ceiling texture. Apply fog.&lt;/li&gt;
&lt;li&gt;Walls: for each screen column, cast a ray using DDA. Step through the grid until hitting a wall tile. Calculate perpendicular distance (avoiding fisheye distortion), determine wall height, map texture coordinates, draw the vertical stripe. Store the distance in a z-buffer.&lt;/li&gt;
&lt;li&gt;Sprites: sort by distance (back-to-front), transform into camera space using the inverse camera matrix, project to screen, draw column by column. Only draw if closer than the z-buffer at that column.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All textures are 16×16 pixels. The camera uses a plane vector of -0.66, giving roughly 66° FOV. Standard Wolfenstein setup. The implementation is correct and produces a proper-looking result. I genuinely understood the maths here, which surprised me going back to it&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="the-good"&gt;
The Good
&lt;a class="heading-link" href="#the-good"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The separation of concerns holds up. Rendering knows nothing about game logic. State management is clean. The map-as-PNG approach is genuinely clever for a project of this scale: paint a level in GIMP, save as PNG, the engine reads it. No custom file format, no level editor to maintain.&lt;/p&gt;
&lt;p&gt;The utility classes (&lt;code&gt;ArrayUtils&lt;/code&gt;, &lt;code&gt;RenderUtils&lt;/code&gt;, &lt;code&gt;AStar&lt;/code&gt;) all have private constructors that throw &lt;code&gt;IllegalStateException&lt;/code&gt; on instantiation. Defensive pattern against accidental instantiation of static utility classes. I was clearly reading &lt;a href="https://www.oreilly.com/library/view/effective-java/9780134686097/" class="external-link" target="_blank" rel="noopener"&gt;Effective Java&lt;/a&gt; at the time&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The sprite rendering uses a proper z-buffer for occlusion behind pillars, walls and other sprites. Most amateur raycasters skip this and just draw sprites on top of everything. Getting the inverse camera matrix transform right for the sprite projection takes some thought; younger me actually seems to have understood the linear algebra.&lt;/p&gt;
&lt;img
src="https://bonnef.in/images/posts/raycaster-enemies.webp"
alt="Enemy sprites rendered with proper z-buffer occlusion"
loading="lazy"
width="600"
style="max-width: min(100%, 600px); height: auto; display: block; margin: 0 auto;"
class="responsive-image"
&gt;
&lt;h2 id="the-bad"&gt;
The Bad
&lt;a class="heading-link" href="#the-bad"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;code&gt;continue&lt;/code&gt; statement inside a for-each loop that iterates over the closed and open sets in the &lt;a href="https://en.wikipedia.org/wiki/A*_search_algorithm" class="external-link" target="_blank" rel="noopener"&gt;A*&lt;/a&gt; implementation. The intent is to skip nodes already explored. The problem: the &lt;code&gt;continue&lt;/code&gt; applies to the &lt;em&gt;inner&lt;/em&gt; loop (the for-each), not the outer loop. Nodes in the closed set are never actually skipped. The pathfinding still produces correct paths because it eventually explores everything, but it&amp;rsquo;s doing exponentially more work than necessary. &lt;a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm" class="external-link" target="_blank" rel="noopener"&gt;Dijkstra&lt;/a&gt; wearing an A* costume.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Enemy.update()&lt;/code&gt; throws &lt;code&gt;IllegalStateException&lt;/code&gt; instead of being abstract. The &lt;code&gt;Enemy&lt;/code&gt; class should be abstract. It isn&amp;rsquo;t. If someone added a new enemy type and forgot to override &lt;code&gt;update()&lt;/code&gt;, they&amp;rsquo;d get a runtime exception instead of a compile error.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DeathState&lt;/code&gt; and &lt;code&gt;MenuState&lt;/code&gt; are nearly identical. Copy-pasted with different title strings. A single class with a configuration parameter would have sufficed.&lt;/p&gt;
&lt;p&gt;Movement speed is per-frame, not per-time. If the game loop runs slower than 60 FPS, everything moves slower. No delta-time multiplication. Classic beginner mistake. It only &amp;ldquo;works&amp;rdquo; because the game is simple enough that the machine never drops frames.&lt;/p&gt;
&lt;h2 id="the-ugly"&gt;
The Ugly
&lt;a class="heading-link" href="#the-ugly"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the line that made me audibly wince:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ImageIO.read(&lt;span style="color:#ff7b72"&gt;new&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;File(&lt;span style="color:#a5d6ff"&gt;&amp;#34;resources/textures/hand.png&amp;#34;&lt;/span&gt;))&lt;span style="color:#6e7681"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is inside the render loop. It reads the hand texture from disk every single frame while the player is casting a spell. At 60 FPS, that&amp;rsquo;s 60 file system reads per second for a static 16×16 PNG. It should be loaded once and cached. I can only assume I added the spell effect late in development and never went back to fix the loading.&lt;/p&gt;
&lt;p&gt;The fog calculation in &lt;code&gt;renderSprites()&lt;/code&gt; creates a new &lt;code&gt;Color&lt;/code&gt; object per pixel to extract RGB components. In the hot rendering path. Every pixel of every sprite allocates and immediately discards an object. The GC must have been working overtime. I could have just used bit shifts: &lt;code&gt;(pixel &amp;gt;&amp;gt; 16) &amp;amp; 0xFF&lt;/code&gt; for red, &lt;code&gt;(pixel &amp;gt;&amp;gt; 8) &amp;amp; 0xFF&lt;/code&gt; for green, &lt;code&gt;pixel &amp;amp; 0xFF&lt;/code&gt; for blue. No allocation needed.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a magic number &lt;code&gt;8355711&lt;/code&gt; used for ceiling darkening. It&amp;rsquo;s &lt;code&gt;0x7F7F7F&lt;/code&gt;. A bitmask to right-shift each colour channel by 1 (halving brightness). No comment, no named constant. Just a raw integer that happens to work because of how RGB is packed into an &lt;code&gt;int&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And my favourite: &lt;code&gt;textures.get(textures.indexOf(texture))&lt;/code&gt;. This looks up a texture by finding its index, then retrieves it by that index. It returns the same object you started with. A no-op that somehow survived into the final version.&lt;/p&gt;
&lt;h2 id="what-id-change-now"&gt;
What I&amp;rsquo;d Change Now
&lt;a class="heading-link" href="#what-id-change-now"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Aside from using an off-the-shelf engine or at least a proper graphics library, if I wanted to write my own raycaster again:&lt;/p&gt;
&lt;p&gt;The rendering should use direct pixel array manipulation (&lt;code&gt;int[]&lt;/code&gt; backing the BufferedImage via &lt;code&gt;DataBufferInt&lt;/code&gt;) instead of &lt;code&gt;setRGB()&lt;/code&gt;/&lt;code&gt;getRGB()&lt;/code&gt; calls, which box values through &lt;code&gt;Color&lt;/code&gt;. Younger me was close to this pattern already but didn&amp;rsquo;t fully commit.&lt;/p&gt;
&lt;p&gt;The enemy base class should be abstract. The state machine could be an enum-based approach instead of a full stack (for a game this simple, you never actually need more than one state active). The A* should use a &lt;code&gt;HashSet&lt;/code&gt; for the closed list instead of a &lt;code&gt;List&lt;/code&gt; with linear search.&lt;/p&gt;
&lt;p&gt;The architecture is pretty much sound, though. The same package structure, the same rendering pipeline, the same state machine pattern; I&amp;rsquo;d use all of these again in a similar project. The mistakes are implementation-level, not design-level. That&amp;rsquo;s reassuring. It means I understood the problem even when I was getting the details wrong.&lt;/p&gt;
&lt;h2 id="the-verdict"&gt;
The Verdict
&lt;a class="heading-link" href="#the-verdict"&gt;
&lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;
&lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;For a project built for fun with no dependencies, no engine, and no prior experience with raycasting, it works. The maths is right. The architecture is right. The details are wrong in entertaining ways. It&amp;rsquo;s exactly the kind of code I&amp;rsquo;d expect from someone who understood the theory, had read the right resources, and hadn&amp;rsquo;t yet learned the hard lessons about garbage collection, file I/O, and the difference between &amp;ldquo;it works&amp;rdquo; and &amp;ldquo;it works efficiently.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I should probably fix the A* bug. It&amp;rsquo;s been broken for a decade but if I start fixing bugs and making improvements I&amp;rsquo;m not sure where I&amp;rsquo;d stop.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://lodev.org/cgtutor/raycasting.html" class="external-link" target="_blank" rel="noopener"&gt;Lode&amp;rsquo;s Raycasting Tutorial&lt;/a&gt; — the definitive resource for this style of raycaster. Almost certainly what I followed at the time.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/effective-java/9780134686097/" class="external-link" target="_blank" rel="noopener"&gt;Effective Java, 3rd Edition&lt;/a&gt; by Joshua Bloch. Item 4: &amp;ldquo;Enforce noninstantiability with a private constructor.&amp;rdquo;&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item></channel></rss>