{"id":3188,"date":"2026-03-01T23:00:59","date_gmt":"2026-03-01T23:00:59","guid":{"rendered":"https:\/\/timallanwheeler.com\/blog\/?p=3188"},"modified":"2026-03-14T16:51:29","modified_gmt":"2026-03-14T16:51:29","slug":"into-depth-shadow-casting-portals","status":"publish","type":"post","link":"https:\/\/timallanwheeler.com\/blog\/2026\/03\/01\/into-depth-shadow-casting-portals\/","title":{"rendered":"Into Depth &#8211; Shadow Casting &amp; Portals"},"content":{"rendered":"<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"409\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1.png\" alt=\"\" class=\"wp-image-3196\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1.png 705w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1-300x174.png 300w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">A debug view showing shadow cast segments from the vantage point of the actor.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p>Last month we covered the unified action system, in which we built some basic things players can do with their heroes, and set the stage for fancier futures and actions for enemy agents. The most significant action was moving, which was built based on a <em>GridView<\/em> of what the actor sees from their current vantage point. As noted in that post, we eventually wanted to expand the grid view to include edge information, such as thin little walls and platforms, but also <em>portals<\/em> that act as non-Euclidean connections to other parts of the map.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"746\" height=\"115\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-32.png\" alt=\"\" class=\"wp-image-3190\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-32.png 746w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-32-300x46.png 300w\" sizes=\"auto, (max-width: 746px) 100vw, 746px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">Here we have a simple two-tile room with a loop-back portal on either end. The result &#8211; the hero can see a bunch of versions of themselves!<\/p>\n\n\n\n<p>In the last post, I said I&#8217;d be adding other entities in and adding more actions, but I decided that the map representation was so fundamental that prioritizing getting these portals in was more important. Any changes there would affect most other systems, so I wanted to get that done first.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Grid Representation<\/h1>\n\n\n\n<p>Prior to this change, the underlying level geometry representation, the<em> Grid,<\/em> was just a 2D array of cells:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"728\" height=\"390\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-36.png\" alt=\"\" class=\"wp-image-3191\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-36.png 728w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-36-300x161.png 300w\" sizes=\"auto, (max-width: 728px) 100vw, 728px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>After this change, I wanted to represent both the cells as well as the edges between them:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"756\" height=\"387\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-39.png\" alt=\"\" class=\"wp-image-3192\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-39.png 756w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-27_13-39-300x154.png 300w\" sizes=\"auto, (max-width: 756px) 100vw, 756px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>This was achieved by adding two additional 2D arrays, one for the horizontal edges and one for the vertical edges. There are definitely other ways to do this, but I felt that (1) this was simple, and (2) there are differences between those edge types. A player might stand on a platform but have a door that they can open, for example.<\/p>\n\n\n\n<p>The edges are single-bit entries, to keep things efficient. If we need more information, we can index into a separate array. I&#8217;m currently using the first two bits to identify the edge type, and the remaining 6 bits, if the edge is a portal, index into a <em>Portal<\/em> array. The Portal stores the information necessary to know which two edges it links.<\/p>\n\n\n\n<p>This change has the immediate consequence that getting the cell neighbor in a given direction does not necessarily produce the Euclidean neighbor. Its second consequence is that cells no longer need to be solid or not &#8212; the edges between cells can instead be solid. This slightly simplifies cells.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Grid View Representation<\/h1>\n\n\n\n<p>The <em>Grid View<\/em>, introduced in the last post to represent the level geometry as seen by an actor from their vantage point, can now represent views where the same underlying cell might show up multiple times due to portal connections. Furthermore, we might have weird cases where one square cell might contain slices from different areas of the map:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-medium is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"300\" height=\"286\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-01-30_12-38-300x286.png\" alt=\"\" class=\"wp-image-3194\" style=\"width:400px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-01-30_12-38-300x286.png 300w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-01-30_12-38.png 316w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">The green arrow points at a view cell which, due to the portal (magenta), has two sections of different grid cells.<\/p>\n\n\n\n<p>Before, the grid view was a simple 2D array with one cell per tile. Now, that is no longer the case.<\/p>\n\n\n\n<p>The updated grid view is organized around <em>ViewNodes<\/em>, <em>ShadowSegments<\/em>, and <em>ViewTransforms<\/em>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">struct GridView {\n    \/\/ The grid tile the view is centered on\n    CellIndex center;\n\n    \/\/ The first ViewNode in each view cell.\n    \/\/ 0xFFFF if there is no view node in that cell.\n    u16 head_node_indices[GRID_VIEW_MAX_X][GRID_VIEW_MAX_Y];\n\n    u16 n_nodes;\n    ViewNode nodes[GRID_VIEW_MAX_NODES];\n\n    \/\/ Shadowcast sectors.\n    u16 n_shadow_segments;\n    ShadowSegment shadow_segments[GRID_VIEW_MAX_NODES];\n\n    \/\/ The transforms for the view as affected by portals.\n    u8 n_view_transforms;\n    ViewTransform view_transforms[GRID_VIEW_MAX_TRANSFORMS];\n};<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Most of the time, a given cell in the grid view will contain a single cell in the underlying grid. However, as we saw above, it might see more than one. Thus, we can look up a linked list of <em>ViewNodes<\/em> per view cell, with an intrusive index to its child (if any). Each such node knows which underlying grid cell it sees, whether it is connected in each cardinal direction to its neighbor view cells, and what sector of the view it contains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">struct ViewNode {\n    CellIndex cell_index;\n\n    \/\/ Intrusive data pointer, indexing into the GridView nodes array,\n    \/\/ to the next view node at the same ViewIndex, if any.\n    \/\/ 0xFFFF if there is no next view node.\n    u16 next_node_index;\n\n    \/\/ Whether this view node is continuous with the view node in the\n    \/\/ given direction.\n    \/\/ See direction flags.\n    u8 connections;\n\n    \/\/ The sector this view node represents.\n    Sector sector;\n\n    \/\/ The stencil id for this view node.\n    \/\/ Identical to the transform_index + 1.\n    u8 stencil_id;\n};<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>We can now represent cases like the motivating portal scenario:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"561\" height=\"333\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-41.png\" alt=\"\" class=\"wp-image-3197\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-41.png 561w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-41-300x178.png 300w\" sizes=\"auto, (max-width: 561px) 100vw, 561px\" \/><\/figure>\n<\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"624\" height=\"368\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-43.png\" alt=\"\" class=\"wp-image-3200\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-43.png 624w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-43-300x177.png 300w\" sizes=\"auto, (max-width: 624px) 100vw, 624px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">I&#8217;ve given the region on the other side of the portal different tile backgrounds to make it more obvious.<\/p>\n\n\n\n<p>In the last post, we constructed the grid view with breadth-first search (BFS) from the center tile. Now, since we&#8217;re working with polar segments that strictly expand outwards, we can actually get away with the even simpler depth-first search (DFS). Everything is radial, and the farther out we get, the narrower our beams are:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"409\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1-1.png\" alt=\"\" class=\"wp-image-3201\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1-1.png 705w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_1-1-300x174.png 300w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">A debug view showing the final shadow cast segments.<\/p>\n\n\n\n<p>The search logic is a bit more complicated given that we have to narrow down the segment every time we pass through a tile&#8217;s edge, we&#8217;re saving view nodes in the new intrusive linked list structure, and edge traversals have to check for portals. Solid edges terminate and trigger the saving of a shadow segment.<\/p>\n\n\n\n<p>Portal traversals have the added complexity of changing physically where we are in the underlying grid. If we think of the underlying grid as one big mesh, we effectively have to position the mesh differently when rendering the stuff on the other side of the portal. That information is stored in the <code>ViewTransform<\/code>, and it is also given its own stencil id.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"708\" height=\"408\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_2.png\" alt=\"\" class=\"wp-image-3202\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_2.png 708w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/02\/2026-02-28_15-35_2-300x173.png 300w\" sizes=\"auto, (max-width: 708px) 100vw, 708px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-small-font-size\">A debug view of the view nodes. We fully see many cells, but some cells we only partially see. The origin is currently split into four views in order for view segments to never exceed 180 degrees. <\/p>\n\n\n\n<p>Above we can see an overlay of the resulting view nodes. There is a little sliver of a cell we can see for a passage off to the left, and there are the two passages, one with a portal, that we can see on the right. The platforms (little brown rectangles) do not block line of sight.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">The Stencil Buffer<\/h1>\n\n\n\n<p>Now that we have the grid view, how do we render it? It is pretty easy to render a square cell, but do we now have to calculate the part of each cell that intersects with the view segment? We very much want to avoid that!<\/p>\n\n\n\n<p>Clean rendering is accomplished via the <em>stencil buffer<\/em>, which is a feature in graphics programming that lets us mask out arbitrary parts of the screen when rendering:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"534\" height=\"318\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_16-02.png\" alt=\"\" class=\"wp-image-3203\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_16-02.png 534w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_16-02-300x179.png 300w\" sizes=\"auto, (max-width: 534px) 100vw, 534px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>We&#8217;ve already been using several buffers when rendering. We&#8217;re writing to the RGB color buffers, and 3D occlusion is handled via the <em>depth buffer<\/em>. The stencil buffer is just an additional buffer (here, one byte per pixel), that we can use to get a stenciling effect.<\/p>\n\n\n\n<p>At one byte (8 bits) per pixel, the stencil buffer can at first glance only hold 8 overlapping stencil masks at once. However, with our 2D orthographic setup, our stencil masks never overlap. So, we can just write 255 unique values per pixel (reserving 0 for the clear).<\/p>\n\n\n\n<p>We simply write the shadow segments to the stencil buffer, setting the buffer to the appropriate stencil ID:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"707\" height=\"409\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_15-35.png\" alt=\"\" class=\"wp-image-3204\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_15-35.png 707w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-02-28_15-35-300x174.png 300w\" sizes=\"auto, (max-width: 707px) 100vw, 707px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-small-font-size\">The stencil buffer has four stencil masks &#8211; the red region in the main chamber, the little slice of hallway to the left, the green region through the portal leading back into the same room, and the yellow region through a different portal to an entirely different room.<\/p>\n\n\n\n<p>Our rendering pipeline now needs to support masking with these stencil IDs. <a href=\"https:\/\/timallanwheeler.com\/blog\/2026\/01\/01\/into-depth-unified-triangle-shader\/\">In January we covered the render setup<\/a>, which involved six different types of render commands that the game could send to the backend:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>render mesh &#8211; render a 3D mesh with lighting, already uploaded to the GPU<\/li>\n\n\n\n<li>render local mesh &#8211; render a 3D mesh without lighting, sending vertices from the CPU<\/li>\n\n\n\n<li>render quad &#8211; an old simple command to render a quad<\/li>\n\n\n\n<li>render sprite &#8211; self-explanatory<\/li>\n\n\n\n<li>render spritesheet sprite &#8211; an old version of render sprite<\/li>\n\n\n\n<li>render panel &#8211; renders a panel with 9-slice scaling by sending vertices from the CPU<\/li>\n\n\n\n<li>render text &#8211; renders font glyphs by sending quads from the CPU<\/li>\n<\/ul>\n\n\n\n<p> Having three versions of everything &#8211; rendering without stencils, rendering with stencils, and rendering <em>to <\/em>the stencil buffer itself, would triple the number of command types. That is too much!<\/p>\n\n\n\n<p>Furthermore, we want to order the render commands such that opaque things are drawn front to back, and transparent things are then drawn back to front, and finally drawing UI elements in order at the end. That would require sorting across command types, which seems really messy.<\/p>\n\n\n\n<p>To achieve this all neatly, I collapsed everything sending vertices from the CPU to a unified command type, and have the backend fill the vertex buffer when the game creates the command:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"600\" height=\"302\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28.png\" alt=\"\" class=\"wp-image-3205\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28.png 600w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28-300x151.png 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/figure>\n<\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"674\" height=\"237\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28_1.png\" alt=\"\" class=\"wp-image-3206\" style=\"width:600px\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28_1.png 674w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-28_1-300x105.png 300w\" sizes=\"auto, (max-width: 674px) 100vw, 674px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>You will notice that the game-side changed a bit. Rather than receiving a command struct and being responsible for populating all fields, I decided to move rendering to methods that expect everything you need to be passed in. This is less error prone, allows for different function signatures that populate what you need, and means I don&#8217;t actually need structs for the different command types if we&#8217;re directly converting to vertices.<\/p>\n\n\n\n<p>Each unified command (everything but the mesh render calls) is simply:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">\/\/ Determines the order in which things are rendered.\nenum class PassType : u8 {\n    OPAQUE = 0,\n    TRANSLUCENT = 1,\n    UI = 2,\n};\n\n\/\/ Determines whether and how a command uses a stencil.\nenum class StencilRequirement : u8 {\n    NONE  = 0, \/\/ Does not use stencils\n    WRITE = 1, \/\/ Writes to the stencil buffer\n    READ  = 2, \/\/ Reads from the stencil buffer\n};\n\n\/\/ Determines how the draw gets executed.\nenum class DrawMode : u8 {\n    ARRAYS,   \/\/ No EBO\n    ELEMENTS, \/\/ With EB\n};\n\nstruct RenderCommandUnlitTriangle {\n    \/\/ The commands are sorted by the sort key prior to rendering, determining the order.\n    \/\/ Bits 63-62 (2 bits): Pass Type (Opaque, Translucent, UI)\n    \/\/ Bits 61-60 (2 bits): Stencil Requirement\n    \/\/ Bits 59    (1 bit):  Draw Mode\n    \/\/ Bits 58-51 (8 bits): Stencil ID (0 for none)\n    \/\/ Bits 50-32  &lt;reserved&gt;\n    \/\/ Bits 31-0  (32 bits)\n    \/\/   In Opaque:      front-to-back distance\n    \/\/   In Translucent: back-to-front distance\n    \/\/   In UI:          sequence ID\n    u64 sort_key;\n\n    \/\/ Draw Arguments\n    u32 n_vertices; \/\/ Number of vertices to draw\n    u32 i_vertex;   \/\/ Index of the start vertex\n    u32 n_indices;  \/\/ Number of indices, if using ELEMENTS\n    u32 i_index;    \/\/ Index of the start element index, if using ELEMENTS\n};<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Apparently, <a href=\"https:\/\/realtimecollisiondetection.net\/blog\/?p=86\">this is much closer to how modern game engines structure their render commands<\/a>. There is one vertex buffer that we write into as commands are added by the game, and the command just keeps track of the appropriate range. The same is true for the index buffer, which in our case is pre-populated buffer for quad rendering (so each quad takes 4 vertices and 6 indices instead of 6 vertices):<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"575\" height=\"163\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-39.png\" alt=\"\" class=\"wp-image-3208\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-39.png 575w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2026\/03\/2026-03-01_14-39-300x85.png 300w\" sizes=\"auto, (max-width: 575px) 100vw, 575px\" \/><\/figure>\n<\/div>\n\n\n<p><\/p>\n\n\n\n<p>The pass type, stencil requirement, and draw mode are then baked into a <em>sort key<\/em> that we can sort on in order to automagically get the commands in the order we want: opaque &lt; transparent &lt; UI, with no stencil &lt; write to stencil &lt; read from stencil, and then grouping by whether we use the elements (index) buffer or not. We even use the lower 32 bits for additional sorting, rendering opaque things front to back (since lighting is expensive and we can discard more pixels that way), rendering transparent things back to front (required to get correct results), and rendering UI elements in the order they are created.<\/p>\n\n\n\n<p>Being able to call <code>glBufferData<\/code> once to send all of the vertices up once at the beginning is really nice. Before, we were writing vertices and sending just the few we needed for every command type each time.<\/p>\n\n\n\n<p>The end result is significantly simpler backend rendering code. It can become even simpler if we eventually unify the mesh rendering with the unlit triangle rendering, but I&#8217;m holding off on that because those render commands have a bunch of uniform data and then I&#8217;m switching between shaders. I&#8217;m not currently rendering transparent meshes, and if I eventually do I&#8217;ll bite the bullet and further consolidate.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p>I am quite happy to have tackled the fundamental grid representation, which as we saw ended up affecting a lot of stuff. The new grid view now needs to be properly integrated into the movement action, and then we can actually start introducing other entities like ropes, buckets and relics. No promises on specifics! We&#8217;ll let the project dictate how it should evolve.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A debug view showing shadow cast segments from the vantage point of the actor. Last month we covered the unified action system, in which we built some basic things players can do with their heroes, and set the stage for fancier futures and actions for enemy agents. The most significant action was moving, which was [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13],"tags":[14,10],"class_list":["post-3188","post","type-post","status-publish","format-standard","hentry","category-sidescroller","tag-into_depth","tag-sidescroller"],"_links":{"self":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3188","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/comments?post=3188"}],"version-history":[{"count":8,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3188\/revisions"}],"predecessor-version":[{"id":3216,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3188\/revisions\/3216"}],"wp:attachment":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/media?parent=3188"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/categories?post=3188"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/tags?post=3188"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}