<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.7.0">Jekyll</generator><link href="https://fouramgames.com/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://fouramgames.com/blog/" rel="alternate" type="text/html" /><updated>2019-01-27T17:42:31-05:00</updated><id>https://fouramgames.com/blog/</id><title type="html">4AM Games Blog</title><subtitle>4AM Games blog - gamedev and messing around with technology.</subtitle><author><name>Ohmnivore</name></author><entry><title type="html">Unity FPSSample - Takeaways from the “Deep dive into networking” talk</title><link href="https://fouramgames.com/blog/unity-fps-sample" rel="alternate" type="text/html" title="Unity FPSSample - Takeaways from the &quot;Deep dive into networking&quot; talk" /><published>2019-01-27T09:23:45-05:00</published><updated>2019-01-27T09:23:45-05:00</updated><id>https://fouramgames.com/blog/unity-fps-sample</id><content type="html" xml:base="https://fouramgames.com/blog/unity-fps-sample">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#intro&quot; id=&quot;markdown-toc-intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#overview&quot; id=&quot;markdown-toc-overview&quot;&gt;Overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#new-stuff&quot; id=&quot;markdown-toc-new-stuff&quot;&gt;New stuff&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#asymmetric-tick-rates&quot; id=&quot;markdown-toc-asymmetric-tick-rates&quot;&gt;Asymmetric tick rates&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#delta-encoding-using-frame-prediction&quot; id=&quot;markdown-toc-delta-encoding-using-frame-prediction&quot;&gt;Delta encoding using frame prediction&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#data-stream-structure&quot; id=&quot;markdown-toc-data-stream-structure&quot;&gt;Data stream structure&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#rtt-measurement&quot; id=&quot;markdown-toc-rtt-measurement&quot;&gt;RTT measurement&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#5862-fps-trick&quot; id=&quot;markdown-toc-5862-fps-trick&quot;&gt;58/62 FPS trick&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#server-sleep-time&quot; id=&quot;markdown-toc-server-sleep-time&quot;&gt;Server sleep time&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#clock-drift&quot; id=&quot;markdown-toc-clock-drift&quot;&gt;Clock drift&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fractional-ticks-on-client&quot; id=&quot;markdown-toc-fractional-ticks-on-client&quot;&gt;Fractional ticks on client&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#classic&quot; id=&quot;markdown-toc-classic&quot;&gt;Classic&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#reliable-udp-implementation&quot; id=&quot;markdown-toc-reliable-udp-implementation&quot;&gt;Reliable UDP implementation&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#past-input-redundancy&quot; id=&quot;markdown-toc-past-input-redundancy&quot;&gt;Past input redundancy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#lag-compensation-for-hitscan&quot; id=&quot;markdown-toc-lag-compensation-for-hitscan&quot;&gt;Lag compensation for hitscan&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#client-side-interpolation&quot; id=&quot;markdown-toc-client-side-interpolation&quot;&gt;Client-side interpolation&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#delta-encoding-compression&quot; id=&quot;markdown-toc-delta-encoding-compression&quot;&gt;Delta encoding compression&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#open-questions&quot; id=&quot;markdown-toc-open-questions&quot;&gt;Open questions&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#server-reconciliation&quot; id=&quot;markdown-toc-server-reconciliation&quot;&gt;Server reconciliation&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#client-side-interpolation-time&quot; id=&quot;markdown-toc-client-side-interpolation-time&quot;&gt;Client-side interpolation time&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#multiple-client-ticks-for-a-single-server-tick&quot; id=&quot;markdown-toc-multiple-client-ticks-for-a-single-server-tick&quot;&gt;Multiple client ticks for a single server tick&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#fractional-ticks&quot; id=&quot;markdown-toc-fractional-ticks&quot;&gt;Fractional ticks&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;/h1&gt;
&lt;p&gt;I recently watched Peter Andreasen’s talk about the Unity FPSSample (&lt;strong&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=k6JTaFE7SYI&quot;&gt;Deep dive into networking for Unity’s FPS Sample game&lt;/a&gt;&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;FPSSample is a reference implementation of a classic Quake-like multiplayer FPS game. The source code is available at &lt;strong&gt;&lt;a href=&quot;https://github.com/Unity-Technologies/FPSSample&quot;&gt;https://github.com/Unity-Technologies/FPSSample&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It was super valuable to learn about networking techniques I had never even heard about. Also I appreciate the many pieces of wisdom sprinkled throughout the session!&lt;/p&gt;

&lt;p&gt;This post has a few purposes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;To highlight the techniques I haven’t seen discussed anywhere else&lt;/li&gt;
  &lt;li&gt;To summarize the talk&lt;/li&gt;
  &lt;li&gt;To note some of my own remaining questions - for later investigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;
&lt;p&gt;FPSSample uses the classic Quake model - where clients send only their inputs and the server sends regular snapshots of the game state.&lt;/p&gt;

&lt;p&gt;The server updates the game state and acts on all players’ inputs simultaneously at a fixed rate - this approach was chosen for its simplicity relative to the one where the server manages a separate game state for each player.&lt;/p&gt;

&lt;h1 id=&quot;new-stuff&quot;&gt;New stuff&lt;/h1&gt;
&lt;p&gt;Here’s stuff I learned and that I haven’t seen discussed elsewhere.&lt;/p&gt;

&lt;h4 id=&quot;asymmetric-tick-rates&quot;&gt;Asymmetric tick rates&lt;/h4&gt;
&lt;p&gt;Asymmetric update rates between client and server isn’t news but Peter discusses this topic in depth.&lt;/p&gt;

&lt;p&gt;So yeah, a client can have a tick rate that is different from the server’s, and a render rate that is different from its tick rate.&lt;/p&gt;

&lt;p&gt;A few paragraphs below expand on that.&lt;/p&gt;

&lt;h4 id=&quot;delta-encoding-using-frame-prediction&quot;&gt;Delta encoding using frame prediction&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://gafferongames.com&quot;&gt;Glenn Fiedler&lt;/a&gt;&lt;/strong&gt; has a few articles that explain delta encoding for snapshot compression.&lt;/p&gt;

&lt;p&gt;The idea is to express all values that we need to replicate as a difference from the ones in the last snapshot that the client acknowledged. It reduces the magnitude of values involved so that smaller data types can be used (and eliminates values that didn’t change).&lt;/p&gt;

&lt;p&gt;FPSSample develops this concept one step further. Instead of using the last acknowledged snapshot as the baseline for encoding, it extrapolates a snapshot using the past three snapshots and uses that as the baseline. Thanks to shared code on the server and client, the client is also able to construct this same baseline on its end.&lt;/p&gt;

&lt;p&gt;This is a very powerful optimization. Consider just position and velocity replication for example: assuming velocity is unchanged, changes in position don’t need to be sent anymore!&lt;/p&gt;

&lt;h4 id=&quot;data-stream-structure&quot;&gt;Data stream structure&lt;/h4&gt;
&lt;p&gt;In data serialization the structure of the data is omitted. Only the data itself gets sent.&lt;/p&gt;

&lt;p&gt;So for example a naive serialized structure for a position update sent by the server might look like:&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Player {
    ID: 1
    position: (x, y, z)
}
Player {
    ID: 2,
    position: (x, y, z)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But the client already knows about the existence of Players 1 and 2 from a previously received snapshot where they were created. So a serialized structure like the following is sufficient:&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;(x, y, z)
(x, y, z)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The client was able to re-construct the structure of the data using its knowledge of the game state. Look at all the bandwidth that was saved!&lt;/p&gt;

&lt;h4 id=&quot;rtt-measurement&quot;&gt;RTT measurement&lt;/h4&gt;
&lt;p&gt;I’m not 100% confident but here’s my understanding of the sample’s RTT measurement:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Host sends packet, saves local time&lt;/li&gt;
  &lt;li&gt;Peer receives packet, saves local time&lt;/li&gt;
  &lt;li&gt;Peer processes packet, sends a response packet which includes the elapsed time between reception and response (processing time)&lt;/li&gt;
  &lt;li&gt;Host receives response, uses saved time to approximate RTT + substracts peer’s reported processing time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important detail is that the peer transmits the processing time to the host.&lt;/p&gt;

&lt;h4 id=&quot;5862-fps-trick&quot;&gt;58/62 FPS trick&lt;/h4&gt;
&lt;p&gt;Players often run games at multiples of 30 FPS (30, 60, 120 are common).&lt;/p&gt;

&lt;p&gt;If we take for example a client and a server both running at 60 FPS, we will observe flip-flopping behavior where some server tick will receive no player input, and the next tick will receive two (if the client and server are ticking in sync).&lt;/p&gt;

&lt;h4 id=&quot;server-sleep-time&quot;&gt;Server sleep time&lt;/h4&gt;
&lt;p&gt;The team implemented a tick rate mechanism that gives ample time for the host machine to sleep (to save on compute costs).&lt;/p&gt;

&lt;p&gt;It uses a low-resolution timer and nudges the next frame’s timing based on this frame’s performance.&lt;/p&gt;

&lt;p&gt;Though I wonder how the irregularity in tick rate affects the smoothness on the clients. Theoretically it should be practically insignificant thanks to the interpolation buffers.&lt;/p&gt;

&lt;h4 id=&quot;clock-drift&quot;&gt;Clock drift&lt;/h4&gt;
&lt;p&gt;As of the time of the talk:&lt;/p&gt;

&lt;p&gt;The team found that Unity’s framerate limiter accumulated drift over long periods of time.&lt;/p&gt;

&lt;p&gt;This may have been addressed by the framerate nudging system described in the previous paragraph, or fixed some other way.&lt;/p&gt;

&lt;h4 id=&quot;fractional-ticks-on-client&quot;&gt;Fractional ticks on client&lt;/h4&gt;
&lt;p&gt;I don’t understand this technique yet.&lt;/p&gt;

&lt;p&gt;The overall idea is that when the client has a tick rate which is different from the server’s tick rate, it still tries to match it to the server’s rate for smoothness by “catching up the small leftover”.&lt;/p&gt;

&lt;h1 id=&quot;classic&quot;&gt;Classic&lt;/h1&gt;
&lt;p&gt;Here’s topics that have been well described on the net by popular docs from Glenn Fiedler, Gabriel Gambetta, and Valve to name just a few.&lt;/p&gt;

&lt;h4 id=&quot;reliable-udp-implementation&quot;&gt;Reliable UDP implementation&lt;/h4&gt;
&lt;p&gt;Peter compares UDP and TCP, describes the dangers of UDP, and describes the sample’s reliable UDP implementation (for example: sequence IDs and ack bitfields).&lt;/p&gt;

&lt;h4 id=&quot;past-input-redundancy&quot;&gt;Past input redundancy&lt;/h4&gt;
&lt;p&gt;Old but gold! The sample sends the past 3 input commands alongside the current one to minimize the impact of a dropped packet.&lt;/p&gt;

&lt;h4 id=&quot;lag-compensation-for-hitscan&quot;&gt;Lag compensation for hitscan&lt;/h4&gt;
&lt;p&gt;When the server checks for hit registration it rewinds the world around the player by [RTT + player’s interpolation time]. The client regularly updates the server about its interpolation time, that’s how the server knows.&lt;/p&gt;

&lt;h4 id=&quot;client-side-interpolation&quot;&gt;Client-side interpolation&lt;/h4&gt;
&lt;p&gt;Used for framerate-independant smooth rendering and to de-jitter incoming snapshots.&lt;/p&gt;

&lt;h4 id=&quot;delta-encoding-compression&quot;&gt;Delta encoding compression&lt;/h4&gt;
&lt;p&gt;There was a summary of the delta encoding technique (against the last snapshot that was acknowledged by the client). Delta encoding through frame prediction was used instead in the end.&lt;/p&gt;

&lt;h1 id=&quot;open-questions&quot;&gt;Open questions&lt;/h1&gt;
&lt;p&gt;Here’s stuff that wasn’t mentioned or discussed at length but that I’m curious about.&lt;/p&gt;

&lt;h4 id=&quot;server-reconciliation&quot;&gt;Server reconciliation&lt;/h4&gt;
&lt;p&gt;Peter mentions that if the server doesn’t receive a player’s input command(s) for a given tick, then that player is assumed to have done nothing during that period. This is only one of the many possibilities for de-sync to happen between client and server.&lt;/p&gt;

&lt;p&gt;It’s also mentioned that a full rollback is performed on the client at the beginning of every frame.&lt;/p&gt;

&lt;p&gt;I’m curious if/how desyncs are smoothed out.&lt;/p&gt;

&lt;h4 id=&quot;client-side-interpolation-time&quot;&gt;Client-side interpolation time&lt;/h4&gt;
&lt;p&gt;I’m curious how the client determines what its interpolation time should be, and how exactly it’s shared with the server.&lt;/p&gt;

&lt;h4 id=&quot;multiple-client-ticks-for-a-single-server-tick&quot;&gt;Multiple client ticks for a single server tick&lt;/h4&gt;
&lt;p&gt;So given that client and server can have different tick rates, the client might send multiple inputs for a single server tick.&lt;/p&gt;

&lt;p&gt;Are the client inputs “merged” together and executed in one shot? Or does the server step through them sequentially? Or something else entirely?&lt;/p&gt;

&lt;h4 id=&quot;fractional-ticks&quot;&gt;Fractional ticks&lt;/h4&gt;
&lt;p&gt;How does this work?&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="online-multiplayer" /><category term="unity" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2019/01/fps_sample.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - The Escher Sandwich</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-the-escher-sandwich" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - The Escher Sandwich" /><published>2019-01-24T11:55:28-05:00</published><updated>2019-01-24T11:55:28-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-the-escher-sandwich</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-the-escher-sandwich">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#the-sandwich-theory&quot; id=&quot;markdown-toc-the-sandwich-theory&quot;&gt;The Sandwich Theory&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-escher-sandwich&quot; id=&quot;markdown-toc-the-escher-sandwich&quot;&gt;The Escher Sandwich&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#draw-priority-overview&quot; id=&quot;markdown-toc-draw-priority-overview&quot;&gt;Draw priority overview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#the-problem&quot; id=&quot;markdown-toc-the-problem&quot;&gt;The problem&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#circuit-behavior---in-emulation&quot; id=&quot;markdown-toc-circuit-behavior---in-emulation&quot;&gt;Circuit behavior - in emulation&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#render-loop&quot; id=&quot;markdown-toc-render-loop&quot;&gt;Render loop&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#obj-pre-processing-step&quot; id=&quot;markdown-toc-obj-pre-processing-step&quot;&gt;OBJ pre-processing step&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#obj-post-processing-step&quot; id=&quot;markdown-toc-obj-post-processing-step&quot;&gt;OBJ post-processing step&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#obj-pre-processing-visualization&quot; id=&quot;markdown-toc-obj-pre-processing-visualization&quot;&gt;OBJ pre-processing visualization&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#visualization-continued&quot; id=&quot;markdown-toc-visualization-continued&quot;&gt;Visualization continued&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the fourteenth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;the-sandwich-theory&quot;&gt;The Sandwich Theory&lt;/h1&gt;
&lt;p&gt;I figured that using painter’s algorithm the draw priority would be:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;First floor tilemap&lt;/li&gt;
  &lt;li&gt;First floor drop shadows&lt;/li&gt;
  &lt;li&gt;First floor walls and objects, depth sorted&lt;/li&gt;
  &lt;li&gt;Second floor tilemap&lt;/li&gt;
  &lt;li&gt;Second floor drop shadows&lt;/li&gt;
  &lt;li&gt;Second floor objects, depth sorted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also illustrated in &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture#frame-breakdown&quot;&gt;the frame breakdown in article #5&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The core idea is that the two tilemaps sandwich everything else that belongs on the first floor.&lt;/p&gt;

&lt;h1 id=&quot;the-escher-sandwich&quot;&gt;The Escher Sandwich&lt;/h1&gt;
&lt;p&gt;I confirmed the theory with mGBA’s debug tools.&lt;/p&gt;

&lt;p&gt;(Quick aside - Drop shadows are treated no different from usual objects, I was wrong about that one. They have no “reserved” draw order.)&lt;/p&gt;

&lt;p&gt;And so I went on my merry way until I noticed this situation:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/enhance_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At play here are two walls (outlined), the top tilemap, and the drop shadow (outlined):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/enhance_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All walls should be sandwiched between the tilemaps. But here the left one is drawn on top of the drop shadow which is drawn on top of the tilemap. How?&lt;/p&gt;

&lt;p&gt;I checked the draw priorities again and they’re not the issue.&lt;/p&gt;

&lt;p&gt;Time to hit the books…&lt;/p&gt;

&lt;h1 id=&quot;draw-priority-overview&quot;&gt;Draw priority overview&lt;/h1&gt;
&lt;p&gt;Draw priority is done in hardware as depicted below (OBJ Processing, BG Processing, and Draw Priority Evaluation circuits). Behavior tied to hardware usually has some distinctive limitations… so we can already expect some gotchas.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/cpu.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;From the &lt;b&gt;AGB Programming Manual&lt;/b&gt;, Version 1.1 &lt;/i&gt;&lt;/p&gt;

&lt;p&gt;The manual describes its usage as follows:&lt;/p&gt;

&lt;p&gt;(OBJ is equivalent to “hardware sprite”. OBJ number is the location of the sprite in memory, from 0 to 127. BG is equivalent to “tilemap”.)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/priority.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;From the &lt;b&gt;AGB Programming Manual&lt;/b&gt;, Version 1.1 &lt;/i&gt;&lt;/p&gt;

&lt;h1 id=&quot;the-problem&quot;&gt;The problem&lt;/h1&gt;
&lt;p&gt;So in summary two things affect a sprite’s draw priority:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Draw priority (the lower priority is drawn first)&lt;/li&gt;
  &lt;li&gt;In-memory location for tie-breaking (the lower in-memory location is drawn first)&lt;/li&gt;
  &lt;li&gt;Sprites should be grouped in-memory by draw priority (if there’s a separating tilemap) (and the groups themselves ordered by draw priority)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Number 3 is the stern cautionary paragraph near the bottom.&lt;/p&gt;

&lt;p&gt;So let’s observe the problematic case again:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/enhance_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s the in-memory sprite layout that I can see with mGBA’s inspection tools:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/sprite_layout_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;BG 2 &amp;amp; 3 are just markers for reference, they're not contained in the sprite layout&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;This layout doesn’t respect cautionary paragraph #3, and that trips up the hardware and causes the Escher sandwich. It doesn’t seem to be intentional as the draw priorities are still set correctly. Even the in-memory locations are correctly set for tie-breaking the character and the drop shadow, it’s only the grouping which is wrong.&lt;/p&gt;

&lt;p&gt;Here’s what a safe layout would like for the same scene:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/sprite_layout_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;circuit-behavior---in-emulation&quot;&gt;Circuit behavior - in emulation&lt;/h1&gt;
&lt;p&gt;Ok fair enough. But why?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I won’t touch on the actual circuits, I’ll only rely on &lt;strong&gt;&lt;a href=&quot;https://github.com/mgba-emu/mgba&quot;&gt;mGBA’s source code&lt;/a&gt;&lt;/strong&gt;. I made sure to verify that the Escher sandwich occurs identically in the mGBA, VisualBoyAdvance-M, and no$gba emulators, as well as in Nintendo’s very own GBA emulator on the 2DS. So I’m fairly certain that whatever logic mGBA is emulating behaves accurately with respect to hardware.&lt;/p&gt;

&lt;h3 id=&quot;render-loop&quot;&gt;Render loop&lt;/h3&gt;
&lt;p&gt;Here’s an abstract summary of the render loop (only the parts which are relevant to the situation):&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;For each of the screen’s horizontal lines (called scanlines, there’s 160 of them):
    &lt;ul&gt;
      &lt;li&gt;For every OBJ in memory (from location 0 to 127):
        &lt;ul&gt;
          &lt;li&gt;Pre-process current OBJ (only if its bounds intersect this scanline)&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;For every priority level (from 0 to 3):
        &lt;ul&gt;
          &lt;li&gt;If any OBJs were pre-processed for this priority level
            &lt;ul&gt;
              &lt;li&gt;Post-process OBJs for this priority level&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;Render tilemaps for this priority level&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;obj-pre-processing-step&quot;&gt;OBJ pre-processing step&lt;/h3&gt;
&lt;p&gt;mGBA keeps a buffer of pixels for the current scanline. This buffer is reserved for OBJs only. A pixel in this buffer contains the RGB color as well as some rendering flags (they include the OBJ’s priority level).&lt;/p&gt;

&lt;p&gt;This step renders the OBJ to that buffer. For every pixel it checks the priorities against the existing pixel (if any) before writing, using it like a depth buffer or Z buffer on top of a color buffer. Once this step executes for all OBJs mGBA has essentially finished rendering OBJs and prioritizing them amongst themselves (for the current scanline).&lt;/p&gt;

&lt;p&gt;HOWEVER transparent pixels are handled in a &lt;em&gt;very&lt;/em&gt; counter-intuitive manner. They don’t modify the current pixel’s color at all but still “promote” the current pixel (if it has been written to at least once before) to the transparent pixel’s priority level. Here it is in the source code: &lt;strong&gt;&lt;a href=&quot;https://github.com/mgba-emu/mgba/blob/9b1c3e53964a7c5afe704c57dbd656e4cfe801d1/src/gba/renderers/software-obj.c#L75&quot;&gt;in a function that renders a single pixel to the scanline buffer&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If this transparent pixel priority level promotion didn’t exist then it would have been just a classic depth buffer without any OBJ memory location shenanigans.&lt;/p&gt;

&lt;p&gt;It must be either a hardware particularity or a conscious decision on Nintendo’s part to support alpha masking for example.&lt;/p&gt;

&lt;p&gt;Don’t worry if this is a bit unclear so far, I added visualizations a bit further down which help a lot.&lt;/p&gt;

&lt;h3 id=&quot;obj-post-processing-step&quot;&gt;OBJ post-processing step&lt;/h3&gt;
&lt;p&gt;This step iterates through the OBJ scanline buffer and copies the pixels belonging to the specified priority level into a general scanline buffer. Tilemaps also write to the general scanline buffer in subsequent steps.&lt;/p&gt;

&lt;p&gt;The OBJs’ in-memory locations don’t matter at all in this step as the ordering information is only being obtained from the OBJ scanline buffer, from the earlier pre-processing step.&lt;/p&gt;

&lt;h3 id=&quot;obj-pre-processing-visualization&quot;&gt;OBJ pre-processing visualization&lt;/h3&gt;
&lt;p&gt;I improvised some logging for mGBA to visually “step through” the construction of the OBJ scanline buffer.&lt;/p&gt;

&lt;p&gt;I’ll use our familiar scene as an example:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/obj_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s inspect scanline 91:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/obj_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s the memory layout of the OBJs we care about:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/obj_3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;(The cyan pixels represent transparent pixels)&lt;/i&gt;&lt;/p&gt;

&lt;h3 id=&quot;visualization-continued&quot;&gt;Visualization continued&lt;/h3&gt;
&lt;p&gt;Here’s the construction of the OBJ scanline buffer step by step:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/obj_4.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The thin white lines separate the steps. The thick white line separates the color buffer from the priority level buffer (in grayscale: lighter gray = lower OBJ priority = drawn on top of darker gray pixels).&lt;/p&gt;

&lt;p&gt;Here are the steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;The left wall OBJ is drawn with priority 3.&lt;/li&gt;
  &lt;li&gt;Transparent pixels from the character OBJ are drawn with priority 2. They don’t modify the color buffer BUT they promote the overlapping pixels that the left wall drew to priority 2 (woops).&lt;/li&gt;
  &lt;li&gt;Opaque and transparent pixels from the drop shadow OBJ are drawn with priority 2. The shadow gets clipped on the left side by the wall pixels that got promoted in the previous step (oh no!).&lt;/li&gt;
  &lt;li&gt;The right wall OBJ is drawn with priority 3. It gets clipped by the right side of the drop shadow OBJ which is the intuitive behavior - no complaints.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When it comes to compositing the OBJs with the tilemaps in a later process, we find that a portion of the left wall now renders on top of the wrong tilemap due to its level order promotion (oh no!). This is what the GBA programming manual warns us about specifically.&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The game’s code doesn’t conform to the platform’s specs which can cause draw priority issues.&lt;/p&gt;

&lt;p&gt;The bug could have remained in the final release because:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The developers most likely had bigger fish to fry. This bug has a very limited impact all things considered.&lt;/li&gt;
  &lt;li&gt;The specific manual used by the developers maybe didn’t contain the cautionary paragraph. It wasn’t present in the first few editions of the English manual for example.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This is the last article in the series (excluding the appendices) but it’s not over yet! I haven’t finished writing all the previous articles so I’ll be publishing them gradually.&lt;/p&gt;

&lt;p&gt;You can subscribe to the &lt;strong&gt;&lt;a href=&quot;https://eepurl.com/dfT7z5&quot;&gt;mailing list&lt;/a&gt;&lt;/strong&gt; to be notified when a new article appears.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Walls</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-walls" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Walls" /><published>2019-01-23T05:15:38-05:00</published><updated>2019-01-23T05:15:38-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-walls</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-walls">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#coordinate-system&quot; id=&quot;markdown-toc-coordinate-system&quot;&gt;Coordinate system&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#walls&quot; id=&quot;markdown-toc-walls&quot;&gt;Walls&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#directions&quot; id=&quot;markdown-toc-directions&quot;&gt;Directions&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#visibility&quot; id=&quot;markdown-toc-visibility&quot;&gt;Visibility&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#re-using-affine-matrices-optimization&quot; id=&quot;markdown-toc-re-using-affine-matrices-optimization&quot;&gt;Re-using affine matrices (optimization)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#affine-matrix&quot; id=&quot;markdown-toc-affine-matrix&quot;&gt;Affine matrix&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#view-space-position&quot; id=&quot;markdown-toc-view-space-position&quot;&gt;View-space position&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#note---tilemap-and-wall-base-scale&quot; id=&quot;markdown-toc-note---tilemap-and-wall-base-scale&quot;&gt;Note - Tilemap and wall base scale&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#note---wall-height-scale&quot; id=&quot;markdown-toc-note---wall-height-scale&quot;&gt;Note - Wall height scale&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the seventh article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;coordinate-system&quot;&gt;Coordinate system&lt;/h1&gt;
&lt;p&gt;Quick reminder (just in case): &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-notes#coordinate_system&quot;&gt;here’s a link to the coordinate system&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h1 id=&quot;walls&quot;&gt;Walls&lt;/h1&gt;

&lt;h2 id=&quot;directions&quot;&gt;Directions&lt;/h2&gt;
&lt;p&gt;All walls are axis-aligned. (Except for the 45-degree walls but they will be discussed in a later article.)&lt;/p&gt;

&lt;p&gt;It means that walls only face 4 directions which I call:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Y_PLUS&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;X_MINUS&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Y_MINUS&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;X_PLUS&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/wall_dirs.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;visibility&quot;&gt;Visibility&lt;/h2&gt;
&lt;p&gt;Our walls represent boxes so it’s safe to assume that if a given side is visible, then the opposite is invisible. (Ex: &lt;code class=&quot;highlighter-rouge&quot;&gt;X_MINUS&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;X_PLUS&lt;/code&gt; visibility is exclusive)&lt;/p&gt;

&lt;p&gt;This fact can be used for an optimization: once a side is determined to be visible, the processing of the opposite one can be skipped. Note that there’s a special case where two opposite sides can be both not visible, it happens when the dot product for both is 0.&lt;/p&gt;

&lt;p&gt;The usual algorithm for determining if a wall is facing away from a view is to check if the dot product of its normal against (the opposite of) the camera’s direction is smaller than 0.&lt;/p&gt;

&lt;p&gt;In short: &lt;code class=&quot;highlighter-rouge&quot;&gt;visible = dot(wallNormal, -cam.directionIn2D) &amp;gt; 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our wall directions are the +/-X axis and +/-Y axis.&lt;/p&gt;

&lt;p&gt;Then the dot product for the &lt;code class=&quot;highlighter-rouge&quot;&gt;X_PLUS&lt;/code&gt; direction is &lt;code class=&quot;highlighter-rouge&quot;&gt;dot(vec(0, 1), -cam.directionIn2D)&lt;/code&gt;. It simplifies to &lt;code class=&quot;highlighter-rouge&quot;&gt;-cam.directionIn2D.y&lt;/code&gt;. The dot product for &lt;code class=&quot;highlighter-rouge&quot;&gt;X_MINUS&lt;/code&gt; is &lt;code class=&quot;highlighter-rouge&quot;&gt;cam.directionIn2D.y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, for &lt;code class=&quot;highlighter-rouge&quot;&gt;Y_PLUS&lt;/code&gt; it’s &lt;code class=&quot;highlighter-rouge&quot;&gt;-cam.directionIn2D.x&lt;/code&gt; and for &lt;code class=&quot;highlighter-rouge&quot;&gt;Y_MINUS&lt;/code&gt; it’s &lt;code class=&quot;highlighter-rouge&quot;&gt;cam.directionIn2D.x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This translates to:&lt;/p&gt;
&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;WallVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WallDirection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Y_PLUS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camDirXY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;WallVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WallDirection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Y_MINUS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camDirXY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;WallVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WallDirection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_PLUS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camDirXY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;WallVisible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WallDirection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X_MINUS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;camDirXY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another way to look at it is to visualize the camera angle from the top, where the relation between wall directions, camera direction, and XY axes is clear:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/visibility.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;re-using-affine-matrices-optimization&quot;&gt;Re-using affine matrices (optimization)&lt;/h2&gt;
&lt;p&gt;Once the visible wall directions are determined, the corresponding affine matrices are computed.&lt;/p&gt;

&lt;p&gt;We re-use these affine matrices for every wall of the same facing direction because an individual wall’s position doesn’t affect its projection (only wall normal and camera direction matter - as will be described below).&lt;/p&gt;

&lt;p&gt;So all visible &lt;code class=&quot;highlighter-rouge&quot;&gt;X_PLUS&lt;/code&gt; walls share the same &lt;code class=&quot;highlighter-rouge&quot;&gt;X_PLUS&lt;/code&gt; affine matrix for example.&lt;/p&gt;

&lt;p&gt;This is a significant optimization: each frame we only compute 1 or 2 affine matrices instead of 1 per wall.&lt;/p&gt;

&lt;h2 id=&quot;affine-matrix&quot;&gt;Affine matrix&lt;/h2&gt;
&lt;p&gt;My initial approach was very awkward, I’m glad that I &lt;strong&gt;&lt;a href=&quot;https://github.com/Ohmnivore/battle/commit/516d843b467bd2453edc6d55b90ad19424d9ddfc&quot;&gt;replaced it&lt;/a&gt;&lt;/strong&gt; by a much simpler one recently.&lt;/p&gt;

&lt;p&gt;In a nutshell we need to find the matrix that will transform the wall texture’s local coordinate space to the coordinate space of the texture as it would appear on screen.&lt;/p&gt;

&lt;p&gt;Here’s an illustration of the full process:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/affine.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can pre-compute the world-space texture X axis (wall tangents). The texture Y axis points straight down (-Z in world space). So we create a matrix which uses those two as basis vectors.&lt;/p&gt;

&lt;p&gt;Next we transform that matrix into camera space (&lt;code class=&quot;highlighter-rouge&quot;&gt;inCamSpace = mat3(camTransformInverse) * inWorldSpace&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Then we perform the orthographic projection (by simply discarding any depth-axis information). Finally we flip the vertical axis to account for screen/canvas coordinates.&lt;/p&gt;

&lt;p&gt;I’m fairly confident that this method would support camera roll and arbitrary wall orientations. I’m tempted to revisit the approach I used earlier for tilemaps so that they could support these things too. Not that it matters for &lt;em&gt;Sonic Battle&lt;/em&gt; of course, since it doesn’t use those bells and whistles.&lt;/p&gt;

&lt;p&gt;Note: on the real hardware we wouldn’t be done just yet. There’s &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-notes#affine&quot;&gt;a few gotchas with affine matrices&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;view-space-position&quot;&gt;View-space position&lt;/h2&gt;
&lt;p&gt;The view space positions of individual walls are computed the same as for tilemaps: &lt;code class=&quot;highlighter-rouge&quot;&gt;camera.transformMatrixInverse * worldSpacePosition&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;note---tilemap-and-wall-base-scale&quot;&gt;Note - Tilemap and wall base scale&lt;/h2&gt;
&lt;p&gt;All dimensions of the game world (save for the characters) have been doubled! Tilemaps and most wall sprites are exactly x2 larger than their source bitmaps. That really tripped me up until I figured it out with mGBA’s inspection tools.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/base_scale.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;This wall is rendered ~64 pixels wide at native screen resolution&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/base_scale_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;But its bitmap is only 32 pixels wide (the &quot;Double Size&quot; flag is unrelated by the way)&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/base_scale_3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;The wall behind it though has the same width as its bitmap (it's only scaled in height) - so there's some flexibility here&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;This could have been done for performance reasons, to use ~4 times fewer hardware sprites to cover the same area (which reduces load on the entire renderer).&lt;/p&gt;

&lt;p&gt;Or for artistic reasons, to reduce the visual noise.&lt;/p&gt;

&lt;h2 id=&quot;note---wall-height-scale&quot;&gt;Note - Wall height scale&lt;/h2&gt;
&lt;p&gt;For gameplay reasons (to prevent the action from vertically overflowing the screen) all walls are additionally scaled in height to make them shorter. It also looks better that way.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/height_scale_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Just the basic x2 scale - that's really tall&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2019/01/height_scale_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;x2 scale and additional height scale (I eyeballed the value, it's ~1.4)&lt;/i&gt;&lt;/p&gt;

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

&lt;p&gt;The next article will discuss the depth sorting of the walls.&lt;/p&gt;

&lt;p&gt;You can subscribe to the &lt;strong&gt;&lt;a href=&quot;https://eepurl.com/dfT7z5&quot;&gt;mailing list&lt;/a&gt;&lt;/strong&gt; to be notified when a new article appears.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Drop Shadows</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-drop-shadows" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Drop Shadows" /><published>2019-01-20T05:55:28-05:00</published><updated>2019-01-20T05:55:28-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-drop-shadows</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-drop-shadows">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#general-concept&quot; id=&quot;markdown-toc-general-concept&quot;&gt;General concept&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#investigation&quot; id=&quot;markdown-toc-investigation&quot;&gt;Investigation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#overlap-detection&quot; id=&quot;markdown-toc-overlap-detection&quot;&gt;Overlap detection&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#edge-case&quot; id=&quot;markdown-toc-edge-case&quot;&gt;Edge case&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#draw-order&quot; id=&quot;markdown-toc-draw-order&quot;&gt;Draw order&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#draw-order---reality&quot; id=&quot;markdown-toc-draw-order---reality&quot;&gt;Draw order - reality&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the twelvth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;general-concept&quot;&gt;General concept&lt;/h1&gt;
&lt;p&gt;Drop shadows are sprites that are drawn under the feet of &lt;em&gt;Sonic Battle&lt;/em&gt; characters. They follow the X and Y center positions of their characters and snap to the terrain height on the Z axis. They serve as a visual clue of the characters’ positions.&lt;/p&gt;

&lt;p&gt;The shadows are scaled uniformly in width and height according to the character’s distance to the ground under their feet. It’s done through frame animation so no affine transformations are used at all (besides the usual translation). The base image is already vertically squished to approximate the effect of the camera’s pitch.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/drop_x2.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;investigation&quot;&gt;Investigation&lt;/h1&gt;
&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;mGBA&lt;/code&gt; emulator’s sprite inspection tool confirms the absence of affine transformations:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/drop_shadow_transform.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It also shows that there are 3 frames for the scaling animation:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/drop_shadow_spritesheet.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I mistakenly implemented the scaling using an affine matrix, and let me tell you - the aliasing is terrible due to the GBA’s small screen resolution. Frame animation was possibly used here to avoid that specific issue, if not for performance reasons.&lt;/p&gt;

&lt;h1 id=&quot;overlap-detection&quot;&gt;Overlap detection&lt;/h1&gt;
&lt;p&gt;The physics engine of &lt;em&gt;Sonic Battle&lt;/em&gt; is out of the scope of this series. But something minimal needed to be implemented for the drop shadow vertical positioning. I would later use the same algorithm for preventing character-wall intersection.&lt;/p&gt;

&lt;p&gt;I was interested in detecting circle overlap against an axis-aligned rectangle (AABB). The circle would represent a character’s contour on the XY plane, and the AABB would be a section of the high ground (or top floor). I found &lt;strong&gt;&lt;a href=&quot;https://gamedev.stackexchange.com/users/83001/jack-ding&quot;&gt;StackOverflow user Jack Ding&lt;/a&gt;&lt;/strong&gt;’s solution (&lt;strong&gt;&lt;a href=&quot;https://gamedev.stackexchange.com/a/120897&quot;&gt;https://gamedev.stackexchange.com/a/120897&lt;/a&gt;&lt;/strong&gt;) particularly intuitive and elegant so I chose to implement it.&lt;/p&gt;

&lt;p&gt;It’s not representitive of the true game’s physics engine, most likely.&lt;/p&gt;

&lt;h1 id=&quot;edge-case&quot;&gt;Edge case&lt;/h1&gt;
&lt;p&gt;It’s possible for characters to be right above rising/falling edges of the terrain. A realistic shadow would need to be split into parts and projected on a number of surfaces. That’s a detail I can live without, and so could the game’s developers: in the edge case the top terrain wins the tie and the shadow appears to levitate.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/drop_shadow_edge.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;draw-order&quot;&gt;Draw order&lt;/h1&gt;
&lt;p&gt;My implementation assumes that because shadows are projected on terrain they should be the very first to be drawn after the said terrain appears on screen. Nothing in the game world is situated between the terrain and shadows so they don’t need to be sorted either (but they do need to be grouped by floor).&lt;/p&gt;

&lt;p&gt;Callback to article 5, &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture&quot;&gt;The big picture&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h1 id=&quot;draw-order---reality&quot;&gt;Draw order - reality&lt;/h1&gt;
&lt;p&gt;The draw order is a tad more complicated than I had thought initially.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/enhance_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/12/enhance_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What! How? Our established draw order here is &lt;code class=&quot;highlighter-rouge&quot;&gt;1st floor tilemap -&amp;gt; walls -&amp;gt; 2nd floor tilemap -&amp;gt; 2nd floor shadow -&amp;gt; 2nd floor character&lt;/code&gt;. But in reality there’s a wall sprite that is drawn &lt;em&gt;after&lt;/em&gt; the 2nd floor shadow? Unacceptable I say.&lt;/p&gt;

&lt;p&gt;I did some digging. Turns out that my draw order theory was largely correct - it corresponds to the original developers’ intentions. However they ran afoul of a tricky hardware constraint. The consequences are whacky draw priorities like the one I captured above.&lt;/p&gt;

&lt;p&gt;I wrote another post that goes into a lot of detail about the issue: &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-the-escher-sandwich&quot;&gt;The Escher Sandwich&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;The next article will discuss the culling of all the walls and objects contained in a scene.&lt;/p&gt;

&lt;p&gt;You can subscribe to the &lt;strong&gt;&lt;a href=&quot;https://eepurl.com/dfT7z5&quot;&gt;mailing list&lt;/a&gt;&lt;/strong&gt; to be notified when a new article appears.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Inverted Walls</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-inverted-walls" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Inverted Walls" /><published>2018-11-25T05:55:28-05:00</published><updated>2018-11-25T05:55:28-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-inverted-walls</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-inverted-walls">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#intro&quot; id=&quot;markdown-toc-intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#inverted-normals&quot; id=&quot;markdown-toc-inverted-normals&quot;&gt;Inverted normals&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#conclusion&quot; id=&quot;markdown-toc-conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the ninth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;/h1&gt;
&lt;p&gt;The &lt;em&gt;Holy Summit&lt;/em&gt; map in &lt;em&gt;Sonic Battle&lt;/em&gt; is unlike the others. Instead of containing box-like platforms it has two inset holes in the ground (for lack of better terms).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/ruins.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;A typical map&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/inverse_normals.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;The outlier&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;When I was studying the renderer it gave me a bit of a scare because it broke my assumption that all maps were roughly the same - that there’s a floor, then walls for small boxes, then another floor to cover the top of the boxes. I feared that it would require custom logic to support.&lt;/p&gt;

&lt;h1 id=&quot;inverted-normals&quot;&gt;Inverted normals&lt;/h1&gt;
&lt;p&gt;Previously we had been looking at boxes from the outside where their wall normals pointed outwards. Now we’re looking “inside” the boxes. So the trick is to invert the normals by placing the walls in the opposite of the usual directions. It doesn’t affect performance either!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/normals1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Normal normals&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/normals2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Inverted normals&lt;/i&gt;&lt;/p&gt;

&lt;h1 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;On the surface it looked like &lt;em&gt;Holy Summit&lt;/em&gt; was going to require lots of custom logic. But it ended up just being a creative usage of the renderer by the original developers. Hats off to them!&lt;/p&gt;

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

&lt;p&gt;The next article will discuss the 45-degree angle walls which are used in octogonal boxes.&lt;/p&gt;

&lt;p&gt;You can subscribe to the &lt;strong&gt;&lt;a href=&quot;https://eepurl.com/dfT7z5&quot;&gt;mailing list&lt;/a&gt;&lt;/strong&gt; to be notified when a new article appears.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Camera and tilemaps</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-camera-and-tilemaps" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Camera and tilemaps" /><published>2018-11-14T09:20:53-05:00</published><updated>2018-11-14T09:20:53-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-camera-and-tilemaps</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-camera-and-tilemaps">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#camera&quot; id=&quot;markdown-toc-camera&quot;&gt;Camera&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#background-info&quot; id=&quot;markdown-toc-background-info&quot;&gt;Background info&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#specific&quot; id=&quot;markdown-toc-specific&quot;&gt;Specific&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#transformation-matrix&quot; id=&quot;markdown-toc-transformation-matrix&quot;&gt;Transformation matrix&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#facing-direction-in-2d&quot; id=&quot;markdown-toc-facing-direction-in-2d&quot;&gt;Facing direction in 2D&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#facing-direction-in-3d&quot; id=&quot;markdown-toc-facing-direction-in-3d&quot;&gt;Facing direction in 3D&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#note---canvas-space&quot; id=&quot;markdown-toc-note---canvas-space&quot;&gt;Note - canvas space&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#tilemap&quot; id=&quot;markdown-toc-tilemap&quot;&gt;Tilemap&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#view-space-position&quot; id=&quot;markdown-toc-view-space-position&quot;&gt;View-space position&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#affine-matrix&quot; id=&quot;markdown-toc-affine-matrix&quot;&gt;Affine matrix&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#scale-logic&quot; id=&quot;markdown-toc-scale-logic&quot;&gt;Scale logic&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#scale-logic-sketch&quot; id=&quot;markdown-toc-scale-logic-sketch&quot;&gt;Scale logic sketch&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#visualization&quot; id=&quot;markdown-toc-visualization&quot;&gt;Visualization&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the sixth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;camera&quot;&gt;Camera&lt;/h1&gt;

&lt;h2 id=&quot;background-info&quot;&gt;Background info&lt;/h2&gt;
&lt;p&gt;A general camera system is described in Joey de Vries’ &lt;em&gt;learnopengl.com&lt;/em&gt; series:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learnopengl.com/Getting-started/Coordinate-Systems&quot;&gt;Coordinate Systems&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://learnopengl.com/Getting-started/Camera&quot;&gt;Camera&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;specific&quot;&gt;Specific&lt;/h2&gt;
&lt;p&gt;For reference, here’s &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-notes#coordinate_system&quot;&gt;the coordinate system used in this series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our camera has very little “primary” state. The state that the user can set is:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Position in 3D space&lt;/li&gt;
  &lt;li&gt;Heading (rotation around the Z axis)&lt;/li&gt;
  &lt;li&gt;Pitch (rotation around the X axis)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also contains “secondary” state which is derived once per frame from the primary state:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Facing direction in 3D&lt;/li&gt;
  &lt;li&gt;Facing direction in 2D (on the XY plane)&lt;/li&gt;
  &lt;li&gt;Transformation matrix and its inverse (the inverse is the conventional view matrix)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;transformation-matrix&quot;&gt;Transformation matrix&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;Create translation matrix using position&lt;/li&gt;
  &lt;li&gt;Apply heading rotation&lt;/li&gt;
  &lt;li&gt;Apply pitch rotation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conventional matrix notation this would be: &lt;code class=&quot;highlighter-rouge&quot;&gt;translationMatrix * headingMatrix * pitchMatrix&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href=&quot;https://glm.g-truc.net/0.9.9/index.html&quot;&gt;GLM math library&lt;/a&gt;&lt;/strong&gt; takes care of the specifics. A few details:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Base axis rotation (like rotation around the Z or X axis) is a special case which can be computed cheaply. GLM’s euler angle functions are used for that.&lt;/li&gt;
  &lt;li&gt;The inverse can be computed from a mat3, then casted back to mat4 and multiplied with the opposite translation matrix (mat3 inverse is a ton faster than the mat4 inverse)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;facing-direction-in-2d&quot;&gt;Facing direction in 2D&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;vec(cos(heading), sin(heading))&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;(Using the trigonometric circle.)&lt;/p&gt;

&lt;h2 id=&quot;facing-direction-in-3d&quot;&gt;Facing direction in 3D&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;The default direction is &lt;code class=&quot;highlighter-rouge&quot;&gt;vec(0, 1, 0, 0)&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;The 3D direction is &lt;code class=&quot;highlighter-rouge&quot;&gt;transformMatrix * defaultDirection&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which simplifies to just taking the second column of the transformation matrix.&lt;/p&gt;

&lt;h2 id=&quot;note---canvas-space&quot;&gt;Note - canvas space&lt;/h2&gt;
&lt;p&gt;I’ll ommit the subject of canvas/screen space (see &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-notes#canvas_screen_space&quot;&gt;this note&lt;/a&gt;&lt;/strong&gt; for details). For the sake of simplicity the series will treat view/camera space as the final space for rendering.&lt;/p&gt;

&lt;h1 id=&quot;tilemap&quot;&gt;Tilemap&lt;/h1&gt;
&lt;p&gt;To render a tilemap we need to compute its affine matrix and its view-space position.&lt;/p&gt;

&lt;p&gt;We’re dealing with two tilemaps (bottom and top). They can both use the same affine matrix because only their positions differ.&lt;/p&gt;

&lt;h2 id=&quot;view-space-position&quot;&gt;View-space position&lt;/h2&gt;
&lt;p&gt;First, to get the view-space (or camera-space) position:
&lt;code class=&quot;highlighter-rouge&quot;&gt;camera.transformMatrixInverse * worldSpacePosition&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;affine-matrix&quot;&gt;Affine matrix&lt;/h2&gt;
&lt;p&gt;Our affine matrix is a 2D (2x2) matrix. To compute it we:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Create a scale matrix from &lt;code class=&quot;highlighter-rouge&quot;&gt;vec(1, cos(camera.pitch))&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Apply rotation to it using the opposite of the camera heading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conventional matrix notation this would be: &lt;code class=&quot;highlighter-rouge&quot;&gt;pitchScaleMatrix * oppositeHeadingRotationMatrix&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;scale-logic&quot;&gt;Scale logic&lt;/h2&gt;
&lt;p&gt;The orthographic projection used by the renderer can be represented by vector projection. In the case of unit vectors, by a dot product more specifically.&lt;/p&gt;

&lt;p&gt;The tilemap’s normal vector needs to be projected on the opposite of the camera’s direction.&lt;/p&gt;

&lt;p&gt;The algebraic dot product formula for 3D vectors is &lt;code class=&quot;highlighter-rouge&quot;&gt;v1.x * v2.x + v1.y * v2.y + v1.z * v2.z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We deal only with the YZ plane here, so let’s ignore &lt;code class=&quot;highlighter-rouge&quot;&gt;x&lt;/code&gt;: &lt;code class=&quot;highlighter-rouge&quot;&gt;v1.y * v2.y + v1.z * v2.z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The tilemap normal is &lt;code class=&quot;highlighter-rouge&quot;&gt;vec(0, 0, 1)&lt;/code&gt;. Let’s plug it into our 2D formula (&lt;code class=&quot;highlighter-rouge&quot;&gt;v1&lt;/code&gt; would be the tilemap normal, and &lt;code class=&quot;highlighter-rouge&quot;&gt;v2&lt;/code&gt; the opposite of the camera direction): &lt;code class=&quot;highlighter-rouge&quot;&gt;0 * v2.y + 1 * v2.z&lt;/code&gt; which simplifies to &lt;code class=&quot;highlighter-rouge&quot;&gt;v2.z&lt;/code&gt;. This is &lt;code class=&quot;highlighter-rouge&quot;&gt;cos(camera.pitch - pi)&lt;/code&gt; (the minus &lt;code class=&quot;highlighter-rouge&quot;&gt;pi&lt;/code&gt; accounts for the opposite direction).&lt;/p&gt;

&lt;p&gt;We’re not quite done though. Because we want to look towards +Z by default instead of -Z, we need to use the opposite direction again. So we get &lt;code class=&quot;highlighter-rouge&quot;&gt;cos(camera.pitch - pi - pi)&lt;/code&gt; which simplifies to &lt;code class=&quot;highlighter-rouge&quot;&gt;cos(camera.pitch)&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;scale-logic-sketch&quot;&gt;Scale logic sketch&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/scale_projection_v2_3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Camera pitch = 0 degrees&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/scale_projection_v2_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Camera pitch = 45 degrees&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/scale_projection_v2_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Camera pitch = 90 degrees&lt;/i&gt;&lt;/p&gt;

&lt;h2 id=&quot;visualization&quot;&gt;Visualization&lt;/h2&gt;
&lt;video class=&quot;center-img&quot; width=&quot;480&quot; height=&quot;320&quot; controls=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_scale.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    &lt;p&gt;Your browser doesn't support HTML5 video. Here is a &lt;a href=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_scale.mp4&quot;&gt;link to the video&lt;/a&gt; instead.&lt;/p&gt;
&lt;/video&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Scale&lt;/i&gt;&lt;/p&gt;

&lt;video class=&quot;center-img&quot; width=&quot;480&quot; height=&quot;320&quot; controls=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_rot.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    &lt;p&gt;Your browser doesn't support HTML5 video. Here is a &lt;a href=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_rot.mp4&quot;&gt;link to the video&lt;/a&gt; instead.&lt;/p&gt;
&lt;/video&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Rotation&lt;/i&gt;&lt;/p&gt;

&lt;video class=&quot;center-img&quot; width=&quot;480&quot; height=&quot;320&quot; controls=&quot;&quot; loop=&quot;&quot;&gt;
    &lt;source src=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_rot_scale.mp4&quot; type=&quot;video/mp4&quot; /&gt;

    &lt;p&gt;Your browser doesn't support HTML5 video. Here is a &lt;a href=&quot;https://fouramgames.com/blog/content/images/2018/11/vis_rot_scale.mp4&quot;&gt;link to the video&lt;/a&gt; instead.&lt;/p&gt;
&lt;/video&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Scale and rotation&lt;/i&gt;&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-walls&quot;&gt;next article&lt;/a&gt;&lt;/strong&gt; deals with walls (“how to transform the 2D surfaces to make them to look 3D”).&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Appendix: Notes</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-notes" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Appendix: Notes" /><published>2018-11-07T06:35:27-05:00</published><updated>2018-11-07T06:35:27-05:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-notes</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-notes">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#intro&quot; id=&quot;markdown-toc-intro&quot;&gt;Intro&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#notes&quot; id=&quot;markdown-toc-notes&quot;&gt;Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the second appendix of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;intro&quot;&gt;Intro&lt;/h1&gt;
&lt;p&gt;This page is a collection of notes and implementation details. Links to specific locations in this doc are scattered across the series.&lt;/p&gt;

&lt;h1 id=&quot;notes&quot;&gt;Notes&lt;/h1&gt;
&lt;h2 id=&quot;coordinate_system&quot;&gt;Coordinate system&lt;/h2&gt;
&lt;p&gt;The series uses the OpenGL coordinate system:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;-Z: forward&lt;/li&gt;
  &lt;li&gt;X: right&lt;/li&gt;
  &lt;li&gt;Y: up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For rotation:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Right-handed&lt;/li&gt;
  &lt;li&gt;Heading (Z axis) is applied first&lt;/li&gt;
  &lt;li&gt;Pitch (X axis) is applied next&lt;/li&gt;
  &lt;li&gt;Roll (Y axis) is never used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default camera view direction is straight down, towards the terrain:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/coord_cam.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The camera will usually be tilted for a lateral view:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/coord.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;draw&quot;&gt;Draw&lt;/h2&gt;
&lt;p&gt;On the GBA the term &lt;em&gt;draw&lt;/em&gt; doesn’t make much sense. What we do instead is write tilemap/sprite properties into specific memory locations and the hardware takes care of the rest. That’s a recurring pattern on the GBA by the way (the memory-mapped I/O principle)! Because there’s no operating system to speak of, practically everything on the GBA is controlled by writing to memory addresses called &lt;em&gt;registers&lt;/em&gt;. From configuring video modes and input handling callbacks to controlling the audio output.&lt;/p&gt;

&lt;p&gt;Long story short, &lt;em&gt;draw&lt;/em&gt; makes sense for the demo implementation because it uses modern graphics APIs through the Oryol framework.&lt;/p&gt;

&lt;h2 id=&quot;canvas_screen_space&quot;&gt;Canvas/screen space&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/canvas_space.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Top - view-space, bottom - canvas space&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Our renderer calculates the view-space position of the items we want to display. That’s not enough however. The positions need to be converted to canvas coordinates for the GBA.&lt;/p&gt;

&lt;p&gt;Gabriel Gambetta &lt;strong&gt;&lt;a href=&quot;http://www.gabrielgambetta.com/computer-graphics-from-scratch/common-concepts.html#coordinate-systems&quot;&gt;explains it&lt;/a&gt;&lt;/strong&gt; in his &lt;em&gt;Computer Graphics from scratch&lt;/em&gt; series.&lt;/p&gt;

&lt;h2 id=&quot;affine&quot;&gt;Affine matrix intricacies&lt;/h2&gt;
&lt;p&gt;There’s a few gotchas described in &lt;strong&gt;&lt;a href=&quot;https://www.coranac.com/tonc/text/affine.htm&quot;&gt;Tonc&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One that stands out is that the GBA actually expects the inverse of the affine matrix.&lt;/p&gt;

&lt;p&gt;Another detail is that it takes fixed-point numbers in the 8.8 format (8 bits for integer value, 8 bits for fractional value). The GBA deals natively in 32 bits, so 16 bit data types are less precise and even underperform! Tonc recommends that the standard 32-bit signed representation always be used until the final conversion is necessary.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - The big picture</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - The big picture" /><published>2018-11-03T06:35:27-04:00</published><updated>2018-11-03T06:35:27-04:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#steps&quot; id=&quot;markdown-toc-steps&quot;&gt;Steps&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#main-loop-general&quot; id=&quot;markdown-toc-main-loop-general&quot;&gt;Main loop (general)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#main-loop-first-floor&quot; id=&quot;markdown-toc-main-loop-first-floor&quot;&gt;Main loop (first floor)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#main-loop-second-floor&quot; id=&quot;markdown-toc-main-loop-second-floor&quot;&gt;Main loop (second floor)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#note---draw&quot; id=&quot;markdown-toc-note---draw&quot;&gt;Note - draw&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#frame-breakdown&quot; id=&quot;markdown-toc-frame-breakdown&quot;&gt;Frame breakdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the fifth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;These are the steps that the demo renderer executes every frame. The rest of the articles in the series will cover them in detail.&lt;/p&gt;

&lt;h1 id=&quot;steps&quot;&gt;Steps&lt;/h1&gt;
&lt;h2 id=&quot;main-loop-general&quot;&gt;Main loop (general)&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Poll inputs&lt;/li&gt;
  &lt;li&gt;Apply camera movement&lt;/li&gt;
  &lt;li&gt;Update camera matrices&lt;/li&gt;
  &lt;li&gt;Compute tilemap affine matrix (shared by both tilemaps)&lt;/li&gt;
  &lt;li&gt;Compute the affine matrix for each axis-aligned wall direction&lt;/li&gt;
  &lt;li&gt;Compute the affine matrix for each 45-degree angle wall direction&lt;/li&gt;
  &lt;li&gt;Compute view-space positions for both tilemaps&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;main-loop-first-floor&quot;&gt;Main loop (first floor)&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Draw the first floor tilemap&lt;/li&gt;
  &lt;li&gt;Update drop shadows + find their view-space positions + group them by floor (first or second)&lt;/li&gt;
  &lt;li&gt;Draw the first floor drop shadows&lt;/li&gt;
  &lt;li&gt;Groups walls and objects by floor + find their view-space positions + depth-sort each floor’s group separately&lt;/li&gt;
  &lt;li&gt;Draw walls and first floor objects&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;main-loop-second-floor&quot;&gt;Main loop (second floor)&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;Draw the second floor tilemap&lt;/li&gt;
  &lt;li&gt;Draw the second floor drop shadows&lt;/li&gt;
  &lt;li&gt;Draw the second floor objects&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;note---draw&quot;&gt;Note - draw&lt;/h2&gt;
&lt;p&gt;The term &lt;em&gt;draw&lt;/em&gt; is technically &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-notes#draw&quot;&gt;inaccurate in the GBA’s context but good enough for my purposes&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h1 id=&quot;frame-breakdown&quot;&gt;Frame breakdown&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/0.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Background color&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Bottom tilemap (first floor)&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Drop shadows on first floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Depth-sorted walls on first floor&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Note that contrary to what the image suggests, every chunk of the wall is treated as a separate object (they're hardware sprites). They're lumped together here only to save time and space.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/4.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Depth-sorted sprite on first floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/5.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Depth-sorted walls on fist floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/6.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Depth-sorted sprite on first floor&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Done with the first floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/7.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Top tilemap (second floor)&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/8.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Drop shadows on second floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/11/frames/9.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Sprite on second floor&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;There are never any walls on the second floor. The frame is complete.&lt;/i&gt;&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-camera-and-tilemaps&quot;&gt;next article&lt;/a&gt;&lt;/strong&gt; finally dives into the implementation! It starts with a discussion of the camera and the tilemap rendering.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Capabilities and limitations</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-capabilities-and-limitations" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Capabilities and limitations" /><published>2018-10-06T08:10:57-04:00</published><updated>2018-10-06T08:10:57-04:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-capacities-and-limitations</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-capabilities-and-limitations">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#coordinate-system&quot; id=&quot;markdown-toc-coordinate-system&quot;&gt;Coordinate system&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#terminology&quot; id=&quot;markdown-toc-terminology&quot;&gt;Terminology&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#capabilities&quot; id=&quot;markdown-toc-capabilities&quot;&gt;Capabilities&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#basic-elements&quot; id=&quot;markdown-toc-basic-elements&quot;&gt;Basic elements&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#inverted-walls&quot; id=&quot;markdown-toc-inverted-walls&quot;&gt;Inverted walls&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#45-degree-walls&quot; id=&quot;markdown-toc-45-degree-walls&quot;&gt;45-degree walls&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#walls-and-objects-culling&quot; id=&quot;markdown-toc-walls-and-objects-culling&quot;&gt;Walls and objects culling&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#backface-wall-culling&quot; id=&quot;markdown-toc-backface-wall-culling&quot;&gt;Backface wall culling&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#drop-shadows&quot; id=&quot;markdown-toc-drop-shadows&quot;&gt;Drop shadows&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#depth-sorting&quot; id=&quot;markdown-toc-depth-sorting&quot;&gt;Depth sorting&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#limitations&quot; id=&quot;markdown-toc-limitations&quot;&gt;Limitations&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#camera&quot; id=&quot;markdown-toc-camera&quot;&gt;Camera&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#projection&quot; id=&quot;markdown-toc-projection&quot;&gt;Projection&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#object-wall-clipping-issue&quot; id=&quot;markdown-toc-object-wall-clipping-issue&quot;&gt;Object-wall clipping issue&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#wall-wall-clipping-issue&quot; id=&quot;markdown-toc-wall-wall-clipping-issue&quot;&gt;Wall-wall clipping issue&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#object-ceiling-clipping-issue&quot; id=&quot;markdown-toc-object-ceiling-clipping-issue&quot;&gt;Object-ceiling clipping issue&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#wall-offset-issue&quot; id=&quot;markdown-toc-wall-offset-issue&quot;&gt;Wall offset issue&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#wall-jitter-issue&quot; id=&quot;markdown-toc-wall-jitter-issue&quot;&gt;Wall jitter issue&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#bottom-line&quot; id=&quot;markdown-toc-bottom-line&quot;&gt;Bottom line&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the fourth article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;coordinate-system&quot;&gt;Coordinate system&lt;/h2&gt;
&lt;p&gt;I’ll be using the OpenGL coordinate system:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;-Z: forward&lt;/li&gt;
  &lt;li&gt;X: right&lt;/li&gt;
  &lt;li&gt;Y: up&lt;/li&gt;
  &lt;li&gt;Right-handed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this system the default camera view direction is straight down, towards the terrain:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/coord_cam.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The camera will usually be tilted for a lateral view:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/coord.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;terminology&quot;&gt;Terminology&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Objects&lt;/em&gt; represent characters, projectiles, and visual effect particles.&lt;/p&gt;

&lt;p&gt;The term is used to distinguish them from &lt;em&gt;walls&lt;/em&gt;, since walls and objects are both GBA hardware-accelerated &lt;em&gt;sprites&lt;/em&gt; (discussed in article 2) under the hood.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;capabilities&quot;&gt;Capabilities&lt;/h1&gt;
&lt;h2 id=&quot;basic-elements&quot;&gt;Basic elements&lt;/h2&gt;
&lt;p&gt;As seen so far, &lt;em&gt;Sonic Battle&lt;/em&gt; can display a floor and a “ceiling”/”second floor” which are aligned to the XY plane.&lt;/p&gt;

&lt;p&gt;It can also display walls which are aligned to either the ZX or the ZY plane.&lt;/p&gt;

&lt;p&gt;These elements are often combined to display “box” structures:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/ruins.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Maps are full of these boxes:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/ruins_map.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;inverted-walls&quot;&gt;Inverted walls&lt;/h2&gt;
&lt;p&gt;There’s a map with inverted walls. It’s like looking at the inside of a box:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/inverse_normals.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/inverse_normals_map.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As will be discussed in a future article, this variety comes at no performance cost.&lt;/p&gt;

&lt;h2 id=&quot;45-degree-walls&quot;&gt;45-degree walls&lt;/h2&gt;
&lt;p&gt;There’s also walls which are twisted 45 degrees around the Z axis:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/hex_walls.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/hex_walls_map.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;walls-and-objects-culling&quot;&gt;Walls and objects culling&lt;/h2&gt;
&lt;p&gt;Walls and objects which are offscreen are removed from the display list to save resources.&lt;/p&gt;

&lt;h2 id=&quot;backface-wall-culling&quot;&gt;Backface wall culling&lt;/h2&gt;
&lt;p&gt;Only “closed” volumes are being rendered, so walls which are facing away from the camera aren’t rendered because they would be invisible anyway:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/backface_culling.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;The dotted outlines show the hidden walls&lt;/i&gt;&lt;/p&gt;

&lt;h2 id=&quot;drop-shadows&quot;&gt;Drop shadows&lt;/h2&gt;
&lt;p&gt;When characters jump, fly, fall, or otherwise move along the Z axis it can become hard to determine their position.&lt;/p&gt;

&lt;p&gt;The game uses a round shadow under each characters’ feet to solve the issue.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/drop_x2.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The shadow’s position indicates the character’s position on the XY plane.&lt;/p&gt;

&lt;p&gt;The shadow’s size hints at his position on the Z axis relative to the object under his feet (not necessarily the “floor”/terrain).&lt;/p&gt;

&lt;h2 id=&quot;depth-sorting&quot;&gt;Depth sorting&lt;/h2&gt;
&lt;p&gt;The hardware-accelerated rendering of the GBA allows to set the draw order of tilemaps and sprites, but it can’t be more granular.&lt;/p&gt;

&lt;p&gt;In other words the renderer is limited to the &lt;em&gt;painter’s algorithm&lt;/em&gt;, where the draw items are sorted by distance to camera and drawn in “far to close” order. This works well overall but has some edge cases which are discussed below.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h1 id=&quot;limitations&quot;&gt;Limitations&lt;/h1&gt;
&lt;h2 id=&quot;camera&quot;&gt;Camera&lt;/h2&gt;
&lt;p&gt;The camera rotation is evaluated in the following order:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Z axis rotation: Heading&lt;/li&gt;
  &lt;li&gt;X axis rotation: Pitch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that Y axis rotation (Roll) is never used in &lt;em&gt;Sonic Battle&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Also, the camera pitch is always between 0 and 90 degrees.&lt;/p&gt;

&lt;p&gt;In the context of the game it makes no sense to break these rules. They also enable many assumptions and performance optimizations.&lt;/p&gt;

&lt;h2 id=&quot;projection&quot;&gt;Projection&lt;/h2&gt;
&lt;p&gt;The projection is orthographic. I think that perspective projection would have been possible with some more work. But it wouldn’t have brought any gameplay value, and it would have hurt the clarity of the visuals.&lt;/p&gt;

&lt;h2 id=&quot;object-wall-clipping-issue&quot;&gt;Object-wall clipping issue&lt;/h2&gt;
&lt;p&gt;The renderer seems to depth-sort by horizontal middle, vertical bottom points. We can observe the side effects it causes, for example:&lt;/p&gt;

&lt;p&gt;Indicated are the sorting reference points for the character, the facing wall, and the side wall:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_clip/clip1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The character’s sorting reference point is in front of the others, so he’s drawn last:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_clip/clip2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The character’s point is between the side wall’s and the facing wall’s. He’s drawn on top of the side wall but is clipped by the facing wall (in the area near his hand):
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_clip/clip3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The character’s point is behind the two others. He’s clipped by both walls:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_clip/clip4.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;wall-wall-clipping-issue&quot;&gt;Wall-wall clipping issue&lt;/h2&gt;
&lt;p&gt;There’s a specific map with unusually positioned walls which don’t depth-sort correctly:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/wall_clip.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/wall_clip_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Illustrated wall bounds and sorting reference points&lt;/i&gt;&lt;/p&gt;

&lt;h2 id=&quot;object-ceiling-clipping-issue&quot;&gt;Object-ceiling clipping issue&lt;/h2&gt;
&lt;p&gt;The “ceiling” faces of the “boxes” are all part of a tilemap and can’t be depth-sorted individually.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/bg3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;&quot;First floor&quot; tilemap&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/bg2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;&quot;Ceiling&quot;/&quot;second floor&quot; tilemap&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Our draw order is roughly this:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;First floor tilemap&lt;/li&gt;
  &lt;li&gt;Walls and objects&lt;/li&gt;
  &lt;li&gt;Second floor tilemap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what happens when objects move up the Z axis, and eventually reach the second floor?&lt;/p&gt;

&lt;p&gt;The draw order becomes as follows to accommodate it:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;First floor tilemap&lt;/li&gt;
  &lt;li&gt;Walls and objects which are on the first floor&lt;/li&gt;
  &lt;li&gt;Second floor tilemap&lt;/li&gt;
  &lt;li&gt;Objects which are on the second floor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A sorting issue happens when a object is in between the first and second floors:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/clip_up.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Jumping up from first to second floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/clip_down.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Jumping down from second to first floor&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;It’s not obvious why the issue also appears when jumping down. It will be discussed in a later article, fear not.&lt;/p&gt;

&lt;h2 id=&quot;wall-offset-issue&quot;&gt;Wall offset issue&lt;/h2&gt;
&lt;p&gt;The walls and tilemaps don’t exactly match positions. This creates cracks at the seams of the “closed” volumes they’re supposed to represent:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/cracks.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;Cracks at the left seams of the boxes&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;I don’t know if it’s caused by fixed point precision issues or by an “off by one” bug.&lt;/p&gt;

&lt;h2 id=&quot;wall-jitter-issue&quot;&gt;Wall jitter issue&lt;/h2&gt;
&lt;p&gt;I think this is caused by fixed-point math precision issues.&lt;/p&gt;

&lt;p&gt;The further the player is from the center of the map (the bigger the magnitude of the numbers involved), the worse the jitter.&lt;/p&gt;

&lt;p&gt;This GIF was recorded at the edge of the one of the widest maps where it’s definitely worse than usual:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/wall_jitter_x2.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;bottom-line&quot;&gt;Bottom line&lt;/h2&gt;
&lt;p&gt;These rendering quirks might seem like a big deal, but they’re honestly not.&lt;/p&gt;

&lt;p&gt;I’ve played through the game twice and I never noticed even one of them. I only discovered the issues after careful inspection once I noticed them in my own implementation.&lt;/p&gt;

&lt;p&gt;Also note that all the images and gifs that I included were upscaled by a factor of x2, so everything is less distinguishable at native resolution, and even less on the console’s small LCD screen.&lt;/p&gt;

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

&lt;p&gt;The &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-the-big-picture&quot;&gt;next article&lt;/a&gt;&lt;/strong&gt; is a high-level summary of my implementation.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry><entry><title type="html">Sonic Battle (GBA) Renderer Series - Palette shifting</title><link href="https://fouramgames.com/blog/sonic-battle-renderer-palette-shifting" rel="alternate" type="text/html" title="Sonic Battle (GBA) Renderer Series - Palette shifting" /><published>2018-09-25T12:42:43-04:00</published><updated>2018-09-25T12:42:43-04:00</updated><id>https://fouramgames.com/blog/sonic-battle-renderer-palette-shifting</id><content type="html" xml:base="https://fouramgames.com/blog/sonic-battle-renderer-palette-shifting">&lt;h1 class=&quot;no_toc&quot; id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h1&gt;
&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#colors&quot; id=&quot;markdown-toc-colors&quot;&gt;Colors&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#color-palettes&quot; id=&quot;markdown-toc-color-palettes&quot;&gt;Color palettes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#putting-it-all-together&quot; id=&quot;markdown-toc-putting-it-all-together&quot;&gt;Putting it all together&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#applications&quot; id=&quot;markdown-toc-applications&quot;&gt;Applications&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#waves&quot; id=&quot;markdown-toc-waves&quot;&gt;Waves&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pulsating-lights-and-circular-motion&quot; id=&quot;markdown-toc-pulsating-lights-and-circular-motion&quot;&gt;Pulsating lights and circular motion&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#shading&quot; id=&quot;markdown-toc-shading&quot;&gt;Shading&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#character-color-variations&quot; id=&quot;markdown-toc-character-color-variations&quot;&gt;Character color variations&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#more&quot; id=&quot;markdown-toc-more&quot;&gt;More&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;Welcome to the third article of the &lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer&quot;&gt;Sonic Battle (GBA) Renderer series&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/overview.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This articles takes a slight detour to discuss palette shifting, a visual technique that &lt;em&gt;Sonic Battle&lt;/em&gt; uses to great effect. It doesn’t have anything to do with 3D rendering but it’s so neat that I included it anyway. It’s also an example of turning a technical limitation into something great, which gamedev is famous for.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;colors&quot;&gt;Colors&lt;/h2&gt;
&lt;p&gt;Colors are represented by 16 bits on the GBA.&lt;/p&gt;

&lt;p&gt;5 bits (containing values 0-31) for red, 5 for green, 5 for blue, and 1 unused leftover: &lt;code class=&quot;highlighter-rouge&quot;&gt;xRRRRRGGGGGBBBBB&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;color-palettes&quot;&gt;Color palettes&lt;/h2&gt;
&lt;p&gt;The GBA’s video mode 2 provides two palettes (each containing 256 colors) at our disposal, one for tilemaps and one for sprites.&lt;/p&gt;

&lt;p&gt;Here’s two sample 256-color palettes:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/palette.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;Background/tilemap palette on the left, sprite/object palette on the right&lt;/p&gt;

&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;Just like &lt;em&gt;tilemaps&lt;/em&gt; are built out of &lt;em&gt;tiles&lt;/em&gt; contained in a &lt;em&gt;tileset&lt;/em&gt;, &lt;em&gt;tiles&lt;/em&gt; are built out of &lt;em&gt;colors&lt;/em&gt; contained in a &lt;em&gt;palette&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This way each pixel in a tile only needs to store the index of its color (8 bits, values 0-255) instead of the full color value (16 bits).&lt;/p&gt;

&lt;p&gt;Additionally, a tile can be configured to use one of the 256-color palette’s 16 sub-palettes (one of the 16 rows). This limits the asset to the 16 colors contained in its sub-palette but it only requires 4 (values 0-15) instead of 8 bits per pixel.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sub_palette.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Palettes also make it easy and fast to change asset colors: tile data can be left untouched because only the entries in the palettes need to be modified.&lt;/p&gt;

&lt;h1 id=&quot;applications&quot;&gt;Applications&lt;/h1&gt;
&lt;h2 id=&quot;waves&quot;&gt;Waves&lt;/h2&gt;
&lt;p&gt;At first I thought this was done through flip-book animation by cycling through tiles.&lt;/p&gt;

&lt;p&gt;An example of the flip-book technique would be cycling through tiles 0-7 of the 4th row (the one with the downwards arrow):
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/flipbook.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s what that looks like:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/flipbook.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot;&gt;&lt;i&gt;(Tileset by &lt;a href=&quot;https://opengameart.org/content/whispers-of-avalon-grassland-tileset&quot;&gt;Leonard Pabin&lt;/a&gt;)&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;But in &lt;em&gt;Sonic Battle&lt;/em&gt; this effect is achieved instead by cycling the water and sand colors. You can see them shifting in the tilemap palette:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/waves.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This palette shifting technique doesn’t need individual animation frames (which entail a copy per frame of every unique tile involved in the animation). Palette shifting eliminates these memory and production costs which helps achieve much smoother animations.&lt;/p&gt;

&lt;h2 id=&quot;pulsating-lights-and-circular-motion&quot;&gt;Pulsating lights and circular motion&lt;/h2&gt;
&lt;p&gt;Same concept, different effects.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/lights.gif&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;shading&quot;&gt;Shading&lt;/h2&gt;
&lt;p&gt;Walls can be either lit or occluded. This only affects their colors, so it saves space to use the same tiles for both versions but with two different palettes.&lt;/p&gt;

&lt;p&gt;With lit walls palette (sub-palette 12):
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/walls_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;With occluded walls palette (sub-palette 13):
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/walls_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;character-color-variations&quot;&gt;Character color variations&lt;/h2&gt;
&lt;p&gt;Players can choose the colors for their character:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_color_1.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Default character palette:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_color_2.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Custom character palette:
&lt;img src=&quot;https://fouramgames.com/blog/content/images/2018/09/sprite_color_3.png&quot; alt=&quot;&quot; class=&quot;center-img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The same technique is also used for enemy color variations. For instance hostile versions of friendly robot characters have grayscale colors.&lt;/p&gt;

&lt;h2 id=&quot;more&quot;&gt;More&lt;/h2&gt;
&lt;p&gt;That’s a wrap for palette shifting. It’s by no means unique to &lt;em&gt;Sonic Battle&lt;/em&gt; though.&lt;/p&gt;

&lt;p&gt;Mark Ferrari had perfected the technique in the 90s. Check out his 8-bit vistas where water waves, waterfalls, rain, snow, and more are simulated through palette shifting: &lt;strong&gt;&lt;a href=&quot;http://www.effectgames.com/effect/article-Old_School_Color_Cycling_with_HTML5.html&quot;&gt;Old School Color Cycling with HTML5&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://fouramgames.com/blog/sonic-battle-renderer-capabilities-and-limitations&quot;&gt;The next article&lt;/a&gt;&lt;/strong&gt; kickstarts the 3D renderer analysis by first discussing its capabilities and limitations.&lt;/p&gt;</content><author><name>Ohmnivore</name></author><category term="gamedev" /><category term="GBA" /><summary type="html">Table of Contents</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://fouramgames.com/blog/content/images/2018/09/overview.png" /></entry></feed>